view usr/src/cmd/cmd-inet/usr.lib/in.ndpd/ndp.c @ 0:c9caec207d52 b86

Initial porting based on b86
author Koji Uno <koji.uno@sun.com>
date Tue, 02 Jun 2009 18:56:50 +0900
parents
children 1a15d5aaf794
line wrap: on
line source

/*
 * 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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"@(#)ndp.c	1.22	07/01/16 SMI"

#include "defs.h"
#include "tables.h"

#include <sys/sysmacros.h>

#include <dhcpagent_ipc.h>
#include <dhcpagent_util.h>

static boolean_t verify_opt_len(struct nd_opt_hdr *opt, int optlen,
		    struct phyint *pi, struct sockaddr_in6 *from);

static void	incoming_rs(struct phyint *pi, struct nd_router_solicit *rs,
		    int len, struct sockaddr_in6 *from);

void		incoming_ra(struct phyint *pi, struct nd_router_advert *ra,
		    int len, struct sockaddr_in6 *from, boolean_t loopback);
static void	incoming_prefix_opt(struct phyint *pi, uchar_t *opt,
		    struct sockaddr_in6 *from, boolean_t loopback);
static void	incoming_prefix_onlink(struct phyint *pi, uchar_t *opt);
void		incoming_prefix_onlink_process(struct prefix *pr,
		    uchar_t *opt);
static void	incoming_prefix_stateful(struct phyint *, uchar_t *);
static boolean_t	incoming_prefix_addrconf(struct phyint *pi,
		    uchar_t *opt, struct sockaddr_in6 *from,
		    boolean_t loopback);
boolean_t	incoming_prefix_addrconf_process(struct phyint *pi,
		    struct prefix *pr, uchar_t *opt,
		    struct sockaddr_in6 *from, boolean_t loopback,
		    boolean_t new_prefix);
static void	incoming_mtu_opt(struct phyint *pi, uchar_t *opt,
		    struct sockaddr_in6 *from);
static void	incoming_lla_opt(struct phyint *pi, uchar_t *opt,
		    struct sockaddr_in6 *from, int isrouter);

static void	verify_ra_consistency(struct phyint *pi,
		    struct nd_router_advert *ra,
		    int len, struct sockaddr_in6 *from);
static void	verify_prefix_opt(struct phyint *pi, uchar_t *opt,
		    char *frombuf);
static void	verify_mtu_opt(struct phyint *pi, uchar_t *opt,
		    char *frombuf);

static void	update_ra_flag(const struct phyint *pi,
		    const struct sockaddr_in6 *from, int isrouter);

/*
 * Return a pointer to the specified option buffer.
 * If not found return NULL.
 */
static void *
find_ancillary(struct msghdr *msg, int cmsg_type)
{
	struct cmsghdr *cmsg;

	for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL;
	    cmsg = CMSG_NXTHDR(msg, cmsg)) {
		if (cmsg->cmsg_level == IPPROTO_IPV6 &&
		    cmsg->cmsg_type == cmsg_type) {
			return (CMSG_DATA(cmsg));
		}
	}
	return (NULL);
}

void
in_data(struct phyint *pi)
{
	struct sockaddr_in6 from;
	struct icmp6_hdr *icmp;
	struct nd_router_solicit *rs;
	struct nd_router_advert *ra;
	static uint64_t in_packet[(IP_MAXPACKET + 1)/8];
	static uint64_t ancillary_data[(IP_MAXPACKET + 1)/8];
	int len;
	char abuf[INET6_ADDRSTRLEN];
	const char *msgbuf;
	struct msghdr msg;
	struct iovec iov;
	uchar_t *opt;
	uint_t hoplimit;

	iov.iov_base = (char *)in_packet;
	iov.iov_len = sizeof (in_packet);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_name = (struct sockaddr *)&from;
	msg.msg_namelen = sizeof (from);
	msg.msg_control = ancillary_data;
	msg.msg_controllen = sizeof (ancillary_data);

	if ((len = recvmsg(pi->pi_sock, &msg, 0)) < 0) {
		logperror_pi(pi, "in_data: recvfrom");
		return;
	}
	if (len == 0)
		return;

	if (inet_ntop(AF_INET6, (void *)&from.sin6_addr,
	    abuf, sizeof (abuf)) == NULL)
		msgbuf = "Unspecified Router";
	else
		msgbuf = abuf;

	/* Ignore packets > 64k or control buffers that don't fit */
	if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
		if (debug & D_PKTBAD) {
			logmsg(LOG_DEBUG, "Truncated message: msg_flags 0x%x "
			    "from %s\n", msg.msg_flags, msgbuf);
		}
		return;
	}

	icmp = (struct icmp6_hdr *)in_packet;

	if (len < ICMP6_MINLEN) {
		logmsg(LOG_INFO, "Too short ICMP packet: %d bytes "
		    "from %s on %s\n",
		    len, msgbuf, pi->pi_name);
		return;
	}

	opt = find_ancillary(&msg, IPV6_HOPLIMIT);
	if (opt == NULL) {
		/* Unknown hoplimit - must drop */
		logmsg(LOG_INFO, "Unknown hop limit from %s on %s\n",
		    msgbuf, pi->pi_name);
		return;
	}
	hoplimit = *(uint_t *)opt;
	opt = find_ancillary(&msg, IPV6_RTHDR);
	if (opt != NULL) {
		/* Can't allow routing headers in ND messages */
		logmsg(LOG_INFO, "ND message with routing header "
		    "from %s on %s\n",
		    msgbuf, pi->pi_name);
		return;
	}
	switch (icmp->icmp6_type) {
	case ND_ROUTER_SOLICIT:
		if (!pi->pi_AdvSendAdvertisements)
			return;
		if (pi->pi_flags & IFF_NORTEXCH) {
			if (debug & D_PKTIN) {
				logmsg(LOG_DEBUG, "Ignore received RS packet "
				    "on %s (no route exchange on interface)\n",
				    pi->pi_name);
			}
			return;
		}

		/*
		 * Assumes that the kernel has verified the AH (if present)
		 * and the ICMP checksum.
		 */
		if (hoplimit != IPV6_MAX_HOPS) {
			logmsg(LOG_DEBUG, "RS hop limit: %d from %s on %s\n",
			    hoplimit, msgbuf, pi->pi_name);
			return;
		}

		if (icmp->icmp6_code != 0) {
			logmsg(LOG_INFO, "RS code: %d from %s on %s\n",
			    icmp->icmp6_code, msgbuf, pi->pi_name);
			return;
		}

		if (len < sizeof (struct nd_router_solicit)) {
			logmsg(LOG_INFO, "RS too short: %d bytes "
			    "from %s on %s\n",
			    len, msgbuf, pi->pi_name);
			return;
		}
		rs = (struct nd_router_solicit *)icmp;
		if (len > sizeof (struct nd_router_solicit)) {
			if (!verify_opt_len((struct nd_opt_hdr *)&rs[1],
			    len - sizeof (struct nd_router_solicit), pi, &from))
				return;
		}
		if (debug & D_PKTIN) {
			print_route_sol("Received valid solicit from ", pi,
			    rs, len, &from);
		}
		incoming_rs(pi, rs, len, &from);
		break;

	case ND_ROUTER_ADVERT:
		if (IN6_IS_ADDR_UNSPECIFIED(&from.sin6_addr)) {
			/*
			 * Router advt. must have address!
			 * Logging the news and returning.
			 */
			logmsg(LOG_DEBUG,
			    "Router's address unspecified in advertisement\n");
			return;
		}
		if (pi->pi_flags & IFF_NORTEXCH) {
			if (debug & D_PKTIN) {
				logmsg(LOG_DEBUG, "Ignore received RA packet "
				    "on %s (no route exchange on interface)\n",
				    pi->pi_name);
			}
			return;
		}

		/*
		 * Assumes that the kernel has verified the AH (if present)
		 * and the ICMP checksum.
		 */
		if (!IN6_IS_ADDR_LINKLOCAL(&from.sin6_addr)) {
			logmsg(LOG_DEBUG, "RA from %s - not link local on %s\n",
			    msgbuf, pi->pi_name);
			return;
		}

		if (hoplimit != IPV6_MAX_HOPS) {
			logmsg(LOG_INFO, "RA hop limit: %d from %s on %s\n",
			    hoplimit, msgbuf, pi->pi_name);
			return;
		}

		if (icmp->icmp6_code != 0) {
			logmsg(LOG_INFO, "RA code: %d from %s on %s\n",
			    icmp->icmp6_code, msgbuf, pi->pi_name);
			return;
		}

		if (len < sizeof (struct nd_router_advert)) {
			logmsg(LOG_INFO, "RA too short: %d bytes "
			    "from %s on %s\n",
			    len, msgbuf, pi->pi_name);
			return;
		}
		ra = (struct nd_router_advert *)icmp;
		if (len > sizeof (struct nd_router_advert)) {
			if (!verify_opt_len((struct nd_opt_hdr *)&ra[1],
			    len - sizeof (struct nd_router_advert), pi, &from))
				return;
		}
		if (debug & D_PKTIN) {
			print_route_adv("Received valid advert from ", pi,
			    ra, len, &from);
		}
		if (pi->pi_AdvSendAdvertisements)
			verify_ra_consistency(pi, ra, len, &from);
		else
			incoming_ra(pi, ra, len, &from, _B_FALSE);
		break;
	}
}

