git: 9front

ref: f9d6ecb14eb37a27dc9a7e70fc387dd13052bd32
dir: /sys/src/9/ip/ipaux.c/

View raw version
#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"../port/error.h"
#include	"ip.h"
#include	"ipv6.h"

char *v6hdrtypes[Maxhdrtype] =
{
	[HBH]		"HopbyHop",
	[ICMP]		"ICMP",
	[IGMP]		"IGMP",
	[GGP]		"GGP",
	[IPINIP]	"IP",
	[ST]		"ST",
	[TCP]		"TCP",
	[UDP]		"UDP",
	[ISO_TP4]	"ISO_TP4",
	[RH]		"Routinghdr",
	[FH]		"Fraghdr",
	[IDRP]		"IDRP",
	[RSVP]		"RSVP",
	[AH]		"Authhdr",
	[ESP]		"ESP",
	[ICMPv6]	"ICMPv6",
	[NNH]		"Nonexthdr",
	[ISO_IP]	"ISO_IP",
	[IGRP]		"IGRP",
	[OSPF]		"OSPF",
};

/*
 *  well known IPv6 addresses
 */
uchar v6Unspecified[IPaddrlen] = {
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0
};
uchar v6loopback[IPaddrlen] = {
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0x01
};

uchar v6linklocal[IPaddrlen] = {
	0xfe, 0x80, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0
};
uchar v6linklocalmask[IPaddrlen] = {
	0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff,
	0, 0, 0, 0,
	0, 0, 0, 0
};
int v6llpreflen = 8;	/* link-local prefix length in bytes */

uchar v6multicast[IPaddrlen] = {
	0xff, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0
};
uchar v6multicastmask[IPaddrlen] = {
	0xff, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0
};
int v6mcpreflen = 1;	/* multicast prefix length */

uchar v6allnodesN[IPaddrlen] = {
	0xff, 0x01, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0x01
};
uchar v6allroutersN[IPaddrlen] = {
	0xff, 0x01, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0x02
};
uchar v6allnodesNmask[IPaddrlen] = {
	0xff, 0xff, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0
};
int v6aNpreflen = 2;	/* all nodes (N) prefix */

uchar v6allnodesL[IPaddrlen] = {
	0xff, 0x02, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0x01
};
uchar v6allroutersL[IPaddrlen] = {
	0xff, 0x02, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0x02
};
uchar v6allnodesLmask[IPaddrlen] = {
	0xff, 0xff, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0
};
int v6aLpreflen = 2;	/* all nodes (L) prefix */

uchar v6solicitednode[IPaddrlen] = {
	0xff, 0x02, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0x01,
	0xff, 0, 0, 0
};
uchar v6solicitednodemask[IPaddrlen] = {
	0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff,
	0xff, 0x0, 0x0, 0x0
};
int v6snpreflen = 13;

ushort
ptclcsum(Block *bp, int offset, int len)
{
	uchar *addr;
	ulong losum, hisum;
	ushort csum;
	int odd, blocklen, x;

	/* Correct to front of data area */
	while(bp != nil && offset && offset >= BLEN(bp)) {
		offset -= BLEN(bp);
		bp = bp->next;
	}
	if(bp == nil)
		return 0;

	addr = bp->rp + offset;
	blocklen = BLEN(bp) - offset;

	if(bp->next == nil) {
		if(blocklen < len)
			len = blocklen;
		return ptclbsum(addr, len) ^ 0xffff;
	}

	losum = 0;
	hisum = 0;

	odd = 0;
	while(len) {
		x = blocklen;
		if(len < x)
			x = len;

		csum = ptclbsum(addr, x);
		if(odd)
			hisum += csum;
		else
			losum += csum;
		odd = (odd+x) & 1;
		len -= x;

		bp = bp->next;
		if(bp == nil)
			break;
		blocklen = BLEN(bp);
		addr = bp->rp;
	}

	losum += hisum>>8;
	losum += (hisum&0xff)<<8;
	while((csum = losum>>16) != 0)
		losum = csum + (losum & 0xffff);

	return losum ^ 0xffff;
}

