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.

fixes: #1082

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

b/Makefile

19
@@ -26,7 +26,8 @@ OS:=$(shell uname)
20
 ifeq ($(OS),Linux)
21
 CPPFLAGS+=-DLINUX
22
 CPPFLAGS+=-D_GNU_SOURCE
23
-LIBS+=-liw
24
+CFLAGS += $(shell pkg-config --cflags libnl-genl-3.0)
25
+LIBS += $(shell pkg-config --libs libnl-genl-3.0)
26
 LIBS+=-lasound
27
 endif
28
 

b/src/print_wireless_info.c

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