/*
 * Process a received router solicitation.
 * Check for source link-layer address option and check if it
 * is time to advertise.
 */
static void
incoming_rs(struct phyint *pi, struct nd_router_solicit *rs, int len,
    struct sockaddr_in6 *from)
{
	struct nd_opt_hdr *opt;
	int optlen;

	/* Process any options */
	len -= sizeof (struct nd_router_solicit);
	opt = (struct nd_opt_hdr *)&rs[1];
	while (len >= sizeof (struct nd_opt_hdr)) {
		optlen = opt->nd_opt_len * 8;
		switch (opt->nd_opt_type) {
		case ND_OPT_SOURCE_LINKADDR:
			incoming_lla_opt(pi, (uchar_t *)opt,
			    from, NDF_ISROUTER_OFF);
			break;
		default:
			break;
		}
		opt = (struct nd_opt_hdr *)((char *)opt + optlen);
		len -= optlen;
	}
	/* Simple algorithm: treat unicast and multicast RSs the same */
	check_to_advertise(pi, RECEIVED_SOLICIT);
}

/*
 * Start up DHCPv6 on a given physical interface.  Does not wait for a message
 * to be returned from the daemon.
 */
void
start_dhcp(struct phyint *pi)
{
	dhcp_ipc_request_t	*request;
	dhcp_ipc_reply_t	*reply	= NULL;
	int			error;
	int			type;

	if (dhcp_start_agent(DHCP_IPC_MAX_WAIT) == -1) {
		logmsg(LOG_ERR, "start_dhcp: unable to start %s\n",
		    DHCP_AGENT_PATH);
		/* make sure we try again next time there's a chance */
		pi->pi_ra_flags &= ~ND_RA_FLAG_MANAGED & ~ND_RA_FLAG_OTHER;
		return;
	}

	type = (pi->pi_ra_flags & ND_RA_FLAG_MANAGED) ? DHCP_START :
	    DHCP_INFORM;

	request = dhcp_ipc_alloc_request(type | DHCP_V6, pi->pi_name, NULL, 0,
	    DHCP_TYPE_NONE);
	if (request == NULL) {
		logmsg(LOG_ERR, "start_dhcp: out of memory\n");
		/* make sure we try again next time there's a chance */
		pi->pi_ra_flags &= ~ND_RA_FLAG_MANAGED & ~ND_RA_FLAG_OTHER;
		return;
	}

	error = dhcp_ipc_make_request(request, &reply, 0);
	free(request);
	if (error != 0) {
		logmsg(LOG_ERR, "start_dhcp: err: %s: %s\n", pi->pi_name,
		    dhcp_ipc_strerror(error));
		return;
	}

	error = reply->return_code;
	free(reply);

	/*
	 * Timeout is considered to be "success" because we don't wait for DHCP
	 * to do its exchange.
	 */
	if (error != DHCP_IPC_SUCCESS && error != DHCP_IPC_E_RUNNING &&
	    error != DHCP_IPC_E_TIMEOUT) {
		logmsg(LOG_ERR, "start_dhcp: ret: %s: %s\n", pi->pi_name,
		    dhcp_ipc_strerror(error));
		return;
	}
}

/*
 * Process a received router advertisement.
 * Called both when packets arrive as well as when we send RAs.
 * In the latter case 'loopback' is set.
 */
void
incoming_ra(struct phyint *pi, struct nd_router_advert *ra, int len,
    struct sockaddr_in6 *from, boolean_t loopback)
{
	struct nd_opt_hdr *opt;
	int optlen;
	struct lifreq lifr;
	boolean_t set_needed = _B_FALSE;
	struct router *dr;
	uint16_t router_lifetime;
	uint_t reachable, retrans;
	boolean_t reachable_time_changed = _B_FALSE;
	boolean_t slla_opt_present	 = _B_FALSE;

	if (no_loopback && loopback)
		return;

	/*
	 * If the interface is FAILED or INACTIVE or OFFLINE, don't
	 * create any addresses on them. in.mpathd assumes that no new
	 * addresses will appear on these. This implies that we
	 * won't create any new prefixes advertised by the router
	 * on FAILED/INACTIVE/OFFLINE interfaces. When the state changes,
	 * the next RA will create the prefix on this interface.
	 */
	if (pi->pi_flags & (IFF_FAILED|IFF_INACTIVE|IFF_OFFLINE))
		return;

