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