enum
{
	Isprefix= 16,
};

#define CLASS(p) ((*(uchar*)(p))>>6)

void
ipv62smcast(uchar *smcast, uchar *a)
{
	ipmove(smcast, v6solicitednode);
	smcast[13] = a[13];
	smcast[14] = a[14];
	smcast[15] = a[15];
}

/*
 *  parse a hex mac address
 */
int
parsemac(uchar *to, char *from, int len)
{
	char nip[4];
	char *p;
	int i;

	p = from;
	memset(to, 0, len);
	for(i = 0; i < len; i++){
		if(p[0] == '\0' || p[1] == '\0')
			break;

		nip[0] = p[0];
		nip[1] = p[1];
		nip[2] = '\0';
		p += 2;

		to[i] = strtoul(nip, 0, 16);
		if(*p == ':')
			p++;
	}
	return i;
}

/*
 *  return multicast version if any
 */
int
ipismulticast(uchar *ip)
{
	if(isv4(ip)){
		if(isv4mcast(&ip[IPv4off]))
			return V4;
	}
	else if(isv6mcast(ip))
		return V6;
	return 0;
}

/*
 *  return ip version of a connection
 */
int
convipvers(Conv *c)
{
	if(isv4(c->raddr) && isv4(c->laddr) || ipcmp(c->raddr, IPnoaddr) == 0)
		return V4;
	else
		return V6;
}

/*
 *  hashing tcp, udp, ... connections
 */
static ulong
iphash(uchar *sa, ushort sp, uchar *da, ushort dp)
{
	return ((sa[IPaddrlen-1]<<24) ^ (sp << 16) ^ (da[IPaddrlen-1]<<8) ^ dp ) % Nipht;
}

void
iphtadd(Ipht *ht, Iphash *h)
{
	ulong hv;

	if(ipcmp(h->raddr, IPnoaddr) != 0)
		h->match = IPmatchexact;
	else {
		if(ipcmp(h->laddr, IPnoaddr) != 0){
			if(h->lport == 0)
				h->match = IPmatchaddr;
			else
				h->match = IPmatchpa;
		} else {
			if(h->lport == 0)
				h->match = IPmatchany;
			else
				h->match = IPmatchport;
		}
	}
	lock(ht);
	hv = iphash(h->raddr, h->rport, h->laddr, h->lport);
	h->nextiphash = ht->tab[hv];
	ht->tab[hv] = h;
	unlock(ht);
}

void
iphtrem(Ipht *ht, Iphash *h)
{
	ulong hv;
	Iphash **l;

	lock(ht);
	hv = iphash(h->raddr, h->rport, h->laddr, h->lport);
	for(l = &ht->tab[hv]; (*l) != nil; l = &(*l)->nextiphash)
		if(*l == h){
			(*l) = h->nextiphash;
			h->nextiphash = nil;
			break;
		}
	unlock(ht);
}

/* look for a matching iphash with the following precedence
 *	raddr,rport,laddr,lport
 *	laddr,lport
 *	*,lport
 *	laddr,*
 *	*,*
 */
