]> git.sur5r.net Git - i3/i3status/blob - src/print_wireless_info.c
Implement %essid and %signal for wireless on OpenBSD.
[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         if (get_wireless_info(interface, &info)) {
309                 walk = format_up;
310                 if (info.flags & WIRELESS_INFO_FLAG_HAS_QUALITY)
311                         START_COLOR((info.quality < info.quality_average ? "color_degraded" : "color_good"));
312         }
313         else {
314                 walk = format_down;
315                 START_COLOR("color_bad");
316         }
317
318         for (; *walk != '\0'; walk++) {
319                 if (*walk != '%') {
320                         *(outwalk++) = *walk;
321                         continue;
322                 }
323
324                 if (BEGINS_WITH(walk+1, "quality")) {
325                         if (info.flags & WIRELESS_INFO_FLAG_HAS_QUALITY) {
326                                 if (info.quality_max)
327                                         outwalk += sprintf(outwalk, "%03d%%", PERCENT_VALUE(info.quality, info.quality_max));
328                                 else
329                                         outwalk += sprintf(outwalk, "%d", info.quality);
330                         } else {
331                                 *(outwalk++) = '?';
332                         }
333                         walk += strlen("quality");
334                 }
335
336                 if (BEGINS_WITH(walk+1, "signal")) {
337                         if (info.flags & WIRELESS_INFO_FLAG_HAS_SIGNAL) {
338                                 if (info.signal_level_max)
339                                         outwalk += sprintf(outwalk, "%03d%%", PERCENT_VALUE(info.signal_level, info.signal_level_max));
340                                 else
341                                         outwalk += sprintf(outwalk, "%d dBm", info.signal_level);
342                         } else {
343                                 *(outwalk++) = '?';
344                         }
345                         walk += strlen("signal");
346                 }
347
348                 if (BEGINS_WITH(walk+1, "noise")) {
349                         if (info.flags & WIRELESS_INFO_FLAG_HAS_NOISE) {
350                                 if (info.noise_level_max)
351                                         outwalk += sprintf(outwalk, "%03d%%", PERCENT_VALUE(info.noise_level, info.noise_level_max));
352                                 else
353                                         outwalk += sprintf(outwalk, "%d dBm", info.noise_level);
354                         } else {
355                                 *(outwalk++) = '?';
356                         }
357                         walk += strlen("noise");
358                 }
359
360                 if (BEGINS_WITH(walk+1, "essid")) {
361                         if (info.flags & WIRELESS_INFO_FLAG_HAS_ESSID)
362                                 outwalk += sprintf(outwalk, "%s", info.essid);
363                         else
364                                 *(outwalk++) = '?';
365                         walk += strlen("essid");
366                 }
367
368                 if (BEGINS_WITH(walk+1, "ip")) {
369                         const char *ip_address = get_ip_addr(interface);
370                         outwalk += sprintf(outwalk, "%s", (ip_address ? ip_address : "no IP"));
371                         walk += strlen("ip");
372                 }
373
374 #ifdef LINUX
375                 if (BEGINS_WITH(walk+1, "bitrate")) {
376                         char br_buffer[128];
377
378                         iw_print_bitrate(br_buffer, sizeof(br_buffer), info.bitrate);
379
380                         outwalk += sprintf(outwalk, "%s", br_buffer);
381                         walk += strlen("bitrate");
382                 }
383 #endif
384         }
385
386         END_COLOR;
387         OUTPUT_FULL_TEXT(buffer);
388 }