summaryrefslogtreecommitdiff
path: root/usr/src/cmd/cmd-inet/usr.sbin/6to4relay.c
blob: f0547145b2be38481db8f9b0ee2c6b2810f347da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2002 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/socket.h>
#include <sys/stream.h>
#include <sys/param.h>

#include <net/route.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <inet/tun.h>

#include <locale.h>

#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <stropts.h>
#include <fcntl.h>

/*
 * Converts an IPv4 address to a 6to4 /64 route.  Address is of the form
 * 2002:<V4ADDR>:<SUBNETID>::/64 where SUBNETID will always be 0 and V4ADDR
 * equals the input IPv4 address.  IN6_V4ADDR_TO_6TO4(v4, v6) creates an
 * address of form 2002:<V4ADDR>:<SUBNETID>::<HOSTID>, where SUBNETID equals 0
 * and HOSTID equals 1.  For this route, we are not concerned about the
 * HOSTID portion of the address, thus it can be set to 0.
 *
 *  void V4ADDR_TO_6TO4_RT(const struct in_addr *v4, in6_addr_t *v6)
 */
#define	V4ADDR_TO_6TO4_RT(v4, v6) \
	(IN6_V4ADDR_TO_6TO4(v4, v6), (v6)->_S6_un._S6_u32[3] = 0)

static void strioctl(int, void *, size_t);
static void getkstatus(ipaddr_t *);
static void printkstatus(void);
static void modifyroute(unsigned int, in6_addr_t *);
static void setkrraddr(ipaddr_t);
static void printerror(char *);
static void usage(void);

/* booleans corresponding to command line flags */
static boolean_t eflag = B_FALSE;
static boolean_t dflag = B_FALSE;
static boolean_t aflag = B_FALSE;

static int fd = -1;

/*
 * srtioctl(cmd, buf, size)
 *
 * Passes the contents of 'buf' using the ioctl specified by 'cmd', by way of
 * the I_STR ioctl mechanism.  The response of the ioctl will be stored in buf
 * when this function returns.  The input 'size' specifies the size of the
 * buffer to be passed.
 */
static void
strioctl(int cmd, void *buf, size_t size)
{
	struct strioctl ioc;

	(void) memset(&ioc, 0, sizeof (ioc));

	ioc.ic_cmd = cmd;
	ioc.ic_timout = 0;
	ioc.ic_len = size;
	ioc.ic_dp = (char *)buf;

	if (ioctl(fd, I_STR, &ioc) < 0) {
		printerror("ioctl (I_STR)");
		(void) close(fd);
		exit(EXIT_FAILURE);
		/* NOTREACHED */
	}
}


/*
 * getkstatus(out_addr)
 *
 * Queries the kernel for the 6to4 Relay Router destination address by sending
 * the SIOCG6TO4TUNRRADDR ioctl to the tunnel module using the I_STR ioctl
 * mechanism.  The value returned, through the ioctl, will be an ipaddr_t
 * embedded in a strioctl.  Output parameter is set with result.
 */
static void
getkstatus(ipaddr_t *out_addr)
{
	ipaddr_t an_addr;

	/* Get the Relay Router address from the kernel */
	strioctl(SIOCG6TO4TUNRRADDR, &an_addr, sizeof (an_addr));

	*out_addr = an_addr;	/* set output parameter */
}


/*
 * printkstatus()
 *
 * Queries the kernel for the current 6to4 Relay Router value, prints
 * a status message based on the value and exits this command.
 * INADDR_ANY is used to denote that Relay Router communication support is
 * disabled within the kernel.
 */
static void
printkstatus(void)
{
	ipaddr_t rr_addr;
	char buf[INET6_ADDRSTRLEN];

	getkstatus(&rr_addr);	/* get value from kernel */
	(void) printf("6to4relay: ");
	if (rr_addr == INADDR_ANY) {
		(void) printf(gettext("6to4 Relay Router communication "
		    "support is disabled.\n"));
	} else {
		(void) printf(gettext("6to4 Relay Router communication "
		    "support is enabled.\n"));
		(void) printf(gettext("IPv4 destination address of Relay "
		    "Router = "));
		(void) printf("%s\n",
		    inet_ntop(AF_INET, &rr_addr, buf, sizeof (buf)));
	}
}

