xref: /plan9/sys/src/9/ip/ipv6.c (revision c1dd2601e42f5138f07719cdf2819a51c0f6f850)
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
ipoput6(Fs * f,Block * bp,int gating,int ttl,int tos,Conv * c)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
ipiput6(Fs * f,Ipifc * ifc,Block * bp)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("ipv6 non-icmp tentative addr %I, drop\n", v6dst);
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
ipfragfree6(IP * ip,Fragment6 * frag)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*
ipfragallo6(IP * ip)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*
procxtns(IP * ip,Block * bp,int doreasm)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
unfraglen(Block * bp,uchar * nexthdr,int setfh)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*
procopts(Block * bp)448 procopts(Block *bp)
449 {
450 	return bp;
451 }
452 
453 Block*
ip6reassemble(IP * ip,int uflen,Block * bp,Ip6hdr * ih)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 }
622