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 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 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 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 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 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 * 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 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 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