]>
Commit | Line | Data |
---|---|---|
9cb2f0c0 MT |
1 | /*############################################################################# |
2 | # # | |
3 | # IPFire.org - A linux based firewall # | |
4 | # Copyright (C) 2018 IPFire Network Development Team # | |
5 | # # | |
6 | # This program is free software: you can redistribute it and/or modify # | |
7 | # it under the terms of the GNU General Public License as published by # | |
8 | # the Free Software Foundation, either version 3 of the License, or # | |
9 | # (at your option) any later version. # | |
10 | # # | |
11 | # This program is distributed in the hope that it will be useful, # | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
14 | # GNU General Public License for more details. # | |
15 | # # | |
16 | # You should have received a copy of the GNU General Public License # | |
17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
18 | # # | |
19 | #############################################################################*/ | |
20 | ||
21 | #include <errno.h> | |
e145b2f3 MT |
22 | #include <linux/nl80211.h> |
23 | #include <netlink/attr.h> | |
24 | #include <netlink/genl/genl.h> | |
25 | #include <netlink/msg.h> | |
26 | #include <stdint.h> | |
9cb2f0c0 MT |
27 | #include <stdio.h> |
28 | #include <stdlib.h> | |
29 | ||
30 | #include <network/libnetwork.h> | |
31 | #include <network/logging.h> | |
e145b2f3 | 32 | #include <network/phy.h> |
9cb2f0c0 MT |
33 | #include "libnetwork-private.h" |
34 | ||
35 | struct network_phy { | |
36 | struct network_ctx* ctx; | |
37 | int refcount; | |
38 | ||
39 | int index; | |
40 | char* name; | |
e145b2f3 MT |
41 | |
42 | unsigned int ht_caps; | |
9cb2f0c0 MT |
43 | }; |
44 | ||
45 | static int phy_get_index(const char* name) { | |
46 | char path[1024]; | |
47 | char index[8]; | |
48 | ||
49 | snprintf(path, sizeof(path), "/sys/class/ieee80211/%s/index", name); | |
50 | ||
51 | FILE* f = fopen(path, "r"); | |
52 | if (!f) | |
53 | return -1; | |
54 | ||
55 | int p = fread(index, 1, sizeof(index), f); | |
56 | if (p < 0) { | |
57 | fclose(f); | |
58 | return -1; | |
59 | } | |
60 | ||
61 | // Terminate buffer | |
62 | index[p] = '\0'; | |
63 | fclose(f); | |
64 | ||
65 | return atoi(index); | |
66 | } | |
67 | ||
e145b2f3 MT |
68 | static void phy_parse_ht_capabilities(struct network_phy* phy, __u16 caps) { |
69 | // RX LDCP | |
70 | if (caps & BIT(0)) | |
71 | phy->ht_caps |= NETWORK_PHY_HT_CAP_RX_LDCP; | |
72 | ||
73 | // HT40 | |
74 | if (caps & BIT(1)) | |
75 | phy->ht_caps |= NETWORK_PHY_HT_CAP_HT40; | |
76 | ||
77 | // Static/Dynamic SM Power Save | |
78 | switch ((caps >> 2) & 0x3) { | |
79 | case 0: | |
80 | phy->ht_caps |= NETWORK_PHY_HT_CAP_SMPS_STATIC; | |
81 | break; | |
82 | ||
83 | case 1: | |
84 | phy->ht_caps |= NETWORK_PHY_HT_CAP_SMPS_DYNAMIC; | |
85 | break; | |
86 | ||
87 | default: | |
88 | break; | |
89 | } | |
90 | ||
91 | // RX Greenfield | |
92 | if (caps & BIT(4)) | |
93 | phy->ht_caps |= NETWORK_PHY_HT_CAP_RX_GF; | |
94 | ||
95 | // RX HT20 Short GI | |
96 | if (caps & BIT(5)) | |
97 | phy->ht_caps |= NETWORK_PHY_HT_CAP_RX_HT20_SGI; | |
98 | ||
99 | // RX HT40 Short GI | |
100 | if (caps & BIT(6)) | |
101 | phy->ht_caps |= NETWORK_PHY_HT_CAP_RX_HT40_SGI; | |
102 | ||
103 | // TX STBC | |
104 | if (caps & BIT(7)) | |
105 | phy->ht_caps |= NETWORK_PHY_HT_CAP_TX_STBC; | |
106 | ||
107 | // RX STBC | |
108 | switch ((caps >> 8) & 0x3) { | |
109 | case 1: | |
110 | phy->ht_caps |= NETWORK_PHY_HT_CAP_RX_STBC1; | |
111 | break; | |
112 | ||
113 | case 2: | |
114 | phy->ht_caps |= NETWORK_PHY_HT_CAP_RX_STBC2; | |
115 | break; | |
116 | ||
117 | case 3: | |
118 | phy->ht_caps |= NETWORK_PHY_HT_CAP_RX_STBC3; | |
119 | break; | |
120 | ||
121 | default: | |
122 | break; | |
123 | } | |
124 | ||
125 | // HT Delayed Block ACK | |
126 | if (caps & BIT(10)) | |
127 | phy->ht_caps |= NETWORK_PHY_HT_CAP_DELAYED_BA; | |
128 | ||
129 | // Max AMSDU length 7935 | |
130 | if (caps & BIT(11)) | |
131 | phy->ht_caps |= NETWORK_PHY_HT_CAP_MAX_AMSDU_7935; | |
132 | ||
133 | // DSSS/CCK HT40 | |
134 | if (caps & BIT(12)) | |
135 | phy->ht_caps |= NETWORK_PHY_HT_CAP_DSSS_CCK_HT40; | |
136 | ||
137 | // Bit 13 is reserved | |
138 | ||
139 | // HT40 Intolerant | |
140 | if (caps & BIT(14)) | |
141 | phy->ht_caps |= NETWORK_PHY_HT_CAP_HT40_INTOLERANT; | |
142 | ||
143 | // L-SIG TXOP protection | |
144 | if (caps & BIT(15)) | |
145 | phy->ht_caps |= NETWORK_PHY_HT_CAP_LSIG_TXOP_PROT; | |
146 | } | |
147 | ||
148 | static int phy_parse_info(struct nl_msg* msg, void* data) { | |
149 | struct network_phy* phy = data; | |
150 | ||
151 | struct nlattr* attrs[NL80211_ATTR_MAX + 1]; | |
152 | struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); | |
153 | ||
154 | nla_parse(attrs, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), | |
155 | genlmsg_attrlen(gnlh, 0), NULL); | |
156 | ||
157 | if (attrs[NL80211_ATTR_WIPHY_BANDS]) { | |
158 | struct nlattr* nl_band; | |
159 | int i; | |
160 | ||
161 | nla_for_each_nested(nl_band, attrs[NL80211_ATTR_WIPHY_BANDS], i) { | |
162 | struct nlattr* band_attrs[NL80211_BAND_ATTR_MAX + 1]; | |
163 | nla_parse(band_attrs, NL80211_BAND_ATTR_MAX, nla_data(nl_band), | |
164 | nla_len(nl_band), NULL); | |
165 | ||
166 | // HT Capabilities | |
167 | if (band_attrs[NL80211_BAND_ATTR_HT_CAPA]) { | |
168 | __u16 caps = nla_get_u16(band_attrs[NL80211_BAND_ATTR_HT_CAPA]); | |
169 | phy_parse_ht_capabilities(phy, caps); | |
170 | } | |
171 | } | |
172 | } | |
173 | ||
174 | return NL_OK; | |
175 | } | |
176 | ||
177 | static int phy_get_info(struct network_phy* phy) { | |
178 | DEBUG(phy->ctx, "Getting information for %s\n", phy->name); | |
179 | ||
180 | struct nl_msg* msg = network_phy_make_netlink_message(phy, NL80211_CMD_GET_WIPHY, 0); | |
181 | if (!msg) | |
182 | return -1; | |
183 | ||
984a1852 MT |
184 | int r = network_send_netlink_message(phy->ctx, msg, phy_parse_info, phy); |
185 | ||
186 | // This is fine since some devices are not supported by NL80211 | |
187 | if (r == -ENODEV) { | |
188 | DEBUG(phy->ctx, "Could not fetch information from kernel\n"); | |
189 | return 0; | |
190 | } | |
191 | ||
192 | return r; | |
e145b2f3 MT |
193 | } |
194 | ||
195 | static void network_phy_free(struct network_phy* phy) { | |
196 | DEBUG(phy->ctx, "Releasing phy at %p\n", phy); | |
197 | ||
198 | if (phy->name) | |
199 | free(phy->name); | |
200 | ||
201 | network_unref(phy->ctx); | |
202 | free(phy); | |
203 | } | |
204 | ||
9cb2f0c0 MT |
205 | NETWORK_EXPORT int network_phy_new(struct network_ctx* ctx, struct network_phy** phy, |
206 | const char* name) { | |
207 | if (!name) | |
208 | return -EINVAL; | |
209 | ||
210 | int index = phy_get_index(name); | |
211 | if (index < 0) | |
212 | return -ENODEV; | |
213 | ||
214 | struct network_phy* p = calloc(1, sizeof(*p)); | |
215 | if (!p) | |
216 | return -ENOMEM; | |
217 | ||
218 | // Initalise object | |
219 | p->ctx = network_ref(ctx); | |
220 | p->refcount = 1; | |
221 | ||
e145b2f3 MT |
222 | p->name = strdup(name); |
223 | ||
224 | // Load information from kernel | |
225 | int r = phy_get_info(p); | |
226 | if (r) { | |
227 | ERROR(p->ctx, "Error getting PHY information from kernel\n"); | |
228 | network_phy_free(p); | |
229 | ||
230 | return r; | |
231 | } | |
232 | ||
9cb2f0c0 MT |
233 | DEBUG(p->ctx, "Allocated phy at %p\n", p); |
234 | *phy = p; | |
235 | return 0; | |
236 | } | |
237 | ||
238 | NETWORK_EXPORT struct network_phy* network_phy_ref(struct network_phy* phy) { | |
239 | if (!phy) | |
240 | return NULL; | |
241 | ||
242 | phy->refcount++; | |
243 | return phy; | |
244 | } | |
245 | ||
9cb2f0c0 MT |
246 | NETWORK_EXPORT struct network_phy* network_phy_unref(struct network_phy* phy) { |
247 | if (!phy) | |
248 | return NULL; | |
249 | ||
250 | if (--phy->refcount > 0) | |
251 | return phy; | |
252 | ||
253 | network_phy_free(phy); | |
254 | return NULL; | |
255 | } | |
e145b2f3 MT |
256 | |
257 | struct nl_msg* network_phy_make_netlink_message(struct network_phy* phy, | |
258 | enum nl80211_commands cmd, int flags) { | |
259 | struct nl_msg* msg = network_make_netlink_message(phy->ctx, cmd, flags); | |
260 | if (!msg) | |
261 | return NULL; | |
262 | ||
263 | // Set PHY index | |
264 | NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, phy->index); | |
265 | ||
266 | return msg; | |
267 | ||
268 | nla_put_failure: | |
269 | nlmsg_free(msg); | |
270 | ||
271 | return NULL; | |
272 | } | |
273 | ||
274 | NETWORK_EXPORT int network_phy_has_ht_capability(struct network_phy* phy, const enum network_phy_ht_caps cap) { | |
275 | return phy->ht_caps & cap; | |
276 | } | |
277 | ||
278 | static const char* network_phy_get_ht_capability_string(const enum network_phy_ht_caps cap) { | |
279 | switch (cap) { | |
280 | case NETWORK_PHY_HT_CAP_RX_LDCP: | |
281 | return "[LDPC]"; | |
282 | ||
283 | case NETWORK_PHY_HT_CAP_HT40: | |
284 | return "[HT40+][HT40-]"; | |
285 | ||
286 | case NETWORK_PHY_HT_CAP_SMPS_STATIC: | |
287 | return "[SMPS-STATIC]"; | |
288 | ||
289 | case NETWORK_PHY_HT_CAP_SMPS_DYNAMIC: | |
290 | return "[SMPS-DYNAMIC]"; | |
291 | ||
292 | case NETWORK_PHY_HT_CAP_RX_GF: | |
293 | return "[GF]"; | |
294 | ||
295 | case NETWORK_PHY_HT_CAP_RX_HT20_SGI: | |
296 | return "[SHORT-GI-20]"; | |
297 | ||
298 | case NETWORK_PHY_HT_CAP_RX_HT40_SGI: | |
299 | return "[SHORT-GI-40]"; | |
300 | ||
301 | case NETWORK_PHY_HT_CAP_TX_STBC: | |
302 | return "[TX-STBC]"; | |
303 | ||
304 | case NETWORK_PHY_HT_CAP_RX_STBC1: | |
305 | return "[RX-STBC1]"; | |
306 | ||
307 | case NETWORK_PHY_HT_CAP_RX_STBC2: | |
308 | return "[RX-STBC12]"; | |
309 | ||
310 | case NETWORK_PHY_HT_CAP_RX_STBC3: | |
311 | return "[RX-STBC123]"; | |
312 | ||
313 | case NETWORK_PHY_HT_CAP_DELAYED_BA: | |
314 | return "[DELAYED-BA]"; | |
315 | ||
316 | case NETWORK_PHY_HT_CAP_MAX_AMSDU_7935: | |
317 | return "[MAX-AMSDU-7935]"; | |
318 | ||
319 | case NETWORK_PHY_HT_CAP_DSSS_CCK_HT40: | |
320 | return "[DSSS_CCK-40]"; | |
321 | ||
322 | case NETWORK_PHY_HT_CAP_HT40_INTOLERANT: | |
323 | return "[40-INTOLERANT]"; | |
324 | ||
325 | case NETWORK_PHY_HT_CAP_LSIG_TXOP_PROT: | |
326 | return "[LSIG-TXOP-PROT]"; | |
327 | } | |
328 | ||
329 | return NULL; | |
330 | } | |
331 | ||
332 | NETWORK_EXPORT char* network_phy_list_ht_capabilities(struct network_phy* phy) { | |
333 | char* buffer = malloc(1024); | |
334 | *buffer = '\0'; | |
335 | ||
336 | char* p = buffer; | |
337 | foreach_ht_cap(cap) { | |
338 | if (network_phy_has_ht_capability(phy, cap)) { | |
339 | const char* cap_str = network_phy_get_ht_capability_string(cap); | |
340 | ||
341 | if (cap_str) | |
342 | p = strncat(p, cap_str, 1024 - 1); | |
343 | } | |
344 | } | |
345 | ||
346 | return buffer; | |
347 | } |