xref: /plan9-contrib/sys/src/cmd/ip/6in4.c (revision b39189fd423aed869c5cf5189bc504918cff969b)
1 /*
2  * 6in4 - tunnel client for automatic 6to4 or configured v6-in-v4 tunnels.
3  *	see rfc3056.
4  */
5 
6 #include <u.h>
7 #include <libc.h>
8 #include <ip.h>
9 
10 enum {
11 	IP_IPV6PROTO	= 41,		/* IPv4 protocol number for IPv6 */
12 	V6to4pfx	= 0x2002,
13 };
14 
15 typedef struct Iphdr Iphdr;
16 struct Iphdr
17 {
18 	uchar	vihl;		/* Version and header length */
19 	uchar	tos;		/* Type of service */
20 	uchar	length[2];	/* packet length */
21 	uchar	id[2];		/* Identification */
22 	uchar	frag[2];	/* Fragment information */
23 	uchar	ttl;		/* Time to live */
24 	uchar	proto;		/* Protocol */
25 	uchar	cksum[2];	/* Header checksum */
26 	uchar	src[4];		/* Ip source (uchar ordering unimportant) */
27 	uchar	dst[4];		/* Ip destination (uchar ordering unimportant) */
28 	uchar	payload[];
29 };
30 
31 #define STFHDR offsetof(Iphdr, payload[0])
32 
33 int anysender;
34 int gateway;
35 int debug;
36 
37 uchar local6[IPaddrlen];
38 uchar remote6[IPaddrlen];
39 uchar remote4[IPaddrlen];
40 uchar localmask[IPaddrlen];
41 uchar localnet[IPaddrlen];
42 uchar myip[IPaddrlen];
43 
44 /* magic anycast address from rfc3068 */
45 uchar anycast6to4[IPv4addrlen] = { 192, 88, 99, 1 };
46 
47 static char *net = "/net";
48 
49 static int	badipv4(uchar*);
50 static int	badipv6(uchar*);
51 static void	ip2tunnel(int, int);
52 static void	tunnel2ip(int, int);
53 
54 static void
55 usage(void)
56 {
57 	fprint(2, "usage: %s [-ag] [-x mtpt] [local6[/mask]] [remote4 [remote6]]\n",
58 		argv0);
59 	exits("Usage");
60 }
61 
62 static char *
63 defv6addr(void)
64 {
65 	uchar *ipv4 = &myip[IPaddrlen - IPv4addrlen];
66 
67 	return smprint("%ux:%2.2x%2.2x:%2.2x%2.2x::1/48", V6to4pfx,
68 		ipv4[0], ipv4[1], ipv4[2], ipv4[3]);
69 }
70 
71 /* process non-option arguments */
72 static void
73 procargs(int argc, char **argv)
74 {
75 	char *p, *loc6;
76 
77 	if (argc < 1)
78 		loc6 = defv6addr();
79 	else if (strcmp(argv[0], "-") == 0) {
80 		loc6 = defv6addr();
81 		argv++;
82 		argc--;
83 	} else {
84 		loc6 = *argv++;
85 		argc--;
86 	}
87 
88 	/* local v6 address (mask defaults to /128) */
89 	memcpy(localmask, IPallbits, sizeof localmask);
90 	p = strchr(loc6, '/');
91 	if (p != nil) {
92 		parseipmask(localmask, p);
93 		*p = 0;
94 	}
95 	if (parseip(local6, loc6) == -1)
96 		sysfatal("bad local v6 address %s", loc6);
97 	if (isv4(local6))
98 		usage();
99 	if (argc >= 1 && argv[0][0] == '/') {
100 		parseipmask(localmask, *argv++);
101 		argc--;
102 	}
103 	if (debug)
104 		fprint(2, "local6 %I %M\n", local6, localmask);
105 
106 	/* remote v4 address (defaults to anycast 6to4) */
107 	if (argc >= 1) {
108 		if (parseip(remote4, *argv++) == -1)
109 			sysfatal("bad remote v4 address %s", argv[-1]);
110 		argc--;
111 		if (!isv4(remote4))
112 			usage();
113 	} else {
114 		v4tov6(remote4, anycast6to4);
115 		anysender++;
116 	}
117 	if (debug)
118 		fprint(2, "remote4 %I\n", remote4);
119 
120 	/* remote v6 address (defaults to link-local w/ v4 as interface part) */
121 	if (argc >= 1) {
122 		if (parseip(remote6, *argv++) == -1)
123 			sysfatal("bad remote v6 address %s", argv[-1]);
124 		argc--;
125 	} else {
126 		remote6[0] = 0xFE;		/* link local */
127 		remote6[1] = 0x80;
128 		memcpy(remote6 + IPv4off, remote4 + IPv4off, IPv4addrlen);
129 	}
130 	USED(argv);
131 	if (argc != 0)
132 		usage();
133 
134 	maskip(local6, localmask, localnet);
135 	if (debug)
136 		fprint(2, "localnet %I remote6 %I\n", localnet, remote6);
137 }
138 
139 static void
140 setup(int *v6net, int *tunp)
141 {
142 	int n, cfd;
143 	char *p, *cl, *ir;
144 	char buf[128], path[64];
145 
146 	/*
147 	 * gain access to IPv6-in-IPv4 packets
148 	 */
149 	p = seprint(buf, buf + sizeof buf, "%s/ipmux!proto=%2.2x;dst=%V",
150 		net, IP_IPV6PROTO, myip + IPv4off);
151 	if (!anysender)
152 		seprint(p, buf + sizeof buf, ";src=%V", remote4 + IPv4off);
153 	*tunp = dial(buf, 0, 0, 0);
154 	if (*tunp < 0)
155 		sysfatal("can't access ipv6-in-ipv4 with dial str %s: %r", buf);
156 	if (debug)
157 		fprint(2, "dialed %s for v6-in-v4 access\n", buf);
158 
159 	/*
160 	 * open local IPv6 interface (as a packet interface)
161 	 */
162 
163 	cl = smprint("%s/ipifc/clone", net);
164 	cfd = open(cl, ORDWR);			/* allocate a conversation */
165 	n = 0;
166 	if (cfd < 0 || (n = read(cfd, buf, sizeof buf - 1)) <= 0)
167 		sysfatal("can't make packet interface %s: %r", cl);
168 	if (debug)
169 		fprint(2, "cloned %s as local v6 interface\n", cl);
170 	free(cl);
171 	buf[n] = 0;
172 
173 	snprint(path, sizeof path, "%s/ipifc/%s/data", net, buf);
174 	*v6net = open(path, ORDWR);
175 	if (*v6net < 0 || fprint(cfd, "bind pkt") < 0)
176 		sysfatal("can't bind packet interface: %r");
177 	/* 1280 is MTU, apparently from rfc2460 */
178 	if (fprint(cfd, "add %I /128 %I 1280", local6, remote6) <= 0)
179 		sysfatal("can't set local ipv6 address: %r");
180 	close(cfd);
181 	if (debug)
182 		fprint(2, "opened & bound %s as local v6 interface\n", path);
183 
184 	if (gateway) {
185 		/* route global addresses through the tunnel to remote6 */
186 		ir = smprint("%s/iproute", net);
187 		cfd = open(ir, OWRITE);
188 		if (cfd >= 0 && debug)
189 			fprint(2, "injected 2000::/3 %I into %s\n", remote6, ir);
190 		free(ir);
191 		if (cfd < 0 || fprint(cfd, "add 2000:: /3 %I", remote6) <= 0)
192 			sysfatal("can't set default global route: %r");
193 	}
194 }
195 
196 static void
197 runtunnel(int v6net, int tunnel)
198 {
199 	/* run the tunnel copying in the background */
200 	switch (rfork(RFPROC|RFNOWAIT|RFMEM|RFNOTEG)) {
201 	case -1:
202 		sysfatal("rfork");
203 	default:
204 		exits(nil);
205 	case 0:
206 		break;
207 	}
208 
209 	switch (rfork(RFPROC|RFNOWAIT|RFMEM)) {
210 	case -1:
211 		sysfatal("rfork");
212 	default:
213 		tunnel2ip(tunnel, v6net);
214 		break;
215 	case 0:
216 		ip2tunnel(v6net, tunnel);
217 		break;
218 	}
219 	exits("tunnel gone");
220 }
221 
222 void
223 main(int argc, char **argv)
224 {
225 	int tunnel, v6net;
226 
227 	fmtinstall('I', eipfmt);
228 	fmtinstall('V', eipfmt);
229 	fmtinstall('M', eipfmt);
230 
231 	ARGBEGIN {
232 	case 'a':
233 		anysender++;
234 		break;
235 	case 'd':
236 		debug++;
237 		break;
238 	case 'g':
239 		gateway++;
240 		break;
241 	case 'x':
242 		net = EARGF(usage());
243 		break;
244 	default:
245 		usage();
246 	} ARGEND
247 
248 	if (myipaddr(myip, net) < 0)
249 		sysfatal("can't find my ipv4 address on %s", net);
250 	if (!isv4(myip))
251 		sysfatal("my ip, %I, is not a v4 address", myip);
252 
253 	procargs(argc, argv);
254 	setup(&v6net, &tunnel);
255 	runtunnel(v6net, tunnel);
256 }
257 
258 /*
259  * based on libthread's threadsetname, but drags in less library code.
260  * actually just sets the arguments displayed.
261  */
262 void
263 procsetname(char *fmt, ...)
264 {
265 	int fd;
266 	char *cmdname;
267 	char buf[128];
268 	va_list arg;
269 
270 	va_start(arg, fmt);
271 	cmdname = vsmprint(fmt, arg);
272 	va_end(arg);
273 	if (cmdname == nil)
274 		return;
275 	snprint(buf, sizeof buf, "#p/%d/args", getpid());
276 	if((fd = open(buf, OWRITE)) >= 0){
277 		write(fd, cmdname, strlen(cmdname)+1);
278 		close(fd);
279 	}
280 	free(cmdname);
281 }
282 
283 /*
284  * encapsulate v6 packets from the packet interface in v4 ones
285  * and send them into the tunnel.
286  */
287 static void
288 ip2tunnel(int in, int out)
289 {
290 	int n, m;
291 	char buf[64*1024];
292 	Iphdr *op;
293 	Ip6hdr *ip;
294 
295 	if (anysender)
296 		procsetname("v6 %I -> tunnel", local6);
297 	else
298 		procsetname("v6 %I -> tunnel %I %I", local6, remote4, remote6);
299 
300 	/* populate v4 header */
301 	op = (Iphdr*)buf;
302 	op->vihl = IP_VER4 | 5;		/* hdr is 5 longs? */
303 	memcpy(op->src, myip + IPv4off, sizeof op->src);
304 	op->proto = IP_IPV6PROTO;
305 	op->ttl = 100;
306 
307 	/* get a V6 packet destined for the tunnel */
308 	while ((n = read(in, buf + STFHDR, sizeof buf - STFHDR)) > 0) {
309 		/* if not IPV6, drop it */
310 		ip = (Ip6hdr*)(buf + STFHDR);
311 		if ((ip->vcf[0] & 0xF0) != IP_VER6)
312 			continue;
313 
314 		/* check length: drop if too short, trim if too long */
315 		m = nhgets(ip->ploadlen) + IPV6HDR_LEN;
316 		if (m > n)
317 			continue;
318 		if (m < n)
319 			n = m;
320 
321 		/* drop if v6 source or destination address is naughty */
322 		if (badipv6(ip->src) ||
323 		    (!equivip6(ip->dst, remote6) && badipv6(ip->dst))) {
324 			syslog(0, "6in4", "egress filtered %I -> %I",
325 				ip->src, ip->dst);
326 			continue;
327 		}
328 
329 		if (debug > 1)
330 			fprint(2, "v6 to tunnel %I -> %I\n", ip->src, ip->dst);
331 		/* send 6to4 packets directly to ipv4 target */
332 		if ((ip->dst[0]<<8 | ip->dst[1]) == V6to4pfx)
333 			memcpy(op->dst, ip->dst+2, sizeof op->dst);
334 		else
335 			memcpy(op->dst, remote4+IPv4off, sizeof op->dst);
336 
337 		n += STFHDR;
338 		/* pass packet to the other end of the tunnel */
339 		if (write(out, op, n) != n) {
340 			syslog(0, "6in4", "error writing to tunnel (%r), giving up");
341 			break;
342 		}
343 	}
344 }
345 
346 /*
347  * decapsulate v6 packets from v4 ones from the tunnel
348  * and forward them to the packet interface
349  */
350 static void
351 tunnel2ip(int in, int out)
352 {
353 	int n, m;
354 	char buf[64*1024];
355 	uchar a[IPaddrlen];
356 	Ip6hdr *op;
357 	Iphdr *ip;
358 
359 	if (anysender)
360 		procsetname("tunnel -> v6 %I", local6);
361 	else
362 		procsetname("tunnel %I %I -> v6 %I", remote4, remote6, local6);
363 
364 	for (;;) {
365 		/* get a packet from the tunnel */
366 		n = read(in, buf, sizeof buf);
367 		ip = (Iphdr*)(buf + IPaddrlen);
368 		n -= IPaddrlen;
369 		if (n <= 0) {
370 			syslog(0, "6in4", "error reading from tunnel (%r), giving up");
371 			break;
372 		}
373 
374 		/* if not IPv4 nor IPv4 protocol IPv6, drop it */
375 		if ((ip->vihl & 0xF0) != IP_VER4 || ip->proto != IP_IPV6PROTO)
376 			continue;
377 
378 		/* check length: drop if too short, trim if too long */
379 		m = nhgets(ip->length);
380 		if (m > n)
381 			continue;
382 		if (m < n)
383 			n = m;
384 
385 		op = (Ip6hdr*)(buf + IPaddrlen + STFHDR);
386 		n -= STFHDR;
387 
388 		/*
389 		 * don't relay: just accept packets for local host/subnet
390 		 * (this blocks link-local and multicast addresses as well)
391 		 */
392 		maskip(op->dst, localmask, a);
393 		if (!equivip6(a, localnet)) {
394 			syslog(0, "6in4", "ingress filtered %I -> %I",
395 				op->src, op->dst);
396 			continue;
397 		}
398 		if (debug > 1)
399 			fprint(2, "tunnel to v6 %I -> %I\n", op->src, op->dst);
400 
401 		/* pass V6 packet to the interface */
402 		if (write(out, op, n) != n) {
403 			syslog(0, "6in4", "error writing to packet interface (%r), giving up");
404 			break;
405 		}
406 	}
407 }
408 
409 static int
410 badipv4(uchar *a)
411 {
412 	switch (a[0]) {
413 	case 0:				/* unassigned */
414 	case 10:			/* private */
415 	case 127:			/* loopback */
416 		return 1;
417 	case 172:
418 		return a[1] >= 16;	/* 172.16.0.0/12 private */
419 	case 192:
420 		return a[1] == 168;	/* 192.168.0.0/16 private */
421 	case 169:
422 		return a[1] == 254;	/* 169.254.0.0/16 DHCP link-local */
423 	}
424 	/* 224.0.0.0/4 multicast, 240.0.0.0/4 reserved, broadcast */
425 	return a[0] >= 240;
426 }
427 
428 /*
429  * 0x0000/16 prefix = v4 compatible, v4 mapped, loopback, unspecified...
430  * site-local is now deprecated, rfc3879
431  */
432 static int
433 badipv6(uchar *a)
434 {
435 	int h = a[0]<<8 | a[1];
436 
437 	return h == 0 || ISIPV6MCAST(a) || ISIPV6LINKLOCAL(a) ||
438 	    h == V6to4pfx && badipv4(a+2);
439 }
440