plan9fox/sys/src/9/ip/udp.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

658 lines
14 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"
#define DPRINT if(0)print
enum
{
UDP_UDPHDR_SZ = 8,
UDP4_PHDR_OFF = 8,
UDP4_PHDR_SZ = 12,
UDP4_IPHDR_SZ = 20,
UDP6_IPHDR_SZ = 40,
UDP6_PHDR_SZ = 40,
UDP6_PHDR_OFF = 0,
IP_UDPPROTO = 17,
UDP_USEAD7 = 52,
Udprxms = 200,
Udptickms = 100,
Udpmaxxmit = 10,
};
typedef struct Udp4hdr Udp4hdr;
struct Udp4hdr
{
/* ip header */
uchar vihl; /* Version and header length */
uchar tos; /* Type of service */
uchar length[2]; /* packet length */
uchar id[2]; /* Identification */
uchar frag[2]; /* Fragment information */
uchar ttl; /* Time to live */
uchar udpproto; /* Protocol */
uchar udpplen[2]; /* Header plus data length */
uchar udpsrc[IPv4addrlen]; /* Ip source */
uchar udpdst[IPv4addrlen]; /* Ip destination */
/* udp header */
uchar udpsport[2]; /* Source port */
uchar udpdport[2]; /* Destination port */
uchar udplen[2]; /* data length */
uchar udpcksum[2]; /* Checksum */
};
typedef struct Udp6hdr Udp6hdr;
struct Udp6hdr {
uchar viclfl[4];
uchar len[2];
uchar nextheader;
uchar hoplimit;
uchar udpsrc[IPaddrlen];
uchar udpdst[IPaddrlen];
/* udp header */
uchar udpsport[2]; /* Source port */
uchar udpdport[2]; /* Destination port */
uchar udplen[2]; /* data length */
uchar udpcksum[2]; /* Checksum */
};
/* MIB II counters */
typedef struct Udpstats Udpstats;
struct Udpstats
{
uvlong udpInDatagrams;
ulong udpNoPorts;
ulong udpInErrors;
uvlong udpOutDatagrams;
};
typedef struct Udppriv Udppriv;
struct Udppriv
{
Ipht ht;
/* MIB counters */
Udpstats ustats;
/* non-MIB stats */
ulong csumerr; /* checksum errors */
ulong lenerr; /* short packet */
};
void udpkick(void *x, Block *bp);
/*
* protocol specific part of Conv
*/
typedef struct Udpcb Udpcb;
struct Udpcb
{
uchar headers;
};
static char*
udpconnect(Conv *c, char **argv, int argc)
{
char *e;
Udppriv *upriv;
upriv = c->p->priv;
e = Fsstdconnect(c, argv, argc);
Fsconnected(c, e);
if(e != nil)
return e;
iphtadd(&upriv->ht, c);
return nil;
}
static int
udpstate(Conv *c, char *state, int n)
{
return snprint(state, n, "%s qin %d qout %d\n",
c->inuse ? "Open" : "Closed",
c->rq ? qlen(c->rq) : 0,
c->wq ? qlen(c->wq) : 0
);
}
static char*
udpannounce(Conv *c, char** argv, int argc)
{
char *e;
Udppriv *upriv;
upriv = c->p->priv;
e = Fsstdannounce(c, argv, argc);
if(e != nil)
return e;
Fsconnected(c, nil);
iphtadd(&upriv->ht, c);
return nil;
}
static void
udpcreate(Conv *c)
{
c->rq = qopen(512*1024, Qmsg, 0, 0);
c->wq = qbypass(udpkick, c);
}
static void
udpclose(Conv *c)
{
Udpcb *ucb;
Udppriv *upriv;
upriv = c->p->priv;
iphtrem(&upriv->ht, c);
c->state = 0;
qclose(c->rq);
qclose(c->wq);
qclose(c->eq);
c->lport = 0;
ipmove(c->laddr, IPnoaddr);
c->rport = 0;
ipmove(c->raddr, IPnoaddr);
ucb = (Udpcb*)c->ptcl;
ucb->headers = 0;
}
void
udpkick(void *x, Block *bp)
{
Conv *c = x;
Udp4hdr *uh4;
Udp6hdr *uh6;
ushort rport;
uchar laddr[IPaddrlen], raddr[IPaddrlen];
Udpcb *ucb;
int dlen, ptcllen;
Udppriv *upriv;
Fs *f;
int version;
Routehint *rh;
ushort csum;
upriv = c->p->priv;
f = c->p->f;
// netlog(c->p->f, Logudp, "udp: kick\n"); /* frequent and uninteresting */
if(bp == nil)
return;
ucb = (Udpcb*)c->ptcl;
switch(ucb->headers) {
case 7:
/* get user specified addresses */
bp = pullupblock(bp, UDP_USEAD7);
if(bp == nil)
return;
ipmove(raddr, bp->rp);
bp->rp += IPaddrlen;
ipmove(laddr, bp->rp);
bp->rp += IPaddrlen;
/* pick interface closest to dest */
if(ipforme(f, laddr) != Runi)
findlocalip(f, laddr, raddr);
bp->rp += IPaddrlen; /* Ignore ifc address */
rport = nhgets(bp->rp);
bp->rp += 2+2; /* Ignore local port */
break;
default:
rport = 0;
break;
}
if(ucb->headers) {
if(isv4(laddr) || ipcmp(laddr, IPnoaddr) == 0)
version = V4;
else
version = V6;
} else {
version = convipvers(c);
}
dlen = blocklen(bp);
/* fill in pseudo header and compute checksum */
switch(version){
case V4:
bp = padblock(bp, UDP4_IPHDR_SZ+UDP_UDPHDR_SZ);
uh4 = (Udp4hdr *)(bp->rp);
ptcllen = dlen + UDP_UDPHDR_SZ;
uh4->ttl = 0;
uh4->udpproto = IP_UDPPROTO;
uh4->frag[0] = 0;
uh4->frag[1] = 0;
hnputs(uh4->udpplen, ptcllen);
if(ucb->headers) {
v6tov4(uh4->udpdst, raddr);
hnputs(uh4->udpdport, rport);
v6tov4(uh4->udpsrc, laddr);
rh = nil;
} else {
v6tov4(uh4->udpdst, c->raddr);
hnputs(uh4->udpdport, c->rport);
if(ipcmp(c->laddr, IPnoaddr) == 0)
findlocalip(f, c->laddr, c->raddr);
v6tov4(uh4->udpsrc, c->laddr);
rh = c;
}
hnputs(uh4->udpsport, c->lport);
hnputs(uh4->udplen, ptcllen);
uh4->udpcksum[0] = 0;
uh4->udpcksum[1] = 0;
csum = ptclcsum(bp, UDP4_PHDR_OFF, dlen+UDP_UDPHDR_SZ+UDP4_PHDR_SZ);
if(csum == 0)
csum = 0xffff; /* -0 */
hnputs(uh4->udpcksum, csum);
uh4->vihl = IP_VER4;
ipoput4(f, bp, 0, c->ttl, c->tos, rh);
break;
case V6:
/*
* using the v6 ip header to create pseudo header
* first then reset it to the normal ip header
*/
bp = padblock(bp, UDP6_IPHDR_SZ+UDP_UDPHDR_SZ);
uh6 = (Udp6hdr *)(bp->rp);
memset(uh6, 0, 8);
ptcllen = dlen + UDP_UDPHDR_SZ;
hnputl(uh6->viclfl, ptcllen);
uh6->hoplimit = IP_UDPPROTO;
if(ucb->headers) {
ipmove(uh6->udpdst, raddr);
hnputs(uh6->udpdport, rport);
ipmove(uh6->udpsrc, laddr);
rh = nil;
} else {
ipmove(uh6->udpdst, c->raddr);
hnputs(uh6->udpdport, c->rport);
if(ipcmp(c->laddr, IPnoaddr) == 0)
findlocalip(f, c->laddr, c->raddr);
ipmove(uh6->udpsrc, c->laddr);
rh = c;
}
hnputs(uh6->udpsport, c->lport);
hnputs(uh6->udplen, ptcllen);
uh6->udpcksum[0] = 0;
uh6->udpcksum[1] = 0;
csum = ptclcsum(bp, UDP6_PHDR_OFF, dlen+UDP_UDPHDR_SZ+UDP6_PHDR_SZ);
if(csum == 0)
csum = 0xffff; /* -0 */
hnputs(uh6->udpcksum, csum);
memset(uh6, 0, 8);
uh6->viclfl[0] = IP_VER6;
hnputs(uh6->len, ptcllen);
uh6->nextheader = IP_UDPPROTO;
ipoput6(f, bp, 0, c->ttl, c->tos, rh);
break;
default:
panic("udpkick: version %d", version);
}
upriv->ustats.udpOutDatagrams++;
}
void
udpiput(Proto *udp, Ipifc *ifc, Block *bp)
{
int len;
Udp4hdr *uh4;
Udp6hdr *uh6;
Iphash *iph;
Conv *c;
Udpcb *ucb;
uchar raddr[IPaddrlen], laddr[IPaddrlen];
ushort rport, lport;
Udppriv *upriv;
Fs *f;
int version;
int ottl, oviclfl, olen;
uchar *p;
upriv = udp->priv;
f = udp->f;
upriv->ustats.udpInDatagrams++;
uh4 = (Udp4hdr*)(bp->rp);
uh6 = (Udp6hdr*)(bp->rp);
version = ((uh4->vihl&0xF0)==IP_VER6) ? V6 : V4;
/* Put back pseudo header for checksum
* (remember old values for icmpnoconv()) */
switch(version) {
case V4:
ottl = uh4->ttl;
uh4->ttl = 0;
len = nhgets(uh4->udplen);
olen = nhgets(uh4->udpplen);
hnputs(uh4->udpplen, len);
v4tov6(raddr, uh4->udpsrc);
v4tov6(laddr, uh4->udpdst);
lport = nhgets(uh4->udpdport);
rport = nhgets(uh4->udpsport);
if(nhgets(uh4->udpcksum)) {
if(ptclcsum(bp, UDP4_PHDR_OFF, len+UDP4_PHDR_SZ)) {
upriv->ustats.udpInErrors++;
netlog(f, Logudp, "udp: checksum error %I\n", raddr);
DPRINT("udp: checksum error %I\n", raddr);
freeblist(bp);
return;
}
}
uh4->ttl = ottl;
hnputs(uh4->udpplen, olen);
break;
case V6:
len = nhgets(uh6->udplen);
oviclfl = nhgetl(uh6->viclfl);
olen = nhgets(uh6->len);
ottl = uh6->hoplimit;
ipmove(raddr, uh6->udpsrc);
ipmove(laddr, uh6->udpdst);
lport = nhgets(uh6->udpdport);
rport = nhgets(uh6->udpsport);
memset(uh6, 0, 8);
hnputl(uh6->viclfl, len);
uh6->hoplimit = IP_UDPPROTO;
if(ptclcsum(bp, UDP6_PHDR_OFF, len+UDP6_PHDR_SZ)) {
upriv->ustats.udpInErrors++;
netlog(f, Logudp, "udp: checksum error %I\n", raddr);
DPRINT("udp: checksum error %I\n", raddr);
freeblist(bp);
return;
}
hnputl(uh6->viclfl, oviclfl);
hnputs(uh6->len, olen);
uh6->nextheader = IP_UDPPROTO;
uh6->hoplimit = ottl;
break;
default:
panic("udpiput: version %d", version);
return; /* to avoid a warning */
}
qlock(udp);
iph = iphtlook(&upriv->ht, raddr, rport, laddr, lport);
if(iph == nil){
/* no conversation found */
upriv->ustats.udpNoPorts++;
qunlock(udp);
netlog(f, Logudp, "udp: no conv %I!%d -> %I!%d\n", raddr, rport,
laddr, lport);
switch(version){
case V4:
icmpnoconv(f, bp);
break;
case V6:
icmphostunr6(f, ifc, bp, Icmp6_port_unreach, 0);
break;
default:
panic("udpiput2: version %d", version);
}
freeblist(bp);
return;
}
if(iph->trans){
Translation *q;
int hop = uh4->ttl;
if(hop <= 1 || (q = transbackward(udp, iph)) == nil){
qunlock(udp);
freeblist(bp);
return;
}
hnputs_csum(uh4->udpdst+0, nhgets(q->forward.raddr+IPv4off+0), uh4->udpcksum);
hnputs_csum(uh4->udpdst+2, nhgets(q->forward.raddr+IPv4off+2), uh4->udpcksum);
hnputs_csum(uh4->udpdport, q->forward.rport, uh4->udpcksum);
/* only use route-hint when from original desination */
if(memcmp(uh4->udpsrc, q->forward.laddr+IPv4off, IPv4addrlen) != 0)
q = nil;
qunlock(udp);
ipoput4(f, bp, 1, hop - 1, uh4->tos, q);
return;
}
c = iphconv(iph);
ucb = (Udpcb*)c->ptcl;
if(c->state == Announced){
if(ucb->headers == 0){
/* create a new conversation */
if(ipforme(f, laddr) != Runi)
ipv6local(ifc, laddr, 0, raddr);
c = Fsnewcall(c, raddr, rport, laddr, lport, version);
if(c == nil){
qunlock(udp);
freeblist(bp);
return;
}
iphtadd(&upriv->ht, c);
ucb = (Udpcb*)c->ptcl;
}
}
qlock(c);
qunlock(udp);
/*
* Trim the packet down to data size
*/
len -= UDP_UDPHDR_SZ;
switch(version){
case V4:
bp = trimblock(bp, UDP4_IPHDR_SZ+UDP_UDPHDR_SZ, len);
break;
case V6:
bp = trimblock(bp, UDP6_IPHDR_SZ+UDP_UDPHDR_SZ, len);
break;
default:
bp = nil;
panic("udpiput4: version %d", version);
}
if(bp == nil){
qunlock(c);
netlog(f, Logudp, "udp: len err %I.%d -> %I.%d\n", raddr, rport,
laddr, lport);
upriv->lenerr++;
return;
}
netlog(f, Logudpmsg, "udp: %I.%d -> %I.%d l %d\n", raddr, rport,
laddr, lport, len);
switch(ucb->headers){
case 7:
/* pass the src address */
bp = padblock(bp, UDP_USEAD7);
p = bp->rp;
ipmove(p, raddr); p += IPaddrlen;
ipmove(p, laddr); p += IPaddrlen;
if(!ipv6local(ifc, p, 0, raddr))
ipmove(p, ifc->lifc != nil ? ifc->lifc->local : IPnoaddr);
p += IPaddrlen;
hnputs(p, rport); p += 2;
hnputs(p, lport);
break;
}
if(qfull(c->rq)){
netlog(f, Logudp, "udp: qfull %I.%d -> %I.%d\n",
raddr, rport, laddr, lport);
freeblist(bp);
} else {
qpass(c->rq, concatblock(bp));
}
qunlock(c);
}
char*
udpctl(Conv *c, char **f, int n)
{
Udpcb *ucb;
ucb = (Udpcb*)c->ptcl;
if(n == 1){
if(strcmp(f[0], "hangup") == 0){
qhangup(c->rq, nil);
qhangup(c->wq, nil);
return nil;
}
if(strcmp(f[0], "headers") == 0){
ucb->headers = 7; /* new headers format */
return nil;
}
}
return "unknown control request";
}
void
udpadvise(Proto *udp, Block *bp, char *msg)
{
Udp4hdr *h4;
Udp6hdr *h6;
uchar source[IPaddrlen], dest[IPaddrlen];
ushort psource, pdest;
Iphash *iph;
Conv *s;
h4 = (Udp4hdr*)(bp->rp);
h6 = (Udp6hdr*)(bp->rp);
if((h4->vihl&0xF0)==IP_VER4) {
v4tov6(dest, h4->udpdst);
v4tov6(source, h4->udpsrc);
psource = nhgets(h4->udpsport);
pdest = nhgets(h4->udpdport);
} else {
ipmove(dest, h6->udpdst);
ipmove(source, h6->udpsrc);
psource = nhgets(h6->udpsport);
pdest = nhgets(h6->udpdport);
}
/* Look for a connection (source/dest reversed; this is the original packet we sent) */
qlock(udp);
iph = iphtlook(&((Udppriv*)udp->priv)->ht, dest, pdest, source, psource);
if(iph == nil)
goto raise;
if(iph->trans){
Translation *q;
if((q = transbackward(udp, iph)) == nil)
goto raise;
/* h4->udpplen is the ip header checksum */
hnputs_csum(h4->udpsrc+0, nhgets(q->forward.raddr+IPv4off+0), h4->udpplen);
hnputs_csum(h4->udpsrc+2, nhgets(q->forward.raddr+IPv4off+2), h4->udpplen);
/* dont bother fixing udp checksum, packet is most likely truncated */
hnputs(h4->udpsport, q->forward.rport);
qunlock(udp);
icmpproxyadvice(udp->f, bp, h4->udpsrc);
return;
}
s = iphconv(iph);
if(s->ignoreadvice)
goto raise;
qlock(s);
qunlock(udp);
qhangup(s->rq, msg);
qhangup(s->wq, msg);
qunlock(s);
freeblist(bp);
return;
raise:
qunlock(udp);
freeblist(bp);
}
Block*
udpforward(Proto *udp, Block *bp, Route *r)
{
uchar da[IPaddrlen], sa[IPaddrlen];
ushort dp, sp;
Udp4hdr *uh4;
Translation *q;
uh4 = (Udp4hdr*)(bp->rp);
v4tov6(sa, uh4->udpsrc);
v4tov6(da, uh4->udpdst);
dp = nhgets(uh4->udpdport);
sp = nhgets(uh4->udpsport);
qlock(udp);
q = transforward(udp, &((Udppriv*)udp->priv)->ht, sa, sp, da, dp, r);
if(q == nil){
qunlock(udp);
freeblist(bp);
return nil;
}
hnputs_csum(uh4->udpsrc+0, nhgets(q->backward.laddr+IPv4off+0), uh4->udpcksum);
hnputs_csum(uh4->udpsrc+2, nhgets(q->backward.laddr+IPv4off+2), uh4->udpcksum);
hnputs_csum(uh4->udpsport, q->backward.lport, uh4->udpcksum);
qunlock(udp);
return bp;
}
int
udpstats(Proto *udp, char *buf, int len)
{
Udppriv *upriv;
upriv = udp->priv;
return snprint(buf, len, "InDatagrams: %llud\nNoPorts: %lud\n"
"InErrors: %lud\nOutDatagrams: %llud\n",
upriv->ustats.udpInDatagrams,
upriv->ustats.udpNoPorts,
upriv->ustats.udpInErrors,
upriv->ustats.udpOutDatagrams);
}
void
udpinit(Fs *fs)
{
Proto *udp;
udp = smalloc(sizeof(Proto));
udp->priv = smalloc(sizeof(Udppriv));
udp->name = "udp";
udp->connect = udpconnect;
udp->announce = udpannounce;
udp->ctl = udpctl;
udp->state = udpstate;
udp->create = udpcreate;
udp->close = udpclose;
udp->rcv = udpiput;
udp->advise = udpadvise;
udp->forward = udpforward;
udp->stats = udpstats;
udp->ipproto = IP_UDPPROTO;
udp->nc = Nchans;
udp->ptclsize = sizeof(Udpcb);
Fsproto(fs, udp);
}