plan9fox/sys/src/9/ip/udp.c
cinap_lenrek e46000f076 devip: pick less surprising interface address in header for incoming UDP packets
We used to just return the first address of the incoming
interface regardless of if the address matches the source
ip type and scope.

This change tries to find the best interface address that
will match the source ip so it can be used as a source
address when replying to the packet.
2020-06-06 23:46:01 +02:00

596 lines
12 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 Unused;
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 (*etherprofiler)(char *name, int qlen);
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);
ipmove(c->laddr, IPnoaddr);
ipmove(c->raddr, IPnoaddr);
c->lport = 0;
c->rport = 0;
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->Unused = 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;
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);
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->Unused;
uh4->Unused = 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->Unused = ottl;
hnputs(uh4->udpplen, olen);
break;
case V6:
uh6 = (Udp6hdr*)(bp->rp);
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);
c = iphtlook(&upriv->ht, raddr, rport, laddr, lport);
if(c == 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;
}
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;
Conv *s, **p;
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 */
qlock(udp);
for(p = udp->conv; (s = *p) != nil; p++) {
if(s->rport == pdest)
if(s->lport == psource)
if(ipcmp(s->raddr, dest) == 0)
if(ipcmp(s->laddr, source) == 0){
if(s->ignoreadvice)
break;
qlock(s);
qunlock(udp);
qhangup(s->rq, msg);
qhangup(s->wq, msg);
qunlock(s);
freeblist(bp);
return;
}
}
qunlock(udp);
freeblist(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->stats = udpstats;
udp->ipproto = IP_UDPPROTO;
udp->nc = Nchans;
udp->ptclsize = sizeof(Udpcb);
Fsproto(fs, udp);
}