Iphash*
iphtlook(Ipht *ht, uchar *sa, ushort sp, uchar *da, ushort dp)
{
	ulong hv;
	Iphash *h;

	lock(ht);
	/* exact 4 pair match (connection) */
	hv = iphash(sa, sp, da, dp);
	for(h = ht->tab[hv]; h != nil; h = h->nextiphash){
		if(h->match != IPmatchexact)
			continue;
		if(sp == h->rport && dp == h->lport
		&& ipcmp(sa, h->raddr) == 0 && ipcmp(da, h->laddr) == 0){
			unlock(ht);
			return h;
		}
	}

	/* match local address and port */
	hv = iphash(IPnoaddr, 0, da, dp);
	for(h = ht->tab[hv]; h != nil; h = h->nextiphash){
		if(h->match != IPmatchpa)
			continue;
		if(dp == h->lport && ipcmp(da, h->laddr) == 0){
			unlock(ht);
			return h;
		}
	}

	/* match just port */
	hv = iphash(IPnoaddr, 0, IPnoaddr, dp);
	for(h = ht->tab[hv]; h != nil; h = h->nextiphash){
		if(h->match != IPmatchport)
			continue;
		if(dp == h->lport){
			unlock(ht);
			return h;
		}
	}

	/* match local address */
	hv = iphash(IPnoaddr, 0, da, 0);
	for(h = ht->tab[hv]; h != nil; h = h->nextiphash){
		if(h->match != IPmatchaddr)
			continue;
		if(ipcmp(da, h->laddr) == 0){
			unlock(ht);
			return h;
		}
	}

	/* look for something that matches anything */
	hv = iphash(IPnoaddr, 0, IPnoaddr, 0);
	for(h = ht->tab[hv]; h != nil; h = h->nextiphash){
		if(h->match != IPmatchany)
			continue;
		unlock(ht);
		return h;
	}
	unlock(ht);
	return nil;
}

static Translation*
deltrans(Proto *p, Translation *q)
{
	netlog(p->f, Logtrans, "trans: removing %s!%I!%d -> %I!%d -> %I!%d\n",
		p->name,
		q->forward.raddr, q->forward.rport,
		q->backward.laddr, q->backward.lport,
		q->forward.laddr, q->forward.lport);

	iphtrem(p->ht, &q->forward);
	iphtrem(p->ht, &q->backward);

	return q;
}

static Translation*
newtrans(Proto *p)
{
	Translation *q;
	ulong now;
	int num;

	/* Use freelist */
	if((q = p->freetranslations) != nil)
		return q;

	/* Reuse expired entries */
	num = 0;
	now = NOW;
	for(q = p->translations; q != nil; q = q->next) {
		if(++num >= 1000 || (now - q->time) >= 5*60*1000)
			return deltrans(p, q);
	}

	/* Allocate, never freed */
	q = malloc(sizeof(*q));
	if(q == nil)
		return nil;
	q->next = nil;
	q->link = nil;
	return q;
}

/*
 * Move entry to front of Proto.translations
 * and update the timestamp.
 *
 * Proto is locked.
 */
static Translation*
transupdate(Proto *p, Translation *q)
{
	q->time = NOW;

	/* already head of translations list? */
	if(q->link == &p->translations)
		return q;

	/* unlink (from translations or freelist) */
	if(q->link != nil && (*q->link = q->next) != nil)
		q->next->link = q->link;

	/* link to front of translations list */
	if((q->next = p->translations) != nil)
		q->next->link = &q->next;
	p->translations = q;
	q->link = &p->translations;

	return q;
}

static Translation*
addtrans(Proto *p, uchar *da, int dp, uchar *sa, int sp, uchar *la, int lp, Routehint rh)
{
	Translation *q;

	if((q = newtrans(p)) == nil)
		return nil;

	/* Match what needs to be forwarded */
	q->forward.trans = 1;
	q->forward.lport = dp;
	q->forward.rport = sp;
	ipmove(q->forward.laddr, da);
	ipmove(q->forward.raddr, sa);

	/* Match what comes back to us */
	q->backward.trans = 2;
	q->backward.lport = lp;
	ipmove(q->backward.laddr, la);
	if(p->ipproto == 1 || p->ipproto == 17){
		/* ICMP and UDP allow reply from anyone (for hole punching) */
		q->backward.rport = 0;
		ipmove(q->backward.raddr, IPnoaddr);
	} else {
		q->backward.rport = dp;
		ipmove(q->backward.raddr, da);
	}

	netlog(p->f, Logtrans, "trans: adding %s!%I!%d -> %I!%d -> %I!%d\n",
		p->name,
		q->forward.raddr, q->forward.rport,
		q->backward.laddr, q->backward.lport,
		q->forward.laddr, q->forward.lport);

	memmove(&q->Routehint, &rh, sizeof(rh));

	iphtadd(p->ht, &q->forward);
	iphtadd(p->ht, &q->backward);

	return transupdate(p, q);
}

