Home | History | Annotate | Download | only in usr.sbin
      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 
     57 typedef enum {
     58 	IPV6DAS_PRINTPOLICY,
     59 	IPV6DAS_SETPOLICY,
     60 	IPV6DAS_SETDEFAULT
     61 } ipv6das_cmd_t;
     62 
     63 static char *myname;	/* Copied from argv[0] */
     64 
     65 static int	parseconf(const char *, ip6_asp_t **);
     66 static int	setpolicy(int, ip6_asp_t *, int);
     67 static int	printpolicy(int);
     68 static int	ip_mask_to_plen_v6(const in6_addr_t *);
     69 static in6_addr_t *ip_plen_to_mask_v6(int, in6_addr_t *);
     70 static int	strioctl(int, int, void *, int);
     71 static void	usage(void);
     72 
     73 int
     74 main(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  */
    144 static int
    145 parseconf(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 
    322 end_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  */
    335 static int
    336 setpolicy(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  */
    354 static int
    355 printpolicy(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  */
    420 int
    421 ip_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  */
    457 in6_addr_t *
    458 ip_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  */
    486 static int
    487 strioctl(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 
    504 static void
    505 usage(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