shithub: riscv

ref: 4aae319f76dd7157149aad6a67c3ca9b03a7f30a
dir: /sys/src/9/pc/wifi.c/

View raw version
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "pool.h"
#include "ureg.h"
#include "../port/error.h"
#include "../port/netif.h"

#include "etherif.h"
#include "wifi.h"

typedef struct SNAP SNAP;
struct SNAP
{
	uchar	dsap;
	uchar	ssap;
	uchar	control;
	uchar	orgcode[3];
	uchar	type[2];
};

enum {
	SNAPHDRSIZE = 8,
};

static char Snone[] = "new";
static char Sconn[] = "connecting";
static char Sauth[] = "authenticated";
static char Sunauth[] = "unauthentictaed";
static char Sassoc[] = "associated";
static char Sunassoc[] = "unassociated";

void
wifiiq(Wifi *wifi, Block *b)
{
	SNAP s;
	Wifipkt w;
	Etherpkt *e;

	if(BLEN(b) < WIFIHDRSIZE)
		goto drop;
	memmove(&w, b->rp, WIFIHDRSIZE);
	switch(w.fc[0] & 0x0c){
	case 0x00:	/* management */
		if((w.fc[1] & 3) != 0x00)	/* STA->STA */
			break;
		qpass(wifi->iq, b);
		return;
	case 0x04:	/* control */
		break;
	case 0x08:	/* data */
		b->rp += WIFIHDRSIZE;
		switch(w.fc[0] & 0xf0){
		case 0x80:
			b->rp += 2;
			if(w.fc[1] & 0x80)
				b->rp += 4;
		case 0x00:
			break;
		default:
			goto drop;
		}
		if(BLEN(b) < SNAPHDRSIZE)
			break;
		memmove(&s, b->rp, SNAPHDRSIZE);
		if(s.dsap != 0xAA || s.ssap != 0xAA || s.control != 3)
			break;
		if(s.orgcode[0] != 0 || s.orgcode[1] != 0 || s.orgcode[2] != 0)
			break;
		b->rp += SNAPHDRSIZE-ETHERHDRSIZE;
		e = (Etherpkt*)b->rp;
		switch(w.fc[1] & 0x03){
		case 0x00:	/* STA->STA */
			memmove(e->d, w.a1, Eaddrlen);
			memmove(e->s, w.a2, Eaddrlen);
			break;
		case 0x01:	/* STA->AP */
			memmove(e->d, w.a3, Eaddrlen);
			memmove(e->s, w.a2, Eaddrlen);
			break;
		case 0x02:	/* AP->STA */
			memmove(e->d, w.a1, Eaddrlen);
			memmove(e->s, w.a3, Eaddrlen);
			break;
		case 0x03:	/* AP->AP */
			goto drop;
		}
		memmove(e->type, s.type, 2);
		etheriq(wifi->ether, b, 1);
		return;
	}
drop:
	freeb(b);
}

static void
wifitx(Wifi *wifi, Wnode *wn, Block *b)
{
	Wifipkt *w;
	uint seq;

	seq = incref(&wifi->txseq);
	seq <<= 4;

	w = (Wifipkt*)b->rp;
	w->dur[0] = 0;
	w->dur[1] = 0;
	w->seq[0] = seq;
	w->seq[1] = seq>>8;

	(*wifi->transmit)(wifi, wn, b);
}


static Wnode*
nodelookup(Wifi *wifi, uchar *bssid, int new)
{
	Wnode *wn, *nn;

	if(memcmp(bssid, wifi->ether->bcast, Eaddrlen) == 0)
		return nil;
	if((wn = wifi->bss) != nil){
		if(memcmp(wn->bssid, bssid, Eaddrlen) == 0){
			wn->lastseen = MACHP(0)->ticks;
			return wn;
		}
	}
	if((nn = wifi->node) == wn)
		nn++;
	for(wn = wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++){
		if(wn == wifi->bss)
			continue;
		if(memcmp(wn->bssid, bssid, Eaddrlen) == 0){
			wn->lastseen = MACHP(0)->ticks;
			return wn;
		}
		if(wn->lastseen < nn->lastseen)
			nn = wn;
	}
	if(!new)
		return nil;
	memmove(nn->bssid, bssid, Eaddrlen);
	nn->ssid[0] = 0;
	nn->ival = 0;
	nn->cap = 0;
	nn->aid = 0;
	nn->channel = 0;
	nn->lastseen = MACHP(0)->ticks;
	return nn;
}

