preliminary support for displaying AP clients
[rofl0r-MacGeiger.git] / macgeiger.c
blobd1e102e4a73d765edab8dc15cedfa1e01ab25fd6
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 if(!w->clients) w->clients = c;
237 else {
238 it = w->clients;
239 while(it->next) it=it->next;
240 it->next = c;
242 return c;
245 static long long getutime64(void);
247 static void set_client_rssi(struct wlaninfo* w, struct ap_client *c) {
248 c->count++;
249 c->total_rssi += w->last_rssi;
250 c->last_seen = getutime64();
251 c->last_rssi = w->last_rssi;
252 c->min_rssi = MIN(c->min_rssi, c->last_rssi);
253 c->max_rssi = MAX(c->max_rssi, c->last_rssi);
256 static int set_rssi(struct wlaninfo *w, struct wps_data* wps) {
257 int i = -1;
258 struct wlaninfo wtmp, *d = &wtmp;
259 if(i == -1) i = get_wlan_by_mac(w->mac);
260 if(i == -1) i = get_new_wlan();
261 if(i != -1) {
262 get_wlan(i, d);
263 if(w->essid[0]) strcpy(d->essid, w->essid);
264 memcpy(d->mac, w->mac, 6);
265 d->total_rssi += w->last_rssi;
266 d->count++;
267 d->last_rssi = w->last_rssi;
268 d->channel = w->channel;
269 d->timestamp = w->timestamp;
270 d->beaconinterval = w->beaconinterval;
271 d->min_rssi = MIN(d->min_rssi, d->last_rssi);
272 d->max_rssi = MAX(d->max_rssi, d->last_rssi);
273 d->enctype = w->enctype;
274 if(wps->version) {
275 if(!d->wps) {
276 d->wps = malloc(sizeof *wps);
277 if(d->wps) init_wps_data(d->wps);
279 if(d->wps) {
280 if(!wps->manufacturer[0]) {
281 d->wps->version = wps->version;
282 d->wps->state = wps->state;
283 d->wps->locked = wps->locked;
284 } else
285 memcpy(d->wps, wps, sizeof(*wps));
288 write_wlan(i, d);
290 return i;
293 volatile int stop;
294 void sigh(int x) {
295 stop = 1;
298 #include "radiotap_flags.h"
300 static unsigned get_flags_off(unsigned flags, unsigned start_off) {
301 return rt_get_flag_offset(flags, IEEE80211_RADIOTAP_FLAGS, start_off);
304 static unsigned get_dbm_off(unsigned flags, unsigned start_off) {
305 return rt_get_flag_offset(flags, IEEE80211_RADIOTAP_DBM_ANTSIGNAL, start_off);
308 static unsigned get_chan_off(unsigned flags, unsigned start_off) {
309 return rt_get_flag_offset(flags, IEEE80211_RADIOTAP_CHANNEL, start_off);
312 static unsigned channel_from_freq(unsigned freq) {
313 return freq==2484?14:(freq-2407)/5;
316 struct dot11frame {
317 uint16_t framecontrol;
318 uint16_t duration;
319 unsigned char receiver[6];
320 unsigned char source[6];
321 unsigned char bssid[6];
322 uint16_t sequence_no;
325 static unsigned char* find_tag(unsigned const char *tagdata, unsigned tag, unsigned bytes_left) {
326 while(bytes_left) {
327 if(*tagdata == tag) return (unsigned char*)tagdata;
328 unsigned tagsize = tagdata[1];
329 tagdata+=2+tagsize;
330 if(bytes_left < 2+tagsize) return 0;
331 bytes_left-=2+tagsize;
333 return 0;
336 static long long timeval2utime(struct timeval*t) {
337 return (t->tv_sec * 1000LL * 1000LL) + t->tv_usec;
340 static long long getutime64(void) {
341 struct timeval t;
342 gettimeofday(&t, NULL);
343 return timeval2utime(&t);
347 static int filebased;
349 static const unsigned char* pcap_next_wrapper(pcap_t *foo, struct pcap_pkthdr *h_out) {
350 if(!filebased) {
351 again:;
352 const unsigned char* ret = 0;
353 struct pcap_pkthdr *hdr_temp;
354 int err = pcap_next_ex(foo, &hdr_temp, &ret);
355 if(err == 1) {
356 /* skip malformed packets, like those emitted by busybox udhcpc */
357 struct ieee80211_radiotap_header *rh = (void*) ret;
358 if(rh->it_version != 0 || end_le16toh(rh->it_len) > hdr_temp->len)
359 goto again;
361 *h_out = *hdr_temp;
362 } else ret = 0;
363 if(ret && outfd != -1){
364 pcapfile_write_packet(outfd, h_out, ret);
366 return ret;
368 static long long pcap_file_start_time, start_time;
369 static unsigned char buf[2][2048];
370 static struct pcap_pkthdr h[2];
371 static int actbuf;
372 const unsigned char* ret;
373 if(start_time == 0 || getutime64() - start_time >= timeval2utime(&h[!actbuf].ts) - pcap_file_start_time) {
374 ret = pcap_next(foo, h_out);
375 if(ret) {
376 h[actbuf] = *h_out;
377 assert(h[actbuf].len <= sizeof buf[actbuf]);
378 memcpy(buf[actbuf], ret, h[actbuf].len);
379 actbuf = !actbuf;
381 if(!start_time) {
382 start_time = getutime64();
383 assert(ret);
384 pcap_file_start_time = timeval2utime(&h_out->ts);
385 return 0;
387 if(ret) {
388 *h_out = h[actbuf];
389 return buf[actbuf];
390 } else return 0;
391 } else
392 return 0;
395 static inline int myisascii(int x) {
396 return x >= ' ' && x < 127;
399 static void dump_packet(const unsigned char* data, size_t len) {
400 static const char atab[] = "0123456789abcdef";
401 char hex[24*2+1], ascii[24+1];
402 unsigned h = 0, a = 0;
403 int fill = ' ';
405 while(len) {
406 len--;
407 hex[h++] = atab[*data >> 4];
408 hex[h++] = atab[*data & 0xf];
409 ascii[a++] = myisascii(*data) ? *data : '.';
410 if(a == 24) {
411 dump:
412 hex[h] = 0;
413 ascii[a] = 0;
414 printf("%s\t%s\n", hex, ascii);
416 if(fill == '_') return; /* jump from filler */
418 a = 0;
419 h = 0;
421 data++;
423 if(a) {
424 filler:
425 while(a<24) {
426 hex[h++] = fill;
427 hex[h++] = fill;
428 ascii[a++] = fill;
430 goto dump;
432 a = 0;
433 fill = '_';
434 goto filler;
437 void setminmax(int val) {
438 min = MIN(min, val);
439 max = MAX(max, val);
440 char mmbuf[128];
441 snprintf(mmbuf, sizeof mmbuf, "min: %d, max: %d", min, max);
442 console_settitle(t, mmbuf);
445 static int get_next_ie(const unsigned char *data, size_t len, size_t *currpos) {
446 if(*currpos + 2 >= len) return 0;
447 *currpos = *currpos + 2 + data[*currpos + 1];
448 if(*currpos >= len) return 0;
449 return 1;
452 static int get_next_wps_el(const unsigned char *data, size_t len, size_t *currpos) {
453 if(*currpos + 4 >= len) return 0;
454 uint16_t el_len;
455 memcpy(&el_len, data + 2 + *currpos, 2);
456 el_len = end_be16toh(el_len);
457 *currpos = *currpos + 4 + el_len;
458 if(*currpos >= len) return 0;
459 return 1;
462 static void process_wps_tag(const unsigned char* tag, size_t len, struct wps_data *wps) {
463 unsigned const char *el;
464 char *str;
465 size_t el_iterator = 0, wfa_iterator, remain;
466 uint16_t el_id, el_len;
467 int hex;
469 do {
470 el = tag + el_iterator;
471 remain = len - el_iterator;
472 memcpy(&el_id, el, 2);
473 el_id = end_be16toh(el_id);
474 memcpy(&el_len, el+2, 2);
475 el_len = end_be16toh(el_len);
476 el += 4;
477 str = 0, hex = 0;
478 switch(el_id) {
479 case 0x104A: /* WPS_VERSION */
480 wps->version = *el;
481 break;
482 case 0x1044: /* WPS_STATE */
483 wps->state = *el;
484 break;
485 case 0x1057: /* WPS_LOCKED */
486 wps->locked = *el;
487 break;
488 case 0x1021: /* WPS_MANUFACTURER */
489 str = wps->manufacturer;
490 break;
491 case 0x1023: /*WPS_MODEL_NAME */
492 str = wps->model_name;
493 break;
494 case 0x1024:
495 str = wps->model_number;
496 break;
497 case 0x1011:
498 str = wps->device_name;
499 break;
500 case 0x1045:
501 str = wps->ssid;
502 break;
503 case 0x1047:
504 str = wps->uuid;
505 hex = 1;
506 break;
507 case 0x1042:
508 str = wps->serial;
509 break;
510 case 0x1041:
511 str = wps->selected_registrar;
512 hex = 1;
513 break;
514 case 0x103B:
515 str = wps->response_type;
516 hex = 1;
517 break;
518 case 0x1054:
519 str = wps->primary_device_type;
520 hex = 1;
521 break;
522 case 0x1008:
523 str = wps->config_methods;
524 hex = 1;
525 break;
526 case 0x103C:
527 str = wps->rf_bands;
528 hex = 1;
529 case 0x102D:
530 str = wps->os_version;
531 break;
532 case 0x1049: /* WPS_VENDOR_EXTENSION */
533 if(el_len >= 5 && !memcmp(el, "\x00\x37\x2A", 3)) { /* WFA_EXTENSION */
534 el_len -= 3;
535 el += 3;
536 wfa_iterator = 0;
537 do {
538 if(wfa_iterator+2 <= el_len && el[wfa_iterator] == 0 /* WPS_VERSION2_ID */) {
539 wps->version = el[2];
541 } while(get_next_ie(el, el_len, &wfa_iterator));
543 break;
545 if(str) {
546 size_t max;
547 if(hex) {
548 max = el_len >= WPS_MAX_STR_LEN/2 ? WPS_MAX_STR_LEN/2 - 1 : el_len;
549 while(max--) {
550 sprintf(str, "%02x", *el);
551 el++;
552 str += 2;
554 *str = 0;
555 } else {
556 max = el_len + 1 >= WPS_MAX_STR_LEN ? WPS_MAX_STR_LEN : el_len + 1;
557 snprintf(str, max, "%s", el);
561 } while(get_next_wps_el(tag, len, &el_iterator));
565 static void process_tags(const unsigned char* tagdata, size_t tagdata_len, struct wlaninfo *temp, struct wps_data *wps) {
566 unsigned const char *tag;
568 /* iterate through tags */
569 size_t ie_iterator = 0, remain;
570 do {
571 tag = tagdata + ie_iterator;
572 remain = tagdata_len - ie_iterator;
573 switch(tag[0]) {
574 case 0: /* essid tag */
575 if(tag[1] <= remain) {
576 memcpy(temp->essid, tag+2, tag[1]);
577 temp->essid[tag[1]] = 0;
579 break;
580 case 3: /* chan nr */
581 assert(tag[1] == 1);
582 temp->channel = tag[2];
583 break;
584 case 0x30: /* RSN_TAG_NUMBER */
585 temp->enctype = ET_WPA2;
586 break;
587 case 0xDD: /* VENDOR_SPECIFIC_TAG*/
588 if(tag[1] >= remain) break;
589 if(tag[1] >= 8 &&
590 !memcmp(tag+2, "\x00\x50\xF2\x01\x01\x00", 6))
591 temp->enctype = ET_WPA;
592 if(tag[1] > 4 && !memcmp(tag+2, "\x00\x50\xf2" /*micro$oft*/ "\x04" /*type WPS*/, 4))
593 process_wps_tag(tag+2+4, tag[1]-4, wps);
594 break;
597 } while(get_next_ie(tagdata, tagdata_len, &ie_iterator));
600 static int process_frame(pcap_t *foo) {
601 struct pcap_pkthdr h;
602 const unsigned char* data = pcap_next_wrapper(foo, &h);
603 if(data) {
604 if(console_getbackendtype(t) == cb_sdl && getenv("DEBUG")) dump_packet(data, h.len);
606 uint32_t flags, offset, fchksum;
608 if(!rt_get_presentflags(data, h.len, &flags, &offset))
609 return -1;
611 struct ieee80211_radiotap_header *rh = (void*) data;
613 unsigned rtap_data = offset;
615 if(flags & (1U << IEEE80211_RADIOTAP_FLAGS)) {
616 unsigned flags_off = get_flags_off(flags, rtap_data);
617 if(data[flags_off] & 0x10 /* IEEE80211_RADIOTAP_F_FCS */) {
618 /* TODO handle bad FCS IEEE80211_RADIOTAP_F_BADFCS 0x40 */
619 memcpy(&fchksum, data + h.len - 4, 4);
620 fchksum = end_le32toh(fchksum);
621 h.len -= 4;
625 struct wlaninfo temp = {0};
627 if(!(flags & (1U << IEEE80211_RADIOTAP_DBM_ANTSIGNAL))) return -1;
628 unsigned dbmoff = get_dbm_off(flags, rtap_data);
629 temp.last_rssi = ((signed char*)data)[dbmoff];
632 // if(!(flags & (1U << IEEE80211_RADIOTAP_CHANNEL))) return -1;
633 short freq;
634 unsigned chanoff = get_chan_off(flags, rtap_data);
635 memcpy(&freq, data+ chanoff, 2);
636 temp.channel = channel_from_freq(freq);
638 uint16_t framectl, fctype;
639 offset = end_le16toh(rh->it_len);
640 memcpy(&framectl, data+offset, 2);
641 framectl = end_le16toh(framectl);
642 struct dot11frame* beacon;
643 unsigned const char* tagdata;
644 unsigned pos;
645 uint16_t caps;
646 size_t tagdata_len;
647 struct wps_data wps;
649 switch(framectl) {
650 /* IEEE 802.11 packet type */
651 case 0x0080: /* beacon */
652 case 0x0050: /* probe response */
653 beacon = (void*)(data+offset);
654 memcpy(&temp.mac,beacon->source,6);
655 offset += sizeof(*beacon);
656 memcpy(&temp.timestamp,data+offset,8);
657 temp.timestamp = end_le64toh(temp.timestamp);
658 offset += 8;
659 memcpy(&temp.beaconinterval, data+offset,2);
660 temp.beaconinterval = end_le16toh(temp.beaconinterval);
661 offset += 2;
662 memcpy(&caps, data+offset, 2);
663 caps = end_le16toh(caps);
664 if(caps & 0x10 /* CAPABILITY_WEP */)
665 temp.enctype = ET_WEP;
666 offset += 2;
667 pos = offset;
668 tagdata = data+pos;
669 tagdata_len = h.len-pos;
670 init_wps_data(&wps);
671 process_tags(tagdata, tagdata_len, &temp, &wps);
672 setminmax(temp.last_rssi);
673 return set_rssi(&temp, &wps);
675 break;
676 case 0x00d4: /*ack*/
677 case 0x0040: /* probe request */
678 return -1;
679 default:
680 fctype = framectl & end_htole16(IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE);
681 if(fctype == end_htole16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_DATA) ||
682 fctype == end_htole16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_QOS_DATA) ||
683 fctype == end_htole16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_NULLFUNC) ||
684 fctype == end_htole16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_QOS_NULLFUNC)
686 beacon = (void*)(data+offset);
687 if(!memcmp(beacon->receiver, "\x01\x80\xc2", 3) ||
688 !memcmp(beacon->receiver, "\x01\x00\x0c\xcc\xcc\xcc", 6) ||
689 !memcmp(beacon->receiver, "\xff\xff\xff", 3))
690 return -1;
692 int wifi_nr = get_wlan_by_mac(beacon->bssid);
693 /* ignore packets sent to unknown APs */
694 if(wifi_nr == -1) return -1;
695 unsigned char *client_mac;
696 if(memcmp(beacon->source, beacon->bssid, 6))
697 /* client to AP */
698 client_mac = beacon->source;
699 else
700 /* AP to client */
701 client_mac = beacon->receiver;
703 struct wlaninfo apbuf, *ap=&apbuf;
704 get_wlan(wifi_nr, ap);
705 struct ap_client* c = get_client(ap, client_mac);
706 if(!c) c = add_client(ap, client_mac);
707 write_wlan(wifi_nr, ap);
708 setminmax(temp.last_rssi);
709 if(client_mac == beacon->source) {
710 set_client_rssi(&temp, c);
711 return -1;
712 } else return wifi_nr;
715 else {
716 return -1;
719 //while(htonl(*(flags++)) & (1U << IEEE80211_RADIOTAP_EXT)) next_chunk+=4;
720 //dprintf(2, "got data\n");
721 //dump();
722 } else usleep(1);
723 return -1;
726 #if 0
727 static int next_chan(int chan) {
728 if(++chan > 11) chan = 1;
729 return chan;
731 #elif 1
732 static int next_chan(int chan) {
733 static char chanlist[]={1,5,9,13,2,6,10,14,3,7,11,4,8,12};
734 int i = 0;
735 for(i = 0; i < sizeof chanlist && chanlist[i] != chan; i++);
736 if(i >=13) return chanlist[0];
737 return chanlist[++i];
739 #else
740 static int next_chan(int chan) {
741 switch (chan) {
742 case 1: case 2: case 3: case 4: case 5:
743 return 6;
744 case 6: case 7: case 8: case 9: case 10:
745 return 11;
746 case 11: case 12: case 13:
747 /* uncomment next line if you leave in a country using chan 14 */
748 //return 14;
749 case 14:
750 return 1;
751 default:
752 assert(0);
753 return 0;
756 #endif
758 static struct {int w, h;} dim;
760 #define BGCOL RGB(33, 66, 133)
761 #define COL_BLACK RGB(0,0,0)
762 #define COL_WHITE RGB(255,255,255)
763 #define COL_YELLOW RGB(255,255,0)
765 static void draw_bg() {
766 unsigned x, y;
767 console_setcolor(t, 0, BGCOL);
768 for(y=0; y < dim.h; y++) {
769 console_goto(t, 0, y);
770 for(x = 0; x < dim.w; x++)
771 console_printchar(t, ' ', 0);
775 static unsigned reduce_color(unsigned val) {
776 unsigned a = val;
777 if (colorcount <= 8) {
778 a /= 85;
779 if(a > 2) a = 2;
780 static const unsigned tbl[] = {0, 127, 255};
781 a = tbl[a];
783 return a;
786 static int get_r(unsigned percent) {
787 return reduce_color((50 - percent/2) * 5);
789 static int get_g(unsigned percent) {
790 return reduce_color(percent/2 * 5);
792 static int get_a(unsigned age) {
793 return reduce_color(5+((50 - age)*5));
795 #define LINES_PER_NET 1
796 static void selection_move(int dir) {
797 if((int)selection+dir < 0) dir=0;
798 lock();
799 unsigned l = DYNA_COUNT(wlans);
800 unlock();
801 if((int)selection+dir >= l ||
802 ((int)selection+dir)*LINES_PER_NET+1 >= dim.h) dir=0;
803 selection += dir;
806 static volatile unsigned bms;
807 static void set_bms(float percent) {
808 float max = 800, min=50;
809 float range=max-min;
810 float rpercent = range/100.f;
811 bms = min + (100 - percent) * rpercent;
814 char *mac2str(unsigned char mac[static 6], char buf[static 18]) {
815 unsigned m, x;
816 char hextab[16] = "0123456789abcdef";
817 for(m = 0, x=0 ; m<6; m++, x+=3) {
818 buf[x] = hextab[mac[m]>>4];
819 buf[x+1] = hextab[mac[m]&15];
820 buf[x+2] = ':';
822 buf[17]=0;
823 return buf;
826 static char* format_timestamp(uint64_t timestamp, char *ts) {
827 #define TSTP_SEC 1000000ULL /* 1 MHz clock -> 1 million ticks/sec */
828 #define TSTP_MIN (TSTP_SEC * 60ULL)
829 #define TSTP_HOUR (TSTP_MIN * 60ULL)
830 #define TSTP_DAY (TSTP_HOUR * 24ULL)
831 uint64_t rem;
832 unsigned days, hours, mins, secs;
833 days = timestamp / TSTP_DAY;
834 rem = timestamp % TSTP_DAY;
835 hours = rem / TSTP_HOUR;
836 rem %= TSTP_HOUR;
837 mins = rem / TSTP_MIN;
838 rem %= TSTP_MIN;
839 secs = rem / TSTP_SEC;
840 sprintf(ts, "%ud %02u:%02u:%02u", days, hours, mins, secs);
841 return ts;
844 static const char* enctype_str(enum enctype et) {
845 static const char enc_name[][5] = {
846 [ET_OPEN]= "OPEN",
847 [ET_WEP] = "WEP",
848 [ET_WPA] = "WPA",
849 [ET_WPA2]= "WPA2",
851 if(et > ET_MAX) abort();
852 return enc_name[et];
855 static char* sanitize_string(char *s, char *new) {
856 size_t i,j, l = strlen(s), ls=l;
857 for(i=0,j=0;i<ls;i++) {
858 if(s[i] < ' ' || s[i] > 127) {
859 sprintf(new + j, "\\x%02x", s[i] & 0xff);
860 j += 3;
861 } else new[j] = s[i];
862 j++;
864 new[j] = 0;
865 return new;
868 #define ESSID_PRINT_START 1
869 #define ESSID_PRINT_END 32+ESSID_PRINT_START
870 #define ESSID_PRINT_LEN (ESSID_PRINT_END - ESSID_PRINT_START)
872 static void dump_wlan_info(unsigned wlanidx) {
873 struct wlaninfo wtmp, *w = &wtmp;
874 get_wlan(wlanidx, w);
875 unsigned line = 3, x, col1, col2, col3, col4;
876 console_setcolor(t, 0, BGCOL);
877 console_setcolor(t, 1, COL_WHITE);
879 col1 = x = 2;
880 console_goto(t, ++x, line);
881 char macbuf[18];
882 console_printf(t, "MAC %s", mac2str(w->mac, macbuf));
883 x += 25;
884 col2 = x;
886 console_goto(t, ++x, line);
887 console_printf(t, "CHAN %d", (int) w->channel);
888 x += 9 + 5;
889 col3 = x;
891 console_goto(t, ++x, line);
892 char ts[64];
893 format_timestamp(w->timestamp, ts);
894 console_printf(t, "UP: %s", ts);
895 x += strlen(ts) +5;
896 col4 = x;
898 console_goto(t, ++x, line);
899 console_printf(t, "BI %d ms", (int) w->beaconinterval);
901 line++;
902 x = col1;
904 console_goto(t, ++x, line);
905 console_printf(t, "AVG %.2f dBm", (double)w->total_rssi/(double)w->count);
906 //x += 14 + 5;
907 x = col2;
909 console_goto(t, ++x, line);
910 console_printf(t, "CURR %d dBm", w->last_rssi);
911 //x += 10 + 5;
912 x = col3;
914 console_goto(t, ++x, line);
915 console_printf(t, "MIN %d dBm", w->min_rssi);
916 //x += 9 + 5;
917 x = col4;
919 console_goto(t, ++x, line);
920 console_printf(t, "MAX %d dBm", w->max_rssi);
921 x += 9 + 5;
923 line++;
924 x = col1;
925 console_goto(t, ++x, line);
926 console_printf(t, "%4s", enctype_str(w->enctype));
928 x = col2;
929 console_goto(t, ++x, line);
930 if(w->wps) console_printf(t, "WPS %d.%d", w->wps->version >> 4, w->wps->version & 15);
932 x = col3;
933 console_goto(t, ++x, line);
934 if(w->wps) console_printf(t, w->wps->locked == 1 ? "LOCKED" : "-");
936 char sanbuf[WPS_MAX_STR_LEN*4+1];
938 x = col4;
939 console_goto(t, ++x, line);
940 if(w->wps && w->wps->manufacturer[0]) {
941 sanitize_string(w->wps->manufacturer, sanbuf);
942 console_printf(t, "%s", sanbuf);
945 line++;
947 x = col1;
948 console_goto(t, ++x, line);
949 if(w->wps && w->wps->model_name[0]) {
950 sanitize_string(w->wps->model_name, sanbuf);
951 console_printf(t, "%s", sanbuf);
954 x = col2;
955 console_goto(t, ++x, line);
956 if(w->wps && w->wps->model_number[0]) {
957 sanitize_string(w->wps->model_number, sanbuf);
958 console_printf(t, "%s", sanbuf);
961 x = col3;
962 console_goto(t, ++x, line);
963 if(w->wps && w->wps->device_name[0]) {
964 sanitize_string(w->wps->device_name, sanbuf);
965 console_printf(t, "%s", sanbuf);
968 x = col4;
969 console_goto(t, ++x, line);
970 if(w->wps && w->wps->serial[0]) {
971 sanitize_string(w->wps->serial, sanbuf);
972 console_printf(t, "%s", sanbuf);
975 line += 2;
976 struct ap_client *c;
977 for(c = w->clients; c; c = c->next) {
978 x = col1;
979 console_goto(t, ++x, line);
980 console_printf(t, "client %s", mac2str(c->mac, macbuf));
981 line++;
985 static void dump_wlan_at(unsigned wlanidx, unsigned line) {
986 console_goto(t, 0, line);
987 console_setcolor(t, 0, BGCOL);
989 console_setcolor(t, 1, COL_YELLOW);
991 if(wlanidx == selection) {
992 console_printchar(t, '>', 0);
993 } else {
994 console_printchar(t, ' ', 0);
997 struct wlaninfo wtmp, *w = &wtmp;
998 get_wlan(wlanidx, w);
1000 long long now = getutime64();
1001 long long age_ms = (now - w->last_seen)/1000;
1002 age_ms=MIN(5000, age_ms)/100; /* seems we end up with a range 0-50 */
1003 unsigned a = get_a(age_ms);
1005 console_setcolor(t, 1, RGB(a,a,a));
1006 console_goto(t, ESSID_PRINT_START, line);
1008 char macbuf[18];
1010 if(*w->essid) {
1011 char essid_san[32*4+1];
1012 sanitize_string(w->essid, essid_san);
1013 console_printf(t, "%*s", ESSID_PRINT_LEN, essid_san);
1014 } else
1015 console_printf(t, "<hidden> %*s", ESSID_PRINT_LEN-9, mac2str(w->mac, macbuf));
1017 console_goto(t, ESSID_PRINT_END, line);
1018 console_printchar(t, ' ', 0);
1020 int scale = max - min;
1021 int width = dim.w - (ESSID_PRINT_LEN+2);
1022 unsigned x;
1023 float widthpercent = (float)width/100.f;
1024 float scalepercent = (float)scale/100.f;
1025 float scaleup = (float)width / (float)scale;
1026 double avg = (double)w->total_rssi/(double)w->count;
1027 float avg_percent = (avg - (float)min) / scalepercent;
1028 float curr_percent = ((float)w->last_rssi - (float)min) / scalepercent;
1029 int avg_marker = (avg - (float)min) * scaleup;
1030 int curr_marker = ((float)w->last_rssi - (float)min) * scaleup;
1032 for(x = 0; x < width; x++) {
1033 rgb_t step_color;
1034 if(wlanidx == selection) step_color = RGB(get_r(x/widthpercent),get_g(x/widthpercent),0);
1035 else step_color = RGB(get_r(x/widthpercent),get_r(x/widthpercent),get_r(x/widthpercent));
1036 console_setcolor(t, 0, step_color);
1037 if(x != curr_marker) console_setcolor(t, 1, COL_BLACK);
1038 else console_setcolor(t, 1, COL_WHITE);
1039 if(x == avg_marker) console_printchar(t, 'I', 0);
1040 else if (x == curr_marker) console_printchar(t, '|', 0);
1041 else if(x == 0) console_printchar(t, '[', 0);
1042 else if(x == width-1) console_printchar(t, ']', 0);
1043 else console_printchar(t, ' ', 0);
1047 static void dump_wlan(unsigned idx) {
1048 if(idx * LINES_PER_NET + 1 > dim.h || (selected && selection != idx)) return;
1049 dump_wlan_at(idx, selected ? 1 : idx * LINES_PER_NET);
1050 if(selected) dump_wlan_info(idx);
1053 int this_wlan_scale_mode = 1;
1054 static void calc_bms(unsigned wlanidx) {
1055 long long now = getutime64();
1056 struct wlaninfo wtmp, *w = &wtmp;
1057 get_wlan(wlanidx, w);
1058 int my_min = this_wlan_scale_mode ? w->min_rssi : min;
1059 int my_max = this_wlan_scale_mode ? w->max_rssi : max;
1060 long long age_ms = (now - w->last_seen)/1000;
1061 age_ms=MIN(5000, age_ms)/100; /* seems we end up with a range 0-50 */
1062 int scale = my_max - my_min;
1063 float scalepercent = (float)scale/100.f;
1064 float curr_percent = ((float)w->last_rssi - (float)my_min) / scalepercent;
1065 if(age_ms < 15) set_bms(curr_percent);
1066 else bms = 0;
1069 static void dump(void) {
1070 unsigned i, l;
1071 lock();
1072 l = DYNA_COUNT(wlans);
1073 unlock();
1074 //dprintf(1, "********************\n");
1075 //draw_bg();
1076 for(i=0;i<l;i++)
1077 dump_wlan(i);
1078 console_refresh(t);
1081 static void initconcol() {
1082 console_init(t);
1083 char *p;
1084 int rw=1024,rh=768;
1085 if((p = getenv("RES"))) {
1086 char *q = strchr(p, 'x');
1087 if(q) {
1088 unsigned l = q-p;
1089 char b[64];
1090 memcpy(b,p,l);
1091 b[l] = 0;
1092 rw=atoi(b);
1093 strcpy(b,++q);
1094 rh=atoi(b);
1097 point reso = {rw, rh};
1098 console_init_graphics(&co, reso, FONT);
1099 console_getbounds(t, &dim.w, &dim.h);
1100 colorcount = console_getcolorcount(t);
1101 #ifdef NO_COLOR
1102 (*console_setcolor)(t, 0, COL_WHITE);
1103 (*console_setcolor)(t, 1, COL_BLACK);
1104 #endif
1105 draw_bg();
1108 static unsigned char blip[] = {0x52, 0x51, 0x51, 0x51, 0xC4, 0x4C, 0xF4, 0xF4, 0xF3,0xEF};
1109 static unsigned blip_frame(int idx) {
1110 idx = idx % (2*sizeof(blip));
1111 if(idx>=sizeof(blip)) idx=(2*sizeof(blip))-idx;
1112 return blip[idx];
1115 static volatile float volume = .5;
1117 static void generate_blip(unsigned char* data, size_t bufsize) {
1118 int i;
1119 for(i=0;i<bufsize;i++) {
1120 float f = blip_frame(i) * volume;
1121 data[i] = f;
1125 static void volume_change(int dir) {
1126 volume += dir * 0.1;
1127 if(volume < 0) volume = 0;
1128 if(volume > 1) volume = 1;
1131 static void* blip_thread(void* arg) {
1132 struct AudioCTX ao;
1133 audio_init(&ao);
1134 unsigned char buf[100], silence[1000];
1135 generate_blip(buf, sizeof(buf));
1136 memset(silence, buf[99], sizeof silence);
1137 long long t = getutime64();
1138 unsigned passed = 0;
1139 float myvol = volume;
1140 while(selected) {
1141 if(myvol != volume) {
1142 generate_blip(buf, sizeof(buf));
1143 myvol = volume;
1145 if(bms && (getutime64() - t)/1000 >= bms) {
1146 audio_write(&ao, buf, sizeof buf);
1147 t = getutime64();
1149 audio_write(&ao, silence, sizeof silence);
1150 usleep(1);
1152 audio_close(&ao);
1153 return 0;
1156 static void* nop_thread(void *arg) {
1157 return 0;
1160 static void* chanwalker_thread(void* arg) {
1161 char* itf = arg;
1162 int channel = 1, delay = 800;
1163 long long tm = 0;
1164 if(filebased) return 0;
1165 while(!selected) {
1166 if((getutime64() - tm)/1000 >= delay) {
1167 int ret = set_channel(itf, channel = next_chan(channel));
1168 if(ret == -1) {
1169 if(console_getbackendtype(t) == cb_sdl)
1170 dprintf(2, "oops couldnt switch to chan %d\n", channel);
1172 tm = getutime64();
1174 usleep(1000);
1176 return 0;
1178 #include <sys/ioctl.h>
1179 #include <sys/socket.h>
1180 /* set an interface up or down, depending on whether up is set.
1181 if checkonly is true, no change will be made and the result
1182 of the function can be interpreted as "isdownup".
1183 if the interface was already up/down, 2 is returned.
1184 if the interface was successfully upped/downed, 1 is returned.
1185 0 is only returned if checkonly is set and the interface was not
1186 in the queried state.
1187 -1 is returned on error. */
1188 static int ifdownup(const char *dev, int up, int checkonly) {
1189 int fd, ret = -1;
1190 if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) return -1;
1191 struct ifreq ifr = {0};
1192 strcpy(ifr.ifr_name, dev);
1193 if(ioctl(fd, SIOCGIFFLAGS, &ifr) <0) goto done;
1194 int isup = ifr.ifr_flags & IFF_UP;
1195 if((up && isup) || (!up && !isup)) ret = 2;
1196 else if (checkonly) ret = 0;
1197 else {
1198 if(up) ifr.ifr_flags |= IFF_UP;
1199 else ifr.ifr_flags &= ~(IFF_UP);
1200 ret = (ioctl(fd, SIOCSIFFLAGS, &ifr) >= 0);
1202 done:
1203 close(fd);
1204 return ret;
1207 #include "wireless-lite.h"
1208 static int getiwmode(const char *dev) {
1209 int fd, ret = -1;
1210 if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) return -1;
1211 struct iwreq iwr = {0};
1212 strcpy(iwr.ifr_name, dev);
1213 if(ioctl(fd, SIOCGIWMODE, &iwr) >=0) ret = iwr.u.mode;
1214 close(fd);
1215 return ret;
1218 static int setiwmode(const char *dev, int mode) {
1219 int fd, ret = -1;
1220 if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) return -1;
1221 struct iwreq iwr = {.u.mode = mode};
1222 strcpy(iwr.ifr_name, dev);
1223 ret = ioctl(fd, SIOCSIWMODE, &iwr);
1224 close(fd);
1225 return ret;
1228 static void* capture_thread(void*arg) {
1229 pcap_t *foo = arg;
1230 while(!stop) {
1231 int ret = process_frame(foo);
1232 long long tmp = getutime64();
1233 if(ret >= 0) {
1234 lock();
1235 wlans[ret].last_seen = tmp;
1236 unlock();
1239 return 0;
1242 static pthread_t bt, wt;
1243 static const char *itf;
1245 static void set_selection(int on) {
1246 selected = on;
1247 if(selected) {
1248 pthread_join(wt, 0);
1249 draw_bg();
1250 pthread_create(&bt, 0, blip_thread, 0);
1251 if(!filebased) {
1252 lock();
1253 int ch = wlans[selection].channel;
1254 unlock();
1255 set_channel(itf, ch);
1257 } else {
1258 pthread_create(&wt, 0, chanwalker_thread, (void*)itf);
1259 pthread_join(bt, 0);
1264 #include "netgui.c"
1266 int main(int argc,char**argv) {
1267 wlans = DYNA_NEW(struct wlaninfo);
1268 int c;
1269 int fixed_chan = 0;
1270 while((c = getopt(argc, argv, "c:")) != EOF) switch(c) {
1271 case 'c': fixed_chan = atoi(optarg); break;
1272 default: return usage(argv[0]);
1274 if(!argv[optind]) return usage(argv[0]);
1276 itf = argv[optind];
1277 min = 127;
1278 max = -60;
1279 outfd = -1;
1280 char errbuf[PCAP_ERRBUF_SIZE];
1281 pcap_t *foo;
1282 if(strchr(itf, '.') && access(itf, R_OK) == 0) {
1283 filebased = 1;
1284 foo = pcap_open_offline(itf, errbuf);
1285 } else {
1286 foo = pcap_create(itf, errbuf);
1287 char fnbuf[512];
1288 snprintf(fnbuf, sizeof fnbuf, "tmp.%s.pcap", itf);
1289 outfd= open(fnbuf, O_WRONLY|O_CREAT|O_TRUNC,0660);
1290 if(outfd != -1)
1291 pcapfile_write_header(outfd);
1293 if(!foo) { dprintf(2, "%s\n", errbuf); return 1; }
1295 int ret, wasdown, orgmode;
1297 if(filebased) goto skip;
1299 if((orgmode = getiwmode(itf)) != IW_MODE_MONITOR) {
1300 if((ret = ifdownup(itf, 0, 0)) == -1) {
1301 iferr:;
1302 perror("error setting up interface - maybe need to run as root.");
1304 wasdown = (ret == 2);
1305 if(setiwmode(itf, IW_MODE_MONITOR) == -1) goto iferr;
1306 } else {
1307 wasdown = (ifdownup(itf, 0, 1) == 2);
1309 if(ifdownup(itf, 1, 0) == -1) goto iferr;
1311 if(pcap_activate(foo)) {
1312 dprintf(2, "pcap_activate failed: %s\n", pcap_geterr(foo));
1313 return 1;
1316 skip:;
1318 initconcol();
1320 signal(SIGINT, sigh);
1322 int channel = fixed_chan ? fixed_chan : 1;
1323 if(fixed_chan) set_channel(itf, fixed_chan);
1324 long long tm = 0;
1325 pthread_t ct, nt;
1326 void *(*cw_func)(void*);
1327 if(filebased || fixed_chan) cw_func = nop_thread;
1328 else cw_func = chanwalker_thread;
1329 pthread_create(&wt, 0, cw_func, (void*) itf);
1330 pthread_create(&ct, 0, capture_thread, foo);
1332 struct netgui_config netgui_cfg;
1333 if(getenv("NETGUI")) {
1334 netgui_start(&netgui_cfg, "0.0.0.0", 9876);
1337 while(!stop) {
1338 long long tmp = getutime64();
1339 if((tmp-tm) >= (1000000 / GUI_FPS)) {
1340 tm = tmp;
1341 dump();
1344 if(selected) calc_bms(selection);
1345 int k = console_getkey_nb(t);
1347 switch(k) {
1348 case CK_CURSOR_RIGHT: this_wlan_scale_mode = !this_wlan_scale_mode; break;
1349 case '+': case '0': volume_change(+1); break;
1350 case '-': case '9': volume_change(-1); break;
1351 case CK_CURSOR_DOWN: selection_move(1);break;
1352 case CK_CURSOR_UP: selection_move(-1);break;
1353 case CK_RETURN:
1354 //selected = !selected;
1355 set_selection(!selected);
1356 break;
1357 case CK_QUIT:
1358 case CK_ESCAPE: stop = 1; break;
1360 usleep(1000);
1363 pcap_breakloop(foo); // this doesn't actually seem to work
1365 if(getenv("NETGUI")) {
1366 netgui_stop(&netgui_cfg);
1369 if(selected) {
1370 selected = 0;
1371 pthread_join(bt, 0);
1372 } else {
1373 selected = 1;
1374 pthread_join(wt, 0);
1377 // since our capture_thread uses blocking reads in order to keep CPU usage
1378 // minimal, we need to get the current read cancelled - and if no packets
1379 // arrive, this can take a *long* time. since pcap_breakloop() doesn't actually
1380 // seem to work, the only way i found to break out of the read is to actually
1381 // bring down the interface - so this must happen before we join the thread
1382 // and close the pcap handle.
1383 if(!filebased) {
1384 if(wasdown || orgmode != IW_MODE_MONITOR) ifdownup(itf, 0, 0);
1385 if(orgmode != IW_MODE_MONITOR) setiwmode(itf, orgmode);
1386 if(!wasdown && orgmode != IW_MODE_MONITOR) ifdownup(itf, 1, 0);
1389 pthread_join(ct, 0);
1391 pcap_close(foo);
1392 console_cleanup(t);
1393 if(outfd != -1) close(outfd);
1394 return 0;