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