i3 - improved tiling WM


Use nl80211 to retrieve wireless stats on Linux

Patch status: needinfo

Patch by Alexander Monakov

Long description:

To minimize the amount of syscalls done on each update, netlink socket is kept
open and some auxiliary information is cached.  Retrieving "%bitrate" requires
a second netlink request and is avoided when not requested for output.

v2: add "%quality" reporting based on signal strength; drop name->index lookup
cache when the interface is gone (thankfully netlink signals that with -ENODEV)

fixes: #1082

To apply this patch, use:
curl http://cr.i3wm.org/patch/692/raw.patch | git am

b/Makefile

22
@@ -26,7 +26,8 @@ OS:=$(shell uname)
23
 ifeq ($(OS),Linux)
24
 CPPFLAGS+=-DLINUX
25
 CPPFLAGS+=-D_GNU_SOURCE
26
-LIBS+=-liw
27
+CFLAGS += $(shell pkg-config --cflags libnl-genl-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,14 @@
37
 #include <yajl/yajl_version.h>
38
 
39
 #ifdef LINUX
40
-#include <iwlib.h>
41
-#else
42
-#ifndef __FreeBSD__
43
+#include <errno.h>
44
+#include <net/if.h>
45
+#include <netlink/netlink.h>
46
+#include <netlink/genl/genl.h>
47
+#include <netlink/genl/ctrl.h>
48
+#include <linux/nl80211.h>
49
 #define IW_ESSID_MAX_SIZE 32
50
 #endif
51
-#endif
52
 
53
 #ifdef __FreeBSD__
54
 #include <sys/param.h>
55
@@ -56,6 +58,7 @@
56
 #define WIRELESS_INFO_FLAG_HAS_SIGNAL                   (1 << 2)
57
 #define WIRELESS_INFO_FLAG_HAS_NOISE                    (1 << 3)
58
 #define WIRELESS_INFO_FLAG_HAS_FREQUENCY                (1 << 4)
59
+#define WIRELESS_INFO_FLAG_HAS_BITRATE                  (1 << 5)
60
 
61
 #define PERCENT_VALUE(value, total) ((int)(value * 100 / (float)total + 0.5f))
62
 
63
@@ -69,135 +72,178 @@ typedef struct {
64
         int signal_level_max;
65
         int noise_level;
66
         int noise_level_max;
67
-        int bitrate;
68
-        double frequency;
69
+        int frequency_mhz;
70
+        int bitrate_mbps;
71
 } wireless_info_t;
72
 
73
+typedef struct {
74
+        unsigned ifidx;
75
+        int skip_bitrate;
76
+} per_instance_t;
77
+
78
+#ifdef LINUX
79
+static struct {
80
+        wireless_info_t *info;
81
+        int status;
82
+        char bssid[6];
83
+} gwi_data;
84
+
85
+static int gwi_finish_cb(struct nl_msg *msg, void *unused_arg) {
86
+        int err = *(int*)nlmsg_data(nlmsg_hdr(msg));
87
+        /* If the interface is gone, drop the name->index cache.  */
88
+        if (err == -ENODEV)
89
+                ((per_instance_t*)(*cur_instance))->ifidx = 0;
90
+        return NL_STOP;
91
+}
92
+
93
+static int gwi_scan_cb(struct nl_msg *msg, void *unused_arg) {
94
+        struct nlattr *nla_bss, *nla;
95
+        int rem;
96
+        struct genlmsghdr *gnlh = genlmsg_hdr(nlmsg_hdr(msg));
97
+        if (!(nla_bss = nla_find(genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NL80211_ATTR_BSS)))
98
+                return NL_SKIP;
99
+
100
+        unsigned char *ie_ptr = 0, *bssid = 0;
101
+        int frequency = 0, signal = 0, signal_max = 0, ie_len = 0, connected = 0;
102
+        nla_for_each_nested(nla, nla_bss, rem) {
103
+                int type = nla_type(nla);
104
+                if (type == NL80211_BSS_BSSID) {
105
+                        bssid = nla_data(nla);
106
+                } else if (type == NL80211_BSS_FREQUENCY) {
107
+                        frequency = nla_get_u32(nla);
108
+                } else if (type == NL80211_BSS_SIGNAL_UNSPEC) {
109
+                        signal = nla_get_u8(nla);
110
+                        signal_max = 100;
111
+                } else if (type == NL80211_BSS_SIGNAL_MBM) {
112
+                        signal = (int)nla_get_u32(nla) / 100;
113
+                } else if (type == NL80211_BSS_INFORMATION_ELEMENTS) {
114
+                        ie_ptr = nla_data(nla);
115
+                        ie_len = nla_len(nla);
116
+                } else if (type == NL80211_BSS_STATUS) {
117
+                        connected = 1;
118
+                }
119
+        }
120
+        if (!connected || !bssid)
121
+                return NL_SKIP;
122
+
123
+        wireless_info_t *info = gwi_data.info;
124
+        if (frequency) {
125
+                info->flags |= WIRELESS_INFO_FLAG_HAS_FREQUENCY;
126
+                info->frequency_mhz = frequency;
127
+        }
128
+        if (signal) {
129
+                info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
130
+                info->signal_level = signal;
131
+                info->signal_level_max = signal_max;
132
+                /* Also provide "quality" stats similar to wext backwards
133
+                 * compatibility layer in the Linux kernel. */
134
+                info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY;
135
+                if (!signal_max) {
136
+                        /* Clamp -110..-40 dBm signal range to 0..70 quality range. */
137
+                        signal_max = 70;
138
+                        signal += 110;
139
+                        signal = signal < 0 ? 0 : signal > signal_max ? signal_max : signal;
140
+                }
141
+                info->quality = signal;
142
+                info->quality_max = signal_max;
143
+                info->quality_average = signal_max / 2;
144
+        }
145
+        while (ie_len >= 2) {
146
+                int len = ie_ptr[1] + 2;
147
+                if (ie_len < len)
148
+                        break;
149
+                if (ie_ptr[0] == 0) {
150
+                        info->flags |= WIRELESS_INFO_FLAG_HAS_ESSID;
151
+                        snprintf(info->essid, sizeof(info->essid), "%.*s", len - 2, ie_ptr + 2);
152
+                        break;
153
+                }
154
+                ie_len -= len;
155
+                ie_ptr += len;
156
+        }
157
+        memcpy(gwi_data.bssid, bssid, 6);
158
+        gwi_data.status = 1;
159
+        return NL_SKIP;
160
+}
161
+
162
+static int gwi_sta_cb(struct nl_msg *msg, void *unused_arg) {
163
+        struct nlattr *nla_sta, *nla_rate, *nla_bitrate;
164
+        struct genlmsghdr *gnlh = genlmsg_hdr(nlmsg_hdr(msg));
165
+        if (!(nla_sta = nla_find(genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NL80211_ATTR_STA_INFO)))
166
+                return NL_SKIP;
167
+        if (!(nla_rate = nla_find(nla_data(nla_sta), nla_len(nla_sta), NL80211_STA_INFO_RX_BITRATE)))
168
+                return NL_SKIP;
169
+        if (!(nla_bitrate = nla_find(nla_data(nla_rate), nla_len(nla_rate), NL80211_RATE_INFO_BITRATE32)))
170
+                return NL_SKIP;
171
+        wireless_info_t *info = gwi_data.info;
172
+        info->flags |= WIRELESS_INFO_FLAG_HAS_BITRATE;
173
+        info->bitrate_mbps = (int)nla_get_u32(nla_bitrate) / 10;
174
+        return NL_SKIP;
175
+}
176
+
177
+static int get_wireless_info_nl80211(const char *interface, wireless_info_t *info) {
178
+        static struct nl_sock *sk;
179
+        if (!(sk || (sk = nl_socket_alloc())))
180
+                return 0;
181
+        static int connected;
182
+        if (!(connected || (connected = (genl_connect(sk) == 0))))
183
+                return 0;
184
+        if (nl_socket_modify_cb(sk, NL_CB_FINISH, NL_CB_CUSTOM, gwi_finish_cb, 0) < 0)
185
+                return 0;
186
+        if (nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, gwi_scan_cb, 0) < 0)
187
+                return 0;
188
+
189
+        static int family;
190
+        if (!(family || (family = genl_ctrl_resolve(sk, "nl80211"))))
191
+                return 0;
192
+
193
+        per_instance_t *p = (per_instance_t*)*cur_instance;
194
+
195
+        if (!(p->ifidx || (p->ifidx = if_nametoindex(interface))))
196
+                return 0;
197
+
198
+        struct nl_msg *msg;
199
+        if (!(msg = nlmsg_alloc()))
200
+                return 0;
201
+
202
+        if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, family, 0, NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0)
203
+            || nla_put_u32(msg, NL80211_ATTR_IFINDEX, p->ifidx) < 0) {
204
+                nlmsg_free(msg);
205
+                return 0;
206
+        }
207
+
208
+        gwi_data.info = info;
209
+        gwi_data.status = 0;
210
+        if (nl_send_sync(sk, msg) < 0)
211
+                return 0;
212
+        if (!gwi_data.status || p->skip_bitrate)
213
+                return gwi_data.status;
214
+        p->skip_bitrate = 1;
215
+
216
+        if (nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, gwi_sta_cb, 0) < 0)
217
+                return 0;
218
+        if (!(msg = nlmsg_alloc()))
219
+                return 0;
220
+
221
+        if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, family, 0, NLM_F_DUMP, NL80211_CMD_GET_STATION, 0)
222
+            || nla_put_u32(msg, NL80211_ATTR_IFINDEX, p->ifidx) < 0
223
+            || nla_put(msg, NL80211_ATTR_MAC, 6, gwi_data.bssid) < 0) {
224
+                nlmsg_free(msg);
225
+                return 0;
226
+        }
227
+        if (nl_send_sync(sk, msg) < 0)
228
+                return 0;
229
+
230
+        return 1;
231
+}
232
+#endif
233
+
234
 static int get_wireless_info(const char *interface, wireless_info_t *info) {
235
+        if (!*cur_instance)
236
+                *cur_instance = calloc(1, sizeof(per_instance_t));
237
         memset(info, 0, sizeof(wireless_info_t));
238
 
239
 #ifdef LINUX
240
-        int skfd = iw_sockets_open();
241
-        if (skfd < 0) {
242
-                perror("iw_sockets_open");
243
-                return 0;
244
-        }
245
-
246
-        wireless_config wcfg;
247
-        if (iw_get_basic_config(skfd, interface, &wcfg) < 0) {
248
-            close(skfd);
249
-            return 0;
250
-        }
251
-
252
-        if (wcfg.has_essid && wcfg.essid_on) {
253
-                info->flags |= WIRELESS_INFO_FLAG_HAS_ESSID;
254
-                strncpy(&info->essid[0], wcfg.essid, IW_ESSID_MAX_SIZE);
255
-                info->essid[IW_ESSID_MAX_SIZE] = '\0';
256
-        }
257
-
258
-        if (wcfg.has_freq) {
259
-                info->frequency = wcfg.freq;
260
-                info->flags |= WIRELESS_INFO_FLAG_HAS_FREQUENCY;
261
-        }
262
-
263
-        /* If the function iw_get_stats does not return proper stats, the
264
-           wifi is considered as down.
265
-           Since ad-hoc network does not have theses stats, we need to return
266
-           here for this mode. */
267
-        if (wcfg.mode == 1) {
268
-                close(skfd);
269
-                return 1;
270
-        }
271
-
272
-        /* Wireless quality is a relative value in a driver-specific range.
273
-           Signal and noise level can be either relative or absolute values
274
-           in dBm. Furthermore, noise and quality can be expressed directly
275
-           in dBm or in RCPI (802.11k), which we convert to dBm. When those
276
-           values are expressed directly in dBm, they range from -192 to 63,
277
-           and since the values are packed into 8 bits, we need to perform
278
-           8-bit arithmetic on them. Assume absolute values if everything
279
-           else fails (driver bug). */
280
-
281
-        iwrange range;
282
-        if (iw_get_range_info(skfd, interface, &range) < 0) {
283
-                close(skfd);
284
-                return 0;
285
-        }
286
-
287
-        iwstats stats;
288
-        if (iw_get_stats(skfd, interface, &stats, &range, 1) < 0) {
289
-                close(skfd);
290
-                return 0;
291
-        }
292
-
293
-        if (stats.qual.level != 0 || (stats.qual.updated & (IW_QUAL_DBM | IW_QUAL_RCPI))) {
294
-                if (!(stats.qual.updated & IW_QUAL_QUAL_INVALID)) {
295
-                        info->quality = stats.qual.qual;
296
-                        info->quality_max = range.max_qual.qual;
297
-                        info->quality_average = range.avg_qual.qual;
298
-                        info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY;
299
-                }
300
-
301
-                if (stats.qual.updated & IW_QUAL_RCPI) {
302
-                        if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
303
-                                info->signal_level = stats.qual.level / 2.0 - 110 + 0.5;
304
-                                info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
305
-                        }
306
-                        if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
307
-                                info->noise_level = stats.qual.noise / 2.0 - 110 + 0.5;
308
-                                info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
309
-                        }
310
-                }
311
-                else {
312
-                        if ((stats.qual.updated & IW_QUAL_DBM) || stats.qual.level > range.max_qual.level) {
313
-                                if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
314
-                                        info->signal_level = stats.qual.level;
315
-                                        if (info->signal_level > 63)
316
-                                                info->signal_level -= 256;
317
-                                        info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
318
-                                }
319
-                                if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
320
-                                        info->noise_level = stats.qual.noise;
321
-                                        if (info->noise_level > 63)
322
-                                                info->noise_level -= 256;
323
-                                        info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
324
-                                }
325
-                        }
326
-                        else {
327
-                                if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
328
-                                        info->signal_level = stats.qual.level;
329
-                                        info->signal_level_max = range.max_qual.level;
330
-                                        info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
331
-                                }
332
-                                if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
333
-                                        info->noise_level = stats.qual.noise;
334
-                                        info->noise_level_max = range.max_qual.noise;
335
-                                        info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
336
-                                }
337
-                        }
338
-                }
339
-        }
340
-        else {
341
-                if (!(stats.qual.updated & IW_QUAL_QUAL_INVALID)) {
342
-                        info->quality = stats.qual.qual;
343
-                        info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY;
344
-                }
345
-                if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
346
-                        info->quality = stats.qual.level;
347
-                        info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
348
-                }
349
-                if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
350
-                        info->quality = stats.qual.noise;
351
-                        info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
352
-                }
353
-        }
354
-
355
-        struct iwreq wrq;
356
-        if (iw_get_ext(skfd, interface, SIOCGIWRATE, &wrq) >= 0)
357
-                info->bitrate = wrq.u.bitrate.value;
358
-
359
-        close(skfd);
360
-        return 1;
361
+        return get_wireless_info_nl80211(interface, info);
362
 #endif