	(void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name));
	lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0';
	if (ioctl(pi->pi_sock, SIOCGLIFLNKINFO, (char *)&lifr) < 0) {
		if (errno == ENXIO)
			return;
		logperror_pi(pi, "incoming_ra: SIOCGLIFLNKINFO");
		return;
	}
	if (ra->nd_ra_curhoplimit != CURHOP_UNSPECIFIED &&
	    ra->nd_ra_curhoplimit != pi->pi_CurHopLimit) {
		pi->pi_CurHopLimit = ra->nd_ra_curhoplimit;

		lifr.lifr_ifinfo.lir_maxhops = pi->pi_CurHopLimit;
		set_needed = _B_TRUE;
	}

	reachable = ntohl(ra->nd_ra_reachable);
	if (reachable != 0 &&
	    reachable != pi->pi_BaseReachableTime) {
		pi->pi_BaseReachableTime = reachable;
		reachable_time_changed = _B_TRUE;
	}

	if (pi->pi_reach_time_since_random < MIN_REACH_RANDOM_INTERVAL ||
	    reachable_time_changed) {
		phyint_reach_random(pi, _B_FALSE);
		set_needed = _B_TRUE;
	}
	lifr.lifr_ifinfo.lir_reachtime = pi->pi_ReachableTime;

	retrans = ntohl(ra->nd_ra_retransmit);
	if (retrans != 0 &&
	    pi->pi_RetransTimer != retrans) {
		pi->pi_RetransTimer = retrans;
		lifr.lifr_ifinfo.lir_reachretrans = pi->pi_RetransTimer;
		set_needed = _B_TRUE;
	}

	if (set_needed) {
		if (ioctl(pi->pi_sock, SIOCSLIFLNKINFO, (char *)&lifr) < 0) {
			logperror_pi(pi, "incoming_ra: SIOCSLIFLNKINFO");
			return;
		}
	}

	/*
	 * If the "managed" flag is set, then just assume that the "other" flag
	 * is set as well.  It's not legal to get addresses alone without
	 * getting other data.
	 */
	if (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)
		ra->nd_ra_flags_reserved |= ND_RA_FLAG_OTHER;

	/*
	 * If either the "managed" or "other" bits have turned on, then it's
	 * now time to invoke DHCP.  If only the "other" bit is set, then don't
	 * get addresses via DHCP; only "other" data.  If "managed" is set,
	 * then we must always get both addresses and "other" data.
	 */
	if (pi->pi_StatefulAddrConf &&
	    (ra->nd_ra_flags_reserved & ~pi->pi_ra_flags &
	    (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER))) {
		if (debug & D_DHCP) {
			logmsg(LOG_DEBUG,
			    "incoming_ra: trigger dhcp %s on %s\n",
			    (ra->nd_ra_flags_reserved & ~pi->pi_ra_flags &
				ND_RA_FLAG_MANAGED) ? "MANAGED" : "OTHER",
			    pi->pi_name);
		}
		pi->pi_ra_flags |= ra->nd_ra_flags_reserved;
		start_dhcp(pi);
	}

	/* Skip default router code if sent from ourselves */
	if (!loopback) {
		/* Find and update or add default router in list */
		dr = router_lookup(pi, from->sin6_addr);
		router_lifetime = ntohs(ra->nd_ra_router_lifetime);
		if (dr == NULL) {
			if (router_lifetime != 0) {
				dr = router_create(pi, from->sin6_addr,
				    MILLISEC * router_lifetime);
				timer_schedule(dr->dr_lifetime);
			}
		} else {
			dr->dr_lifetime = MILLISEC * router_lifetime;
			if (dr->dr_lifetime != 0)
				timer_schedule(dr->dr_lifetime);
			if ((dr->dr_lifetime != 0 && !dr->dr_inkernel) ||
			    (dr->dr_lifetime == 0 && dr->dr_inkernel))
				router_update_k(dr);
		}
	}
	/* Process any options */
	len -= sizeof (struct nd_router_advert);
	opt = (struct nd_opt_hdr *)&ra[1];
	while (len >= sizeof (struct nd_opt_hdr)) {
		optlen = opt->nd_opt_len * 8;
		switch (opt->nd_opt_type) {
		case ND_OPT_PREFIX_INFORMATION:
			incoming_prefix_opt(pi, (uchar_t *)opt, from,
			    loopback);
			break;
		case ND_OPT_MTU:
			incoming_mtu_opt(pi, (uchar_t *)opt, from);
			break;
		case ND_OPT_SOURCE_LINKADDR:
			/* skip lla option if sent from ourselves! */
			if (!loopback) {
				incoming_lla_opt(pi, (uchar_t *)opt,
				    from, NDF_ISROUTER_ON);
				slla_opt_present = _B_TRUE;
			}
			break;
		default:
			break;
		}
		opt = (struct nd_opt_hdr *)((char *)opt + optlen);
		len -= optlen;
	}
	if (!loopback && !slla_opt_present)
		update_ra_flag(pi, from, NDF_ISROUTER_ON);
	/* Stop sending solicitations */
	check_to_solicit(pi, SOLICIT_DONE);
}

/*
 * Process a received prefix option.
 * Unless addrconf is turned off we process both the addrconf and the
 * onlink aspects of the prefix option.
 *
 * Note that when a flag (onlink or auto) is turned off we do nothing -
 * the prefix will time out.
 */
static void
incoming_prefix_opt(struct phyint *pi, uchar_t *opt,
    struct sockaddr_in6 *from, boolean_t loopback)
{
	struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt;
	boolean_t	good_prefix = _B_TRUE;

	if (8 * po->nd_opt_pi_len != sizeof (*po)) {
		char abuf[INET6_ADDRSTRLEN];

		(void) inet_ntop(AF_INET6, (void *)&from->sin6_addr,
		    abuf, sizeof (abuf));
		logmsg(LOG_INFO, "prefix option from %s on %s wrong size "
		    "(%d bytes)\n",
		    abuf, pi->pi_name,
		    8 * (int)po->nd_opt_pi_len);
		return;
	}
	if (IN6_IS_ADDR_LINKLOCAL(&po->nd_opt_pi_prefix)) {
		char abuf[INET6_ADDRSTRLEN];

		(void) inet_ntop(AF_INET6, (void *)&from->sin6_addr,
		    abuf, sizeof (abuf));
		logmsg(LOG_INFO, "RA from %s on %s contains link-local prefix "
		    "- ignored\n",
		    abuf, pi->pi_name);
		return;
	}
	if ((po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) &&
	    pi->pi_StatelessAddrConf) {
		good_prefix = incoming_prefix_addrconf(pi, opt, from, loopback);
	}
	if ((po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) &&
	    good_prefix) {
		incoming_prefix_onlink(pi, opt);
	}
	if (pi->pi_StatefulAddrConf)
		incoming_prefix_stateful(pi, opt);
}

/*
 * Process prefix options with the onlink flag set.
 *
 * If there are no routers ndpd will add an onlink
 * default route which will allow communication
 * between neighbors.
 *
 * This function needs to loop to find the same prefix multiple times
 * as if a failover happened earlier, the addresses belonging to
 * a different interface may be found here on this interface.
 */
static void
incoming_prefix_onlink(struct phyint *pi, uchar_t *opt)
{
	struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt;
	int plen;
	struct prefix *pr;
	uint32_t validtime;	/* Without 2 hour rule */
	boolean_t found_one = _B_FALSE;

	plen = po->nd_opt_pi_prefix_len;
	for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) {
		if (pr->pr_prefix_len == plen &&
		    prefix_equal(po->nd_opt_pi_prefix, pr->pr_prefix, plen)) {
			/* Exclude static prefixes */
			if (pr->pr_state & PR_STATIC)
				continue;
			found_one = _B_TRUE;
			incoming_prefix_onlink_process(pr, opt);
		}
	}

	validtime = ntohl(po->nd_opt_pi_valid_time);
	/*
	 * If we have found a matching prefix already or validtime
	 * is zero, we have nothing to do.
	 */
	if (validtime == 0 || found_one)
		return;
	pr = prefix_create(pi, po->nd_opt_pi_prefix, plen, 0);
	if (pr == NULL)
		return;
	incoming_prefix_onlink_process(pr, opt);
}

