e46000f076
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.
596 lines
12 KiB
C
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);
|
|
}
|