1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#pragma ident	"%Z%%M%	%I%	%E% SMI"
28
29#include <libintl.h>
30#include <locale.h>
31#include <unistd.h>
32#include <stdlib.h>
33#include <stdio.h>
34#include <errno.h>
35#include <string.h>
36#include <ctype.h>
37#include <sys/param.h>
38#include <sys/types.h>
39#include <stropts.h>
40#include <sys/conf.h>
41#include <sys/socket.h>
42#include <sys/sockio.h>
43#include <netinet/in.h>
44#include <arpa/inet.h>
45#include <inet/ip.h>
46#include <inet/ip6_asp.h>
47
48/*
49 * The size of the table we initially use to retrieve the kernel's policy
50 * table.  If this value is too small, we use the value returned from the
51 * SIOCGIP6ADDRPOLICY ioctl.
52 */
53#define	KERN_POLICY_SIZE	32
54#define	IPV6DAS_MAXLINELEN	1024
55#define	IPV6DAS_MAXENTRIES	512
56
57typedef enum {
58	IPV6DAS_PRINTPOLICY,
59	IPV6DAS_SETPOLICY,
60	IPV6DAS_SETDEFAULT
61} ipv6das_cmd_t;
62
63static char *myname;	/* Copied from argv[0] */
64
65static int	parseconf(const char *, ip6_asp_t **);
66static int	setpolicy(int, ip6_asp_t *, int);
67static int	printpolicy(int);
68static int	ip_mask_to_plen_v6(const in6_addr_t *);
69static in6_addr_t *ip_plen_to_mask_v6(int, in6_addr_t *);
70static int	strioctl(int, int, void *, int);
71static void	usage(void);
72
73int
74main(int argc, char **argv)
75{
76	int		opt, status, sock, count;
77	char		*conf_filename;
78	ipv6das_cmd_t	ipv6das_cmd = IPV6DAS_PRINTPOLICY;
79	ip6_asp_t	*policy_table;
80
81	myname = *argv;
82
83	(void) setlocale(LC_ALL, "");
84
85#if	!defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
86#define	TEXT_DOMAIN	"SYS_TEST"
87#endif
88
89	(void) textdomain(TEXT_DOMAIN);
90
91	while ((opt = getopt(argc, argv, "df:")) != EOF)
92		switch (opt) {
93		case 'd':
94			ipv6das_cmd = IPV6DAS_SETDEFAULT;
95			break;
96		case 'f':
97			conf_filename = optarg;
98			ipv6das_cmd = IPV6DAS_SETPOLICY;
99			break;
100		default:
101			usage();
102			return (EXIT_FAILURE);
103		}
104	if (argc > optind) {
105		/* shouldn't be any extra args */
106		usage();
107		return (EXIT_FAILURE);
108	}
109
110	/* Open a socket that we can use to send ioctls down to IP. */
111	if ((sock = socket(PF_INET6, SOCK_DGRAM, 0)) == -1) {
112		perror("socket");
113		return (EXIT_FAILURE);
114	}
115
116	switch (ipv6das_cmd) {
117	case IPV6DAS_SETPOLICY:
118		if ((count = parseconf(conf_filename, &policy_table)) <= 0)
119			return (EXIT_FAILURE);
120		status = setpolicy(sock, policy_table, count);
121		free(policy_table);
122		break;
123	case IPV6DAS_SETDEFAULT:
124		status = setpolicy(sock, NULL, 0);
125		break;
126	case IPV6DAS_PRINTPOLICY:
127	default:
128		status = printpolicy(sock);
129		break;
130	}
131
132	(void) close(sock);
133	return (status);
134}
135
136/*
137 * parseconf(filename, new_policy)
138 *
139 * Parses the file identified by filename, filling in new_policy
140 * with the address selection policy table specified in filename.
141 * Returns -1 on failure, or the number of table entries found
142 * on success.
143 */
144static int
145parseconf(const char *filename, ip6_asp_t **new_policy)
146{
147	FILE		*fp;
148	char		line[IPV6DAS_MAXLINELEN];
149	char		*cp, *end;
150	char		*prefixstr;
151	uint_t		lineno = 0, entryindex = 0;
152	int		plen, precedence;
153	char		*label;
154	size_t		labellen;
155	int		retval;
156	ip6_asp_t	tmp_policy[IPV6DAS_MAXENTRIES];
157	boolean_t	have_default = B_FALSE;
158	in6_addr_t	prefix, mask;
159	boolean_t	comment_found = B_FALSE, end_of_line = B_FALSE;
160
161	if ((fp = fopen(filename, "r")) == NULL) {
162		perror(filename);
163		return (-1);
164	}
165
166	while (fgets(line, sizeof (line), fp) != NULL) {
167		if (entryindex == IPV6DAS_MAXENTRIES) {
168			(void) fprintf(stderr,
169			    gettext("%s: too many entries\n"), filename);
170			retval = -1;
171			goto end_parse;
172		}
173
174		lineno++;
175		cp = line;
176
177		/* Skip leading whitespace */
178		while (isspace(*cp))
179			cp++;
180
181		/* Is this a comment or blank line? */
182		if (*cp == '#' || *cp == '\0')
183			continue;
184
185		/*
186		 * Anything else must be of the form:
187		 * <IPv6-addr>/<plen> <precedence> <label>
188		 */
189		prefixstr = cp;
190		if ((cp = strchr(cp, '/')) == NULL) {
191			(void) fprintf(stderr,
192			    gettext("%s: invalid prefix on line %d: %s\n"),
193			    filename, lineno, prefixstr);
194			continue;
195		}
196		*cp = '\0';
197		if (inet_pton(AF_INET6, prefixstr, &prefix) != 1) {
198			(void) fprintf(stderr,
199			    gettext("%s: invalid prefix on line %d: %s\n"),
200			    filename, lineno, prefixstr);
201			continue;
202		}
203		cp++;
204
205		errno = 0;
206		plen = strtol(cp, &end, 10);
207		if (cp == end || errno != 0) {
208			(void) fprintf(stderr,
209			    gettext("%s: invalid prefix length on line %d\n"),
210			    filename, lineno);
211			continue;
212		}
213		if (ip_plen_to_mask_v6(plen, &mask) == NULL) {
214			(void) fprintf(stderr,
215			    gettext("%s: invalid prefix length on line %d:"
216			    " %d\n"), filename, lineno, plen);
217			continue;
218		}
219		cp = end;
220
221		errno = 0;
222		precedence = strtol(cp, &end, 10);
223		if (cp == end || precedence < 0 || errno != 0) {
224			(void) fprintf(stderr,
225			    gettext("%s: invalid precedence on line %d\n"),
226			    filename, lineno);
227			continue;
228		}
229		cp = end;
230
231		while (isspace(*cp))
232			cp++;
233		label = cp;
234		/*
235		 * NULL terminate the label string.  The label string is
236		 * composed of non-blank characters, and can optionally be
237		 * followed by a comment.
238		 */
239		while (*cp != '\0' && !isspace(*cp) && *cp != '#')
240			cp++;
241		if (*cp == '#')
242			comment_found = B_TRUE;
243		else if (*cp == '\0' || *cp == '\n')
244			end_of_line = B_TRUE;
245		*cp = '\0';
246
247		labellen = cp - label;
248		if (labellen == 0) {
249			(void) fprintf(stderr,
250			    gettext("%s: missing label on line %d\n"),
251			    filename, lineno);
252			continue;
253		}
254		if (labellen >= IP6_ASP_MAXLABELSIZE) {
255			(void) fprintf(stderr,
256			    gettext("%s: label too long on line %d, labels "
257			    "have a %d character limit.\n"), filename, lineno,
258			    IP6_ASP_MAXLABELSIZE - 1);
259			continue;
260		}
261
262		tmp_policy[entryindex].ip6_asp_prefix = prefix;
263		tmp_policy[entryindex].ip6_asp_mask = mask;
264		tmp_policy[entryindex].ip6_asp_precedence = precedence;
265		/*
266		 * We're specifically using strncpy() to copy the label
267		 * to take advantage of the fact that strncpy will add
268		 * NULL characters to the target string up to the given
269		 * length, so don't change the call to strncpy() with
270		 * out also taking into account this requirement.  The
271		 * labels are stored in the kernel in that way in order
272		 * to make comparisons more efficient: all 16 bytes of
273		 * the labels are compared to each other; random bytes
274		 * after the NULL terminator would yield incorrect
275		 * comparisons.
276		 */
277		(void) strncpy(tmp_policy[entryindex].ip6_asp_label, label,
278		    IP6_ASP_MAXLABELSIZE);
279
280		/*
281		 * Anything else on the line should be a comment; print
282		 * a warning if that's not the case.
283		 */
284		if (!comment_found && !end_of_line) {
285			cp++;
286			while (*cp != '\0' && isspace(*cp) && *cp != '#')
287				cp++;
288			if (*cp != '\0' && *cp != '#') {
289				(void) fprintf(stderr,
290				    gettext("%s: characters following label "
291				    "on line %d will be ignored\n"),
292				    filename, lineno);
293			}
294		}
295
296		if (IN6_IS_ADDR_UNSPECIFIED(&prefix) && plen == 0)
297			have_default = B_TRUE;
298
299		comment_found = B_FALSE;
300		end_of_line = B_FALSE;
301		entryindex++;
302	}
303
304	if (!have_default) {
305		(void) fprintf(stderr,
306		    gettext("%s: config doesn't contain a default entry.\n"),
307		    filename);
308		retval = -1;
309		goto end_parse;
310	}
311
312	/* Allocate the caller's array. */
313	if ((*new_policy = malloc(entryindex * sizeof (ip6_asp_t))) == NULL) {
314		perror("malloc");
315		retval = -1;
316		goto end_parse;
317	}
318
319	(void) memcpy(*new_policy, tmp_policy, entryindex * sizeof (ip6_asp_t));
320	retval = entryindex;
321
322end_parse:
323	(void) fclose(fp);
324	return (retval);
325}
326
327/*
328 * setpolicy(sock, new_policy, count)
329 *
330 * Sends an SIOCSIP6ADDRPOLICY ioctl to the kernel to set the address
331 * selection policy table pointed to by new_policy.  count should be
332 * the number of entries in the table; sock should be an open INET6
333 * socket.  Returns EXIT_FAILURE or EXIT_SUCCESS.
334 */
335static int
336setpolicy(int sock, ip6_asp_t *new_policy, int count)
337{
338	if (strioctl(sock, SIOCSIP6ADDRPOLICY, new_policy,
339	    count * sizeof (ip6_asp_t)) < 0) {
340		perror("SIOCSIP6ADDRPOLICY");
341		return (EXIT_FAILURE);
342	}
343	return (EXIT_SUCCESS);
344}
345
346/*
347 * printpolicy(sock)
348 *
349 * Queries the kernel for the current address selection policy using
350 * the open socket sock, and prints the result.  Returns EXIT_FAILURE
351 * if the table cannot be obtained, or EXIT_SUCCESS if the table is
352 * obtained and printed successfully.
353 */
354static int
355printpolicy(int sock)
356{
357	ip6_asp_t	policy[KERN_POLICY_SIZE];
358	ip6_asp_t	*policy_ptr = policy;
359	int		count, policy_index;
360	char		prefixstr[INET6_ADDRSTRLEN + sizeof ("/128")];
361
362	if ((count = strioctl(sock, SIOCGIP6ADDRPOLICY, policy_ptr,
363	    KERN_POLICY_SIZE * sizeof (ip6_asp_t))) < 0) {
364		perror("SIOCGIP6ADDRPOLICY");
365		return (EXIT_FAILURE);
366	}
367	if (count > KERN_POLICY_SIZE) {
368		policy_ptr = malloc(count * sizeof (ip6_asp_t));
369		if (policy_ptr == NULL) {
370			perror("malloc");
371			return (EXIT_FAILURE);
372		}
373		if ((count = strioctl(sock, SIOCGIP6ADDRPOLICY, policy_ptr,
374		    count * sizeof (ip6_asp_t))) < 0) {
375			perror("SIOCGIP6ADDRPOLICY");
376			return (EXIT_FAILURE);
377		}
378	}
379
380	if (count == 0) {
381		/*
382		 * There should always at least be a default entry in the
383		 * policy table, so the minimum acceptable value of
384		 * policy_count is 1.
385		 */
386		(void) fprintf(stderr, gettext("%s: ERROR: "
387		    "IPv6 address selection policy is empty.\n"), myname);
388		return (EXIT_FAILURE);
389	}
390
391	/*
392	 * The format printed here must also be parsable by parseconf(),
393	 * since we expect users to be able to redirect this output to
394	 * a usable configuration file if need be.
395	 */
396	(void) printf("# Prefix                  "
397		"                    Precedence Label\n");
398	for (policy_index = 0; policy_index < count; policy_index++) {
399		(void) snprintf(prefixstr, sizeof (prefixstr), "%s/%d",
400		    inet_ntop(AF_INET6,
401			&policy_ptr[policy_index].ip6_asp_prefix, prefixstr,
402			sizeof (prefixstr)),
403		    ip_mask_to_plen_v6(&policy_ptr[policy_index].ip6_asp_mask));
404		(void) printf("%-45s %10d %s\n", prefixstr,
405		    policy_ptr[policy_index].ip6_asp_precedence,
406		    policy_ptr[policy_index].ip6_asp_label);
407	}
408
409	if (policy_ptr != policy)
410		free(policy_ptr);
411	return (EXIT_SUCCESS);
412}
413
414/*
415 * ip_mask_to_plen_v6(v6mask)
416 *
417 * This function takes a mask and returns number of bits set in the
418 * mask (the represented prefix length).  Assumes a contigious mask.
419 */
420int
421ip_mask_to_plen_v6(const in6_addr_t *v6mask)
422{
423	uint8_t		bits;
424	uint32_t	mask;
425	int		i;
426
427	if (v6mask->_S6_un._S6_u32[3] == 0xffffffff) /* check for all ones */
428		return (IPV6_ABITS);
429
430	/* Find number of words with 32 ones */
431	bits = 0;
432	for (i = 0; i < 4; i++) {
433		if (v6mask->_S6_un._S6_u32[i] == 0xffffffff) {
434			bits += 32;
435			continue;
436		}
437		break;
438	}
439
440	/*
441	 * Find number of bits in the last word by searching
442	 * for the first one from the right
443	 */
444	mask = ntohl(v6mask->_S6_un._S6_u32[i]);
445	if (mask == 0)
446		return (bits);
447
448	return (bits + 32 - (ffs(mask) - 1));
449}
450
451/*
452 * ip_plen_to_mask_v6(plen, bitmask)
453 *
454 * Convert a prefix length to the mask for that prefix.
455 * Returns the argument bitmask.
456 */
457in6_addr_t *
458ip_plen_to_mask_v6(int plen, in6_addr_t *bitmask)
459{
460	uint32_t *ptr;
461
462	if (plen > IPV6_ABITS || plen < 0)
463		return (NULL);
464
465	(void) memset(bitmask, 0, sizeof (in6_addr_t));
466	if (plen == 0)
467		return (bitmask);
468
469	ptr = (uint32_t *)bitmask;
470	while (plen > 32) {
471		*ptr++ = 0xffffffffU;
472		plen -= 32;
473	}
474	*ptr = htonl(0xffffffffU << (32 - plen));
475	return (bitmask);
476}
477
478/*
479 * strioctl(fd, cmd, ptr, ilen)
480 *
481 * Passes an I_STR ioctl to fd.  The ioctl type is specified by cmd, and
482 * any date to be sent down is specified by a pointer to the buffer (ptr)
483 * and the buffer size (ilen).  Returns the return value from the ioctl()
484 * call.
485 */
486static int
487strioctl(int fd, int cmd, void *ptr, int ilen)
488{
489	struct strioctl str;
490	int retv;
491
492	str.ic_cmd = cmd;
493	str.ic_timout = 0;
494	str.ic_len = ilen;
495	str.ic_dp = ptr;
496
497	while ((retv = ioctl(fd, I_STR, &str)) == -1) {
498		if (errno != EINTR)
499			break;
500	}
501	return (retv);
502}
503
504static void
505usage(void)
506{
507	(void) fprintf(stderr, gettext(
508	    "Usage: %s\n"
509	    "       %s -f <filename>\n"
510	    "       %s -d\n"), myname, myname, myname);
511}
512