/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include <libintl.h> #include <locale.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <ctype.h> #include <sys/param.h> #include <sys/types.h> #include <stropts.h> #include <sys/conf.h> #include <sys/socket.h> #include <sys/sockio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <inet/ip.h> #include <inet/ip6_asp.h> /* * The size of the table we initially use to retrieve the kernel's policy * table. If this value is too small, we use the value returned from the * SIOCGIP6ADDRPOLICY ioctl. */ #define KERN_POLICY_SIZE 32 #define IPV6DAS_MAXLINELEN 1024 #define IPV6DAS_MAXENTRIES 512 typedef enum { IPV6DAS_PRINTPOLICY, IPV6DAS_SETPOLICY, IPV6DAS_SETDEFAULT } ipv6das_cmd_t; static char *myname; /* Copied from argv[0] */ static int parseconf(const char *, ip6_asp_t **); static int setpolicy(int, ip6_asp_t *, int); static int printpolicy(int); static int ip_mask_to_plen_v6(const in6_addr_t *); static in6_addr_t *ip_plen_to_mask_v6(int, in6_addr_t *); static int strioctl(int, int, void *, int); static void usage(void); int main(int argc, char **argv) { int opt, status, sock, count; char *conf_filename; ipv6das_cmd_t ipv6das_cmd = IPV6DAS_PRINTPOLICY; ip6_asp_t *policy_table; myname = *argv; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); while ((opt = getopt(argc, argv, "df:")) != EOF) switch (opt) { case 'd': ipv6das_cmd = IPV6DAS_SETDEFAULT; break; case 'f': conf_filename = optarg; ipv6das_cmd = IPV6DAS_SETPOLICY; break; default: usage(); return (EXIT_FAILURE); } if (argc > optind) { /* shouldn't be any extra args */ usage(); return (EXIT_FAILURE); } /* Open a socket that we can use to send ioctls down to IP. */ if ((sock = socket(PF_INET6, SOCK_DGRAM, 0)) == -1) { perror("socket"); return (EXIT_FAILURE); } switch (ipv6das_cmd) { case IPV6DAS_SETPOLICY: if ((count = parseconf(conf_filename, &policy_table)) <= 0) return (EXIT_FAILURE); status = setpolicy(sock, policy_table, count); free(policy_table); break; case IPV6DAS_SETDEFAULT: status = setpolicy(sock, NULL, 0); break; case IPV6DAS_PRINTPOLICY: default: status = printpolicy(sock); break; } (void) close(sock); return (status); } /* * parseconf(filename, new_policy) * * Parses the file identified by filename, filling in new_policy * with the address selection policy table specified in filename. * Returns -1 on failure, or the number of table entries found * on success. */ static int parseconf(const char *filename, ip6_asp_t **new_policy) { FILE *fp; char line[IPV6DAS_MAXLINELEN]; char *cp, *end; char *prefixstr; uint_t lineno = 0, entryindex = 0; int plen, precedence; char *label; size_t labellen; int retval; ip6_asp_t tmp_policy[IPV6DAS_MAXENTRIES]; boolean_t have_default = B_FALSE; in6_addr_t prefix, mask; boolean_t comment_found = B_FALSE, end_of_line = B_FALSE; if ((fp = fopen(filename, "r")) == NULL) { perror(filename); return (-1); } while (fgets(line, sizeof (line), fp) != NULL) { if (entryindex == IPV6DAS_MAXENTRIES) { (void) fprintf(stderr, gettext("%s: too many entries\n"), filename); retval = -1; goto end_parse; } lineno++; cp = line; /* Skip leading whitespace */ while (isspace(*cp)) cp++; /* Is this a comment or blank line? */ if (*cp == '#' || *cp == '\0') continue; /* * Anything else must be of the form: * <IPv6-addr>/<plen> <precedence> <label> */ prefixstr = cp; if ((cp = strchr(cp, '/')) == NULL) { (void) fprintf(stderr, gettext("%s: invalid prefix on line %d: %s\n"), filename, lineno, prefixstr); continue; } *cp = '\0'; if (inet_pton(AF_INET6, prefixstr, &prefix) != 1) { (void) fprintf(stderr, gettext("%s: invalid prefix on line %d: %s\n"), filename, lineno, prefixstr); continue; } cp++; errno = 0; plen = strtol(cp, &end, 10); if (cp == end || errno != 0) { (void) fprintf(stderr, gettext("%s: invalid prefix length on line %d\n"), filename, lineno); continue; } if (ip_plen_to_mask_v6(plen, &mask) == NULL) { (void) fprintf(stderr, gettext("%s: invalid prefix length on line %d:" " %d\n"), filename, lineno, plen); continue; } cp = end; errno = 0; precedence = strtol(cp, &end, 10); if (cp == end || precedence < 0 || errno != 0) { (void) fprintf(stderr, gettext("%s: invalid precedence on line %d\n"), filename, lineno); continue; } cp = end; while (isspace(*cp)) cp++; label = cp; /* * NULL terminate the label string. The label string is * composed of non-blank characters, and can optionally be * followed by a comment. */ while (*cp != '\0' && !isspace(*cp) && *cp != '#') cp++; if (*cp == '#') comment_found = B_TRUE; else if (*cp == '\0' || *cp == '\n') end_of_line = B_TRUE; *cp = '\0'; labellen = cp - label; if (labellen == 0) { (void) fprintf(stderr, gettext("%s: missing label on line %d\n"), filename, lineno); continue; } if (labellen >= IP6_ASP_MAXLABELSIZE) { (void) fprintf(stderr, gettext("%s: label too long on line %d, labels " "have a %d character limit.\n"), filename, lineno, IP6_ASP_MAXLABELSIZE - 1); continue; } tmp_policy[entryindex].ip6_asp_prefix = prefix; tmp_policy[entryindex].ip6_asp_mask = mask; tmp_policy[entryindex].ip6_asp_precedence = precedence; /* * We're specifically using strncpy() to copy the label * to take advantage of the fact that strncpy will add * NULL characters to the target string up to the given * length, so don't change the call to strncpy() with * out also taking into account this requirement. The * labels are stored in the kernel in that way in order * to make comparisons more efficient: all 16 bytes of * the labels are compared to each other; random bytes * after the NULL terminator would yield incorrect * comparisons. */ (void) strncpy(tmp_policy[entryindex].ip6_asp_label, label, IP6_ASP_MAXLABELSIZE); /* * Anything else on the line should be a comment; print * a warning if that's not the case. */ if (!comment_found && !end_of_line) { cp++; while (*cp != '\0' && isspace(*cp) && *cp != '#') cp++; if (*cp != '\0' && *cp != '#') { (void) fprintf(stderr, gettext("%s: characters following label " "on line %d will be ignored\n"), filename, lineno); } } if (IN6_IS_ADDR_UNSPECIFIED(&prefix) && plen == 0) have_default = B_TRUE; comment_found = B_FALSE; end_of_line = B_FALSE; entryindex++; } if (!have_default) { (void) fprintf(stderr, gettext("%s: config doesn't contain a default entry.\n"), filename); retval = -1; goto end_parse; } /* Allocate the caller's array. */ if ((*new_policy = malloc(entryindex * sizeof (ip6_asp_t))) == NULL) { perror("malloc"); retval = -1; goto end_parse; } (void) memcpy(*new_policy, tmp_policy, entryindex * sizeof (ip6_asp_t)); retval = entryindex; end_parse: (void) fclose(fp); return (retval); } /* * setpolicy(sock, new_policy, count) * * Sends an SIOCSIP6ADDRPOLICY ioctl to the kernel to set the address * selection policy table pointed to by new_policy. count should be * the number of entries in the table; sock should be an open INET6 * socket. Returns EXIT_FAILURE or EXIT_SUCCESS. */ static int setpolicy(int sock, ip6_asp_t *new_policy, int count) { if (strioctl(sock, SIOCSIP6ADDRPOLICY, new_policy, count * sizeof (ip6_asp_t)) < 0) { perror("SIOCSIP6ADDRPOLICY"); return (EXIT_FAILURE); } return (EXIT_SUCCESS); } /* * printpolicy(sock) * * Queries the kernel for the current address selection policy using * the open socket sock, and prints the result. Returns EXIT_FAILURE * if the table cannot be obtained, or EXIT_SUCCESS if the table is * obtained and printed successfully. */ static int printpolicy(int sock) { ip6_asp_t policy[KERN_POLICY_SIZE]; ip6_asp_t *policy_ptr = policy; int count, policy_index; char prefixstr[INET6_ADDRSTRLEN + sizeof ("/128")]; if ((count = strioctl(sock, SIOCGIP6ADDRPOLICY, policy_ptr, KERN_POLICY_SIZE * sizeof (ip6_asp_t))) < 0) { perror("SIOCGIP6ADDRPOLICY"); return (EXIT_FAILURE); } if (count > KERN_POLICY_SIZE) { policy_ptr = malloc(count * sizeof (ip6_asp_t)); if (policy_ptr == NULL) { perror("malloc"); return (EXIT_FAILURE); } if ((count = strioctl(sock, SIOCGIP6ADDRPOLICY, policy_ptr, count * sizeof (ip6_asp_t))) < 0) { perror("SIOCGIP6ADDRPOLICY"); return (EXIT_FAILURE); } } if (count == 0) { /* * There should always at least be a default entry in the * policy table, so the minimum acceptable value of * policy_count is 1. */ (void) fprintf(stderr, gettext("%s: ERROR: " "IPv6 address selection policy is empty.\n"), myname); return (EXIT_FAILURE); } /* * The format printed here must also be parsable by parseconf(), * since we expect users to be able to redirect this output to * a usable configuration file if need be. */ (void) printf("# Prefix " " Precedence Label\n"); for (policy_index = 0; policy_index < count; policy_index++) { (void) snprintf(prefixstr, sizeof (prefixstr), "%s/%d", inet_ntop(AF_INET6, &policy_ptr[policy_index].ip6_asp_prefix, prefixstr, sizeof (prefixstr)), ip_mask_to_plen_v6(&policy_ptr[policy_index].ip6_asp_mask)); (void) printf("%-45s %10d %s\n", prefixstr, policy_ptr[policy_index].ip6_asp_precedence, policy_ptr[policy_index].ip6_asp_label); } if (policy_ptr != policy) free(policy_ptr); return (EXIT_SUCCESS); } /* * ip_mask_to_plen_v6(v6mask) * * This function takes a mask and returns number of bits set in the * mask (the represented prefix length). Assumes a contigious mask. */ int ip_mask_to_plen_v6(const in6_addr_t *v6mask) { uint8_t bits; uint32_t mask; int i; if (v6mask->_S6_un._S6_u32[3] == 0xffffffff) /* check for all ones */ return (IPV6_ABITS); /* Find number of words with 32 ones */ bits = 0; for (i = 0; i < 4; i++) { if (v6mask->_S6_un._S6_u32[i] == 0xffffffff) { bits += 32; continue; } break; } /* * Find number of bits in the last word by searching * for the first one from the right */ mask = ntohl(v6mask->_S6_un._S6_u32[i]); if (mask == 0) return (bits); return (bits + 32 - (ffs(mask) - 1)); } /* * ip_plen_to_mask_v6(plen, bitmask) * * Convert a prefix length to the mask for that prefix. * Returns the argument bitmask. */ in6_addr_t * ip_plen_to_mask_v6(int plen, in6_addr_t *bitmask) { uint32_t *ptr; if (plen > IPV6_ABITS || plen < 0) return (NULL); (void) memset(bitmask, 0, sizeof (in6_addr_t)); if (plen == 0) return (bitmask); ptr = (uint32_t *)bitmask; while (plen > 32) { *ptr++ = 0xffffffffU; plen -= 32; } *ptr = htonl(0xffffffffU << (32 - plen)); return (bitmask); } /* * strioctl(fd, cmd, ptr, ilen) * * Passes an I_STR ioctl to fd. The ioctl type is specified by cmd, and * any date to be sent down is specified by a pointer to the buffer (ptr) * and the buffer size (ilen). Returns the return value from the ioctl() * call. */ static int strioctl(int fd, int cmd, void *ptr, int ilen) { struct strioctl str; int retv; str.ic_cmd = cmd; str.ic_timout = 0; str.ic_len = ilen; str.ic_dp = ptr; while ((retv = ioctl(fd, I_STR, &str)) == -1) { if (errno != EINTR) break; } return (retv); } static void usage(void) { (void) fprintf(stderr, gettext( "Usage: %s\n" " %s -f <filename>\n" " %s -d\n"), myname, myname, myname); }