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