/*
 * Called with the 4-tuple (sa,sp,da,dp)
 * that should be source translated,
 * returning the translation.
 *
 * Proto is locked.
 */
Translation*
transforward(Proto *p, uchar *sa, int sp, uchar *da, int dp, Route *r)
{
	uchar la[IPaddrlen];
	Routehint rh;
	Iphash *iph;
	Ipifc *ifc;
	int lp;

	/* Translation already exists? */
	iph = iphtlook(p->ht, sa, sp, da, dp);
	if(iph != nil){
		if(iph->trans == 1)
			return transupdate(p, iphforward(iph));
		if(iph->match == IPmatchexact){
			netlog(p->f, Logtrans, "trans: backwards collision: %s!%I!%d -> %I!%d\n",
				p->name, sa, sp, da, dp);
			return nil;
		}
	}

	/* No route means dont make a new entry */
	if(r == nil)
		return nil;

	/* Bad forward route? */
	if((ifc = r->ifc) == nil){
		netlog(p->f, Logtrans, "trans: no interface: %s!%I!%d -> %I!%d\n",
			p->name, sa, sp, da, dp);
		return nil;
	}

	/* Bad source address? */
	if(ipismulticast(sa) || ipforme(p->f, sa) != 0){
		netlog(p->f, Logtrans, "trans: bad source address: %s!%I!%d -> %I!%d\n",
			p->name, sa, sp, da, dp);
		return nil;
	}

	/* Find a source address on the destination interface */
	rlock(ifc);
	memmove(la, v4prefix, IPv4off);
	if(!ipv4local(ifc, la+IPv4off, 0, (r->type & (Rifc|Runi|Rbcast|Rmulti))? da+IPv4off: r->v4.gate)){
		runlock(ifc);
		netlog(p->f, Logtrans, "trans: no source ip: %s!%I!%d -> %I!%d\n",
			p->name, sa, sp, da, dp);
		return nil;
	}
	runlock(ifc);

	/* Check backward route */
	rh.a = nil;
	rh.r = nil;
	if(ipismulticast(da))
		r = v4lookup(p->f, sa+IPv4off, la+IPv4off, nil);
	else
		r = v4lookup(p->f, sa+IPv4off, da+IPv4off, &rh);
	if(r == nil || (r->ifc == ifc && !ifc->reflect)){
		netlog(p->f, Logtrans, "trans: bad backward route: %s!%I!%d <- %I <- %I!%d\n",
			p->name, sa, sp, la, da, dp);
		return nil;
	}

	/* Find local port */
	lp = unusedlport(p);
	if(lp <= 0){
		netlog(p->f, Logtrans, "trans: no local port: %s!%I!%d <- %I <- %I!%d\n",
			p->name, sa, sp, la, da, dp);
		return nil;
	}

	return addtrans(p, da, dp, sa, sp, la, lp, rh);
}

/*
 * Check if backward translation is valid and
 * update timestamp.
 *
 * Proto is locked.
 */
Translation*
transbackward(Proto *p, Iphash *iph)
{
	if(iph == nil || iph->trans != 2)
		return nil;
	return transupdate(p, iphbackward(iph));
}

enum {
	Transfmtsize = 3*16 + 3*6,	/* excluding \0 */
};

long
transfsize(Proto *p)
{
	Translation *q;
	int n = 0;

	if(p->ht == nil || p->forward == nil)
		return -1;

	qlock(p);
	for(q = p->translations; q != nil; q = q->next)
		n += Transfmtsize;
	qunlock(p);

	return n;
}

