plan9fox/sys/src/9/ip/ipaux.c
cinap_lenrek d2a7d88662 devip: implement network address translation routes
This adds a new route "t"-flag that enables network address translation,
replacing the source address (and local port) of a forwarded packet to
one of the outgoing interface.

The state for a translation is kept in a new Translation structure,
which contains two Iphash entries, so it can be inserted into the
per protocol 4-tuple hash table, requiering no extra lookups.

Translations have a low overhead (~200 bytes on amd64),
so we can have many of them. They get reused after 5 minutes
of inactivity or when the per protocol limit of 1000 entries
is reached (then the one with longest inactivity is reused).

The protocol needs to export a "forward" function that is responsible
for modifying the forwarded packet, and then handle translations in
its input function for iphash hits with Iphash.trans != 0.

This patch also fixes a few minor things found during development:

- Include the Iphash in the Conv structure, avoiding estra malloc
- Fix ttl exceeded check (ttl < 1 -> ttl <= 1)
- Router should not reply with ttl exceeded for multicast flows
- Extra checks for icmp advice to avoid protocol confusions.
2022-03-12 20:53:17 +00:00

570 lines
10 KiB
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)
{
assert(IPaddrlen == 16);
memmove(smcast, v6solicitednode, IPaddrlen);
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;
}
/*
* Move entry to front of Proto.translations
* and update the timestamp.
*
* Proto is locked.
*/
static Translation*
transupdate(Proto *p, Translation *q)
{
q->time = NOW;
/* unlink */
if(q->link != nil && (*q->link = q->next) != nil)
q->next->link = q->link;
/* link to front */
if((q->next = p->translations) != nil)
q->next->link = &q->next;
p->translations = q;
q->link = &p->translations;
return 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, Ipht *ht, uchar *sa, int sp, uchar *da, int dp, Route *r)
{
uchar ia[IPaddrlen];
Routehint rh;
Translation *q;
Iphash *iph;
Ipifc *ifc;
int lport;
ulong now;
int num;
/* Translation already exists? */
iph = iphtlook(ht, sa, sp, da, dp);
if(iph != nil) {
if(iph->trans != 1)
return nil;
return transupdate(p, iphforward(iph));
}
/* 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;
}
/* Bad forward route? */
if(r == nil || (ifc = r->ifc) == nil){
netlog(p->f, Logtrans, "trans: no forward route: %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(ia, v4prefix, IPv4off);
if(!ipv4local(ifc, ia+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, ia+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, ia, da, dp);
return nil;
}
/* Find local port */
lport = unusedlport(p);
if(lport <= 0){
netlog(p->f, Logtrans, "trans: no local port: %s!%I!%d <- %I <- %I!%d\n",
p->name, sa, sp, ia, da, dp);
return nil;
}
/* 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){
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(ht, &q->forward);
iphtrem(ht, &q->backward);
break;
}
}
if(q == nil){
q = malloc(sizeof(*q));
if(q == nil)
return nil;
q->link = 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 = lport;
ipmove(q->backward.laddr, ia);
if(p->ipproto == 1 || ipismulticast(da)){
q->backward.rport = 0;
ipmove(q->backward.raddr, IPnoaddr);
} else {
q->backward.rport = dp;
ipmove(q->backward.raddr, da);
}
memmove(&q->Routehint, &rh, sizeof(rh));
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);
iphtadd(ht, &q->forward);
iphtadd(ht, &q->backward);
return transupdate(p, q);
}
/*
* 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));
}
/*
* Checksum adjusting hnputs()
*/
void
hnputs_csum(void *p, ushort v, uchar *pcsum)
{
ulong csum;
assert((((uchar*)p - pcsum) & 1) == 0);
csum = nhgets(pcsum)^0xFFFF;
csum += nhgets(p)^0xFFFF;
csum += v;
hnputs(p, v);
while(v = csum >> 16)
csum = (csum & 0xFFFF) + v;
hnputs(pcsum, csum^0xFFFF);
}