]> git.lizzy.rs Git - plan9front.git/blob - sys/src/9/ip/ipv6.c
kernel: dont use atomic increment for Proc.nlocks, maintain Lock.m for lock(), use...
[plan9front.git] / sys / src / 9 / ip / ipv6.c
1 #include        "u.h"
2 #include        "../port/lib.h"
3 #include        "mem.h"
4 #include        "dat.h"
5 #include        "fns.h"
6 #include        "../port/error.h"
7
8 #include        "ip.h"
9 #include        "ipv6.h"
10
11 enum
12 {
13         IP6FHDR         = 8,            /* sizeof(Fraghdr6) */
14 };
15
16 #define IPV6CLASS(hdr)  (((hdr)->vcf[0]&0x0F)<<2 | ((hdr)->vcf[1]&0xF0)>>2)
17 #define BLKIPVER(xp)    (((Ip6hdr*)((xp)->rp))->vcf[0] & 0xF0)
18 /*
19  * This sleazy macro is stolen shamelessly from ip.c, see comment there.
20  */
21 #define BKFG(xp)        ((Ipfrag*)((xp)->base))
22
23 Block*          ip6reassemble(IP*, int, Block*, Ip6hdr*);
24 Fragment6*      ipfragallo6(IP*);
25 void            ipfragfree6(IP*, Fragment6*);
26 Block*          procopts(Block *bp);
27 static Block*   procxtns(IP *ip, Block *bp, int doreasm);
28 int             unfraglen(Block *bp, uchar *nexthdr, int setfh);
29
30 int
31 ipoput6(Fs *f, Block *bp, int gating, int ttl, int tos, Conv *c)
32 {
33         int medialen, len, chunk, uflen, flen, seglen, lid, offset, fragoff;
34         int morefrags, blklen, rv = 0, tentative;
35         uchar *gate, nexthdr;
36         Block *xp, *nb;
37         Fraghdr6 fraghdr;
38         IP *ip;
39         Ip6hdr *eh;
40         Ipifc *ifc;
41         Route *r, *sr;
42
43         ip = f->ip;
44
45         /* Fill out the ip header */
46         eh = (Ip6hdr*)(bp->rp);
47
48         ip->stats[OutRequests]++;
49
50         /* Number of uchars in data and ip header to write */
51         len = blocklen(bp);
52
53         tentative = iptentative(f, eh->src);
54         if(tentative){
55                 netlog(f, Logip, "reject tx of packet with tentative src address %I\n",
56                         eh->src);
57                 goto free;
58         }
59
60         if(gating){
61                 chunk = nhgets(eh->ploadlen);
62                 if(chunk > len){
63                         ip->stats[OutDiscards]++;
64                         netlog(f, Logip, "short gated packet\n");
65                         goto free;
66                 }
67                 if(chunk + IP6HDR < len)
68                         len = chunk + IP6HDR;
69         }
70
71         if(len >= IP_MAX){
72                 ip->stats[OutDiscards]++;
73                 netlog(f, Logip, "exceeded ip max size %I\n", eh->dst);
74                 goto free;
75         }
76
77         r = v6lookup(f, eh->dst, c);
78         if(r == nil){
79 //              print("no route for %I, src %I free\n", eh->dst, eh->src);
80                 ip->stats[OutNoRoutes]++;
81                 netlog(f, Logip, "no interface %I\n", eh->dst);
82                 rv = -1;
83                 goto free;
84         }
85
86         ifc = r->ifc;
87         if(r->type & (Rifc|Runi))
88                 gate = eh->dst;
89         else if(r->type & (Rbcast|Rmulti)) {
90                 gate = eh->dst;
91                 sr = v6lookup(f, eh->src, nil);
92                 if(sr && (sr->type & Runi))
93                         ifc = sr->ifc;
94         }
95         else
96                 gate = r->v6.gate;
97
98         if(!gating)
99                 eh->vcf[0] = IP_VER6;
100         eh->ttl = ttl;
101         if(!gating) {
102                 eh->vcf[0] |= tos >> 4;
103                 eh->vcf[1]  = tos << 4;
104         }
105
106         if(!canrlock(ifc))
107                 goto free;
108
109         if(waserror()){
110                 runlock(ifc);
111                 nexterror();
112         }
113
114         if(ifc->m == nil)
115                 goto raise;
116
117         /* If we dont need to fragment just send it */
118         medialen = ifc->maxtu - ifc->m->hsize;
119         if(len <= medialen) {
120                 hnputs(eh->ploadlen, len - IP6HDR);
121                 ifc->m->bwrite(ifc, bp, V6, gate);
122                 runlock(ifc);
123                 poperror();
124                 return 0;
125         }
126
127         if(gating && ifc->reassemble <= 0) {
128                 /*
129                  * v6 intermediate nodes are not supposed to fragment pkts;
130                  * we fragment if ifc->reassemble is turned on; an exception
131                  * needed for nat.
132                  */
133                 ip->stats[OutDiscards]++;
134                 icmppkttoobig6(f, ifc, bp);
135                 netlog(f, Logip, "%I: gated pkts not fragmented\n", eh->dst);
136                 goto raise;
137         }
138
139         /* start v6 fragmentation */
140         uflen = unfraglen(bp, &nexthdr, 1);
141         if(uflen > medialen) {
142                 ip->stats[FragFails]++;
143                 ip->stats[OutDiscards]++;
144                 netlog(f, Logip, "%I: unfragmentable part too big\n", eh->dst);
145                 goto raise;
146         }
147
148         flen = len - uflen;
149         seglen = (medialen - (uflen + IP6FHDR)) & ~7;
150         if(seglen < 8) {
151                 ip->stats[FragFails]++;
152                 ip->stats[OutDiscards]++;
153                 netlog(f, Logip, "%I: seglen < 8\n", eh->dst);
154                 goto raise;
155         }
156
157         lid = incref(&ip->id6);
158         fraghdr.nexthdr = nexthdr;
159         fraghdr.res = 0;
160         hnputl(fraghdr.id, lid);
161
162         xp = bp;
163         offset = uflen;
164         while (xp && offset && offset >= BLEN(xp)) {
165                 offset -= BLEN(xp);
166                 xp = xp->next;
167         }
168         xp->rp += offset;
169
170         fragoff = 0;
171         morefrags = 1;
172
173         for(; fragoff < flen; fragoff += seglen) {
174                 nb = allocb(uflen + IP6FHDR + seglen);
175
176                 if(fragoff + seglen >= flen) {
177                         seglen = flen - fragoff;
178                         morefrags = 0;
179                 }
180
181                 hnputs(eh->ploadlen, seglen+IP6FHDR);
182                 memmove(nb->wp, eh, uflen);
183                 nb->wp += uflen;
184
185                 hnputs(fraghdr.offsetRM, fragoff); /* last 3 bits must be 0 */
186                 fraghdr.offsetRM[1] |= morefrags;
187                 memmove(nb->wp, &fraghdr, IP6FHDR);
188                 nb->wp += IP6FHDR;
189
190                 /* Copy data */
191                 chunk = seglen;
192                 while (chunk) {
193                         if(!xp) {
194                                 ip->stats[OutDiscards]++;
195                                 ip->stats[FragFails]++;
196                                 freeblist(nb);
197                                 netlog(f, Logip, "!xp: chunk in v6%d\n", chunk);
198                                 goto raise;
199                         }
200                         blklen = chunk;
201                         if(BLEN(xp) < chunk)
202                                 blklen = BLEN(xp);
203                         memmove(nb->wp, xp->rp, blklen);
204
205                         nb->wp += blklen;
206                         xp->rp += blklen;
207                         chunk -= blklen;
208                         if(xp->rp == xp->wp)
209                                 xp = xp->next;
210                 }
211
212                 ifc->m->bwrite(ifc, nb, V6, gate);
213                 ip->stats[FragCreates]++;
214         }
215         ip->stats[FragOKs]++;
216
217 raise:
218         runlock(ifc);
219         poperror();
220 free:
221         freeblist(bp);
222         return rv;
223 }
224
225 void
226 ipiput6(Fs *f, Ipifc *ifc, Block *bp)
227 {
228         int hl, hop, tos, notforme, tentative;
229         uchar proto;
230         uchar v6dst[IPaddrlen];
231         IP *ip;
232         Ip6hdr *h;
233         Proto *p;
234         Route *r, *sr;
235
236         ip = f->ip;
237         ip->stats[InReceives]++;
238
239         /*
240          *  Ensure we have all the header info in the first
241          *  block.  Make life easier for other protocols by
242          *  collecting up to the first 64 bytes in the first block.
243          */
244         if(BLEN(bp) < 64) {
245                 hl = blocklen(bp);
246                 if(hl < IP6HDR)
247                         hl = IP6HDR;
248                 if(hl > 64)
249                         hl = 64;
250                 bp = pullupblock(bp, hl);
251                 if(bp == nil)
252                         return;
253         }
254
255         h = (Ip6hdr *)bp->rp;
256
257         memmove(&v6dst[0], &h->dst[0], IPaddrlen);
258         notforme = ipforme(f, v6dst) == 0;
259         tentative = iptentative(f, v6dst);
260
261         if(tentative && h->proto != ICMPv6) {
262                 print("tentative addr, drop\n");
263                 freeblist(bp);
264                 return;
265         }
266
267         /* Check header version */
268         if(BLKIPVER(bp) != IP_VER6) {
269                 ip->stats[InHdrErrors]++;
270                 netlog(f, Logip, "ip: bad version %ux\n", (h->vcf[0]&0xF0)>>2);
271                 freeblist(bp);
272                 return;
273         }
274
275         /* route */
276         if(notforme) {
277                 if(!ip->iprouting){
278                         freeblist(bp);
279                         return;
280                 }
281
282                 /* don't forward to link-local destinations */
283                 if(islinklocal(h->dst) ||
284                    (isv6mcast(h->dst) && (h->dst[1]&0xF) <= Link_local_scop)){
285                         ip->stats[OutDiscards]++;
286                         freeblist(bp);
287                         return;
288                 }
289                         
290                 /* don't forward to source's network */
291                 sr = v6lookup(f, h->src, nil);
292                 r  = v6lookup(f, h->dst, nil);
293
294                 if(r == nil || sr == r){
295                         ip->stats[OutDiscards]++;
296                         freeblist(bp);
297                         return;
298                 }
299
300                 /* don't forward if packet has timed out */
301                 hop = h->ttl;
302                 if(hop < 1) {
303                         ip->stats[InHdrErrors]++;
304                         icmpttlexceeded6(f, ifc, bp);
305                         freeblist(bp);
306                         return;
307                 }
308
309                 /* process headers & reassemble if the interface expects it */
310                 bp = procxtns(ip, bp, r->ifc->reassemble);
311                 if(bp == nil)
312                         return;
313
314                 ip->stats[ForwDatagrams]++;
315                 h = (Ip6hdr *)bp->rp;
316                 tos = IPV6CLASS(h);
317                 hop = h->ttl;
318                 ipoput6(f, bp, 1, hop-1, tos, nil);
319                 return;
320         }
321
322         /* reassemble & process headers if needed */
323         bp = procxtns(ip, bp, 1);
324         if(bp == nil)
325                 return;
326
327         h = (Ip6hdr *) (bp->rp);
328         proto = h->proto;
329         p = Fsrcvpcol(f, proto);
330         if(p && p->rcv) {
331                 ip->stats[InDelivers]++;
332                 (*p->rcv)(p, ifc, bp);
333                 return;
334         }
335
336         ip->stats[InDiscards]++;
337         ip->stats[InUnknownProtos]++;
338         freeblist(bp);
339 }
340
341 /*
342  * ipfragfree6 - copied from ipfragfree4 - assume hold fraglock6
343  */
344 void
345 ipfragfree6(IP *ip, Fragment6 *frag)
346 {
347         Fragment6 *fl, **l;
348
349         if(frag->blist)
350                 freeblist(frag->blist);
351
352         memset(frag->src, 0, IPaddrlen);
353         frag->id = 0;
354         frag->blist = nil;
355
356         l = &ip->flisthead6;
357         for(fl = *l; fl; fl = fl->next) {
358                 if(fl == frag) {
359                         *l = frag->next;
360                         break;
361                 }
362                 l = &fl->next;
363         }
364
365         frag->next = ip->fragfree6;
366         ip->fragfree6 = frag;
367 }
368
369 /*
370  * ipfragallo6 - copied from ipfragalloc4
371  */
372 Fragment6*
373 ipfragallo6(IP *ip)
374 {
375         Fragment6 *f;
376
377         while(ip->fragfree6 == nil) {
378                 /* free last entry on fraglist */
379                 for(f = ip->flisthead6; f->next; f = f->next)
380                         ;
381                 ipfragfree6(ip, f);
382         }
383         f = ip->fragfree6;
384         ip->fragfree6 = f->next;
385         f->next = ip->flisthead6;
386         ip->flisthead6 = f;
387         f->age = NOW + 30000;
388
389         return f;
390 }
391
392 static Block*
393 procxtns(IP *ip, Block *bp, int doreasm)
394 {
395         int offset;
396         uchar proto;
397         Ip6hdr *h;
398
399         h = (Ip6hdr *)bp->rp;
400         offset = unfraglen(bp, &proto, 0);
401
402         if(proto == FH && doreasm != 0) {
403                 bp = ip6reassemble(ip, offset, bp, h);
404                 if(bp == nil)
405                         return nil;
406                 offset = unfraglen(bp, &proto, 0);
407         }
408
409         if(proto == DOH || offset > IP6HDR)
410                 bp = procopts(bp);
411         return bp;
412 }
413
414 /*
415  * returns length of "Unfragmentable part", i.e., sum of lengths of ipv6 hdr,
416  * hop-by-hop & routing headers if present; *nexthdr is set to nexthdr value
417  * of the last header in the "Unfragmentable part"; if setfh != 0, nexthdr
418  * field of the last header in the "Unfragmentable part" is set to FH.
419  */
420 int
421 unfraglen(Block *bp, uchar *nexthdr, int setfh)
422 {
423         uchar *p, *q;
424         int ufl, hs;
425
426         p = bp->rp;
427         q = p+6;   /* proto, = p+sizeof(Ip6hdr.vcf)+sizeof(Ip6hdr.ploadlen) */
428         *nexthdr = *q;
429         ufl = IP6HDR;
430         p += ufl;
431
432         while (*nexthdr == HBH || *nexthdr == RH) {
433                 *nexthdr = *p;
434                 hs = ((int)*(p+1) + 1) * 8;
435                 ufl += hs;
436                 q = p;
437                 p += hs;
438         }
439
440         if(*nexthdr == FH)
441                 *q = *p;
442         if(setfh)
443                 *q = FH;
444         return ufl;
445 }
446
447 Block*
448 procopts(Block *bp)
449 {
450         return bp;
451 }
452
453 Block*
454 ip6reassemble(IP* ip, int uflen, Block* bp, Ip6hdr* ih)
455 {
456         int fend, offset, ovlap, len, fragsize, pktposn;
457         uint id;
458         uchar src[IPaddrlen], dst[IPaddrlen];
459         Block *bl, **l, *last, *prev;
460         Fraghdr6 *fraghdr;
461         Fragment6 *f, *fnext;
462
463         fraghdr = (Fraghdr6 *)(bp->rp + uflen);
464         memmove(src, ih->src, IPaddrlen);
465         memmove(dst, ih->dst, IPaddrlen);
466         id = nhgetl(fraghdr->id);
467         offset = nhgets(fraghdr->offsetRM) & ~7;
468
469         /*
470          *  block lists are too hard, pullupblock into a single block
471          */
472         if(bp->next){
473                 bp = pullupblock(bp, blocklen(bp));
474                 ih = (Ip6hdr *)bp->rp;
475         }
476
477         qlock(&ip->fraglock6);
478
479         /*
480          *  find a reassembly queue for this fragment
481          */
482         for(f = ip->flisthead6; f; f = fnext){
483                 fnext = f->next;
484                 if(ipcmp(f->src, src)==0 && ipcmp(f->dst, dst)==0 && f->id == id)
485                         break;
486                 if(f->age < NOW){
487                         ip->stats[ReasmTimeout]++;
488                         ipfragfree6(ip, f);
489                 }
490         }
491
492         /*
493          *  if this isn't a fragmented packet, accept it
494          *  and get rid of any fragments that might go
495          *  with it.
496          */
497         if(nhgets(fraghdr->offsetRM) == 0) {    /* 1st frag is also last */
498                 if(f) {
499                         ipfragfree6(ip, f);
500                         ip->stats[ReasmFails]++;
501                 }
502                 qunlock(&ip->fraglock6);
503                 return bp;
504         }
505
506         if(bp->base+IPFRAGSZ >= bp->rp){
507                 bp = padblock(bp, IPFRAGSZ);
508                 bp->rp += IPFRAGSZ;
509         }
510
511         BKFG(bp)->foff = offset;
512         BKFG(bp)->flen = nhgets(ih->ploadlen) + IP6HDR - uflen - IP6FHDR;
513
514         /* First fragment allocates a reassembly queue */
515         if(f == nil) {
516                 f = ipfragallo6(ip);
517                 f->id = id;
518                 memmove(f->src, src, IPaddrlen);
519                 memmove(f->dst, dst, IPaddrlen);
520
521                 f->blist = bp;
522
523                 qunlock(&ip->fraglock6);
524                 ip->stats[ReasmReqds]++;
525                 return nil;
526         }
527
528         /*
529          *  find the new fragment's position in the queue
530          */
531         prev = nil;
532         l = &f->blist;
533         bl = f->blist;
534         while(bl != nil && BKFG(bp)->foff > BKFG(bl)->foff) {
535                 prev = bl;
536                 l = &bl->next;
537                 bl = bl->next;
538         }
539
540         /* Check overlap of a previous fragment - trim away as necessary */
541         if(prev) {
542                 ovlap = BKFG(prev)->foff + BKFG(prev)->flen - BKFG(bp)->foff;
543                 if(ovlap > 0) {
544                         if(ovlap >= BKFG(bp)->flen) {
545                                 freeblist(bp);
546                                 qunlock(&ip->fraglock6);
547                                 return nil;
548                         }
549                         BKFG(prev)->flen -= ovlap;
550                 }
551         }
552
553         /* Link onto assembly queue */
554         bp->next = *l;
555         *l = bp;
556
557         /* Check to see if succeeding segments overlap */
558         if(bp->next) {
559                 l = &bp->next;
560                 fend = BKFG(bp)->foff + BKFG(bp)->flen;
561
562                 /* Take completely covered segments out */
563                 while(*l) {
564                         ovlap = fend - BKFG(*l)->foff;
565                         if(ovlap <= 0)
566                                 break;
567                         if(ovlap < BKFG(*l)->flen) {
568                                 BKFG(*l)->flen -= ovlap;
569                                 BKFG(*l)->foff += ovlap;
570                                 /* move up ih hdrs */
571                                 memmove((*l)->rp + ovlap, (*l)->rp, uflen);
572                                 (*l)->rp += ovlap;
573                                 break;
574                         }
575                         last = (*l)->next;
576                         (*l)->next = nil;
577                         freeblist(*l);
578                         *l = last;
579                 }
580         }
581
582         /*
583          *  look for a complete packet.  if we get to a fragment
584          *  with the trailing bit of fraghdr->offsetRM[1] set, we're done.
585          */
586         pktposn = 0;
587         for(bl = f->blist; bl && BKFG(bl)->foff == pktposn; bl = bl->next) {
588                 fraghdr = (Fraghdr6 *)(bl->rp + uflen);
589                 if((fraghdr->offsetRM[1] & 1) == 0) {
590                         bl = f->blist;
591
592                         /* get rid of frag header in first fragment */
593                         memmove(bl->rp + IP6FHDR, bl->rp, uflen);
594                         bl->rp += IP6FHDR;
595                         len = nhgets(((Ip6hdr*)bl->rp)->ploadlen) - IP6FHDR;
596                         bl->wp = bl->rp + len + IP6HDR;
597                         /*
598                          * Pullup all the fragment headers and
599                          * return a complete packet
600                          */
601                         for(bl = bl->next; bl; bl = bl->next) {
602                                 fragsize = BKFG(bl)->flen;
603                                 len += fragsize;
604                                 bl->rp += uflen + IP6FHDR;
605                                 bl->wp = bl->rp + fragsize;
606                         }
607
608                         bl = f->blist;
609                         f->blist = nil;
610                         ipfragfree6(ip, f);
611                         ih = (Ip6hdr*)bl->rp;
612                         hnputs(ih->ploadlen, len);
613                         qunlock(&ip->fraglock6);
614                         ip->stats[ReasmOKs]++;
615                         return bl;
616                 }
617                 pktposn += BKFG(bl)->flen;
618         }
619         qunlock(&ip->fraglock6);
620         return nil;
621 }