]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/ayiya.c
ip/ipconfig: add missing {} as DEBUG() is a macro
[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 *p, *loc6;
298
299         if (argc < 3)
300                 usage();
301
302         loc6 = *argv++;
303         argc--;
304
305         /* local v6 address (mask defaults to /128) */
306         memcpy(localmask, IPallbits, sizeof localmask);
307         p = strchr(loc6, '/');
308         if (p != nil) {
309                 parseipmask(localmask, p);
310                 *p = 0;
311         }
312         if (parseip(local6, loc6) == -1)
313                 sysfatal("bad local v6 address %s", loc6);
314         if (isv4(local6))
315                 usage();
316         if (argc >= 1 && argv[0][0] == '/') {
317                 parseipmask(localmask, *argv++);
318                 argc--;
319         }
320         if (debug)
321                 fprint(2, "local6 %I %M\n", local6, localmask);
322
323         outside = netmkaddr(*argv++, "udp", "5072");
324         argc--;
325         if(outside == nil)
326                 usage();
327         outside = strdup(outside);
328         if (debug)
329                 fprint(2, "outside %s\n", outside);
330
331         /* remote v6 address */
332         if (parseip(remote6, *argv++) == -1)
333                 sysfatal("bad remote v6 address %s", argv[-1]);
334         argc--;
335         if (argc != 0)
336                 usage();
337
338         maskip(local6, localmask, localnet);
339         if (debug)
340                 fprint(2, "localnet %I remote6 %I\n", localnet, remote6);
341 }
342
343 static void
344 setup(int *v6net)
345 {
346         int n, cfd;
347         char *cl, *ir;
348         char buf[128], path[64];
349
350         /*
351          * open local IPv6 interface (as a packet interface)
352          */
353
354         cl = smprint("%s/ipifc/clone", inside);
355         cfd = open(cl, ORDWR);                  /* allocate a conversation */
356         n = 0;
357         if (cfd < 0 || (n = read(cfd, buf, sizeof buf - 1)) <= 0)
358                 sysfatal("can't make packet interface %s: %r", cl);
359         if (debug)
360                 fprint(2, "cloned %s as local v6 interface\n", cl);
361         free(cl);
362         buf[n] = 0;
363
364         snprint(path, sizeof path, "%s/ipifc/%s/data", inside, buf);
365         *v6net = open(path, ORDWR);
366         if (*v6net < 0 || fprint(cfd, "bind pkt") < 0)
367                 sysfatal("can't bind packet interface: %r");
368         if (fprint(cfd, "add %I %M %I %d", local6, localmask, remote6,
369                 mtu - (IPV4HDR_LEN+8) - (8+conf.idlen+conf.siglen)) <= 0)
370                 sysfatal("can't set local ipv6 address: %r");
371         close(cfd);
372         if (debug)
373                 fprint(2, "opened & bound %s as local v6 interface\n", path);
374
375         if (gateway) {
376                 /* route global addresses through the tunnel to remote6 */
377                 ir = smprint("%s/iproute", inside);
378                 cfd = open(ir, OWRITE);
379                 if (cfd >= 0 && debug)
380                         fprint(2, "injected 2000::/3 %I into %s\n", remote6, ir);
381                 free(ir);
382                 if (cfd < 0 || fprint(cfd, "add 2000:: /3 %I", remote6) <= 0)
383                         sysfatal("can't set default global route: %r");
384         }
385 }
386
387 static void
388 runtunnel(int v6net, int tunnel)
389 {
390         /* run the tunnel copying in the background */
391         switch (rfork(RFPROC|RFNOWAIT|RFMEM|RFNOTEG)) {
392         case -1:
393                 sysfatal("rfork");
394         default:
395                 exits(nil);
396         case 0:
397                 break;
398         }
399
400         switch (rfork(RFPROC|RFNOWAIT|RFMEM)) {
401         case -1:
402                 sysfatal("rfork");
403         default:
404                 tunnel2ip(tunnel, v6net);
405                 break;
406         case 0:
407                 ip2tunnel(v6net, tunnel);
408                 break;
409         }
410         exits("tunnel gone");
411 }
412
413 void
414 main(int argc, char **argv)
415 {
416         int tunnel, v6net;
417
418         fmtinstall('I', eipfmt);
419         fmtinstall('V', eipfmt);
420         fmtinstall('M', eipfmt);
421
422         ARGBEGIN {
423         case 'd':
424                 debug++;
425                 break;
426         case 'g':
427                 gateway++;
428                 break;
429         case 'm':
430                 mtu = atoi(EARGF(usage()));
431                 break;
432         case 'x':
433                 inside = EARGF(usage());
434                 break;
435         case 'k':
436                 secret = EARGF(usage());
437                 break;
438         default:
439                 usage();
440         } ARGEND
441
442         procargs(argc, argv);
443
444         conf.idtype = IdInteger;
445         conf.idlen = sizeof(local6);
446         conf.identity = local6;
447
448         conf.authmeth = AuthNone;
449         conf.hashmeth = HashSHA1;
450         conf.siglen = ayiyahash(conf.hashmeth, nil, 0, nil);
451         conf.signature = nullsig;
452
453         if(secret != nil){
454                 conf.authmeth = AuthSharedKey;
455                 conf.signature = malloc(conf.siglen);
456                 ayiyahash(conf.hashmeth, (uchar*)secret, strlen(secret), conf.signature);
457                 memset(secret, 0, strlen(secret));      /* prevent accidents */
458         }
459
460         tunnel = dial(outside, nil, nil, nil);
461         if (tunnel < 0)
462                 sysfatal("can't dial tunnel: %r");
463
464         setup(&v6net);
465         runtunnel(v6net, tunnel);
466         exits(0);
467 }
468
469 /*
470  * based on libthread's threadsetname, but drags in less library code.
471  * actually just sets the arguments displayed.
472  */
473 void
474 procsetname(char *fmt, ...)
475 {
476         int fd;
477         char *cmdname;
478         char buf[128];
479         va_list arg;
480
481         va_start(arg, fmt);
482         cmdname = vsmprint(fmt, arg);
483         va_end(arg);
484         if (cmdname == nil)
485                 return;
486         snprint(buf, sizeof buf, "#p/%d/args", getpid());
487         if((fd = open(buf, OWRITE)) >= 0){
488                 write(fd, cmdname, strlen(cmdname)+1);
489                 close(fd);
490         }
491         free(cmdname);
492 }
493
494 static int alarmed;
495
496 static void
497 catcher(void*, char *msg)
498 {
499         if(strstr(msg, "alarm") != nil){
500                 alarmed = 1;
501                 noted(NCONT);
502         }
503         noted(NDFLT);
504 }
505
506 /*
507  * encapsulate v6 packets from the packet interface
508  * and send them into the tunnel.
509  */
510 static void
511 ip2tunnel(int in, int out)
512 {
513         uchar buf[AYIYAMAXHDR + IP_MAXPAY], *p;
514         Ip6hdr *ip;
515         AYIYA y[1];
516         int n, m;
517
518         procsetname("v6 %I -> tunnel %s %I", local6, outside, remote6);
519
520         notify(catcher);
521
522         /* get a V6 packet destined for the tunnel */
523         for(;;) {
524                 alarmed = 0;
525                 alarm(60*1000);
526
527                 p = buf + AYIYAMAXHDR;
528                 if ((n = read(in, p, IP_MAXPAY)) <= 0) {
529                         if(!alarmed)
530                                 break;
531
532                         /* send heartbeat */
533                         y->nexthdr = 59;
534                         y->opcode = OpNone;
535                         if(ayiyaout(out, y, p, 0) < 0)
536                                 break;
537
538                         continue;
539                 }
540
541                 ip = (Ip6hdr*)p;
542
543                 /* if not IPV6, drop it */
544                 if ((ip->vcf[0] & 0xF0) != IP_VER6)
545                         continue;
546
547                 /* check length: drop if too short, trim if too long */
548                 m = nhgets(ip->ploadlen) + IPV6HDR_LEN;
549                 if (m > n)
550                         continue;
551                 if (m < n)
552                         n = m;
553
554                 /* drop if v6 source or destination address is naughty */
555                 if (badipv6(ip->src)) {
556                         syslog(0, "ayiya", "egress filtered %I -> %I; bad src",
557                                 ip->src, ip->dst);
558                         continue;
559                 }
560                 if ((!equivip6(ip->dst, remote6) && badipv6(ip->dst))) {
561                         syslog(0, "ayiya", "egress filtered %I -> %I; "
562                                 "bad dst not remote", ip->src, ip->dst);
563                         continue;
564                 }
565
566                 if (debug > 1)
567                         fprint(2, "v6 to tunnel %I -> %I\n", ip->src, ip->dst);
568
569                 /* pass packet to the other end of the tunnel */
570                 y->nexthdr = IP_IPV6PROTO;
571                 y->opcode = OpForward;
572                 if(ayiyaout(out, y, p, n) < 0 && !alarmed)
573                         break;
574         }
575
576         alarm(0);
577 }
578
579 /*
580  * decapsulate v6 packets from the tunnel
581  * and forward them to the packet interface
582  */
583 static void
584 tunnel2ip(int in, int out)
585 {
586         uchar buf[2*AYIYAMAXHDR + IP_MAXPAY + 5], *p;
587         uchar a[IPaddrlen];
588         Ip6hdr *op;
589         AYIYA y[1];
590         int n, m;
591
592         procsetname("tunnel %s %I -> v6 %I", outside, remote6, local6);
593
594         for (;;) {
595                 p = buf + AYIYAMAXHDR;  /* space for reply header */
596
597                 /* get a packet from the tunnel */
598                 if ((n = read(in, p, AYIYAMAXHDR + IP_MAXPAY)) <= 0)
599                         break;
600
601                 /* zero slackspace */
602                 memset(p+n, 0, 5);
603
604                 m = ayiyaunpack(y, p, n);
605                 if (m <= 0 || m > n)
606                         continue;
607
608                 if (debug > 1) {
609                         fprint(2, "recv: ");
610                         ayiyadump(y);
611                 }
612
613                 if (ayiyaverify(y, p, n) != 0) {
614                         fprint(2, "ayiya bad packet signature\n");
615                         continue;
616                 }
617                 n -= m, p += m;
618
619                 switch(y->opcode){
620                 case OpForward:
621                 case OpEchoRequest:
622                 case OpEchoRequestAndForward:
623                         break;
624                 case OpMOTD:
625                         fprint(2, "ayiya motd: %s\n", (char*)p);
626                         continue;
627                 case OpQueryRequest:
628                         if(n < 4)
629                                 continue;
630                         if (ayiyarquery((char*)p + 4) < 0)
631                                 continue;
632                         n = 4 + strlen((char*)p + 4);
633                         y->opcode = OpQueryResponse;
634                         if (ayiyaout(in, y, p, n) < 0)
635                                 return;
636                         continue;
637                 case OpNone:
638                 case OpEchoResponse:
639                 case OpQueryResponse:
640                         continue;
641                 default:
642                         fprint(2, "ayiya unknown opcode: %x\n", y->opcode);
643                         continue;
644                 }
645
646                 switch(y->opcode){
647                 case OpForward:
648                 case OpEchoRequestAndForward:
649                         /* if not IPv6 nor ICMPv6, drop it */
650                         if (y->nexthdr != IP_IPV6PROTO && y->nexthdr != IP_ICMPV6PROTO) {
651                                 syslog(0, "ayiya",
652                                         "dropping pkt from tunnel with inner proto %d",
653                                         y->nexthdr);
654                                 break;
655                         }
656
657                         op = (Ip6hdr*)p;
658                         if(n < IPV6HDR_LEN)
659                                 break;
660
661                         /*
662                          * don't relay: just accept packets for local host/subnet
663                          * (this blocks link-local and multicast addresses as well)
664                          */
665                         maskip(op->dst, localmask, a);
666                         if (!equivip6(a, localnet)) {
667                                 syslog(0, "ayiya", "ingress filtered %I -> %I; "
668                                         "dst not on local net", op->src, op->dst);
669                                 break;
670                         }
671                         if (debug > 1)
672                                 fprint(2, "tunnel to v6 %I -> %I\n", op->src, op->dst);
673
674                         /* pass V6 packet to the interface */
675                         if (write(out, p, n) != n) {
676                                 syslog(0, "ayiya", "error writing to packet interface (%r), giving up");
677                                 return;
678                         }
679                         break;
680                 }
681
682                 switch(y->opcode){
683                 case OpEchoRequest:
684                 case OpEchoRequestAndForward:
685                         y->opcode = OpEchoResponse;
686                         if (ayiyaout(in, y, p, n) < 0)
687                                 return;
688                 }
689         }
690 }
691
692 static int
693 badipv4(uchar *a)
694 {
695         switch (a[0]) {
696         case 0:                         /* unassigned */
697         case 10:                        /* private */
698         case 127:                       /* loopback */
699                 return 1;
700         case 172:
701                 return a[1] >= 16;      /* 172.16.0.0/12 private */
702         case 192:
703                 return a[1] == 168;     /* 192.168.0.0/16 private */
704         case 169:
705                 return a[1] == 254;     /* 169.254.0.0/16 DHCP link-local */
706         }
707         /* 224.0.0.0/4 multicast, 240.0.0.0/4 reserved, broadcast */
708         return a[0] >= 240;
709 }
710
711 /*
712  * 0x0000/16 prefix = v4 compatible, v4 mapped, loopback, unspecified...
713  * site-local is now deprecated, rfc3879
714  */
715 static int
716 badipv6(uchar *a)
717 {
718         int h = a[0]<<8 | a[1];
719
720         return h == 0 || ISIPV6MCAST(a) || ISIPV6LINKLOCAL(a) ||
721             h == V6to4pfx && badipv4(a+2);
722 }