root/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/tables.c
/*
 * 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.
 *
 * Copyright 2024 Oxide Computer Company
 */

#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;
        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';
        pi->pi_ifaddr = in6addr_any;
}

/*
 * 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);
}