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