]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/ayiya.c
ip/ayiya: experimental anything in anything tunnel protocol client
[plan9front.git] / sys / src / cmd / ip / ayiya.c
1 /*
2  * ayiya - tunnel client.
3  */
4
5 #include <u.h>
6 #include <libc.h>
7 #include <ip.h>
8 #include <mp.h>
9 #include <libsec.h>
10
11 /*
12  * IPv6 and related IP protocols & their numbers:
13  *
14  * ipv6         41      IPv6            # Internet Protocol, version 6
15  * ipv6-route   43      IPv6-Route      # Routing Header for IPv6
16  * ipv6-frag    44      IPv6-Frag       # Fragment Header for IPv6
17  * esp          50      ESP             # Encapsulating Security Payload
18  * ah           51      AH              # Authentication Header
19  * ipv6-icmp    58      IPv6-ICMP icmp6 # ICMP version 6
20  * ipv6-nonxt   59      IPv6-NoNxt      # No Next Header for IPv6
21  * ipv6-opts    60      IPv6-Opts       # Destination Options for IPv6
22  */
23 enum {
24         IP_IPV6PROTO    = 41,           /* IPv4 protocol number for IPv6 */
25         IP_ESPPROTO     = 50,           /* IP v4 and v6 protocol number */
26         IP_AHPROTO      = 51,           /* IP v4 and v6 protocol number */
27         IP_ICMPV6PROTO  = 58,
28         V6to4pfx        = 0x2002,
29
30         IP_MAXPAY       = 2*1024,
31 };
32
33 enum {
34         AYIYAMAXID      = 1<<15,
35         AYIYAMAXSIG     = 15*4,
36
37         AYIYAMAXHDR     = 8+AYIYAMAXID+AYIYAMAXSIG,
38
39         IdNone = 0,
40         IdInteger,
41         IdString,
42
43         HashNone = 0,
44         HashMD5,
45         HashSHA1,
46
47         AuthNone = 0,
48         AuthSharedKey,
49         AuthPubKey,
50
51         OpNone = 0,
52         OpForward,
53         OpEchoRequest,
54         OpEchoRequestAndForward,
55         OpEchoResponse,
56         OpMOTD,
57         OpQueryRequest,
58         OpQueryResponse,        
59 };
60
61 typedef struct AYIYA AYIYA;
62 struct AYIYA
63 {
64         uint    idlen;
65         uint    idtype;
66         uint    siglen;
67         uint    hashmeth;
68         uint    authmeth;
69         uint    opcode;
70         uint    nexthdr;
71         uint    epochtime;
72
73         uchar   *identity;
74         uchar   *signature;
75 };
76
77 AYIYA   conf;
78
79 int gateway;
80 int debug;
81
82 uchar local6[IPaddrlen];
83 uchar remote6[IPaddrlen];
84 uchar localmask[IPaddrlen];
85 uchar localnet[IPaddrlen];
86
87 uchar nullsig[AYIYAMAXSIG];
88
89 static char *secret = nil;
90
91 static char *outside = nil;     /* dial string of tunnel server */
92 static char *inside = "/net";
93
94 static int      badipv4(uchar*);
95 static int      badipv6(uchar*);
96 static void     ip2tunnel(int, int);
97 static void     tunnel2ip(int, int);
98
99 static void
100 ayiyadump(AYIYA *a)
101 {
102         int i;
103
104         fprint(2, "idlen=%ud idtype=%ux siglen=%ud hashmeth=%ud authmeth=%ud opcode=%ux nexthdr=%ux epochtime=%ux\n",
105                 a->idlen, a->idtype, a->siglen, a->hashmeth, a->authmeth, a->opcode, a->nexthdr, a->epochtime);
106         fprint(2, "identity=[ ");
107         for(i=0; i<a->idlen; i++)
108                 fprint(2, "%.2ux ", a->identity[i]);
109         fprint(2, "] ");
110         fprint(2, "signature=[ ");
111         for(i=0; i<a->siglen; i++)
112                 fprint(2, "%.2ux ", a->signature[i]);
113         fprint(2, "]\n");
114
115 }
116
117 static uint
118 lg2(uint a)
119 {
120         uint n;
121
122         for(n = 0; (a >>= 1) != 0; n++)
123                 ;
124         return n;
125 }
126
127 static int
128 ayiyapack(AYIYA *a, uchar *pay, int paylen)
129 {
130         uchar *pkt;
131
132         pkt = pay;
133         if(a->siglen > 0){
134                 pkt -= a->siglen;
135                 memmove(pkt, a->signature, a->siglen);
136         }
137         if(a->idlen > 0){
138                 pkt -= a->idlen;
139                 memmove(pkt, a->identity, a->idlen);
140         }
141
142         pkt -= 4;
143         pkt[0] = a->epochtime>>24;
144         pkt[1] = a->epochtime>>16;
145         pkt[2] = a->epochtime>>8;
146         pkt[3] = a->epochtime;
147
148         pkt -= 4;
149         pkt[0] = (lg2(a->idlen)<<4) | a->idtype;
150         pkt[1] = ((a->siglen/4)<<4) | a->hashmeth;
151         pkt[2] = (a->authmeth<<4) | a->opcode;
152         pkt[3] = a->nexthdr;
153
154         USED(paylen);
155
156         return pay - pkt;
157 }
158
159 static int
160 ayiyaunpack(AYIYA *a, uchar *pkt, int pktlen)
161 {
162         int hdrlen;
163
164         if(pktlen < 8)
165                 return -1;
166
167         a->idlen = 1<<(pkt[0] >> 4);
168         a->idtype = pkt[0] & 15;
169         a->siglen = (pkt[1] >> 4) * 4;
170         a->hashmeth = pkt[1] & 15;
171         a->authmeth = pkt[2] >> 4;
172         a->opcode = pkt[2] & 15;
173         a->nexthdr = pkt[3];
174         a->epochtime = pkt[7] | pkt[6]<<8 | pkt[5]<<16 | pkt[4]<<24;
175
176         hdrlen = 8 + a->idlen + a->siglen;
177         if(hdrlen > pktlen)
178                 return -1;
179
180         a->identity = nil;
181         if(a->idlen > 0)
182                 a->identity = pkt + 8;
183
184         a->signature = nil;
185         if(a->siglen > 0)
186                 a->signature = pkt + 8 + a->idlen;
187
188         return hdrlen;
189 }
190
191 static int
192 ayiyahash(uint meth, uchar *pkt, int pktlen, uchar *dig)
193 {
194         switch(meth){
195         case HashMD5:
196                 if(dig != nil)
197                         md5(pkt, pktlen, dig, nil);
198                 return MD5dlen;
199         case HashSHA1:
200                 if(dig != nil)
201                         sha1(pkt, pktlen, dig, nil);
202                 return SHA1dlen;
203         }
204         return 0;
205 }
206
207 static void
208 ayiyasign(AYIYA *a, uchar *pkt, int pktlen)
209 {
210         uchar dig[AYIYAMAXSIG], *pktsig;
211
212         if(a->hashmeth == HashNone && a->siglen == 0)
213                 return;
214
215         assert(a->siglen <= sizeof(dig));
216         assert(a->siglen <= pktlen - a->idlen - 8);
217         pktsig = pkt + 8 + a->idlen;
218
219         if(ayiyahash(a->hashmeth, pkt, pktlen, dig) != a->siglen){
220                 memset(pktsig, 0, a->siglen);
221                 return;
222         }
223
224         memmove(pktsig, dig, a->siglen);
225 }
226
227 static int
228 ayiyaverify(AYIYA *a, uchar *pkt, int pktlen)
229 {
230         uchar dig[AYIYAMAXSIG], sig[AYIYAMAXSIG];
231
232         if(conf.hashmeth == HashNone && a->siglen == 0)
233                 return 0;
234         if(a->hashmeth != conf.hashmeth || a->authmeth != conf.authmeth || a->siglen != conf.siglen)
235                 return -1;
236         memmove(sig, a->signature, a->siglen);
237         memmove(a->signature, conf.signature, a->siglen);
238         if(ayiyahash(a->hashmeth, pkt, pktlen, dig) != a->siglen)
239                 return -1;
240         return memcmp(sig, dig, a->siglen) != 0;
241 }
242
243 static int
244 ayiyaout(int fd, AYIYA *a, uchar *p, int n)
245 {
246         int m;
247
248         a->idlen = conf.idlen;
249         a->siglen = conf.siglen;
250         a->idtype = conf.idtype;
251         a->hashmeth = conf.hashmeth;
252         a->authmeth = conf.authmeth;
253         a->identity = conf.identity;
254         a->signature = conf.signature;
255
256         a->epochtime = time(nil);
257
258         if (debug > 1) {
259                 fprint(2, "send: ");
260                 ayiyadump(a);
261         }
262
263         m = ayiyapack(a, p, n);
264         n += m, p -= m;
265
266         ayiyasign(a, p, n);
267
268         if (write(fd, p, n) != n) {
269                 syslog(0, "ayiya", "error writing to tunnel (%r), giving up");
270                 return -1;
271         }
272         return 0;
273 }
274
275 static int
276 ayiyarquery(char *q)
277 {
278         fprint(2, "ayiyarquery: %s\n", q);
279         *q = '\0';
280         return 0;
281 }
282
283 static void
284 usage(void)
285 {
286         fprint(2, "usage: %s [-g] [-x mtpt] [-k secret] local6[/mask] remote4 remote6\n",
287                 argv0);
288         exits("Usage");
289 }
290
291 /* process non-option arguments */
292 static void
293 procargs(int argc, char **argv)
294 {
295         char *p, *loc6;
296
297         if (argc < 3)
298                 usage();
299
300         loc6 = *argv++;
301         argc--;
302
303         /* local v6 address (mask defaults to /128) */
304         memcpy(localmask, IPallbits, sizeof localmask);
305         p = strchr(loc6, '/');
306         if (p != nil) {
307                 parseipmask(localmask, p);
308                 *p = 0;
309         }
310         if (parseip(local6, loc6) == -1)
311                 sysfatal("bad local v6 address %s", loc6);
312         if (isv4(local6))
313                 usage();
314         if (argc >= 1 && argv[0][0] == '/') {
315                 parseipmask(localmask, *argv++);
316                 argc--;
317         }
318         if (debug)
319                 fprint(2, "local6 %I %M\n", local6, localmask);
320
321         outside = netmkaddr(*argv++, "udp", "5072");
322         argc--;
323         if(outside == nil)
324                 usage();
325         outside = strdup(outside);
326         if (debug)
327                 fprint(2, "outside %s\n", outside);
328
329         /* remote v6 address */
330         if (parseip(remote6, *argv++) == -1)
331                 sysfatal("bad remote v6 address %s", argv[-1]);
332         argc--;
333         if (argc != 0)
334                 usage();
335
336         maskip(local6, localmask, localnet);
337         if (debug)
338                 fprint(2, "localnet %I remote6 %I\n", localnet, remote6);
339 }
340
341 static void
342 setup(int *v6net)
343 {
344         int n, cfd;
345         char *cl, *ir;
346         char buf[128], path[64];
347
348         /*
349          * open local IPv6 interface (as a packet interface)
350          */
351
352         cl = smprint("%s/ipifc/clone", inside);
353         cfd = open(cl, ORDWR);                  /* allocate a conversation */
354         n = 0;
355         if (cfd < 0 || (n = read(cfd, buf, sizeof buf - 1)) <= 0)
356                 sysfatal("can't make packet interface %s: %r", cl);
357         if (debug)
358                 fprint(2, "cloned %s as local v6 interface\n", cl);
359         free(cl);
360         buf[n] = 0;
361
362         snprint(path, sizeof path, "%s/ipifc/%s/data", inside, buf);
363         *v6net = open(path, ORDWR);
364         if (*v6net < 0 || fprint(cfd, "bind pkt") < 0)
365                 sysfatal("can't bind packet interface: %r");
366         /* 1280 is MTU, apparently from rfc2460 */
367         if (fprint(cfd, "add %I %M %I 1280", local6, localmask, remote6) <= 0)
368                 sysfatal("can't set local ipv6 address: %r");
369         close(cfd);
370         if (debug)
371                 fprint(2, "opened & bound %s as local v6 interface\n", path);
372
373         if (gateway) {
374                 /* route global addresses through the tunnel to remote6 */
375                 ir = smprint("%s/iproute", inside);
376                 cfd = open(ir, OWRITE);
377                 if (cfd >= 0 && debug)
378                         fprint(2, "injected 2000::/3 %I into %s\n", remote6, ir);
379                 free(ir);
380                 if (cfd < 0 || fprint(cfd, "add 2000:: /3 %I", remote6) <= 0)
381                         sysfatal("can't set default global route: %r");
382         }
383 }
384
385 static void
386 runtunnel(int v6net, int tunnel)
387 {
388         /* run the tunnel copying in the background */
389         switch (rfork(RFPROC|RFNOWAIT|RFMEM|RFNOTEG)) {
390         case -1:
391                 sysfatal("rfork");
392         default:
393                 exits(nil);
394         case 0:
395                 break;
396         }
397
398         switch (rfork(RFPROC|RFNOWAIT|RFMEM)) {
399         case -1:
400                 sysfatal("rfork");
401         default:
402                 tunnel2ip(tunnel, v6net);
403                 break;
404         case 0:
405                 ip2tunnel(v6net, tunnel);
406                 break;
407         }
408         exits("tunnel gone");
409 }
410
411 void
412 main(int argc, char **argv)
413 {
414         int tunnel, v6net;
415
416         fmtinstall('I', eipfmt);
417         fmtinstall('V', eipfmt);
418         fmtinstall('M', eipfmt);
419
420         ARGBEGIN {
421         case 'd':
422                 debug++;
423                 break;
424         case 'g':
425                 gateway++;
426                 break;
427         case 'x':
428                 inside = EARGF(usage());
429                 break;
430         case 'k':
431                 secret = EARGF(usage());
432                 break;
433         default:
434                 usage();
435         } ARGEND
436
437         procargs(argc, argv);
438
439         conf.idtype = IdInteger;
440         conf.idlen = sizeof(local6);
441         conf.identity = local6;
442
443         conf.authmeth = AuthNone;
444         conf.hashmeth = HashSHA1;
445         conf.siglen = ayiyahash(conf.hashmeth, nil, 0, nil);
446         conf.signature = nullsig;
447
448         if(secret != nil){
449                 conf.authmeth = AuthSharedKey;
450                 conf.signature = malloc(conf.siglen);
451                 ayiyahash(conf.hashmeth, (uchar*)secret, strlen(secret), conf.signature);
452                 memset(secret, 0, strlen(secret));      /* prevent accidents */
453         }
454
455         tunnel = dial(outside, nil, nil, nil);
456         if (tunnel < 0)
457                 sysfatal("can't dial tunnel: %r");
458
459         setup(&v6net);
460         runtunnel(v6net, tunnel);
461         exits(0);
462 }
463
464 /*
465  * based on libthread's threadsetname, but drags in less library code.
466  * actually just sets the arguments displayed.
467  */
468 void
469 procsetname(char *fmt, ...)
470 {
471         int fd;
472         char *cmdname;
473         char buf[128];
474         va_list arg;
475
476         va_start(arg, fmt);
477         cmdname = vsmprint(fmt, arg);
478         va_end(arg);
479         if (cmdname == nil)
480                 return;
481         snprint(buf, sizeof buf, "#p/%d/args", getpid());
482         if((fd = open(buf, OWRITE)) >= 0){
483                 write(fd, cmdname, strlen(cmdname)+1);
484                 close(fd);
485         }
486         free(cmdname);
487 }
488
489 static int alarmed;
490
491 static void
492 catcher(void*, char *msg)
493 {
494         if(strstr(msg, "alarm") != nil){
495                 alarmed = 1;
496                 noted(NCONT);
497         }
498         noted(NDFLT);
499 }
500
501 /*
502  * encapsulate v6 packets from the packet interface
503  * and send them into the tunnel.
504  */
505 static void
506 ip2tunnel(int in, int out)
507 {
508         uchar buf[AYIYAMAXHDR + IP_MAXPAY], *p;
509         Ip6hdr *ip;
510         AYIYA y[1];
511         int n, m;
512
513         procsetname("v6 %I -> tunnel %s %I", local6, outside, remote6);
514
515         notify(catcher);
516
517         /* get a V6 packet destined for the tunnel */
518         for(;;) {
519                 alarmed = 0;
520                 alarm(60*1000);
521
522                 p = buf + AYIYAMAXHDR;
523                 if ((n = read(in, p, IP_MAXPAY)) <= 0) {
524                         if(!alarmed)
525                                 break;
526
527                         /* send heartbeat */
528                         y->nexthdr = 59;
529                         y->opcode = OpNone;
530                         if(ayiyaout(out, y, p, 0) < 0)
531                                 break;
532
533                         continue;
534                 }
535
536                 ip = (Ip6hdr*)p;
537
538                 /* if not IPV6, drop it */
539                 if ((ip->vcf[0] & 0xF0) != IP_VER6)
540                         continue;
541
542                 /* check length: drop if too short, trim if too long */
543                 m = nhgets(ip->ploadlen) + IPV6HDR_LEN;
544                 if (m > n)
545                         continue;
546                 if (m < n)
547                         n = m;
548
549                 /* drop if v6 source or destination address is naughty */
550                 if (badipv6(ip->src)) {
551                         syslog(0, "ayiya", "egress filtered %I -> %I; bad src",
552                                 ip->src, ip->dst);
553                         continue;
554                 }
555                 if ((!equivip6(ip->dst, remote6) && badipv6(ip->dst))) {
556                         syslog(0, "ayiya", "egress filtered %I -> %I; "
557                                 "bad dst not remote", ip->src, ip->dst);
558                         continue;
559                 }
560
561                 if (debug > 1)
562                         fprint(2, "v6 to tunnel %I -> %I\n", ip->src, ip->dst);
563
564                 /* pass packet to the other end of the tunnel */
565                 y->nexthdr = IP_IPV6PROTO;
566                 y->opcode = OpForward;
567                 if(ayiyaout(out, y, p, n) < 0 && !alarmed)
568                         break;
569         }
570
571         alarm(0);
572 }
573
574 /*
575  * decapsulate v6 packets from the tunnel
576  * and forward them to the packet interface
577  */
578 static void
579 tunnel2ip(int in, int out)
580 {
581         uchar buf[2*AYIYAMAXHDR + IP_MAXPAY + 5], *p;
582         uchar a[IPaddrlen];
583         Ip6hdr *op;
584         AYIYA y[1];
585         int n, m;
586
587         procsetname("tunnel %s %I -> v6 %I", outside, remote6, local6);
588
589         for (;;) {
590                 p = buf + AYIYAMAXHDR;  /* space for reply header */
591
592                 /* get a packet from the tunnel */
593                 if ((n = read(in, p, AYIYAMAXHDR + IP_MAXPAY)) <= 0)
594                         break;
595
596                 /* zero slackspace */
597                 memset(p+n, 0, 5);
598
599                 m = ayiyaunpack(y, p, n);
600                 if (m <= 0 || m > n)
601                         continue;
602
603                 if (debug > 1) {
604                         fprint(2, "recv: ");
605                         ayiyadump(y);
606                 }
607
608                 if (ayiyaverify(y, p, n) != 0) {
609                         fprint(2, "ayiya bad packet signature\n");
610                         continue;
611                 }
612                 n -= m, p += m;
613
614                 switch(y->opcode){
615                 case OpForward:
616                 case OpEchoRequest:
617                 case OpEchoRequestAndForward:
618                         break;
619                 case OpMOTD:
620                         fprint(2, "ayiya motd: %s\n", (char*)p);
621                         continue;
622                 case OpQueryRequest:
623                         if(n < 4)
624                                 continue;
625                         if (ayiyarquery((char*)p + 4) < 0)
626                                 continue;
627                         n = 4 + strlen((char*)p + 4);
628                         y->opcode = OpQueryResponse;
629                         if (ayiyaout(in, y, p, n) < 0)
630                                 return;
631                         continue;
632                 case OpNone:
633                 case OpEchoResponse:
634                 case OpQueryResponse:
635                         continue;
636                 default:
637                         fprint(2, "ayiya unknown opcode: %x\n", y->opcode);
638                         continue;
639                 }
640
641                 switch(y->opcode){
642                 case OpForward:
643                 case OpEchoRequestAndForward:
644                         /* if not IPv6 nor ICMPv6, drop it */
645                         if (y->nexthdr != IP_IPV6PROTO && y->nexthdr != IP_ICMPV6PROTO) {
646                                 syslog(0, "ayiya",
647                                         "dropping pkt from tunnel with inner proto %d",
648                                         y->nexthdr);
649                                 break;
650                         }
651
652                         op = (Ip6hdr*)p;
653                         if(n < IPV6HDR_LEN)
654                                 break;
655
656                         /*
657                          * don't relay: just accept packets for local host/subnet
658                          * (this blocks link-local and multicast addresses as well)
659                          */
660                         maskip(op->dst, localmask, a);
661                         if (!equivip6(a, localnet)) {
662                                 syslog(0, "ayiya", "ingress filtered %I -> %I; "
663                                         "dst not on local net", op->src, op->dst);
664                                 break;
665                         }
666                         if (debug > 1)
667                                 fprint(2, "tunnel to v6 %I -> %I\n", op->src, op->dst);
668
669                         /* pass V6 packet to the interface */
670                         if (write(out, p, n) != n) {
671                                 syslog(0, "ayiya", "error writing to packet interface (%r), giving up");
672                                 return;
673                         }
674                         break;
675                 }
676
677                 switch(y->opcode){
678                 case OpEchoRequest:
679                 case OpEchoRequestAndForward:
680                         y->opcode = OpEchoResponse;
681                         if (ayiyaout(in, y, p, n) < 0)
682                                 return;
683                 }
684         }
685 }
686
687 static int
688 badipv4(uchar *a)
689 {
690         switch (a[0]) {
691         case 0:                         /* unassigned */
692         case 10:                        /* private */
693         case 127:                       /* loopback */
694                 return 1;
695         case 172:
696                 return a[1] >= 16;      /* 172.16.0.0/12 private */
697         case 192:
698                 return a[1] == 168;     /* 192.168.0.0/16 private */
699         case 169:
700                 return a[1] == 254;     /* 169.254.0.0/16 DHCP link-local */
701         }
702         /* 224.0.0.0/4 multicast, 240.0.0.0/4 reserved, broadcast */
703         return a[0] >= 240;
704 }
705
706 /*
707  * 0x0000/16 prefix = v4 compatible, v4 mapped, loopback, unspecified...
708  * site-local is now deprecated, rfc3879
709  */
710 static int
711 badipv6(uchar *a)
712 {
713         int h = a[0]<<8 | a[1];
714
715         return h == 0 || ISIPV6MCAST(a) || ISIPV6LINKLOCAL(a) ||
716             h == V6to4pfx && badipv4(a+2);
717 }