static void
sendauth(Wifi *wifi, Wnode *bss)
{
	Wifipkt *w;
	Block *b;
	uchar *p;

	b = allocb(WIFIHDRSIZE + 3*2);
	w = (Wifipkt*)b->wp;
	w->fc[0] = 0xB0;	/* auth request */
	w->fc[1] = 0x00;	/* STA->STA */
	memmove(w->a1, bss->bssid, Eaddrlen);	/* ??? */
	memmove(w->a2, wifi->ether->ea, Eaddrlen);
	memmove(w->a3, bss->bssid, Eaddrlen);
	b->wp += WIFIHDRSIZE;
	p = b->wp;
	*p++ = 0;	/* alg */
	*p++ = 0;
	*p++ = 1;	/* seq */
	*p++ = 0;
	*p++ = 0;	/* status */
	*p++ = 0;
	b->wp = p;
	wifitx(wifi, bss, b);
}

static void
sendassoc(Wifi *wifi, Wnode *bss)
{
	Wifipkt *w;
	Block *b;
	uchar *p;

	b = allocb(WIFIHDRSIZE + 128);
	w = (Wifipkt*)b->wp;
	w->fc[0] = 0x00;	/* assoc request */
	w->fc[1] = 0x00;	/* STA->STA */
	memmove(w->a1, bss->bssid, Eaddrlen);	/* ??? */
	memmove(w->a2, wifi->ether->ea, Eaddrlen);
	memmove(w->a3, bss->bssid, Eaddrlen);
	b->wp += WIFIHDRSIZE;
	p = b->wp;
	*p++ = 1;	/* capinfo */
	*p++ = 0;
	*p++ = 16;	/* interval */
	*p++ = 16>>8;
	*p++ = 0;	/* SSID */
	*p = strlen(bss->ssid);
	memmove(p+1, bss->ssid, *p);
	p += 1+*p;
	*p++ = 1;	/* RATES (BUG: these are all lies!) */
	*p++ = 4;
	*p++ = 0x82;
	*p++ = 0x84;
	*p++ = 0x8b;
	*p++ = 0x96;
	b->wp = p;
	wifitx(wifi, bss, b);
}

static void
recvassoc(Wifi *wifi, Wnode *wn, uchar *d, int len)
{
	uint s;

	if(len < 2+2+2)
		return;

	d += 2;	/* caps */
	s = d[0] | d[1]<<8;
	d += 2;
	switch(s){
	case 0x00:
		wn->aid = d[0] | d[1]<<8;
		wifi->status = Sassoc;
		break;
	default:
		wn->aid = 0;
		wifi->status = Sunassoc;
		return;
	}
}

static void
recvbeacon(Wifi *, Wnode *wn, uchar *d, int len)
{
	uchar *e, *x;
	uchar t, m[256/8];

	if(len < 8+2+2)
		return;

	d += 8;	/* timestamp */
	wn->ival = d[0] | d[1]<<8;
	d += 2;
	wn->cap = d[0] | d[1]<<8;
	d += 2;

	memset(m, 0, sizeof(m));
	for(e = d + len; d+2 <= e; d = x){
		d += 2;
		x = d + d[-1];
		t = d[-2];

		/* skip double entries */
		if(m[t/8] & 1<<(t%8))
			continue;
		m[t/8] |= 1<<(t%8);

		switch(t){
		case 0:	/* SSID */
			len = 0;
			while(len < 32 && d+len < x && d[len] != 0)
				len++;
			if(len == 0)
				continue;
			if(len != strlen(wn->ssid) || strncmp(wn->ssid, (char*)d, len) != 0){
				strncpy(wn->ssid, (char*)d, len);
				wn->ssid[len] = 0;
			}
			break;
		case 3:	/* DSPARAMS */
			if(d != x)
				wn->channel = d[0];
			break;
		}
	}
}

static void
wifiproc(void *arg)
{
	Wifi *wifi;
	Wifipkt *w;
	Wnode *wn;
	Block *b;

	b = nil;
	wifi = arg;
	for(;;){
		if(b != nil)
			freeb(b);
		if((b = qbread(wifi->iq, 100000)) == nil)
			break;
		w = (Wifipkt*)b->rp;
		switch(w->fc[0] & 0xf0){
		case 0x50:	/* probe response */
		case 0x80:	/* beacon */
			if((wn = nodelookup(wifi, w->a3, 1)) == nil)
				continue;
			b->rp += WIFIHDRSIZE;
			recvbeacon(wifi, wn, b->rp, BLEN(b));
			if(wifi->bss == nil && wifi->essid[0] != 0 && strcmp(wifi->essid, wn->ssid) == 0){
				wifi->bss = wn;
				wifi->status = Sconn;
				sendauth(wifi, wn);
			}
			continue;
		}
		if(memcmp(w->a1, wifi->ether->ea, Eaddrlen))
			continue;
		if((wn = nodelookup(wifi, w->a3, 0)) == nil)
			continue;
		if(wn != wifi->bss)
			continue;
		switch(w->fc[0] & 0xf0){
		case 0x10:	/* assoc response */
		case 0x30:	/* reassoc response */
			b->rp += WIFIHDRSIZE;
			recvassoc(wifi, wn, b->rp, BLEN(b));
			break;
		case 0xb0:	/* auth */
			wifi->status = Sauth;
			sendassoc(wifi, wn);
			break;
		case 0xc0:	/* deauth */
			wn->aid = 0;
			wifi->status = Sunauth;
			sendauth(wifi, wn);
			break;
		}
	}
	pexit("wifi in queue closed", 0);
}

