i3 - improved tiling WM


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: