/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <stdlib.h>
#include <sys/sockio.h>
#include <netinet/in.h>
#include <netinet/dhcp.h>
#include <string.h>
#include <unistd.h>
#include <search.h>
#include <libdevinfo.h>
#include <libdlpi.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>
#include <dhcpmsg.h>

#include "agent.h"
#include "interface.h"
#include "util.h"
#include "packet.h"
#include "states.h"

dhcp_pif_t *v4root;
dhcp_pif_t *v6root;

static uint_t cached_v4_max_mtu, cached_v6_max_mtu;

/*
 * Interface flags to watch: things that should be under our direct control.
 */
#define	DHCP_IFF_WATCH	(IFF_DHCPRUNNING | IFF_DEPRECATED | IFF_ADDRCONF | \
	IFF_TEMPORARY)

static void clear_lif_dhcp(dhcp_lif_t *);

/*
 * insert_pif(): creates a new physical interface structure and chains it on
 *		 the list.  Initializes state that remains consistent across
 *		 all use of the physical interface entry.
 *
 *   input: const char *: the name of the physical interface
 *	    boolean_t: if B_TRUE, this is DHCPv6
 *	    int *: ignored on input; if insert_pif fails, set to a DHCP_IPC_E_*
 *		   error code with the reason why
 *  output: dhcp_pif_t *: a pointer to the new entry, or NULL on failure
 */

dhcp_pif_t *
insert_pif(const char *pname, boolean_t isv6, int *error)
{
	dhcp_pif_t *pif;
	struct lifreq lifr;
	lifgroupinfo_t lifgr;
	dlpi_handle_t dh = NULL;
	int fd = isv6 ? v6_sock_fd : v4_sock_fd;

	if ((pif = calloc(1, sizeof (*pif))) == NULL) {
		dhcpmsg(MSG_ERR, "insert_pif: cannot allocate pif entry for "
		    "%s", pname);
		*error = DHCP_IPC_E_MEMORY;
		return (NULL);
	}

	pif->pif_isv6 = isv6;
	pif->pif_hold_count = 1;
	pif->pif_running = B_TRUE;

	if (strlcpy(pif->pif_name, pname, LIFNAMSIZ) >= LIFNAMSIZ) {
		dhcpmsg(MSG_ERROR, "insert_pif: interface name %s is too long",
		    pname);
		*error = DHCP_IPC_E_INVIF;
		goto failure;
	}

	/*
	 * This is a bit gross, but IP has a confused interface.  We must
	 * assume that the zeroth LIF is plumbed, and must query there to get
	 * the interface index number.
	 */
	(void) strlcpy(lifr.lifr_name, pname, LIFNAMSIZ);

	if (ioctl(fd, SIOCGLIFINDEX, &lifr) == -1) {
		*error = (errno == ENXIO) ? DHCP_IPC_E_INVIF : DHCP_IPC_E_INT;
		dhcpmsg(MSG_ERR, "insert_pif: SIOCGLIFINDEX for %s", pname);
		goto failure;
	}
	pif->pif_index = lifr.lifr_index;

	if (ioctl(fd, SIOCGLIFMTU, &lifr) == -1) {
		*error = (errno == ENXIO) ? DHCP_IPC_E_INVIF : DHCP_IPC_E_INT;
		dhcpmsg(MSG_ERR, "insert_pif: SIOCGLIFMTU for %s", pname);
		goto failure;
	}
	pif->pif_max = lifr.lifr_mtu;

	if (pif->pif_max < DHCP_DEF_MAX_SIZE) {
		dhcpmsg(MSG_ERROR, "insert_pif: MTU of %s is too small to "
		    "support DHCP (%u < %u)", pname, pif->pif_max,
		    DHCP_DEF_MAX_SIZE);
		*error = DHCP_IPC_E_INVIF;
		goto failure;
	}

	/*
	 * Check if the pif is in an IPMP group.  Interfaces using IPMP don't
	 * have dedicated hardware addresses, and get their hardware type from
	 * the SIOCGLIFGROUPINFO ioctl rather than DLPI.
	 */
	if (ioctl(fd, SIOCGLIFGROUPNAME, &lifr) == -1) {
		*error = DHCP_IPC_E_INT;
		dhcpmsg(MSG_ERR, "insert_pif: SIOCGLIFGROUPNAME for %s", pname);
		goto failure;
	}

	if (lifr.lifr_groupname[0] != '\0') {
		(void) strlcpy(lifgr.gi_grname, lifr.lifr_groupname,
		    LIFGRNAMSIZ);
		if (ioctl(fd, SIOCGLIFGROUPINFO, &lifgr) == -1) {
			*error = DHCP_IPC_E_INT;
			dhcpmsg(MSG_ERR, "insert_pif: SIOCGLIFGROUPINFO for %s",
			    lifgr.gi_grname);
			goto failure;
		}

		pif->pif_hwtype = dlpi_arptype(lifgr.gi_mactype);
		pif->pif_under_ipmp = (strcmp(pname, lifgr.gi_grifname) != 0);
		(void) strlcpy(pif->pif_grifname, lifgr.gi_grifname, LIFNAMSIZ);

		/*
		 * For IPMP underlying interfaces, stash the interface index
		 * of the IPMP meta-interface; we'll use it to send/receive
		 * traffic.  This is both necessary (since IP_BOUND_IF for
		 * non-unicast traffic won't work on underlying interfaces)
		 * and preferred (since a test address lease will be able to
		 * be maintained as long as another interface in the group is
		 * still functioning).
		 */
		if (pif->pif_under_ipmp) {
			(void) strlcpy(lifr.lifr_name, pif->pif_grifname,
			    LIFNAMSIZ);

			if (ioctl(fd, SIOCGLIFINDEX, &lifr) == -1) {
				*error = DHCP_IPC_E_INT;
				dhcpmsg(MSG_ERR, "insert_pif: SIOCGLIFINDEX "
				    "for %s", lifr.lifr_name);
				goto failure;
			}
			pif->pif_grindex = lifr.lifr_index;
		}
	}

	/*
	 * For IPv4, if the hardware type is still unknown, use DLPI to
	 * determine it, the hardware address, and hardware address length.
	 */
	if (!isv6 && pif->pif_hwtype == 0) {
		int			rc;
		dlpi_info_t		dlinfo;

		if ((rc = dlpi_open(pname, &dh, 0)) != DLPI_SUCCESS) {
			dhcpmsg(MSG_ERROR, "insert_pif: dlpi_open: %s",
			    dlpi_strerror(rc));
			*error = DHCP_IPC_E_INVIF;
			goto failure;
		}

		if ((rc = dlpi_bind(dh, ETHERTYPE_IP, NULL)) != DLPI_SUCCESS) {
			dhcpmsg(MSG_ERROR, "insert_pif: dlpi_bind: %s",
			    dlpi_strerror(rc));
			*error = DHCP_IPC_E_INVIF;
			goto failure;
		}

		if ((rc = dlpi_info(dh, &dlinfo, 0)) != DLPI_SUCCESS) {
			dhcpmsg(MSG_ERROR, "insert_pif: dlpi_info: %s",
			    dlpi_strerror(rc));
			*error = DHCP_IPC_E_INVIF;
			goto failure;
		}

		pif->pif_hwtype = dlpi_arptype(dlinfo.di_mactype);
		pif->pif_hwlen  = dlinfo.di_physaddrlen;

		dhcpmsg(MSG_DEBUG, "insert_pif: %s: hwtype %d, hwlen %d",
		    pname, pif->pif_hwtype, pif->pif_hwlen);

		if (pif->pif_hwlen > 0) {
			pif->pif_hwaddr = malloc(pif->pif_hwlen);
			if (pif->pif_hwaddr == NULL) {
				dhcpmsg(MSG_ERR, "insert_pif: cannot allocate "
				    "pif_hwaddr for %s", pname);
				*error = DHCP_IPC_E_MEMORY;
				goto failure;
			}
			(void) memcpy(pif->pif_hwaddr, dlinfo.di_physaddr,
			    pif->pif_hwlen);
		}

		dlpi_close(dh);
		dh = NULL;
	}

	insque(pif, isv6 ? &v6root : &v4root);

	return (pif);
failure:
	if (dh != NULL)
		dlpi_close(dh);
	release_pif(pif);
	return (NULL);
}

/*
 * hold_pif(): acquire a hold on a physical interface structure.
 *
 *   input: dhcp_pif_t *: a pointer to the PIF structure
 *  output: none
 */

void
hold_pif(dhcp_pif_t *pif)
{
	pif->pif_hold_count++;
	dhcpmsg(MSG_DEBUG2, "hold_pif: hold count on %s: %u", pif->pif_name,
	    pif->pif_hold_count);
}

/*
 * release_pif(): release a hold on a physical interface structure; will
 *		  destroy the structure on the last hold removed.
 *
 *   input: dhcp_pif_t *: a pointer to the PIF structure
 *  output: none
 */

void
release_pif(dhcp_pif_t *pif)
{
	if (pif->pif_hold_count == 0) {
		dhcpmsg(MSG_CRIT, "release_pif: extraneous release");
		return;
	}

	if (--pif->pif_hold_count == 0) {
		dhcpmsg(MSG_DEBUG, "release_pif: freeing PIF %s",
		    pif->pif_name);

		remque(pif);
		free(pif->pif_hwaddr);
		free(pif);
	} else {
		dhcpmsg(MSG_DEBUG2, "release_pif: hold count on %s: %u",
		    pif->pif_name, pif->pif_hold_count);
	}
}

/*
 * lookup_pif_by_uindex(): Looks up PIF entries given truncated index and
 *			   previous PIF pointer (or NULL for list start).
 *			   Caller is expected to iterate through all
 *			   potential matches to find interface of interest.
 *
 *   input: uint16_t: the interface index (truncated)
 *	    dhcp_pif_t *: the previous PIF, or NULL for list start
 *	    boolean_t: B_TRUE if using DHCPv6, B_FALSE otherwise
 *  output: dhcp_pif_t *: the next matching PIF, or NULL if not found
 *    note: This operates using the 'truncated' (16-bit) ifindex as seen by
 *	    routing socket clients.  The value stored in pif_index is the
 *	    32-bit ifindex from the ioctl interface.
 */

dhcp_pif_t *
lookup_pif_by_uindex(uint16_t ifindex, dhcp_pif_t *pif, boolean_t isv6)
{
	if (pif == NULL)
		pif = isv6 ? v6root : v4root;
	else
		pif = pif->pif_next;

	for (; pif != NULL; pif = pif->pif_next) {
		if ((pif->pif_index & 0xffff) == ifindex)
			break;
	}

	return (pif);
}

/*
 * lookup_pif_by_name(): Looks up a physical interface entry given a name.
 *
 *   input: const char *: the physical interface name
 *	    boolean_t: B_TRUE if using DHCPv6, B_FALSE otherwise
 *  output: dhcp_pif_t *: the matching PIF, or NULL if not found
 */

dhcp_pif_t *
lookup_pif_by_name(const char *pname, boolean_t isv6)
{
	dhcp_pif_t *pif;

	pif = isv6 ? v6root : v4root;

	for (; pif != NULL; pif = pif->pif_next) {
		if (strcmp(pif->pif_name, pname) == 0)
			break;
	}

	return (pif);
}

/*
 * pif_status(): update the physical interface up/down status.
 *
 *   input: dhcp_pif_t *: the physical interface to be updated
 *	    boolean_t: B_TRUE if the interface is going up
 *  output: none
 */

void
pif_status(dhcp_pif_t *pif, boolean_t isup)
{
	dhcp_lif_t *lif;
	dhcp_smach_t *dsmp;

	pif->pif_running = isup;
	dhcpmsg(MSG_DEBUG, "interface %s has %s", pif->pif_name,
	    isup ? "come back up" : "gone down");
	for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) {
		for (dsmp = lif->lif_smachs; dsmp != NULL;
		    dsmp = dsmp->dsm_next) {
			if (isup)
				refresh_smach(dsmp);
			else
				remove_default_routes(dsmp);
		}
	}
}

/* Helper for insert_lif: extract addresses as defined */
#define	ASSIGN_ADDR(v4, v6, lf) \
	if (pif->pif_isv6) { \
		lif->v6 = ((struct sockaddr_in6 *)&lifr.lf)->sin6_addr; \
	} else { \
		lif->v4 = ((struct sockaddr_in *)&lifr.lf)->sin_addr.s_addr; \
	}

/*
 * insert_lif(): Creates a new logical interface structure and chains it on
 *		 the list for a given physical interface.  Initializes state
 *		 that remains consistent across all use of the logical
 *		 interface entry.  Caller's PIF hold is transferred to the
 *		 LIF on success, and is dropped on failure.
 *
 *   input: dhcp_pif_t *: pointer to the physical interface for this LIF
 *	    const char *: the name of the logical interface
 *	    int *: ignored on input; if insert_pif fails, set to a DHCP_IPC_E_*
 *		   error code with the reason why
 *  output: dhcp_lif_t *: a pointer to the new entry, or NULL on failure
 */

dhcp_lif_t *
insert_lif(dhcp_pif_t *pif, const char *lname, int *error)
{
	dhcp_lif_t *lif;
	int fd;
	struct lifreq lifr;

	if ((lif = calloc(1, sizeof (*lif))) == NULL) {
		dhcpmsg(MSG_ERR, "insert_lif: cannot allocate lif entry for "
		    "%s", lname);
		*error = DHCP_IPC_E_MEMORY;
		return (NULL);
	}

	lif->lif_sock_ip_fd = -1;
	lif->lif_packet_id = -1;
	lif->lif_iaid_id = -1;
	lif->lif_hold_count = 1;
	lif->lif_pif = pif;
	lif->lif_removed = B_TRUE;
	init_timer(&lif->lif_preferred, 0);
	init_timer(&lif->lif_expire, 0);

	if (strlcpy(lif->lif_name, lname, LIFNAMSIZ) >= LIFNAMSIZ) {
		dhcpmsg(MSG_ERROR, "insert_lif: interface name %s is too long",
		    lname);
		*error = DHCP_IPC_E_INVIF;
		goto failure;
	}

	(void) strlcpy(lifr.lifr_name, lname, LIFNAMSIZ);

	fd = pif->pif_isv6 ? v6_sock_fd : v4_sock_fd;

	if (ioctl(fd, SIOCGLIFMTU, &lifr) == -1)
		lif->lif_max = 1024;
	else
		lif->lif_max = lifr.lifr_mtu;

	if (ioctl(fd, SIOCGLIFADDR, &lifr) == -1) {
		if (errno == ENXIO)
			*error = DHCP_IPC_E_INVIF;
		else
			*error = DHCP_IPC_E_INT;
		dhcpmsg(MSG_ERR, "insert_lif: SIOCGLIFADDR for %s", lname);
		goto failure;
	}
	ASSIGN_ADDR(lif_addr, lif_v6addr, lifr_addr);

	if (ioctl(fd, SIOCGLIFNETMASK, &lifr) == -1) {
		if (errno == ENXIO)
			*error = DHCP_IPC_E_INVIF;
		else
			*error = DHCP_IPC_E_INT;
		dhcpmsg(MSG_ERR, "insert_lif: SIOCGLIFNETMASK for %s", lname);
		goto failure;
	}
	ASSIGN_ADDR(lif_netmask, lif_v6mask, lifr_addr);

	if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) {
		*error = DHCP_IPC_E_INT;
		dhcpmsg(MSG_ERR, "insert_lif: SIOCGLIFFLAGS for %s", lname);
		goto failure;
	}
	lif->lif_flags = lifr.lifr_flags;

	/*
	 * If we've just detected the interface going up or down, then signal
	 * an appropriate action.  There may be other state machines here.
	 */
	if ((lifr.lifr_flags & IFF_RUNNING) && !pif->pif_running) {
		pif_status(pif, B_TRUE);
	} else if (!(lifr.lifr_flags & IFF_RUNNING) && pif->pif_running) {
		pif_status(pif, B_FALSE);
	}

	if (lifr.lifr_flags & IFF_POINTOPOINT) {
		if (ioctl(fd, SIOCGLIFDSTADDR, &lifr) == -1) {
			*error = DHCP_IPC_E_INT;
			dhcpmsg(MSG_ERR, "insert_lif: SIOCGLIFDSTADDR for %s",
			    lname);
			goto failure;
		}
		ASSIGN_ADDR(lif_peer, lif_v6peer, lifr_dstaddr);
	} else if (!pif->pif_isv6 && (lifr.lifr_flags & IFF_BROADCAST)) {
		if (ioctl(fd, SIOCGLIFBRDADDR, &lifr) == -1) {
			*error = DHCP_IPC_E_INT;
			dhcpmsg(MSG_ERR, "insert_lif: SIOCGLIFBRDADDR for %s",
			    lname);
			goto failure;
		}
		lif->lif_broadcast =
		    ((struct sockaddr_in *)&lifr.lifr_broadaddr)->sin_addr.
		    s_addr;
	}

	if (pif->pif_isv6)
		cached_v6_max_mtu = 0;
	else
		cached_v4_max_mtu = 0;

	lif->lif_removed = B_FALSE;
	insque(lif, &pif->pif_lifs);

	return (lif);

