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.
This commit is contained in:
cinap_lenrek 2014-09-06 22:59:58 +02:00
parent 460f482ad0
commit a49ddece8b
2 changed files with 718 additions and 0 deletions

717
sys/src/cmd/ip/ayiya.c Normal file
View file

@ -0,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);
}

View file

@ -1,6 +1,7 @@
</$objtype/mkfile
TARG = 6in4\
ayiya\
dhcpclient\
ftpd\
gping\