void
incoming_prefix_onlink_process(struct prefix *pr, uchar_t *opt)
{
	struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt;
	uint32_t validtime;	/* Without 2 hour rule */
	char abuf[INET6_ADDRSTRLEN];

	validtime = ntohl(po->nd_opt_pi_valid_time);
	if (validtime != 0)
		pr->pr_state |= PR_ONLINK;
	else
		pr->pr_state &= ~PR_ONLINK;

	/*
	 * Convert from seconds to milliseconds avoiding overflow.
	 * If the lifetime in the packet is e.g. PREFIX_INFINITY - 1
	 * (4 billion seconds - about 130 years) we will in fact time
	 * out the prefix after 4 billion milliseconds - 46 days).
	 * Thus the longest lifetime (apart from infinity) is 46 days.
	 * Note that this ensures that PREFIX_INFINITY still means "forever".
	 */
	if (pr->pr_flags & IFF_TEMPORARY) {
		pr->pr_OnLinkLifetime = pr->pr_ValidLifetime;
	} else {
		if (validtime >= PREFIX_INFINITY / MILLISEC)
			pr->pr_OnLinkLifetime = PREFIX_INFINITY - 1;
		else
			pr->pr_OnLinkLifetime = validtime * MILLISEC;
	}
	pr->pr_OnLinkFlag = _B_TRUE;
	if (debug & (D_PREFIX|D_TMP)) {
		logmsg(LOG_DEBUG, "incoming_prefix_onlink_process(%s, %s/%u) "
		    "onlink %u state 0x%x, kstate 0x%x\n",
		    pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix,
		    abuf, sizeof (abuf)), pr->pr_prefix_len,
		    pr->pr_OnLinkLifetime, pr->pr_state, pr->pr_kernel_state);
	}

	if (pr->pr_kernel_state != pr->pr_state) {
		prefix_update_k(pr);
	}

	if (pr->pr_OnLinkLifetime != 0)
		timer_schedule(pr->pr_OnLinkLifetime);
}

/*
 * Process all prefix options by locating the DHCPv6-configured interfaces, and
 * applying the netmasks as needed.
 */
static void
incoming_prefix_stateful(struct phyint *pi, uchar_t *opt)
{
	struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt;
	struct prefix *pr;
	boolean_t foundpref;
	char abuf[INET6_ADDRSTRLEN];

	/* Make sure it's a valid prefix. */
	if (ntohl(po->nd_opt_pi_valid_time) == 0) {
		if (debug & D_DHCP)
			logmsg(LOG_DEBUG, "incoming_prefix_stateful: ignoring "
			    "prefix with no valid time\n");
		return;
	}

	if (debug & D_DHCP)
		logmsg(LOG_DEBUG, "incoming_prefix_stateful(%s, %s/%d)\n",
		    pi->pi_name, inet_ntop(AF_INET6,
		    (void *)&po->nd_opt_pi_prefix, abuf, sizeof (abuf)),
		    po->nd_opt_pi_prefix_len);
	foundpref = _B_FALSE;
	for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) {
		if (prefix_equal(po->nd_opt_pi_prefix, pr->pr_address,
		    po->nd_opt_pi_prefix_len)) {
			if ((pr->pr_flags & IFF_DHCPRUNNING) &&
			    pr->pr_prefix_len != po->nd_opt_pi_prefix_len) {
				pr->pr_prefix_len = po->nd_opt_pi_prefix_len;
				if (pr->pr_flags & IFF_UP) {
					if (debug & D_DHCP)
						logmsg(LOG_DEBUG,
						    "incoming_prefix_stateful:"
						    " set mask on DHCP %s\n",
						    pr->pr_name);
					prefix_update_dhcp(pr);
				}
			}
			if (pr->pr_prefix_len == po->nd_opt_pi_prefix_len &&
			    (!(pr->pr_state & PR_STATIC) ||
			    (pr->pr_flags & IFF_DHCPRUNNING)))
				foundpref = _B_TRUE;
		}
	}
	/*
	 * If there's no matching DHCPv6 prefix present, then create an empty
	 * one so that we'll be able to configure it later.
	 */
	if (!foundpref) {
		pr = prefix_create(pi, po->nd_opt_pi_prefix,
		    po->nd_opt_pi_prefix_len, IFF_DHCPRUNNING);
		if (pr != NULL) {
			pr->pr_state = PR_STATIC;
			if (debug & D_DHCP)
				logmsg(LOG_DEBUG,
				    "incoming_prefix_stateful: created dummy "
				    "prefix for later\n");
		}
	}
}

/*
 * Process prefix options with the autonomous flag set.
 * Returns false if this prefix results in a bad address (duplicate)
 * This function needs to loop to find the same prefix multiple times
 * as if a failover happened earlier, the addresses belonging to
 * a different interface may be found here on this interface.
 */
static boolean_t
incoming_prefix_addrconf(struct phyint *pi, uchar_t *opt,
    struct sockaddr_in6 *from, boolean_t loopback)
{
	struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt;
	int plen;
	struct prefix *pr;
	uint32_t validtime, preftime;	/* In seconds */
	char abuf[INET6_ADDRSTRLEN];
	char pbuf[INET6_ADDRSTRLEN];
	boolean_t found_pub = _B_FALSE;
	boolean_t found_tmp = _B_FALSE;
	boolean_t ret;

	validtime = ntohl(po->nd_opt_pi_valid_time);
	preftime = ntohl(po->nd_opt_pi_preferred_time);
	plen = po->nd_opt_pi_prefix_len;

	/* Sanity checks */
	if (validtime < preftime) {
		(void) inet_ntop(AF_INET6, (void *)&from->sin6_addr,
		    abuf, sizeof (abuf));
		(void) inet_ntop(AF_INET6,
		    (void *)&po->nd_opt_pi_prefix,
		    pbuf, sizeof (pbuf));
		logmsg(LOG_WARNING, "prefix option %s/%u from %s on %s: "
		    "valid %u < pref %u ignored\n",
		    pbuf, plen, abuf, pi->pi_name,
		    validtime, preftime);
		return (_B_FALSE);
	}

	for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) {
		if (pr->pr_prefix_len == plen &&
		    prefix_equal(po->nd_opt_pi_prefix, pr->pr_prefix, plen)) {

			/* Exclude static prefixes and DHCP */
			if ((pr->pr_state & PR_STATIC) ||
			    (pr->pr_flags & IFF_DHCPRUNNING))
				continue;
			if (pr->pr_flags & IFF_TEMPORARY) {
				/*
				 * If this address is deprecated and its token
				 * doesn't match the current tmp token, we want
				 * to create a new address with the current
				 * token.  So don't count this addr as a match.
				 */
				if (!((pr->pr_flags & IFF_DEPRECATED) &&
				    !token_equal(pi->pi_tmp_token,
				    pr->pr_address, TMP_TOKEN_BITS)))
					found_tmp = _B_TRUE;
			} else {
				found_pub = _B_TRUE;
			}
			(void) incoming_prefix_addrconf_process(pi, pr, opt,
			    from, loopback, _B_FALSE);
		}
	}

	/*
	 * If we have found a matching prefix (for public and, if temp addrs
	 * are enabled, for temporary) already or validtime is zero, we have
	 * nothing to do.
	 */
	if (validtime == 0 ||
	    (found_pub && (!pi->pi_TmpAddrsEnabled || found_tmp)))
		return (_B_TRUE);

	if (!found_pub) {
		pr = prefix_create(pi, po->nd_opt_pi_prefix, plen, 0);
		if (pr == NULL)
			return (_B_TRUE);
		ret = incoming_prefix_addrconf_process(pi, pr, opt, from,
		    loopback, _B_TRUE);
	}
	/*
	 * if processing of the public address failed,
	 * don't bother with the temporary address.
	 */
	if (ret == _B_FALSE)
		return (_B_FALSE);

	if (pi->pi_TmpAddrsEnabled && !found_tmp) {
		pr = prefix_create(pi, po->nd_opt_pi_prefix, plen,
		    IFF_TEMPORARY);
		if (pr == NULL)
			return (_B_TRUE);
		ret = incoming_prefix_addrconf_process(pi, pr, opt, from,
		    loopback, _B_TRUE);
	}

	return (ret);
}

