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