failure:
	release_lif(lif);
	return (NULL);
}

/*
 * hold_lif(): acquire a hold on a logical interface structure.
 *
 *   input: dhcp_lif_t *: a pointer to the LIF structure
 *  output: none
 */

void
hold_lif(dhcp_lif_t *lif)
{
	lif->lif_hold_count++;
	dhcpmsg(MSG_DEBUG2, "hold_lif: hold count on %s: %u", lif->lif_name,
	    lif->lif_hold_count);
}

/*
 * release_lif(): release a hold on a logical interface structure; will
 *		  destroy the structure on the last hold removed.
 *
 *   input: dhcp_lif_t *: a pointer to the LIF structure
 *  output: none
 */

void
release_lif(dhcp_lif_t *lif)
{
	if (lif->lif_hold_count == 0) {
		dhcpmsg(MSG_CRIT, "release_lif: extraneous release on %s",
		    lif->lif_name);
		return;
	}

	if (lif->lif_hold_count == 1 && !lif->lif_removed) {
		unplumb_lif(lif);
		return;
	}

	if (--lif->lif_hold_count == 0) {
		dhcp_pif_t *pif;

		dhcpmsg(MSG_DEBUG, "release_lif: freeing LIF %s",
		    lif->lif_name);

		if (lif->lif_lease != NULL)
			dhcpmsg(MSG_CRIT,
			    "release_lif: still holding lease at last hold!");
		close_ip_lif(lif);
		pif = lif->lif_pif;
		if (pif->pif_isv6)
			cached_v6_max_mtu = 0;
		else
			cached_v4_max_mtu = 0;
		release_pif(pif);
		free(lif);
	} else {
		dhcpmsg(MSG_DEBUG2, "release_lif: hold count on %s: %u",
		    lif->lif_name, lif->lif_hold_count);
	}
}

/*
 * remove_lif(): remove a logical interface from its PIF and lease (if any) and
 *		 the lease's hold on the LIF.  Assumes that we did not plumb
 *		 the interface.
 *
 *   input: dhcp_lif_t *: a pointer to the LIF structure
 *  output: none
 */

void
remove_lif(dhcp_lif_t *lif)
{
	if (lif->lif_plumbed) {
		dhcpmsg(MSG_CRIT, "remove_lif: attempted invalid removal of %s",
		    lif->lif_name);
		return;
	}
	if (lif->lif_removed) {
		dhcpmsg(MSG_CRIT, "remove_lif: extraneous removal of %s",
		    lif->lif_name);
	} else {
		dhcp_lif_t *lifnext;
		dhcp_lease_t *dlp;

		dhcpmsg(MSG_DEBUG2, "remove_lif: removing %s", lif->lif_name);
		lif->lif_removed = B_TRUE;
		lifnext = lif->lif_next;
		clear_lif_dhcp(lif);
		cancel_lif_timers(lif);
		if (lif->lif_iaid_id != -1 &&
		    iu_cancel_timer(tq, lif->lif_iaid_id, NULL) == 1) {
			lif->lif_iaid_id = -1;
			release_lif(lif);
		}

		/* Remove from PIF list */
		remque(lif);

		/* If we were part of a lease, then remove ourselves */
		if ((dlp = lif->lif_lease) != NULL) {
			if (--dlp->dl_nlifs == 0)
				dlp->dl_lifs = NULL;
			else if (dlp->dl_lifs == lif)
				dlp->dl_lifs = lifnext;
			if (lif->lif_declined != NULL) {
				dlp->dl_smach->dsm_lif_down--;
				lif->lif_declined = NULL;
			}
			if (lif->lif_dad_wait) {
				lif->lif_dad_wait = _B_FALSE;
				dlp->dl_smach->dsm_lif_wait--;
			}
			lif->lif_lease = NULL;
			release_lif(lif);
		}
	}
}

/*
 * lookup_lif_by_name(): Looks up a logical interface entry given a name and
 *			 a physical interface.
 *
 *   input: const char *: the logical interface name
 *	    const dhcp_pif_t *: the physical interface
 *  output: dhcp_lif_t *: the matching LIF, or NULL if not found
 */

dhcp_lif_t *
lookup_lif_by_name(const char *lname, const dhcp_pif_t *pif)
{
	dhcp_lif_t *lif;

	for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) {
		if (strcmp(lif->lif_name, lname) == 0)
			break;
	}

	return (lif);
}

/*
 * checkaddr(): checks if the given address is still set on the given LIF
 *
 *   input: const dhcp_lif_t *: the LIF to check
 *	    int: the address to look up on the interface (ioctl)
 *	    const in6_addr_t *: the address to compare to
 *	    const char *: name of the address for logging purposes
 *  output: boolean_t: B_TRUE if the address is still set; B_FALSE if not
 */

static boolean_t
checkaddr(const dhcp_lif_t *lif, int ioccmd, const in6_addr_t *addr,
    const char *aname)
{
	boolean_t isv6;
	int fd;
	struct lifreq lifr;
	char abuf1[INET6_ADDRSTRLEN];
	char abuf2[INET6_ADDRSTRLEN];

	(void) memset(&lifr, 0, sizeof (struct lifreq));
	(void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ);

	isv6 = lif->lif_pif->pif_isv6;
	fd = isv6 ? v6_sock_fd : v4_sock_fd;

	if (ioctl(fd, ioccmd, &lifr) == -1) {
		if (errno == ENXIO) {
			dhcpmsg(MSG_WARNING, "checkaddr: interface %s is gone",
			    lif->lif_name);
			return (B_FALSE);
		}
		dhcpmsg(MSG_DEBUG,
		    "checkaddr: ignoring ioctl error on %s %x: %s",
		    lif->lif_name, ioccmd, strerror(errno));
	} else if (isv6) {
		struct sockaddr_in6 *sin6 =
		    (struct sockaddr_in6 *)&lifr.lifr_addr;

		if (!IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, addr)) {
			dhcpmsg(MSG_WARNING,
			    "checkaddr: expected %s %s on %s, have %s", aname,
			    inet_ntop(AF_INET6, addr, abuf1, sizeof (abuf1)),
			    lif->lif_name, inet_ntop(AF_INET6, &sin6->sin6_addr,
			    abuf2, sizeof (abuf2)));
			return (B_FALSE);
		}
	} else {
		struct sockaddr_in *sinp =
		    (struct sockaddr_in *)&lifr.lifr_addr;
		ipaddr_t v4addr;

		IN6_V4MAPPED_TO_IPADDR(addr, v4addr);
		if (sinp->sin_addr.s_addr != v4addr) {
			dhcpmsg(MSG_WARNING,
			    "checkaddr: expected %s %s on %s, have %s", aname,
			    inet_ntop(AF_INET, &v4addr, abuf1, sizeof (abuf1)),
			    lif->lif_name, inet_ntop(AF_INET, &sinp->sin_addr,
			    abuf2, sizeof (abuf2)));
			return (B_FALSE);
		}
	}
	return (B_TRUE);
}