363
 #if defined(__FreeBSD__) || defined(__DragonFly__)
364
         int s, len, inwid;
365
@@ -404,27 +450,25 @@ void print_wireless_info(yajl_gen json_gen, char *buffer, const char *interface,
366
 
367
                 if (BEGINS_WITH(walk+1, "frequency")) {
368
                         if (info.flags & WIRELESS_INFO_FLAG_HAS_FREQUENCY)
369
-                                outwalk += sprintf(outwalk, "%1.1f GHz", info.frequency / 1e9);
370
+                                outwalk += sprintf(outwalk, "%1.1f GHz", info.frequency_mhz * 1e-3);
371
                         else
372
                                 *(outwalk++) = '?';
373
                         walk += strlen("frequency");
374
                 }
375
 
376
+                if (BEGINS_WITH(walk+1, "bitrate")) {
377
+                        ((per_instance_t*)(*cur_instance))->skip_bitrate = 0;
378
+                        if (info.flags & WIRELESS_INFO_FLAG_HAS_BITRATE)
379
+                                outwalk += sprintf(outwalk, "%d Mbps", info.bitrate_mbps);
380
+                        else
381
+                                *(outwalk++) = '?';
382
+                        walk += strlen("bitrate");
383
+                }
384
+
385
                 if (BEGINS_WITH(walk+1, "ip")) {
386
 			outwalk += sprintf(outwalk, "%s", ip_address);
387
 			walk += strlen("ip");
388
                 }
389
-
390
-#ifdef LINUX
391
-                if (BEGINS_WITH(walk+1, "bitrate")) {
392
-                        char br_buffer[128];
393
-
394
-                        iw_print_bitrate(br_buffer, sizeof(br_buffer), info.bitrate);
395
-
396
-                        outwalk += sprintf(outwalk, "%s", br_buffer);
397
-                        walk += strlen("bitrate");
398
-                }
399
-#endif
400
         }
401
 
402
 out: