]> git.sur5r.net Git - i3/i3status/blob - src/print_wireless_info.c
Fix Dragonfly BSD CPU temperature gauge
[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         }
336
337         if (get_wireless_info(interface, &info)) {
338                 walk = format_up;
339                 if (info.flags & WIRELESS_INFO_FLAG_HAS_QUALITY)
340                         START_COLOR((info.quality < info.quality_average ? "color_degraded" : "color_good"));
341                 else
342                         START_COLOR("color_good");
343         } else {
344                 walk = format_down;
345                 START_COLOR("color_bad");
346         }
347
348         for (; *walk != '\0'; walk++) {
349                 if (*walk != '%') {
350                         *(outwalk++) = *walk;
351                         continue;
352                 }
353
354                 if (BEGINS_WITH(walk+1, "quality")) {
355                         if (info.flags & WIRELESS_INFO_FLAG_HAS_QUALITY) {
356                                 if (info.quality_max)
357                                         outwalk += sprintf(outwalk, "%03d%%", PERCENT_VALUE(info.quality, info.quality_max));
358                                 else
359                                         outwalk += sprintf(outwalk, "%d", info.quality);
360                         } else {
361                                 *(outwalk++) = '?';
362                         }
363                         walk += strlen("quality");
364                 }
365
366                 if (BEGINS_WITH(walk+1, "signal")) {
367                         if (info.flags & WIRELESS_INFO_FLAG_HAS_SIGNAL) {
368                                 if (info.signal_level_max)
369                                         outwalk += sprintf(outwalk, "%03d%%", PERCENT_VALUE(info.signal_level, info.signal_level_max));
370                                 else
371                                         outwalk += sprintf(outwalk, "%d dBm", info.signal_level);
372                         } else {
373                                 *(outwalk++) = '?';
374                         }
375                         walk += strlen("signal");
376                 }
377
378                 if (BEGINS_WITH(walk+1, "noise")) {
379                         if (info.flags & WIRELESS_INFO_FLAG_HAS_NOISE) {
380                                 if (info.noise_level_max)
381                                         outwalk += sprintf(outwalk, "%03d%%", PERCENT_VALUE(info.noise_level, info.noise_level_max));
382                                 else
383                                         outwalk += sprintf(outwalk, "%d dBm", info.noise_level);
384                         } else {
385                                 *(outwalk++) = '?';
386                         }
387                         walk += strlen("noise");
388                 }
389
390                 if (BEGINS_WITH(walk+1, "essid")) {
391                         if (info.flags & WIRELESS_INFO_FLAG_HAS_ESSID)
392                                 outwalk += sprintf(outwalk, "%s", info.essid);
393                         else
394                                 *(outwalk++) = '?';
395                         walk += strlen("essid");
396                 }
397
398                 if (BEGINS_WITH(walk+1, "ip")) {
399                         outwalk += sprintf(outwalk, "%s", ip_address);
400                         walk += strlen("ip");
401                 }
402
403 #ifdef LINUX
404                 if (BEGINS_WITH(walk+1, "bitrate")) {
405                         char br_buffer[128];
406
407                         iw_print_bitrate(br_buffer, sizeof(br_buffer), info.bitrate);
408
409                         outwalk += sprintf(outwalk, "%s", br_buffer);
410                         walk += strlen("bitrate");
411                 }
412 #endif
413         }
414
415 out:
416         END_COLOR;
417         OUTPUT_FULL_TEXT(buffer);
418 }