/*
 * verify_lif(): verifies than a LIF is still valid (i.e., has not been
 *		 explicitly or implicitly dropped or released)
 *
 *   input: const dhcp_lif_t *: the LIF to verify
 *  output: boolean_t: B_TRUE if the LIF is still valid, B_FALSE otherwise
 */

boolean_t
verify_lif(const dhcp_lif_t *lif)
{
	boolean_t isv6;
	int fd;
	struct lifreq lifr;
	dhcp_pif_t *pif = lif->lif_pif;

	(void) memset(&lifr, 0, sizeof (struct lifreq));
	(void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ);

	isv6 = pif->pif_isv6;
	fd = isv6 ? v6_sock_fd : v4_sock_fd;

	if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) {
		if (errno != ENXIO) {
			dhcpmsg(MSG_ERR,
			    "verify_lif: SIOCGLIFFLAGS failed on %s",
			    lif->lif_name);
		}
		return (B_FALSE);
	}

	/*
	 * If important flags have changed, then abandon the interface.
	 */
	if ((lif->lif_flags ^ lifr.lifr_flags) & DHCP_IFF_WATCH) {
		dhcpmsg(MSG_DEBUG, "verify_lif: unexpected flag change on %s: "
		    "%llx to %llx (%llx)", lif->lif_name, lif->lif_flags,
		    lifr.lifr_flags, (lif->lif_flags ^ lifr.lifr_flags) &
		    DHCP_IFF_WATCH);
		return (B_FALSE);
	}

	/*
	 * Check for delete and recreate.
	 */
	if (ioctl(fd, SIOCGLIFINDEX, &lifr) == -1) {
		if (errno != ENXIO) {
			dhcpmsg(MSG_ERR, "verify_lif: SIOCGLIFINDEX failed "
			    "on %s", lif->lif_name);
		}
		return (B_FALSE);
	}
	if (lifr.lifr_index != pif->pif_index) {
		dhcpmsg(MSG_DEBUG,
		    "verify_lif: ifindex on %s changed: %u to %u",
		    lif->lif_name, pif->pif_index, lifr.lifr_index);
		return (B_FALSE);
	}

	if (pif->pif_under_ipmp) {
		(void) strlcpy(lifr.lifr_name, pif->pif_grifname, LIFNAMSIZ);

		if (ioctl(fd, SIOCGLIFINDEX, &lifr) == -1) {
			if (errno != ENXIO) {
				dhcpmsg(MSG_ERR, "verify_lif: SIOCGLIFINDEX "
				    "failed on %s", lifr.lifr_name);
			}
			return (B_FALSE);
		}

		if (lifr.lifr_index != pif->pif_grindex) {
			dhcpmsg(MSG_DEBUG, "verify_lif: IPMP group ifindex "
			    "on %s changed: %u to %u", lifr.lifr_name,
			    pif->pif_grindex, lifr.lifr_index);
			return (B_FALSE);
		}
	}

	/*
	 * If the IP address, netmask, or broadcast address have changed, or
	 * the interface has been unplumbed, then we act like there has been an
	 * implicit drop.  (Note that the netmask is under DHCP control for
	 * IPv4, but not for DHCPv6, and that IPv6 doesn't have broadcast
	 * addresses.)
	 */

	if (!checkaddr(lif, SIOCGLIFADDR, &lif->lif_v6addr, "local address"))
		return (B_FALSE);

	if (isv6) {
		/*
		 * If it's not point-to-point, we're done.  If it is, then
		 * check the peer's address as well.
		 */
		return (!(lif->lif_flags & IFF_POINTOPOINT) ||
		    checkaddr(lif, SIOCGLIFDSTADDR, &lif->lif_v6peer,
		    "peer address"));
	} else {
		if (!checkaddr(lif, SIOCGLIFNETMASK, &lif->lif_v6mask,
		    "netmask"))
			return (B_FALSE);

		return (checkaddr(lif,
		    (lif->lif_flags & IFF_POINTOPOINT) ? SIOCGLIFDSTADDR :
		    SIOCGLIFBRDADDR, &lif->lif_v6peer, "peer address"));
	}
}

/*
 * canonize_lif(): puts the interface in a canonical (zeroed) form.  This is
 *		   used only on the "main" LIF for IPv4.  All other interfaces
 *		   are under dhcpagent control and are removed using
 *		   unplumb_lif().
 *
 *   input: dhcp_lif_t *: the interface to canonize
 *	    boolean_t: only canonize lif if it's under DHCP control
 *  output: none
 */

static void
canonize_lif(dhcp_lif_t *lif, boolean_t dhcponly)
{
	boolean_t isv6;
	int fd;
	struct lifreq lifr;

	/*
	 * If there's nothing here, then don't touch the interface.  This can
	 * happen when an already-canonized LIF is recanonized.
	 */
	if (IN6_IS_ADDR_UNSPECIFIED(&lif->lif_v6addr))
		return;

	isv6 = lif->lif_pif->pif_isv6;
	dhcpmsg(MSG_VERBOSE, "canonizing IPv%d interface %s",
	    isv6 ? 6 : 4, lif->lif_name);

	lif->lif_v6addr = my_in6addr_any;
	lif->lif_v6mask = my_in6addr_any;
	lif->lif_v6peer = my_in6addr_any;

	(void) memset(&lifr, 0, sizeof (struct lifreq));
	(void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ);

	fd = isv6 ? v6_sock_fd : v4_sock_fd;

	if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) {
		if (errno != ENXIO) {
			dhcpmsg(MSG_ERR, "canonize_lif: can't get flags for %s",
			    lif->lif_name);
		}
		return;
	}
	lif->lif_flags = lifr.lifr_flags;

	if (dhcponly && !(lifr.lifr_flags & IFF_DHCPRUNNING)) {
		dhcpmsg(MSG_INFO,
		    "canonize_lif: cannot clear %s; flags are %llx",
		    lif->lif_name, lifr.lifr_flags);
		return;
	}

	(void) memset(&lifr.lifr_addr, 0, sizeof (lifr.lifr_addr));
	if (isv6) {
		struct sockaddr_in6 *sin6 =
		    (struct sockaddr_in6 *)&lifr.lifr_addr;

		sin6->sin6_family = AF_INET6;
		sin6->sin6_addr = my_in6addr_any;
	} else {
		struct sockaddr_in *sinv =
		    (struct sockaddr_in *)&lifr.lifr_addr;

		sinv->sin_family = AF_INET;
		sinv->sin_addr.s_addr = htonl(INADDR_ANY);
	}

	if (ioctl(fd, SIOCSLIFADDR, &lifr) == -1) {
		dhcpmsg(MSG_ERR,
		    "canonize_lif: can't clear local address on %s",
		    lif->lif_name);
	}

	/* Clearing the address means that we're no longer waiting on DAD */
	if (lif->lif_dad_wait) {
		lif->lif_dad_wait = _B_FALSE;
		lif->lif_lease->dl_smach->dsm_lif_wait--;
	}

	if (lif->lif_flags & IFF_POINTOPOINT) {
		if (ioctl(fd, SIOCSLIFDSTADDR, &lifr) == -1) {
			dhcpmsg(MSG_ERR,
			    "canonize_lif: can't clear remote address on %s",
			    lif->lif_name);
		}
	} else if (!isv6) {
		if (ioctl(fd, SIOCSLIFBRDADDR, &lifr) == -1) {
			dhcpmsg(MSG_ERR,
			    "canonize_lif: can't clear broadcast address on %s",
			    lif->lif_name);
		}
	}

	/*
	 * Clear the netmask last as it has to be refetched after clearing.
	 * Netmask is under in.ndpd control with IPv6.
	 */
	if (!isv6) {
		/* Clear the netmask */
		if (ioctl(fd, SIOCSLIFNETMASK, &lifr) == -1) {
			dhcpmsg(MSG_ERR,
			    "canonize_lif: can't clear netmask on %s",
			    lif->lif_name);
		} else  {
			/*
			 * When the netmask is cleared, the kernel actually sets
			 * the netmask to 255.0.0.0.  So, refetch that netmask.
			 */
			if (ioctl(fd, SIOCGLIFNETMASK, &lifr) == -1) {
				dhcpmsg(MSG_ERR,
				    "canonize_lif: can't reload cleared "
				    "netmask on %s", lif->lif_name);
			} else {
				/* Refetch succeeded, update LIF */
				lif->lif_netmask =
				    ((struct sockaddr_in *)&lifr.lifr_addr)->
				    sin_addr.s_addr;
			}
		}
	}
}

/*
 * plumb_lif(): Adds the LIF to the system.  This is used for all
 *		DHCPv6-derived interfaces.  The returned LIF has a hold
 *		on it.  The caller (configure_v6_leases) deals with the DAD
 *		wait counters.
 *
 *   input: dhcp_lif_t *: the interface to unplumb
 *  output: none
 */

dhcp_lif_t *
plumb_lif(dhcp_pif_t *pif, const in6_addr_t *addr)
{
	dhcp_lif_t *lif;
	char abuf[INET6_ADDRSTRLEN];
	struct lifreq lifr;
	struct sockaddr_in6 *sin6;
	int error;

	(void) inet_ntop(AF_INET6, addr, abuf, sizeof (abuf));

	for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) {
		if (IN6_ARE_ADDR_EQUAL(&lif->lif_v6addr, addr)) {
			dhcpmsg(MSG_ERR,
			    "plumb_lif: entry for %s already exists!", abuf);
			return (NULL);
		}
	}

	/* First, create a new zero-address logical interface */
	(void) memset(&lifr, 0, sizeof (lifr));
	(void) strlcpy(lifr.lifr_name, pif->pif_name, sizeof (lifr.lifr_name));
	if (ioctl(v6_sock_fd, SIOCLIFADDIF, &lifr) == -1) {
		dhcpmsg(MSG_ERR, "plumb_lif: SIOCLIFADDIF %s", pif->pif_name);
		return (NULL);
	}

	/* Next, set the netmask to all ones */
	sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr;
	sin6->sin6_family = AF_INET6;
	(void) memset(&sin6->sin6_addr, 0xff, sizeof (sin6->sin6_addr));
	if (ioctl(v6_sock_fd, SIOCSLIFNETMASK, &lifr) == -1) {
		dhcpmsg(MSG_ERR, "plumb_lif: SIOCSLIFNETMASK %s",
		    lifr.lifr_name);
		goto failure;
	}

	/* Now set the interface address */
	sin6->sin6_addr = *addr;
	if (ioctl(v6_sock_fd, SIOCSLIFADDR, &lifr) == -1) {
		dhcpmsg(MSG_ERR, "plumb_lif: SIOCSLIFADDR %s %s",
		    lifr.lifr_name, abuf);
		goto failure;
	}

	/* Mark the interface up */
	if (ioctl(v6_sock_fd, SIOCGLIFFLAGS, &lifr) == -1) {
		dhcpmsg(MSG_ERR, "plumb_lif: SIOCGLIFFLAGS %s",
		    lifr.lifr_name);
		goto failure;
	}

	/*
	 * See comment in set_lif_dhcp().
	 */
	if (pif->pif_under_ipmp && !(lifr.lifr_flags & IFF_NOFAILOVER))
		lifr.lifr_flags |= IFF_NOFAILOVER | IFF_DEPRECATED;

	lifr.lifr_flags |= IFF_UP | IFF_DHCPRUNNING;
	if (ioctl(v6_sock_fd, SIOCSLIFFLAGS, &lifr) == -1) {
		dhcpmsg(MSG_ERR, "plumb_lif: SIOCSLIFFLAGS %s",
		    lifr.lifr_name);
		goto failure;
	}

	/* Now we can create the internal LIF structure */
	hold_pif(pif);
	if ((lif = insert_lif(pif, lifr.lifr_name, &error)) == NULL)
		goto failure;

	dhcpmsg(MSG_DEBUG, "plumb_lif: plumbed up %s on %s", abuf,
	    lif->lif_name);
	lif->lif_plumbed = B_TRUE;

	return (lif);

failure:
	if (ioctl(v6_sock_fd, SIOCLIFREMOVEIF, &lifr) == -1 &&
	    errno != ENXIO) {
		dhcpmsg(MSG_ERR, "plumb_lif: SIOCLIFREMOVEIF %s",
		    lifr.lifr_name);
	}
	return (NULL);
}

/*
 * unplumb_lif(): Removes the LIF from dhcpagent and the system.  This is used
 *		  for all interfaces configured by DHCP (those in leases).
 *
 *   input: dhcp_lif_t *: the interface to unplumb
 *  output: none
 */

void
unplumb_lif(dhcp_lif_t *lif)
{
	dhcp_lease_t *dlp;

	if (lif->lif_plumbed) {
		struct lifreq lifr;

		(void) memset(&lifr, 0, sizeof (lifr));
		(void) strlcpy(lifr.lifr_name, lif->lif_name,
		    sizeof (lifr.lifr_name));
		if (ioctl(v6_sock_fd, SIOCLIFREMOVEIF, &lifr) == -1 &&
		    errno != ENXIO) {
			dhcpmsg(MSG_ERR, "unplumb_lif: SIOCLIFREMOVEIF %s",
			    lif->lif_name);
		}
		lif->lif_plumbed = B_FALSE;
	}

	/*
	 * Special case: if we're "unplumbing" the main LIF for DHCPv4, then
	 * just canonize it and remove it from the lease.  The DAD wait flags
	 * are handled by canonize_lif or by remove_lif.
	 */
	if ((dlp = lif->lif_lease) != NULL && dlp->dl_smach->dsm_lif == lif) {
		canonize_lif(lif, B_TRUE);
		cancel_lif_timers(lif);
		if (lif->lif_declined != NULL) {
			dlp->dl_smach->dsm_lif_down--;
			lif->lif_declined = NULL;
		}
		dlp->dl_nlifs = 0;
		dlp->dl_lifs = NULL;
		lif->lif_lease = NULL;
		release_lif(lif);
	} else {
		remove_lif(lif);
	}
}

/*
 * attach_lif(): create a new logical interface, creating the physical
 *		 interface as necessary.
 *
 *   input: const char *: the logical interface name
 *	    boolean_t: B_TRUE for IPv6
 *	    int *: set to DHCP_IPC_E_* if creation fails
 *  output: dhcp_lif_t *: pointer to new entry, or NULL on failure
 */

dhcp_lif_t *
attach_lif(const char *lname, boolean_t isv6, int *error)
{
	dhcp_pif_t *pif;
	char pname[LIFNAMSIZ], *cp;

	(void) strlcpy(pname, lname, sizeof (pname));
	if ((cp = strchr(pname, ':')) != NULL)
		*cp = '\0';

	if ((pif = lookup_pif_by_name(pname, isv6)) != NULL)
		hold_pif(pif);
	else if ((pif = insert_pif(pname, isv6, error)) == NULL)
		return (NULL);

	if (lookup_lif_by_name(lname, pif) != NULL) {
		dhcpmsg(MSG_ERROR, "attach_lif: entry for %s already exists!",
		    lname);
		release_pif(pif);
		*error = DHCP_IPC_E_INVIF;
		return (NULL);
	}

	/* If LIF creation fails, then insert_lif discards our PIF hold */
	return (insert_lif(pif, lname, error));
}