static void
wifietheroq(Wifi *wifi, Block *b)
{
	Etherpkt e;
	Wifipkt *w;
	Wnode *bss;
	SNAP *s;

	bss = wifi->bss;
	if(bss == nil || BLEN(b) < ETHERHDRSIZE){
		freeb(b);
		return;
	}
	memmove(&e, b->rp, ETHERHDRSIZE);

	b->rp += ETHERHDRSIZE;
	b = padblock(b, WIFIHDRSIZE + SNAPHDRSIZE);

	w = (Wifipkt*)b->rp;
	w->fc[0] = 0x08;	/* data */
	w->fc[1] = 0x01;	/* STA->AP */
	memmove(w->a1, bss->bssid, Eaddrlen);
	memmove(w->a2, e.s, Eaddrlen);
	memmove(w->a3, e.d, Eaddrlen);

	s = (SNAP*)(b->rp + WIFIHDRSIZE);
	s->dsap = s->ssap = 0xAA;
	s->control = 0x03;
	s->orgcode[0] = 0;
	s->orgcode[1] = 0;
	s->orgcode[2] = 0;
	memmove(s->type, e.type, 2);

	wifitx(wifi, bss, b);
}

static void
wifoproc(void *arg)
{
	Ether *ether;
	Wifi *wifi;
	Block *b;

	wifi = arg;
	ether = wifi->ether;
	while((b = qbread(ether->oq, 1000000)) != nil)
		wifietheroq(wifi, b);
	pexit("ether out queue closed", 0);
}

Wifi*
wifiattach(Ether *ether, void (*transmit)(Wifi*, Wnode*, Block*))
{
	char name[32];
	Wifi *wifi;

	wifi = malloc(sizeof(Wifi));
	wifi->ether = ether;
	wifi->iq = qopen(8*1024, 0, 0, 0);
	wifi->transmit = transmit;
	wifi->status = Snone;

	snprint(name, sizeof(name), "#l%dwifi", ether->ctlrno);
	kproc(name, wifiproc, wifi);
	snprint(name, sizeof(name), "#l%dwifo", ether->ctlrno);
	kproc(name, wifoproc, wifi);

	return wifi;
}

long
wifictl(Wifi *wifi, void *buf, long n)
{
	Cmdbuf *cb;
	Wnode *wn;

	cb = nil;
	if(waserror()){
		free(cb);
		nexterror();
	}
	cb = parsecmd(buf, n);
	if(cb->f[0] && strcmp(cb->f[0], "essid") == 0){
		if(cb->f[1] == nil){
			wifi->essid[0] = 0;
			wifi->bss = nil;
			wifi->status = Snone;
		} else {
			strncpy(wifi->essid, cb->f[1], 32);
			wifi->essid[32] = 0;
			for(wn = wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++)
				if(strcmp(wifi->essid, wn->ssid) == 0){
					wifi->bss = wn;
					wifi->status = Sconn;
					sendauth(wifi, wn);
					break;
				}
		}
	}
	poperror();
	free(cb);
	return n;
}

long
wifistat(Wifi *wifi, void *buf, long n, ulong off)
{
	static uchar zeros[Eaddrlen];
	char *s, *p, *e;
	Wnode *wn;
	long now;

	p = s = smalloc(4096);
	e = s + 4096;

	p = seprint(p, e, "status: %s\n", wifi->status);
	p = seprint(p, e, "essid: %s\n", wifi->essid);
	wn = wifi->bss;
	p = seprint(p, e, "bssid: %E\n", wn != nil ? wn->bssid : zeros);

	now = MACHP(0)->ticks;
	for(wn = wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++){
		if(wn->lastseen == 0)
			continue;
		p = seprint(p, e, "node: %E %.4x %d %ld %d %s\n",
			wn->bssid, wn->cap, wn->ival, TK2MS(now - wn->lastseen), wn->channel, wn->ssid);
	}
	n = readstr(off, buf, n, s);
	free(s);
	return n;
}