ref: a49ddece8bf3ce16e9049d0cbc5c890c0461f342
parent: 460f482ad05aeb12a7ed3bb92adeb03d8c6e4c0a
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sat Sep 6 18:59:58 EDT 2014
ip/ayiya: experimental anything in anything tunnel protocol client this is a work in progress implementation of the ayiya (anything in anything) protocol as used by sixxs.net. hiro tested it and it worked for him, but progress has stalled as sixxs.net rejected my request for an account and ignored my emails since.
--- /dev/null
+++ b/sys/src/cmd/ip/ayiya.c
@@ -1,0 +1,717 @@
+/*
+ * ayiya - tunnel client.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <mp.h>
+#include <libsec.h>
+
+/*
+ * IPv6 and related IP protocols & their numbers:
+ *
+ * ipv6 41 IPv6 # Internet Protocol, version 6
+ * ipv6-route 43 IPv6-Route # Routing Header for IPv6
+ * ipv6-frag 44 IPv6-Frag # Fragment Header for IPv6
+ * esp 50 ESP # Encapsulating Security Payload
+ * ah 51 AH # Authentication Header
+ * ipv6-icmp 58 IPv6-ICMP icmp6 # ICMP version 6
+ * ipv6-nonxt 59 IPv6-NoNxt # No Next Header for IPv6
+ * ipv6-opts 60 IPv6-Opts # Destination Options for IPv6
+ */
+enum {
+ IP_IPV6PROTO = 41, /* IPv4 protocol number for IPv6 */
+ IP_ESPPROTO = 50, /* IP v4 and v6 protocol number */
+ IP_AHPROTO = 51, /* IP v4 and v6 protocol number */
+ IP_ICMPV6PROTO = 58,
+ V6to4pfx = 0x2002,
+
+ IP_MAXPAY = 2*1024,
+};
+
+enum {
+ AYIYAMAXID = 1<<15,
+ AYIYAMAXSIG = 15*4,
+
+ AYIYAMAXHDR = 8+AYIYAMAXID+AYIYAMAXSIG,
+
+ IdNone = 0,
+ IdInteger,
+ IdString,
+
+ HashNone = 0,
+ HashMD5,
+ HashSHA1,
+
+ AuthNone = 0,
+ AuthSharedKey,
+ AuthPubKey,
+
+ OpNone = 0,
+ OpForward,
+ OpEchoRequest,
+ OpEchoRequestAndForward,
+ OpEchoResponse,
+ OpMOTD,
+ OpQueryRequest,
+ OpQueryResponse,
+};
+
+typedef struct AYIYA AYIYA;
+struct AYIYA
+{
+ uint idlen;
+ uint idtype;
+ uint siglen;
+ uint hashmeth;
+ uint authmeth;
+ uint opcode;
+ uint nexthdr;
+ uint epochtime;
+
+ uchar *identity;
+ uchar *signature;
+};
+
+AYIYA conf;
+
+int gateway;
+int debug;
+
+uchar local6[IPaddrlen];
+uchar remote6[IPaddrlen];
+uchar localmask[IPaddrlen];
+uchar localnet[IPaddrlen];
+
+uchar nullsig[AYIYAMAXSIG];
+
+static char *secret = nil;
+
+static char *outside = nil; /* dial string of tunnel server */
+static char *inside = "/net";
+
+static int badipv4(uchar*);
+static int badipv6(uchar*);
+static void ip2tunnel(int, int);
+static void tunnel2ip(int, int);
+
+static void
+ayiyadump(AYIYA *a)
+{
+ int i;
+
+ fprint(2, "idlen=%ud idtype=%ux siglen=%ud hashmeth=%ud authmeth=%ud opcode=%ux nexthdr=%ux epochtime=%ux\n",
+ a->idlen, a->idtype, a->siglen, a->hashmeth, a->authmeth, a->opcode, a->nexthdr, a->epochtime);
+ fprint(2, "identity=[ ");
+ for(i=0; i<a->idlen; i++)
+ fprint(2, "%.2ux ", a->identity[i]);
+ fprint(2, "] ");
+ fprint(2, "signature=[ ");
+ for(i=0; i<a->siglen; i++)
+ fprint(2, "%.2ux ", a->signature[i]);
+ fprint(2, "]\n");
+
+}
+
+static uint
+lg2(uint a)
+{
+ uint n;
+
+ for(n = 0; (a >>= 1) != 0; n++)
+ ;
+ return n;
+}
+
+static int
+ayiyapack(AYIYA *a, uchar *pay, int paylen)
+{
+ uchar *pkt;
+
+ pkt = pay;
+ if(a->siglen > 0){
+ pkt -= a->siglen;
+ memmove(pkt, a->signature, a->siglen);
+ }
+ if(a->idlen > 0){
+ pkt -= a->idlen;
+ memmove(pkt, a->identity, a->idlen);
+ }
+
+ pkt -= 4;
+ pkt[0] = a->epochtime>>24;
+ pkt[1] = a->epochtime>>16;
+ pkt[2] = a->epochtime>>8;
+ pkt[3] = a->epochtime;
+
+ pkt -= 4;
+ pkt[0] = (lg2(a->idlen)<<4) | a->idtype;
+ pkt[1] = ((a->siglen/4)<<4) | a->hashmeth;
+ pkt[2] = (a->authmeth<<4) | a->opcode;
+ pkt[3] = a->nexthdr;
+
+ USED(paylen);
+
+ return pay - pkt;
+}
+
+static int
+ayiyaunpack(AYIYA *a, uchar *pkt, int pktlen)
+{
+ int hdrlen;
+
+ if(pktlen < 8)
+ return -1;
+
+ a->idlen = 1<<(pkt[0] >> 4);
+ a->idtype = pkt[0] & 15;
+ a->siglen = (pkt[1] >> 4) * 4;
+ a->hashmeth = pkt[1] & 15;
+ a->authmeth = pkt[2] >> 4;
+ a->opcode = pkt[2] & 15;
+ a->nexthdr = pkt[3];
+ a->epochtime = pkt[7] | pkt[6]<<8 | pkt[5]<<16 | pkt[4]<<24;
+
+ hdrlen = 8 + a->idlen + a->siglen;
+ if(hdrlen > pktlen)
+ return -1;
+
+ a->identity = nil;
+ if(a->idlen > 0)
+ a->identity = pkt + 8;
+
+ a->signature = nil;
+ if(a->siglen > 0)
+ a->signature = pkt + 8 + a->idlen;
+
+ return hdrlen;
+}
+
+static int
+ayiyahash(uint meth, uchar *pkt, int pktlen, uchar *dig)
+{
+ switch(meth){
+ case HashMD5:
+ if(dig != nil)
+ md5(pkt, pktlen, dig, nil);
+ return MD5dlen;
+ case HashSHA1:
+ if(dig != nil)
+ sha1(pkt, pktlen, dig, nil);
+ return SHA1dlen;
+ }
+ return 0;
+}
+
+static void
+ayiyasign(AYIYA *a, uchar *pkt, int pktlen)
+{
+ uchar dig[AYIYAMAXSIG], *pktsig;
+
+ if(a->hashmeth == HashNone && a->siglen == 0)
+ return;
+
+ assert(a->siglen <= sizeof(dig));
+ assert(a->siglen <= pktlen - a->idlen - 8);
+ pktsig = pkt + 8 + a->idlen;
+
+ if(ayiyahash(a->hashmeth, pkt, pktlen, dig) != a->siglen){
+ memset(pktsig, 0, a->siglen);
+ return;
+ }
+
+ memmove(pktsig, dig, a->siglen);
+}
+
+static int
+ayiyaverify(AYIYA *a, uchar *pkt, int pktlen)
+{
+ uchar dig[AYIYAMAXSIG], sig[AYIYAMAXSIG];
+
+ if(conf.hashmeth == HashNone && a->siglen == 0)
+ return 0;
+ if(a->hashmeth != conf.hashmeth || a->authmeth != conf.authmeth || a->siglen != conf.siglen)
+ return -1;
+ memmove(sig, a->signature, a->siglen);
+ memmove(a->signature, conf.signature, a->siglen);
+ if(ayiyahash(a->hashmeth, pkt, pktlen, dig) != a->siglen)
+ return -1;
+ return memcmp(sig, dig, a->siglen) != 0;
+}
+
+static int
+ayiyaout(int fd, AYIYA *a, uchar *p, int n)
+{
+ int m;
+
+ a->idlen = conf.idlen;
+ a->siglen = conf.siglen;
+ a->idtype = conf.idtype;
+ a->hashmeth = conf.hashmeth;
+ a->authmeth = conf.authmeth;
+ a->identity = conf.identity;
+ a->signature = conf.signature;
+
+ a->epochtime = time(nil);
+
+ if (debug > 1) {
+ fprint(2, "send: ");
+ ayiyadump(a);
+ }
+
+ m = ayiyapack(a, p, n);
+ n += m, p -= m;
+
+ ayiyasign(a, p, n);
+
+ if (write(fd, p, n) != n) {
+ syslog(0, "ayiya", "error writing to tunnel (%r), giving up");
+ return -1;
+ }
+ return 0;
+}
+
+static int
+ayiyarquery(char *q)
+{
+ fprint(2, "ayiyarquery: %s\n", q);
+ *q = '\0';
+ return 0;
+}
+
+static void
+usage(void)
+{
+ fprint(2, "usage: %s [-g] [-x mtpt] [-k secret] local6[/mask] remote4 remote6\n",
+ argv0);
+ exits("Usage");
+}
+
+/* process non-option arguments */
+static void
+procargs(int argc, char **argv)
+{
+ char *p, *loc6;
+
+ if (argc < 3)
+ usage();
+
+ loc6 = *argv++;
+ argc--;
+
+ /* local v6 address (mask defaults to /128) */
+ memcpy(localmask, IPallbits, sizeof localmask);
+ p = strchr(loc6, '/');
+ if (p != nil) {
+ parseipmask(localmask, p);
+ *p = 0;
+ }
+ if (parseip(local6, loc6) == -1)
+ sysfatal("bad local v6 address %s", loc6);
+ if (isv4(local6))
+ usage();
+ if (argc >= 1 && argv[0][0] == '/') {
+ parseipmask(localmask, *argv++);
+ argc--;
+ }
+ if (debug)
+ fprint(2, "local6 %I %M\n", local6, localmask);
+
+ outside = netmkaddr(*argv++, "udp", "5072");
+ argc--;
+ if(outside == nil)
+ usage();
+ outside = strdup(outside);
+ if (debug)
+ fprint(2, "outside %s\n", outside);
+
+ /* remote v6 address */
+ if (parseip(remote6, *argv++) == -1)
+ sysfatal("bad remote v6 address %s", argv[-1]);
+ argc--;
+ if (argc != 0)
+ usage();
+
+ maskip(local6, localmask, localnet);
+ if (debug)
+ fprint(2, "localnet %I remote6 %I\n", localnet, remote6);
+}
+
+static void
+setup(int *v6net)
+{
+ int n, cfd;
+ char *cl, *ir;
+ char buf[128], path[64];
+
+ /*
+ * open local IPv6 interface (as a packet interface)
+ */
+
+ cl = smprint("%s/ipifc/clone", inside);
+ cfd = open(cl, ORDWR); /* allocate a conversation */
+ n = 0;
+ if (cfd < 0 || (n = read(cfd, buf, sizeof buf - 1)) <= 0)
+ sysfatal("can't make packet interface %s: %r", cl);
+ if (debug)
+ fprint(2, "cloned %s as local v6 interface\n", cl);
+ free(cl);
+ buf[n] = 0;
+
+ snprint(path, sizeof path, "%s/ipifc/%s/data", inside, buf);
+ *v6net = open(path, ORDWR);
+ if (*v6net < 0 || fprint(cfd, "bind pkt") < 0)
+ sysfatal("can't bind packet interface: %r");
+ /* 1280 is MTU, apparently from rfc2460 */
+ if (fprint(cfd, "add %I %M %I 1280", local6, localmask, remote6) <= 0)
+ sysfatal("can't set local ipv6 address: %r");
+ close(cfd);
+ if (debug)
+ fprint(2, "opened & bound %s as local v6 interface\n", path);
+
+ if (gateway) {
+ /* route global addresses through the tunnel to remote6 */
+ ir = smprint("%s/iproute", inside);
+ cfd = open(ir, OWRITE);
+ if (cfd >= 0 && debug)
+ fprint(2, "injected 2000::/3 %I into %s\n", remote6, ir);
+ free(ir);
+ if (cfd < 0 || fprint(cfd, "add 2000:: /3 %I", remote6) <= 0)
+ sysfatal("can't set default global route: %r");
+ }
+}
+
+static void
+runtunnel(int v6net, int tunnel)
+{
+ /* run the tunnel copying in the background */
+ switch (rfork(RFPROC|RFNOWAIT|RFMEM|RFNOTEG)) {
+ case -1:
+ sysfatal("rfork");
+ default:
+ exits(nil);
+ case 0:
+ break;
+ }
+
+ switch (rfork(RFPROC|RFNOWAIT|RFMEM)) {
+ case -1:
+ sysfatal("rfork");
+ default:
+ tunnel2ip(tunnel, v6net);
+ break;
+ case 0:
+ ip2tunnel(v6net, tunnel);
+ break;
+ }
+ exits("tunnel gone");
+}
+
+void
+main(int argc, char **argv)
+{
+ int tunnel, v6net;
+
+ fmtinstall('I', eipfmt);
+ fmtinstall('V', eipfmt);
+ fmtinstall('M', eipfmt);
+
+ ARGBEGIN {
+ case 'd':
+ debug++;
+ break;
+ case 'g':
+ gateway++;
+ break;
+ case 'x':
+ inside = EARGF(usage());
+ break;
+ case 'k':
+ secret = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND
+
+ procargs(argc, argv);
+
+ conf.idtype = IdInteger;
+ conf.idlen = sizeof(local6);
+ conf.identity = local6;
+
+ conf.authmeth = AuthNone;
+ conf.hashmeth = HashSHA1;
+ conf.siglen = ayiyahash(conf.hashmeth, nil, 0, nil);
+ conf.signature = nullsig;
+
+ if(secret != nil){
+ conf.authmeth = AuthSharedKey;
+ conf.signature = malloc(conf.siglen);
+ ayiyahash(conf.hashmeth, (uchar*)secret, strlen(secret), conf.signature);
+ memset(secret, 0, strlen(secret)); /* prevent accidents */
+ }
+
+ tunnel = dial(outside, nil, nil, nil);
+ if (tunnel < 0)
+ sysfatal("can't dial tunnel: %r");
+
+ setup(&v6net);
+ runtunnel(v6net, tunnel);
+ exits(0);
+}
+
+/*
+ * based on libthread's threadsetname, but drags in less library code.
+ * actually just sets the arguments displayed.
+ */
+void
+procsetname(char *fmt, ...)
+{
+ int fd;
+ char *cmdname;
+ char buf[128];
+ va_list arg;
+
+ va_start(arg, fmt);
+ cmdname = vsmprint(fmt, arg);
+ va_end(arg);
+ if (cmdname == nil)
+ return;
+ snprint(buf, sizeof buf, "#p/%d/args", getpid());
+ if((fd = open(buf, OWRITE)) >= 0){
+ write(fd, cmdname, strlen(cmdname)+1);
+ close(fd);
+ }
+ free(cmdname);
+}
+
+static int alarmed;
+
+static void
+catcher(void*, char *msg)
+{
+ if(strstr(msg, "alarm") != nil){
+ alarmed = 1;
+ noted(NCONT);
+ }
+ noted(NDFLT);
+}
+
+/*
+ * encapsulate v6 packets from the packet interface
+ * and send them into the tunnel.
+ */
+static void
+ip2tunnel(int in, int out)
+{
+ uchar buf[AYIYAMAXHDR + IP_MAXPAY], *p;
+ Ip6hdr *ip;
+ AYIYA y[1];
+ int n, m;
+
+ procsetname("v6 %I -> tunnel %s %I", local6, outside, remote6);
+
+ notify(catcher);
+
+ /* get a V6 packet destined for the tunnel */
+ for(;;) {
+ alarmed = 0;
+ alarm(60*1000);
+
+ p = buf + AYIYAMAXHDR;
+ if ((n = read(in, p, IP_MAXPAY)) <= 0) {
+ if(!alarmed)
+ break;
+
+ /* send heartbeat */
+ y->nexthdr = 59;
+ y->opcode = OpNone;
+ if(ayiyaout(out, y, p, 0) < 0)
+ break;
+
+ continue;
+ }
+
+ ip = (Ip6hdr*)p;
+
+ /* if not IPV6, drop it */
+ if ((ip->vcf[0] & 0xF0) != IP_VER6)
+ continue;
+
+ /* check length: drop if too short, trim if too long */
+ m = nhgets(ip->ploadlen) + IPV6HDR_LEN;
+ if (m > n)
+ continue;
+ if (m < n)
+ n = m;
+
+ /* drop if v6 source or destination address is naughty */
+ if (badipv6(ip->src)) {
+ syslog(0, "ayiya", "egress filtered %I -> %I; bad src",
+ ip->src, ip->dst);
+ continue;
+ }
+ if ((!equivip6(ip->dst, remote6) && badipv6(ip->dst))) {
+ syslog(0, "ayiya", "egress filtered %I -> %I; "
+ "bad dst not remote", ip->src, ip->dst);
+ continue;
+ }
+
+ if (debug > 1)
+ fprint(2, "v6 to tunnel %I -> %I\n", ip->src, ip->dst);
+
+ /* pass packet to the other end of the tunnel */
+ y->nexthdr = IP_IPV6PROTO;
+ y->opcode = OpForward;
+ if(ayiyaout(out, y, p, n) < 0 && !alarmed)
+ break;
+ }
+
+ alarm(0);
+}
+
+/*
+ * decapsulate v6 packets from the tunnel
+ * and forward them to the packet interface
+ */
+static void
+tunnel2ip(int in, int out)
+{
+ uchar buf[2*AYIYAMAXHDR + IP_MAXPAY + 5], *p;
+ uchar a[IPaddrlen];
+ Ip6hdr *op;
+ AYIYA y[1];
+ int n, m;
+
+ procsetname("tunnel %s %I -> v6 %I", outside, remote6, local6);
+
+ for (;;) {
+ p = buf + AYIYAMAXHDR; /* space for reply header */
+
+ /* get a packet from the tunnel */
+ if ((n = read(in, p, AYIYAMAXHDR + IP_MAXPAY)) <= 0)
+ break;
+
+ /* zero slackspace */
+ memset(p+n, 0, 5);
+
+ m = ayiyaunpack(y, p, n);
+ if (m <= 0 || m > n)
+ continue;
+
+ if (debug > 1) {
+ fprint(2, "recv: ");
+ ayiyadump(y);
+ }
+
+ if (ayiyaverify(y, p, n) != 0) {
+ fprint(2, "ayiya bad packet signature\n");
+ continue;
+ }
+ n -= m, p += m;
+
+ switch(y->opcode){
+ case OpForward:
+ case OpEchoRequest:
+ case OpEchoRequestAndForward:
+ break;
+ case OpMOTD:
+ fprint(2, "ayiya motd: %s\n", (char*)p);
+ continue;
+ case OpQueryRequest:
+ if(n < 4)
+ continue;
+ if (ayiyarquery((char*)p + 4) < 0)
+ continue;
+ n = 4 + strlen((char*)p + 4);
+ y->opcode = OpQueryResponse;
+ if (ayiyaout(in, y, p, n) < 0)
+ return;
+ continue;
+ case OpNone:
+ case OpEchoResponse:
+ case OpQueryResponse:
+ continue;
+ default:
+ fprint(2, "ayiya unknown opcode: %x\n", y->opcode);
+ continue;
+ }
+
+ switch(y->opcode){
+ case OpForward:
+ case OpEchoRequestAndForward:
+ /* if not IPv6 nor ICMPv6, drop it */
+ if (y->nexthdr != IP_IPV6PROTO && y->nexthdr != IP_ICMPV6PROTO) {
+ syslog(0, "ayiya",
+ "dropping pkt from tunnel with inner proto %d",
+ y->nexthdr);
+ break;
+ }
+
+ op = (Ip6hdr*)p;
+ if(n < IPV6HDR_LEN)
+ break;
+
+ /*
+ * don't relay: just accept packets for local host/subnet
+ * (this blocks link-local and multicast addresses as well)
+ */
+ maskip(op->dst, localmask, a);
+ if (!equivip6(a, localnet)) {
+ syslog(0, "ayiya", "ingress filtered %I -> %I; "
+ "dst not on local net", op->src, op->dst);
+ break;
+ }
+ if (debug > 1)
+ fprint(2, "tunnel to v6 %I -> %I\n", op->src, op->dst);
+
+ /* pass V6 packet to the interface */
+ if (write(out, p, n) != n) {
+ syslog(0, "ayiya", "error writing to packet interface (%r), giving up");
+ return;
+ }
+ break;
+ }
+
+ switch(y->opcode){
+ case OpEchoRequest:
+ case OpEchoRequestAndForward:
+ y->opcode = OpEchoResponse;
+ if (ayiyaout(in, y, p, n) < 0)
+ return;
+ }
+ }
+}
+
+static int
+badipv4(uchar *a)
+{
+ switch (a[0]) {
+ case 0: /* unassigned */
+ case 10: /* private */
+ case 127: /* loopback */
+ return 1;
+ case 172:
+ return a[1] >= 16; /* 172.16.0.0/12 private */
+ case 192:
+ return a[1] == 168; /* 192.168.0.0/16 private */
+ case 169:
+ return a[1] == 254; /* 169.254.0.0/16 DHCP link-local */
+ }
+ /* 224.0.0.0/4 multicast, 240.0.0.0/4 reserved, broadcast */
+ return a[0] >= 240;
+}
+
+/*
+ * 0x0000/16 prefix = v4 compatible, v4 mapped, loopback, unspecified...
+ * site-local is now deprecated, rfc3879
+ */
+static int
+badipv6(uchar *a)
+{
+ int h = a[0]<<8 | a[1];
+
+ return h == 0 || ISIPV6MCAST(a) || ISIPV6LINKLOCAL(a) ||
+ h == V6to4pfx && badipv4(a+2);
+}
--- a/sys/src/cmd/ip/mkfile
+++ b/sys/src/cmd/ip/mkfile
@@ -1,6 +1,7 @@
</$objtype/mkfile
TARG = 6in4\
+ ayiya\
dhcpclient\
ftpd\
gping\