channel-switch: use sys/ioctl.h instead of stropts.h
[rofl0r-MacGeiger.git] / macgeiger.c
blob8ccbec3d6681a9531afe9cb743263b80a51cf677
2 /*
3 MacGeiger WIFI AP detector
4 Copyright (C) 2014 rofl0r
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include <pcap/pcap.h>
21 #include <stdio.h>
22 #include <signal.h>
23 #include <assert.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <arpa/inet.h>
27 #include <pthread.h>
28 #include <ctype.h>
29 #include <fcntl.h>
31 #define GUI_FPS 40
33 #include "audio-backend.c"
35 #include "pcapfile.h"
37 #define LIBRARY_CODE
38 #include "channel-switch.c"
40 #ifndef MIN
41 #define MIN(x, y) ((x) < (y) ? (x) : (y))
42 #endif
44 #ifndef MAX
45 #define MAX(x, y) ((x) > (y) ? (x) : (y))
46 #endif
48 #pragma RcB2 LINK "-lpcap" "-lpthread"
50 #include "../concol/console.h"
51 #include "../concol/console_keys.h"
52 #include "../concol/fonts/allfonts.h"
54 #ifdef NO_COLOR
55 #define console_setcolor(A, B, C) do {} while(0)
56 #endif
58 static int outfd;
60 static int usage(const char *argv0) {
61 dprintf(2, "%s [-c channel] network-interface\n"
62 "i.e.: %s wlan0\n", argv0, argv0
64 return 1;
67 /* originally 256, but that would make the struct too big for the stack */
68 #define WPS_MAX_STR_LEN 64
69 struct wps_data
71 uint8_t version;
72 uint8_t state;
73 uint8_t locked;
74 char manufacturer[WPS_MAX_STR_LEN];
75 char model_name[WPS_MAX_STR_LEN];
76 char model_number[WPS_MAX_STR_LEN];
77 char device_name[WPS_MAX_STR_LEN];
78 char ssid[WPS_MAX_STR_LEN];
79 char uuid[WPS_MAX_STR_LEN];
80 char serial[WPS_MAX_STR_LEN];
81 char selected_registrar[WPS_MAX_STR_LEN];
82 char response_type[WPS_MAX_STR_LEN];
83 char primary_device_type[WPS_MAX_STR_LEN];
84 char config_methods[WPS_MAX_STR_LEN];
85 char rf_bands[WPS_MAX_STR_LEN];
86 char os_version[WPS_MAX_STR_LEN];
89 static void init_wps_data(struct wps_data* wps) {
90 wps->version = 0;
91 wps->state = 0;
92 wps->locked = 0;
93 wps->manufacturer[0] = 0;
94 wps->model_name[0] = 0;
95 wps->model_number[0] = 0;
96 wps->device_name[0] = 0;
97 wps->ssid[0] = 0;
98 wps->uuid[0] = 0;
99 wps->serial[0] = 0;
100 wps->selected_registrar[0] = 0;
101 wps->response_type[0] = 0;
102 wps->primary_device_type[0] = 0;
103 wps->config_methods[0] = 0;
104 wps->rf_bands[0] = 0;
105 wps->os_version[0] = 0;
108 enum enctype {
109 ET_OPEN = 0,
110 ET_WEP,
111 ET_WPA,
112 ET_WPA2,
113 ET_MAX = ET_WPA2
116 struct ap_client {
117 long long total_rssi;
118 long long last_seen;
119 unsigned long count;
120 signed char last_rssi;
121 signed char min_rssi;
122 signed char max_rssi;
123 unsigned char mac[6];
124 struct ap_client *next;
127 struct wlaninfo {
128 struct wps_data *wps;
129 long long total_rssi;
130 long long last_seen;
131 uint64_t timestamp;
132 unsigned long count;
133 uint16_t beaconinterval;
134 char essid[32];
135 unsigned char mac[6];
136 unsigned char channel;
137 signed char last_rssi;
138 signed char min_rssi;
139 signed char max_rssi;
140 char enctype;
141 struct ap_client *clients;
144 #define STATIC_ALLOC 1
145 #if STATIC_ALLOC
146 static struct wlaninfo wlans_storage[256];
147 static unsigned wlan_count;
148 #define DYNA_NEW(X) wlans_storage
149 #define DYNA_GROW(X) (wlan_count++, wlans)
150 #define DYNA_COUNT(X) wlan_count
151 #else
152 #include "../lib/include/dynarray.h"
153 #endif
155 static struct wlaninfo *wlans;
157 static pthread_mutex_t wlan_lock = PTHREAD_MUTEX_INITIALIZER;
158 #define lock() pthread_mutex_lock(&wlan_lock)
159 #define unlock() pthread_mutex_unlock(&wlan_lock)
161 static inline void get_wlan(size_t index, struct wlaninfo* out) {
162 lock();
163 memcpy(out, &wlans[index], sizeof(*out));
164 unlock();
167 static inline void write_wlan(size_t index, struct wlaninfo* in) {
168 lock();
169 memcpy(&wlans[index], in, sizeof(*in));
170 unlock();
174 static signed char min, max;
175 static unsigned char selection, selected;
176 static Console co, *t = &co;
177 static int colorcount;
179 static int get_wlan_by_essid(char* essid) {
180 unsigned i, l;
181 int res = -1;
182 lock();
183 l = DYNA_COUNT(wlans);
184 for(i=0;i<l;i++) {
185 if(!strcmp(essid, wlans[i].essid)) {
186 res = i;
187 break;
190 unlock();
191 return res;
194 static int get_wlan_by_mac(unsigned char mac[6]) {
195 unsigned i, l;
196 int res = -1;
197 lock();
198 l = DYNA_COUNT(wlans);
199 for(i=0;i<l;i++) {
200 if(!memcmp(mac, wlans[i].mac, 6)) {
201 res = i;
202 break;
205 unlock();
206 return res;
209 static int get_new_wlan(void) {
210 lock();
211 void* p = DYNA_GROW(wlans);
212 if(!p) {
213 unlock();
214 return -1;
216 wlans = p;
217 memset(&wlans[DYNA_COUNT(wlans)-1], 0, sizeof(wlans[0]));
218 wlans[DYNA_COUNT(wlans)-1].min_rssi = 127;
219 wlans[DYNA_COUNT(wlans)-1].max_rssi = -127;
220 int res = DYNA_COUNT(wlans)-1;
221 unlock();
222 return res;
225 static struct ap_client *get_client(struct wlaninfo *w, unsigned char mac[6]) {
226 struct ap_client *c;
227 for(c = w->clients; c; c = c->next) {
228 if(!memcmp(mac, c->mac, 6)) return c;
230 return 0;
233 static struct ap_client *add_client(struct wlaninfo *w, unsigned char mac[6]) {
234 struct ap_client *c = calloc(1, sizeof(*c)), *it;
235 memcpy(c->mac, mac, 6);
236 c->min_rssi = 127;
237 c->max_rssi = -127;
238 if(!w->clients) w->clients = c;
239 else {
240 it = w->clients;
241 while(it->next) it=it->next;
242 it->next = c;
244 return c;
247 static long long getutime64(void);
249 static void set_client_rssi(struct wlaninfo* w, struct ap_client *c) {
250 c->count++;
251 c->total_rssi += w->last_rssi;
252 c->last_seen = getutime64();
253 c->last_rssi = w->last_rssi;
254 c->min_rssi = MIN(c->min_rssi, c->last_rssi);
255 c->max_rssi = MAX(c->max_rssi, c->last_rssi);
258 static int set_rssi(struct wlaninfo *w, struct wps_data* wps) {
259 int i = -1;
260 struct wlaninfo wtmp, *d = &wtmp;
261 if(i == -1) i = get_wlan_by_mac(w->mac);
262 if(i == -1) i = get_new_wlan();
263 if(i != -1) {
264 get_wlan(i, d);
265 if(w->essid[0]) strcpy(d->essid, w->essid);
266 memcpy(d->mac, w->mac, 6);
267 d->total_rssi += w->last_rssi;
268 d->count++;
269 d->last_rssi = w->last_rssi;
270 d->channel = w->channel;
271 d->timestamp = w->timestamp;
272 d->beaconinterval = w->beaconinterval;
273 d->min_rssi = MIN(d->min_rssi, d->last_rssi);
274 d->max_rssi = MAX(d->max_rssi, d->last_rssi);
275 d->enctype = w->enctype;
276 if(wps->version) {
277 if(!d->wps) {
278 d->wps = malloc(sizeof *wps);
279 if(d->wps) init_wps_data(d->wps);
281 if(d->wps) {
282 if(!wps->manufacturer[0]) {
283 d->wps->version = wps->version;
284 d->wps->state = wps->state;
285 d->wps->locked = wps->locked;
286 } else
287 memcpy(d->wps, wps, sizeof(*wps));
290 write_wlan(i, d);
292 return i;
295 volatile int stop;
296 void sigh(int x) {
297 stop = 1;
300 #include "radiotap_flags.h"
302 static unsigned get_flags_off(unsigned flags, unsigned start_off) {
303 return rt_get_flag_offset(flags, IEEE80211_RADIOTAP_FLAGS, start_off);
306 static unsigned get_dbm_off(unsigned flags, unsigned start_off) {
307 return rt_get_flag_offset(flags, IEEE80211_RADIOTAP_DBM_ANTSIGNAL, start_off);
310 static unsigned get_chan_off(unsigned flags, unsigned start_off) {
311 return rt_get_flag_offset(flags, IEEE80211_RADIOTAP_CHANNEL, start_off);
314 static unsigned channel_from_freq(unsigned freq) {
315 return freq==2484?14:(freq-2407)/5;
318 struct dot11frame {
319 uint16_t framecontrol;
320 uint16_t duration;
321 unsigned char receiver[6];
322 unsigned char source[6];
323 unsigned char bssid[6];
324 uint16_t sequence_no;
327 static unsigned char* find_tag(unsigned const char *tagdata, unsigned tag, unsigned bytes_left) {
328 while(bytes_left) {
329 if(*tagdata == tag) return (unsigned char*)tagdata;
330 unsigned tagsize = tagdata[1];
331 tagdata+=2+tagsize;
332 if(bytes_left < 2+tagsize) return 0;
333 bytes_left-=2+tagsize;
335 return 0;
338 static long long timeval2utime(struct timeval*t) {
339 return (t->tv_sec * 1000LL * 1000LL) + t->tv_usec;
342 static long long getutime64(void) {
343 struct timeval t;
344 gettimeofday(&t, NULL);
345 return timeval2utime(&t);
349 static int filebased;
351 static const unsigned char* pcap_next_wrapper(pcap_t *foo, struct pcap_pkthdr *h_out) {
352 if(!filebased) {
353 again:;
354 const unsigned char* ret = 0;
355 struct pcap_pkthdr *hdr_temp;
356 int err = pcap_next_ex(foo, &hdr_temp, &ret);
357 if(err == 1) {
358 /* skip malformed packets, like those emitted by busybox udhcpc */
359 struct ieee80211_radiotap_header *rh = (void*) ret;
360 if(rh->it_version != 0 || end_le16toh(rh->it_len) > hdr_temp->len)
361 goto again;
363 *h_out = *hdr_temp;
364 } else ret = 0;
365 if(ret && outfd != -1){
366 pcapfile_write_packet(outfd, h_out, ret);
368 return ret;
370 static long long pcap_file_start_time, start_time;
371 static unsigned char buf[2][2048];
372 static struct pcap_pkthdr h[2];
373 static int actbuf;
374 const unsigned char* ret;
375 if(start_time == 0 || getutime64() - start_time >= timeval2utime(&h[!actbuf].ts) - pcap_file_start_time) {
376 ret = pcap_next(foo, h_out);
377 if(ret) {
378 h[actbuf] = *h_out;
379 assert(h[actbuf].len <= sizeof buf[actbuf]);
380 memcpy(buf[actbuf], ret, h[actbuf].len);
381 actbuf = !actbuf;
383 if(!start_time) {
384 start_time = getutime64();
385 assert(ret);
386 pcap_file_start_time = timeval2utime(&h_out->ts);
387 return 0;
389 if(ret) {
390 *h_out = h[actbuf];
391 return buf[actbuf];
392 } else return 0;
393 } else
394 return 0;
397 static inline int myisascii(int x) {
398 return x >= ' ' && x < 127;
401 static void dump_packet(const unsigned char* data, size_t len) {
402 static const char atab[] = "0123456789abcdef";
403 char hex[24*2+1], ascii[24+1];
404 unsigned h = 0, a = 0;
405 int fill = ' ';
407 while(len) {
408 len--;
409 hex[h++] = atab[*data >> 4];
410 hex[h++] = atab[*data & 0xf];
411 ascii[a++] = myisascii(*data) ? *data : '.';
412 if(a == 24) {
413 dump:
414 hex[h] = 0;
415 ascii[a] = 0;
416 printf("%s\t%s\n", hex, ascii);
418 if(fill == '_') return; /* jump from filler */
420 a = 0;
421 h = 0;
423 data++;
425 if(a) {
426 filler:
427 while(a<24) {
428 hex[h++] = fill;
429 hex[h++] = fill;
430 ascii[a++] = fill;
432 goto dump;
434 a = 0;
435 fill = '_';
436 goto filler;
439 void setminmax(int val) {
440 min = MIN(min, val);
441 max = MAX(max, val);
442 char mmbuf[128];
443 snprintf(mmbuf, sizeof mmbuf, "min: %d, max: %d", min, max);
444 console_settitle(t, mmbuf);
447 static int get_next_ie(const unsigned char *data, size_t len, size_t *currpos) {
448 if(*currpos + 2 >= len) return 0;
449 *currpos = *currpos + 2 + data[*currpos + 1];
450 if(*currpos >= len) return 0;
451 return 1;
454 static int get_next_wps_el(const unsigned char *data, size_t len, size_t *currpos) {
455 if(*currpos + 4 >= len) return 0;
456 uint16_t el_len;
457 memcpy(&el_len, data + 2 + *currpos, 2);
458 el_len = end_be16toh(el_len);
459 *currpos = *currpos + 4 + el_len;
460 if(*currpos >= len) return 0;
461 return 1;
464 static void process_wps_tag(const unsigned char* tag, size_t len, struct wps_data *wps) {
465 unsigned const char *el;
466 char *str;
467 size_t el_iterator = 0, wfa_iterator, remain;
468 uint16_t el_id, el_len;
469 int hex;
471 do {
472 el = tag + el_iterator;
473 remain = len - el_iterator;
474 memcpy(&el_id, el, 2);
475 el_id = end_be16toh(el_id);
476 memcpy(&el_len, el+2, 2);
477 el_len = end_be16toh(el_len);
478 el += 4;
479 str = 0, hex = 0;
480 switch(el_id) {
481 case 0x104A: /* WPS_VERSION */
482 wps->version = *el;
483 break;
484 case 0x1044: /* WPS_STATE */
485 wps->state = *el;
486 break;
487 case 0x1057: /* WPS_LOCKED */
488 wps->locked = *el;
489 break;
490 case 0x1021: /* WPS_MANUFACTURER */
491 str = wps->manufacturer;
492 break;
493 case 0x1023: /*WPS_MODEL_NAME */
494 str = wps->model_name;
495 break;
496 case 0x1024:
497 str = wps->model_number;
498 break;
499 case 0x1011:
500 str = wps->device_name;
501 break;
502 case 0x1045:
503 str = wps->ssid;
504 break;
505 case 0x1047:
506 str = wps->uuid;
507 hex = 1;
508 break;
509 case 0x1042:
510 str = wps->serial;
511 break;
512 case 0x1041:
513 str = wps->selected_registrar;
514 hex = 1;
515 break;
516 case 0x103B:
517 str = wps->response_type;
518 hex = 1;
519 break;
520 case 0x1054:
521 str = wps->primary_device_type;
522 hex = 1;
523 break;
524 case 0x1008:
525 str = wps->config_methods;
526 hex = 1;
527 break;
528 case 0x103C:
529 str = wps->rf_bands;
530 hex = 1;
531 case 0x102D:
532 str = wps->os_version;
533 break;
534 case 0x1049: /* WPS_VENDOR_EXTENSION */
535 if(el_len >= 5 && !memcmp(el, "\x00\x37\x2A", 3)) { /* WFA_EXTENSION */
536 el_len -= 3;
537 el += 3;
538 wfa_iterator = 0;
539 do {
540 if(wfa_iterator+2 <= el_len && el[wfa_iterator] == 0 /* WPS_VERSION2_ID */) {
541 wps->version = el[2];
543 } while(get_next_ie(el, el_len, &wfa_iterator));
545 break;
547 if(str) {
548 size_t max;
549 if(hex) {
550 max = el_len >= WPS_MAX_STR_LEN/2 ? WPS_MAX_STR_LEN/2 - 1 : el_len;
551 while(max--) {
552 sprintf(str, "%02x", *el);
553 el++;
554 str += 2;
556 *str = 0;
557 } else {
558 max = el_len + 1 >= WPS_MAX_STR_LEN ? WPS_MAX_STR_LEN : el_len + 1;
559 snprintf(str, max, "%s", el);
563 } while(get_next_wps_el(tag, len, &el_iterator));
567 static void process_tags(const unsigned char* tagdata, size_t tagdata_len, struct wlaninfo *temp, struct wps_data *wps) {
568 unsigned const char *tag;
570 /* iterate through tags */
571 size_t ie_iterator = 0, remain;
572 do {
573 tag = tagdata + ie_iterator;
574 remain = tagdata_len - ie_iterator;
575 switch(tag[0]) {
576 case 0: /* essid tag */
577 if(tag[1] <= remain) {
578 memcpy(temp->essid, tag+2, tag[1]);
579 temp->essid[tag[1]] = 0;
581 break;
582 case 3: /* chan nr */
583 assert(tag[1] == 1);
584 temp->channel = tag[2];
585 break;
586 case 0x30: /* RSN_TAG_NUMBER */
587 temp->enctype = ET_WPA2;
588 break;
589 case 0xDD: /* VENDOR_SPECIFIC_TAG*/
590 if(tag[1] >= remain) break;
591 if(tag[1] >= 8 &&
592 !memcmp(tag+2, "\x00\x50\xF2\x01\x01\x00", 6))
593 temp->enctype = ET_WPA;
594 if(tag[1] > 4 && !memcmp(tag+2, "\x00\x50\xf2" /*micro$oft*/ "\x04" /*type WPS*/, 4))
595 process_wps_tag(tag+2+4, tag[1]-4, wps);
596 break;
599 } while(get_next_ie(tagdata, tagdata_len, &ie_iterator));
602 static int process_frame(pcap_t *foo) {
603 struct pcap_pkthdr h;
604 const unsigned char* data = pcap_next_wrapper(foo, &h);
605 if(data) {
606 if(console_getbackendtype(t) == cb_sdl && getenv("DEBUG")) dump_packet(data, h.len);
608 uint32_t flags, offset, fchksum;
610 if(!rt_get_presentflags(data, h.len, &flags, &offset))
611 return -1;
613 struct ieee80211_radiotap_header *rh = (void*) data;
615 unsigned rtap_data = offset;
617 if(flags & (1U << IEEE80211_RADIOTAP_FLAGS)) {
618 unsigned flags_off = get_flags_off(flags, rtap_data);
619 if(data[flags_off] & 0x10 /* IEEE80211_RADIOTAP_F_FCS */) {
620 /* TODO handle bad FCS IEEE80211_RADIOTAP_F_BADFCS 0x40 */
621 memcpy(&fchksum, data + h.len - 4, 4);
622 fchksum = end_le32toh(fchksum);
623 h.len -= 4;
627 struct wlaninfo temp = {0};
629 if(!(flags & (1U << IEEE80211_RADIOTAP_DBM_ANTSIGNAL))) return -1;
630 unsigned dbmoff = get_dbm_off(flags, rtap_data);
631 temp.last_rssi = ((signed char*)data)[dbmoff];
634 // if(!(flags & (1U << IEEE80211_RADIOTAP_CHANNEL))) return -1;
635 short freq;
636 unsigned chanoff = get_chan_off(flags, rtap_data);
637 memcpy(&freq, data+ chanoff, 2);
638 temp.channel = channel_from_freq(freq);
640 uint16_t framectl, fctype;
641 offset = end_le16toh(rh->it_len);
642 memcpy(&framectl, data+offset, 2);
643 framectl = end_le16toh(framectl);
644 struct dot11frame* beacon;
645 unsigned const char* tagdata;
646 unsigned pos;
647 uint16_t caps;
648 size_t tagdata_len;
649 struct wps_data wps;
651 switch(framectl) {
652 /* IEEE 802.11 packet type */
653 case 0x0080: /* beacon */
654 case 0x0050: /* probe response */
655 beacon = (void*)(data+offset);
656 memcpy(&temp.mac,beacon->source,6);
657 offset += sizeof(*beacon);
658 memcpy(&temp.timestamp,data+offset,8);
659 temp.timestamp = end_le64toh(temp.timestamp);
660 offset += 8;
661 memcpy(&temp.beaconinterval, data+offset,2);
662 temp.beaconinterval = end_le16toh(temp.beaconinterval);
663 offset += 2;
664 memcpy(&caps, data+offset, 2);
665 caps = end_le16toh(caps);
666 if(caps & 0x10 /* CAPABILITY_WEP */)
667 temp.enctype = ET_WEP;
668 offset += 2;
669 pos = offset;
670 tagdata = data+pos;
671 tagdata_len = h.len-pos;
672 init_wps_data(&wps);
673 process_tags(tagdata, tagdata_len, &temp, &wps);
674 setminmax(temp.last_rssi);
675 return set_rssi(&temp, &wps);
677 break;
678 case 0x00d4: /*ack*/
679 case 0x0040: /* probe request */
680 return -1;
681 default:
682 fctype = framectl & end_htole16(IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE);
683 if(fctype == end_htole16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_DATA) ||
684 fctype == end_htole16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_QOS_DATA) ||
685 fctype == end_htole16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_NULLFUNC) ||
686 fctype == end_htole16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_QOS_NULLFUNC)
688 beacon = (void*)(data+offset);
689 if(!memcmp(beacon->receiver, "\x01\x80\xc2", 3) ||
690 !memcmp(beacon->receiver, "\x01\x00\x0c\xcc\xcc\xcc", 6) ||
691 !memcmp(beacon->receiver, "\xff\xff\xff", 3))
692 return -1;
694 int wifi_nr = get_wlan_by_mac(beacon->bssid);
695 /* ignore packets sent to unknown APs */
696 if(wifi_nr == -1) return -1;
697 unsigned char *client_mac;
698 if(memcmp(beacon->source, beacon->bssid, 6))
699 /* client to AP */
700 client_mac = beacon->source;
701 else
702 /* AP to client */
703 client_mac = beacon->receiver;
705 struct wlaninfo apbuf, *ap=&apbuf;
706 get_wlan(wifi_nr, ap);
707 struct ap_client* c = get_client(ap, client_mac);
708 if(!c) c = add_client(ap, client_mac);
709 write_wlan(wifi_nr, ap);
710 setminmax(temp.last_rssi);
711 if(client_mac == beacon->source) {
712 set_client_rssi(&temp, c);
713 return -1;
714 } else return wifi_nr;
717 else {
718 return -1;
721 //while(htonl(*(flags++)) & (1U << IEEE80211_RADIOTAP_EXT)) next_chunk+=4;
722 //dprintf(2, "got data\n");
723 //dump();
724 } else usleep(1);
725 return -1;
728 #if 0
729 static int next_chan(int chan) {
730 if(++chan > 11) chan = 1;
731 return chan;
733 #elif 1
734 static int next_chan(int chan) {
735 static char chanlist[]={1,5,9,13,2,6,10,14,3,7,11,4,8,12};
736 int i = 0;
737 for(i = 0; i < sizeof chanlist && chanlist[i] != chan; i++);
738 if(i >=13) return chanlist[0];
739 return chanlist[++i];
741 #else
742 static int next_chan(int chan) {
743 switch (chan) {
744 case 1: case 2: case 3: case 4: case 5:
745 return 6;
746 case 6: case 7: case 8: case 9: case 10:
747 return 11;
748 case 11: case 12: case 13:
749 /* uncomment next line if you leave in a country using chan 14 */
750 //return 14;
751 case 14:
752 return 1;
753 default:
754 assert(0);
755 return 0;
758 #endif
760 static struct {int w, h;} dim;
762 #define BGCOL RGB(33, 66, 133)
763 #define COL_BLACK RGB(0,0,0)
764 #define COL_WHITE RGB(255,255,255)
765 #define COL_YELLOW RGB(255,255,0)
767 static void draw_bg() {
768 unsigned x, y;
769 console_setcolor(t, 0, BGCOL);
770 for(y=0; y < dim.h; y++) {
771 console_goto(t, 0, y);
772 for(x = 0; x < dim.w; x++)
773 console_printchar(t, ' ', 0);
777 static unsigned reduce_color(unsigned val) {
778 unsigned a = val;
779 if (colorcount <= 8) {
780 a /= 85;
781 if(a > 2) a = 2;
782 static const unsigned tbl[] = {0, 127, 255};
783 a = tbl[a];
785 return a;
788 static int get_r(unsigned percent) {
789 return reduce_color((50 - percent/2) * 5);
791 static int get_g(unsigned percent) {
792 return reduce_color(percent/2 * 5);
794 static int get_a(unsigned age) {
795 return reduce_color(5+((50 - age)*5));
797 #define LINES_PER_NET 1
798 static void selection_move(int dir) {
799 if((int)selection+dir < 0) dir=0;
800 lock();
801 unsigned l = DYNA_COUNT(wlans);
802 unlock();
803 if((int)selection+dir >= l ||
804 ((int)selection+dir)*LINES_PER_NET+1 >= dim.h) dir=0;
805 selection += dir;
808 static volatile unsigned bms;
809 static void set_bms(float percent) {
810 float max = 800, min=50;
811 float range=max-min;
812 float rpercent = range/100.f;
813 bms = min + (100 - percent) * rpercent;
816 char *mac2str(unsigned char mac[static 6], char buf[static 18]) {
817 unsigned m, x;
818 char hextab[16] = "0123456789abcdef";
819 for(m = 0, x=0 ; m<6; m++, x+=3) {
820 buf[x] = hextab[mac[m]>>4];
821 buf[x+1] = hextab[mac[m]&15];
822 buf[x+2] = ':';
824 buf[17]=0;
825 return buf;
828 static char* format_timestamp(uint64_t timestamp, char *ts) {
829 #define TSTP_SEC 1000000ULL /* 1 MHz clock -> 1 million ticks/sec */
830 #define TSTP_MIN (TSTP_SEC * 60ULL)
831 #define TSTP_HOUR (TSTP_MIN * 60ULL)
832 #define TSTP_DAY (TSTP_HOUR * 24ULL)
833 uint64_t rem;
834 unsigned days, hours, mins, secs;
835 days = timestamp / TSTP_DAY;
836 rem = timestamp % TSTP_DAY;
837 hours = rem / TSTP_HOUR;
838 rem %= TSTP_HOUR;
839 mins = rem / TSTP_MIN;
840 rem %= TSTP_MIN;
841 secs = rem / TSTP_SEC;
842 sprintf(ts, "%ud %02u:%02u:%02u", days, hours, mins, secs);
843 return ts;
846 static const char* enctype_str(enum enctype et) {
847 static const char enc_name[][5] = {
848 [ET_OPEN]= "OPEN",
849 [ET_WEP] = "WEP",
850 [ET_WPA] = "WPA",
851 [ET_WPA2]= "WPA2",
853 if(et > ET_MAX) abort();
854 return enc_name[et];
857 static char* sanitize_string(char *s, char *new) {
858 size_t i,j, l = strlen(s), ls=l;
859 for(i=0,j=0;i<ls;i++) {
860 if(s[i] < ' ' || s[i] > 127) {
861 sprintf(new + j, "\\x%02x", s[i] & 0xff);
862 j += 3;
863 } else new[j] = s[i];
864 j++;
866 new[j] = 0;
867 return new;
870 static unsigned gray_shade_from_timestamp(long long last_seen) {
871 long long now = getutime64();
872 long long age_ms = (now - last_seen)/1000;
873 age_ms=MIN(5000, age_ms)/100; /* seems we end up with a range 0-50 */
874 return get_a(age_ms);
877 #define ESSID_PRINT_START 1
878 #define ESSID_PRINT_END 32+ESSID_PRINT_START
879 #define ESSID_PRINT_LEN (ESSID_PRINT_END - ESSID_PRINT_START)
881 static void dump_wlan_info(unsigned wlanidx) {
882 struct wlaninfo wtmp, *w = &wtmp;
883 get_wlan(wlanidx, w);
884 unsigned line = 3, x, col1, col2, col3, col4;
885 console_setcolor(t, 0, BGCOL);
886 console_setcolor(t, 1, COL_WHITE);
888 col1 = x = 2;
889 console_goto(t, ++x, line);
890 char macbuf[18];
891 console_printf(t, "MAC %s", mac2str(w->mac, macbuf));
892 x += 25;
893 col2 = x;
895 console_goto(t, ++x, line);
896 console_printf(t, "CHAN %d", (int) w->channel);
897 x += 9 + 5;
898 col3 = x;
900 console_goto(t, ++x, line);
901 char ts[64];
902 format_timestamp(w->timestamp, ts);
903 console_printf(t, "UP: %s", ts);
904 x += strlen(ts) +5;
905 col4 = x;
907 console_goto(t, ++x, line);
908 console_printf(t, "BI %d ms", (int) w->beaconinterval);
910 line++;
911 x = col1;
913 console_goto(t, ++x, line);
914 console_printf(t, "AVG %.2f dBm", (double)w->total_rssi/(double)w->count);
915 //x += 14 + 5;
916 x = col2;
918 console_goto(t, ++x, line);
919 console_printf(t, "CURR %d dBm", w->last_rssi);
920 //x += 10 + 5;
921 x = col3;
923 console_goto(t, ++x, line);
924 console_printf(t, "MIN %d dBm", w->min_rssi);
925 //x += 9 + 5;
926 x = col4;
928 console_goto(t, ++x, line);
929 console_printf(t, "MAX %d dBm", w->max_rssi);
930 x += 9 + 5;
932 line++;
933 x = col1;
934 console_goto(t, ++x, line);
935 console_printf(t, "%4s", enctype_str(w->enctype));
937 x = col2;
938 console_goto(t, ++x, line);
939 if(w->wps) console_printf(t, "WPS %d.%d", w->wps->version >> 4, w->wps->version & 15);
941 x = col3;
942 console_goto(t, ++x, line);
943 if(w->wps) console_printf(t, w->wps->locked == 1 ? "LOCKED" : "-");
945 char sanbuf[WPS_MAX_STR_LEN*4+1];
947 x = col4;
948 console_goto(t, ++x, line);
949 if(w->wps && w->wps->manufacturer[0]) {
950 sanitize_string(w->wps->manufacturer, sanbuf);
951 console_printf(t, "%s", sanbuf);
954 line++;
956 x = col1;
957 console_goto(t, ++x, line);
958 if(w->wps && w->wps->model_name[0]) {
959 sanitize_string(w->wps->model_name, sanbuf);
960 console_printf(t, "%s", sanbuf);
963 x = col2;
964 console_goto(t, ++x, line);
965 if(w->wps && w->wps->model_number[0]) {
966 sanitize_string(w->wps->model_number, sanbuf);
967 console_printf(t, "%s", sanbuf);
970 x = col3;
971 console_goto(t, ++x, line);
972 if(w->wps && w->wps->device_name[0]) {
973 sanitize_string(w->wps->device_name, sanbuf);
974 console_printf(t, "%s", sanbuf);
977 x = col4;
978 console_goto(t, ++x, line);
979 if(w->wps && w->wps->serial[0]) {
980 sanitize_string(w->wps->serial, sanbuf);
981 console_printf(t, "%s", sanbuf);
984 line += 2;
985 if(w->clients) {
986 x = col1;
987 console_goto(t, ++x, line++);
988 console_printf(t, "CLIENT"
989 "%*s%s" "%*s%s" "%*s%s" "%*s%s",
990 21-6, "", "AVG",
991 15-3, "", "CURR",
992 12-4, "", "MIN",
993 12-3, "", "MAX"
996 struct ap_client *c;
997 for(c = w->clients; c; c = c->next) {
999 unsigned a = gray_shade_from_timestamp(c->last_seen);
1000 console_setcolor(t, 1, RGB(a,a,a));
1002 x = col1;
1003 console_goto(t, ++x, line);
1004 console_printf(t, "%s", mac2str(c->mac, macbuf));
1006 x += 20;
1008 console_setcolor(t, 1, COL_WHITE);
1010 console_goto(t, ++x, line);
1011 console_printf(t, "%.2f dBm", (double)c->total_rssi/(double)c->count);
1012 x += 14;
1014 console_goto(t, ++x, line);
1015 console_printf(t, "%d dBm", c->last_rssi);
1016 x += 11;
1018 console_goto(t, ++x, line);
1019 console_printf(t, "%d dBm", c->min_rssi);
1020 x += 11;
1022 console_goto(t, ++x, line);
1023 console_printf(t, "%d dBm", c->max_rssi);
1024 x += 9 + 5;
1026 line++;
1030 static void dump_wlan_at(unsigned wlanidx, unsigned line) {
1031 console_goto(t, 0, line);
1032 console_setcolor(t, 0, BGCOL);
1034 console_setcolor(t, 1, COL_YELLOW);
1036 if(wlanidx == selection) {
1037 console_printchar(t, '>', 0);
1038 } else {
1039 console_printchar(t, ' ', 0);
1042 struct wlaninfo wtmp, *w = &wtmp;
1043 get_wlan(wlanidx, w);
1045 unsigned a = gray_shade_from_timestamp(w->last_seen);
1046 console_setcolor(t, 1, RGB(a,a,a));
1047 console_goto(t, ESSID_PRINT_START, line);
1049 char macbuf[18];
1051 if(*w->essid) {
1052 char essid_san[32*4+1];
1053 sanitize_string(w->essid, essid_san);
1054 console_printf(t, "%*s", ESSID_PRINT_LEN, essid_san);
1055 } else
1056 console_printf(t, "<hidden> %*s", ESSID_PRINT_LEN-9, mac2str(w->mac, macbuf));
1058 console_goto(t, ESSID_PRINT_END, line);
1059 console_printchar(t, ' ', 0);
1061 int scale = max - min;
1062 int width = dim.w - (ESSID_PRINT_LEN+2);
1063 unsigned x;
1064 float widthpercent = (float)width/100.f;
1065 float scalepercent = (float)scale/100.f;
1066 float scaleup = (float)width / (float)scale;
1067 double avg = (double)w->total_rssi/(double)w->count;
1068 float avg_percent = (avg - (float)min) / scalepercent;
1069 float curr_percent = ((float)w->last_rssi - (float)min) / scalepercent;
1070 int avg_marker = (avg - (float)min) * scaleup;
1071 int curr_marker = ((float)w->last_rssi - (float)min) * scaleup;
1073 for(x = 0; x < width; x++) {
1074 rgb_t step_color;
1075 if(wlanidx == selection) step_color = RGB(get_r(x/widthpercent),get_g(x/widthpercent),0);
1076 else step_color = RGB(get_r(x/widthpercent),get_r(x/widthpercent),get_r(x/widthpercent));
1077 console_setcolor(t, 0, step_color);
1078 if(x != curr_marker) console_setcolor(t, 1, COL_BLACK);
1079 else console_setcolor(t, 1, COL_WHITE);
1080 if(x == avg_marker) console_printchar(t, 'I', 0);
1081 else if (x == curr_marker) console_printchar(t, '|', 0);
1082 else if(x == 0) console_printchar(t, '[', 0);
1083 else if(x == width-1) console_printchar(t, ']', 0);
1084 else console_printchar(t, ' ', 0);
1088 static void dump_wlan(unsigned idx) {
1089 if(idx * LINES_PER_NET + 1 > dim.h || (selected && selection != idx)) return;
1090 dump_wlan_at(idx, selected ? 1 : idx * LINES_PER_NET);
1091 if(selected) dump_wlan_info(idx);
1094 int this_wlan_scale_mode = 1;
1095 static void calc_bms(unsigned wlanidx) {
1096 long long now = getutime64();
1097 struct wlaninfo wtmp, *w = &wtmp;
1098 get_wlan(wlanidx, w);
1099 int my_min = this_wlan_scale_mode ? w->min_rssi : min;
1100 int my_max = this_wlan_scale_mode ? w->max_rssi : max;
1101 long long age_ms = (now - w->last_seen)/1000;
1102 age_ms=MIN(5000, age_ms)/100; /* seems we end up with a range 0-50 */
1103 int scale = my_max - my_min;
1104 float scalepercent = (float)scale/100.f;
1105 float curr_percent = ((float)w->last_rssi - (float)my_min) / scalepercent;
1106 if(age_ms < 15) set_bms(curr_percent);
1107 else bms = 0;
1110 static void dump(void) {
1111 unsigned i, l;
1112 lock();
1113 l = DYNA_COUNT(wlans);
1114 unlock();
1115 //dprintf(1, "********************\n");
1116 //draw_bg();
1117 for(i=0;i<l;i++)
1118 dump_wlan(i);
1119 console_refresh(t);
1122 static void initconcol() {
1123 console_init(t);
1124 char *p;
1125 int rw=1024,rh=768;
1126 if((p = getenv("RES"))) {
1127 char *q = strchr(p, 'x');
1128 if(q) {
1129 unsigned l = q-p;
1130 char b[64];
1131 memcpy(b,p,l);
1132 b[l] = 0;
1133 rw=atoi(b);
1134 strcpy(b,++q);
1135 rh=atoi(b);
1138 point reso = {rw, rh};
1139 console_init_graphics(&co, reso, FONT);
1140 console_getbounds(t, &dim.w, &dim.h);
1141 colorcount = console_getcolorcount(t);
1142 #ifdef NO_COLOR
1143 (*console_setcolor)(t, 0, COL_WHITE);
1144 (*console_setcolor)(t, 1, COL_BLACK);
1145 #endif
1146 draw_bg();
1149 static unsigned char blip[] = {0x52, 0x51, 0x51, 0x51, 0xC4, 0x4C, 0xF4, 0xF4, 0xF3,0xEF};
1150 static unsigned blip_frame(int idx) {
1151 idx = idx % (2*sizeof(blip));
1152 if(idx>=sizeof(blip)) idx=(2*sizeof(blip))-idx;
1153 return blip[idx];
1156 static volatile float volume = .5;
1158 static void generate_blip(unsigned char* data, size_t bufsize) {
1159 int i;
1160 for(i=0;i<bufsize;i++) {
1161 float f = blip_frame(i) * volume;
1162 data[i] = f;
1166 static void volume_change(int dir) {
1167 volume += dir * 0.1;
1168 if(volume < 0) volume = 0;
1169 if(volume > 1) volume = 1;
1172 static void* blip_thread(void* arg) {
1173 struct AudioCTX ao;
1174 audio_init(&ao);
1175 unsigned char buf[100], silence[1000];
1176 generate_blip(buf, sizeof(buf));
1177 memset(silence, buf[99], sizeof silence);
1178 long long t = getutime64();
1179 unsigned passed = 0;
1180 float myvol = volume;
1181 while(selected) {
1182 if(myvol != volume) {
1183 generate_blip(buf, sizeof(buf));
1184 myvol = volume;
1186 if(bms && (getutime64() - t)/1000 >= bms) {
1187 audio_write(&ao, buf, sizeof buf);
1188 t = getutime64();
1190 audio_write(&ao, silence, sizeof silence);
1191 usleep(1);
1193 audio_close(&ao);
1194 return 0;
1197 static void* nop_thread(void *arg) {
1198 return 0;
1201 static void* chanwalker_thread(void* arg) {
1202 char* itf = arg;
1203 int channel = 1, delay = 800;
1204 long long tm = 0;
1205 if(filebased) return 0;
1206 while(!selected) {
1207 if((getutime64() - tm)/1000 >= delay) {
1208 int ret = set_channel(itf, channel = next_chan(channel));
1209 if(ret == -1) {
1210 if(console_getbackendtype(t) == cb_sdl)
1211 dprintf(2, "oops couldnt switch to chan %d\n", channel);
1213 tm = getutime64();
1215 usleep(1000);
1217 return 0;
1219 #include <sys/ioctl.h>
1220 #include <sys/socket.h>
1221 /* set an interface up or down, depending on whether up is set.
1222 if checkonly is true, no change will be made and the result
1223 of the function can be interpreted as "isdownup".
1224 if the interface was already up/down, 2 is returned.
1225 if the interface was successfully upped/downed, 1 is returned.
1226 0 is only returned if checkonly is set and the interface was not
1227 in the queried state.
1228 -1 is returned on error. */
1229 static int ifdownup(const char *dev, int up, int checkonly) {
1230 int fd, ret = -1;
1231 if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) return -1;
1232 struct ifreq ifr = {0};
1233 strcpy(ifr.ifr_name, dev);
1234 if(ioctl(fd, SIOCGIFFLAGS, &ifr) <0) goto done;
1235 int isup = ifr.ifr_flags & IFF_UP;
1236 if((up && isup) || (!up && !isup)) ret = 2;
1237 else if (checkonly) ret = 0;
1238 else {
1239 if(up) ifr.ifr_flags |= IFF_UP;
1240 else ifr.ifr_flags &= ~(IFF_UP);
1241 ret = (ioctl(fd, SIOCSIFFLAGS, &ifr) >= 0);
1243 done:
1244 close(fd);
1245 return ret;
1248 #include "wireless-lite.h"
1249 static int getiwmode(const char *dev) {
1250 int fd, ret = -1;
1251 if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) return -1;
1252 struct iwreq iwr = {0};
1253 strcpy(iwr.ifr_name, dev);
1254 if(ioctl(fd, SIOCGIWMODE, &iwr) >=0) ret = iwr.u.mode;
1255 close(fd);
1256 return ret;
1259 static int setiwmode(const char *dev, int mode) {
1260 int fd, ret = -1;
1261 if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) return -1;
1262 struct iwreq iwr = {.u.mode = mode};
1263 strcpy(iwr.ifr_name, dev);
1264 ret = ioctl(fd, SIOCSIWMODE, &iwr);
1265 close(fd);
1266 return ret;
1269 static void* capture_thread(void*arg) {
1270 pcap_t *foo = arg;
1271 while(!stop) {
1272 int ret = process_frame(foo);
1273 long long tmp = getutime64();
1274 if(ret >= 0) {
1275 lock();
1276 wlans[ret].last_seen = tmp;
1277 unlock();
1280 return 0;
1283 static pthread_t bt, wt;
1284 static const char *itf;
1286 static void set_selection(int on) {
1287 selected = on;
1288 if(selected) {
1289 pthread_join(wt, 0);
1290 draw_bg();
1291 pthread_create(&bt, 0, blip_thread, 0);
1292 if(!filebased) {
1293 lock();
1294 int ch = wlans[selection].channel;
1295 unlock();
1296 set_channel(itf, ch);
1298 } else {
1299 pthread_create(&wt, 0, chanwalker_thread, (void*)itf);
1300 pthread_join(bt, 0);
1305 #include "netgui.c"
1307 int main(int argc,char**argv) {
1308 wlans = DYNA_NEW(struct wlaninfo);
1309 int c;
1310 int fixed_chan = 0;
1311 while((c = getopt(argc, argv, "c:")) != EOF) switch(c) {
1312 case 'c': fixed_chan = atoi(optarg); break;
1313 default: return usage(argv[0]);
1315 if(!argv[optind]) return usage(argv[0]);
1317 itf = argv[optind];
1318 min = 127;
1319 max = -60;
1320 outfd = -1;
1321 char errbuf[PCAP_ERRBUF_SIZE];
1322 pcap_t *foo;
1323 if(strchr(itf, '.') && access(itf, R_OK) == 0) {
1324 filebased = 1;
1325 foo = pcap_open_offline(itf, errbuf);
1326 } else {
1327 foo = pcap_create(itf, errbuf);
1328 char fnbuf[512];
1329 snprintf(fnbuf, sizeof fnbuf, "tmp.%s.pcap", itf);
1330 outfd= open(fnbuf, O_WRONLY|O_CREAT|O_TRUNC,0660);
1331 if(outfd != -1)
1332 pcapfile_write_header(outfd);
1334 if(!foo) { dprintf(2, "%s\n", errbuf); return 1; }
1336 int ret, wasdown, orgmode;
1338 if(filebased) goto skip;
1340 if((orgmode = getiwmode(itf)) != IW_MODE_MONITOR) {
1341 if((ret = ifdownup(itf, 0, 0)) == -1) {
1342 iferr:;
1343 perror("error setting up interface - maybe need to run as root.");
1345 wasdown = (ret == 2);
1346 if(setiwmode(itf, IW_MODE_MONITOR) == -1) goto iferr;
1347 } else {
1348 wasdown = (ifdownup(itf, 0, 1) == 2);
1350 if(ifdownup(itf, 1, 0) == -1) goto iferr;
1352 if(pcap_activate(foo)) {
1353 dprintf(2, "pcap_activate failed: %s\n", pcap_geterr(foo));
1354 return 1;
1357 skip:;
1359 initconcol();
1361 signal(SIGINT, sigh);
1363 int channel = fixed_chan ? fixed_chan : 1;
1364 if(fixed_chan) set_channel(itf, fixed_chan);
1365 long long tm = 0;
1366 pthread_t ct, nt;
1367 void *(*cw_func)(void*);
1368 if(filebased || fixed_chan) cw_func = nop_thread;
1369 else cw_func = chanwalker_thread;
1370 pthread_create(&wt, 0, cw_func, (void*) itf);
1371 pthread_create(&ct, 0, capture_thread, foo);
1373 struct netgui_config netgui_cfg;
1374 if(getenv("NETGUI")) {
1375 netgui_start(&netgui_cfg, "0.0.0.0", 9876);
1378 while(!stop) {
1379 long long tmp = getutime64();
1380 if((tmp-tm) >= (1000000 / GUI_FPS)) {
1381 tm = tmp;
1382 dump();
1385 if(selected) calc_bms(selection);
1386 int k = console_getkey_nb(t);
1388 switch(k) {
1389 case CK_CURSOR_RIGHT: this_wlan_scale_mode = !this_wlan_scale_mode; break;
1390 case '+': case '0': volume_change(+1); break;
1391 case '-': case '9': volume_change(-1); break;
1392 case CK_CURSOR_DOWN: selection_move(1);break;
1393 case CK_CURSOR_UP: selection_move(-1);break;
1394 case CK_RETURN:
1395 //selected = !selected;
1396 set_selection(!selected);
1397 break;
1398 case CK_QUIT:
1399 case CK_ESCAPE: stop = 1; break;
1401 usleep(1000);
1404 pcap_breakloop(foo); // this doesn't actually seem to work
1406 if(getenv("NETGUI")) {
1407 netgui_stop(&netgui_cfg);
1410 if(selected) {
1411 selected = 0;
1412 pthread_join(bt, 0);
1413 } else {
1414 selected = 1;
1415 pthread_join(wt, 0);
1418 // since our capture_thread uses blocking reads in order to keep CPU usage
1419 // minimal, we need to get the current read cancelled - and if no packets
1420 // arrive, this can take a *long* time. since pcap_breakloop() doesn't actually
1421 // seem to work, the only way i found to break out of the read is to actually
1422 // bring down the interface - so this must happen before we join the thread
1423 // and close the pcap handle.
1424 if(!filebased) {
1425 if(wasdown || orgmode != IW_MODE_MONITOR) ifdownup(itf, 0, 0);
1426 if(orgmode != IW_MODE_MONITOR) setiwmode(itf, orgmode);
1427 if(!wasdown && orgmode != IW_MODE_MONITOR) ifdownup(itf, 1, 0);
1430 pthread_join(ct, 0);
1432 pcap_close(foo);
1433 console_cleanup(t);
1434 if(outfd != -1) close(outfd);
1435 return 0;