/*
 * set_lif_dhcp(): Set logical interface flags to show that it's managed
 *		   by DHCP.
 *
 *   input: dhcp_lif_t *: the logical interface
 *  output: int: set to DHCP_IPC_E_* if operation fails
 */

int
set_lif_dhcp(dhcp_lif_t *lif)
{
	int fd;
	int err;
	struct lifreq lifr;
	dhcp_pif_t *pif = lif->lif_pif;

	fd = pif->pif_isv6 ? v6_sock_fd : v4_sock_fd;

	(void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ);

	if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) {
		err = errno;
		dhcpmsg(MSG_ERR, "set_lif_dhcp: SIOCGLIFFLAGS for %s",
		    lif->lif_name);
		return (err == ENXIO ? DHCP_IPC_E_INVIF : DHCP_IPC_E_INT);
	}
	lif->lif_flags = lifr.lifr_flags;

	/*
	 * Check for conflicting sources of address control, and other
	 * unacceptable configurations.
	 */
	if (lifr.lifr_flags & (IFF_LOOPBACK|IFF_ADDRCONF|IFF_TEMPORARY|
	    IFF_VIRTUAL)) {
		dhcpmsg(MSG_ERR, "set_lif_dhcp: cannot use %s: flags are %llx",
		    lif->lif_name, lifr.lifr_flags);
		return (DHCP_IPC_E_INVIF);
	}

	/*
	 * If IFF_DHCPRUNNING is already set on the interface and we're not
	 * adopting it, the agent probably crashed and burned.  Note it, but
	 * don't let it stop the proceedings (we're pretty sure we're not
	 * already running, since we were able to bind to our IPC port).
	 */
	if (lifr.lifr_flags & IFF_DHCPRUNNING) {
		dhcpmsg(MSG_VERBOSE, "set_lif_dhcp: IFF_DHCPRUNNING already set"
		    " on %s", lif->lif_name);
	} else {
		/*
		 * If the lif is on an interface under IPMP, IFF_NOFAILOVER
		 * must be set or the kernel will prevent us from setting
		 * IFF_DHCPRUNNING (since the subsequent IFF_UP would lead to
		 * migration).  We set IFF_DEPRECATED too since the kernel
		 * will set it automatically when setting IFF_NOFAILOVER,
		 * causing our lif_flags value to grow stale.
		 */
		if (pif->pif_under_ipmp && !(lifr.lifr_flags & IFF_NOFAILOVER))
			lifr.lifr_flags |= IFF_NOFAILOVER | IFF_DEPRECATED;

		lifr.lifr_flags |= IFF_DHCPRUNNING;
		if (ioctl(fd, SIOCSLIFFLAGS, &lifr) == -1) {
			dhcpmsg(MSG_ERR, "set_lif_dhcp: SIOCSLIFFLAGS for %s",
			    lif->lif_name);
			return (DHCP_IPC_E_INT);
		}
		lif->lif_flags = lifr.lifr_flags;
	}
	return (DHCP_IPC_SUCCESS);
}

/*
 * clear_lif_dhcp(): Clear logical interface flags to show that it's no longer
 *		     managed by DHCP.
 *
 *   input: dhcp_lif_t *: the logical interface
 *  output: none
 */

static void
clear_lif_dhcp(dhcp_lif_t *lif)
{
	int fd;
	struct lifreq lifr;

	fd = lif->lif_pif->pif_isv6 ? v6_sock_fd : v4_sock_fd;

	(void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ);

	if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1)
		return;

	if (!(lifr.lifr_flags & IFF_DHCPRUNNING))
		return;

	lif->lif_flags = lifr.lifr_flags &= ~IFF_DHCPRUNNING;
	(void) ioctl(fd, SIOCSLIFFLAGS, &lifr);
}

/*
 * set_lif_deprecated(): Set the "deprecated" flag to tell users that this
 *			 address will be going away.  As the interface is
 *			 going away, we don't care if there are errors.
 *
 *   input: dhcp_lif_t *: the logical interface
 *  output: none
 */

void
set_lif_deprecated(dhcp_lif_t *lif)
{
	int fd;
	struct lifreq lifr;

	if (lif->lif_flags & IFF_DEPRECATED)
		return;

	fd = lif->lif_pif->pif_isv6 ? v6_sock_fd : v4_sock_fd;

	(void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ);

	if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1)
		return;

	if (lifr.lifr_flags & IFF_DEPRECATED)
		return;

	lifr.lifr_flags |= IFF_DEPRECATED;
	(void) ioctl(fd, SIOCSLIFFLAGS, &lifr);
	lif->lif_flags = lifr.lifr_flags;
}

/*
 * clear_lif_deprecated(): Clear the "deprecated" flag to tell users that this
 *			   address will not be going away.  This happens if we
 *			   get a renewal after preferred lifetime but before
 *			   the valid lifetime.
 *
 *   input: dhcp_lif_t *: the logical interface
 *  output: boolean_t: B_TRUE on success.
 */

boolean_t
clear_lif_deprecated(dhcp_lif_t *lif)
{
	int fd;
	struct lifreq lifr;

	fd = lif->lif_pif->pif_isv6 ? v6_sock_fd : v4_sock_fd;

	(void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ);

	if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) {
		dhcpmsg(MSG_ERR, "clear_lif_deprecated: SIOCGLIFFLAGS for %s",
		    lif->lif_name);
		return (B_FALSE);
	}

	/*
	 * Check for conflicting sources of address control, and other
	 * unacceptable configurations.
	 */
	if (lifr.lifr_flags & (IFF_LOOPBACK|IFF_ADDRCONF|IFF_TEMPORARY|
	    IFF_VIRTUAL)) {
		dhcpmsg(MSG_ERR, "clear_lif_deprecated: cannot use %s: flags "
		    "are %llx", lif->lif_name, lifr.lifr_flags);
		return (B_FALSE);
	}

	/*
	 * Don't try to clear IFF_DEPRECATED if this is a test address,
	 * since IPMP's use of IFF_DEPRECATED is not compatible with ours.
	 */
	if (lifr.lifr_flags & IFF_NOFAILOVER)
		return (B_TRUE);

	if (!(lifr.lifr_flags & IFF_DEPRECATED))
		return (B_TRUE);

	lifr.lifr_flags &= ~IFF_DEPRECATED;
	if (ioctl(fd, SIOCSLIFFLAGS, &lifr) == -1) {
		dhcpmsg(MSG_ERR, "clear_lif_deprecated: SIOCSLIFFLAGS for %s",
		    lif->lif_name);
		return (B_FALSE);
	} else {
		lif->lif_flags = lifr.lifr_flags;
		return (B_TRUE);
	}
}

/*
 * open_ip_lif(): open up an IP socket for I/O on a given LIF (v4 only).
 *
 *   input: dhcp_lif_t *: the logical interface to operate on
 *	    in_addr_t: the address the socket will be bound to (in hbo)
 *	    boolean_t: B_TRUE if the address should be brought up (if needed)
 *  output: boolean_t: B_TRUE if the socket was opened successfully.
 */

