/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "defs.h" #include "tables.h" #include #include struct phyint *phyints = NULL; int num_of_phyints = 0; static void phyint_print(struct phyint *pi); static void phyint_insert(struct phyint *pi); static boolean_t tmptoken_isvalid(struct in6_addr *token); static void prefix_print(struct prefix *pr); static void prefix_insert(struct phyint *pi, struct prefix *pr); static char *prefix_print_state(int state, char *buf, int buflen); static void prefix_set(struct in6_addr *prefix, struct in6_addr addr, int bits); static void adv_prefix_print(struct adv_prefix *adv_pr); static void adv_prefix_insert(struct phyint *pi, struct adv_prefix *adv_pr); static void adv_prefix_delete(struct adv_prefix *adv_pr); static void router_print(struct router *dr); static void router_insert(struct phyint *pi, struct router *dr); static void router_delete(struct router *dr); static void router_add_k(struct router *dr); static void router_delete_k(struct router *dr); static int rtmseq; /* rtm_seq sequence number */ /* 1 week in ms */ #define NDP_PREFIX_DEFAULT_LIFETIME (7*24*60*60*1000) struct phyint * phyint_lookup(char *name) { struct phyint *pi; if (debug & D_PHYINT) logmsg(LOG_DEBUG, "phyint_lookup(%s)\n", name); for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (strcmp(pi->pi_name, name) == 0) break; } return (pi); } struct phyint * phyint_lookup_on_index(uint_t ifindex) { struct phyint *pi; if (debug & D_PHYINT) logmsg(LOG_DEBUG, "phyint_lookup_on_index(%d)\n", ifindex); for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (pi->pi_index == ifindex) break; } return (pi); } struct phyint * phyint_create(char *name) { struct phyint *pi; int i; if (debug & D_PHYINT) logmsg(LOG_DEBUG, "phyint_create(%s)\n", name); pi = (struct phyint *)calloc(sizeof (struct phyint), 1); if (pi == NULL) { logmsg(LOG_ERR, "phyint_create: out of memory\n"); return (NULL); } (void) strncpy(pi->pi_name, name, sizeof (pi->pi_name)); pi->pi_name[sizeof (pi->pi_name) - 1] = '\0'; /* * Copy the defaults from the defaults array. * Do not copy the cf_notdefault fields since these have not * been explicitly set for the phyint. */ for (i = 0; i < I_IFSIZE; i++) pi->pi_config[i].cf_value = ifdefaults[i].cf_value; /* * TmpDesyncFactor is used to desynchronize temporary token * generation among systems; the actual preferred lifetime value * of a temporary address will be (TmpPreferredLifetime - * TmpDesyncFactor). It's a random value, with a user-configurable * maximum value. The value is constant throughout the lifetime * of the in.ndpd process, but can change if the daemon is restarted, * per RFC3041. */ if (pi->pi_TmpMaxDesyncFactor != 0) { time_t seed = time(NULL); srand((uint_t)seed); pi->pi_TmpDesyncFactor = rand() % pi->pi_TmpMaxDesyncFactor; /* we actually want [1,max], not [0,(max-1)] */ pi->pi_TmpDesyncFactor++; } pi->pi_TmpRegenCountdown = TIMER_INFINITY; pi->pi_sock = -1; pi->pi_stateless = pi->pi_StatelessAddrConf; pi->pi_stateful = pi->pi_StatefulAddrConf; pi->pi_autoconf = _B_TRUE; pi->pi_default_token = _B_TRUE; if (phyint_init_from_k(pi) == -1) { free(pi); return (NULL); } phyint_insert(pi); if (pi->pi_sock != -1) { if (poll_add(pi->pi_sock) == -1) { phyint_delete(pi); return (NULL); } } return (pi); } /* Insert in linked list */ static void phyint_insert(struct phyint *pi) { /* Insert in list */ pi->pi_next = phyints; pi->pi_prev = NULL; if (phyints) phyints->pi_prev = pi; phyints = pi; num_of_phyints++; } /* * Initialize both the phyint data structure and the pi_sock for * sending and receving on the interface. * Extract information from the kernel (if present) and set pi_kernel_state. */ int phyint_init_from_k(struct phyint *pi) { struct ipv6_mreq v6mcastr; struct lifreq lifr; int fd; int save_errno; boolean_t newsock; uint_t ttl; struct sockaddr_in6 *sin6; if (debug & D_PHYINT) logmsg(LOG_DEBUG, "phyint_init_from_k(%s)\n", pi->pi_name); start_over: if (pi->pi_sock < 0) { pi->pi_sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (pi->pi_sock < 0) { logperror_pi(pi, "phyint_init_from_k: socket"); return (-1); } newsock = _B_TRUE; } else { newsock = _B_FALSE; } fd = pi->pi_sock; (void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (ioctl(fd, SIOCGLIFINDEX, (char *)&lifr) < 0) { if (errno == ENXIO) { if (newsock) { (void) close(pi->pi_sock); pi->pi_sock = -1; } if (debug & D_PHYINT) { logmsg(LOG_DEBUG, "phyint_init_from_k(%s): " "not exist\n", pi->pi_name); } return (0); } logperror_pi(pi, "phyint_init_from_k: SIOCGLIFINDEX"); goto error; } if (!newsock && (pi->pi_index != lifr.lifr_index)) { /* * Interface has been re-plumbed, lets open a new socket. * This situation can occur if plumb/unplumb are happening * quite frequently. */ phyint_cleanup(pi); goto start_over; } pi->pi_index = lifr.lifr_index; if (ioctl(fd, SIOCGLIFFLAGS, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: ioctl (get flags)"); goto error; } pi->pi_flags = lifr.lifr_flags; /* * If the link local interface is not up yet or it's IFF_UP and the * IFF_NOLOCAL flag is set, then ignore the interface. */ if (!(pi->pi_flags & IFF_UP) || (pi->pi_flags & IFF_NOLOCAL)) { if (newsock) { (void) close(pi->pi_sock); pi->pi_sock = -1; } if (debug & D_PHYINT) { logmsg(LOG_DEBUG, "phyint_init_from_k(%s): " "IFF_NOLOCAL or not IFF_UP\n", pi->pi_name); } return (0); } pi->pi_kernel_state |= PI_PRESENT; if (ioctl(fd, SIOCGLIFMTU, (caddr_t)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: ioctl (get mtu)"); goto error; } pi->pi_mtu = lifr.lifr_mtu; if (ioctl(fd, SIOCGLIFADDR, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCGLIFADDR"); goto error; } sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; pi->pi_ifaddr = sin6->sin6_addr; if (pi->pi_autoconf && pi->pi_default_token) { if (ioctl(fd, SIOCGLIFTOKEN, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCGLIFTOKEN"); goto error; } /* Ignore interface if the token is all zeros */ sin6 = (struct sockaddr_in6 *)&lifr.lifr_token; if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { logmsg(LOG_ERR, "ignoring interface %s: zero token\n", pi->pi_name); goto error; } pi->pi_token = sin6->sin6_addr; pi->pi_token_length = lifr.lifr_addrlen; } /* * Guess a remote token for POINTOPOINT by looking at * the link-local destination address. */ if (pi->pi_flags & IFF_POINTOPOINT) { if (ioctl(fd, SIOCGLIFDSTADDR, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCGLIFDSTADDR"); goto error; } sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; if (sin6->sin6_family != AF_INET6 || IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) || !IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { pi->pi_dst_token = in6addr_any; } else { pi->pi_dst_token = sin6->sin6_addr; /* Clear link-local prefix (first 10 bits) */ pi->pi_dst_token.s6_addr[0] = 0; pi->pi_dst_token.s6_addr[1] &= 0x3f; } } else { pi->pi_dst_token = in6addr_any; } if (newsock) { icmp6_filter_t filter; int on = 1; /* Set default values */ pi->pi_LinkMTU = pi->pi_mtu; pi->pi_CurHopLimit = 0; pi->pi_BaseReachableTime = ND_REACHABLE_TIME; phyint_reach_random(pi, _B_FALSE); pi->pi_RetransTimer = ND_RETRANS_TIMER; /* Setup socket for transmission and reception */ if (setsockopt(fd, IPPROTO_IPV6, IPV6_BOUND_IF, (char *)&pi->pi_index, sizeof (pi->pi_index)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "IPV6_BOUND_IF"); goto error; } ttl = IPV6_MAX_HOPS; if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&ttl, sizeof (ttl)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "IPV6_UNICAST_HOPS"); goto error; } if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&ttl, sizeof (ttl)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "IPV6_MULTICAST_HOPS"); goto error; } v6mcastr.ipv6mr_multiaddr = all_nodes_mcast; v6mcastr.ipv6mr_interface = pi->pi_index; if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&v6mcastr, sizeof (v6mcastr)) < 0) { /* * One benign reason IPV6_JOIN_GROUP could fail is * when `pi' has been placed into an IPMP group and we * haven't yet processed the routing socket message * informing us of its disappearance. As such, if * it's now in a group, don't print an error. */ save_errno = errno; (void) strlcpy(lifr.lifr_name, pi->pi_name, LIFNAMSIZ); if (ioctl(fd, SIOCGLIFGROUPNAME, &lifr) == -1 || lifr.lifr_groupname[0] == '\0') { errno = save_errno; logperror_pi(pi, "phyint_init_from_k: " "setsockopt IPV6_JOIN_GROUP"); } goto error; } pi->pi_state |= PI_JOINED_ALLNODES; pi->pi_kernel_state |= PI_JOINED_ALLNODES; /* * Filter out so that we only receive router advertisements and * router solicitations. */ ICMP6_FILTER_SETBLOCKALL(&filter); ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, (char *)&filter, sizeof (filter)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "ICMP6_FILTER"); goto error; } /* Enable receipt of ancillary data */ if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, (char *)&on, sizeof (on)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "IPV6_RECVHOPLIMIT"); goto error; } if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVRTHDR, (char *)&on, sizeof (on)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "IPV6_RECVRTHDR"); goto error; } } if (pi->pi_AdvSendAdvertisements && !(pi->pi_kernel_state & PI_JOINED_ALLROUTERS)) { v6mcastr.ipv6mr_multiaddr = all_routers_mcast; v6mcastr.ipv6mr_interface = pi->pi_index; if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&v6mcastr, sizeof (v6mcastr)) < 0) { /* * See IPV6_JOIN_GROUP comment above. */ save_errno = errno; (void) strlcpy(lifr.lifr_name, pi->pi_name, LIFNAMSIZ); if (ioctl(fd, SIOCGLIFGROUPNAME, &lifr) == -1 || lifr.lifr_groupname[0] == '\0') { errno = save_errno; logperror_pi(pi, "phyint_init_from_k: " "setsockopt IPV6_JOIN_GROUP"); } goto error; } pi->pi_state |= PI_JOINED_ALLROUTERS; pi->pi_kernel_state |= PI_JOINED_ALLROUTERS; } /* * If not already set, set the IFF_ROUTER interface flag based on * AdvSendAdvertisements. Note that this will also enable IPv6 * forwarding on the interface. We don't clear IFF_ROUTER if we're * not advertising on an interface, because we could still be * forwarding on those interfaces. */ (void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (ioctl(fd, SIOCGLIFFLAGS, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCGLIFFLAGS"); goto error; } if (!(lifr.lifr_flags & IFF_ROUTER) && pi->pi_AdvSendAdvertisements) { lifr.lifr_flags |= IFF_ROUTER; if (ioctl(fd, SIOCSLIFFLAGS, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCSLIFFLAGS"); goto error; } pi->pi_flags = lifr.lifr_flags; } /* Set linkinfo parameters */ (void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; lifr.lifr_ifinfo.lir_maxhops = pi->pi_CurHopLimit; lifr.lifr_ifinfo.lir_reachtime = pi->pi_ReachableTime; lifr.lifr_ifinfo.lir_reachretrans = pi->pi_RetransTimer; /* Setting maxmtu to 0 means that we're leaving the MTU alone */ lifr.lifr_ifinfo.lir_maxmtu = 0; if (ioctl(fd, SIOCSLIFLNKINFO, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCSLIFLNKINFO"); goto error; } if (debug & D_PHYINT) { logmsg(LOG_DEBUG, "phyint_init_from_k(%s): done\n", pi->pi_name); } return (0); error: /* Pretend the interface does not exist in the kernel */ pi->pi_kernel_state &= ~PI_PRESENT; if (newsock) { (void) close(pi->pi_sock); pi->pi_sock = -1; } return (-1); } /* * Delete (unlink and free). * Handles delete of things that have not yet been inserted in the list. */ void phyint_delete(struct phyint *pi) { if (debug & D_PHYINT) logmsg(LOG_DEBUG, "phyint_delete(%s)\n", pi->pi_name); assert(num_of_phyints > 0); while (pi->pi_router_list) router_delete(pi->pi_router_list); while (pi->pi_prefix_list) { prefix_update_ipadm_addrobj(pi->pi_prefix_list, _B_FALSE); prefix_delete(pi->pi_prefix_list); } while (pi->pi_adv_prefix_list) adv_prefix_delete(pi->pi_adv_prefix_list); if (pi->pi_sock != -1) { (void) poll_remove(pi->pi_sock); if (close(pi->pi_sock) < 0) { logperror_pi(pi, "phyint_delete: close"); } pi->pi_sock = -1; } if (pi->pi_prev == NULL) { if (phyints == pi) phyints = pi->pi_next; } else { pi->pi_prev->pi_next = pi->pi_next; } if (pi->pi_next != NULL) pi->pi_next->pi_prev = pi->pi_prev; pi->pi_next = pi->pi_prev = NULL; free(pi); num_of_phyints--; } /* * Called with the number of milliseconds elapsed since the last call. * Determines if any timeout event has occurred and * returns the number of milliseconds until the next timeout event * for the phyint itself (excluding prefixes and routers). * Returns TIMER_INFINITY for "never". */ uint_t phyint_timer(struct phyint *pi, uint_t elapsed) { uint_t next = TIMER_INFINITY; if (pi->pi_AdvSendAdvertisements) { if (pi->pi_adv_state != NO_ADV) { int old_state = pi->pi_adv_state; if (debug & (D_STATE|D_PHYINT)) { logmsg(LOG_DEBUG, "phyint_timer ADV(%s) " "state %d\n", pi->pi_name, (int)old_state); } next = advertise_event(pi, ADV_TIMER, elapsed); if (debug & D_STATE) { logmsg(LOG_DEBUG, "phyint_timer ADV(%s) " "state %d -> %d\n", pi->pi_name, (int)old_state, (int)pi->pi_adv_state); } } } else { if (pi->pi_sol_state != NO_SOLICIT) { int old_state = pi->pi_sol_state; if (debug & (D_STATE|D_PHYINT)) { logmsg(LOG_DEBUG, "phyint_timer SOL(%s) " "state %d\n", pi->pi_name, (int)old_state); } next = solicit_event(pi, SOL_TIMER, elapsed); if (debug & D_STATE) { logmsg(LOG_DEBUG, "phyint_timer SOL(%s) " "state %d -> %d\n", pi->pi_name, (int)old_state, (int)pi->pi_sol_state); } } } /* * If the phyint has been unplumbed, we don't want to call * phyint_reach_random. We will be in the NO_ADV or NO_SOLICIT state. */ if ((pi->pi_AdvSendAdvertisements && (pi->pi_adv_state != NO_ADV)) || (!pi->pi_AdvSendAdvertisements && (pi->pi_sol_state != NO_SOLICIT))) { pi->pi_reach_time_since_random += elapsed; if (pi->pi_reach_time_since_random >= MAX_REACH_RANDOM_INTERVAL) phyint_reach_random(pi, _B_TRUE); } return (next); } static void phyint_print(struct phyint *pi) { struct prefix *pr; struct adv_prefix *adv_pr; struct router *dr; char abuf[INET6_ADDRSTRLEN]; logmsg(LOG_DEBUG, "Phyint %s index %d state %x, kernel %x, " "num routers %d\n", pi->pi_name, pi->pi_index, pi->pi_state, pi->pi_kernel_state, pi->pi_num_k_routers); logmsg(LOG_DEBUG, "\taddress: %s flags %llx\n", inet_ntop(AF_INET6, (void *)&pi->pi_ifaddr, abuf, sizeof (abuf)), pi->pi_flags); logmsg(LOG_DEBUG, "\tsock %d mtu %d\n", pi->pi_sock, pi->pi_mtu); logmsg(LOG_DEBUG, "\ttoken: len %d %s\n", pi->pi_token_length, inet_ntop(AF_INET6, (void *)&pi->pi_token, abuf, sizeof (abuf))); if (pi->pi_TmpAddrsEnabled) { logmsg(LOG_DEBUG, "\ttmp_token: %s\n", inet_ntop(AF_INET6, (void *)&pi->pi_tmp_token, abuf, sizeof (abuf))); logmsg(LOG_DEBUG, "\ttmp config: pref %d valid %d " "maxdesync %d desync %d regen %d\n", pi->pi_TmpPreferredLifetime, pi->pi_TmpValidLifetime, pi->pi_TmpMaxDesyncFactor, pi->pi_TmpDesyncFactor, pi->pi_TmpRegenAdvance); } if (pi->pi_flags & IFF_POINTOPOINT) { logmsg(LOG_DEBUG, "\tdst_token: %s\n", inet_ntop(AF_INET6, (void *)&pi->pi_dst_token, abuf, sizeof (abuf))); } logmsg(LOG_DEBUG, "\tLinkMTU %d CurHopLimit %d " "BaseReachableTime %d\n\tReachableTime %d RetransTimer %d\n", pi->pi_LinkMTU, pi->pi_CurHopLimit, pi->pi_BaseReachableTime, pi->pi_ReachableTime, pi->pi_RetransTimer); if (!pi->pi_AdvSendAdvertisements) { /* Solicit state */ logmsg(LOG_DEBUG, "\tSOLICIT: time_left %d state %d count %d\n", pi->pi_sol_time_left, pi->pi_sol_state, pi->pi_sol_count); } else { /* Advertise state */ logmsg(LOG_DEBUG, "\tADVERT: time_left %d state %d count %d " "since last %d\n", pi->pi_adv_time_left, pi->pi_adv_state, pi->pi_adv_count, pi->pi_adv_time_since_sent); print_iflist(pi->pi_config); } for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) prefix_print(pr); for (adv_pr = pi->pi_adv_prefix_list; adv_pr != NULL; adv_pr = adv_pr->adv_pr_next) { adv_prefix_print(adv_pr); } for (dr = pi->pi_router_list; dr != NULL; dr = dr->dr_next) router_print(dr); logmsg(LOG_DEBUG, "\n"); } /* * Store the LLA for the phyint `pi' `lifrp'. Returns 0 on success, or * -1 on failure. * * Note that we do not cache the hardware address since there's no reliable * mechanism to determine when it's become stale. */ int phyint_get_lla(struct phyint *pi, struct lifreq *lifrp) { struct sockaddr_in6 *sin6; /* If this phyint doesn't have a link-layer address, bail */ if (!(pi->pi_flags & IFF_MULTICAST) || (pi->pi_flags & IFF_POINTOPOINT)) { return (-1); } (void) strlcpy(lifrp->lifr_name, pi->pi_name, LIFNAMSIZ); sin6 = (struct sockaddr_in6 *)&(lifrp->lifr_nd.lnr_addr); sin6->sin6_family = AF_INET6; sin6->sin6_addr = pi->pi_ifaddr; if (ioctl(pi->pi_sock, SIOCLIFGETND, lifrp) < 0) { /* * For IPMP interfaces, don't report ESRCH errors since that * merely indicates that there are no active interfaces in the * IPMP group (and thus there's no working hardware address), * and the packet will thus never make it out anyway. */ if (!(pi->pi_flags & IFF_IPMP) || errno != ESRCH) logperror_pi(pi, "phyint_get_lla: SIOCLIFGETND"); return (-1); } return (0); } /* * Randomize pi->pi_ReachableTime. * Done periodically when there are no RAs and at a maximum frequency when * RA's arrive. * Assumes that caller has determined that it is time to generate * a new random ReachableTime. */ void phyint_reach_random(struct phyint *pi, boolean_t set_needed) { struct lifreq lifr; pi->pi_ReachableTime = GET_RANDOM( (int)(ND_MIN_RANDOM_FACTOR * pi->pi_BaseReachableTime), (int)(ND_MAX_RANDOM_FACTOR * pi->pi_BaseReachableTime)); if (set_needed) { bzero(&lifr, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, pi->pi_name, LIFNAMSIZ); lifr.lifr_ifinfo.lir_reachtime = pi->pi_ReachableTime; if (ioctl(pi->pi_sock, SIOCSLIFLNKINFO, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_reach_random: SIOCSLIFLNKINFO"); return; } } pi->pi_reach_time_since_random = 0; } /* * Validate a temporary token against a list of known bad values. * Currently assumes that token is 8 bytes long! Current known * bad values include 0, reserved anycast tokens (RFC 2526), tokens * used by ISATAP (draft-ietf-ngtrans-isatap-N), any token already * assigned to this interface, or any token for which the global * bit is set. * * Called by tmptoken_create(). * * Return _B_TRUE if token is valid (no match), _B_FALSE if not. */ static boolean_t tmptoken_isvalid(struct in6_addr *token) { struct phyint *pi; struct in6_addr mask; struct in6_addr isatap = { 0, 0, 0, 0, 0, 0, 0, 0, \ 0, 0, 0x5e, 0xfe, 0, 0, 0, 0 }; struct in6_addr anycast = { 0, 0, 0, 0, \ 0, 0, 0, 0, \ 0xfd, 0xff, 0xff, 0xff, \ 0xff, 0xff, 0xff, 0x80 }; if (IN6_IS_ADDR_UNSPECIFIED(token)) return (_B_FALSE); if (token->s6_addr[8] & 0x2) return (_B_FALSE); (void) memcpy(&mask, token, sizeof (mask)); mask._S6_un._S6_u32[3] = 0; if (IN6_ARE_ADDR_EQUAL(&isatap, token)) return (_B_FALSE); mask._S6_un._S6_u32[3] = token->_S6_un._S6_u32[3] & 0xffffff80; if (IN6_ARE_ADDR_EQUAL(&anycast, token)) return (_B_FALSE); for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (((pi->pi_token_length == TMP_TOKEN_BITS) && IN6_ARE_ADDR_EQUAL(&pi->pi_token, token)) || IN6_ARE_ADDR_EQUAL(&pi->pi_tmp_token, token)) return (_B_FALSE); } /* none of our tests failed, must be a good one! */ return (_B_TRUE); } /* * Generate a temporary token and set up its timer * * Called from incoming_prefix_addrconf_process() (when token is first * needed) and from tmptoken_timer() (when current token expires). * * Returns _B_TRUE if a token was successfully generated, _B_FALSE if not. */ boolean_t tmptoken_create(struct phyint *pi) { int fd, i = 0, max_tries = 15; struct in6_addr token; uint32_t *tokenp = &(token._S6_un._S6_u32[2]); char buf[INET6_ADDRSTRLEN]; if ((fd = open("/dev/urandom", O_RDONLY)) == -1) { perror("open /dev/urandom"); goto no_token; } bzero((char *)&token, sizeof (token)); do { if (read(fd, (void *)tokenp, TMP_TOKEN_BYTES) == -1) { perror("read /dev/urandom"); (void) close(fd); goto no_token; } /* * Assume EUI-64 formatting, and thus 64-bit * token len; need to clear global bit. */ token.s6_addr[8] &= 0xfd; i++; } while (!tmptoken_isvalid(&token) && i < max_tries); (void) close(fd); if (i == max_tries) { no_token: logmsg(LOG_WARNING, "tmptoken_create(%s): failed to create " "token; disabling temporary addresses on %s\n", pi->pi_name, pi->pi_name); pi->pi_TmpAddrsEnabled = 0; return (_B_FALSE); } pi->pi_tmp_token = token; if (debug & D_TMP) logmsg(LOG_DEBUG, "tmptoken_create(%s): created temporary " "token %s\n", pi->pi_name, inet_ntop(AF_INET6, &pi->pi_tmp_token, buf, sizeof (buf))); pi->pi_TmpRegenCountdown = (pi->pi_TmpPreferredLifetime - pi->pi_TmpDesyncFactor - pi->pi_TmpRegenAdvance) * MILLISEC; if (pi->pi_TmpRegenCountdown != 0) timer_schedule(pi->pi_TmpRegenCountdown); return (_B_TRUE); } /* * Delete a temporary token. This is outside the normal timeout process, * so mark any existing addresses based on this token DEPRECATED and set * their preferred lifetime to 0. Don't tamper with valid lifetime, that * will be used to eventually remove the address. Also reset the current * pi_tmp_token value to 0. * * Called from incoming_prefix_addrconf_process() if DAD fails on a temp * addr. */ void tmptoken_delete(struct phyint *pi) { struct prefix *pr; for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if (!(pr->pr_flags & IFF_TEMPORARY) || (pr->pr_flags & IFF_DEPRECATED) || (!token_equal(pr->pr_address, pi->pi_tmp_token, TMP_TOKEN_BITS))) { continue; } pr->pr_PreferredLifetime = 0; pr->pr_state |= PR_DEPRECATED; prefix_update_k(pr); } (void) memset(&pi->pi_tmp_token, 0, sizeof (pi->pi_tmp_token)); } /* * Called from run_timeouts() with the number of milliseconds elapsed * since the last call. Determines if any timeout event has occurred * and returns the number of milliseconds until the next timeout event * for the tmp token. Returns TIMER_INFINITY for "never". */ uint_t tmptoken_timer(struct phyint *pi, uint_t elapsed) { struct nd_opt_prefix_info opt; struct sockaddr_in6 sin6; struct prefix *pr, *newpr; if (debug & D_TMP) { logmsg(LOG_DEBUG, "tmptoken_timer(%s, %d) regencountdown %d\n", pi->pi_name, (int)elapsed, pi->pi_TmpRegenCountdown); } if (!pi->pi_TmpAddrsEnabled || (pi->pi_TmpRegenCountdown == TIMER_INFINITY)) return (TIMER_INFINITY); if (pi->pi_TmpRegenCountdown > elapsed) { pi->pi_TmpRegenCountdown -= elapsed; return (pi->pi_TmpRegenCountdown); } /* * Tmp token timer has expired. Start by generating a new token. * If we can't get a new token, tmp addrs are disabled on this * interface, so there's no need to continue, or to set a timer. */ if (!tmptoken_create(pi)) return (TIMER_INFINITY); /* * Now that we have a new token, walk the list of prefixes to * find which ones need a corresponding tmp addr generated. */ for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if (!(pr->pr_state & PR_AUTO) || pr->pr_state & PR_STATIC || pr->pr_state & PR_DEPRECATED || pr->pr_flags & IFF_TEMPORARY) continue; newpr = prefix_create(pi, pr->pr_prefix, pr->pr_prefix_len, IFF_TEMPORARY); if (newpr == NULL) { char pbuf[INET6_ADDRSTRLEN]; char tbuf[INET6_ADDRSTRLEN]; (void) inet_ntop(AF_INET6, &pr->pr_prefix, pbuf, sizeof (pbuf)); (void) inet_ntop(AF_INET6, &pi->pi_tmp_token, tbuf, sizeof (tbuf)); logmsg(LOG_ERR, "can't create new tmp addr " "(%s, %s, %s)\n", pi->pi_name, pbuf, tbuf); continue; } /* * We want to use incoming_prefix_*_process() functions to * set up the new tmp addr, so cobble together a prefix * info option struct based on the existing prefix to pass * in. The lifetimes will be based on the current time * remaining. * * The "from" param is only used for messages; pass in * ::0 for that. */ opt.nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION; opt.nd_opt_pi_len = sizeof (opt) / 8; opt.nd_opt_pi_prefix_len = pr->pr_prefix_len; opt.nd_opt_pi_flags_reserved = ND_OPT_PI_FLAG_AUTO; opt.nd_opt_pi_valid_time = htonl(pr->pr_ValidLifetime / 1000); opt.nd_opt_pi_preferred_time = htonl(pr->pr_PreferredLifetime / 1000); if (pr->pr_state & PR_ONLINK) opt.nd_opt_pi_flags_reserved &= ND_OPT_PI_FLAG_ONLINK; opt.nd_opt_pi_prefix = pr->pr_prefix; (void) memset(&sin6, 0, sizeof (sin6)); if (!incoming_prefix_addrconf_process(pi, newpr, (uchar_t *)&opt, &sin6, _B_FALSE, _B_TRUE)) { char pbuf[INET6_ADDRSTRLEN]; char tbuf[INET6_ADDRSTRLEN]; (void) inet_ntop(AF_INET6, &pr->pr_prefix, pbuf, sizeof (pbuf)); (void) inet_ntop(AF_INET6, &pi->pi_tmp_token, tbuf, sizeof (tbuf)); logmsg(LOG_ERR, "can't create new tmp addr " "(%s, %s, %s)\n", pi->pi_name, pbuf, tbuf); continue; } if (pr->pr_state & PR_ONLINK) { incoming_prefix_onlink_process(newpr, (uchar_t *)&opt); } } /* * appropriate timers were scheduled when * the token and addresses were created. */ return (TIMER_INFINITY); } /* * tlen specifies the token length in bits. Compares the lower * tlen bits of the two addresses provided and returns _B_TRUE if * they match, _B_FALSE if not. Also returns _B_FALSE for invalid * values of tlen. */ boolean_t token_equal(struct in6_addr t1, struct in6_addr t2, int tlen) { uchar_t mask; int j, abytes, tbytes, tbits; if (tlen < 0 || tlen > IPV6_ABITS) return (_B_FALSE); abytes = IPV6_ABITS >> 3; tbytes = tlen >> 3; tbits = tlen & 7; for (j = abytes - 1; j >= abytes - tbytes; j--) if (t1.s6_addr[j] != t2.s6_addr[j]) return (_B_FALSE); if (tbits == 0) return (_B_TRUE); /* We only care about the tbits rightmost bits */ mask = 0xff >> (8 - tbits); if ((t1.s6_addr[j] & mask) != (t2.s6_addr[j] & mask)) return (_B_FALSE); return (_B_TRUE); } /* * Lookup prefix structure that matches the prefix and prefix length. * Assumes that the bits after prefixlen might not be zero. */ static struct prefix * prefix_lookup(struct phyint *pi, struct in6_addr prefix, int prefixlen) { struct prefix *pr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_lookup(%s, %s/%u)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&prefix, abuf, sizeof (abuf)), prefixlen); } for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if (pr->pr_prefix_len == prefixlen && prefix_equal(prefix, pr->pr_prefix, prefixlen)) return (pr); } return (NULL); } /* * Compare two prefixes that have the same prefix length. * Fails if the prefix length is unreasonable. */ boolean_t prefix_equal(struct in6_addr p1, struct in6_addr p2, int plen) { uchar_t mask; int j, pbytes, pbits; if (plen < 0 || plen > IPV6_ABITS) return (_B_FALSE); pbytes = plen >> 3; pbits = plen & 7; for (j = 0; j < pbytes; j++) if (p1.s6_addr[j] != p2.s6_addr[j]) return (_B_FALSE); if (pbits == 0) return (_B_TRUE); /* Make the N leftmost bits one */ mask = 0xff << (8 - pbits); if ((p1.s6_addr[j] & mask) != (p2.s6_addr[j] & mask)) return (_B_FALSE); return (_B_TRUE); } /* * Set a prefix from an address and a prefix length. * Force all the bits after the prefix length to be zero. */ void prefix_set(struct in6_addr *prefix, struct in6_addr addr, int prefix_len) { uchar_t mask; int j; if (prefix_len < 0 || prefix_len > IPV6_ABITS) return; bzero((char *)prefix, sizeof (*prefix)); for (j = 0; prefix_len > 8; prefix_len -= 8, j++) prefix->s6_addr[j] = addr.s6_addr[j]; /* Make the N leftmost bits one */ mask = 0xff << (8 - prefix_len); prefix->s6_addr[j] = addr.s6_addr[j] & mask; } /* * Lookup a prefix based on the kernel's interface name. */ struct prefix * prefix_lookup_name(struct phyint *pi, char *name) { struct prefix *pr; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_lookup_name(%s, %s)\n", pi->pi_name, name); } if (name[0] == '\0') return (NULL); for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if (strcmp(name, pr->pr_name) == 0) return (pr); } return (NULL); } /* * Search the phyints list to make sure that this new prefix does * not already exist in any other physical interfaces that have * the same address as this one */ struct prefix * prefix_lookup_addr_match(struct prefix *pr) { char abuf[INET6_ADDRSTRLEN]; struct phyint *pi; struct prefix *otherpr = NULL; struct in6_addr prefix; int prefixlen; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_lookup_addr_match(%s/%u)\n", inet_ntop(AF_INET6, (void *)&pr->pr_address, abuf, sizeof (abuf)), pr->pr_prefix_len); } prefix = pr->pr_prefix; prefixlen = pr->pr_prefix_len; for (pi = phyints; pi != NULL; pi = pi->pi_next) { otherpr = prefix_lookup(pi, prefix, prefixlen); if (otherpr == pr) continue; if (otherpr != NULL && (otherpr->pr_state & PR_AUTO) && IN6_ARE_ADDR_EQUAL(&pr->pr_address, &otherpr->pr_address)) return (otherpr); } return (NULL); } /* * Initialize a new prefix without setting lifetimes etc. */ struct prefix * prefix_create(struct phyint *pi, struct in6_addr prefix, int prefixlen, uint64_t flags) { struct prefix *pr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_create(%s, %s/%u, 0x%llx)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&prefix, abuf, sizeof (abuf)), prefixlen, flags); } pr = (struct prefix *)calloc(sizeof (struct prefix), 1); if (pr == NULL) { logmsg(LOG_ERR, "prefix_create: out of memory\n"); return (NULL); } /* * The prefix might have non-zero bits after the prefix len bits. * Force them to be zero. */ prefix_set(&pr->pr_prefix, prefix, prefixlen); pr->pr_prefix_len = prefixlen; pr->pr_PreferredLifetime = PREFIX_INFINITY; pr->pr_ValidLifetime = PREFIX_INFINITY; pr->pr_OnLinkLifetime = PREFIX_INFINITY; pr->pr_kernel_state = 0; pr->pr_flags |= flags; prefix_insert(pi, pr); return (pr); } /* * Create a new named prefix. Caller should use prefix_init_from_k * to initialize the content. */ struct prefix * prefix_create_name(struct phyint *pi, char *name) { struct prefix *pr; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_create_name(%s, %s)\n", pi->pi_name, name); } pr = (struct prefix *)calloc(sizeof (struct prefix), 1); if (pr == NULL) { logmsg(LOG_ERR, "prefix_create_name: out of memory\n"); return (NULL); } (void) strncpy(pr->pr_name, name, sizeof (pr->pr_name)); pr->pr_name[sizeof (pr->pr_name) - 1] = '\0'; prefix_insert(pi, pr); return (pr); } /* Insert in linked list */ static void prefix_insert(struct phyint *pi, struct prefix *pr) { pr->pr_next = pi->pi_prefix_list; pr->pr_prev = NULL; if (pi->pi_prefix_list != NULL) pi->pi_prefix_list->pr_prev = pr; pi->pi_prefix_list = pr; pr->pr_physical = pi; } /* * Initialize the prefix from the content of the kernel. * If IFF_ADDRCONF is set we treat it as PR_AUTO (i.e. an addrconf * prefix). However, we cannot derive the lifetime from * the kernel, thus it is set to 1 week. * Ignore the prefix if the interface is not IFF_UP. * If it's from DHCPv6, then we set the netmask. */ int prefix_init_from_k(struct prefix *pr) { struct lifreq lifr; struct sockaddr_in6 *sin6; int sock = pr->pr_physical->pi_sock; (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (ioctl(sock, SIOCGLIFADDR, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_init_from_k: ioctl (get addr)"); goto error; } if (lifr.lifr_addr.ss_family != AF_INET6) { logmsg(LOG_ERR, "ignoring interface %s: not AF_INET6\n", pr->pr_name); goto error; } sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; pr->pr_address = sin6->sin6_addr; if (ioctl(sock, SIOCGLIFFLAGS, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_init_from_k: ioctl (get flags)"); goto error; } pr->pr_flags = lifr.lifr_flags; /* * If this is a DHCPv6 interface, then we control the netmask. */ if (lifr.lifr_flags & IFF_DHCPRUNNING) { struct phyint *pi = pr->pr_physical; struct prefix *pr2; pr->pr_prefix_len = IPV6_ABITS; if (!(lifr.lifr_flags & IFF_UP) || IN6_IS_ADDR_UNSPECIFIED(&pr->pr_address) || IN6_IS_ADDR_LINKLOCAL(&pr->pr_address)) { if (debug & D_DHCP) logmsg(LOG_DEBUG, "prefix_init_from_k: " "ignoring DHCP %s not ready\n", pr->pr_name); return (0); } for (pr2 = pi->pi_prefix_list; pr2 != NULL; pr2 = pr2->pr_next) { /* * Examine any non-static (autoconfigured) prefixes as * well as existing DHCP-controlled prefixes for valid * prefix length information. */ if (pr2->pr_prefix_len != IPV6_ABITS && (!(pr2->pr_state & PR_STATIC) || (pr2->pr_flags & IFF_DHCPRUNNING)) && prefix_equal(pr->pr_prefix, pr2->pr_prefix, pr2->pr_prefix_len)) { pr->pr_prefix_len = pr2->pr_prefix_len; break; } } if (pr2 == NULL) { if (debug & D_DHCP) logmsg(LOG_DEBUG, "prefix_init_from_k: no " "saved mask for DHCP %s; need to " "resolicit\n", pr->pr_name); (void) check_to_solicit(pi, RESTART_INIT_SOLICIT); } else { if (debug & D_DHCP) logmsg(LOG_DEBUG, "prefix_init_from_k: using " "%s mask for DHCP %s\n", pr2->pr_name[0] == '\0' ? "saved" : pr2->pr_name, pr->pr_name); prefix_update_dhcp(pr); } /* * If this interface was created using ipadm, store the * addrobj for the DHCPv6 interface in ipmgmtd daemon's * in-memory aobjmap. */ prefix_update_ipadm_addrobj(pr, _B_TRUE); } else { if (ioctl(sock, SIOCGLIFSUBNET, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_init_from_k: ioctl (get subnet)"); goto error; } if (lifr.lifr_subnet.ss_family != AF_INET6) { logmsg(LOG_ERR, "ignoring interface %s: not AF_INET6\n", pr->pr_name); goto error; } /* * Guard against the prefix having non-zero bits after the * prefix len bits. */ sin6 = (struct sockaddr_in6 *)&lifr.lifr_subnet; pr->pr_prefix_len = lifr.lifr_addrlen; prefix_set(&pr->pr_prefix, sin6->sin6_addr, pr->pr_prefix_len); if (pr->pr_prefix_len != IPV6_ABITS && (pr->pr_flags & IFF_UP) && IN6_ARE_ADDR_EQUAL(&pr->pr_address, &pr->pr_prefix)) { char abuf[INET6_ADDRSTRLEN]; logmsg(LOG_ERR, "ignoring interface %s: it appears to " "be configured with an invalid interface id " "(%s/%u)\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_address, abuf, sizeof (abuf)), pr->pr_prefix_len); goto error; } } pr->pr_kernel_state = 0; if (pr->pr_prefix_len != IPV6_ABITS) pr->pr_kernel_state |= PR_ONLINK; if (!(pr->pr_flags & (IFF_NOLOCAL | IFF_DHCPRUNNING))) pr->pr_kernel_state |= PR_AUTO; if ((pr->pr_flags & IFF_DEPRECATED) && (pr->pr_kernel_state & PR_AUTO)) pr->pr_kernel_state |= PR_DEPRECATED; if (!(pr->pr_flags & IFF_ADDRCONF)) { /* Prevent ndpd from stepping on this prefix */ pr->pr_kernel_state |= PR_STATIC; } pr->pr_state = pr->pr_kernel_state; /* Adjust pr_prefix_len based if PR_AUTO is set */ if (pr->pr_state & PR_AUTO) { pr->pr_prefix_len = IPV6_ABITS - pr->pr_physical->pi_token_length; prefix_set(&pr->pr_prefix, pr->pr_prefix, pr->pr_prefix_len); } /* Can't extract lifetimes from the kernel - use 1 week */ pr->pr_ValidLifetime = NDP_PREFIX_DEFAULT_LIFETIME; pr->pr_PreferredLifetime = NDP_PREFIX_DEFAULT_LIFETIME; pr->pr_OnLinkLifetime = NDP_PREFIX_DEFAULT_LIFETIME; /* * If this is a temp addr, the creation time needs to be set. * Though it won't be entirely accurate, the current time is * an okay approximation. */ if (pr->pr_flags & IFF_TEMPORARY) pr->pr_CreateTime = getcurrenttime() / MILLISEC; if (pr->pr_kernel_state == 0) pr->pr_name[0] = '\0'; return (0); error: /* Pretend that the prefix does not exist in the kernel */ pr->pr_kernel_state = 0; pr->pr_name[0] = '\0'; return (-1); } /* * Delete (unlink and free) and remove from kernel if the prefix * was added by in.ndpd (i.e. PR_STATIC is not set). * Handles delete of things that have not yet been inserted in the list * i.e. pr_physical is NULL. * Removes the ipadm addrobj created for the prefix. */ void prefix_delete(struct prefix *pr) { struct phyint *pi; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_delete(%s, %s, %s/%u)\n", pr->pr_physical->pi_name, pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len); } pi = pr->pr_physical; /* Remove non-static prefixes from the kernel. */ pr->pr_state &= PR_STATIC; if (pr->pr_kernel_state != pr->pr_state) prefix_update_k(pr); if (pr->pr_prev == NULL) { if (pi != NULL) pi->pi_prefix_list = pr->pr_next; } else { pr->pr_prev->pr_next = pr->pr_next; } if (pr->pr_next != NULL) pr->pr_next->pr_prev = pr->pr_prev; pr->pr_next = pr->pr_prev = NULL; free(pr); } /* * Toggle one or more IFF_ flags for a prefix. Turn on 'onflags' and * turn off 'offflags'. */ static int prefix_modify_flags(struct prefix *pr, uint64_t onflags, uint64_t offflags) { struct lifreq lifr; struct phyint *pi = pr->pr_physical; uint64_t old_flags; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_modify_flags(%s, %s, %s/%u) " "flags %llx on %llx off %llx\n", pr->pr_physical->pi_name, pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, pr->pr_flags, onflags, offflags); } /* Assumes that only the PR_STATIC link-local matches the pi_name */ if (!(pr->pr_state & PR_STATIC) && strcmp(pr->pr_name, pi->pi_name) == 0) { logmsg(LOG_ERR, "prefix_modify_flags(%s, on %llx, off %llx): " "name matches interface name\n", pi->pi_name, onflags, offflags); return (-1); } (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (ioctl(pi->pi_sock, SIOCGLIFFLAGS, (char *)&lifr) < 0) { if (errno != ENXIO) { logperror_pr(pr, "prefix_modify_flags: SIOCGLIFFLAGS"); logmsg(LOG_ERR, "prefix_modify_flags(%s, %s) old 0x%llx" " on 0x%llx off 0x%llx\n", pr->pr_physical->pi_name, pr->pr_name, pr->pr_flags, onflags, offflags); } return (-1); } old_flags = lifr.lifr_flags; lifr.lifr_flags |= onflags; lifr.lifr_flags &= ~offflags; pr->pr_flags = lifr.lifr_flags; if (ioctl(pi->pi_sock, SIOCSLIFFLAGS, (char *)&lifr) < 0) { if (errno != ENXIO) { logperror_pr(pr, "prefix_modify_flags: SIOCSLIFFLAGS"); logmsg(LOG_ERR, "prefix_modify_flags(%s, %s) old 0x%llx" " new 0x%llx on 0x%llx off 0x%llx\n", pr->pr_physical->pi_name, pr->pr_name, old_flags, lifr.lifr_flags, onflags, offflags); } return (-1); } return (0); } /* * Update the subnet mask for this interface under DHCPv6 control. */ void prefix_update_dhcp(struct prefix *pr) { struct lifreq lifr; (void) memset(&lifr, 0, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_addr.ss_family = AF_INET6; prefix_set(&((struct sockaddr_in6 *)&lifr.lifr_addr)->sin6_addr, pr->pr_address, pr->pr_prefix_len); lifr.lifr_addrlen = pr->pr_prefix_len; /* * Ignore ENXIO, as the dhcpagent process is responsible for plumbing * and unplumbing these. */ if (ioctl(pr->pr_physical->pi_sock, SIOCSLIFSUBNET, (char *)&lifr) == -1 && errno != ENXIO) logperror_pr(pr, "prefix_update_dhcp: ioctl (set subnet)"); } /* * Make the kernel state match what is in the prefix structure. * This includes creating the prefix (allocating a new interface name) * as well as setting the local address and on-link subnet prefix * and controlling the IFF_ADDRCONF and IFF_DEPRECATED flags. */ void prefix_update_k(struct prefix *pr) { struct lifreq lifr; char abuf[INET6_ADDRSTRLEN]; char buf1[PREFIX_STATESTRLEN], buf2[PREFIX_STATESTRLEN]; struct phyint *pi = pr->pr_physical; struct sockaddr_in6 *sin6; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s, %s, %s/%u) " "from %s to %s\n", pr->pr_physical->pi_name, pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, prefix_print_state(pr->pr_kernel_state, buf1, sizeof (buf1)), prefix_print_state(pr->pr_state, buf2, sizeof (buf2))); } if (pr->pr_kernel_state == pr->pr_state) return; /* No changes */ /* Skip static prefixes */ if (pr->pr_state & PR_STATIC) return; if (pr->pr_kernel_state == 0) { uint64_t onflags; /* * Create a new logical interface name and store in pr_name. * Set IFF_ADDRCONF. Do not set an address (yet). */ if (pr->pr_name[0] != '\0') { /* Name already set! */ logmsg(LOG_ERR, "prefix_update_k(%s, %s, %s/%u) " "from %s to %s name is already allocated\n", pr->pr_physical->pi_name, pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, prefix_print_state(pr->pr_kernel_state, buf1, sizeof (buf1)), prefix_print_state(pr->pr_state, buf2, sizeof (buf2))); return; } (void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; lifr.lifr_addr.ss_family = AF_UNSPEC; if (ioctl(pi->pi_sock, SIOCLIFADDIF, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCLIFADDIF"); return; } (void) strncpy(pr->pr_name, lifr.lifr_name, sizeof (pr->pr_name)); pr->pr_name[sizeof (pr->pr_name) - 1] = '\0'; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k: new name %s\n", pr->pr_name); } /* * The IFF_TEMPORARY flag might have already been set; if * so, it needs to be or'd into the flags we're turning on. * But be careful, we might be re-creating a manually * removed interface, in which case we don't want to try * to set *all* the flags we might have in our copy of the * flags yet. */ onflags = IFF_ADDRCONF; if (pr->pr_flags & IFF_TEMPORARY) onflags |= IFF_TEMPORARY; if (prefix_modify_flags(pr, onflags, 0) == -1) return; } if ((pr->pr_state & (PR_ONLINK|PR_AUTO)) == 0) { /* Remove the interface */ if (prefix_modify_flags(pr, 0, IFF_UP|IFF_DEPRECATED) == -1) return; (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k: remove name %s\n", pr->pr_name); } /* * Assumes that only the PR_STATIC link-local matches * the pi_name */ if (!(pr->pr_state & PR_STATIC) && strcmp(pr->pr_name, pi->pi_name) == 0) { logmsg(LOG_ERR, "prefix_update_k(%s): " "name matches if\n", pi->pi_name); return; } /* Remove logical interface based on pr_name */ lifr.lifr_addr.ss_family = AF_UNSPEC; if (ioctl(pi->pi_sock, SIOCLIFREMOVEIF, (char *)&lifr) < 0 && errno != ENXIO) { logperror_pr(pr, "prefix_update_k: SIOCLIFREMOVEIF"); } pr->pr_kernel_state = 0; pr->pr_name[0] = '\0'; return; } if ((pr->pr_state & PR_AUTO) && !(pr->pr_kernel_state & PR_AUTO)) { /* * Set local address and set the prefix length to 128. * Turn off IFF_NOLOCAL in case it was set. * Turn on IFF_UP. */ (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; bzero(sin6, sizeof (struct sockaddr_in6)); sin6->sin6_family = AF_INET6; sin6->sin6_addr = pr->pr_address; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) set addr %s " "for PR_AUTO on\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_address, abuf, sizeof (abuf))); } if (ioctl(pi->pi_sock, SIOCSLIFADDR, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFADDR"); return; } /* * If this interface was created using ipadm, store the * addrobj for the prefix in ipmgmtd daemon's aobjmap. */ prefix_update_ipadm_addrobj(pr, _B_TRUE); if (pr->pr_state & PR_ONLINK) { sin6->sin6_addr = pr->pr_prefix; lifr.lifr_addrlen = pr->pr_prefix_len; } else { sin6->sin6_addr = pr->pr_address; lifr.lifr_addrlen = IPV6_ABITS; } if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) set subnet " "%s/%u for PR_AUTO on\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, abuf, sizeof (abuf)), lifr.lifr_addrlen); } if (ioctl(pi->pi_sock, SIOCSLIFSUBNET, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFSUBNET"); return; } /* * For ptp interfaces, create a destination based on * prefix and prefix len together with the remote token * extracted from the remote pt-pt address. This is used by * ip to choose a proper source for outgoing packets. */ if (pi->pi_flags & IFF_POINTOPOINT) { int i; sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; bzero(sin6, sizeof (struct sockaddr_in6)); sin6->sin6_family = AF_INET6; sin6->sin6_addr = pr->pr_prefix; for (i = 0; i < 16; i++) { sin6->sin6_addr.s6_addr[i] |= pi->pi_dst_token.s6_addr[i]; } if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) " "set dstaddr %s for PR_AUTO on\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, abuf, sizeof (abuf))); } if (ioctl(pi->pi_sock, SIOCSLIFDSTADDR, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFDSTADDR"); return; } } if (prefix_modify_flags(pr, IFF_UP, IFF_NOLOCAL) == -1) return; pr->pr_kernel_state |= PR_AUTO; if (pr->pr_state & PR_ONLINK) pr->pr_kernel_state |= PR_ONLINK; else pr->pr_kernel_state &= ~PR_ONLINK; } if (!(pr->pr_state & PR_AUTO) && (pr->pr_kernel_state & PR_AUTO)) { /* Turn on IFF_NOLOCAL and set the local address to all zero */ if (prefix_modify_flags(pr, IFF_NOLOCAL, 0) == -1) return; (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; bzero(sin6, sizeof (struct sockaddr_in6)); sin6->sin6_family = AF_INET6; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) set addr %s " "for PR_AUTO off\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, abuf, sizeof (abuf))); } if (ioctl(pi->pi_sock, SIOCSLIFADDR, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFADDR"); return; } pr->pr_kernel_state &= ~PR_AUTO; } if ((pr->pr_state & PR_DEPRECATED) && !(pr->pr_kernel_state & PR_DEPRECATED) && (pr->pr_kernel_state & PR_AUTO)) { /* Only applies if PR_AUTO */ if (prefix_modify_flags(pr, IFF_DEPRECATED, 0) == -1) return; pr->pr_kernel_state |= PR_DEPRECATED; } if (!(pr->pr_state & PR_DEPRECATED) && (pr->pr_kernel_state & PR_DEPRECATED)) { if (prefix_modify_flags(pr, 0, IFF_DEPRECATED) == -1) return; pr->pr_kernel_state &= ~PR_DEPRECATED; } if ((pr->pr_state & PR_ONLINK) && !(pr->pr_kernel_state & PR_ONLINK)) { /* Set the subnet and set IFF_UP */ (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; bzero(sin6, sizeof (struct sockaddr_in6)); sin6->sin6_family = AF_INET6; sin6->sin6_addr = pr->pr_prefix; lifr.lifr_addrlen = pr->pr_prefix_len; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) set subnet " "%s/%d for PR_ONLINK on\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, abuf, sizeof (abuf)), lifr.lifr_addrlen); } if (ioctl(pi->pi_sock, SIOCSLIFSUBNET, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFSUBNET"); return; } /* * If we've previously marked the interface "up" while * processing the PR_AUTO flag -- via incoming_prefix_addrconf * -- then there's no need to set it "up" again. We're done; * just set PR_ONLINK to indicate that we've set the subnet. */ if (!(pr->pr_state & PR_AUTO) && prefix_modify_flags(pr, IFF_UP | IFF_NOLOCAL, 0) == -1) return; pr->pr_kernel_state |= PR_ONLINK; } if (!(pr->pr_state & PR_ONLINK) && (pr->pr_kernel_state & PR_ONLINK)) { /* Set the prefixlen to 128 */ (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; bzero(sin6, sizeof (struct sockaddr_in6)); sin6->sin6_family = AF_INET6; sin6->sin6_addr = pr->pr_address; lifr.lifr_addrlen = IPV6_ABITS; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) set subnet " "%s/%d for PR_ONLINK off\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, abuf, sizeof (abuf)), lifr.lifr_addrlen); } if (ioctl(pi->pi_sock, SIOCSLIFSUBNET, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFSUBNET"); return; } pr->pr_kernel_state &= ~PR_ONLINK; } } /* * Called with the number of millseconds elapsed since the last call. * Determines if any timeout event has occurred and * returns the number of milliseconds until the next timeout event. * Returns TIMER_INFINITY for "never". */ uint_t prefix_timer(struct prefix *pr, uint_t elapsed) { uint_t next = TIMER_INFINITY; char abuf[INET6_ADDRSTRLEN]; if (debug & (D_PREFIX|D_TMP)) { logmsg(LOG_DEBUG, "prefix_timer(%s, %s/%u, %d) " "valid %d pref %d onlink %d\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, elapsed, pr->pr_ValidLifetime, pr->pr_PreferredLifetime, pr->pr_OnLinkLifetime); } /* Exclude static prefixes */ if (pr->pr_state & PR_STATIC) return (next); if (pr->pr_AutonomousFlag && (pr->pr_PreferredLifetime != PREFIX_INFINITY)) { if (pr->pr_PreferredLifetime <= elapsed) { pr->pr_PreferredLifetime = 0; } else { pr->pr_PreferredLifetime -= elapsed; if (pr->pr_PreferredLifetime < next) next = pr->pr_PreferredLifetime; } } if (pr->pr_AutonomousFlag && (pr->pr_ValidLifetime != PREFIX_INFINITY)) { if (pr->pr_ValidLifetime <= elapsed) { pr->pr_ValidLifetime = 0; } else { pr->pr_ValidLifetime -= elapsed; if (pr->pr_ValidLifetime < next) next = pr->pr_ValidLifetime; } } if (pr->pr_OnLinkFlag && (pr->pr_OnLinkLifetime != PREFIX_INFINITY)) { if (pr->pr_OnLinkLifetime <= elapsed) { pr->pr_OnLinkLifetime = 0; } else { pr->pr_OnLinkLifetime -= elapsed; if (pr->pr_OnLinkLifetime < next) next = pr->pr_OnLinkLifetime; } } if (pr->pr_AutonomousFlag && pr->pr_ValidLifetime == 0) pr->pr_state &= ~(PR_AUTO|PR_DEPRECATED); if (pr->pr_AutonomousFlag && pr->pr_PreferredLifetime == 0 && (pr->pr_state & PR_AUTO)) { pr->pr_state |= PR_DEPRECATED; if (debug & D_TMP) logmsg(LOG_WARNING, "prefix_timer: deprecated " "prefix(%s)\n", pr->pr_name); } if (pr->pr_OnLinkFlag && pr->pr_OnLinkLifetime == 0) pr->pr_state &= ~PR_ONLINK; if (pr->pr_state != pr->pr_kernel_state) { /* Might cause prefix to be deleted! */ /* Log a message when an addrconf prefix goes away */ if ((pr->pr_kernel_state & PR_AUTO) && !(pr->pr_state & PR_AUTO)) { char abuf[INET6_ADDRSTRLEN]; logmsg(LOG_WARNING, "Address removed due to timeout %s\n", inet_ntop(AF_INET6, (void *)&pr->pr_address, abuf, sizeof (abuf))); } prefix_update_k(pr); } return (next); } static char * prefix_print_state(int state, char *buf, int buflen) { char *cp; int cplen = buflen; cp = buf; cp[0] = '\0'; if (state & PR_ONLINK) { if (strlcat(cp, "ONLINK ", cplen) >= cplen) return (buf); cp += strlen(cp); cplen = buflen - (cp - buf); } if (state & PR_AUTO) { if (strlcat(cp, "AUTO ", cplen) >= cplen) return (buf); cp += strlen(cp); cplen = buflen - (cp - buf); } if (state & PR_DEPRECATED) { if (strlcat(cp, "DEPRECATED ", cplen) >= cplen) return (buf); cp += strlen(cp); cplen = buflen - (cp - buf); } if (state & PR_STATIC) { if (strlcat(cp, "STATIC ", cplen) >= cplen) return (buf); cp += strlen(cp); cplen = buflen - (cp - buf); } return (buf); } static void prefix_print(struct prefix *pr) { char abuf[INET6_ADDRSTRLEN]; char buf1[PREFIX_STATESTRLEN], buf2[PREFIX_STATESTRLEN]; logmsg(LOG_DEBUG, "Prefix name: %s prefix %s/%u state %s " "kernel_state %s\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, prefix_print_state(pr->pr_state, buf2, sizeof (buf2)), prefix_print_state(pr->pr_kernel_state, buf1, sizeof (buf1))); logmsg(LOG_DEBUG, "\tAddress: %s flags %llx in_use %d\n", inet_ntop(AF_INET6, (void *)&pr->pr_address, abuf, sizeof (abuf)), pr->pr_flags, pr->pr_in_use); logmsg(LOG_DEBUG, "\tValidLifetime %u PreferredLifetime %u " "OnLinkLifetime %u\n", pr->pr_ValidLifetime, pr->pr_PreferredLifetime, pr->pr_OnLinkLifetime); logmsg(LOG_DEBUG, "\tOnLink %d Auto %d\n", pr->pr_OnLinkFlag, pr->pr_AutonomousFlag); logmsg(LOG_DEBUG, "\n"); } /* * Lookup advertisement prefix structure that matches the prefix and * prefix length. * Assumes that the bits after prefixlen might not be zero. */ struct adv_prefix * adv_prefix_lookup(struct phyint *pi, struct in6_addr prefix, int prefixlen) { struct adv_prefix *adv_pr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "adv_prefix_lookup(%s, %s/%u)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&prefix, abuf, sizeof (abuf)), prefixlen); } for (adv_pr = pi->pi_adv_prefix_list; adv_pr != NULL; adv_pr = adv_pr->adv_pr_next) { if (adv_pr->adv_pr_prefix_len == prefixlen && prefix_equal(prefix, adv_pr->adv_pr_prefix, prefixlen)) return (adv_pr); } return (NULL); } /* * Initialize a new advertisement prefix. */ struct adv_prefix * adv_prefix_create(struct phyint *pi, struct in6_addr prefix, int prefixlen) { struct adv_prefix *adv_pr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "adv_prefix_create(%s, %s/%u)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&prefix, abuf, sizeof (abuf)), prefixlen); } adv_pr = (struct adv_prefix *)calloc(sizeof (struct adv_prefix), 1); if (adv_pr == NULL) { logmsg(LOG_ERR, "adv_prefix_create: calloc\n"); return (NULL); } /* * The prefix might have non-zero bits after the prefix len bits. * Force them to be zero. */ prefix_set(&adv_pr->adv_pr_prefix, prefix, prefixlen); adv_pr->adv_pr_prefix_len = prefixlen; adv_prefix_insert(pi, adv_pr); return (adv_pr); } /* Insert in linked list */ static void adv_prefix_insert(struct phyint *pi, struct adv_prefix *adv_pr) { adv_pr->adv_pr_next = pi->pi_adv_prefix_list; adv_pr->adv_pr_prev = NULL; if (pi->pi_adv_prefix_list != NULL) pi->pi_adv_prefix_list->adv_pr_prev = adv_pr; pi->pi_adv_prefix_list = adv_pr; adv_pr->adv_pr_physical = pi; } /* * Delete (unlink and free) from our tables. There should be * a corresponding "struct prefix *" which will clean up the kernel * if necessary. adv_prefix is just used for sending out advertisements. */ static void adv_prefix_delete(struct adv_prefix *adv_pr) { struct phyint *pi; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "adv_prefix_delete(%s, %s/%u)\n", adv_pr->adv_pr_physical->pi_name, inet_ntop(AF_INET6, (void *)&adv_pr->adv_pr_prefix, abuf, sizeof (abuf)), adv_pr->adv_pr_prefix_len); } pi = adv_pr->adv_pr_physical; if (adv_pr->adv_pr_prev == NULL) { if (pi != NULL) pi->pi_adv_prefix_list = adv_pr->adv_pr_next; } else { adv_pr->adv_pr_prev->adv_pr_next = adv_pr->adv_pr_next; } if (adv_pr->adv_pr_next != NULL) adv_pr->adv_pr_next->adv_pr_prev = adv_pr->adv_pr_prev; adv_pr->adv_pr_next = adv_pr->adv_pr_prev = NULL; free(adv_pr); } /* * Called with the number of millseconds elapsed since the last call. * Determines if any timeout event has occurred and * returns the number of milliseconds until the next timeout event. * Returns TIMER_INFINITY for "never". */ uint_t adv_prefix_timer(struct adv_prefix *adv_pr, uint_t elapsed) { int seconds_elapsed = (elapsed + 500) / 1000; /* Rounded */ char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "adv_prefix_timer(%s, %s/%u, %d)\n", adv_pr->adv_pr_physical->pi_name, inet_ntop(AF_INET6, (void *)&adv_pr->adv_pr_prefix, abuf, sizeof (abuf)), adv_pr->adv_pr_prefix_len, elapsed); } /* Decrement Expire time left for real-time lifetimes */ if (adv_pr->adv_pr_AdvValidRealTime) { if (adv_pr->adv_pr_AdvValidExpiration > seconds_elapsed) adv_pr->adv_pr_AdvValidExpiration -= seconds_elapsed; else adv_pr->adv_pr_AdvValidExpiration = 0; } if (adv_pr->adv_pr_AdvPreferredRealTime) { if (adv_pr->adv_pr_AdvPreferredExpiration > seconds_elapsed) { adv_pr->adv_pr_AdvPreferredExpiration -= seconds_elapsed; } else { adv_pr->adv_pr_AdvPreferredExpiration = 0; } } return (TIMER_INFINITY); } static void adv_prefix_print(struct adv_prefix *adv_pr) { print_prefixlist(adv_pr->adv_pr_config); } /* Lookup router on its link-local IPv6 address */ struct router * router_lookup(struct phyint *pi, struct in6_addr addr) { struct router *dr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_lookup(%s, %s)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&addr, abuf, sizeof (abuf))); } for (dr = pi->pi_router_list; dr != NULL; dr = dr->dr_next) { if (bcmp((char *)&addr, (char *)&dr->dr_address, sizeof (addr)) == 0) return (dr); } return (NULL); } /* * Create a default router entry. * The lifetime parameter is in seconds. */ struct router * router_create(struct phyint *pi, struct in6_addr addr, uint_t lifetime) { struct router *dr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_create(%s, %s, %u)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&addr, abuf, sizeof (abuf)), lifetime); } dr = (struct router *)calloc(sizeof (struct router), 1); if (dr == NULL) { logmsg(LOG_ERR, "router_create: out of memory\n"); return (NULL); } dr->dr_address = addr; dr->dr_lifetime = lifetime; router_insert(pi, dr); if (dr->dr_lifetime != 0) router_add_k(dr); return (dr); } /* Insert in linked list */ static void router_insert(struct phyint *pi, struct router *dr) { dr->dr_next = pi->pi_router_list; dr->dr_prev = NULL; if (pi->pi_router_list != NULL) pi->pi_router_list->dr_prev = dr; pi->pi_router_list = dr; dr->dr_physical = pi; } /* * Delete (unlink and free). * Handles delete of things that have not yet been inserted in the list * i.e. dr_physical is NULL. */ static void router_delete(struct router *dr) { struct phyint *pi; char abuf[INET6_ADDRSTRLEN]; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_delete(%s, %s, %u)\n", dr->dr_physical->pi_name, inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_lifetime); } pi = dr->dr_physical; if (dr->dr_inkernel && (pi->pi_kernel_state & PI_PRESENT)) router_delete_k(dr); if (dr->dr_prev == NULL) { if (pi != NULL) pi->pi_router_list = dr->dr_next; } else { dr->dr_prev->dr_next = dr->dr_next; } if (dr->dr_next != NULL) dr->dr_next->dr_prev = dr->dr_prev; dr->dr_next = dr->dr_prev = NULL; free(dr); } /* * Update the kernel to match dr_lifetime */ void router_update_k(struct router *dr) { char abuf[INET6_ADDRSTRLEN]; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_update_k(%s, %s, %u)\n", dr->dr_physical->pi_name, inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_lifetime); } if (dr->dr_lifetime == 0 && dr->dr_inkernel) { /* Log a message when last router goes away */ if (dr->dr_physical->pi_num_k_routers == 1) { logmsg(LOG_WARNING, "Last default router (%s) removed on %s\n", inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_physical->pi_name); } router_delete(dr); } else if (dr->dr_lifetime != 0 && !dr->dr_inkernel) router_add_k(dr); } /* * Called with the number of millseconds elapsed since the last call. * Determines if any timeout event has occurred and * returns the number of milliseconds until the next timeout event. * Returns TIMER_INFINITY for "never". */ uint_t router_timer(struct router *dr, uint_t elapsed) { uint_t next = TIMER_INFINITY; char abuf[INET6_ADDRSTRLEN]; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_timer(%s, %s, %u, %d)\n", dr->dr_physical->pi_name, inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_lifetime, elapsed); } if (dr->dr_lifetime <= elapsed) { dr->dr_lifetime = 0; } else { dr->dr_lifetime -= elapsed; if (dr->dr_lifetime < next) next = dr->dr_lifetime; } if (dr->dr_lifetime == 0) { /* Log a message when last router goes away */ if (dr->dr_physical->pi_num_k_routers == 1) { logmsg(LOG_WARNING, "Last default router (%s) timed out on %s\n", inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_physical->pi_name); } router_delete(dr); } return (next); } /* * Add a default route to the kernel (unless the lifetime is zero) * Handles onlink default routes. */ static void router_add_k(struct router *dr) { struct phyint *pi = dr->dr_physical; char abuf[INET6_ADDRSTRLEN]; int rlen; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_add_k(%s, %s, %u)\n", dr->dr_physical->pi_name, inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_lifetime); } rta_gateway->sin6_addr = dr->dr_address; rta_ifp->sdl_index = if_nametoindex(pi->pi_name); if (rta_ifp->sdl_index == 0) { logperror_pi(pi, "router_add_k: if_nametoindex"); return; } rt_msg->rtm_flags = RTF_GATEWAY; rt_msg->rtm_type = RTM_ADD; rt_msg->rtm_seq = ++rtmseq; rlen = write(rtsock, rt_msg, rt_msg->rtm_msglen); if (rlen < 0) { if (errno != EEXIST) { logperror_pi(pi, "router_add_k: RTM_ADD"); return; } } else if (rlen < rt_msg->rtm_msglen) { logmsg(LOG_ERR, "router_add_k: write to routing socket got " "only %d for rlen (interface %s)\n", rlen, pi->pi_name); return; } dr->dr_inkernel = _B_TRUE; pi->pi_num_k_routers++; } /* * Delete a route from the kernel. * Handles onlink default routes. */ static void router_delete_k(struct router *dr) { struct phyint *pi = dr->dr_physical; char abuf[INET6_ADDRSTRLEN]; int rlen; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_delete_k(%s, %s, %u)\n", dr->dr_physical->pi_name, inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_lifetime); } rta_gateway->sin6_addr = dr->dr_address; rta_ifp->sdl_index = if_nametoindex(pi->pi_name); if (rta_ifp->sdl_index == 0) { logperror_pi(pi, "router_delete_k: if_nametoindex"); return; } rt_msg->rtm_flags = RTF_GATEWAY; rt_msg->rtm_type = RTM_DELETE; rt_msg->rtm_seq = ++rtmseq; rlen = write(rtsock, rt_msg, rt_msg->rtm_msglen); if (rlen < 0) { if (errno != ESRCH) { logperror_pi(pi, "router_delete_k: RTM_DELETE"); } } else if (rlen < rt_msg->rtm_msglen) { logmsg(LOG_ERR, "router_delete_k: write to routing socket got " "only %d for rlen (interface %s)\n", rlen, pi->pi_name); } dr->dr_inkernel = _B_FALSE; pi->pi_num_k_routers--; } static void router_print(struct router *dr) { char abuf[INET6_ADDRSTRLEN]; logmsg(LOG_DEBUG, "Router %s on %s inkernel %d lifetime %u\n", inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_physical->pi_name, dr->dr_inkernel, dr->dr_lifetime); } void phyint_print_all(void) { struct phyint *pi; for (pi = phyints; pi != NULL; pi = pi->pi_next) { phyint_print(pi); } } void phyint_cleanup(struct phyint *pi) { pi->pi_state = 0; pi->pi_kernel_state = 0; if (pi->pi_AdvSendAdvertisements) { check_to_advertise(pi, ADV_OFF); } else { check_to_solicit(pi, SOLICIT_OFF); } while (pi->pi_router_list) router_delete(pi->pi_router_list); (void) poll_remove(pi->pi_sock); (void) close(pi->pi_sock); pi->pi_sock = -1; pi->pi_stateless = pi->pi_StatelessAddrConf; pi->pi_stateful = pi->pi_StatefulAddrConf; pi->pi_ipadm_aobjname[0] = '\0'; } /* * Sets/removes the ipadm address object name for the given prefix. */ void prefix_update_ipadm_addrobj(struct prefix *pr, boolean_t add) { struct phyint *pi = pr->pr_physical; int lnum = 0; char *cp; ipadm_handle_t iph; ipadm_status_t status; /* * If ipadm was used to autoconfigure this interface, * pi_ipadm_aobjname will contain the address object name * that is used to identify the addresses. Use the same * address object name for this prefix. */ if (pi->pi_ipadm_aobjname[0] == '\0' || pr->pr_name[0] == '\0' || IN6_IS_ADDR_LINKLOCAL(&pr->pr_address) || (!(pr->pr_flags & IFF_ADDRCONF) && !(pr->pr_flags & IFF_DHCPRUNNING))) { return; } if ((status = ipadm_open(&iph, 0)) != IPADM_SUCCESS) { logmsg(LOG_ERR, "Could not open handle to libipadm: %s\n", ipadm_status2str(status)); return; } cp = strrchr(pr->pr_name, ':'); if (cp != NULL) lnum = atoi(++cp); if (add) { status = ipadm_add_aobjname(iph, pi->pi_name, AF_INET6, pi->pi_ipadm_aobjname, IPADM_ADDR_IPV6_ADDRCONF, lnum); } else { status = ipadm_delete_aobjname(iph, pi->pi_name, AF_INET6, pi->pi_ipadm_aobjname, IPADM_ADDR_IPV6_ADDRCONF, lnum); } /* Ignore the error if the ipmgmtd daemon is not running */ if (status != IPADM_SUCCESS && status != IPADM_IPC_ERROR) { logmsg(LOG_ERR, "ipadm error in %s '%s' : %s\n", (add ? "adding" : "deleting"), pi->pi_ipadm_aobjname, ipadm_status2str(status)); } ipadm_close(iph); }