boolean_t
incoming_prefix_addrconf_process(struct phyint *pi, struct prefix *pr,
    uchar_t *opt, struct sockaddr_in6 *from, boolean_t loopback,
    boolean_t new_prefix)
{
	struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt;
	char abuf[INET6_ADDRSTRLEN];
	char pbuf[INET6_ADDRSTRLEN];
	uint32_t validtime, preftime;	/* In seconds */
	uint32_t recorded_validtime;	/* In seconds */
	int plen;
	struct prefix *other_pr;

	validtime = ntohl(po->nd_opt_pi_valid_time);
	preftime = ntohl(po->nd_opt_pi_preferred_time);
	plen = po->nd_opt_pi_prefix_len;
	if (!new_prefix) {
		/*
		 * Check 2 hour rule on valid lifetime.
		 * Follows: RFC 2462
		 * If we advertised this prefix ourselves we skip
		 * these checks. They are also skipped if we did not
		 * previously do addrconf on this prefix.
		 */
		recorded_validtime = pr->pr_ValidLifetime / MILLISEC;

		if (loopback || !(pr->pr_state & PR_AUTO) ||
		    validtime >= MIN_VALID_LIFETIME ||
		    /* LINTED - statement has no consequent */
		    validtime >= recorded_validtime) {
			/* OK */
		} else if (recorded_validtime < MIN_VALID_LIFETIME &&
		    validtime < recorded_validtime) {
			/* Ignore the prefix */
			(void) inet_ntop(AF_INET6,
			    (void *)&from->sin6_addr,
			    abuf, sizeof (abuf));
			(void) inet_ntop(AF_INET6,
			    (void *)&po->nd_opt_pi_prefix,
			    pbuf, sizeof (pbuf));
			logmsg(LOG_INFO, "prefix option %s/%u from %s on %s: "
			    "too short valid lifetime %u stored %u "
			    "- ignored\n",
			    pbuf, plen, abuf, pi->pi_name,
			    validtime, recorded_validtime);
			return (_B_TRUE);
		} else {
			/*
			 * If the router clock runs slower than the
			 * host by 1 second over 2 hours then this
			 * test will set the lifetime back to 2 hours
			 * once i.e. a lifetime decrementing in
			 * realtime might cause the prefix to live an
			 * extra 2 hours on the host.
			 */
			(void) inet_ntop(AF_INET6,
			    (void *)&from->sin6_addr,
			    abuf, sizeof (abuf));
			(void) inet_ntop(AF_INET6,
			    (void *)&po->nd_opt_pi_prefix,
			    pbuf, sizeof (pbuf));
			logmsg(LOG_INFO, "prefix option %s/%u from %s on %s: "
			    "valid time %u stored %u rounded up "
			    "to %u\n",
			    pbuf, plen, abuf, pi->pi_name,
			    validtime, recorded_validtime,
			    MIN_VALID_LIFETIME);
			validtime = MIN_VALID_LIFETIME;
		}
	}

	/*
	 * For RFC3041 addresses, need to take token lifetime
	 * into account, too.
	 */
	if (pr->pr_flags & IFF_TEMPORARY) {
		uint_t	cur_tpreftime =
		    pi->pi_TmpPreferredLifetime - pi->pi_TmpDesyncFactor;

		if (new_prefix) {
			validtime = MIN(validtime, pi->pi_TmpValidLifetime);
			preftime = MIN(preftime, cur_tpreftime);
		} else {
			uint_t cur_vexp, cur_pexp, curtime;
			curtime = getcurrenttime() / MILLISEC;

			cur_vexp = pr->pr_CreateTime + pi->pi_TmpValidLifetime;
			cur_pexp = pr->pr_CreateTime + cur_tpreftime;
			if (curtime > cur_vexp)
				validtime = 0;
			else if ((curtime + validtime) > cur_vexp)
				validtime = cur_vexp - curtime;
			/*
			 * If this is an existing address which was deprecated
			 * because of a bad token, we don't want to update its
			 * preferred lifetime!
			 */
			if ((pr->pr_PreferredLifetime == 0) &&
			    !token_equal(pr->pr_address, pi->pi_tmp_token,
			    TMP_TOKEN_BITS))
				preftime = 0;
			else if (curtime > cur_pexp)
				preftime = 0;
			else if ((curtime + preftime) > cur_pexp)
				preftime = cur_pexp - curtime;
		}
		if ((preftime != 0) && (preftime <= pi->pi_TmpRegenAdvance)) {
			(void) inet_ntop(AF_INET6,
			    (void *)&from->sin6_addr,
			    abuf, sizeof (abuf));
			(void) inet_ntop(AF_INET6,
			    (void *)&po->nd_opt_pi_prefix,
			    pbuf, sizeof (pbuf));
			logmsg(LOG_WARNING, "prefix opt %s/%u from %s on %s: "
			    "preferred lifetime(%d) <= TmpRegenAdvance(%d)\n",
			    pbuf, plen, abuf, pi->pi_name, preftime,
			    pi->pi_TmpRegenAdvance);
			if (new_prefix)
				prefix_delete(pr);
			return (_B_TRUE);
		}
	}
	if (debug & D_TMP)
		logmsg(LOG_DEBUG, "calculated lifetimes(%s, 0x%llx): v %d, "
		    "p %d\n", pr->pr_name, pr->pr_flags, validtime, preftime);

	if (!(pr->pr_state & PR_AUTO)) {
		int i, tokenlen;
		in6_addr_t *token;
		/*
		 * Form a new local address if the lengths match.
		 */
		if (pr->pr_flags && IFF_TEMPORARY) {
			if (IN6_IS_ADDR_UNSPECIFIED(&pi->pi_tmp_token)) {
				if (!tmptoken_create(pi)) {
					prefix_delete(pr);
					return (_B_TRUE);
				}
			}
			tokenlen = TMP_TOKEN_BITS;
			token = &pi->pi_tmp_token;
		} else {
			tokenlen = pi->pi_token_length;
			token = &pi->pi_token;
		}
		if (pr->pr_prefix_len + tokenlen != IPV6_ABITS) {
			(void) inet_ntop(AF_INET6,
			    (void *)&from->sin6_addr,
			    abuf, sizeof (abuf));
			(void) inet_ntop(AF_INET6,
			    (void *)&po->nd_opt_pi_prefix,
			    pbuf, sizeof (pbuf));
			logmsg(LOG_INFO, "prefix option %s/%u from %s on %s: "
			    "mismatched length %d token length %d\n",
			    pbuf, plen, abuf, pi->pi_name,
			    pr->pr_prefix_len, tokenlen);
			return (_B_TRUE);
		}
		for (i = 0; i < 16; i++) {
			/*
			 * prefix_create ensures that pr_prefix has all-zero
			 * bits after prefixlen.
			 */
			pr->pr_address.s6_addr[i] = pr->pr_prefix.s6_addr[i] |
			    token->s6_addr[i];
		}
		/*
		 * Check if any other physical interface has the same
		 * address configured already
		 */
		if ((other_pr = prefix_lookup_addr_match(pr)) != NULL) {
			/*
			 * Delete this prefix structure as kernel
			 * does not allow duplicated addresses
			 */

			logmsg(LOG_ERR, "incoming_prefix_addrconf_process: "
			    "Duplicate prefix  %s received on interface %s\n",
			    inet_ntop(AF_INET6,
			    (void *)&po->nd_opt_pi_prefix, abuf,
			    sizeof (abuf)), pi->pi_name);
			logmsg(LOG_ERR, "incoming_prefix_addrconf_process: "
			    "Prefix already exists in interface %s\n",
			    other_pr->pr_physical->pi_name);
			if (new_prefix) {
				prefix_delete(pr);
				return (_B_FALSE);
			}
			/* Ignore for addrconf purposes */
			validtime = preftime = 0;
		}
		if ((pr->pr_flags & IFF_TEMPORARY) && new_prefix) {
			pr->pr_CreateTime = getcurrenttime() / MILLISEC;
			if (debug & D_TMP)
				logmsg(LOG_DEBUG,
				    "created tmp addr(%s v %d p %d)\n",
				    pr->pr_name, validtime, preftime);
		}
	}

	if (validtime != 0)
		pr->pr_state |= PR_AUTO;
	else
		pr->pr_state &= ~(PR_AUTO|PR_DEPRECATED);
	if (preftime != 0 || !(pr->pr_state & PR_AUTO))
		pr->pr_state &= ~PR_DEPRECATED;
	else
		pr->pr_state |= PR_DEPRECATED;

	/*
	 * Convert from seconds to milliseconds avoiding overflow.
	 * If the lifetime in the packet is e.g. PREFIX_INFINITY - 1
	 * (4 billion seconds - about 130 years) we will in fact time
	 * out the prefix after 4 billion milliseconds - 46 days).
	 * Thus the longest lifetime (apart from infinity) is 46 days.
	 * Note that this ensures that PREFIX_INFINITY still means "forever".
	 */
	if (validtime >= PREFIX_INFINITY / MILLISEC)
		pr->pr_ValidLifetime = PREFIX_INFINITY - 1;
	else
		pr->pr_ValidLifetime = validtime * MILLISEC;
	if (preftime >= PREFIX_INFINITY / MILLISEC)
		pr->pr_PreferredLifetime = PREFIX_INFINITY - 1;
	else
		pr->pr_PreferredLifetime = preftime * MILLISEC;
	pr->pr_AutonomousFlag = _B_TRUE;

	if (debug & D_PREFIX) {
		logmsg(LOG_DEBUG, "incoming_prefix_addrconf_process(%s, %s/%u) "
		    "valid %u pref %u\n",
		    pr->pr_physical->pi_name,
		    inet_ntop(AF_INET6, (void *)&pr->pr_prefix,
		    abuf, sizeof (abuf)), pr->pr_prefix_len,
		    pr->pr_ValidLifetime, pr->pr_PreferredLifetime);
	}

	if (pr->pr_state & PR_AUTO) {
		/* Take the min of the two timeouts by calling it twice */
		if (pr->pr_ValidLifetime != 0)
			timer_schedule(pr->pr_ValidLifetime);
		if (pr->pr_PreferredLifetime != 0)
			timer_schedule(pr->pr_PreferredLifetime);
	}
	if (pr->pr_kernel_state != pr->pr_state) {
		/* 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 zero "
			    "valid lifetime %s\n",
			    inet_ntop(AF_INET6, (void *)&pr->pr_address,
			    abuf, sizeof (abuf)));
		}
		prefix_update_k(pr);
	}
	return (_B_TRUE);
}

/*
 * Process an MTU option received in a router advertisement.
 */
static void
incoming_mtu_opt(struct phyint *pi, uchar_t *opt,
    struct sockaddr_in6 *from)
{
	struct nd_opt_mtu *mo = (struct nd_opt_mtu *)opt;
	struct lifreq lifr;
	uint32_t mtu;

	if (8 * mo->nd_opt_mtu_len != sizeof (*mo)) {
		char abuf[INET6_ADDRSTRLEN];

		(void) inet_ntop(AF_INET6, (void *)&from->sin6_addr,
		    abuf, sizeof (abuf));
		logmsg(LOG_INFO, "mtu option from %s on %s wrong size "
		    "(%d bytes)\n",
		    abuf, pi->pi_name,
		    8 * (int)mo->nd_opt_mtu_len);
		return;
	}
	mtu = ntohl(mo->nd_opt_mtu_mtu);
	if (pi->pi_LinkMTU == mtu)
		return;	/* No change */
	if (mtu > pi->pi_mtu) {
		/* Can't exceed physical MTU */
		char abuf[INET6_ADDRSTRLEN];

		(void) inet_ntop(AF_INET6, (void *)&from->sin6_addr,
		    abuf, sizeof (abuf));
		logmsg(LOG_INFO, "mtu option from %s on %s too large "
		    "MTU %d - %d\n", abuf, pi->pi_name, mtu, pi->pi_mtu);
		return;
	}
	if (mtu < IPV6_MIN_MTU) {
		char abuf[INET6_ADDRSTRLEN];

		(void) inet_ntop(AF_INET6, (void *)&from->sin6_addr,
		    abuf, sizeof (abuf));
		logmsg(LOG_INFO, "mtu option from %s on %s too small "
		    "MTU (%d)\n", abuf, pi->pi_name, mtu);
		return;
	}

	pi->pi_LinkMTU = mtu;
	(void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name));
	lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0';
	if (ioctl(pi->pi_sock, SIOCGLIFLNKINFO, (char *)&lifr) < 0) {
		logperror_pi(pi, "incoming_mtu_opt: SIOCGLIFLNKINFO");
		return;
	}
	lifr.lifr_ifinfo.lir_maxmtu = pi->pi_LinkMTU;
	if (ioctl(pi->pi_sock, SIOCSLIFLNKINFO, (char *)&lifr) < 0) {
		logperror_pi(pi, "incoming_mtu_opt: SIOCSLIFLNKINFO");
		return;
	}
}

/*
 * Process a source link-layer address option received in a router
 * advertisement or solicitation.
 */
static void
incoming_lla_opt(struct phyint *pi, uchar_t *opt,
    struct sockaddr_in6 *from, int isrouter)
{
	struct nd_opt_lla *lo = (struct nd_opt_lla *)opt;
	struct lifreq lifr;
	struct sockaddr_in6 *sin6;
	int max_content_len;

	if (pi->pi_hdw_addr_len == 0)
		return;

	/*
	 * Can't remove padding since it is link type specific.
	 * However, we check against the length of our link-layer
	 * address.
	 * Note: assumes that all links have a fixed lengh address.
	 */
	max_content_len = lo->nd_opt_lla_len * 8 - sizeof (struct nd_opt_hdr);
	if (max_content_len < pi->pi_hdw_addr_len ||
	    (max_content_len >= 8 &&
	    max_content_len - 7 > pi->pi_hdw_addr_len)) {
		char abuf[INET6_ADDRSTRLEN];

		(void) inet_ntop(AF_INET6, (void *)&from->sin6_addr,
		    abuf, sizeof (abuf));
		logmsg(LOG_INFO, "lla option from %s on %s too long with bad "
		    "physaddr length (%d vs. %d bytes)\n",
		    abuf, pi->pi_name,
		    max_content_len, pi->pi_hdw_addr_len);
		return;
	}

	lifr.lifr_nd.lnr_hdw_len = pi->pi_hdw_addr_len;
	bcopy((char *)lo->nd_opt_lla_hdw_addr,
	    (char *)lifr.lifr_nd.lnr_hdw_addr,
	    lifr.lifr_nd.lnr_hdw_len);

	sin6 = (struct sockaddr_in6 *)&lifr.lifr_nd.lnr_addr;
	bzero(sin6, sizeof (struct sockaddr_in6));
	sin6->sin6_family = AF_INET6;
	sin6->sin6_addr = from->sin6_addr;

	/*
	 * Set IsRouter flag if RA; clear if RS.
	 */
	lifr.lifr_nd.lnr_state_create = ND_STALE;
	lifr.lifr_nd.lnr_state_same_lla = ND_UNCHANGED;
	lifr.lifr_nd.lnr_state_diff_lla = ND_STALE;
	lifr.lifr_nd.lnr_flags = isrouter;
	(void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name));
	lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0';
	if (ioctl(pi->pi_sock, SIOCLIFSETND, (char *)&lifr) < 0) {
		logperror_pi(pi, "incoming_lla_opt: SIOCLIFSETND");
		return;
	}
}

