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
main(int argc,char ** argv)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
parseconf(const char * filename,ip6_asp_t ** new_policy)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
setpolicy(int sock,ip6_asp_t * new_policy,int count)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
printpolicy(int sock)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
ip_mask_to_plen_v6(const in6_addr_t * v6mask)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 *
ip_plen_to_mask_v6(int plen,in6_addr_t * bitmask)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
strioctl(int fd,int cmd,void * ptr,int ilen)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
usage(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