#include #include #include #include #include <9p.h> #include #include #include #include char mntpt[64]; char iface[128]; // manage ifconfig int iflag; Controlset *cs; int ctldeletequits = 1; int mainstacksize = 16384; // write to 'log' control. void wlog(char *fmt, ...) { va_list arg; static int nlog; char buf[512]; if(cs == nil) return; if(++nlog > 10) chanprint(cs->ctl, "log delete 0"); va_start(arg, fmt); vsnprint(buf, sizeof(buf), fmt, arg); va_end(arg); chanprint(cs->ctl, "log add %q", buf); } // count lines static int wc(char *data) { int nl; char *p; nl = 0; for(;;){ p = strchr(data, '\n'); if(p == nil) break; nl++; data = p + 1; } return nl; } typedef struct Node Node; struct Node { int active; char bssid[13]; int cap; int age; // ms ago? int channel; char essid[40]; }; enum { MaxNodes = 128 /* see /sys/src/9/pc/wifi.c */ }; typedef struct Ifstats Ifstats; struct Ifstats { QLock; char essid[35]; char bssid[13]; char status[40]; int channel; Node nodes[MaxNodes]; int nnodes; }; int parseIfstats(char *data, Ifstats *ifs, int donodes) { char **tok, *etok[6]; int nl, i, ntok, netok, inode; Node *n; assert(data != nil); assert(ifs != nil); nl = wc(data); nl++; tok = mallocz(sizeof(char*) * nl, 1); if(tok == nil) return -1; ntok = getfields(data, tok, nl, 0, "\n"); ifs->essid[0] = 0; ifs->bssid[0] = 0; ifs->status[0] = 0; ifs->channel = 0; if(donodes){ ifs->nnodes = 0; // count nodes; for(i = 0; i < ntok; i++){ if(strncmp(tok[i], "node:", 5) == 0) ifs->nnodes++; } if(ifs->nnodes > MaxNodes){ ifs->nnodes = MaxNodes; wlog("warning: node list truncated"); } } inode = 0; for(i = 0; i < ntok; i++){ netok = gettokens(tok[i], etok, nelem(etok), " "); switch(netok){ case 0: break; default: case 1: if(strcmp(etok[0], "essid:") == 0) break; Weird: wlog("failed to parse line with tag %q - not wifi ether?", etok[0]); break; case 2: if(strcmp(etok[0], "essid:") == 0) strncpy(ifs->essid, etok[1], sizeof(ifs->essid)); else if(strcmp(etok[0], "bssid:") == 0) strncpy(ifs->bssid, etok[1], sizeof(ifs->bssid)); else if(strcmp(etok[0], "status:") == 0) strncpy(ifs->status, etok[1], sizeof(ifs->status)); else if(strcmp(etok[0], "channel:") == 0) ifs->channel = atoi(etok[1]); else if(strcmp(etok[0], "brsne:") != 0 && strncmp(etok[0], "txkey", 5) != 0 && strncmp(etok[0], "rxkey", 5) != 0) goto Weird; break; case 3: if(strcmp(etok[0], "status:") == 0) snprint(ifs->status, sizeof(ifs->status), "%s %s", etok[1], etok[2]); else if(strcmp(etok[0], "essid:") == 0) snprint(ifs->essid, sizeof(ifs->essid), "%s %s", etok[1], etok[2]); else goto Weird; break; case 5: // empty name.. case 6: if(donodes){ if(strcmp(etok[0], "node:") != 0) goto Weird; if(atoi(etok[3]) > 30000){ // not seen in 30s, skip ifs->nnodes--; break; } if(inode >= ifs->nnodes) break; n = &ifs->nodes[inode]; strncpy(n->bssid, etok[1], sizeof(n->bssid)); n->active = !strcmp(ifs->bssid, n->bssid); n->cap = atoi(etok[2]); n->age = atoi(etok[3]); n->channel = atoi(etok[4]); if(netok == 6) strncpy(n->essid, etok[5], sizeof(n->essid)); inode++; } break; } } free(tok); return 0; } static char* snarf(char *f) { int fd; long n; char *buf; fd = open(f, OREAD); if(fd < 0) return nil; buf = ctlmalloc(8192+1); n = read(fd, buf, 8192); close(fd); if(n < 0){ free(buf); return nil; } buf[n] = 0; return buf; } static int writef(char *f, char *fmt, ...) { int fd; va_list arg; if((fd = open(f, OWRITE)) < 0) return -1; va_start(arg, fmt); vfprint(fd, fmt, arg); va_end(arg); close(fd); return 0; } void ipcmd(void *v) { void **vv; char *verb, *ifc; Channel *pidc; vv = v; pidc = vv[0]; ifc = vv[1]; verb = vv[2]; rfork(RFENVG|RFFDG|RFNAMEG); close(2); procexecl(pidc, "/bin/ip/ipconfig", "ipconfig", "-F", "-p", "ether", ifc, verb, nil); wlog("exec failed: %r"); threadexits("exec"); } void flushnet(Channel *pidc) { void *arg[3]; char buf[128]; arg[0] = pidc;; arg[1] = iface; arg[2] = "unbind"; snprint(buf, sizeof(buf), "%s/ndb", mntpt); //close(open(buf, OWRITE|OTRUNC)); snprint(buf, sizeof(buf), "%s/iproute", mntpt); writef(buf, "flush\n"); proccreate(ipcmd, arg, 8192); recvul(pidc); } enum { IPEXIT = 0, IPSTART = 1, IPSTOP = 2, }; void ipconfigproc(void *v) { Channel *c, *pidc; ulong cmd, pid; void *arg[3]; c = v; pidc = chancreate(sizeof(ulong), 0); pid = ~0UL; arg[0] = pidc; arg[1] = iface; arg[2] = nil; threadsetname("ipconfig %s", iface); for(;;){ cmd = recvul(c); switch(cmd){ case IPEXIT: if(pid != ~0UL) postnote(PNPROC, (int)pid, "die yankee pig dog"); flushnet(pidc); chanclose(pidc); threadexits(nil); break; case IPSTART: if(pid == ~0UL){ flushnet(pidc); sleep(500); proccreate(ipcmd, arg, 8192); pid = recvul(pidc); wlog("ipconfig running as pid %d", (int)pid); } break; case IPSTOP: if(pid != ~0UL){ wlog("killing ipconfig pid %d", (int)pid); postnote(PNPROC, (int)pid, "die yankee pig dog"); } flushnet(pidc); pid = ~0UL; break; } } } int sortnodes(void *a, void *b) { Node *aa, *bb; aa = a; bb = b; /* age sort */ /* if(aa->age < bb->age) return -1; if(aa->age > bb->age) return 1; return 0; */ if(aa->active > bb->active) return -1; if(aa->active < bb->active) return 1; return strcmp(aa->bssid, bb->bssid); } void timerproc(void *v) { Channel *c; int cnt; c = v; cnt = 0; threadsetname("timer"); for(;;){ sleep(500); if(++cnt > 30){ cnt = 0; sendul(c, 1); } else sendul(c, 0); } } void listproc(void *v) { void **vv; char *buf, stf[128], liste[128]; int rv, i; ulong nop; Channel *ev, *timer; Alt alts[3]; Ifstats *ifs; Node *n; vv = v; ifs = vv[0]; ev = vv[1]; snprint(stf, sizeof(stf), "%s/ifstats", iface); threadsetname("ifstats %s", stf); timer = chancreate(sizeof(ulong), 0); proccreate(timerproc, timer, 8192); // ev == scan button pressed alts[0].c = ev; alts[0].v = &nop; alts[0].op = CHANRCV; alts[1].c = timer; alts[1].v = &nop; alts[1].op = CHANRCV; alts[2].op = CHANEND; for(;;){ rv = alt(alts); if(rv < 0) sysfatal("alt: interrupted: %r"); buf = snarf(stf); if(buf == nil){ wlog("unable to read %s: %r", stf); goto Sleep; } qlock(ifs); if(parseIfstats(buf, ifs, nop) < 0){ qunlock(ifs); free(buf); wlog("unable to parse %s: %r", stf); goto Sleep; } free(buf); chanprint(cs->ctl, "status value 'status: %s'", ifs->status[0] == 0 ? "" : ifs->status); chanprint(cs->ctl, "status accumulate 'essid: %s'", ifs->essid[0] == 0 ? "" : ifs->essid); chanprint(cs->ctl, "status accumulate 'bssid: %s'", ifs->bssid); chanprint(cs->ctl, "status add 'channel: %d'", ifs->channel); int good = (ifs->status[0] != 0 && strcmp(ifs->status, "associated") == 0); chanprint(cs->ctl, "list selectcolor %s", good ? "green" : "yellow"); // only update list if scan button was pressed. if(nop){ if(0) wlog("found %d nodes", ifs->nnodes); chanprint(cs->ctl, "list clear"); qsort(ifs->nodes, ifs->nnodes, sizeof(Node), sortnodes); for(i = 0; i < ifs->nnodes; i++){ n = &ifs->nodes[i]; snprint(liste, sizeof(liste), " %-20.20s ... %-13s %-2d %6dms", n->essid, n->bssid, n->channel, n->age); chanprint(cs->ctl, "list accumulate %q", liste); if(n->active) chanprint(cs->ctl, "list select %d 1", i); } chanprint(cs->ctl, "slider max %d", ifs->nnodes); chanprint(cs->ctl, "list topline 0"); chanprint(cs->ctl, "list show"); } qunlock(ifs); Sleep: sleep(1); } } enum { Disconnect = 0xFFFFFFUL }; void wifiproc(void *v) { char clone[128]; ulong sel; void **vv; Channel *ev, *sync, *ipcmd; Ifstats *ifs; Node *n; vv = v; ifs = vv[0]; ev = vv[1]; sync = vv[2]; ipcmd = chancreate(sizeof(ulong), 0); proccreate(ipconfigproc, ipcmd, 8192); snprint(clone, sizeof(clone), "%s/clone", iface); threadsetname("clone %s", clone); for(;;){ sel = recvul(ev); qlock(ifs); if(sel >= ifs->nnodes && sel != Disconnect) goto Sync; if(sel != Disconnect){ n = &ifs->nodes[sel]; wlog("selected %q %s", n->essid, n->bssid); if(writef(clone, "essid %q\n", n->essid) < 0) wlog("clone: %r"); if(writef(clone, "bssid %q\n", n->bssid) < 0) wlog("clone: %r"); if(iflag){ sendul(ipcmd, IPSTOP); sendul(ipcmd, IPSTART); } }else{ wlog("disconnecting"); if(writef(clone, "essid ''\n") < 0) wlog("clone: %r"); if(writef(clone, "bssid ffffffffffff\n") < 0) wlog("clone: %r"); if(iflag) sendul(ipcmd, IPSTOP); } Sync: qunlock(ifs); sendul(sync, 0); } } void usage(void) { fprint(2, "usage: %s [-x netmntpt] [-i] [etherN]\n", argv0); threadexitsall("usage"); } void threadmain(int argc, char *argv[]) { char *evs; Control *scanbut, *status, *slider, *list, *log; Channel *ev, *schan, *wchan, *rchan; void *parg[3]; Ifstats ifs; strcpy(mntpt, "/net"); snprint(iface, sizeof(iface), "%s/ether0", mntpt); ARGBEGIN{ case 'i': iflag++; break; case 'x': strcpy(mntpt, EARGF(usage())); break; default: usage(); }ARGEND if(argc >= 1) usage(); memset(&ifs, 0, sizeof(Ifstats)); if(argc == 1) snprint(iface, sizeof(iface), "%s/%s", mntpt, argv[0]); initdraw(nil, nil, "wifi"); initcontrols(); cs = newcontrolset(screen, nil, nil, nil); cs->clicktotype = 1; scanbut = createtextbutton(cs, "scan"); chanprint(cs->ctl, "scan border 1\nscan text Scan\nscan align center"); status = createtext(cs, "status"); chanprint(cs->ctl, "status border 1"); slider = createslider(cs, "slider"); chanprint(cs->ctl, "slider border 1"); chanprint(cs->ctl, "slider vis 8"); chanprint(cs->ctl, "slider size 12 10 12 2048"); chanprint(cs->ctl, "slider absolute 0"); chanprint(cs->ctl, "slider format '%%q: list topline %%d'"); list = createtext(cs, "list"); chanprint(cs->ctl, "list border 1"); chanprint(cs->ctl, "list align center"); log = createtext(cs, "log"); chanprint(cs->ctl, "log border 1"); chanprint(cs->ctl, "log scroll 1"); USED(status); USED(log); ev = chancreate(sizeof(char*), 1); controlwire(slider, "event", cs->ctl); controlwire(list, "event", ev); controlwire(scanbut, "event", ev); activate(slider); activate(list); activate(scanbut); resizecontrolset(cs); schan = chancreate(sizeof(ulong), 0); parg[0] = &ifs; parg[1] = schan; proccreate(listproc, parg, 16384); // initiate scan sendul(schan, 1); wchan = chancreate(sizeof(ulong), 0); rchan = chancreate(sizeof(ulong), 0); parg[0] = &ifs; parg[1] = wchan; parg[2] = rchan; proccreate(wifiproc, parg, 16384); threadsetname("wifi %s", argv[0]); for(evs = recvp(ev); evs != nil; evs = recvp(ev)){ char *evtok[4]; int nevtok; nevtok = tokenize(evs, evtok, nelem(evtok)); // if click scan, reread ifstats if(nevtok == 3 && strcmp(evtok[0], "scan:") == 0 && strcmp(evtok[1], "value") == 0){ chanprint(cs->ctl, "scan value 0"); sendul(schan, 1); } // if click in list, set essid/bssid if(nevtok == 4 && strcmp(evtok[0], "list:") == 0 && strcmp(evtok[1], "select") == 0){ if(atoi(evtok[3]) > 0){ sendul(wchan, strtoul(evtok[2], nil, 10)); recvul(rchan); } else { sendul(wchan, Disconnect); recvul(rchan); } sendul(schan, 1); } } threadexitsall(nil); } void resizecontrolset(Controlset*) { Rectangle r, r1, r2, r3, r4; Rectangle s; if(getwindow(display, Refnone) < 0) sysfatal("resize failed: %r"); r = insetrect(screen->r, 10); r1 = r; r2 = r; r3 = r; r4 = r; s = r; // scan r1.max.y = r1.min.y+2+(font->height*4); r1.max.x = r1.min.x + 80; // status r2.max.y = r2.min.y+2+(font->height*4); r2.min.x = r1.max.x + 5; // log r4.min.y = r4.max.y - 2 - (font->height*5); r4.max.y = r.max.y; s.min.y = r2.max.y+5; s.max.y = r4.min.y-5; s.max.x = s.min.x + 20; // more room for list r3.min.x = s.max.x + 5; r3.min.y = r2.max.y+5; //r2.max.y = r2.min.y+2+(font->height*10); r3.max.y = r4.min.y - 5; chanprint(cs->ctl, "scan rect %R\nscan show", r1); chanprint(cs->ctl, "status rect %R\nstatus show", r2); chanprint(cs->ctl, "slider rect %R\nslider show", s); chanprint(cs->ctl, "list rect %R\nlist show", r3); chanprint(cs->ctl, "log rect %R\nlog show", r4); }