ref: f9d6ecb14eb37a27dc9a7e70fc387dd13052bd32
dir: /sys/src/9/ip/ipaux.c/
#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);
}