475 lines · 11.1 KB · C
RawDownload
/*  * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * 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 }; static char *net = "/net"; static int	badipv4(uchar*);static int	badipv6(uchar*);static void	ip2tunnel(int, int);static void	tunnel2ip(int, int); static voidusage(void){	fprint(2, "usage: %s [-ag] [-x mtpt] [local6[/mask]] [remote4 [remote6]]\n",		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 voidprocargs(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 voidsetup(int *v6net, int *tunp){	int n, cfd;	char *p, *cl, *ir;	char buf[128], path[64]; 	/*	 * gain access to IPv6-in-IPv4 packets via ipmux	 */	p = seprint(buf, buf + sizeof buf, "%s/ipmux!proto=%2.2x|%2.2x;dst=%V",		net, IP_IPV6PROTO, IP_ICMPV6PROTO, myip + IPv4off);	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)	 */ 	cl = smprint("%s/ipifc/clone", net);	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", net, 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 /128 %I 1280", local6, 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", net);		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 voidruntunnel(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");} voidmain(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':		net = EARGF(usage());		break;	default:		usage();	} ARGEND 	if (myipaddr(myip, net) < 0)		sysfatal("can't find my ipv4 address on %s", net);	if (!isv4(myip))		sysfatal("my ip, %I, is not a v4 address", myip); 	procargs(argc, argv);	setup(&v6net, &tunnel);	runtunnel(v6net, tunnel);	exits(0);} /* * based on libthread's threadsetname, but drags in less library code. * actually just sets the arguments displayed. */voidprocsetname(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 voidip2tunnel(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);	op->proto = IP_IPV6PROTO;	/* inner protocol */	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 */		if (badipv6(ip->src)) {			syslog(0, "6in4", "egress filtered %I -> %I; bad src",				ip->src, ip->dst);			continue;		}		if ((!equivip6(ip->dst, remote6) && badipv6(ip->dst))) {			syslog(0, "6in4", "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); 		/* 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 voidtunnel2ip(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 ||		    ip->proto != IP_IPV6PROTO && ip->proto != IP_ICMPV6PROTO) {			syslog(0, "6in4",				"dropping pkt from tunnel with inner proto %d",				ip->proto);			continue;		} 		/* 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)) {			syslog(0, "6in4", "ingress filtered %I -> %I; "				"dst not on local net", op->src, op->dst);			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 intbadipv4(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 intbadipv6(uchar *a){	int h = a[0]<<8 | a[1]; 	return h == 0 || ISIPV6MCAST(a) || ISIPV6LINKLOCAL(a) ||	    h == V6to4pfx && badipv4(a+2);}