/*
 * modifyroute(cmd, in_gw)
 *
 * Modifies a default IPv6 route with DST = ::, GATEWAY = in_gw, NETMASK = ::
 * and flags = <GATEWAY, STATIC>.
 * This route is to be propagated through the 6to4 site so that 6to4 hosts
 * can send packets to native IPv6 hosts behind a remote 6to4 Relay Router.
 */
static void
modifyroute(unsigned int cmd, in6_addr_t *in_gw)
{
	static int rtmseq;
	int rtsock;
	int rlen;

	static struct {
		struct rt_msghdr	rt_hdr;
		struct sockaddr_in6	rt_dst;
		struct sockaddr_in6	rt_gate;
		struct sockaddr_in6	rt_mask;
	} rt_msg;

	/* Open a routing socket for passing route commands */
	if ((rtsock = socket(AF_ROUTE, SOCK_RAW, AF_INET)) < 0) {
		printerror("socket");
		(void) close(fd);
		exit(EXIT_FAILURE);
		/* NOTREACHED */
	}

	(void) memset(&rt_msg, 0, sizeof (rt_msg));
	rt_msg.rt_hdr.rtm_msglen = sizeof (rt_msg);
	rt_msg.rt_hdr.rtm_version = RTM_VERSION;
	rt_msg.rt_hdr.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;
	rt_msg.rt_hdr.rtm_pid = getpid();
	rt_msg.rt_hdr.rtm_type = cmd;
	rt_msg.rt_hdr.rtm_seq = ++rtmseq;
	rt_msg.rt_hdr.rtm_flags = RTF_STATIC | RTF_GATEWAY;

	/* DST */
	rt_msg.rt_dst.sin6_family = AF_INET6;
	(void) memset(&rt_msg.rt_dst.sin6_addr.s6_addr, 0,
	    sizeof (in6_addr_t));

	/* GATEWAY */
	rt_msg.rt_gate.sin6_family = AF_INET6;
	bcopy(in_gw->s6_addr, &rt_msg.rt_gate.sin6_addr.s6_addr,
	    sizeof (in6_addr_t));

	/* NETMASK */
	rt_msg.rt_mask.sin6_family = AF_INET6;
	(void) memset(&rt_msg.rt_mask.sin6_addr.s6_addr, 0,
	    sizeof (in6_addr_t));

	/* Send the routing message */
	rlen = write(rtsock, &rt_msg, rt_msg.rt_hdr.rtm_msglen);
	if (rlen < rt_msg.rt_hdr.rtm_msglen) {
		if (rlen < 0) {
			(void) fprintf(stderr,
			    gettext("6to4relay: write to routing socket: %s\n"),
			    strerror(errno));
		} else {
			(void) fprintf(stderr, gettext("6to4relay: write to "
			    "routing socket got only %d for rlen\n"), rlen);
		}
	}
	(void) close(rtsock);
}

/*
 * setkrraddr(in_addr)
 *
 * Sets the 6to4 Relay Router destination address value in the kernel using
 * the SIOCS6TO4TUNRRADDR ioctl using the I_STR ioctl mechanism.
 * The address is sent to the kernel, as an ipaddr_t, embedded in an strioctl.
 */
static void
setkrraddr(ipaddr_t in_addr)
{
	/* set Relay Router address */
	strioctl(SIOCS6TO4TUNRRADDR, &in_addr, sizeof (in_addr));
}

static void
printerror(char *s)
{
	int sverrno = errno;

	(void) fprintf(stderr, "6to4relay: ");
	if (s != NULL)
		(void) fprintf(stderr, "%s: ", s);
	(void) fprintf(stderr, "%s\n", strerror(sverrno));
}

static void
usage(void)
{
	(void) fprintf(stderr,
	    gettext("usage:\n"
		"\t6to4relay\n"
		"\t6to4relay -e [-a <addr>]\n"
		"\t6to4relay -d\n"
		"\t6to4relay -h\n"));
}

