2011-03-30 12:46:40 +00:00
|
|
|
/*
|
|
|
|
* 6in4 - tunnel client for automatic 6to4 or configured v6-in-v4 tunnels.
|
|
|
|
* see rfc3056.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <u.h>
|
|
|
|
#include <libc.h>
|
|
|
|
#include <ip.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,
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct Iphdr Iphdr;
|
|
|
|
struct Iphdr
|
|
|
|
{
|
|
|
|
uchar vihl; /* Version and header length */
|
|
|
|
uchar tos; /* Type of service */
|
|
|
|
uchar length[2]; /* packet length */
|
|
|
|
uchar id[2]; /* Identification */
|
|
|
|
uchar frag[2]; /* Fragment information */
|
|
|
|
uchar ttl; /* Time to live */
|
|
|
|
uchar proto; /* Protocol */
|
|
|
|
uchar cksum[2]; /* Header checksum */
|
|
|
|
uchar src[4]; /* Ip source (uchar ordering unimportant) */
|
|
|
|
uchar dst[4]; /* Ip destination (uchar ordering unimportant) */
|
|
|
|
uchar payload[];
|
|
|
|
};
|
|
|
|
|
|
|
|
#define STFHDR offsetof(Iphdr, payload[0])
|
|
|
|
|
|
|
|
int anysender;
|
|
|
|
int gateway;
|
|
|
|
int debug;
|
|
|
|
|
|
|
|
uchar local6[IPaddrlen];
|
|
|
|
uchar remote6[IPaddrlen];
|
|
|
|
uchar remote4[IPaddrlen];
|
|
|
|
uchar localmask[IPaddrlen];
|
|
|
|
uchar localnet[IPaddrlen];
|
|
|
|
uchar myip[IPaddrlen];
|
|
|
|
|
|
|
|
/* magic anycast address from rfc3068 */
|
|
|
|
uchar anycast6to4[IPv4addrlen] = { 192, 88, 99, 1 };
|
|
|
|
|
2013-01-14 06:09:25 +00:00
|
|
|
static char *inside = "/net";
|
|
|
|
static char *outside = "/net";
|
2011-03-30 12:46:40 +00:00
|
|
|
|
|
|
|
static int badipv4(uchar*);
|
|
|
|
static int badipv6(uchar*);
|
|
|
|
static void ip2tunnel(int, int);
|
|
|
|
static void tunnel2ip(int, int);
|
|
|
|
|
|
|
|
static void
|
|
|
|
usage(void)
|
|
|
|
{
|
2013-01-14 06:09:25 +00:00
|
|
|
fprint(2, "usage: %s [-ag] [-x mtpt] [-o mtpt] [local6[/mask]] [remote4 [remote6]]\n",
|
2011-03-30 12:46:40 +00:00
|
|
|
argv0);
|
|
|
|
exits("Usage");
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *
|
|
|
|
defv6addr(void)
|
|
|
|
{
|
|
|
|
uchar *ipv4 = &myip[IPaddrlen - IPv4addrlen];
|
|
|
|
|
|
|
|
return smprint("%ux:%2.2x%2.2x:%2.2x%2.2x::1/48", V6to4pfx,
|
|
|
|
ipv4[0], ipv4[1], ipv4[2], ipv4[3]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* process non-option arguments */
|
|
|
|
static void
|
|
|
|
procargs(int argc, char **argv)
|
|
|
|
{
|
|
|
|
char *p, *loc6;
|
|
|
|
|
|
|
|
if (argc < 1)
|
|
|
|
loc6 = defv6addr();
|
|
|
|
else if (strcmp(argv[0], "-") == 0) {
|
|
|
|
loc6 = defv6addr();
|
|
|
|
argv++;
|
|
|
|
argc--;
|
|
|
|
} else {
|
|
|
|
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);
|
|
|
|
|
|
|
|
/* remote v4 address (defaults to anycast 6to4) */
|
|
|
|
if (argc >= 1) {
|
|
|
|
if (parseip(remote4, *argv++) == -1)
|
|
|
|
sysfatal("bad remote v4 address %s", argv[-1]);
|
|
|
|
argc--;
|
|
|
|
if (!isv4(remote4))
|
|
|
|
usage();
|
|
|
|
} else {
|
|
|
|
v4tov6(remote4, anycast6to4);
|
|
|
|
anysender++;
|
|
|
|
}
|
|
|
|
if (debug)
|
|
|
|
fprint(2, "remote4 %I\n", remote4);
|
|
|
|
|
|
|
|
/* remote v6 address (defaults to link-local w/ v4 as interface part) */
|
|
|
|
if (argc >= 1) {
|
|
|
|
if (parseip(remote6, *argv++) == -1)
|
|
|
|
sysfatal("bad remote v6 address %s", argv[-1]);
|
|
|
|
argc--;
|
|
|
|
} else {
|
|
|
|
remote6[0] = 0xFE; /* link local */
|
|
|
|
remote6[1] = 0x80;
|
|
|
|
memcpy(remote6 + IPv4off, remote4 + IPv4off, IPv4addrlen);
|
|
|
|
}
|
|
|
|
USED(argv);
|
|
|
|
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 *tunp)
|
|
|
|
{
|
|
|
|
int n, cfd;
|
|
|
|
char *p, *cl, *ir;
|
|
|
|
char buf[128], path[64];
|
|
|
|
|
|
|
|
/*
|
2012-10-11 11:21:30 +00:00
|
|
|
* gain access to IPv6-in-IPv4 packets via ipmux
|
2011-03-30 12:46:40 +00:00
|
|
|
*/
|
|
|
|
p = seprint(buf, buf + sizeof buf, "%s/ipmux!proto=%2.2x|%2.2x;dst=%V",
|
2013-01-14 06:09:25 +00:00
|
|
|
outside, IP_IPV6PROTO, IP_ICMPV6PROTO, myip + IPv4off);
|
2011-03-30 12:46:40 +00:00
|
|
|
if (!anysender)
|
|
|
|
seprint(p, buf + sizeof buf, ";src=%V", remote4 + IPv4off);
|
|
|
|
*tunp = dial(buf, 0, 0, 0);
|
|
|
|
if (*tunp < 0)
|
|
|
|
sysfatal("can't access ipv6-in-ipv4 with dial str %s: %r", buf);
|
|
|
|
if (debug)
|
|
|
|
fprint(2, "dialed %s for v6-in-v4 access\n", buf);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* open local IPv6 interface (as a packet interface)
|
|
|
|
*/
|
|
|
|
|
2013-01-14 06:09:25 +00:00
|
|
|
cl = smprint("%s/ipifc/clone", inside);
|
2011-03-30 12:46:40 +00:00
|
|
|
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;
|
|
|
|
|
2013-01-14 06:09:25 +00:00
|
|
|
snprint(path, sizeof path, "%s/ipifc/%s/data", inside, buf);
|
2011-03-30 12:46:40 +00:00
|
|
|
*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 */
|
2012-12-21 18:18:47 +00:00
|
|
|
if (fprint(cfd, "add %I %M %I 1280", local6, localmask, remote6) <= 0)
|
2011-03-30 12:46:40 +00:00
|
|
|
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 */
|
2013-01-14 06:09:25 +00:00
|
|
|
ir = smprint("%s/iproute", inside);
|
2011-03-30 12:46:40 +00:00
|
|
|
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 'a':
|
|
|
|
anysender++;
|
|
|
|
break;
|
|
|
|
case 'd':
|
|
|
|
debug++;
|
|
|
|
break;
|
|
|
|
case 'g':
|
|
|
|
gateway++;
|
|
|
|
break;
|
|
|
|
case 'x':
|
2013-01-14 06:09:25 +00:00
|
|
|
outside = inside = EARGF(usage());
|
|
|
|
break;
|
|
|
|
case 'o':
|
|
|
|
outside = EARGF(usage());
|
2011-03-30 12:46:40 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
usage();
|
|
|
|
} ARGEND
|
|
|
|
|
2013-01-14 06:09:25 +00:00
|
|
|
if (myipaddr(myip, outside) < 0)
|
|
|
|
sysfatal("can't find my ipv4 address on %s", outside);
|
2011-03-30 12:46:40 +00:00
|
|
|
if (!isv4(myip))
|
|
|
|
sysfatal("my ip, %I, is not a v4 address", myip);
|
|
|
|
|
|
|
|
procargs(argc, argv);
|
|
|
|
setup(&v6net, &tunnel);
|
|
|
|
runtunnel(v6net, tunnel);
|
2012-10-11 11:21:30 +00:00
|
|
|
exits(0);
|
2011-03-30 12:46:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* encapsulate v6 packets from the packet interface in v4 ones
|
|
|
|
* and send them into the tunnel.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ip2tunnel(int in, int out)
|
|
|
|
{
|
|
|
|
int n, m;
|
|
|
|
char buf[64*1024];
|
|
|
|
Iphdr *op;
|
|
|
|
Ip6hdr *ip;
|
|
|
|
|
|
|
|
if (anysender)
|
|
|
|
procsetname("v6 %I -> tunnel", local6);
|
|
|
|
else
|
|
|
|
procsetname("v6 %I -> tunnel %I %I", local6, remote4, remote6);
|
|
|
|
|
|
|
|
/* populate v4 header */
|
|
|
|
op = (Iphdr*)buf;
|
|
|
|
op->vihl = IP_VER4 | 5; /* hdr is 5 longs? */
|
|
|
|
memcpy(op->src, myip + IPv4off, sizeof op->src);
|
2012-10-11 11:21:30 +00:00
|
|
|
op->proto = IP_IPV6PROTO; /* inner protocol */
|
2011-03-30 12:46:40 +00:00
|
|
|
op->ttl = 100;
|
|
|
|
|
|
|
|
/* get a V6 packet destined for the tunnel */
|
|
|
|
ip = (Ip6hdr*)(buf + STFHDR);
|
|
|
|
while ((n = read(in, ip, sizeof buf - STFHDR)) > 0) {
|
|
|
|
/* 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 */
|
2012-10-11 11:21:30 +00:00
|
|
|
if (badipv6(ip->src)) {
|
|
|
|
syslog(0, "6in4", "egress filtered %I -> %I; bad src",
|
2011-03-30 12:46:40 +00:00
|
|
|
ip->src, ip->dst);
|
|
|
|
continue;
|
|
|
|
}
|
2012-10-11 11:21:30 +00:00
|
|
|
if ((!equivip6(ip->dst, remote6) && badipv6(ip->dst))) {
|
|
|
|
syslog(0, "6in4", "egress filtered %I -> %I; "
|
|
|
|
"bad dst not remote", ip->src, ip->dst);
|
|
|
|
continue;
|
|
|
|
}
|
2011-03-30 12:46:40 +00:00
|
|
|
|
|
|
|
if (debug > 1)
|
|
|
|
fprint(2, "v6 to tunnel %I -> %I\n", ip->src, ip->dst);
|
2012-10-11 11:21:30 +00:00
|
|
|
|
2011-03-30 12:46:40 +00:00
|
|
|
/* send 6to4 packets directly to ipv4 target */
|
|
|
|
if ((ip->dst[0]<<8 | ip->dst[1]) == V6to4pfx)
|
|
|
|
memcpy(op->dst, ip->dst+2, sizeof op->dst);
|
|
|
|
else
|
|
|
|
memcpy(op->dst, remote4+IPv4off, sizeof op->dst);
|
|
|
|
|
|
|
|
n += STFHDR;
|
|
|
|
/* pass packet to the other end of the tunnel */
|
|
|
|
if (write(out, op, n) != n) {
|
|
|
|
syslog(0, "6in4", "error writing to tunnel (%r), giving up");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* decapsulate v6 packets from v4 ones from the tunnel
|
|
|
|
* and forward them to the packet interface
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
tunnel2ip(int in, int out)
|
|
|
|
{
|
|
|
|
int n, m;
|
|
|
|
char buf[64*1024];
|
|
|
|
uchar a[IPaddrlen];
|
|
|
|
Ip6hdr *op;
|
|
|
|
Iphdr *ip;
|
|
|
|
|
|
|
|
if (anysender)
|
|
|
|
procsetname("tunnel -> v6 %I", local6);
|
|
|
|
else
|
|
|
|
procsetname("tunnel %I %I -> v6 %I", remote4, remote6, local6);
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
/* get a packet from the tunnel */
|
|
|
|
n = read(in, buf, sizeof buf);
|
|
|
|
ip = (Iphdr*)(buf + IPaddrlen);
|
|
|
|
n -= IPaddrlen;
|
|
|
|
if (n <= 0) {
|
|
|
|
syslog(0, "6in4", "error reading from tunnel (%r), giving up");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if not IPv4 nor IPv4 protocol IPv6 nor ICMPv6, drop it */
|
|
|
|
if ((ip->vihl & 0xF0) != IP_VER4 ||
|
2012-10-11 11:21:30 +00:00
|
|
|
ip->proto != IP_IPV6PROTO && ip->proto != IP_ICMPV6PROTO) {
|
|
|
|
syslog(0, "6in4",
|
|
|
|
"dropping pkt from tunnel with inner proto %d",
|
|
|
|
ip->proto);
|
2011-03-30 12:46:40 +00:00
|
|
|
continue;
|
2012-10-11 11:21:30 +00:00
|
|
|
}
|
2011-03-30 12:46:40 +00:00
|
|
|
|
|
|
|
/* check length: drop if too short, trim if too long */
|
|
|
|
m = nhgets(ip->length);
|
|
|
|
if (m > n)
|
|
|
|
continue;
|
|
|
|
if (m < n)
|
|
|
|
n = m;
|
|
|
|
|
|
|
|
op = (Ip6hdr*)(buf + IPaddrlen + STFHDR);
|
|
|
|
n -= STFHDR;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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)) {
|
2012-10-11 11:21:30 +00:00
|
|
|
syslog(0, "6in4", "ingress filtered %I -> %I; "
|
|
|
|
"dst not on local net", op->src, op->dst);
|
2011-03-30 12:46:40 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (debug > 1)
|
|
|
|
fprint(2, "tunnel to v6 %I -> %I\n", op->src, op->dst);
|
|
|
|
|
|
|
|
/* pass V6 packet to the interface */
|
|
|
|
if (write(out, op, n) != n) {
|
|
|
|
syslog(0, "6in4", "error writing to packet interface (%r), giving up");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|