]> git.sur5r.net Git - i3/i3status/blob - src/print_wireless_info.c
wifi: properly display ad-hoc networks
[i3/i3status] / src / print_wireless_info.c
1 // vim:ts=8:expandtab
2 #include <stdio.h>
3 #include <string.h>
4 #include <yajl/yajl_gen.h>
5 #include <yajl/yajl_version.h>
6
7 #ifdef LINUX
8 #include <iwlib.h>
9 #else
10 #ifndef __FreeBSD__
11 #define IW_ESSID_MAX_SIZE 32
12 #endif
13 #endif
14
15 #ifdef __FreeBSD__
16 #include <sys/param.h>
17 #include <sys/ioctl.h>
18 #include <sys/socket.h>
19 #include <ifaddrs.h>
20 #include <net/if.h>
21 #include <net/if_media.h>
22 #include <net80211/ieee80211.h>
23 #include <net80211/ieee80211_ioctl.h>
24 #include <unistd.h>
25 #define IW_ESSID_MAX_SIZE IEEE80211_NWID_LEN
26 #endif
27
28 #ifdef __DragonFly__
29 #include <sys/param.h>
30 #include <sys/ioctl.h>
31 #include <sys/socket.h>
32 #include <ifaddrs.h>
33 #include <net/if.h>
34 #include <net/if_media.h>
35 #include <netproto/802_11/ieee80211.h>
36 #include <netproto/802_11/ieee80211_ioctl.h>
37 #include <unistd.h>
38 #define IW_ESSID_MAX_SIZE IEEE80211_NWID_LEN
39 #endif
40
41 #ifdef __OpenBSD__
42 #include <sys/ioctl.h>
43 #include <sys/socket.h>
44 #include <net/if.h>
45 #include <sys/types.h>
46 #include <netinet/in.h>
47 #include <netinet/if_ether.h>
48 #include <net80211/ieee80211.h>
49 #include <net80211/ieee80211_ioctl.h>
50 #endif
51
52 #include "i3status.h"
53
54 #define WIRELESS_INFO_FLAG_HAS_ESSID                    (1 << 0)
55 #define WIRELESS_INFO_FLAG_HAS_QUALITY                  (1 << 1)
56 #define WIRELESS_INFO_FLAG_HAS_SIGNAL                   (1 << 2)
57 #define WIRELESS_INFO_FLAG_HAS_NOISE                    (1 << 3)
58
59 #define PERCENT_VALUE(value, total) ((int)(value * 100 / (float)total + 0.5f))
60
61 typedef struct {
62         int flags;
63         char essid[IW_ESSID_MAX_SIZE + 1];
64         int quality;
65         int quality_max;
66         int quality_average;
67         int signal_level;
68         int signal_level_max;
69         int noise_level;
70         int noise_level_max;
71         int bitrate;
72 } wireless_info_t;
73
74 static int get_wireless_info(const char *interface, wireless_info_t *info) {
75         memset(info, 0, sizeof(wireless_info_t));
76
77 #ifdef LINUX
78         int skfd = iw_sockets_open();
79         if (skfd < 0) {
80                 perror("iw_sockets_open");
81                 return 0;
82         }
83
84         wireless_config wcfg;
85         if (iw_get_basic_config(skfd, interface, &wcfg) < 0) {
86             close(skfd);
87             return 0;
88         }
89
90         if (wcfg.has_essid && wcfg.essid_on) {
91                 info->flags |= WIRELESS_INFO_FLAG_HAS_ESSID;
92                 strncpy(&info->essid[0], wcfg.essid, IW_ESSID_MAX_SIZE);
93                 info->essid[IW_ESSID_MAX_SIZE] = '\0';
94         }
95
96         /* If the function iw_get_stats does not return proper stats, the
97            wifi is considered as down.
98            Since ad-hoc network does not have theses stats, we need to return
99            here for this mode. */
100         if (wcfg.mode == 1) {
101                 close(skfd);
102                 return 1;
103         }
104
105         /* Wireless quality is a relative value in a driver-specific range.
106            Signal and noise level can be either relative or absolute values
107            in dBm. Furthermore, noise and quality can be expressed directly
108            in dBm or in RCPI (802.11k), which we convert to dBm. When those
109            values are expressed directly in dBm, they range from -192 to 63,
110            and since the values are packed into 8 bits, we need to perform
111            8-bit arithmetic on them. Assume absolute values if everything
112            else fails (driver bug). */
113
114         iwrange range;
115         if (iw_get_range_info(skfd, interface, &range) < 0) {
116                 close(skfd);
117                 return 0;
118         }
119
120         iwstats stats;
121         if (iw_get_stats(skfd, interface, &stats, &range, 1) < 0) {
122                 close(skfd);
123                 return 0;
124         }
125
126         if (stats.qual.level != 0 || (stats.qual.updated & (IW_QUAL_DBM | IW_QUAL_RCPI))) {
127                 if (!(stats.qual.updated & IW_QUAL_QUAL_INVALID)) {
128                         info->quality = stats.qual.qual;
129                         info->quality_max = range.max_qual.qual;
130                         info->quality_average = range.avg_qual.qual;
131                         info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY;
132                 }
133
134                 if (stats.qual.updated & IW_QUAL_RCPI) {
135                         if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
136                                 info->signal_level = stats.qual.level / 2.0 - 110 + 0.5;
137                                 info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
138                         }
139                         if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
140                                 info->noise_level = stats.qual.noise / 2.0 - 110 + 0.5;
141                                 info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
142                         }
143                 }
144                 else {
145                         if ((stats.qual.updated & IW_QUAL_DBM) || stats.qual.level > range.max_qual.level) {
146                                 if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
147                                         info->signal_level = stats.qual.level;
148                                         if (info->signal_level > 63)
149                                                 info->signal_level -= 256;
150                                         info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
151                                 }
152                                 if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
153                                         info->noise_level = stats.qual.noise;
154                                         if (info->noise_level > 63)
155                                                 info->noise_level -= 256;
156                                         info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
157                                 }
158                         }
159                         else {
160                                 if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
161                                         info->signal_level = stats.qual.level;
162                                         info->signal_level_max = range.max_qual.level;
163                                         info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
164                                 }
165                                 if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
166                                         info->noise_level = stats.qual.noise;
167                                         info->noise_level_max = range.max_qual.noise;
168                                         info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
169                                 }
170                         }
171                 }
172         }
173         else {
174                 if (!(stats.qual.updated & IW_QUAL_QUAL_INVALID)) {
175                         info->quality = stats.qual.qual;
176                         info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY;
177                 }
178                 if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
179                         info->quality = stats.qual.level;
180                         info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
181                 }
182                 if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
183                         info->quality = stats.qual.noise;
184                         info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
185                 }
186         }
187
188         struct iwreq wrq;
189         if (iw_get_ext(skfd, interface, SIOCGIWRATE, &wrq) >= 0)
190                 info->bitrate = wrq.u.bitrate.value;
191
192         close(skfd);
193         return 1;
194 #endif
195 #if defined(__FreeBSD__) || defined(__DragonFly__)
196         int s, len, inwid;
197         uint8_t buf[24 * 1024], *cp;
198         struct ieee80211req na;
199         char network_id[IEEE80211_NWID_LEN + 1];
200
201         if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
202                 return (0);
203
204         memset(&na, 0, sizeof(na));
205         strlcpy(na.i_name, interface, sizeof(na.i_name));
206         na.i_type = IEEE80211_IOC_SSID;
207         na.i_data = &info->essid[0];
208         na.i_len = IEEE80211_NWID_LEN + 1;
209         if ((inwid = ioctl(s, SIOCG80211, (caddr_t)&na)) == -1) {
210                 close(s);
211                 return (0);
212         }
213         if (inwid == 0) {
214                 if (na.i_len <= IEEE80211_NWID_LEN)
215                         len = na.i_len + 1;
216                 else
217                         len = IEEE80211_NWID_LEN + 1;
218                 info->essid[len -1] = '\0';
219         } else {
220                 close(s);
221                 return (0);
222         }
223         info->flags |= WIRELESS_INFO_FLAG_HAS_ESSID;
224
225         memset(&na, 0, sizeof(na));
226         strlcpy(na.i_name, interface, sizeof(na.i_name));
227         na.i_type = IEEE80211_IOC_SCAN_RESULTS;
228         na.i_data = buf;
229         na.i_len = sizeof(buf);
230
231         if (ioctl(s, SIOCG80211, (caddr_t)&na) == -1) {
232                 printf("fail\n");
233                 close(s);
234                 return (0);
235         }
236
237         close(s);
238         len = na.i_len;
239         cp = buf;
240         struct ieee80211req_scan_result *sr;
241         uint8_t *vp;
242         sr = (struct ieee80211req_scan_result *)cp;
243         vp = (u_int8_t *)(sr + 1);
244         strlcpy(network_id, (const char *)vp, sr->isr_ssid_len + 1);
245         if (!strcmp(network_id, &info->essid[0])) {
246                 info->signal_level = sr->isr_rssi;
247                 info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
248                 info->noise_level = sr->isr_noise;
249                 info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
250                 info->quality = sr->isr_intval;
251                 info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY;
252         }
253
254         return 1;
255 #endif
256 #ifdef __OpenBSD__
257         struct ifreq ifr;
258         struct ieee80211_bssid bssid;
259         struct ieee80211_nwid nwid;
260         struct ieee80211_nodereq nr;
261
262         struct ether_addr ea;
263
264         int s, len, ibssid, inwid;
265         u_int8_t zero_bssid[IEEE80211_ADDR_LEN];
266
267         if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
268                 return (0);
269
270         memset(&ifr, 0, sizeof(ifr));
271         ifr.ifr_data = (caddr_t)&nwid;
272         (void)strlcpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
273         inwid = ioctl(s, SIOCG80211NWID, (caddr_t)&ifr);
274
275         memset(&bssid, 0, sizeof(bssid));
276         strlcpy(bssid.i_name, interface, sizeof(bssid.i_name));
277         ibssid = ioctl(s, SIOCG80211BSSID, &bssid);
278
279         if (ibssid != 0 || inwid != 0) {
280                 close(s);
281                 return 0;
282         }
283
284         /* NWID */
285         {
286                 if (nwid.i_len <= IEEE80211_NWID_LEN)
287                         len = nwid.i_len + 1;
288                 else
289                         len = IEEE80211_NWID_LEN + 1;
290
291                 strncpy(&info->essid[0], nwid.i_nwid, len);
292                 info->essid[IW_ESSID_MAX_SIZE] = '\0';
293                 info->flags |= WIRELESS_INFO_FLAG_HAS_ESSID;
294         }
295
296         /* Signal strength */
297         {
298                 memset(&zero_bssid, 0, sizeof(zero_bssid));
299                 if (ibssid == 0 && memcmp(bssid.i_bssid, zero_bssid, IEEE80211_ADDR_LEN) != 0) {
300                         memcpy(&ea.ether_addr_octet, bssid.i_bssid, sizeof(ea.ether_addr_octet));
301
302                         bzero(&nr, sizeof(nr));
303                         bcopy(bssid.i_bssid, &nr.nr_macaddr, sizeof(nr.nr_macaddr));
304                         strlcpy(nr.nr_ifname, interface, sizeof(nr.nr_ifname));
305
306                         if (ioctl(s, SIOCG80211NODE, &nr) == 0 && nr.nr_rssi) {
307                                 if (nr.nr_max_rssi)
308                                         info->signal_level_max = IEEE80211_NODEREQ_RSSI(&nr);
309                                 else
310                                         info->signal_level = nr.nr_rssi;
311
312                                 info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
313                         }
314                 }
315         }
316
317         close(s);
318         return 1;
319 #endif
320         return 0;
321 }
322
323 void print_wireless_info(yajl_gen json_gen, char *buffer, const char *interface, const char *format_up, const char *format_down) {
324         const char *walk;
325         char *outwalk = buffer;
326         wireless_info_t info;
327
328         INSTANCE(interface);
329
330         const char *ip_address = get_ip_addr(interface);
331         if (ip_address == NULL) {
332                 START_COLOR("color_bad");
333                 outwalk += sprintf(outwalk, "%s", format_down);
334                 goto out;
335         } else {
336                 START_COLOR("color_good");
337         }
338
339         if (get_wireless_info(interface, &info)) {
340                 walk = format_up;
341                 if (info.flags & WIRELESS_INFO_FLAG_HAS_QUALITY)
342                         START_COLOR((info.quality < info.quality_average ? "color_degraded" : "color_good"));
343         }
344         else {
345                 walk = format_down;
346                 START_COLOR("color_bad");
347         }
348
349         for (; *walk != '\0'; walk++) {
350                 if (*walk != '%') {
351                         *(outwalk++) = *walk;
352                         continue;
353                 }
354
355                 if (BEGINS_WITH(walk+1, "quality")) {
356                         if (info.flags & WIRELESS_INFO_FLAG_HAS_QUALITY) {
357                                 if (info.quality_max)
358                                         outwalk += sprintf(outwalk, "%03d%%", PERCENT_VALUE(info.quality, info.quality_max));
359                                 else
360                                         outwalk += sprintf(outwalk, "%d", info.quality);
361                         } else {
362                                 *(outwalk++) = '?';
363                         }
364                         walk += strlen("quality");
365                 }
366
367                 if (BEGINS_WITH(walk+1, "signal")) {
368                         if (info.flags & WIRELESS_INFO_FLAG_HAS_SIGNAL) {
369                                 if (info.signal_level_max)
370                                         outwalk += sprintf(outwalk, "%03d%%", PERCENT_VALUE(info.signal_level, info.signal_level_max));
371                                 else
372                                         outwalk += sprintf(outwalk, "%d dBm", info.signal_level);
373                         } else {
374                                 *(outwalk++) = '?';
375                         }
376                         walk += strlen("signal");
377                 }
378
379                 if (BEGINS_WITH(walk+1, "noise")) {
380                         if (info.flags & WIRELESS_INFO_FLAG_HAS_NOISE) {
381                                 if (info.noise_level_max)
382                                         outwalk += sprintf(outwalk, "%03d%%", PERCENT_VALUE(info.noise_level, info.noise_level_max));
383                                 else
384                                         outwalk += sprintf(outwalk, "%d dBm", info.noise_level);
385                         } else {
386                                 *(outwalk++) = '?';
387                         }
388                         walk += strlen("noise");
389                 }
390
391                 if (BEGINS_WITH(walk+1, "essid")) {
392                         if (info.flags & WIRELESS_INFO_FLAG_HAS_ESSID)
393                                 outwalk += sprintf(outwalk, "%s", info.essid);
394                         else
395                                 *(outwalk++) = '?';
396                         walk += strlen("essid");
397                 }
398
399                 if (BEGINS_WITH(walk+1, "ip")) {
400                         outwalk += sprintf(outwalk, "%s", ip_address);
401                         walk += strlen("ip");
402                 }
403
404 #ifdef LINUX
405                 if (BEGINS_WITH(walk+1, "bitrate")) {
406                         char br_buffer[128];
407
408                         iw_print_bitrate(br_buffer, sizeof(br_buffer), info.bitrate);
409
410                         outwalk += sprintf(outwalk, "%s", br_buffer);
411                         walk += strlen("bitrate");
412                 }
413 #endif
414         }
415
416 out:
417         END_COLOR;
418         OUTPUT_FULL_TEXT(buffer);
419 }