| /* |
| * 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 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| #include "defs.h" |
| #include "tables.h" |
| |
| #include <time.h> |
| #include <assert.h> |
| |
| 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; |
| 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 (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_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); |
| } |
| } 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. |
| */ |
| 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); |
| } |
| /* Remove non-static prefixes from the kernel. */ |
| pr->pr_state &= PR_STATIC; |
| pi = pr->pr_physical; |
| 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 (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; |
| } |