ref: 1c08e72835dbf2e8a485f65e23d6dfa560e3b9ed
dir: /sys/src/cmd/ip/ayiya.c/
/*
* 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 mtu = 1500-8;
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] [-m mtu] [-x mtpt] [-k secret] local6[/mask] remote4 remote6\n",
argv0);
exits("Usage");
}
/* process non-option arguments */
static void
procargs(int argc, char **argv)
{
char *ipstr, *maskstr;
if (argc < 3)
usage();
ipstr = *argv++, argc--;
maskstr = strchr(ipstr, '/');
if (maskstr == nil && **argv == '/')
maskstr = *argv++, argc--;
if (parseipandmask(local6, localmask, ipstr, maskstr) == -1 || isv4(local6))
sysfatal("bad local v6 address/mask: %s", ipstr);
if (debug)
fprint(2, "local6 %I %M\n", local6, localmask);
argc--;
outside = netmkaddr(*argv++, "udp", "5072");
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");
if (fprint(cfd, "add %I %M %I %d", local6, localmask, remote6,
mtu - (IPV4HDR_LEN+8) - (8+conf.idlen+conf.siglen)) <= 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 'm':
mtu = atoi(EARGF(usage()));
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);
}
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 ((ipcmp(ip->dst, remote6) != 0 && 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 (ipcmp(a, localnet) != 0) {
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);
}