boolean_t
open_ip_lif(dhcp_lif_t *lif, in_addr_t addr_hbo, boolean_t bringup)
{
	const char *errmsg;
	struct lifreq lifr;
	int on = 1;
	uchar_t ttl = 255;
	uint32_t ifindex;
	dhcp_pif_t *pif = lif->lif_pif;

	if (lif->lif_sock_ip_fd != -1) {
		dhcpmsg(MSG_WARNING, "open_ip_lif: socket already open on %s",
		    lif->lif_name);
		return (B_FALSE);
	}

	lif->lif_sock_ip_fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (lif->lif_sock_ip_fd == -1) {
		errmsg = "cannot create v4 socket";
		goto failure;
	}

	if (!bind_sock(lif->lif_sock_ip_fd, IPPORT_BOOTPC, addr_hbo)) {
		errmsg = "cannot bind v4 socket";
		goto failure;
	}

	/*
	 * If we bound to INADDR_ANY, we have no IFF_UP source address to use.
	 * Thus, enable IP_UNSPEC_SRC so that we can send packets with an
	 * unspecified (0.0.0.0) address.  Also, enable IP_DHCPINIT_IF so that
	 * the IP module will accept unicast DHCP traffic regardless of the IP
	 * address it's sent to.  (We'll then figure out which packets are
	 * ours based on the xid.)
	 */
	if (addr_hbo == INADDR_ANY) {
		if (setsockopt(lif->lif_sock_ip_fd, IPPROTO_IP, IP_UNSPEC_SRC,
		    &on, sizeof (int)) == -1) {
			errmsg = "cannot set IP_UNSPEC_SRC";
			goto failure;
		}

		if (setsockopt(lif->lif_sock_ip_fd, IPPROTO_IP, IP_DHCPINIT_IF,
		    &pif->pif_index, sizeof (int)) == -1) {
			errmsg = "cannot set IP_DHCPINIT_IF";
			goto failure;
		}
	}

	/*
	 * Unfortunately, some hardware (such as the Linksys WRT54GC)
	 * decrements the TTL *prior* to accepting DHCP traffic destined
	 * for it.  To workaround this, tell IP to use a TTL of 255 for
	 * broadcast packets sent from this socket.
	 */
	if (setsockopt(lif->lif_sock_ip_fd, IPPROTO_IP, IP_BROADCAST_TTL, &ttl,
	    sizeof (uchar_t)) == -1) {
		errmsg = "cannot set IP_BROADCAST_TTL";
		goto failure;
	}

	ifindex = pif->pif_under_ipmp ? pif->pif_grindex : pif->pif_index;
	if (setsockopt(lif->lif_sock_ip_fd, IPPROTO_IP, IP_BOUND_IF, &ifindex,
	    sizeof (int)) == -1) {
		errmsg = "cannot set IP_BOUND_IF";
		goto failure;
	}

	(void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ);
	if (ioctl(v4_sock_fd, SIOCGLIFFLAGS, &lifr) == -1) {
		errmsg = "cannot get interface flags";
		goto failure;
	}

	/*
	 * If the lif is part of an interface under IPMP, IFF_NOFAILOVER must
	 * be set or the kernel will prevent us from setting IFF_DHCPRUNNING
	 * (since the subsequent IFF_UP would lead to migration).  We set
	 * IFF_DEPRECATED too since the kernel will set it automatically when
	 * setting IFF_NOFAILOVER, causing our lif_flags value to grow stale.
	 */
	if (pif->pif_under_ipmp && !(lifr.lifr_flags & IFF_NOFAILOVER)) {
		lifr.lifr_flags |= IFF_NOFAILOVER | IFF_DEPRECATED;
		if (ioctl(v4_sock_fd, SIOCSLIFFLAGS, &lifr) == -1) {
			errmsg = "cannot set IFF_NOFAILOVER";
			goto failure;
		}
	}
	lif->lif_flags = lifr.lifr_flags;

	/*
	 * If this is initial bringup, make sure the address we're acquiring a
	 * lease on is IFF_UP.
	 */
	if (bringup && !(lifr.lifr_flags & IFF_UP)) {
		/*
		 * Start from a clean slate.
		 */
		canonize_lif(lif, B_FALSE);

		lifr.lifr_flags |= IFF_UP;
		if (ioctl(v4_sock_fd, SIOCSLIFFLAGS, &lifr) == -1) {
			errmsg = "cannot bring up";
			goto failure;
		}
		lif->lif_flags = lifr.lifr_flags;

		/*
		 * When bringing 0.0.0.0 IFF_UP, the kernel changes the
		 * netmask to 255.0.0.0, so re-fetch our expected netmask.
		 */
		if (ioctl(v4_sock_fd, SIOCGLIFNETMASK, &lifr) == -1) {
			errmsg = "cannot get netmask";
			goto failure;
		}

		lif->lif_netmask =
		    ((struct sockaddr_in *)&lifr.lifr_addr)->sin_addr.s_addr;
	}

	/*
	 * Usually, bringing up the address we're acquiring a lease on is
	 * sufficient to allow packets to be sent and received via the
	 * IP_BOUND_IF we did earlier.  However, if we're acquiring a lease on
	 * an underlying IPMP interface, the group interface will be used for
	 * sending and receiving IP packets via IP_BOUND_IF.  Thus, ensure at
	 * least one address on the group interface is IFF_UP.
	 */
	if (bringup && pif->pif_under_ipmp) {
		(void) strlcpy(lifr.lifr_name, pif->pif_grifname, LIFNAMSIZ);
		if (ioctl(v4_sock_fd, SIOCGLIFFLAGS, &lifr) == -1) {
			errmsg = "cannot get IPMP group interface flags";
			goto failure;
		}

		if (!(lifr.lifr_flags & IFF_UP)) {
			lifr.lifr_flags |= IFF_UP;
			if (ioctl(v4_sock_fd, SIOCSLIFFLAGS, &lifr) == -1) {
				errmsg = "cannot bring up IPMP group interface";
				goto failure;
			}
		}
	}

	lif->lif_packet_id = iu_register_event(eh, lif->lif_sock_ip_fd, POLLIN,
	    dhcp_packet_lif, lif);
	if (lif->lif_packet_id == -1) {
		errmsg = "cannot register to receive DHCP packets";
		goto failure;
	}

	return (B_TRUE);
failure:
	dhcpmsg(MSG_ERR, "open_ip_lif: %s: %s", lif->lif_name, errmsg);
	close_ip_lif(lif);
	return (B_FALSE);
}

/*
 * close_ip_lif(): close an IP socket for I/O on a given LIF.
 *
 *   input: dhcp_lif_t *: the logical interface to operate on
 *  output: none
 */

void
close_ip_lif(dhcp_lif_t *lif)
{
	if (lif->lif_packet_id != -1) {
		(void) iu_unregister_event(eh, lif->lif_packet_id, NULL);
		lif->lif_packet_id = -1;
	}
	if (lif->lif_sock_ip_fd != -1) {
		(void) close(lif->lif_sock_ip_fd);
		lif->lif_sock_ip_fd = -1;
	}
}

/*
 * lif_mark_decline(): mark a LIF as having been declined due to a duplicate
 *		       address or some other conflict.  This is used in
 *		       send_declines() to report failure back to the server.
 *
 *   input: dhcp_lif_t *: the logical interface to operate on
 *	    const char *: text string explaining why the address is declined
 *  output: none
 */

void
lif_mark_decline(dhcp_lif_t *lif, const char *reason)
{
	if (lif->lif_declined == NULL) {
		dhcp_lease_t *dlp;

		lif->lif_declined = reason;
		if ((dlp = lif->lif_lease) != NULL)
			dlp->dl_smach->dsm_lif_down++;
	}
}

/*
 * schedule_lif_timer(): schedules the LIF-related timer
 *
 *   input: dhcp_lif_t *: the logical interface to operate on
 *	    dhcp_timer_t *: the timer to schedule
 *	    iu_tq_callback_t *: the callback to call upon firing
 *  output: boolean_t: B_TRUE if the timer was scheduled successfully
 */

boolean_t
schedule_lif_timer(dhcp_lif_t *lif, dhcp_timer_t *dt, iu_tq_callback_t *expire)
{
	/*
	 * If there's a timer running, cancel it and release its lease
	 * reference.
	 */
	if (dt->dt_id != -1) {
		if (!cancel_timer(dt))
			return (B_FALSE);
		release_lif(lif);
	}

	if (schedule_timer(dt, expire, lif)) {
		hold_lif(lif);
		return (B_TRUE);
	} else {
		dhcpmsg(MSG_WARNING,
		    "schedule_lif_timer: cannot schedule timer");
		return (B_FALSE);
	}
}

/*
 * cancel_lif_timer(): cancels a LIF-related timer
 *
 *   input: dhcp_lif_t *: the logical interface to operate on
 *	    dhcp_timer_t *: the timer to cancel
 *  output: none
 */

