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