ipv6.c 14 KB

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