Use nl80211 to retrieve wireless stats on Linux (#1082)
Patch status: needinfo
Patch by Alexander Monakov
Long description:
This initial cut does not support "bitrate", "quality" and "noise": to obtain those, an additional netlink request would be required. To minimize the amount of syscalls done on each update, netlink socket is kept open and some auxiliary information is cached. For now, interface id is cached in a naive way, which prevent running multiple instances of the plugin.
To apply this patch, use:
curl http://cr.i3wm.org/patch/685/raw.patch | git am
b/Makefile
20 |
@@ -26,7 +26,10 @@ OS:=$(shell uname) |
21 |
ifeq ($(OS),Linux) |
22 |
CPPFLAGS+=-DLINUX |
23 |
CPPFLAGS+=-D_GNU_SOURCE |
24 |
-LIBS+=-liw |
25 |
+CFLAGS += $(shell pkg-config --cflags libnl-3.0) |
26 |
+CFLAGS += $(shell pkg-config --cflags libnl-genl-3.0) |
27 |
+LIBS += $(shell pkg-config --libs libnl-3.0) |
28 |
+LIBS += $(shell pkg-config --libs libnl-genl-3.0) |
29 |
LIBS+=-lasound |
30 |
endif |
31 |
|
b/src/print_wireless_info.c
36 |
@@ -5,12 +5,13 @@ |
37 |
#include <yajl/yajl_version.h> |
38 |
|
39 |
#ifdef LINUX |
40 |
-#include <iwlib.h> |
41 |
-#else |
42 |
-#ifndef __FreeBSD__ |
43 |
+#include <net/if.h> |
44 |
+#include <netlink/netlink.h> |
45 |
+#include <netlink/genl/genl.h> |
46 |
+#include <netlink/genl/ctrl.h> |
47 |
+#include <linux/nl80211.h> |
48 |
#define IW_ESSID_MAX_SIZE 32 |
49 |
#endif |
50 |
-#endif |
51 |
|
52 |
#ifdef __FreeBSD__ |
53 |
#include <sys/param.h> |
54 |
@@ -69,135 +70,114 @@ typedef struct { |
55 |
int signal_level_max; |
56 |
int noise_level; |
57 |
int noise_level_max; |
58 |
- int bitrate; |
59 |
- double frequency; |
60 |
+ int frequency_mhz; |
61 |
} wireless_info_t; |
62 |
|
63 |
+#ifdef LINUX |
64 |
+static struct { |
65 |
+ wireless_info_t *info; |
66 |
+ int status; |
67 |
+} gwi_data; |
68 |
+ |
69 |
+static int gwi_scan_cb(struct nl_msg *msg, void *unused_arg) { |
70 |
+ struct nlattr *nla_bss, *nla; |
71 |
+ int rem; |
72 |
+ struct genlmsghdr *gnlh = genlmsg_hdr(nlmsg_hdr(msg)); |
73 |
+ if (!(nla_bss = nla_find(genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NL80211_ATTR_BSS))) |
74 |
+ return NL_SKIP; |
75 |
+ |
76 |
+ unsigned char *ie_ptr = 0; |
77 |
+ int frequency = 0, signal = 0, signal_max = 0, ie_len = 0, connected = 0; |
78 |
+ nla_for_each_nested(nla, nla_bss, rem) { |
79 |
+ int type = nla_type(nla); |
80 |
+ if (type == NL80211_BSS_FREQUENCY) { |
81 |
+ frequency = nla_get_u32(nla); |
82 |
+ } else if (type == NL80211_BSS_SIGNAL_UNSPEC) { |
83 |
+ signal = nla_get_u8(nla); |
84 |
+ signal_max = 100; |
85 |
+ } else if (type == NL80211_BSS_SIGNAL_MBM) { |
86 |
+ signal = (int)nla_get_u32(nla) / 100; |
87 |
+ } else if (type == NL80211_BSS_INFORMATION_ELEMENTS) { |
88 |
+ ie_ptr = nla_data(nla); |
89 |
+ ie_len = nla_len(nla); |
90 |
+ } else if (type == NL80211_BSS_STATUS) { |
91 |
+ connected = 1; |
92 |
+ } |
93 |
+ } |
94 |
+ if (!connected) |
95 |
+ return NL_SKIP; |
96 |
+ |
97 |
+ wireless_info_t *info = gwi_data.info; |
98 |
+ if (frequency) { |
99 |
+ info->flags |= WIRELESS_INFO_FLAG_HAS_FREQUENCY; |
100 |
+ info->frequency_mhz = frequency; |
101 |
+ } |
102 |
+ if (signal) { |
103 |
+ info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL; |
104 |
+ info->signal_level = signal; |
105 |
+ info->signal_level_max = signal_max; |
106 |
+ } |
107 |
+ while (ie_len >= 2) { |
108 |
+ int len = ie_ptr[1] + 2; |
109 |
+ if (ie_len < len) |
110 |
+ break; |
111 |
+ if (ie_ptr[0] == 0) { |
112 |
+ info->flags |= WIRELESS_INFO_FLAG_HAS_ESSID; |
113 |
+ snprintf(info->essid, sizeof(info->essid), "%.*s", len - 2, ie_ptr + 2); |
114 |
+ break; |
115 |
+ } |
116 |
+ ie_len -= len; |
117 |
+ ie_ptr += len; |
118 |
+ } |
119 |
+ gwi_data.status = 1; |
120 |
+ return NL_SKIP; |
121 |
+} |
122 |
+ |
123 |
+static int get_wireless_info_nl80211(const char *interface, wireless_info_t *info) { |
124 |
+ static struct nl_sock *sk; |
125 |
+ if (!sk) { |
126 |
+ if (!(sk = nl_socket_alloc())) |
127 |
+ return 0; |
128 |
+ if (genl_connect(sk) < 0 |
129 |
+ || nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, gwi_scan_cb, 0) < 0) { |
130 |
+ nl_socket_free(sk); |
131 |
+ sk = 0; |
132 |
+ return 0; |
133 |
+ } |
134 |
+ } |
135 |
+ |
136 |
+ static int family; |
137 |
+ if (!(family || (family = genl_ctrl_resolve(sk, "nl80211")))) |
138 |
+ return 0; |
139 |
+ |
140 |
+ /* For now, assume only one instance and cache ifidx */ |
141 |
+ static unsigned ifidx; |
142 |
+ if (!(ifidx || (ifidx = if_nametoindex(interface)))) |
143 |
+ return 0; |
144 |
+ |
145 |
+ struct nl_msg *msg; |
146 |
+ if (!(msg = nlmsg_alloc())) |
147 |
+ return 0; |
148 |
+ |
149 |
+ if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, family, 0, NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0) |
150 |
+ || nla_put_u32(msg, NL80211_ATTR_IFINDEX, ifidx) < 0) { |
151 |
+ nlmsg_free(msg); |
152 |
+ return 0; |
153 |
+ } |
154 |
+ |
155 |
+ gwi_data.info = info; |
156 |
+ gwi_data.status = 0; |
157 |
+ if (nl_send_sync(sk, msg) < 0) |
158 |
+ return 0; |
159 |
+ return gwi_data.status; |
160 |
+} |
161 |
+#endif |
162 |
+ |
163 |
static int get_wireless_info(const char *interface, wireless_info_t *info) { |
164 |
memset(info, 0, sizeof(wireless_info_t)); |
165 |
|
166 |
#ifdef LINUX |
167 |
- int skfd = iw_sockets_open(); |
168 |
- if (skfd < 0) { |
169 |
- perror("iw_sockets_open"); |
170 |
- return 0; |
171 |
- } |
172 |
- |
173 |
- wireless_config wcfg; |
174 |
- if (iw_get_basic_config(skfd, interface, &wcfg) < 0) { |
175 |
- close(skfd); |
176 |
- return 0; |
177 |
- } |
178 |
- |
179 |
- if (wcfg.has_essid && wcfg.essid_on) { |
180 |
- info->flags |= WIRELESS_INFO_FLAG_HAS_ESSID; |
181 |
- strncpy(&info->essid[0], wcfg.essid, IW_ESSID_MAX_SIZE); |
182 |
- info->essid[IW_ESSID_MAX_SIZE] = '\0'; |
183 |
- } |
184 |
- |
185 |
- if (wcfg.has_freq) { |
186 |
- info->frequency = wcfg.freq; |
187 |
- info->flags |= WIRELESS_INFO_FLAG_HAS_FREQUENCY; |
188 |
- } |
189 |
- |
190 |
- /* If the function iw_get_stats does not return proper stats, the |
191 |
- wifi is considered as down. |
192 |
- Since ad-hoc network does not have theses stats, we need to return |
193 |
- here for this mode. */ |
194 |
- if (wcfg.mode == 1) { |
195 |
- close(skfd); |
196 |
- return 1; |
197 |
- } |
198 |
- |
199 |
- /* Wireless quality is a relative value in a driver-specific range. |
200 |
- Signal and noise level can be either relative or absolute values |
201 |
- in dBm. Furthermore, noise and quality can be expressed directly |
202 |
- in dBm or in RCPI (802.11k), which we convert to dBm. When those |
203 |
- values are expressed directly in dBm, they range from -192 to 63, |
204 |
- and since the values are packed into 8 bits, we need to perform |
205 |
- 8-bit arithmetic on them. Assume absolute values if everything |
206 |
- else fails (driver bug). */ |
207 |
- |
208 |
- iwrange range; |
209 |
- if (iw_get_range_info(skfd, interface, &range) < 0) { |
210 |
- close(skfd); |
211 |
- return 0; |
212 |
- } |
213 |
- |
214 |
- iwstats stats; |
215 |
- if (iw_get_stats(skfd, interface, &stats, &range, 1) < 0) { |
216 |
- close(skfd); |
217 |
- return 0; |
218 |
- } |
219 |
- |
220 |
- if (stats.qual.level != 0 || (stats.qual.updated & (IW_QUAL_DBM | IW_QUAL_RCPI))) { |
221 |
- if (!(stats.qual.updated & IW_QUAL_QUAL_INVALID)) { |
222 |
- info->quality = stats.qual.qual; |
223 |
- info->quality_max = range.max_qual.qual; |
224 |
- info->quality_average = range.avg_qual.qual; |
225 |
- info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY; |
226 |
- } |
227 |
- |
228 |
- if (stats.qual.updated & IW_QUAL_RCPI) { |
229 |
- if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) { |
230 |
- info->signal_level = stats.qual.level / 2.0 - 110 + 0.5; |
231 |
- info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL; |
232 |
- } |
233 |
- if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) { |
234 |
- info->noise_level = stats.qual.noise / 2.0 - 110 + 0.5; |
235 |
- info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE; |
236 |
- } |
237 |
- } |
238 |
- else { |
239 |
- if ((stats.qual.updated & IW_QUAL_DBM) || stats.qual.level > range.max_qual.level) { |
240 |
- if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) { |
241 |
- info->signal_level = stats.qual.level; |
242 |
- if (info->signal_level > 63) |
243 |
- info->signal_level -= 256; |
244 |
- info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL; |
245 |
- } |
246 |
- if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) { |
247 |
- info->noise_level = stats.qual.noise; |
248 |
- if (info->noise_level > 63) |
249 |
- info->noise_level -= 256; |
250 |
- info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE; |
251 |
- } |
252 |
- } |
253 |
- else { |
254 |
- if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) { |
255 |
- info->signal_level = stats.qual.level; |
256 |
- info->signal_level_max = range.max_qual.level; |
257 |
- info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL; |
258 |
- } |
259 |
- if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) { |
260 |
- info->noise_level = stats.qual.noise; |
261 |
- info->noise_level_max = range.max_qual.noise; |
262 |
- info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE; |
263 |
- } |
264 |
- } |
265 |
- } |
266 |
- } |
267 |
- else { |
268 |
- if (!(stats.qual.updated & IW_QUAL_QUAL_INVALID)) { |
269 |
- info->quality = stats.qual.qual; |
270 |
- info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY; |
271 |
- } |
272 |
- if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) { |
273 |
- info->quality = stats.qual.level; |
274 |
- info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL; |
275 |
- } |
276 |
- if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) { |
277 |
- info->quality = stats.qual.noise; |
278 |
- info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE; |
279 |
- } |
280 |
- } |
281 |
- |
282 |
- struct iwreq wrq; |
283 |
- if (iw_get_ext(skfd, interface, SIOCGIWRATE, &wrq) >= 0) |
284 |
- info->bitrate = wrq.u.bitrate.value; |
285 |
- |
286 |
- close(skfd); |
287 |
- return 1; |
288 |
+ return get_wireless_info_nl80211(interface, info); |
289 |
#endif |
290 |
#if defined(__FreeBSD__) || defined(__DragonFly__) |
291 |
int s, len, inwid; |
292 |
@@ -404,7 +384,7 @@ void print_wireless_info(yajl_gen json_gen, char *buffer, const char *interface, |
293 |
|
294 |
if (BEGINS_WITH(walk+1, "frequency")) { |
295 |
if (info.flags & WIRELESS_INFO_FLAG_HAS_FREQUENCY) |
296 |
- outwalk += sprintf(outwalk, "%1.1f GHz", info.frequency / 1e9); |
297 |
+ outwalk += sprintf(outwalk, "%1.1f GHz", info.frequency_mhz * 1e-3); |
298 |
else |
299 |
*(outwalk++) = '?'; |
300 |
walk += strlen("frequency"); |
301 |
@@ -414,17 +394,6 @@ void print_wireless_info(yajl_gen json_gen, char *buffer, const char *interface, |
302 |
outwalk += sprintf(outwalk, "%s", ip_address); |
303 |
walk += strlen("ip"); |
304 |
} |
305 |
- |
306 |
-#ifdef LINUX |
307 |
- if (BEGINS_WITH(walk+1, "bitrate")) { |
308 |
- char br_buffer[128]; |
309 |
- |
310 |
- iw_print_bitrate(br_buffer, sizeof(br_buffer), info.bitrate); |
311 |
- |
312 |
- outwalk += sprintf(outwalk, "%s", br_buffer); |
313 |
- walk += strlen("bitrate"); |
314 |
- } |
315 |
-#endif |
316 |
} |
317 |
|
318 |
out: |