/*
 * Verify the content of the received router advertisement against our
 * own configuration as specified in RFC 2461.
 */
static void
verify_ra_consistency(struct phyint *pi, struct nd_router_advert *ra, int len,
    struct sockaddr_in6 *from)
{
	char frombuf[INET6_ADDRSTRLEN];
	struct nd_opt_hdr *opt;
	int optlen;
	uint_t reachable, retrans;
	boolean_t pktflag, myflag;

	(void) inet_ntop(AF_INET6, (void *)&from->sin6_addr,
	    frombuf, sizeof (frombuf));

	if (ra->nd_ra_curhoplimit != 0 &&
	    pi->pi_AdvCurHopLimit != 0 &&
	    ra->nd_ra_curhoplimit != pi->pi_AdvCurHopLimit) {
		logmsg(LOG_INFO, "RA from %s on %s inconsistent cur hop "
		    "limit:\n\treceived %d configuration %d\n",
		    frombuf, pi->pi_name,
		    ra->nd_ra_curhoplimit, pi->pi_AdvCurHopLimit);
	}

	reachable = ntohl(ra->nd_ra_reachable);
	if (reachable != 0 && pi->pi_AdvReachableTime != 0 &&
	    reachable != pi->pi_AdvReachableTime) {
		logmsg(LOG_INFO, "RA from %s on %s inconsistent reachable "
		    "time:\n\treceived %d configuration %d\n",
		    frombuf, pi->pi_name,
		    reachable, pi->pi_AdvReachableTime);
	}