long
transread(Proto *p, char *a, ulong off, int n)
{
	Translation *q;
	char *b, *s;
	int m;

	if(n > READSTR)
		n = READSTR;

	if((b = s = malloc(n+1)) == nil)
		error(Enomem);

	qlock(p);
	for(q = p->translations; q != nil; q = q->next){
		if(n < Transfmtsize)
			break;
		m = snprint(s, n+1, "%-15I %5d %-15I %5d %-15I %5d\n",
			q->forward.laddr, q->forward.lport,	/* da, dp */
			q->forward.raddr, q->forward.rport,	/* sa, sp */
			q->backward.laddr, q->backward.lport);	/* la, lp */
		if(off) {
			if(m < off)
				off -= m;
			else
				off = 0;
			continue;
		}
		s += m;
		n -= m;
	}
	qunlock(p);

	/* copy outside of lock */
	if(waserror()){
		free(b);
		nexterror();
	}
	n = s - b;
	memmove(a, b, n);
	free(b);
	poperror();

	return n;
}

static void
flushtrans(Proto *p)
{
	Translation *q, *l;

	/* Delete all active translations */
	l = nil;
	for(q = p->translations; q != nil; q = q->next)
		l = deltrans(p, q);
	if(l == nil)
		return;

	/* Tanslation list becomes freelist */
	q = p->translations;
	p->translations = nil;
	if((l->next = p->freetranslations) != nil)
		l->next->link = &l->next;
	q->link = &p->freetranslations;
	p->freetranslations = q;
}

long
transwrite(Proto *p, char *a, ulong off, int n)
{
	char buf[Transfmtsize+1], *f[6];
	uchar da[IPaddrlen], sa[IPaddrlen], la[IPaddrlen];
	int dp, sp, lp;
	Routehint rh;
	Route *r;

	if(p->ht == nil || p->forward == nil)
		error("protocol does not support translations");

	if(n == 0 && off == 0){
		qlock(p);
		flushtrans(p);
		qunlock(p);
		return n;
	}
	if((uint)n >= sizeof(buf))
		error(Ebadctl);
	memmove(buf, a, n);
	buf[n] = '\0';
	if(tokenize(buf, f, 6) != 6)
		error(Ebadctl);

	if(parseip(da, f[0]) == -1 || !isv4(da))
		error(Ebadctl);
	if((dp = strtoul(f[1], nil, 0)) & ~0xffff)
		error(Ebadctl);
	if(parseip(sa, f[2]) == -1 || !isv4(sa))
		error(Ebadctl);
	if((sp = strtoul(f[3], nil, 0)) & ~0xffff)
		error(Ebadctl);
	if(parseip(la, f[4]) == -1 || !isv4(la))
		error(Ebadctl);
	if((lp = strtoul(f[5], nil, 0)) & ~0xffff)
		error(Ebadctl);

	if(ipforme(p->f, la) != Runi)
		error("local ip not found");

	if(ipismulticast(sa) || ipforme(p->f, sa) != 0)
		error("bad source address");

	/* check forward route */
	r = v4lookup(p->f, da+IPv4off, sa+IPv4off, nil);
	if(r == nil || (r->type & Rtrans) == 0)
		error("no translating route to desination");

	/* check backward route */
	rh.a = nil;
	rh.r = nil;
	if(ipismulticast(da))
		r = v4lookup(p->f, sa+IPv4off, la+IPv4off, nil);
	else
		r = v4lookup(p->f, sa+IPv4off, da+IPv4off, &rh);
	if(r == nil)
		error("no route to soruce address");

	qlock(p);
	if(waserror()){
		qunlock(p);
		nexterror();
	}
	if(lportinuse(p, lp))
		error("local port in use");
	addtrans(p, da, dp, sa, sp, la, lp, rh);
	qunlock(p);
	poperror();

	return n;
}

/*
 * Checksum adjusting hnputs()
 */
void
hnputs_csum(void *p, ushort v, uchar *pcsum)
{
	ulong csum;
	ushort o;

	csum = nhgets(pcsum)^0xFFFF;
	o = nhgets(p);
	hnputs(p, v);
	if(((uchar*)p - pcsum) & 1){
		o = o << 8 | o >> 8;
		v = v << 8 | v >> 8;
	}
	csum += o ^ 0xFFFF;
	csum += v;
	while(v = csum >> 16)
		csum = (csum & 0xFFFF) + v;
	hnputs(pcsum, csum^0xFFFF);
}