]> git.lizzy.rs Git - plan9front.git/blob - sys/src/9/ip/gre.c
kernel: dont use atomic increment for Proc.nlocks, maintain Lock.m for lock(), use...
[plan9front.git] / sys / src / 9 / ip / gre.c
1 /*
2  * Generic Routing Encapsulation over IPv4, rfc1702
3  */
4 #include "u.h"
5 #include "../port/lib.h"
6 #include "mem.h"
7 #include "dat.h"
8 #include "fns.h"
9 #include "../port/error.h"
10
11 #include "ip.h"
12
13 enum {
14         GRE_IPONLY      = 12,           /* size of ip header */
15         GRE_IPPLUSGRE   = 12,           /* minimum size of GRE header */
16         IP_GREPROTO     = 47,
17
18         GRErxms         = 200,
19         GREtickms       = 100,
20         GREmaxxmit      = 10,
21
22         K               = 1024,
23         GREqlen         = 256 * K,
24
25         GRE_cksum       = 0x8000,
26         GRE_routing     = 0x4000,
27         GRE_key         = 0x2000,
28         GRE_seq         = 0x1000,
29
30         Nring           = 1 << 10,      /* power of two, please */
31         Ringmask        = Nring - 1,
32
33         GREctlraw       = 0,
34         GREctlcooked,
35         GREctlretunnel,
36         GREctlreport,
37         GREctldlsuspend,
38         GREctlulsuspend,
39         GREctldlresume,
40         GREctlulresume,
41         GREctlforward,
42         GREctlulkey,
43         Ncmds,
44 };
45
46 typedef struct GREhdr GREhdr;
47 struct GREhdr{
48         /* ip header */
49         uchar   vihl;           /* Version and header length */
50         uchar   tos;            /* Type of service */
51         uchar   len[2];         /* packet length (including headers) */
52         uchar   id[2];          /* Identification */
53         uchar   frag[2];        /* Fragment information */
54         uchar   ttl;
55         uchar   proto;          /* Protocol */
56         uchar   cksum[2];       /* checksum */
57         uchar   src[4];         /* Ip source */
58         uchar   dst[4];         /* Ip destination */
59
60         /* gre header */
61         uchar   flags[2];
62         uchar   eproto[2];      /* encapsulation protocol */
63 };
64
65 typedef struct GREpriv GREpriv;
66 struct GREpriv{
67         /* non-MIB stats */
68         ulong   lenerr;                 /* short packet */
69 };
70
71 typedef struct Bring    Bring;
72 struct Bring{
73         Block   *ring[Nring];
74         long    produced;
75         long    consumed;
76 };
77
78 typedef struct GREconv  GREconv;
79 struct GREconv{
80         int     raw;
81
82         /* Retunnelling information.  v4 only */
83         uchar   north[4];                       /* HA */
84         uchar   south[4];                       /* Base station */
85         uchar   hoa[4];                         /* Home address */
86         uchar   coa[4];                         /* Careof address */
87         ulong   seq;                            /* Current sequence # */
88         int     dlsusp;                         /* Downlink suspended? */
89         int     ulsusp;                         /* Uplink suspended? */
90         ulong   ulkey;                          /* GRE key */
91
92         QLock   lock;                           /* Lock for rings */
93         Bring   dlpending;                      /* Ring of pending packets */
94         Bring   dlbuffered;                     /* Received while suspended */
95         Bring   ulbuffered;                     /* Received while suspended */
96 };
97
98 typedef struct Metablock Metablock;
99 struct Metablock{
100         uchar   *rp;
101         ulong   seq;
102 };
103
104 static char *grectlcooked(Conv *, int, char **);
105 static char *grectldlresume(Conv *, int, char **);
106 static char *grectldlsuspend(Conv *, int, char **);
107 static char *grectlforward(Conv *, int, char **);
108 static char *grectlraw(Conv *, int, char **);
109 static char *grectlreport(Conv *, int, char **);
110 static char *grectlretunnel(Conv *, int, char **);
111 static char *grectlulkey(Conv *, int, char **);
112 static char *grectlulresume(Conv *, int, char **);
113 static char *grectlulsuspend(Conv *, int, char **);
114
115 static struct{
116         char    *cmd;
117         int     argc;
118         char    *(*f)(Conv *, int, char **);
119 } grectls[Ncmds] = {
120 [GREctlraw]     =       {       "raw",          1,      grectlraw,      },
121 [GREctlcooked]  =       {       "cooked",       1,      grectlcooked,   },
122 [GREctlretunnel]=       {       "retunnel",     5,      grectlretunnel, },
123 [GREctlreport]  =       {       "report",       2,      grectlreport,   },
124 [GREctldlsuspend]=      {       "dlsuspend",    1,      grectldlsuspend,},
125 [GREctlulsuspend]=      {       "ulsuspend",    1,      grectlulsuspend,},
126 [GREctldlresume]=       {       "dlresume",     1,      grectldlresume, },
127 [GREctlulresume]=       {       "ulresume",     1,      grectlulresume, },
128 [GREctlforward] =       {       "forward",      2,      grectlforward,  },
129 [GREctlulkey]   =       {       "ulkey",        2,      grectlulkey,    },
130 };
131
132 static uchar nulladdr[4];
133 static char *sessend = "session end";
134
135 static void grekick(void *x, Block *bp);
136 static char *gresetup(Conv *, char *, char *, char *);
137
138 ulong grepdin, grepdout, grebdin, grebdout;
139 ulong grepuin, grepuout, grebuin, grebuout;
140
141 static Block *
142 getring(Bring *r)
143 {
144         Block *bp;
145
146         if(r->consumed == r->produced)
147                 return nil;
148
149         bp = r->ring[r->consumed & Ringmask];
150         r->ring[r->consumed & Ringmask] = nil;
151         r->consumed++;
152         return bp;
153 }
154
155 static void
156 addring(Bring *r, Block *bp)
157 {
158         Block *tbp;
159
160         if(r->produced - r->consumed > Ringmask){
161                 /* Full! */
162                 tbp = r->ring[r->produced & Ringmask];
163                 assert(tbp);
164                 freeb(tbp);
165                 r->consumed++;
166         }
167         r->ring[r->produced & Ringmask] = bp;
168         r->produced++;
169 }
170
171 static char *
172 greconnect(Conv *c, char **argv, int argc)
173 {
174         Proto *p;
175         char *err;
176         Conv *tc, **cp, **ecp;
177
178         err = Fsstdconnect(c, argv, argc);
179         if(err != nil)
180                 return err;
181
182         /* make sure noone's already connected to this other sys */
183         p = c->p;
184         qlock(p);
185         ecp = &p->conv[p->nc];
186         for(cp = p->conv; cp < ecp; cp++){
187                 tc = *cp;
188                 if(tc == nil)
189                         break;
190                 if(tc == c)
191                         continue;
192                 if(tc->rport == c->rport && ipcmp(tc->raddr, c->raddr) == 0){
193                         err = "already connected to that addr/proto";
194                         ipmove(c->laddr, IPnoaddr);
195                         ipmove(c->raddr, IPnoaddr);
196                         break;
197                 }
198         }
199         qunlock(p);
200
201         if(err != nil)
202                 return err;
203         Fsconnected(c, nil);
204
205         return nil;
206 }
207
208 static void
209 grecreate(Conv *c)
210 {
211         c->rq = qopen(GREqlen, Qmsg, 0, c);
212         c->wq = qbypass(grekick, c);
213 }
214
215 static int
216 grestate(Conv *c, char *state, int n)
217 {
218         GREconv *grec;
219         char *ep, *p;
220
221         grec = c->ptcl;
222         p    = state;
223         ep   = p + n;
224         p    = seprint(p, ep, "%s%s%s%shoa %V north %V south %V seq %ulx "
225          "pending %uld  %uld buffered dl %uld %uld ul %uld %uld ulkey %.8ulx\n",
226                         c->inuse? "Open ": "Closed ",
227                         grec->raw? "raw ": "",
228                         grec->dlsusp? "DL suspended ": "",
229                         grec->ulsusp? "UL suspended ": "",
230                         grec->hoa, grec->north, grec->south, grec->seq,
231                         grec->dlpending.consumed, grec->dlpending.produced,
232                         grec->dlbuffered.consumed, grec->dlbuffered.produced,
233                         grec->ulbuffered.consumed, grec->ulbuffered.produced,
234                         grec->ulkey);
235         return p - state;
236 }
237
238 static char*
239 greannounce(Conv*, char**, int)
240 {
241         return "gre does not support announce";
242 }
243
244 static void
245 greclose(Conv *c)
246 {
247         GREconv *grec;
248         Block *bp;
249
250         grec = c->ptcl;
251
252         /* Make sure we don't forward any more packets */
253         memset(grec->hoa, 0, sizeof grec->hoa);
254         memset(grec->north, 0, sizeof grec->north);
255         memset(grec->south, 0, sizeof grec->south);
256
257         qlock(&grec->lock);
258         while((bp = getring(&grec->dlpending)) != nil)
259                 freeb(bp);
260
261         while((bp = getring(&grec->dlbuffered)) != nil)
262                 freeb(bp);
263
264         while((bp = getring(&grec->ulbuffered)) != nil)
265                 freeb(bp);
266
267         grec->dlpending.produced = grec->dlpending.consumed = 0;
268         grec->dlbuffered.produced = grec->dlbuffered.consumed = 0;
269         grec->ulbuffered.produced = grec->ulbuffered.consumed = 0;
270         qunlock(&grec->lock);
271
272         grec->raw = 0;
273         grec->seq = 0;
274         grec->dlsusp = grec->ulsusp = 1;
275
276         qhangup(c->rq, sessend);
277         qhangup(c->wq, sessend);
278         qhangup(c->eq, sessend);
279         ipmove(c->laddr, IPnoaddr);
280         ipmove(c->raddr, IPnoaddr);
281         c->lport = c->rport = 0;
282 }
283
284 static void
285 grekick(void *x, Block *bp)
286 {
287         Conv *c;
288         GREconv *grec;
289         GREhdr *gre;
290         uchar laddr[IPaddrlen], raddr[IPaddrlen];
291
292         if(bp == nil)
293                 return;
294
295         c    = x;
296         grec = c->ptcl;
297
298         /* Make space to fit ip header (gre header already there) */
299         bp = padblock(bp, GRE_IPONLY);
300         if(bp == nil)
301                 return;
302
303         /* make sure the message has a GRE header */
304         bp = pullupblock(bp, GRE_IPONLY+GRE_IPPLUSGRE);
305         if(bp == nil)
306                 return;
307
308         gre = (GREhdr *)bp->rp;
309         gre->vihl = IP_VER4;
310
311         if(grec->raw == 0){
312                 v4tov6(raddr, gre->dst);
313                 if(ipcmp(raddr, v4prefix) == 0)
314                         memmove(gre->dst, c->raddr + IPv4off, IPv4addrlen);
315                 v4tov6(laddr, gre->src);
316                 if(ipcmp(laddr, v4prefix) == 0){
317                         if(ipcmp(c->laddr, IPnoaddr) == 0)
318                                 /* pick interface closest to dest */
319                                 findlocalip(c->p->f, c->laddr, raddr);
320                         memmove(gre->src, c->laddr + IPv4off, sizeof gre->src);
321                 }
322                 hnputs(gre->eproto, c->rport);
323         }
324
325         gre->proto = IP_GREPROTO;
326         gre->frag[0] = gre->frag[1] = 0;
327
328         grepdout++;
329         grebdout += BLEN(bp);
330         ipoput4(c->p->f, bp, 0, c->ttl, c->tos, nil);
331 }
332
333 static void
334 gredownlink(Conv *c, Block *bp)
335 {
336         Metablock *m;
337         GREconv *grec;
338         GREhdr *gre;
339         int hdrlen, suspended, extra;
340         ushort flags;
341         ulong seq;
342
343         gre = (GREhdr *)bp->rp;
344         if(gre->ttl == 1){
345                 freeb(bp);
346                 return;
347         }
348
349         /*
350          * We've received a packet with a GRE header and we need to
351          * re-adjust the packet header to strip all unwanted parts
352          * but leave room for only a sequence number.
353          */
354         grec   = c->ptcl;
355         flags  = nhgets(gre->flags);
356         hdrlen = 0;
357         if(flags & GRE_cksum)
358                 hdrlen += 2;
359         if(flags & GRE_routing){
360                 print("%V routing info present.  Discarding packet", gre->src);
361                 freeb(bp);
362                 return;
363         }
364         if(flags & (GRE_cksum|GRE_routing))
365                 hdrlen += 2;                    /* Offset field */
366         if(flags & GRE_key)
367                 hdrlen += 4;
368         if(flags & GRE_seq)
369                 hdrlen += 4;
370
371         /*
372          * The outgoing packet only has the sequence number set.  Make room
373          * for the sequence number.
374          */
375         if(hdrlen != sizeof(ulong)){
376                 extra = hdrlen - sizeof(ulong);
377                 if(extra < 0 && bp->rp - bp->base < -extra){
378                         print("gredownlink: cannot add sequence number\n");
379                         freeb(bp);
380                         return;
381                 }
382                 memmove(bp->rp + extra, bp->rp, sizeof(GREhdr));
383                 bp->rp += extra;
384                 assert(BLEN(bp) >= sizeof(GREhdr) + sizeof(ulong));
385                 gre = (GREhdr *)bp->rp;
386         }
387         seq = grec->seq++;
388         hnputs(gre->flags, GRE_seq);
389         hnputl(bp->rp + sizeof(GREhdr), seq);
390
391         /*
392          * Keep rp and seq at the base.  ipoput4 consumes rp for
393          * refragmentation.
394          */
395         assert(bp->rp - bp->base >= sizeof(Metablock));
396         m = (Metablock *)bp->base;
397         m->rp  = bp->rp;
398         m->seq = seq;
399
400         /*
401          * Here we make a decision what we're doing with the packet.  We're
402          * doing this w/o holding a lock which means that later on in the
403          * process we may discover we've done the wrong thing.  I don't want
404          * to call ipoput with the lock held.
405          */
406 restart:
407         suspended = grec->dlsusp;
408         if(suspended){
409                 if(!canqlock(&grec->lock)){
410                         /*
411                          * just give up.  too bad, we lose a packet.  this
412                          * is just too hard and my brain already hurts.
413                          */
414                         freeb(bp);
415                         return;
416                 }
417
418                 if(!grec->dlsusp){
419                         /*
420                          * suspend race.  We though we were suspended, but
421                          * we really weren't.
422                          */
423                         qunlock(&grec->lock);
424                         goto restart;
425                 }
426
427                 /* Undo the incorrect ref count addition */
428                 addring(&grec->dlbuffered, bp);
429                 qunlock(&grec->lock);
430                 return;
431         }
432
433         /*
434          * When we get here, we're not suspended.  Proceed to send the
435          * packet.
436          */
437         memmove(gre->src, grec->coa, sizeof gre->dst);
438         memmove(gre->dst, grec->south, sizeof gre->dst);
439
440         /*
441          * Make sure the packet does not go away.
442          */
443         _xinc(&bp->ref);
444         assert(bp->ref == 2);
445
446         ipoput4(c->p->f, bp, 0, gre->ttl - 1, gre->tos, nil);
447         grepdout++;
448         grebdout += BLEN(bp);
449
450         /*
451          * Now make sure we didn't do the wrong thing.
452          */
453         if(!canqlock(&grec->lock)){
454                 freeb(bp);              /* The packet just goes away */
455                 return;
456         }
457
458         /* We did the right thing */
459         addring(&grec->dlpending, bp);
460         qunlock(&grec->lock);
461 }
462
463 static void
464 greuplink(Conv *c, Block *bp)
465 {
466         GREconv *grec;
467         GREhdr *gre;
468         ushort flags;
469
470         gre = (GREhdr *)bp->rp;
471         if(gre->ttl == 1)
472                 return;
473
474         grec = c->ptcl;
475         memmove(gre->src, grec->coa, sizeof gre->src);
476         memmove(gre->dst, grec->north, sizeof gre->dst);
477
478         /*
479          * Add a key, if needed.
480          */
481         if(grec->ulkey){
482                 flags = nhgets(gre->flags);
483                 if(flags & (GRE_cksum|GRE_routing)){
484                         print("%V routing info present.  Discarding packet\n",
485                                 gre->src);
486                         freeb(bp);
487                         return;
488                 }
489
490                 if((flags & GRE_key) == 0){
491                         /* Make room for the key */
492                         if(bp->rp - bp->base < sizeof(ulong)){
493                                 print("%V can't add key\n", gre->src);
494                                 freeb(bp);
495                                 return;
496                         }
497
498                         bp->rp -= 4;
499                         memmove(bp->rp, bp->rp + 4, sizeof(GREhdr));
500
501                         gre = (GREhdr *)bp->rp;
502                         hnputs(gre->flags, flags | GRE_key);
503                 }
504
505                 /* Add the key */
506                 hnputl(bp->rp + sizeof(GREhdr), grec->ulkey);
507         }
508
509         if(!canqlock(&grec->lock)){
510                 freeb(bp);
511                 return;
512         }
513
514         if(grec->ulsusp)
515                 addring(&grec->ulbuffered, bp);
516         else{
517                 ipoput4(c->p->f, bp, 0, gre->ttl - 1, gre->tos, nil);
518                 grepuout++;
519                 grebuout += BLEN(bp);
520         }
521         qunlock(&grec->lock);
522 }
523
524 static void
525 greiput(Proto *proto, Ipifc *, Block *bp)
526 {
527         int len, hdrlen;
528         ushort eproto, flags;
529         uchar raddr[IPaddrlen];
530         Conv *c, **p;
531         GREconv *grec;
532         GREhdr *gre;
533         GREpriv *gpriv;
534         Ip4hdr *ip;
535
536         /*
537          * We don't want to deal with block lists.  Ever.  The problem is
538          * that when the block is forwarded, devether.c puts the block into
539          * a queue that also uses ->next.  Just do not use ->next here!
540          */
541         if(bp->next){
542                 len = blocklen(bp);
543                 bp  = pullupblock(bp, len);
544                 assert(BLEN(bp) == len && bp->next == nil);
545         }
546
547         gre = (GREhdr *)bp->rp;
548         if(BLEN(bp) < sizeof(GREhdr) || gre->proto != IP_GREPROTO){
549                 freeb(bp);
550                 return;
551         }
552
553         v4tov6(raddr, gre->src);
554         eproto = nhgets(gre->eproto);
555         flags  = nhgets(gre->flags);
556         hdrlen = sizeof(GREhdr);
557
558         if(flags & GRE_cksum)
559                 hdrlen += 2;
560         if(flags & GRE_routing){
561                 print("%I routing info present.  Discarding packet\n", raddr);
562                 freeb(bp);
563                 return;
564         }
565         if(flags & (GRE_cksum|GRE_routing))
566                 hdrlen += 2;                    /* Offset field */
567         if(flags & GRE_key)
568                 hdrlen += 4;
569         if(flags & GRE_seq)
570                 hdrlen += 4;
571
572         if(BLEN(bp) - hdrlen < sizeof(Ip4hdr)){
573                 print("greretunnel: packet too short (s=%V d=%V)\n",
574                         gre->src, gre->dst);
575                 freeb(bp);
576                 return;
577         }
578         ip = (Ip4hdr *)(bp->rp + hdrlen);
579
580         qlock(proto);
581         /*
582          * Look for a conversation structure for this port and address, or
583          * match the retunnel part, or match on the raw flag.
584          */
585         for(p = proto->conv; *p; p++) {
586                 c = *p;
587
588                 if(c->inuse == 0)
589                         continue;
590
591                 /*
592                  * Do not stop this session - blocking here
593                  * implies that etherread is blocked.
594                  */
595                 grec = c->ptcl;
596                 if(memcmp(ip->dst, grec->hoa, sizeof ip->dst) == 0){
597                         grepdin++;
598                         grebdin += BLEN(bp);
599                         gredownlink(c, bp);
600                         qunlock(proto);
601                         return;
602                 }
603
604                 if(memcmp(ip->src, grec->hoa, sizeof ip->src) == 0){
605                         grepuin++;
606                         grebuin += BLEN(bp);
607                         greuplink(c, bp);
608                         qunlock(proto);
609                         return;
610                 }
611         }
612
613         /*
614          * when we get here, none of the forwarding tunnels matched.  now
615          * try to match on raw and conversational sessions.
616          */
617         for(c = nil, p = proto->conv; *p; p++) {
618                 c = *p;
619
620                 if(c->inuse == 0)
621                         continue;
622
623                 /*
624                  * Do not stop this session - blocking here
625                  * implies that etherread is blocked.
626                  */
627                 grec = c->ptcl;
628                 if(c->rport == eproto &&
629                     (grec->raw || ipcmp(c->raddr, raddr) == 0))
630                         break;
631         }
632
633         qunlock(proto);
634
635         if(*p == nil){
636                 freeb(bp);
637                 return;
638         }
639
640         /*
641          * Trim the packet down to data size
642          */
643         len = nhgets(gre->len) - GRE_IPONLY;
644         if(len < GRE_IPPLUSGRE){
645                 freeb(bp);
646                 return;
647         }
648
649         bp = trimblock(bp, GRE_IPONLY, len);
650         if(bp == nil){
651                 gpriv = proto->priv;
652                 gpriv->lenerr++;
653                 return;
654         }
655
656         /*
657          *  Can't delimit packet so pull it all into one block.
658          */
659         if(qlen(c->rq) > GREqlen)
660                 freeb(bp);
661         else{
662                 bp = concatblock(bp);
663                 if(bp == 0)
664                         panic("greiput");
665                 qpass(c->rq, bp);
666         }
667 }
668
669 int
670 grestats(Proto *gre, char *buf, int len)
671 {
672         GREpriv *gpriv;
673
674         gpriv = gre->priv;
675         return snprint(buf, len,
676                 "gre: %lud %lud %lud %lud %lud %lud %lud %lud, lenerrs %lud\n",
677                 grepdin, grepdout, grepuin, grepuout,
678                 grebdin, grebdout, grebuin, grebuout, gpriv->lenerr);
679 }
680
681 static char *
682 grectlraw(Conv *c, int, char **)
683 {
684         GREconv *grec;
685
686         grec = c->ptcl;
687         grec->raw = 1;
688         return nil;
689 }
690
691 static char *
692 grectlcooked(Conv *c, int, char **)
693 {
694         GREconv *grec;
695
696         grec = c->ptcl;
697         grec->raw = 0;
698         return nil;
699 }
700
701 static char *
702 grectlretunnel(Conv *c, int, char **argv)
703 {
704         GREconv *grec;
705         uchar ipaddr[4];
706
707         grec = c->ptcl;
708         if(memcmp(grec->hoa, nulladdr, sizeof grec->hoa))
709                 return "tunnel already set up";
710
711         v4parseip(ipaddr, argv[1]);
712         if(memcmp(ipaddr, nulladdr, sizeof ipaddr) == 0)
713                 return "bad hoa";
714         memmove(grec->hoa, ipaddr, sizeof grec->hoa);
715         v4parseip(ipaddr, argv[2]);
716         memmove(grec->north, ipaddr, sizeof grec->north);
717         v4parseip(ipaddr, argv[3]);
718         memmove(grec->south, ipaddr, sizeof grec->south);
719         v4parseip(ipaddr, argv[4]);
720         memmove(grec->coa, ipaddr, sizeof grec->coa);
721         grec->ulsusp = 1;
722         grec->dlsusp = 0;
723
724         return nil;
725 }
726
727 static char *
728 grectlreport(Conv *c, int, char **argv)
729 {
730         ulong seq;
731         Block *bp;
732         Bring *r;
733         GREconv *grec;
734         Metablock *m;
735
736         grec = c->ptcl;
737         seq  = strtoul(argv[1], nil, 0);
738
739         qlock(&grec->lock);
740         r = &grec->dlpending;
741         while(r->produced - r->consumed > 0){
742                 bp = r->ring[r->consumed & Ringmask];
743
744                 assert(bp && bp->rp - bp->base >= sizeof(Metablock));
745                 m = (Metablock *)bp->base;
746                 if((long)(seq - m->seq) <= 0)
747                         break;
748
749                 r->ring[r->consumed & Ringmask] = nil;
750                 r->consumed++;
751
752                 freeb(bp);
753         }
754         qunlock(&grec->lock);
755         return nil;
756 }
757
758 static char *
759 grectldlsuspend(Conv *c, int, char **)
760 {
761         GREconv *grec;
762
763         grec = c->ptcl;
764         if(grec->dlsusp)
765                 return "already suspended";
766
767         grec->dlsusp = 1;
768         return nil;
769 }
770
771 static char *
772 grectlulsuspend(Conv *c, int, char **)
773 {
774         GREconv *grec;
775
776         grec = c->ptcl;
777         if(grec->ulsusp)
778                 return "already suspended";
779
780         grec->ulsusp = 1;
781         return nil;
782 }
783
784 static char *
785 grectldlresume(Conv *c, int, char **)
786 {
787         GREconv *grec;
788         GREhdr *gre;
789         Block *bp;
790
791         grec = c->ptcl;
792
793         qlock(&grec->lock);
794         if(!grec->dlsusp){
795                 qunlock(&grec->lock);
796                 return "not suspended";
797         }
798
799         while((bp = getring(&grec->dlbuffered)) != nil){
800                 gre = (GREhdr *)bp->rp;
801                 qunlock(&grec->lock);
802
803                 /*
804                  * Make sure the packet does not go away.
805                  */
806                 _xinc(&bp->ref);
807                 assert(bp->ref == 2);
808
809                 ipoput4(c->p->f, bp, 0, gre->ttl - 1, gre->tos, nil);
810
811                 qlock(&grec->lock);
812                 addring(&grec->dlpending, bp);
813         }
814         grec->dlsusp = 0;
815         qunlock(&grec->lock);
816         return nil;
817 }
818
819 static char *
820 grectlulresume(Conv *c, int, char **)
821 {
822         GREconv *grec;
823         GREhdr *gre;
824         Block *bp;
825
826         grec = c->ptcl;
827
828         qlock(&grec->lock);
829         while((bp = getring(&grec->ulbuffered)) != nil){
830                 gre = (GREhdr *)bp->rp;
831
832                 qunlock(&grec->lock);
833                 ipoput4(c->p->f, bp, 0, gre->ttl - 1, gre->tos, nil);
834                 qlock(&grec->lock);
835         }
836         grec->ulsusp = 0;
837         qunlock(&grec->lock);
838         return nil;
839 }
840
841 static char *
842 grectlforward(Conv *c, int, char **argv)
843 {
844         int len;
845         Block *bp, *nbp;
846         GREconv *grec;
847         GREhdr *gre;
848         Metablock *m;
849
850         grec = c->ptcl;
851
852         v4parseip(grec->south, argv[1]);
853         memmove(grec->north, grec->south, sizeof grec->north);
854
855         qlock(&grec->lock);
856         if(!grec->dlsusp){
857                 qunlock(&grec->lock);
858                 return "not suspended";
859         }
860         grec->dlsusp = 0;
861         grec->ulsusp = 0;
862
863         while((bp = getring(&grec->dlpending)) != nil){
864
865                 assert(bp->rp - bp->base >= sizeof(Metablock));
866                 m = (Metablock *)bp->base;
867                 assert(m->rp >= bp->base && m->rp < bp->lim);
868
869                 /*
870                  * If the packet is still held inside the IP transmit
871                  * system, make a copy of the packet first.
872                  */
873                 if(bp->ref > 1){
874                         len = bp->wp - m->rp;
875                         nbp = allocb(len);
876                         memmove(nbp->wp, m->rp, len);
877                         nbp->wp += len;
878                         freeb(bp);
879                         bp  = nbp;
880                 }
881                 else{
882                         /* Patch up rp */
883                         bp->rp = m->rp;
884                 }
885
886                 gre = (GREhdr *)bp->rp;
887                 memmove(gre->src, grec->coa, sizeof gre->dst);
888                 memmove(gre->dst, grec->south, sizeof gre->dst);
889
890                 qunlock(&grec->lock);
891                 ipoput4(c->p->f, bp, 0, gre->ttl - 1, gre->tos, nil);
892                 qlock(&grec->lock);
893         }
894
895         while((bp = getring(&grec->dlbuffered)) != nil){
896                 gre = (GREhdr *)bp->rp;
897                 memmove(gre->src, grec->coa, sizeof gre->dst);
898                 memmove(gre->dst, grec->south, sizeof gre->dst);
899
900                 qunlock(&grec->lock);
901                 ipoput4(c->p->f, bp, 0, gre->ttl - 1, gre->tos, nil);
902                 qlock(&grec->lock);
903         }
904
905         while((bp = getring(&grec->ulbuffered)) != nil){
906                 gre = (GREhdr *)bp->rp;
907
908                 memmove(gre->src, grec->coa, sizeof gre->dst);
909                 memmove(gre->dst, grec->south, sizeof gre->dst);
910
911                 qunlock(&grec->lock);
912                 ipoput4(c->p->f, bp, 0, gre->ttl - 1, gre->tos, nil);
913                 qlock(&grec->lock);
914         }
915         qunlock(&grec->lock);
916         return nil;
917 }
918
919 static char *
920 grectlulkey(Conv *c, int, char **argv)
921 {
922         GREconv *grec;
923
924         grec = c->ptcl;
925         grec->ulkey = strtoul(argv[1], nil, 0);
926         return nil;
927 }
928
929 char *
930 grectl(Conv *c, char **f, int n)
931 {
932         int i;
933
934         if(n < 1)
935                 return "too few arguments";
936
937         for(i = 0; i < Ncmds; i++)
938                 if(strcmp(f[0], grectls[i].cmd) == 0)
939                         break;
940
941         if(i == Ncmds)
942                 return "no such command";
943         if(grectls[i].argc != 0 && grectls[i].argc != n)
944                 return "incorrect number of arguments";
945
946         return grectls[i].f(c, n, f);
947 }
948
949 void
950 greinit(Fs *fs)
951 {
952         Proto *gre;
953
954         gre = smalloc(sizeof(Proto));
955         gre->priv = smalloc(sizeof(GREpriv));
956         gre->name = "gre";
957         gre->connect = greconnect;
958         gre->announce = greannounce;
959         gre->state = grestate;
960         gre->create = grecreate;
961         gre->close = greclose;
962         gre->rcv = greiput;
963         gre->ctl = grectl;
964         gre->advise = nil;
965         gre->stats = grestats;
966         gre->ipproto = IP_GREPROTO;
967         gre->nc = 64;
968         gre->ptclsize = sizeof(GREconv);
969
970         Fsproto(fs, gre);
971 }