static void
cancel_lif_timer(dhcp_lif_t *lif, dhcp_timer_t *dt)
{
	if (dt->dt_id == -1)
		return;
	if (cancel_timer(dt)) {
		dhcpmsg(MSG_DEBUG2,
		    "cancel_lif_timer: canceled expiry timer on %s",
		    lif->lif_name);
		release_lif(lif);
	} else {
		dhcpmsg(MSG_WARNING,
		    "cancel_lif_timer: cannot cancel timer on %s",
		    lif->lif_name);
	}
}

/*
 * cancel_lif_timers(): cancels the LIF-related timers
 *
 *   input: dhcp_lif_t *: the logical interface to operate on
 *  output: none
 */

void
cancel_lif_timers(dhcp_lif_t *lif)
{
	cancel_lif_timer(lif, &lif->lif_preferred);
	cancel_lif_timer(lif, &lif->lif_expire);
}

/*
 * get_max_mtu(): find the maximum MTU of all interfaces for I/O on common
 *		  file descriptors (v4_sock_fd and v6_sock_fd).
 *
 *   input: boolean_t: B_TRUE for IPv6, B_FALSE for IPv4
 *  output: none
 */

uint_t
get_max_mtu(boolean_t isv6)
{
	uint_t *mtup = isv6 ? &cached_v6_max_mtu : &cached_v4_max_mtu;

	if (*mtup == 0) {
		dhcp_pif_t *pif;
		dhcp_lif_t *lif;
		struct lifreq lifr;

		/* Set an arbitrary lower bound */
		*mtup = 1024;
		pif = isv6 ? v6root : v4root;
		for (; pif != NULL; pif = pif->pif_next) {
			for (lif = pif->pif_lifs; lif != NULL;
			    lif = lif->lif_next) {
				(void) strlcpy(lifr.lifr_name, lif->lif_name,
				    LIFNAMSIZ);
				if (ioctl(v4_sock_fd, SIOCGLIFMTU, &lifr) !=
				    -1 && lifr.lifr_mtu > *mtup) {
					*mtup = lifr.lifr_mtu;
				}
			}
		}
	}
	return (*mtup);
}

/*
 * expired_lif_state(): summarize the state of expired LIFs on a given state
 *			machine.
 *
 *   input: dhcp_smach_t *: the state machine to scan
 *  output: dhcp_expire_t: overall state
 */

dhcp_expire_t
expired_lif_state(dhcp_smach_t *dsmp)
{
	dhcp_lease_t *dlp;
	dhcp_lif_t *lif;
	uint_t nlifs;
	uint_t numlifs;
	uint_t numexp;

	numlifs = numexp = 0;
	for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) {
		lif = dlp->dl_lifs;
		nlifs = dlp->dl_nlifs;
		numlifs += nlifs;
		for (; nlifs > 0; nlifs--, lif = lif->lif_next) {
			if (lif->lif_expired)
				numexp++;
		}
	}
	if (numlifs == 0)
		return (DHCP_EXP_NOLIFS);
	else if (numexp == 0)
		return (DHCP_EXP_NOEXP);
	else if (numlifs == numexp)
		return (DHCP_EXP_ALLEXP);
	else
		return (DHCP_EXP_SOMEEXP);
}

/*
 * find_expired_lif(): find the first expired LIF on a given state machine
 *
 *   input: dhcp_smach_t *: the state machine to scan
 *  output: dhcp_lif_t *: the first expired LIF, or NULL if none.
 */

dhcp_lif_t *
find_expired_lif(dhcp_smach_t *dsmp)
{
	dhcp_lease_t *dlp;
	dhcp_lif_t *lif;
	uint_t nlifs;

	for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) {
		lif = dlp->dl_lifs;
		nlifs = dlp->dl_nlifs;
		for (; nlifs > 0; nlifs--, lif = lif->lif_next) {
			if (lif->lif_expired)
				return (lif);
		}
	}
	return (NULL);
}

/*
 * remove_v6_strays(): remove any stray interfaces marked as DHCPRUNNING.  Used
 *		       only for DHCPv6.
 *
 *   input: none
 *  output: none
 */

void
remove_v6_strays(void)
{
	struct lifnum lifn;
	struct lifconf lifc;
	struct lifreq *lifrp, *lifrmax;
	uint_t numifs;
	uint64_t flags;

	/*
	 * Get the approximate number of interfaces in the system.  It's only
	 * approximate because the system is dynamic -- interfaces may be
	 * plumbed or unplumbed at any time.  This is also the reason for the
	 * "+ 10" fudge factor: we're trying to avoid unnecessary looping.
	 */
	(void) memset(&lifn, 0, sizeof (lifn));
	lifn.lifn_family = AF_INET6;
	lifn.lifn_flags = LIFC_ALLZONES | LIFC_NOXMIT | LIFC_TEMPORARY;
	if (ioctl(v6_sock_fd, SIOCGLIFNUM, &lifn) == -1) {
		dhcpmsg(MSG_ERR,
		    "remove_v6_strays: cannot read number of interfaces");
		numifs = 10;
	} else {
		numifs = lifn.lifn_count + 10;
	}

	/*
	 * Get the interface information.  We do this in a loop so that we can
	 * recover from EINVAL from the kernel -- delivered when the buffer is
	 * too small.
	 */
	(void) memset(&lifc, 0, sizeof (lifc));
	lifc.lifc_family = AF_INET6;
	lifc.lifc_flags = LIFC_ALLZONES | LIFC_NOXMIT | LIFC_TEMPORARY;
	for (;;) {
		lifc.lifc_len = numifs * sizeof (*lifrp);
		lifrp = realloc(lifc.lifc_buf, lifc.lifc_len);
		if (lifrp == NULL) {
			dhcpmsg(MSG_ERR,
			    "remove_v6_strays: cannot allocate memory");
			free(lifc.lifc_buf);
			return;
		}
		lifc.lifc_buf = (caddr_t)lifrp;
		errno = 0;
		if (ioctl(v6_sock_fd, SIOCGLIFCONF, &lifc) == 0 &&
		    lifc.lifc_len < numifs * sizeof (*lifrp))
			break;
		if (errno == 0 || errno == EINVAL) {
			numifs <<= 1;
		} else {
			dhcpmsg(MSG_ERR, "remove_v6_strays: SIOCGLIFCONF");
			free(lifc.lifc_buf);
			return;
		}
	}

	lifrmax = lifrp + lifc.lifc_len / sizeof (*lifrp);
	for (; lifrp < lifrmax; lifrp++) {
		/*
		 * Get the interface flags; we're interested in the DHCP ones.
		 */
		if (ioctl(v6_sock_fd, SIOCGLIFFLAGS, lifrp) == -1)
			continue;
		flags = lifrp->lifr_flags;
		if (!(flags & IFF_DHCPRUNNING))
			continue;
		/*
		 * If the interface has a link-local address, then we don't
		 * control it.  Just remove the flag.
		 */
		if (ioctl(v6_sock_fd, SIOCGLIFADDR, lifrp) == -1)
			continue;
		if (IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *)&lifrp->
		    lifr_addr)->sin6_addr)) {
			lifrp->lifr_flags = flags & ~IFF_DHCPRUNNING;
			(void) ioctl(v6_sock_fd, SIOCSLIFFLAGS, lifrp);
			continue;
		}
		/*
		 * All others are (or were) under our control.  Clean up by
		 * removing them.
		 */
		if (ioctl(v6_sock_fd, SIOCLIFREMOVEIF, lifrp) == 0) {
			dhcpmsg(MSG_DEBUG, "remove_v6_strays: removed %s",
			    lifrp->lifr_name);
		} else if (errno != ENXIO) {
			dhcpmsg(MSG_ERR,
			    "remove_v6_strays: SIOCLIFREMOVEIF %s",
			    lifrp->lifr_name);
		}
	}
	free(lifc.lifc_buf);
}