	retrans = ntohl(ra->nd_ra_retransmit);
	if (retrans != 0 && pi->pi_AdvRetransTimer != 0 &&
	    retrans != pi->pi_AdvRetransTimer) {
		logmsg(LOG_INFO, "RA from %s on %s inconsistent retransmit "
		    "timer:\n\treceived %d configuration %d\n",
		    frombuf, pi->pi_name,
		    retrans, pi->pi_AdvRetransTimer);
	}

	pktflag = ((ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) != 0);
	myflag = (pi->pi_AdvManagedFlag != 0);
	if (pktflag != myflag) {
		logmsg(LOG_INFO, "RA from %s on %s inconsistent managed "
		    "flag:\n\treceived %s configuration %s\n",
		    frombuf, pi->pi_name,
		    (pktflag ? "ON" : "OFF"),
		    (myflag ? "ON" : "OFF"));
	}
	pktflag = ((ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) != 0);
	myflag = (pi->pi_AdvOtherConfigFlag != 0);
	if (pktflag != myflag) {
		logmsg(LOG_INFO, "RA from %s on %s inconsistent other config "
		    "flag:\n\treceived %s configuration %s\n",
		    frombuf, pi->pi_name,
		    (pktflag ? "ON" : "OFF"),
		    (myflag ? "ON" : "OFF"));
	}

	/* Process any options */
	len -= sizeof (struct nd_router_advert);
	opt = (struct nd_opt_hdr *)&ra[1];
	while (len >= sizeof (struct nd_opt_hdr)) {
		optlen = opt->nd_opt_len * 8;
		switch (opt->nd_opt_type) {
		case ND_OPT_PREFIX_INFORMATION:
			verify_prefix_opt(pi, (uchar_t *)opt, frombuf);
			break;
		case ND_OPT_MTU:
			verify_mtu_opt(pi, (uchar_t *)opt, frombuf);
			break;
		default:
			break;
		}
		opt = (struct nd_opt_hdr *)((char *)opt + optlen);
		len -= optlen;
	}
}

/*
 * Verify that the lifetimes and onlink/auto flags are consistent
 * with our settings.
 */
static void
verify_prefix_opt(struct phyint *pi, uchar_t *opt, char *frombuf)
{
	struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt;
	int plen;
	struct adv_prefix *adv_pr;
	uint32_t validtime, preftime;
	char prefixbuf[INET6_ADDRSTRLEN];
	int pktflag, myflag;

	if (8 * po->nd_opt_pi_len != sizeof (*po)) {
		logmsg(LOG_INFO, "RA prefix option from %s on %s wrong size "
		    "(%d bytes)\n",
		    frombuf, pi->pi_name,
		    8 * (int)po->nd_opt_pi_len);
		return;
	}
	if (IN6_IS_ADDR_LINKLOCAL(&po->nd_opt_pi_prefix)) {
		logmsg(LOG_INFO, "RA from %s on %s contains link-local "
		    "prefix - ignored\n",
		    frombuf, pi->pi_name);
		return;
	}
	plen = po->nd_opt_pi_prefix_len;
	adv_pr = adv_prefix_lookup(pi, po->nd_opt_pi_prefix, plen);
	if (adv_pr == NULL)
		return;

	/* Ignore prefixes which we do not advertise */
	if (!adv_pr->adv_pr_AdvAutonomousFlag && !adv_pr->adv_pr_AdvOnLinkFlag)
		return;
	(void) inet_ntop(AF_INET6, (void *)&adv_pr->adv_pr_prefix,
	    prefixbuf, sizeof (prefixbuf));
	pktflag = ((po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) != 0);
	myflag = (adv_pr->adv_pr_AdvAutonomousFlag != 0);
	if (pktflag != myflag) {
		logmsg(LOG_INFO,
		    "RA from %s on %s inconsistent autonomous flag for \n\t"
		    "prefix %s/%u: received %s configuration %s\n",
		    frombuf, pi->pi_name, prefixbuf, adv_pr->adv_pr_prefix_len,
		    (pktflag ? "ON" : "OFF"),
		    (myflag ? "ON" : "OFF"));
	}

	pktflag = ((po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) != 0);
	myflag = (adv_pr->adv_pr_AdvOnLinkFlag != 0);
	if (pktflag != myflag) {
		logmsg(LOG_INFO, "RA from %s on %s inconsistent on link flag "
		    "for \n\tprefix %s/%u: received %s configuration %s\n",
		    frombuf, pi->pi_name, prefixbuf, adv_pr->adv_pr_prefix_len,
		    (pktflag ? "ON" : "OFF"),
		    (myflag ? "ON" : "OFF"));
	}
	validtime = ntohl(po->nd_opt_pi_valid_time);
	preftime = ntohl(po->nd_opt_pi_preferred_time);

	/*
	 * Take into account variation for lifetimes decrementing
	 * in real time. Allow +/- 10 percent and +/- 10 seconds.
	 */
#define	LOWER_LIMIT(val)	((val) - (val)/10 - 10)
#define	UPPER_LIMIT(val)	((val) + (val)/10 + 10)
	if (adv_pr->adv_pr_AdvValidRealTime) {
		if (adv_pr->adv_pr_AdvValidExpiration > 0 &&
		    (validtime <
		    LOWER_LIMIT(adv_pr->adv_pr_AdvValidExpiration) ||
		    validtime >
		    UPPER_LIMIT(adv_pr->adv_pr_AdvValidExpiration))) {
			logmsg(LOG_INFO, "RA from %s on %s inconsistent valid "
			    "lifetime for\n\tprefix %s/%u: received %d "
			    "configuration %d\n",
			    frombuf, pi->pi_name, prefixbuf,
			    adv_pr->adv_pr_prefix_len,
			    validtime, adv_pr->adv_pr_AdvValidExpiration);
		}
	} else {
		if (validtime != adv_pr->adv_pr_AdvValidLifetime) {
			logmsg(LOG_INFO, "RA from %s on %s inconsistent valid "
			    "lifetime for\n\tprefix %s/%u: received %d "
			    "configuration %d\n",
			    frombuf, pi->pi_name, prefixbuf,
			    adv_pr->adv_pr_prefix_len,
			    validtime, adv_pr->adv_pr_AdvValidLifetime);
		}
	}

	if (adv_pr->adv_pr_AdvPreferredRealTime) {
		if (adv_pr->adv_pr_AdvPreferredExpiration > 0 &&
		    (preftime <
		    LOWER_LIMIT(adv_pr->adv_pr_AdvPreferredExpiration) ||
		    preftime >
		    UPPER_LIMIT(adv_pr->adv_pr_AdvPreferredExpiration))) {
			logmsg(LOG_INFO, "RA from %s on %s inconsistent "
			    "preferred lifetime for\n\tprefix %s/%u: "
			    "received %d configuration %d\n",
			    frombuf, pi->pi_name, prefixbuf,
			    adv_pr->adv_pr_prefix_len,
			    preftime, adv_pr->adv_pr_AdvPreferredExpiration);
		}
	} else {
		if (preftime != adv_pr->adv_pr_AdvPreferredLifetime) {
			logmsg(LOG_INFO, "RA from %s on %s inconsistent "
			    "preferred lifetime for\n\tprefix %s/%u: "
			    "received %d configuration %d\n",
			    frombuf, pi->pi_name, prefixbuf,
			    adv_pr->adv_pr_prefix_len,
			    preftime, adv_pr->adv_pr_AdvPreferredLifetime);
		}
	}
}

