xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.sbin/ipaddrsel.c (revision 5703ae8931e75b07e7f34f6ed5b484dbb05a83d4)
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