int
main(int argc, char **argv)
{
	int ch;
	char *in_addr = NULL;
	int ret = EXIT_SUCCESS;

	(void) setlocale(LC_ALL, "");

#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	/* open /dev/ip for use */
	if ((fd = open("/dev/ip", O_RDWR)) == -1) {
		printerror(gettext("can't open /dev/ip"));
		exit(EXIT_FAILURE);
	}

	if (ioctl(fd, I_PUSH, TUN_NAME) < 0) {
		printerror("ioctl (I_PUSH)");
		ret = EXIT_FAILURE;
		goto done;
	}

	/* If no args are specified, print status as queried from kernel */
	if (argc < 2) {
		printkstatus();
		goto done;
	}
	while ((ch = getopt(argc, argv, "ea:dh")) != EOF) {
		switch (ch) {
		case 'e':
			eflag = B_TRUE;
			break;
		case 'd':
			dflag = B_TRUE;
			break;
		case 'a':
			aflag = B_TRUE;
			in_addr = optarg;
			break;
		case 'h':
			usage();
			goto done;
		default:
			usage();
			ret = EXIT_FAILURE;
			goto done;
		}
	}
	/*
	 * If -a is specified, -e must also be specified.  Also, the
	 * combination of -e and -d is illegal.  Fail on either case.
	 */
	if ((aflag && !eflag) || (eflag && dflag)) {
		usage();
		ret = EXIT_FAILURE;
		goto done;
	}

	/*
	 * Enable Relay Router communication support in the kernel.
	 */
	if (eflag) {
		struct in_addr current_addr; /* addr currently set in kernel */
		struct in_addr new_addr; /* new addr we plan to set */
		in6_addr_t v6_rt;

		/*
		 * if -a was not specified, the well-known anycast will
		 * be used.
		 */
		if (!aflag) {
			new_addr.s_addr = htonl(INADDR_6TO4RRANYCAST);

		} else if (inet_pton(AF_INET, in_addr, &new_addr) <= 0) {
			(void) fprintf(stderr, gettext("6to4relay: input "
			    "address (%s) is not a valid IPv4 dotted-decimal "
			    "string.\n"), in_addr);
			ret = EXIT_FAILURE;
			goto done;
		}

		/*
		 * INADDR_ANY has special meaning in the kernel, reject this
		 * input and exit.
		 */
		if (new_addr.s_addr == INADDR_ANY) {
			(void) fprintf(stderr, gettext("6to4relay: input "
			    "(0.0.0.0) is not a valid IPv4 unicast "
			    "address.\n"));
			ret = EXIT_FAILURE;
			goto done;
		}

		/*
		 * get the current Relay Router address from the kernel.
		 *
		 * 1. If the current address is INADDR_ANY, set the new
		 *    address in the kernel and add a default IPv6 route using
		 *    the new address.
		 *
		 * 2. If the current address is different than the new address,
		 *    set the new address in the kernel, delete the
		 *    old default IPv6 route and add a new default IPv6 route
		 *    (using the new address).
		 *
		 * 3. If the kernel address is the same as the one we are
		 *    adding, no additional processing is needed.
		 */
		getkstatus(&current_addr.s_addr);

		if (current_addr.s_addr == INADDR_ANY) {
			setkrraddr(new_addr.s_addr);
			V4ADDR_TO_6TO4_RT(&new_addr, &v6_rt);
			modifyroute(RTM_ADD, &v6_rt);
		} else if (new_addr.s_addr != current_addr.s_addr) {
			setkrraddr(new_addr.s_addr);
			/* remove old default IPv6 route */
			V4ADDR_TO_6TO4_RT(&current_addr, &v6_rt);
			modifyroute(RTM_DELETE, &v6_rt);
			/*
			 * Add new default IPv6 route using a 6to4 address
			 * created from the address we just set in the kernel.
			 */
			V4ADDR_TO_6TO4_RT(&new_addr, &v6_rt);
			modifyroute(RTM_ADD, &v6_rt);
		}
	}

	/*
	 * Disable Relay Router communication support in kernel.
	 */
	if (dflag) {
		struct in_addr current_addr; /* addr currently set in kernel */
		in6_addr_t v6_rt;

		/*
		 * get Relay Router address from the kernel and delete
		 * default IPv6 route that was added for it.
		 */
		getkstatus(&current_addr.s_addr);
		if (current_addr.s_addr == INADDR_ANY) {
			/*
			 * Feature is already disabled in kernel, no
			 * additional processing is needed.
			 */
			goto done;
		}

		V4ADDR_TO_6TO4_RT(&current_addr, &v6_rt);
		modifyroute(RTM_DELETE, &v6_rt);

		/*
		 * INADDR_ANY (0.0.0.0) is used by the kernel to disable Relay
		 * Router communication support.
		 */
		setkrraddr(INADDR_ANY);
	}
done:
	(void) close(fd);
	return (ret);
}