#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 { QLock; 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(128*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; Conv *rc; 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(memcmp(laddr, v4prefix, IPv4off) == 0 || ipcmp(laddr, IPnoaddr) == 0) version = 4; else version = 6; } else { if( (memcmp(c->raddr, v4prefix, IPv4off) == 0 && memcmp(c->laddr, v4prefix, IPv4off) == 0) || ipcmp(c->raddr, IPnoaddr) == 0) version = 4; else version = 6; } dlen = blocklen(bp); /* fill in pseudo header and compute checksum */ switch(version){ case V4: bp = padblock(bp, UDP4_IPHDR_SZ+UDP_UDPHDR_SZ); if(bp == nil) return; 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); rc = 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); rc = c; } hnputs(uh4->udpsport, c->lport); hnputs(uh4->udplen, ptcllen); uh4->udpcksum[0] = 0; uh4->udpcksum[1] = 0; hnputs(uh4->udpcksum, ptclcsum(bp, UDP4_PHDR_OFF, dlen+UDP_UDPHDR_SZ+UDP4_PHDR_SZ)); uh4->vihl = IP_VER4; ipoput4(f, bp, 0, c->ttl, c->tos, rc); break; case V6: bp = padblock(bp, UDP6_IPHDR_SZ+UDP_UDPHDR_SZ); if(bp == nil) return; /* * using the v6 ip header to create pseudo header * first then reset it to the normal ip header */ 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); rc = 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); rc = c; } hnputs(uh6->udpsport, c->lport); hnputs(uh6->udplen, ptcllen); uh6->udpcksum[0] = 0; uh6->udpcksum[1] = 0; hnputs(uh6->udpcksum, ptclcsum(bp, UDP6_PHDR_OFF, dlen+UDP_UDPHDR_SZ+UDP6_PHDR_SZ)); 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, rc); 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) ? 6 : 4; /* 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: icmphostunr(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) { switch(version){ case V4: v4tov6(laddr, ifc->lifc->local); break; case V6: ipmove(laddr, ifc->lifc->local); break; default: panic("udpiput3: version %d", version); } } 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; ipmove(p, ifc->lifc->local); p += IPaddrlen; hnputs(p, rport); p += 2; hnputs(p, lport); break; } if(bp->next) bp = concatblock(bp); if(qfull(c->rq)){ qunlock(c); netlog(f, Logudp, "udp: qfull %I.%d -> %I.%d\n", raddr, rport, laddr, lport); freeblist(bp); return; } qpass(c->rq, bp); qunlock(c); } char* udpctl(Conv *c, char **f, int n) { Udpcb *ucb; ucb = (Udpcb*)c->ptcl; if(n == 1){ 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; int version; h4 = (Udp4hdr*)(bp->rp); version = ((h4->vihl&0xF0)==IP_VER6) ? 6 : 4; switch(version) { case V4: v4tov6(dest, h4->udpdst); v4tov6(source, h4->udpsrc); psource = nhgets(h4->udpsport); pdest = nhgets(h4->udpdport); break; case V6: h6 = (Udp6hdr*)(bp->rp); ipmove(dest, h6->udpdst); ipmove(source, h6->udpsrc); psource = nhgets(h6->udpsport); pdest = nhgets(h6->udpdport); break; default: panic("udpadvise: version %d", version); return; /* to avoid a warning */ } /* Look for a connection */ qlock(udp); for(p = udp->conv; *p; p++) { s = *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); }