/*
 * Verify the received MTU against our own configuration.
 */
static void
verify_mtu_opt(struct phyint *pi, uchar_t *opt, char *frombuf)
{
	struct nd_opt_mtu *mo = (struct nd_opt_mtu *)opt;
	uint32_t mtu;

	if (8 * mo->nd_opt_mtu_len != sizeof (*mo)) {
		logmsg(LOG_INFO, "mtu option from %s on %s wrong size "
		    "(%d bytes)\n",
		    frombuf, pi->pi_name,
		    8 * (int)mo->nd_opt_mtu_len);
		return;
	}
	mtu = ntohl(mo->nd_opt_mtu_mtu);
	if (pi->pi_AdvLinkMTU != 0 &&
	    pi->pi_AdvLinkMTU != mtu) {
		logmsg(LOG_INFO, "RA from %s on %s inconsistent MTU: "
		    "received %d configuration %d\n",
		    frombuf, pi->pi_name,
		    mtu, pi->pi_AdvLinkMTU);
	}
}

/*
 * Verify that all options have a non-zero length and that
 * the options fit within the total length of the packet (optlen).
 */
static boolean_t
verify_opt_len(struct nd_opt_hdr *opt, int optlen,
    struct phyint *pi, struct sockaddr_in6 *from)
{
	while (optlen > 0) {
		if (opt->nd_opt_len == 0) {
			char abuf[INET6_ADDRSTRLEN];

			(void) inet_ntop(AF_INET6,
			    (void *)&from->sin6_addr,
			    abuf, sizeof (abuf));

			logmsg(LOG_INFO, "Zero length option type 0x%x "
			    "from %s on %s\n",
			    opt->nd_opt_type, abuf, pi->pi_name);
			return (_B_FALSE);
		}
		optlen -= 8 * opt->nd_opt_len;
		if (optlen < 0) {
			char abuf[INET6_ADDRSTRLEN];

			(void) inet_ntop(AF_INET6,
			    (void *)&from->sin6_addr,
			    abuf, sizeof (abuf));

			logmsg(LOG_INFO, "Too large option: type 0x%x len %u "
			    "from %s on %s\n",
			    opt->nd_opt_type, opt->nd_opt_len,
			    abuf, pi->pi_name);
			return (_B_FALSE);
		}
		opt = (struct nd_opt_hdr *)((char *)opt +
		    8 * opt->nd_opt_len);
	}
	return (_B_TRUE);
}

/*
 * Update IsRouter Flag for Host turning into a router or vice-versa.
 */
static void
update_ra_flag(const struct phyint *pi, const struct sockaddr_in6 *from,
    int isrouter)
{
	struct lifreq lifr;
	char abuf[INET6_ADDRSTRLEN];
	struct sockaddr_in6 *sin6;

	/* check if valid flag is being set */
	if ((isrouter != NDF_ISROUTER_ON) &&
	    (isrouter != NDF_ISROUTER_OFF)) {
		logmsg(LOG_ERR, "update_ra_flag: Invalid IsRouter "
		    "flag %d\n", isrouter);
		return;
	}

	sin6 = (struct sockaddr_in6 *)&lifr.lifr_nd.lnr_addr;
	bzero(sin6, sizeof (*sin6));
	sin6->sin6_family = AF_INET6;
	sin6->sin6_addr = from->sin6_addr;

	(void) strlcpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name));

	if (ioctl(pi->pi_sock, SIOCLIFGETND, (char *)&lifr) < 0) {
		if (errno == ESRCH) {
			if (debug & D_IFSCAN) {
				logmsg(LOG_DEBUG,
"update_ra_flag: SIOCLIFGETND: nce doesn't exist, not setting IFF_ROUTER");
			}
		} else {
			logperror_pi(pi, "update_ra_flag: SIOCLIFGETND");
		}
	} else {
		/*
		 * The lif_nd_req structure has three state values to be used
		 * when changing/updating nces :
		 * lnr_state_create, lnr_state_same_lla, and lnr_state_diff_lla.
		 *
		 * In this case, we're updating an nce, without changing lla;
		 * so we set lnr_state_same_lla to ND_UNCHANGED, indicating that
		 * nce's state should not be affected by our flag change.
		 *
		 * The kernel implementation also expects the lnr_state_create
		 * field be always set, before processing ioctl request for NCE
		 * update.
		 * We use the state as STALE, while addressing the possibility
		 * of NCE deletion when ioctl with SIOCLIFGETND argument
		 * in earlier step is returned - further in such case we don't
		 * want to re-create the entry in the reachable state.
		 */
		lifr.lifr_nd.lnr_state_create = ND_STALE;
		lifr.lifr_nd.lnr_state_same_lla = ND_UNCHANGED;
		lifr.lifr_nd.lnr_flags = isrouter;
		if ((ioctl(pi->pi_sock, SIOCLIFSETND, (char *)&lifr)) < 0) {
			logperror_pi(pi, "update_ra_flag: SIOCLIFSETND");
		} else {
			(void) inet_ntop(AF_INET6, (void *)&from->sin6_addr,
			    abuf, sizeof (abuf));
			logmsg(LOG_INFO, "update_ra_flag: IsRouter flag "
			    "updated for %s\n", abuf);
		}
	}
}