summaryrefslogtreecommitdiff
path: root/sys/src/9/ip/ipv6.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/9/ip/ipv6.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/9/ip/ipv6.c')
-rwxr-xr-xsys/src/9/ip/ipv6.c621
1 files changed, 621 insertions, 0 deletions
diff --git a/sys/src/9/ip/ipv6.c b/sys/src/9/ip/ipv6.c
new file mode 100755
index 000000000..606f638e9
--- /dev/null
+++ b/sys/src/9/ip/ipv6.c
@@ -0,0 +1,621 @@
+#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"
+
+enum
+{
+ IP6FHDR = 8, /* sizeof(Fraghdr6) */
+};
+
+#define IPV6CLASS(hdr) (((hdr)->vcf[0]&0x0F)<<2 | ((hdr)->vcf[1]&0xF0)>>2)
+#define BLKIPVER(xp) (((Ip6hdr*)((xp)->rp))->vcf[0] & 0xF0)
+/*
+ * This sleazy macro is stolen shamelessly from ip.c, see comment there.
+ */
+#define BKFG(xp) ((Ipfrag*)((xp)->base))
+
+Block* ip6reassemble(IP*, int, Block*, Ip6hdr*);
+Fragment6* ipfragallo6(IP*);
+void ipfragfree6(IP*, Fragment6*);
+Block* procopts(Block *bp);
+static Block* procxtns(IP *ip, Block *bp, int doreasm);
+int unfraglen(Block *bp, uchar *nexthdr, int setfh);
+
+int
+ipoput6(Fs *f, Block *bp, int gating, int ttl, int tos, Conv *c)
+{
+ int medialen, len, chunk, uflen, flen, seglen, lid, offset, fragoff;
+ int morefrags, blklen, rv = 0, tentative;
+ uchar *gate, nexthdr;
+ Block *xp, *nb;
+ Fraghdr6 fraghdr;
+ IP *ip;
+ Ip6hdr *eh;
+ Ipifc *ifc;
+ Route *r, *sr;
+
+ ip = f->ip;
+
+ /* Fill out the ip header */
+ eh = (Ip6hdr*)(bp->rp);
+
+ ip->stats[OutRequests]++;
+
+ /* Number of uchars in data and ip header to write */
+ len = blocklen(bp);
+
+ tentative = iptentative(f, eh->src);
+ if(tentative){
+ netlog(f, Logip, "reject tx of packet with tentative src address %I\n",
+ eh->src);
+ goto free;
+ }
+
+ if(gating){
+ chunk = nhgets(eh->ploadlen);
+ if(chunk > len){
+ ip->stats[OutDiscards]++;
+ netlog(f, Logip, "short gated packet\n");
+ goto free;
+ }
+ if(chunk + IP6HDR < len)
+ len = chunk + IP6HDR;
+ }
+
+ if(len >= IP_MAX){
+ ip->stats[OutDiscards]++;
+ netlog(f, Logip, "exceeded ip max size %I\n", eh->dst);
+ goto free;
+ }
+
+ r = v6lookup(f, eh->dst, c);
+ if(r == nil){
+// print("no route for %I, src %I free\n", eh->dst, eh->src);
+ ip->stats[OutNoRoutes]++;
+ netlog(f, Logip, "no interface %I\n", eh->dst);
+ rv = -1;
+ goto free;
+ }
+
+ ifc = r->ifc;
+ if(r->type & (Rifc|Runi))
+ gate = eh->dst;
+ else if(r->type & (Rbcast|Rmulti)) {
+ gate = eh->dst;
+ sr = v6lookup(f, eh->src, nil);
+ if(sr && (sr->type & Runi))
+ ifc = sr->ifc;
+ }
+ else
+ gate = r->v6.gate;
+
+ if(!gating)
+ eh->vcf[0] = IP_VER6;
+ eh->ttl = ttl;
+ if(!gating) {
+ eh->vcf[0] |= tos >> 4;
+ eh->vcf[1] = tos << 4;
+ }
+
+ if(!canrlock(ifc))
+ goto free;
+
+ if(waserror()){
+ runlock(ifc);
+ nexterror();
+ }
+
+ if(ifc->m == nil)
+ goto raise;
+
+ /* If we dont need to fragment just send it */
+ medialen = ifc->maxtu - ifc->m->hsize;
+ if(len <= medialen) {
+ hnputs(eh->ploadlen, len - IP6HDR);
+ ifc->m->bwrite(ifc, bp, V6, gate);
+ runlock(ifc);
+ poperror();
+ return 0;
+ }
+
+ if(gating && ifc->reassemble <= 0) {
+ /*
+ * v6 intermediate nodes are not supposed to fragment pkts;
+ * we fragment if ifc->reassemble is turned on; an exception
+ * needed for nat.
+ */
+ ip->stats[OutDiscards]++;
+ icmppkttoobig6(f, ifc, bp);
+ netlog(f, Logip, "%I: gated pkts not fragmented\n", eh->dst);
+ goto raise;
+ }
+
+ /* start v6 fragmentation */
+ uflen = unfraglen(bp, &nexthdr, 1);
+ if(uflen > medialen) {
+ ip->stats[FragFails]++;
+ ip->stats[OutDiscards]++;
+ netlog(f, Logip, "%I: unfragmentable part too big\n", eh->dst);
+ goto raise;
+ }
+
+ flen = len - uflen;
+ seglen = (medialen - (uflen + IP6FHDR)) & ~7;
+ if(seglen < 8) {
+ ip->stats[FragFails]++;
+ ip->stats[OutDiscards]++;
+ netlog(f, Logip, "%I: seglen < 8\n", eh->dst);
+ goto raise;
+ }
+
+ lid = incref(&ip->id6);
+ fraghdr.nexthdr = nexthdr;
+ fraghdr.res = 0;
+ hnputl(fraghdr.id, lid);
+
+ xp = bp;
+ offset = uflen;
+ while (xp && offset && offset >= BLEN(xp)) {
+ offset -= BLEN(xp);
+ xp = xp->next;
+ }
+ xp->rp += offset;
+
+ fragoff = 0;
+ morefrags = 1;
+
+ for(; fragoff < flen; fragoff += seglen) {
+ nb = allocb(uflen + IP6FHDR + seglen);
+
+ if(fragoff + seglen >= flen) {
+ seglen = flen - fragoff;
+ morefrags = 0;
+ }
+
+ hnputs(eh->ploadlen, seglen+IP6FHDR);
+ memmove(nb->wp, eh, uflen);
+ nb->wp += uflen;
+
+ hnputs(fraghdr.offsetRM, fragoff); /* last 3 bits must be 0 */
+ fraghdr.offsetRM[1] |= morefrags;
+ memmove(nb->wp, &fraghdr, IP6FHDR);
+ nb->wp += IP6FHDR;
+
+ /* Copy data */
+ chunk = seglen;
+ while (chunk) {
+ if(!xp) {
+ ip->stats[OutDiscards]++;
+ ip->stats[FragFails]++;
+ freeblist(nb);
+ netlog(f, Logip, "!xp: chunk in v6%d\n", chunk);
+ goto raise;
+ }
+ blklen = chunk;
+ if(BLEN(xp) < chunk)
+ blklen = BLEN(xp);
+ memmove(nb->wp, xp->rp, blklen);
+
+ nb->wp += blklen;
+ xp->rp += blklen;
+ chunk -= blklen;
+ if(xp->rp == xp->wp)
+ xp = xp->next;
+ }
+
+ ifc->m->bwrite(ifc, nb, V6, gate);
+ ip->stats[FragCreates]++;
+ }
+ ip->stats[FragOKs]++;
+
+raise:
+ runlock(ifc);
+ poperror();
+free:
+ freeblist(bp);
+ return rv;
+}
+
+void
+ipiput6(Fs *f, Ipifc *ifc, Block *bp)
+{
+ int hl, hop, tos, notforme, tentative;
+ uchar proto;
+ uchar v6dst[IPaddrlen];
+ IP *ip;
+ Ip6hdr *h;
+ Proto *p;
+ Route *r, *sr;
+
+ ip = f->ip;
+ ip->stats[InReceives]++;
+
+ /*
+ * Ensure we have all the header info in the first
+ * block. Make life easier for other protocols by
+ * collecting up to the first 64 bytes in the first block.
+ */
+ if(BLEN(bp) < 64) {
+ hl = blocklen(bp);
+ if(hl < IP6HDR)
+ hl = IP6HDR;
+ if(hl > 64)
+ hl = 64;
+ bp = pullupblock(bp, hl);
+ if(bp == nil)
+ return;
+ }
+
+ h = (Ip6hdr *)bp->rp;
+
+ memmove(&v6dst[0], &h->dst[0], IPaddrlen);
+ notforme = ipforme(f, v6dst) == 0;
+ tentative = iptentative(f, v6dst);
+
+ if(tentative && h->proto != ICMPv6) {
+ print("tentative addr, drop\n");
+ freeblist(bp);
+ return;
+ }
+
+ /* Check header version */
+ if(BLKIPVER(bp) != IP_VER6) {
+ ip->stats[InHdrErrors]++;
+ netlog(f, Logip, "ip: bad version %ux\n", (h->vcf[0]&0xF0)>>2);
+ freeblist(bp);
+ return;
+ }
+
+ /* route */
+ if(notforme) {
+ if(!ip->iprouting){
+ freeblist(bp);
+ return;
+ }
+
+ /* don't forward to link-local destinations */
+ if(islinklocal(h->dst) ||
+ (isv6mcast(h->dst) && (h->dst[1]&0xF) <= Link_local_scop)){
+ ip->stats[OutDiscards]++;
+ freeblist(bp);
+ return;
+ }
+
+ /* don't forward to source's network */
+ sr = v6lookup(f, h->src, nil);
+ r = v6lookup(f, h->dst, nil);
+
+ if(r == nil || sr == r){
+ ip->stats[OutDiscards]++;
+ freeblist(bp);
+ return;
+ }
+
+ /* don't forward if packet has timed out */
+ hop = h->ttl;
+ if(hop < 1) {
+ ip->stats[InHdrErrors]++;
+ icmpttlexceeded6(f, ifc, bp);
+ freeblist(bp);
+ return;
+ }
+
+ /* process headers & reassemble if the interface expects it */
+ bp = procxtns(ip, bp, r->ifc->reassemble);
+ if(bp == nil)
+ return;
+
+ ip->stats[ForwDatagrams]++;
+ h = (Ip6hdr *)bp->rp;
+ tos = IPV6CLASS(h);
+ hop = h->ttl;
+ ipoput6(f, bp, 1, hop-1, tos, nil);
+ return;
+ }
+
+ /* reassemble & process headers if needed */
+ bp = procxtns(ip, bp, 1);
+ if(bp == nil)
+ return;
+
+ h = (Ip6hdr *) (bp->rp);
+ proto = h->proto;
+ p = Fsrcvpcol(f, proto);
+ if(p && p->rcv) {
+ ip->stats[InDelivers]++;
+ (*p->rcv)(p, ifc, bp);
+ return;
+ }
+
+ ip->stats[InDiscards]++;
+ ip->stats[InUnknownProtos]++;
+ freeblist(bp);
+}
+
+/*
+ * ipfragfree6 - copied from ipfragfree4 - assume hold fraglock6
+ */
+void
+ipfragfree6(IP *ip, Fragment6 *frag)
+{
+ Fragment6 *fl, **l;
+
+ if(frag->blist)
+ freeblist(frag->blist);
+
+ memset(frag->src, 0, IPaddrlen);
+ frag->id = 0;
+ frag->blist = nil;
+
+ l = &ip->flisthead6;
+ for(fl = *l; fl; fl = fl->next) {
+ if(fl == frag) {
+ *l = frag->next;
+ break;
+ }
+ l = &fl->next;
+ }
+
+ frag->next = ip->fragfree6;
+ ip->fragfree6 = frag;
+}
+
+/*
+ * ipfragallo6 - copied from ipfragalloc4
+ */
+Fragment6*
+ipfragallo6(IP *ip)
+{
+ Fragment6 *f;
+
+ while(ip->fragfree6 == nil) {
+ /* free last entry on fraglist */
+ for(f = ip->flisthead6; f->next; f = f->next)
+ ;
+ ipfragfree6(ip, f);
+ }
+ f = ip->fragfree6;
+ ip->fragfree6 = f->next;
+ f->next = ip->flisthead6;
+ ip->flisthead6 = f;
+ f->age = NOW + 30000;
+
+ return f;
+}
+
+static Block*
+procxtns(IP *ip, Block *bp, int doreasm)
+{
+ int offset;
+ uchar proto;
+ Ip6hdr *h;
+
+ h = (Ip6hdr *)bp->rp;
+ offset = unfraglen(bp, &proto, 0);
+
+ if(proto == FH && doreasm != 0) {
+ bp = ip6reassemble(ip, offset, bp, h);
+ if(bp == nil)
+ return nil;
+ offset = unfraglen(bp, &proto, 0);
+ }
+
+ if(proto == DOH || offset > IP6HDR)
+ bp = procopts(bp);
+ return bp;
+}
+
+/*
+ * returns length of "Unfragmentable part", i.e., sum of lengths of ipv6 hdr,
+ * hop-by-hop & routing headers if present; *nexthdr is set to nexthdr value
+ * of the last header in the "Unfragmentable part"; if setfh != 0, nexthdr
+ * field of the last header in the "Unfragmentable part" is set to FH.
+ */
+int
+unfraglen(Block *bp, uchar *nexthdr, int setfh)
+{
+ uchar *p, *q;
+ int ufl, hs;
+
+ p = bp->rp;
+ q = p+6; /* proto, = p+sizeof(Ip6hdr.vcf)+sizeof(Ip6hdr.ploadlen) */
+ *nexthdr = *q;
+ ufl = IP6HDR;
+ p += ufl;
+
+ while (*nexthdr == HBH || *nexthdr == RH) {
+ *nexthdr = *p;
+ hs = ((int)*(p+1) + 1) * 8;
+ ufl += hs;
+ q = p;
+ p += hs;
+ }
+
+ if(*nexthdr == FH)
+ *q = *p;
+ if(setfh)
+ *q = FH;
+ return ufl;
+}
+
+Block*
+procopts(Block *bp)
+{
+ return bp;
+}
+
+Block*
+ip6reassemble(IP* ip, int uflen, Block* bp, Ip6hdr* ih)
+{
+ int fend, offset, ovlap, len, fragsize, pktposn;
+ uint id;
+ uchar src[IPaddrlen], dst[IPaddrlen];
+ Block *bl, **l, *last, *prev;
+ Fraghdr6 *fraghdr;
+ Fragment6 *f, *fnext;
+
+ fraghdr = (Fraghdr6 *)(bp->rp + uflen);
+ memmove(src, ih->src, IPaddrlen);
+ memmove(dst, ih->dst, IPaddrlen);
+ id = nhgetl(fraghdr->id);
+ offset = nhgets(fraghdr->offsetRM) & ~7;
+
+ /*
+ * block lists are too hard, pullupblock into a single block
+ */
+ if(bp->next){
+ bp = pullupblock(bp, blocklen(bp));
+ ih = (Ip6hdr *)bp->rp;
+ }
+
+ qlock(&ip->fraglock6);
+
+ /*
+ * find a reassembly queue for this fragment
+ */
+ for(f = ip->flisthead6; f; f = fnext){
+ fnext = f->next;
+ if(ipcmp(f->src, src)==0 && ipcmp(f->dst, dst)==0 && f->id == id)
+ break;
+ if(f->age < NOW){
+ ip->stats[ReasmTimeout]++;
+ ipfragfree6(ip, f);
+ }
+ }
+
+ /*
+ * if this isn't a fragmented packet, accept it
+ * and get rid of any fragments that might go
+ * with it.
+ */
+ if(nhgets(fraghdr->offsetRM) == 0) { /* 1st frag is also last */
+ if(f) {
+ ipfragfree6(ip, f);
+ ip->stats[ReasmFails]++;
+ }
+ qunlock(&ip->fraglock6);
+ return bp;
+ }
+
+ if(bp->base+IPFRAGSZ >= bp->rp){
+ bp = padblock(bp, IPFRAGSZ);
+ bp->rp += IPFRAGSZ;
+ }
+
+ BKFG(bp)->foff = offset;
+ BKFG(bp)->flen = nhgets(ih->ploadlen) + IP6HDR - uflen - IP6FHDR;
+
+ /* First fragment allocates a reassembly queue */
+ if(f == nil) {
+ f = ipfragallo6(ip);
+ f->id = id;
+ memmove(f->src, src, IPaddrlen);
+ memmove(f->dst, dst, IPaddrlen);
+
+ f->blist = bp;
+
+ qunlock(&ip->fraglock6);
+ ip->stats[ReasmReqds]++;
+ return nil;
+ }
+
+ /*
+ * find the new fragment's position in the queue
+ */
+ prev = nil;
+ l = &f->blist;
+ bl = f->blist;
+ while(bl != nil && BKFG(bp)->foff > BKFG(bl)->foff) {
+ prev = bl;
+ l = &bl->next;
+ bl = bl->next;
+ }
+
+ /* Check overlap of a previous fragment - trim away as necessary */
+ if(prev) {
+ ovlap = BKFG(prev)->foff + BKFG(prev)->flen - BKFG(bp)->foff;
+ if(ovlap > 0) {
+ if(ovlap >= BKFG(bp)->flen) {
+ freeblist(bp);
+ qunlock(&ip->fraglock6);
+ return nil;
+ }
+ BKFG(prev)->flen -= ovlap;
+ }
+ }
+
+ /* Link onto assembly queue */
+ bp->next = *l;
+ *l = bp;
+
+ /* Check to see if succeeding segments overlap */
+ if(bp->next) {
+ l = &bp->next;
+ fend = BKFG(bp)->foff + BKFG(bp)->flen;
+
+ /* Take completely covered segments out */
+ while(*l) {
+ ovlap = fend - BKFG(*l)->foff;
+ if(ovlap <= 0)
+ break;
+ if(ovlap < BKFG(*l)->flen) {
+ BKFG(*l)->flen -= ovlap;
+ BKFG(*l)->foff += ovlap;
+ /* move up ih hdrs */
+ memmove((*l)->rp + ovlap, (*l)->rp, uflen);
+ (*l)->rp += ovlap;
+ break;
+ }
+ last = (*l)->next;
+ (*l)->next = nil;
+ freeblist(*l);
+ *l = last;
+ }
+ }
+
+ /*
+ * look for a complete packet. if we get to a fragment
+ * with the trailing bit of fraghdr->offsetRM[1] set, we're done.
+ */
+ pktposn = 0;
+ for(bl = f->blist; bl && BKFG(bl)->foff == pktposn; bl = bl->next) {
+ fraghdr = (Fraghdr6 *)(bl->rp + uflen);
+ if((fraghdr->offsetRM[1] & 1) == 0) {
+ bl = f->blist;
+
+ /* get rid of frag header in first fragment */
+ memmove(bl->rp + IP6FHDR, bl->rp, uflen);
+ bl->rp += IP6FHDR;
+ len = nhgets(((Ip6hdr*)bl->rp)->ploadlen) - IP6FHDR;
+ bl->wp = bl->rp + len + IP6HDR;
+ /*
+ * Pullup all the fragment headers and
+ * return a complete packet
+ */
+ for(bl = bl->next; bl; bl = bl->next) {
+ fragsize = BKFG(bl)->flen;
+ len += fragsize;
+ bl->rp += uflen + IP6FHDR;
+ bl->wp = bl->rp + fragsize;
+ }
+
+ bl = f->blist;
+ f->blist = nil;
+ ipfragfree6(ip, f);
+ ih = (Ip6hdr*)bl->rp;
+ hnputs(ih->ploadlen, len);
+ qunlock(&ip->fraglock6);
+ ip->stats[ReasmOKs]++;
+ return bl;
+ }
+ pktposn += BKFG(bl)->flen;
+ }
+ qunlock(&ip->fraglock6);
+ return nil;
+}