/*
 * 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 (c) 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2016, Chris Fraire <cfraire@me.com>.
 */

#include <arpa/inet.h>
#include <assert.h>
#include <libdlaggr.h>
#include <libdllink.h>
#include <libdlstat.h>
#include <libnwam.h>
#include <libscf.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <values.h>
#include <zone.h>

#include "conditions.h"
#include "events.h"
#include "objects.h"
#include "ncp.h"
#include "util.h"

/*
 * ncu.c - handles various NCU tasks - intialization/refresh, state machine
 * for NCUs etc.
 */

#define	VBOX_IFACE_PREFIX	"vboxnet"

static void populate_ip_ncu_properties(nwam_ncu_handle_t, nwamd_ncu_t *);

/*
 * Find ncu of specified type for link/interface name.
 */
nwamd_object_t
nwamd_ncu_object_find(nwam_ncu_type_t type, const char *name)
{
	nwam_error_t err;
	char *object_name;
	nwamd_object_t ncu_obj = NULL;

	if ((err = nwam_ncu_name_to_typed_name(name, type, &object_name))
	    != NWAM_SUCCESS) {
		nlog(LOG_ERR, "nwamd_ncu_find: nwam_ncu_name_to_typed_name "
		    "returned %s", nwam_strerror(err));
		return (NULL);
	}
	ncu_obj = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, object_name);

	free(object_name);
	return (ncu_obj);
}

nwam_error_t
nwamd_set_ncu_string(nwam_ncu_handle_t ncuh, char **strval, uint_t cnt,
    const char *prop)
{
	nwam_error_t err;
	nwam_value_t val;

	if ((err = nwam_value_create_string_array(strval, cnt, &val))
	    != NWAM_SUCCESS)
		return (err);
	err = nwam_ncu_set_prop_value(ncuh, prop, val);
	nwam_value_free(val);
	return (err);
}

nwam_error_t
nwamd_set_ncu_uint(nwam_ncu_handle_t ncuh, uint64_t *uintval, uint_t cnt,
    const char *prop)
{
	nwam_error_t err;
	nwam_value_t val;

	if ((err = nwam_value_create_uint64_array(uintval, cnt, &val))
	    != NWAM_SUCCESS)
		return (err);
	err = nwam_ncu_set_prop_value(ncuh, prop, val);
	nwam_value_free(val);
	return (err);
}

nwam_error_t
nwamd_get_ncu_string(nwam_ncu_handle_t ncuh, nwam_value_t *val, char ***strval,
    uint_t *cnt, const char *prop)
{
	nwam_error_t err;

	if ((err = nwam_ncu_get_prop_value(ncuh, prop, val)) != NWAM_SUCCESS)
		return (err);
	return (nwam_value_get_string_array(*val, strval, cnt));
}

nwam_error_t
nwamd_get_ncu_uint(nwam_ncu_handle_t ncuh, nwam_value_t *val,
    uint64_t **uintval, uint_t *cnt, const char *prop)
{
	nwam_error_t err;

	if ((err = nwam_ncu_get_prop_value(ncuh, prop, val)) != NWAM_SUCCESS)
		return (err);
	return (nwam_value_get_uint64_array(*val, uintval, cnt));
}

nwam_error_t
nwamd_get_ncu_boolean(nwam_ncu_handle_t ncuh, nwam_value_t *val,
    boolean_t **boolval, uint_t *cnt, const char *prop)
{
	nwam_error_t err;

	if ((err = nwam_ncu_get_prop_value(ncuh, prop, val)) != NWAM_SUCCESS)
		return (err);
	return (nwam_value_get_boolean_array(*val, boolval, cnt));
}

/*
 * Run link/interface state machine in response to a state change
 * or enable/disable action event.
 */
static void
nwamd_ncu_state_machine(const char *object_name)
{
	nwamd_object_t object;
	nwamd_ncu_t *ncu;
	link_state_t link_state;
	nwamd_event_t event;
	nwam_wlan_t key_wlan, connected_wlan;
	nwamd_link_t *link;
	char linkname[NWAM_MAX_NAME_LEN];
	boolean_t up;

	if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, object_name))
	    == NULL) {
		nlog(LOG_ERR, "nwamd_ncu_state_machine: "
		    "request for nonexistent NCU %s", object_name);
		return;
	}

	ncu = object->nwamd_object_data;
	link = &ncu->ncu_link;

	switch (object->nwamd_object_aux_state) {
	case NWAM_AUX_STATE_INITIALIZED:
		if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) {
			/*
			 * For wired/wireless links, need to get link
			 * up/down events and even if these are not supported,
			 * dlpi_open()ing the link prevents the driver from
			 * being unloaded.
			 */
			nwamd_dlpi_add_link(object);

			if (link->nwamd_link_media == DL_WIFI) {
				/*
				 * First, if we're unexpectedly connected,
				 * disconnect.
				 */
				if (!link->nwamd_link_wifi_connected &&
				    nwamd_wlan_connected(object)) {
					nlog(LOG_DEBUG,
					    "nwamd_ncu_state_machine: "
					    "WiFi unexpectedly connected, "
					    "disconnecting...");
					(void) dladm_wlan_disconnect(dld_handle,
					    link->nwamd_link_id);
					nwamd_set_selected_connected(ncu,
					    B_FALSE, B_FALSE);
				}
				/* move to scanning aux state */
				nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
				    object_name, object->nwamd_object_state,
				    NWAM_AUX_STATE_LINK_WIFI_SCANNING);
			} else {
				/*
				 * If initial wired link state is unknown, we
				 * will need to assume the link is up, since
				 * we won´t get DL_NOTE_LINK_UP/DOWN events.
				 */
				link_state = nwamd_get_link_state
				    (ncu->ncu_name);
				if (link_state == LINK_STATE_UP ||
				    link_state == LINK_STATE_UNKNOWN) {
					nwamd_object_set_state
					    (NWAM_OBJECT_TYPE_NCU,
					    object_name, NWAM_STATE_ONLINE,
					    NWAM_AUX_STATE_UP);
				} else {
					nwamd_object_set_state
					    (NWAM_OBJECT_TYPE_NCU,
					    object_name,
					    NWAM_STATE_ONLINE_TO_OFFLINE,
					    NWAM_AUX_STATE_DOWN);
				}
			}
		} else {
			/*
			 * In the current implementation, initialization has to
			 * start from scratch since the complexity of minimizing
			 * configuration change is considerable (e.g. if we
			 * refresh and had DHCP running on the physical
			 * interface, and now have changed to static assignment,
			 * we need to remove DHCP etc).  To avoid all this,
			 * unplumb before re-plumbing the protocols and
			 * addresses we wish to configure.  In the future, it
			 * would be good to try and minimize configuration
			 * changes.
			 */
			nwamd_unplumb_interface(ncu, AF_INET);
			nwamd_unplumb_interface(ncu, AF_INET6);

			/*
			 * We may be restarting the state machine.  Re-read
			 * the IP NCU properties as the ipadm_addrobj_t in
			 * nwamd_if_address should not be reused.
			 */
			populate_ip_ncu_properties(object->nwamd_object_handle,
			    ncu);

			/*
			 * Enqueue a WAITING_FOR_ADDR aux state change so that
			 * we are eligible to receive the IF_STATE events
			 * associated with static, DHCP, DHCPv6 and autoconf
			 * address assignment.  The latter two can happen
			 * quite quickly after plumbing so we need to be ready.
			 */
			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
			    object_name, NWAM_STATE_OFFLINE_TO_ONLINE,
			    NWAM_AUX_STATE_IF_WAITING_FOR_ADDR);

			if (ncu->ncu_if.nwamd_if_ipv4)
				nwamd_plumb_interface(ncu, AF_INET);

			if (ncu->ncu_if.nwamd_if_ipv6)
				nwamd_plumb_interface(ncu, AF_INET6);

			/* Configure addresses */
			nwamd_configure_interface_addresses(ncu);
		}
		break;

	case NWAM_AUX_STATE_IF_DHCP_TIMED_OUT:
	case NWAM_AUX_STATE_IF_WAITING_FOR_ADDR:
		/*
		 * nothing to do here - RTM_NEWADDRs will trigger IF_STATE
		 * events to move us online.
		 */
		break;

	case NWAM_AUX_STATE_LINK_WIFI_SCANNING:
		/* launch scan thread */
		(void) strlcpy(linkname, ncu->ncu_name, sizeof (linkname));
		(void) nwamd_wlan_scan(linkname);
		/* Create periodic scan event */
		nwamd_ncu_create_periodic_scan_event(object);
		break;

	case NWAM_AUX_STATE_LINK_WIFI_NEED_SELECTION:
		/* send "need choice" event */
		event = nwamd_event_init_wlan
		    (ncu->ncu_name, NWAM_EVENT_TYPE_WLAN_NEED_CHOICE, B_FALSE,
		    link->nwamd_link_wifi_scan.nwamd_wifi_scan_curr,
		    link->nwamd_link_wifi_scan.nwamd_wifi_scan_curr_num);
		if (event == NULL)
			break;
		nwamd_event_enqueue(event);
		nwamd_set_selected_connected(ncu, B_FALSE, B_FALSE);
		break;

	case NWAM_AUX_STATE_LINK_WIFI_NEED_KEY:
		/*
		 * Send "need key" event.  Set selected to true, connected
		 * and have_key to false.  Do not fill in WLAN details as
		 * multiple WLANs may match the ESSID name, and each may
		 * have a different speed and channel.
		 */
		bzero(&key_wlan, sizeof (key_wlan));
		(void) strlcpy(key_wlan.nww_essid, link->nwamd_link_wifi_essid,
		    sizeof (key_wlan.nww_essid));
		(void) strlcpy(key_wlan.nww_bssid, link->nwamd_link_wifi_bssid,
		    sizeof (key_wlan.nww_bssid));
		key_wlan.nww_security_mode =
		    link->nwamd_link_wifi_security_mode;
		key_wlan.nww_selected = B_TRUE;
		key_wlan.nww_connected = B_FALSE;
		key_wlan.nww_have_key = B_FALSE;
		event = nwamd_event_init_wlan
		    (ncu->ncu_name, NWAM_EVENT_TYPE_WLAN_NEED_KEY, B_FALSE,
		    &key_wlan, 1);
		if (event == NULL)
			break;
		nwamd_event_enqueue(event);
		break;

	case NWAM_AUX_STATE_LINK_WIFI_CONNECTING:
		(void) strlcpy(linkname, ncu->ncu_name, sizeof (linkname));
		nwamd_wlan_connect(linkname);
		break;

	case NWAM_AUX_STATE_UP:
	case NWAM_AUX_STATE_DOWN:
		up = (object->nwamd_object_aux_state == NWAM_AUX_STATE_UP);
		if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) {
			if (link->nwamd_link_media == DL_WIFI) {
				/*
				 * Connected/disconnected - send WLAN
				 * connection report.
				 */
				link->nwamd_link_wifi_connected = up;
				nwamd_set_selected_connected(ncu, B_TRUE, up);

				(void) strlcpy(connected_wlan.nww_essid,
				    link->nwamd_link_wifi_essid,
				    sizeof (connected_wlan.nww_essid));
				(void) strlcpy(connected_wlan.nww_bssid,
				    link->nwamd_link_wifi_bssid,
				    sizeof (connected_wlan.nww_bssid));
				connected_wlan.nww_security_mode =
				    link->nwamd_link_wifi_security_mode;
				event = nwamd_event_init_wlan
				    (ncu->ncu_name,
				    NWAM_EVENT_TYPE_WLAN_CONNECTION_REPORT, up,
				    &connected_wlan, 1);
				if (event == NULL)
					break;
				nwamd_event_enqueue(event);

				/*
				 * If disconnected, restart the state machine
				 * for the WiFi link (WiFi is always trying
				 * to connect).
				 *
				 * If connected, start signal strength
				 * monitoring thread.
				 */
				if (!up && ncu->ncu_enabled) {
					nlog(LOG_DEBUG,
					    "nwamd_ncu_state_machine: "
					    "wifi disconnect - start over "
					    "after %dsec interval",
					    WIRELESS_RETRY_INTERVAL);
					link->nwamd_link_wifi_connected =
					    B_FALSE;
					/* propogate down event to IP NCU */
					nwamd_propogate_link_up_down_to_ip
					    (ncu->ncu_name, B_FALSE);
					nwamd_object_set_state_timed
					    (NWAM_OBJECT_TYPE_NCU, object_name,
					    NWAM_STATE_OFFLINE_TO_ONLINE,
					    NWAM_AUX_STATE_INITIALIZED,
					    WIRELESS_RETRY_INTERVAL);
				} else {
					nlog(LOG_DEBUG,
					    "nwamd_ncu_state_machine: "
					    "wifi connected, start monitoring");
					(void) strlcpy(linkname, ncu->ncu_name,
					    sizeof (linkname));
					nwamd_wlan_monitor_signal(linkname);
				}
			}
		}

		/* If not in ONLINE/OFFLINE state yet, change state */
		if ((up && object->nwamd_object_state != NWAM_STATE_ONLINE) ||
		    (!up && object->nwamd_object_state != NWAM_STATE_OFFLINE)) {
			nlog(LOG_DEBUG, "nwamd_ncu_state_machine: "
			    "%s is moving %s", object_name,
			    up ? "online" : "offline");
			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
			    object_name,
			    up ? NWAM_STATE_ONLINE : NWAM_STATE_OFFLINE,
			    up ? NWAM_AUX_STATE_UP : NWAM_AUX_STATE_DOWN);

			if (ncu->ncu_type == NWAM_NCU_TYPE_INTERFACE) {
				if (up) {
					/*
					 * Moving online, add v4/v6 default
					 * routes (if any).
					 */
					nwamd_add_default_routes(ncu);
				} else {
					/*
					 * If this is an interface NCU and we
					 * got a down event, it is a consequence
					 * of NCU refresh, so reapply addresses
					 * by reinitializing.
					 */
					nwamd_object_set_state
					    (NWAM_OBJECT_TYPE_NCU, object_name,
					    NWAM_STATE_OFFLINE_TO_ONLINE,
					    NWAM_AUX_STATE_INITIALIZED);
				}
			}
		} else {
			nlog(LOG_DEBUG, "nwamd_ncu_state_machine: "
			    "%s is %s", object_name,
			    up ? "online" : "offline");
		}
		/*
		 * NCU is UP or DOWN, trigger all condition checking, even if
		 * the NCU is already in the ONLINE state - an ENM may depend
		 * on NCU activity.
		 */
		nwamd_create_triggered_condition_check_event(NEXT_FEW_SECONDS);
		break;

	case NWAM_AUX_STATE_CONDITIONS_NOT_MET:
		/*
		 * Link/interface is moving offline.  Nothing to do except
		 * for WiFi, where we disconnect.  Don't unplumb IP on
		 * a link since it may be a transient change.
		 */
		if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) {
			if (link->nwamd_link_media == DL_WIFI) {
				(void) dladm_wlan_disconnect(dld_handle,
				    link->nwamd_link_id);
				link->nwamd_link_wifi_connected = B_FALSE;
				nwamd_set_selected_connected(ncu, B_FALSE,
				    B_FALSE);
			}
		} else {
			/*
			 * Unplumb here. In the future we may elaborate on
			 * the approach used and not unplumb for WiFi
			 * until we reconnect to a different WLAN (i.e. with
			 * a different ESSID).
			 */
			nwamd_unplumb_interface(ncu, AF_INET);
			nwamd_unplumb_interface(ncu, AF_INET6);
		}
		if (object->nwamd_object_state != NWAM_STATE_OFFLINE) {
			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
			    object_name, NWAM_STATE_OFFLINE,
			    NWAM_AUX_STATE_CONDITIONS_NOT_MET);
		}
		break;

	case NWAM_AUX_STATE_MANUAL_DISABLE:
		/* Manual disable, set enabled state appropriately. */
		ncu->ncu_enabled = B_FALSE;
		/* FALLTHROUGH */
	case NWAM_AUX_STATE_UNINITIALIZED:
	case NWAM_AUX_STATE_NOT_FOUND:
		/*
		 * Link/interface NCU has been disabled/deactivated/removed.
		 * For WiFi links disconnect, and for IP interfaces we unplumb.
		 */
		if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) {
			if (link->nwamd_link_media == DL_WIFI) {
				(void) dladm_wlan_disconnect(dld_handle,
				    link->nwamd_link_id);
				link->nwamd_link_wifi_connected = B_FALSE;
				nwamd_set_selected_connected(ncu, B_FALSE,
				    B_FALSE);
			}
			nwamd_dlpi_delete_link(object);
		} else {
			/* Unplumb here. */
			if (ncu->ncu_if.nwamd_if_ipv4) {
				nwamd_unplumb_interface(ncu, AF_INET);
			}
			if (ncu->ncu_if.nwamd_if_ipv6) {
				nwamd_unplumb_interface(ncu, AF_INET6);
			}
			/* trigger location condition checking */
			nwamd_create_triggered_condition_check_event(0);
		}

		switch (object->nwamd_object_aux_state) {
		case NWAM_AUX_STATE_MANUAL_DISABLE:
			/* Change state to DISABLED if manually disabled */
			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
			    object_name, NWAM_STATE_DISABLED,
			    NWAM_AUX_STATE_MANUAL_DISABLE);
			/* Note that NCU has been disabled */
			ncu->ncu_enabled = B_FALSE;
			break;
		case NWAM_AUX_STATE_NOT_FOUND:
			/* Change state to UNINITIALIZED for device removal */
			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
			    object_name, NWAM_STATE_UNINITIALIZED,
			    NWAM_AUX_STATE_NOT_FOUND);
			break;
		default:
			break;
		}
		break;
	default:
		nlog(LOG_ERR, "nwamd_ncu_state_machine: unexpected state");
		break;
	}

	nwamd_object_release(object);
}

static int
ncu_create_init_fini_event(nwam_ncu_handle_t ncuh, void *data)
{
	boolean_t *init = data;
	char *name, *typedname;
	nwam_error_t err;
	nwam_value_t typeval = NULL;
	uint64_t *type;
	uint_t numvalues;
	nwamd_event_t ncu_event;

	if (nwam_ncu_get_name(ncuh, &name) != NWAM_SUCCESS) {
		nlog(LOG_ERR,
		    "ncu_create_init_fini_event: could not get NCU name");
		return (0);
	}

	nlog(LOG_DEBUG, "ncu_create_init_fini_event(%s, %p)", name, data);

	if ((err = nwamd_get_ncu_uint(ncuh, &typeval, &type, &numvalues,
	    NWAM_NCU_PROP_TYPE)) != NWAM_SUCCESS) {
		nlog(LOG_ERR, "ncu_create_init_fini_event: "
		    "could not get NCU type: %s", nwam_strerror(err));
		free(name);
		nwam_value_free(typeval);
		return (0);
	}

	/* convert name to typedname for event */
	if ((err = nwam_ncu_name_to_typed_name(name, *type, &typedname))
	    != NWAM_SUCCESS) {
		nlog(LOG_ERR, "ncu_create_init_fini_event: "
		    "NCU name translation failed: %s", nwam_strerror(err));
		free(name);
		return (0);
	}
	free(name);
	nwam_value_free(typeval);

	ncu_event = nwamd_event_init(*init ?
	    NWAM_EVENT_TYPE_OBJECT_INIT : NWAM_EVENT_TYPE_OBJECT_FINI,
	    NWAM_OBJECT_TYPE_NCU, 0, typedname);
	if (ncu_event != NULL)
		nwamd_event_enqueue(ncu_event);
	free(typedname);

	return (0);
}

/*
 * Initialization - walk the NCUs, creating initialization events for each
 * NCU.  nwamd_ncu_handle_init_event() will check if the associated
 * physical link exists or not.
 */
void
nwamd_init_ncus(void)
{
	boolean_t init = B_TRUE;

	(void) pthread_mutex_lock(&active_ncp_mutex);
	if (active_ncph != NULL) {
		nlog(LOG_DEBUG, "nwamd_init_ncus: "
		    "(re)intializing NCUs for NCP %s", active_ncp);
		(void) nwam_ncp_walk_ncus(active_ncph,
		    ncu_create_init_fini_event, &init, NWAM_FLAG_NCU_TYPE_ALL,
		    NULL);
	}
	(void) pthread_mutex_unlock(&active_ncp_mutex);
}

void
nwamd_fini_ncus(void)
{
	boolean_t init = B_FALSE;

	/* We may not have an active NCP on initialization, so skip fini */
	(void) pthread_mutex_lock(&active_ncp_mutex);
	if (active_ncph != NULL) {
		nlog(LOG_DEBUG, "nwamd_fini_ncus: deinitializing NCUs for %s",
		    active_ncp);
		(void) nwam_ncp_walk_ncus(active_ncph,
		    ncu_create_init_fini_event, &init, NWAM_FLAG_NCU_TYPE_ALL,
		    NULL);
	}
	(void) pthread_mutex_unlock(&active_ncp_mutex);
}

/*
 * Most properties of this type don't need to be cached locally.  Only those
 * interesting to the daemon are stored in an nwamd_ncu_t.
 */
static void
populate_common_ncu_properties(nwam_ncu_handle_t ncuh, nwamd_ncu_t *ncu_data)
{
	nwam_value_t ncu_prop;
	nwam_error_t err;
	boolean_t enablevalue;
	uint_t numvalues;
	char **parent;

	if ((err = nwam_ncu_get_prop_value(ncuh, NWAM_NCU_PROP_ENABLED,
	    &ncu_prop)) != NWAM_SUCCESS) {
		char *name;
		(void) nwam_ncu_name_to_typed_name(ncu_data->ncu_name,
		    ncu_data->ncu_type, &name);
		nlog(LOG_ERR, "nwam_ncu_get_prop_value %s ENABLED failed: %s",
		    name, nwam_strerror(err));
		free(name);
		ncu_data->ncu_enabled = B_TRUE;
	} else {
		if ((err = nwam_value_get_boolean(ncu_prop, &enablevalue)) !=
		    NWAM_SUCCESS) {
			nlog(LOG_ERR, "nwam_value_get_boolean ENABLED failed: "
			    "%s", nwam_strerror(err));
		} else {
			ncu_data->ncu_enabled = enablevalue;
		}
		nwam_value_free(ncu_prop);
	}

	if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &parent,
	    &numvalues, NWAM_NCU_PROP_PARENT_NCP)) != NWAM_SUCCESS) {
		nlog(LOG_ERR, "nwam_ncu_get_prop_value %s PARENT failed: %s",
		    ncu_data->ncu_name, nwam_strerror(err));
	} else {
		(void) strlcpy(ncu_data->ncu_parent, parent[0],
		    sizeof (ncu_data->ncu_parent));
		nwam_value_free(ncu_prop);
	}
}

/*
 * Read in link properties.
 */
static void
populate_link_ncu_properties(nwam_ncu_handle_t ncuh, nwamd_ncu_t *ncu_data)
{
	nwam_value_t ncu_prop;
	nwam_error_t err;
	char **mac_addr;
	uint64_t *uintval;
	uint_t numvalues;

	/* activation-mode */
	if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &uintval, &numvalues,
	    NWAM_NCU_PROP_ACTIVATION_MODE)) != NWAM_SUCCESS) {
		nlog(LOG_ERR,
		    "populate_link_ncu_properties: could not get %s value: %s",
		    NWAM_NCU_PROP_ACTIVATION_MODE, nwam_strerror(err));
	} else {
		ncu_data->ncu_link.nwamd_link_activation_mode = uintval[0];
		nwam_value_free(ncu_prop);
	}

	/* priority-group and priority-mode for prioritized activation */
	if (ncu_data->ncu_link.nwamd_link_activation_mode ==
	    NWAM_ACTIVATION_MODE_PRIORITIZED) {
		/* ncus with prioritized activation are always enabled */
		ncu_data->ncu_enabled = B_TRUE;
		if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &uintval,
		    &numvalues, NWAM_NCU_PROP_PRIORITY_MODE))
		    != NWAM_SUCCESS) {
			nlog(LOG_ERR, "populate_link_ncu_properties: "
			    "could not get %s value: %s",
			    NWAM_NCU_PROP_PRIORITY_MODE, nwam_strerror(err));
		} else {
			ncu_data->ncu_link.nwamd_link_priority_mode =
			    uintval[0];
			nwam_value_free(ncu_prop);
		}

		if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &uintval,
		    &numvalues, NWAM_NCU_PROP_PRIORITY_GROUP))
		    != NWAM_SUCCESS) {
			nlog(LOG_ERR, "populate_link_ncu_properties: "
			    "could not get %s value: %s",
			    NWAM_NCU_PROP_PRIORITY_GROUP, nwam_strerror(err));
		} else {
			ncu_data->ncu_link.nwamd_link_priority_group =
			    uintval[0];
			nwam_value_free(ncu_prop);
		}
	}

	/* link-mac-addr */
	if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &mac_addr, &numvalues,
	    NWAM_NCU_PROP_LINK_MAC_ADDR)) != NWAM_SUCCESS) {
		nlog(LOG_DEBUG,
		    "populate_link_ncu_properties: could not get %s value: %s",
		    NWAM_NCU_PROP_LINK_MAC_ADDR, nwam_strerror(err));
		ncu_data->ncu_link.nwamd_link_mac_addr = NULL;
	} else {
		ncu_data->ncu_link.nwamd_link_mac_addr = strdup(*mac_addr);
		ncu_data->ncu_link.nwamd_link_mac_addr_len = strlen(*mac_addr);
		nwam_value_free(ncu_prop);
	}

	/* link-mtu */
	if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &uintval, &numvalues,
	    NWAM_NCU_PROP_LINK_MTU)) != NWAM_SUCCESS) {
		nlog(LOG_DEBUG,
		    "populate_link_ncu_properties: could not get %s value: %s",
		    NWAM_NCU_PROP_LINK_MTU, nwam_strerror(err));
		ncu_data->ncu_link.nwamd_link_mtu = 0;
	} else {
		ncu_data->ncu_link.nwamd_link_mtu = uintval[0];
		nwam_value_free(ncu_prop);
	}

	/* link-autopush */
	if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop,
	    &ncu_data->ncu_link.nwamd_link_autopush,
	    &ncu_data->ncu_link.nwamd_link_num_autopush,
	    NWAM_NCU_PROP_LINK_AUTOPUSH)) != NWAM_SUCCESS) {
		nlog(LOG_DEBUG,
		    "populate_link_ncu_properties: could not get %s value: %s",
		    NWAM_NCU_PROP_LINK_AUTOPUSH, nwam_strerror(err));
		ncu_data->ncu_link.nwamd_link_num_autopush = 0;
	}
}

static void
populate_ip_ncu_properties(nwam_ncu_handle_t ncuh, nwamd_ncu_t *ncu_data)
{
	nwamd_if_t *nif = &ncu_data->ncu_if;
	struct nwamd_if_address **nifa, *nifai, *nifait;
	boolean_t static_addr = B_FALSE, *boolvalue, dhcp_primary = B_FALSE;
	uint64_t *addrsrcvalue;
	nwam_value_t ncu_prop;
	nwam_error_t err;
	ipadm_addrobj_t ipaddr;
	ipadm_status_t ipstatus;
	char **addrvalue, ipreqhost[MAXNAMELEN];
	uint_t numvalues;
	uint64_t *ipversion;
	int i;

	nif->nwamd_if_ipv4 = B_FALSE;
	nif->nwamd_if_ipv6 = B_FALSE;
	nif->nwamd_if_dhcp_requested = B_FALSE;
	nif->nwamd_if_stateful_requested = B_FALSE;
	nif->nwamd_if_stateless_requested = B_FALSE;
	nif->nwamd_if_ipv4_default_route_set = B_FALSE;
	nif->nwamd_if_ipv6_default_route_set = B_FALSE;

	/* ip-version */
	if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &ipversion, &numvalues,
	    NWAM_NCU_PROP_IP_VERSION)) != NWAM_SUCCESS) {
		nlog(LOG_ERR,
		    "populate_ip_ncu_properties: could not get %s value: %s",
		    NWAM_NCU_PROP_IP_VERSION, nwam_strerror(err));
	} else {
		for (i = 0; i < numvalues; i++) {
			switch (ipversion[i]) {
			case IPV4_VERSION:
				nif->nwamd_if_ipv4 = B_TRUE;
				break;
			case IPV6_VERSION:
				nif->nwamd_if_ipv6 = B_TRUE;
				break;
			default:
				nlog(LOG_ERR, "bogus ip version %lld",
				    ipversion[i]);
				break;
			}
		}
		nwam_value_free(ncu_prop);
	}

	/* ip-primary */
	if ((err = nwamd_get_ncu_boolean(ncuh, &ncu_prop, &boolvalue,
	    &numvalues, NWAM_NCU_PROP_IP_PRIMARY)) != NWAM_SUCCESS) {
		/* ip-primary is optional, so do not LOG_ERR */
		nlog(LOG_DEBUG, "populate_ip_ncu_properties: "
		    "could not get %s value: %s",
		    NWAM_NCU_PROP_IP_PRIMARY, nwam_strerror(err));
	} else {
		if (numvalues > 0)
			dhcp_primary = boolvalue[0];
		nwam_value_free(ncu_prop);
	}

	/* ip-reqhost */
	*ipreqhost = '\0';

	if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue,
	    &numvalues, NWAM_NCU_PROP_IP_REQHOST)) != NWAM_SUCCESS) {
		/* ip-reqhost is optional, so do not LOG_ERR */
		nlog(LOG_DEBUG, "populate_ip_ncu_properties: "
		    "could not get %s value: %s",
		    NWAM_NCU_PROP_IP_REQHOST, nwam_strerror(err));
	} else {
		if (numvalues > 0 && strlcpy(ipreqhost, addrvalue[0],
		    sizeof (ipreqhost)) >= sizeof (ipreqhost)) {
			nlog(LOG_WARNING, "populate_ip_ncu_properties: "
			    "too long %s value: %s",
			    NWAM_NCU_PROP_IP_REQHOST, addrvalue[0]);
			*ipreqhost = '\0';
		}
		nwam_value_free(ncu_prop);
	}

	/* Free the old list. */
	for (nifai = nif->nwamd_if_list; nifai != NULL; nifai = nifait) {
		nifait = nifai->next;
		nifai->next = NULL;
		ipadm_destroy_addrobj(nifai->ipaddr);
		free(nifai);
	}
	nif->nwamd_if_list = NULL;
	nifa = &(nif->nwamd_if_list);

	if (!nif->nwamd_if_ipv4)
		goto skip_ipv4;

	/* ipv4-addrsrc */
	if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &addrsrcvalue,
	    &numvalues, NWAM_NCU_PROP_IPV4_ADDRSRC)) != NWAM_SUCCESS) {
		nlog(nif->nwamd_if_ipv4 ? LOG_ERR : LOG_DEBUG,
		    "populate_ip_ncu_properties: could not get %s value: %s",
		    NWAM_NCU_PROP_IPV4_ADDRSRC, nwam_strerror(err));
	} else {
		for (i = 0; i < numvalues; i++) {
			switch (addrsrcvalue[i]) {
			case NWAM_ADDRSRC_DHCP:
				nif->nwamd_if_dhcp_requested = B_TRUE;
				break;
			case NWAM_ADDRSRC_STATIC:
				static_addr = B_TRUE;
				break;
			default:
				break;
			}
		}
		nwam_value_free(ncu_prop);
	}
	if (nif->nwamd_if_dhcp_requested) {
		ipstatus = ipadm_create_addrobj(IPADM_ADDR_DHCP,
		    ncu_data->ncu_name, &ipaddr);
		if (ipstatus != IPADM_SUCCESS) {
			nlog(LOG_ERR, "populate_ip_ncu_properties: "
			    "ipadm_create_addrobj failed for v4 dhcp: %s",
			    ipadm_status2str(ipstatus));
			goto skip_ipv4_dhcp;
		}

		ipstatus = ipadm_set_wait_time(ipaddr, ncu_wait_time);
		if (ipstatus != IPADM_SUCCESS) {
			nlog(LOG_ERR, "populate_ip_ncu_properties: "
			    "ipadm_set_wait_time failed for v4 dhcp: %s",
			    ipadm_status2str(ipstatus));
			ipadm_destroy_addrobj(ipaddr);
			goto skip_ipv4_dhcp;
		}
		ipstatus = ipadm_set_primary(ipaddr, dhcp_primary);
		if (ipstatus != IPADM_SUCCESS) {
			nlog(LOG_ERR, "populate_ip_ncu_properties: "
			    "ipadm_set_primary failed for v4 dhcp: %s",
			    ipadm_status2str(ipstatus));
			ipadm_destroy_addrobj(ipaddr);
			goto skip_ipv4_dhcp;
		}
		ipstatus = ipadm_set_reqhost(ipaddr, ipreqhost);
		if (ipstatus != IPADM_SUCCESS) {
			nlog(LOG_ERR, "populate_ip_ncu_properties: "
			    "ipadm_set_reqhost failed for v4 dhcp: %s",
			    ipadm_status2str(ipstatus));
			ipadm_destroy_addrobj(ipaddr);
			goto skip_ipv4_dhcp;
		}
		if ((*nifa = calloc(sizeof (**nifa), 1)) != NULL) {
			(*nifa)->family = AF_INET;
			(*nifa)->ipaddr_atype = IPADM_ADDR_DHCP;
			(*nifa)->ipaddr = ipaddr;
			nifa = &((*nifa)->next);
			*nifa = NULL;
		} else {
			nlog(LOG_ERR, "populate_ip_ncu_properties: "
			    "couldn't allocate nwamd address for v4 dhcp: %s",
			    strerror(errno));
			ipadm_destroy_addrobj(ipaddr);
		}
	}

skip_ipv4_dhcp:
	/* ipv4-addr */
	if (static_addr) {
		if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue,
		    &numvalues, NWAM_NCU_PROP_IPV4_ADDR)) != NWAM_SUCCESS) {
			nlog(LOG_ERR, "populate_ip_ncu_properties: "
			    "could not get %s value: %s",
			    NWAM_NCU_PROP_IPV4_ADDR, nwam_strerror(err));
		} else {
			for (i = 0; i < numvalues; i++) {
				ipstatus = ipadm_create_addrobj(
				    IPADM_ADDR_STATIC, ncu_data->ncu_name,
				    &ipaddr);
				if (ipstatus != IPADM_SUCCESS) {
					nlog(LOG_ERR,
					    "populate_ip_ncu_properties: "
					    "ipadm_create_addrobj failed "
					    "for %s: %s", addrvalue[i],
					    ipadm_status2str(ipstatus));
					continue;
				}
				/* ipadm_set_addr takes <addr>[/<mask>] */
				ipstatus = ipadm_set_addr(ipaddr, addrvalue[i],
				    AF_INET);
				if (ipstatus != IPADM_SUCCESS) {
					nlog(LOG_ERR,
					    "populate_ip_ncu_properties: "
					    "ipadm_set_addr failed for %s: %s",
					    addrvalue[i],
					    ipadm_status2str(ipstatus));
					ipadm_destroy_addrobj(ipaddr);
					continue;
				}

				if ((*nifa = calloc(sizeof (**nifa), 1))
				    != NULL) {
					(*nifa)->family = AF_INET;
					(*nifa)->ipaddr_atype =
					    IPADM_ADDR_STATIC;
					(*nifa)->ipaddr = ipaddr;
					nifa = &((*nifa)->next);
				} else {
					nlog(LOG_ERR,
					    "populate_ip_ncu_properties: "
					    "couldn't allocate nwamd address "
					    "for %s: %s", addrvalue[i],
					    strerror(errno));
					ipadm_destroy_addrobj(ipaddr);
				}
			}
			*nifa = NULL;

			nwam_value_free(ncu_prop);
		}
	}

	/* get default route, if any */
	if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue,
	    &numvalues, NWAM_NCU_PROP_IPV4_DEFAULT_ROUTE)) == NWAM_SUCCESS) {
		/* Only one default route is allowed. */
		nif->nwamd_if_ipv4_default_route.sin_family = AF_INET;
		(void) inet_pton(AF_INET, addrvalue[0],
		    &(nif->nwamd_if_ipv4_default_route.sin_addr));
		nif->nwamd_if_ipv4_default_route_set = B_TRUE;
		nwam_value_free(ncu_prop);
	}

skip_ipv4:
	if (!nif->nwamd_if_ipv6)
		goto skip_ipv6;

	/* ipv6-addrsrc */
	static_addr = B_FALSE;
	if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &addrsrcvalue,
	    &numvalues, NWAM_NCU_PROP_IPV6_ADDRSRC)) != NWAM_SUCCESS) {
		nlog(nif->nwamd_if_ipv6 ? LOG_ERR : LOG_DEBUG,
		    "populate_ip_ncu_properties: could not get %s value: %s",
		    NWAM_NCU_PROP_IPV6_ADDRSRC, nwam_strerror(err));
	} else {
		for (i = 0; i < numvalues; i++) {
			switch (addrsrcvalue[i]) {
			case NWAM_ADDRSRC_DHCP:
				nif->nwamd_if_stateful_requested = B_TRUE;
				break;
			case NWAM_ADDRSRC_AUTOCONF:
				nif->nwamd_if_stateless_requested = B_TRUE;
				break;
			case NWAM_ADDRSRC_STATIC:
				static_addr = B_TRUE;
				break;
			default:
				break;
			}
		}
		nwam_value_free(ncu_prop);
	}
	/*
	 * Both stateful and stateless share the same nwamd_if_address because
	 * only one ipaddr for both of these addresses can be created.
	 * ipadm_create_addr() adds both addresses from the same ipaddr.
	 */
	if (nif->nwamd_if_stateful_requested ||
	    nif->nwamd_if_stateless_requested) {
		ipstatus = ipadm_create_addrobj(IPADM_ADDR_IPV6_ADDRCONF,
		    ncu_data->ncu_name, &ipaddr);
		if (ipstatus != IPADM_SUCCESS) {
			nlog(LOG_ERR, "populate_ip_ncu_properties: "
			    "ipadm_create_addrobj failed for v6 "
			    "stateless/stateful: %s",
			    ipadm_status2str(ipstatus));
			goto skip_ipv6_addrconf;
		}
		/* create_addrobj sets both stateless and stateful to B_TRUE */
		if (!nif->nwamd_if_stateful_requested) {
			ipstatus = ipadm_set_stateful(ipaddr, B_FALSE);
			if (ipstatus != IPADM_SUCCESS) {
				nlog(LOG_ERR, "populate_ip_ncu_properties: "
				    "ipadm_set_stateful failed for v6: %s",
				    ipadm_status2str(ipstatus));
				ipadm_destroy_addrobj(ipaddr);
				goto skip_ipv6_addrconf;
			}
		}
		if (!nif->nwamd_if_stateless_requested) {
			ipstatus = ipadm_set_stateless(ipaddr, B_FALSE);
			if (ipstatus != IPADM_SUCCESS) {
				nlog(LOG_ERR, "populate_ip_ncu_properties: "
				    "ipadm_set_stateless failed for v6: %s",
				    ipadm_status2str(ipstatus));
				ipadm_destroy_addrobj(ipaddr);
				goto skip_ipv6_addrconf;
			}
		}
		if ((*nifa = calloc(sizeof (**nifa), 1)) != NULL) {
			(*nifa)->family = AF_INET6;
			(*nifa)->ipaddr_atype = IPADM_ADDR_IPV6_ADDRCONF;
			(*nifa)->ipaddr = ipaddr;
			nifa = &((*nifa)->next);
			*nifa = NULL;
		} else {
			nlog(LOG_ERR, "populate_ip_ncu_properties: "
			    "couldn't allocate nwamd address for "
			    "v6 stateless/stateful: %s", strerror(errno));
			ipadm_destroy_addrobj(ipaddr);
		}
	}

skip_ipv6_addrconf:
	/* ipv6-addr */
	if (static_addr) {
		if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue,
		    &numvalues, NWAM_NCU_PROP_IPV6_ADDR)) != NWAM_SUCCESS) {
			nlog(LOG_ERR, "populate_ip_ncu_properties: "
			    "could not get %s value: %s",
			    NWAM_NCU_PROP_IPV6_ADDR, nwam_strerror(err));
		} else {
			for (i = 0; i < numvalues; i++) {
				ipstatus = ipadm_create_addrobj(
				    IPADM_ADDR_STATIC, ncu_data->ncu_name,
				    &ipaddr);
				if (ipstatus != IPADM_SUCCESS) {
					nlog(LOG_ERR,
					    "populate_ip_ncu_properties: "
					    "ipadm_create_addrobj failed "
					    "for %s: %s", addrvalue[i],
					    ipadm_status2str(ipstatus));
					continue;
				}
				/* ipadm_set_addr takes <addr>[/<mask>] */
				ipstatus = ipadm_set_addr(ipaddr, addrvalue[i],
				    AF_INET6);
				if (ipstatus != IPADM_SUCCESS) {
					nlog(LOG_ERR,
					    "populate_ip_ncu_properties: "
					    "ipadm_set_addr failed for %s: %s",
					    addrvalue[i],
					    ipadm_status2str(ipstatus));
					ipadm_destroy_addrobj(ipaddr);
					continue;
				}

				if ((*nifa = calloc(sizeof (**nifa), 1))
				    != NULL) {
					(*nifa)->family = AF_INET6;
					(*nifa)->ipaddr_atype =
					    IPADM_ADDR_STATIC;
					(*nifa)->ipaddr = ipaddr;
					nifa = &((*nifa)->next);
				} else {
					nlog(LOG_ERR,
					    "populate_ip_ncu_properties: "
					    "couldn't allocate nwamd address "
					    "for %s: %s", addrvalue[i],
					    strerror(errno));
					ipadm_destroy_addrobj(ipaddr);
				}
			}
			*nifa = NULL;

			nwam_value_free(ncu_prop);
		}
	}

	/* get default route, if any */
	if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue,
	    &numvalues, NWAM_NCU_PROP_IPV6_DEFAULT_ROUTE)) == NWAM_SUCCESS) {
		/* Only one default route is allowed. */
		nif->nwamd_if_ipv6_default_route.sin6_family = AF_INET6;
		(void) inet_pton(AF_INET6, addrvalue[0],
		    &(nif->nwamd_if_ipv6_default_route.sin6_addr));
		nif->nwamd_if_ipv6_default_route_set = B_TRUE;
		nwam_value_free(ncu_prop);
	}

skip_ipv6:
	;
}

static nwamd_ncu_t *
nwamd_ncu_init(nwam_ncu_type_t ncu_type, const char *name)
{
	nwamd_ncu_t *rv;

	nlog(LOG_DEBUG, "nwamd_ncu_init(%d, %s)", ncu_type, name);

	if ((rv = calloc(1, sizeof (*rv))) == NULL)
		return (NULL);

	rv->ncu_type = ncu_type;
	rv->ncu_name = strdup(name);
	rv->ncu_enabled = B_FALSE;

	/* Initialize link/interface-specific data */
	if (rv->ncu_type == NWAM_NCU_TYPE_LINK) {
		(void) bzero(&rv->ncu_link, sizeof (nwamd_link_t));
		(void) dladm_name2info(dld_handle, name,
		    &rv->ncu_link.nwamd_link_id, NULL, NULL,
		    &rv->ncu_link.nwamd_link_media);
		(void) pthread_mutex_init(
		    &rv->ncu_link.nwamd_link_wifi_mutex, NULL);
		rv->ncu_link.nwamd_link_wifi_priority = MAXINT;
	} else {
		(void) bzero(&rv->ncu_if, sizeof (nwamd_if_t));
	}

	return (rv);
}

void
nwamd_ncu_free(nwamd_ncu_t *ncu)
{
	if (ncu != NULL) {
		assert(ncu->ncu_type == NWAM_NCU_TYPE_LINK ||
		    ncu->ncu_type == NWAM_NCU_TYPE_INTERFACE);
		if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) {
			struct nwamd_link *l = &ncu->ncu_link;
			int i;

			free(l->nwamd_link_wifi_key);
			free(l->nwamd_link_mac_addr);
			for (i = 0; i < l->nwamd_link_num_autopush; i++)
				free(l->nwamd_link_autopush[i]);
		} else if (ncu->ncu_type == NWAM_NCU_TYPE_INTERFACE) {
			struct nwamd_if_address *nifa;

			nifa = ncu->ncu_if.nwamd_if_list;
			while (nifa != NULL) {
				struct nwamd_if_address *n;

				n = nifa;
				nifa = nifa->next;
				ipadm_destroy_addrobj(n->ipaddr);
				free(n);
			}
		}
		free(ncu->ncu_name);
		free(ncu);
	}
}

static int
nwamd_ncu_display(nwamd_object_t ncu_obj, void *data)
{
	nwamd_ncu_t *ncu = (nwamd_ncu_t *)ncu_obj->nwamd_object_data;
	data = data;
	nlog(LOG_DEBUG, "NCU (%p) %s state %s, %s",
	    (void *)ncu, ncu_obj->nwamd_object_name,
	    nwam_state_to_string(ncu_obj->nwamd_object_state),
	    nwam_aux_state_to_string(ncu_obj->nwamd_object_aux_state));
	return (0);
}

void
nwamd_log_ncus(void)
{
	nlog(LOG_DEBUG, "NCP %s", active_ncp);
	(void) nwamd_walk_objects(NWAM_OBJECT_TYPE_NCU, nwamd_ncu_display,
	    NULL);
}

int
nwamd_ncu_action(const char *ncu, const char *parent, nwam_action_t action)
{
	nwamd_event_t ncu_event = nwamd_event_init_object_action
	    (NWAM_OBJECT_TYPE_NCU, ncu, parent, action);
	if (ncu_event == NULL)
		return (1);
	nwamd_event_enqueue(ncu_event);
	return (0);
}

static void
add_phys_ncu_to_ncp(nwam_ncp_handle_t ncph, const char *name)
{
	dladm_status_t dlrtn;
	uint32_t media;
	boolean_t is_wireless;
	nwam_error_t err;
	nwam_ncu_handle_t ncuh;
	uint64_t uintval;

	if ((dlrtn = dladm_name2info(dld_handle, name, NULL, NULL, NULL,
	    &media)) != DLADM_STATUS_OK) {
		char errmsg[DLADM_STRSIZE];
		nlog(LOG_ERR, "failed to get media type for %s: %s", name,
		    dladm_status2str(dlrtn, errmsg));
		return;
	}
	is_wireless = (media == DL_WIFI);

	if ((err = nwam_ncu_create(ncph, name, NWAM_NCU_TYPE_LINK,
	    NWAM_NCU_CLASS_PHYS, &ncuh)) != NWAM_SUCCESS) {
		nlog(LOG_ERR, "failed to create link ncu for %s: %s", name,
		    nwam_strerror(err));
		if (err == NWAM_ENTITY_READ_ONLY) {
			nwamd_event_t retry_event;

			/*
			 * Root filesystem may be read-only, retry in
			 * a few seconds.
			 */
			nlog(LOG_DEBUG, "Retrying addition of phys ncu for %s",
			    name);
			retry_event = nwamd_event_init_link_action(name,
			    NWAM_ACTION_ADD);
			if (retry_event != NULL) {
				nwamd_event_enqueue_timed(retry_event,
				    NWAMD_READONLY_RETRY_INTERVAL);
			}
		}
		return;
	}

	uintval = NWAM_ACTIVATION_MODE_PRIORITIZED;
	if ((err = nwamd_set_ncu_uint(ncuh, &uintval, 1,
	    NWAM_NCU_PROP_ACTIVATION_MODE)) != NWAM_SUCCESS) {
		goto finish;
	}

	uintval = is_wireless ? 1 : 0;
	if ((err = nwamd_set_ncu_uint(ncuh, &uintval, 1,
	    NWAM_NCU_PROP_PRIORITY_GROUP)) != NWAM_SUCCESS) {
		goto finish;
	}

	uintval = is_wireless ? NWAM_PRIORITY_MODE_EXCLUSIVE :
	    NWAM_PRIORITY_MODE_SHARED;
	if ((err = nwamd_set_ncu_uint(ncuh, &uintval, 1,
	    NWAM_NCU_PROP_PRIORITY_MODE)) != NWAM_SUCCESS) {
		goto finish;
	}

	err = nwam_ncu_commit(ncuh, 0);

finish:
	nwam_ncu_free(ncuh);
	if (err != NWAM_SUCCESS) {
		nlog(LOG_ERR,
		    "failed to create automatic link ncu for %s: %s",
		    name, nwam_strerror(err));
	}
}

static void
add_ip_ncu_to_ncp(nwam_ncp_handle_t ncph, const char *name)
{
	nwam_error_t err;
	nwam_ncu_handle_t ncuh;

	if ((err = nwam_ncu_create(ncph, name, NWAM_NCU_TYPE_INTERFACE,
	    NWAM_NCU_CLASS_IP, &ncuh)) != NWAM_SUCCESS) {
		nlog(LOG_ERR, "failed to create ip ncu for %s: %s", name,
		    nwam_strerror(err));
		/*
		 * Root filesystem may be read-only, but no need to
		 * retry here since add_phys_ncu_to_ncp() enqueues
		 * a retry event which will lead to add_ip_ncu_to_ncp()
		 * being called.
		 */
		return;
	}

	/* IP NCU has the default values, so nothing else to do */
	err = nwam_ncu_commit(ncuh, 0);

	nwam_ncu_free(ncuh);
	if (err != NWAM_SUCCESS) {
		nlog(LOG_ERR,
		    "failed to create ip ncu for %s: %s", name,
		    nwam_strerror(err));
	}
}

static void
remove_ncu_from_ncp(nwam_ncp_handle_t ncph, const char *name,
    nwam_ncu_type_t type)
{
	nwam_error_t err;
	nwam_ncu_handle_t ncuh;

	if ((err = nwam_ncu_read(ncph, name, type, 0, &ncuh)) != NWAM_SUCCESS) {
		nlog(LOG_ERR, "failed to read automatic ncu %s: %s", name,
		    nwam_strerror(err));
		return;
	}

	err = nwam_ncu_destroy(ncuh, 0);
	if (err != NWAM_SUCCESS) {
		nlog(LOG_ERR, "failed to delete automatic ncu %s: %s", name,
		    nwam_strerror(err));
	}
}

/*
 * Device represented by NCU has been added or removed for the active
 * User NCP.  If an associated NCU of the given type is found, transition it
 * to the appropriate state.
 */
void
ncu_action_change_state(nwam_action_t action, nwam_ncu_type_t type,
    const char *name)
{
	nwamd_object_t ncu_obj = NULL;
	nwamd_ncu_t *ncu;

	if ((ncu_obj = nwamd_ncu_object_find(type, name)) == NULL)
		return;

	ncu = ncu_obj->nwamd_object_data;

	/*
	 * If device has been added, transition from uninitialized to offline.
	 * If device has been removed, transition to uninitialized (via online*
	 * if the NCU is currently enabled in order to tear down config).
	 */
	if (action == NWAM_ACTION_ADD) {
		nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
		    ncu_obj->nwamd_object_name,
		    NWAM_STATE_OFFLINE, NWAM_AUX_STATE_CONDITIONS_NOT_MET);
	} else {
		if (ncu->ncu_enabled) {
			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
			    ncu_obj->nwamd_object_name,
			    NWAM_STATE_ONLINE_TO_OFFLINE,
			    NWAM_AUX_STATE_NOT_FOUND);
		} else {
			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
			    ncu_obj->nwamd_object_name,
			    NWAM_STATE_UNINITIALIZED,
			    NWAM_AUX_STATE_NOT_FOUND);
		}
	}
	nwamd_object_release(ncu_obj);
}

/*
 * Called with hotplug sysevent or when nwam is started and walking the
 * physical interfaces.  Add/remove both link and interface NCUs from the
 * Automatic NCP.  Assumes that both link and interface NCUs don't exist.
 */
void
nwamd_ncu_handle_link_action_event(nwamd_event_t event)
{
	nwam_ncp_handle_t ncph;
	nwam_ncu_type_t type;
	nwam_action_t action =
	    event->event_msg->nwe_data.nwe_link_action.nwe_action;
	nwam_error_t err;
	char *name;
	boolean_t automatic_ncp_active = B_FALSE;

	if (action != NWAM_ACTION_ADD && action != NWAM_ACTION_REMOVE) {
		nlog(LOG_ERR, "nwamd_ncu_handle_link_action_event: "
		    "invalid link action %s", nwam_action_to_string(action));
		nwamd_event_do_not_send(event);
		return;
	}

	nlog(LOG_DEBUG, "nwamd_ncu_handle_link_action_event: "
	    "link action '%s' event on %s", nwam_action_to_string(action),
	    event->event_object[0] == 0 ? "n/a" : event->event_object);

	if ((err = nwam_ncu_typed_name_to_name(event->event_object, &type,
	    &name)) != NWAM_SUCCESS) {
		nlog(LOG_ERR, "nwamd_ncu_handle_link_action_event: "
		    "translation from typedname error: %s", nwam_strerror(err));
		nwamd_event_do_not_send(event);
		return;
	}

	(void) pthread_mutex_lock(&active_ncp_mutex);
	if (strcmp(active_ncp, NWAM_NCP_NAME_AUTOMATIC) == 0 &&
	    active_ncph != NULL) {
		automatic_ncp_active = B_TRUE;
	}
	(void) pthread_mutex_unlock(&active_ncp_mutex);

	/*
	 * We could use active_ncph for cases where the Automatic NCP is active,
	 * but that would involve holding the active_ncp_mutex for too long.
	 */
	if ((err = nwam_ncp_read(NWAM_NCP_NAME_AUTOMATIC, 0, &ncph))
	    == NWAM_ENTITY_NOT_FOUND) {
		/* Automatic NCP doesn't exist, create it */
		err = nwam_ncp_create(NWAM_NCP_NAME_AUTOMATIC, 0, &ncph);
	}
	if (err != NWAM_SUCCESS)
		goto fail;

	/* add or remove NCUs from Automatic NCP */
	if (action == NWAM_ACTION_ADD) {
		add_phys_ncu_to_ncp(ncph, name);
		add_ip_ncu_to_ncp(ncph, name);
	} else {
		/*
		 * Order is important here, remove IP NCU first to prevent
		 * propogation of down event from link to IP.  No need to
		 * create REFRESH or DESTROY events.  They are generated by
		 * nwam_ncu_commit() and nwam_ncu_destroy().
		 */
		remove_ncu_from_ncp(ncph, name, NWAM_NCU_TYPE_INTERFACE);
		remove_ncu_from_ncp(ncph, name, NWAM_NCU_TYPE_LINK);
	}
	nwam_ncp_free(ncph);

	/*
	 * If the Automatic NCP is not active, and the associated NCUs
	 * exist, they must be moved into the appropriate states given the
	 * action that has occurred.
	 */
	if (!automatic_ncp_active) {
		ncu_action_change_state(action, NWAM_NCU_TYPE_INTERFACE, name);
		ncu_action_change_state(action, NWAM_NCU_TYPE_LINK, name);
	}

	/* Need NCU check to evaluate state in light of added/removed NCUs */
	if (!nwamd_event_enqueued(NWAM_EVENT_TYPE_NCU_CHECK,
	    NWAM_OBJECT_TYPE_NCP, NULL)) {
		nwamd_create_ncu_check_event(NEXT_FEW_SECONDS);
	}

fail:
	free(name);
	if (err != NWAM_SUCCESS) {
		nwamd_event_t retry_event = nwamd_event_init_link_action(name,
		    action);
		if (retry_event == NULL) {
			nlog(LOG_ERR, "nwamd_ncu_handle_link_action_event: "
			    "could not create retry event to read/create "
			    "%s NCP", NWAM_NCP_NAME_AUTOMATIC);
			return;
		}

		nlog(LOG_ERR, "nwamd_ncu_handle_link_action_event: "
		    "could not read/create %s NCP, retrying in %d seconds",
		    NWAM_NCP_NAME_AUTOMATIC, NWAMD_READONLY_RETRY_INTERVAL);
		nwamd_event_enqueue_timed(retry_event,
		    NWAMD_READONLY_RETRY_INTERVAL);
	}
}

/*
 * Figure out if this link is part of an aggregation.  This is fairly
 * inefficient since we generate this list for every query and search
 * linearly.  A better way would be to generate the list of links in an
 * aggregation once and then check each link against it.
 */
struct link_aggr_search_data {
	datalink_id_t linkid;
	boolean_t under;
};

static int
ncu_aggr_search(const char *name, void *data)
{
	struct link_aggr_search_data *lasd = data;
	dladm_aggr_grp_attr_t ginfo;
	datalink_id_t linkid;
	int i;

	if (dladm_name2info(dld_handle, name, &linkid, NULL, NULL, NULL) !=
	    DLADM_STATUS_OK)
		return (DLADM_WALK_CONTINUE);
	if (dladm_aggr_info(dld_handle, linkid, &ginfo, DLADM_OPT_ACTIVE)
	    != DLADM_STATUS_OK || ginfo.lg_nports == 0)
		return (DLADM_WALK_CONTINUE);

	for (i = 0; i < ginfo.lg_nports; i++) {
		if (lasd->linkid == ginfo.lg_ports[i].lp_linkid) {
			lasd->under = B_TRUE;
			return (DLADM_WALK_TERMINATE);
		}
	}
	free(ginfo.lg_ports);
	return (DLADM_WALK_CONTINUE);
}

static boolean_t
nwamd_link_belongs_to_an_aggr(const char *name)
{
	struct link_aggr_search_data lasd;

	if (dladm_name2info(dld_handle, name, &lasd.linkid, NULL, NULL, NULL)
	    != DLADM_STATUS_OK)
		return (B_FALSE);
	lasd.under = B_FALSE;
	(void) dladm_walk(ncu_aggr_search, dld_handle, &lasd,
	    DATALINK_CLASS_AGGR, DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);
	return (lasd.under);
}

/*
 * If NCU doesn't exist for interface with given name, enqueue a ADD
 * LINK_ACTION event.
 */
static int
ncu_create_link_action_event(const char *name, void *data)
{
	nwam_ncp_handle_t ncph = data;
	nwam_ncu_handle_t ncuh;
	nwamd_event_t link_event;

	/* Do not generate an event if this is a VirtualBox interface. */
	if (strncmp(name, VBOX_IFACE_PREFIX, strlen(VBOX_IFACE_PREFIX)) == 0)
		return (DLADM_WALK_CONTINUE);

	/* Do not generate an event if this link belongs to another zone. */
	if (!nwamd_link_belongs_to_this_zone(name))
		return (DLADM_WALK_CONTINUE);

	/* Do not generate an event if this link belongs to an aggregation. */
	if (nwamd_link_belongs_to_an_aggr(name)) {
		return (DLADM_WALK_CONTINUE);
	}

	/* Don't create an event if the NCU already exists. */
	if (ncph != NULL && nwam_ncu_read(ncph, name, NWAM_NCU_TYPE_LINK, 0,
	    &ncuh) == NWAM_SUCCESS) {
		nwam_ncu_free(ncuh);
		return (DLADM_WALK_CONTINUE);
	}

	nlog(LOG_DEBUG, "ncu_create_link_action_event: adding ncus for %s",
	    name);

	link_event = nwamd_event_init_link_action(name, NWAM_ACTION_ADD);
	if (link_event != NULL)
		nwamd_event_enqueue(link_event);

	return (DLADM_WALK_CONTINUE);
}

/*
 * Check if interface exists for this NCU. If not, enqueue a REMOVE
 * LINK_ACTION event.
 */
/* ARGSUSED */
static int
nwamd_destroy_ncu(nwam_ncu_handle_t ncuh, void *data)
{
	char *name;
	uint32_t flags;
	nwamd_event_t link_event;

	if (nwam_ncu_get_name(ncuh, &name) != NWAM_SUCCESS) {
		nlog(LOG_ERR, "nwamd_destroy_ncu: could not get NCU name");
		return (0);
	}

	/* Interfaces that exist return DLADM_OPT_ACTIVE flag */
	if ((dladm_name2info(dld_handle, name, NULL, &flags, NULL, NULL)
	    == DLADM_STATUS_OK && (flags & DLADM_OPT_ACTIVE)) &&
	    !nwamd_link_belongs_to_an_aggr(name)) {
		free(name);
		return (0);
	}

	nlog(LOG_DEBUG, "nwamd_destroy_ncu: destroying ncus for %s", name);

	link_event = nwamd_event_init_link_action(name, NWAM_ACTION_REMOVE);
	if (link_event != NULL)
		nwamd_event_enqueue(link_event);
	free(name);
	return (0);
}

/*
 * Called when nwamd is starting up.
 *
 * Walk all NCUs and destroy any NCU from the Automatic NCP without an
 * underlying interface (assumption here is that the interface was removed
 * when nwam was disabled).
 *
 * Walk the physical interfaces and create ADD LINK_ACTION event, which
 * will create appropriate interface and link NCUs in the Automatic NCP.
 */
void
nwamd_walk_physical_configuration(void)
{
	nwam_ncp_handle_t ncph;
	datalink_class_t dlclass = DATALINK_CLASS_PHYS;
	zoneid_t zoneid = getzoneid();

	(void) pthread_mutex_lock(&active_ncp_mutex);
	if (strcmp(active_ncp, NWAM_NCP_NAME_AUTOMATIC) == 0 &&
	    active_ncph != NULL) {
		ncph = active_ncph;
	} else {
		if (nwam_ncp_read(NWAM_NCP_NAME_AUTOMATIC, 0, &ncph)
		    != NWAM_SUCCESS) {
			ncph = NULL;
		}
	}

	/* destroy NCUs for interfaces that don't exist */
	if (ncph != NULL) {
		(void) nwam_ncp_walk_ncus(ncph, nwamd_destroy_ncu, NULL,
		    NWAM_FLAG_NCU_TYPE_LINK, NULL);
	}

	/* In non-global zones NWAM can support VNICs */
	if (zoneid != GLOBAL_ZONEID)
		dlclass |= DATALINK_CLASS_VNIC;

	/* create NCUs for interfaces without NCUs */
	(void) dladm_walk(ncu_create_link_action_event, dld_handle, ncph,
	    dlclass, DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);

	if (strcmp(active_ncp, NWAM_NCP_NAME_AUTOMATIC) != 0 ||
	    active_ncph == NULL) {
		nwam_ncp_free(ncph);
	}
	(void) pthread_mutex_unlock(&active_ncp_mutex);
}

/*
 * Handle NCU initialization/refresh event.
 */
void
nwamd_ncu_handle_init_event(nwamd_event_t event)
{
	nwamd_object_t object = NULL;
	nwam_ncu_handle_t ncuh;
	nwamd_ncu_t *ncu = NULL;
	nwam_error_t err;
	nwam_ncu_type_t type;
	char *name;
	uint32_t flags;
	boolean_t new = B_TRUE;

	nlog(LOG_DEBUG, "nwamd_ncu_handle_init_event(%s)",
	    event->event_object);

	/* Get base linkname rather than interface:linkname or link:linkname */
	err = nwam_ncu_typed_name_to_name(event->event_object,
	    &type, &name);
	if (err != NWAM_SUCCESS) {
		nlog(LOG_ERR, "nwamd_ncu_handle_init_event: "
		    "nwam_ncu_typed_name_to_name returned %s",
		    nwam_strerror(err));
		nwamd_event_do_not_send(event);
		return;
	}

	(void) pthread_mutex_lock(&active_ncp_mutex);
	if (active_ncph == NULL) {
		nlog(LOG_DEBUG,
		    "nwamd_ncu_handle_init_event: active NCP handle NULL");
		nwamd_event_do_not_send(event);
		free(name);
		(void) pthread_mutex_unlock(&active_ncp_mutex);
		return;
	}
	err = nwam_ncu_read(active_ncph, event->event_object,
	    type, 0, &ncuh);
	(void) pthread_mutex_unlock(&active_ncp_mutex);
	if (err != NWAM_SUCCESS) {
		nlog(LOG_ERR, "nwamd_ncu_handle_init_event: "
		    "could not read object '%s': %s",
		    event->event_object, nwam_strerror(err));
		free(name);
		nwamd_event_do_not_send(event);
		return;
	}

	if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU,
	    event->event_object)) != NULL)
		new = B_FALSE;

	/*
	 * For new NCUs, or interface NCUs, we (re)initialize data from scratch.
	 * For link NCUs, we want to retain object data.
	 */
	switch (type) {
	case NWAM_NCU_TYPE_LINK:
		if (new) {
			ncu = nwamd_ncu_init(type, name);
		} else {
			ncu = object->nwamd_object_data;
			nwam_ncu_free(object->nwamd_object_handle);
		}
		populate_common_ncu_properties(ncuh, ncu);
		populate_link_ncu_properties(ncuh, ncu);
		break;
	case NWAM_NCU_TYPE_INTERFACE:
		if (!new) {
			nwam_ncu_free(object->nwamd_object_handle);
			nwamd_ncu_free(object->nwamd_object_data);
		}
		ncu = nwamd_ncu_init(type, name);
		populate_common_ncu_properties(ncuh, ncu);
		populate_ip_ncu_properties(ncuh, ncu);
		break;
	default:
		nlog(LOG_ERR, "unknown ncu type %d", type);
		free(name);
		nwam_ncu_free(ncuh);
		nwamd_event_do_not_send(event);
		nwamd_object_release(object);
		return;
	}

	if (new) {
		nlog(LOG_DEBUG, "nwamd_ncu_handle_init_event: didn't find "
		    "ncu so create it %s", name);
		object = nwamd_object_init(NWAM_OBJECT_TYPE_NCU,
		    event->event_object, ncuh, ncu);
	} else {
		nlog(LOG_DEBUG, "nwamd_ncu_handle_init_event: refreshing "
		    "ncu %s", name);
		object->nwamd_object_data = ncu;
		object->nwamd_object_handle = ncuh;
	}

	/*
	 * If the physical link for this NCU doesn't exist in the system,
	 * the state should be UNINITIALIZED/NOT_FOUND.  Interfaces that
	 * exist return DLADM_OPT_ACTIVE flag.
	 */
	if (dladm_name2info(dld_handle, name, NULL, &flags, NULL, NULL)
	    != DLADM_STATUS_OK || !(flags & DLADM_OPT_ACTIVE)) {
		nlog(LOG_DEBUG, "nwam_ncu_handle_init_event: "
		    "interface for NCU %s doesn't exist",
		    event->event_object);
		nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
		    object->nwamd_object_name, NWAM_STATE_UNINITIALIZED,
		    NWAM_AUX_STATE_NOT_FOUND);
		free(name);
		nwamd_object_release(object);
		return;
	}

	/*
	 * If NCU is being initialized (rather than refreshed), the
	 * object_state is INITIALIZED (from nwamd_object_init()).
	 */
	if (object->nwamd_object_state == NWAM_STATE_INITIALIZED) {
		/*
		 * If the NCU is disabled, initial state should be DISABLED.
		 *
		 * Otherwise, the initial state will be
		 * OFFLINE/CONDITIONS_NOT_MET, and the link selection
		 * algorithm will do the rest.
		 */
		if (!ncu->ncu_enabled) {
			object->nwamd_object_state = NWAM_STATE_DISABLED;
			object->nwamd_object_aux_state =
			    NWAM_AUX_STATE_MANUAL_DISABLE;
		} else {
			object->nwamd_object_state = NWAM_STATE_OFFLINE;
			object->nwamd_object_aux_state =
			    NWAM_AUX_STATE_CONDITIONS_NOT_MET;
		}
	} else {
		nwamd_link_t *link = &ncu->ncu_link;

		/*
		 * Refresh NCU.  Deal with disabled cases first, moving NCUs
		 * that are not disabled - but have the enabled value set - to
		 * the disabled state.  Then handle cases where the NCU was
		 * disabled but is no longer.  Finally,  deal with refresh of
		 * link and interface NCUs, as these are handled differently.
		 */
		if (!ncu->ncu_enabled) {
			if (object->nwamd_object_state != NWAM_STATE_DISABLED) {
				nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
				    object->nwamd_object_name,
				    NWAM_STATE_ONLINE_TO_OFFLINE,
				    NWAM_AUX_STATE_MANUAL_DISABLE);
			}
			goto done;
		} else {
			if (object->nwamd_object_state == NWAM_STATE_DISABLED) {
				int64_t c;

				/*
				 * Try to activate the NCU if manual or
				 * prioritized (when priority <= current).
				 */
				(void) pthread_mutex_lock(&active_ncp_mutex);
				c = current_ncu_priority_group;
				(void) pthread_mutex_unlock(&active_ncp_mutex);
				if (link->nwamd_link_activation_mode ==
				    NWAM_ACTIVATION_MODE_MANUAL ||
				    (link->nwamd_link_activation_mode ==
				    NWAM_ACTIVATION_MODE_PRIORITIZED &&
				    link->nwamd_link_priority_mode <= c)) {
					nwamd_object_set_state
					    (NWAM_OBJECT_TYPE_NCU,
					    object->nwamd_object_name,
					    NWAM_STATE_OFFLINE_TO_ONLINE,
					    NWAM_AUX_STATE_INITIALIZED);
				} else {
					nwamd_object_set_state
					    (NWAM_OBJECT_TYPE_NCU,
					    object->nwamd_object_name,
					    NWAM_STATE_OFFLINE_TO_ONLINE,
					    NWAM_AUX_STATE_INITIALIZED);
				}
				goto done;
			}
		}

		switch (type) {
		case NWAM_NCU_TYPE_LINK:
			if (ncu->ncu_link.nwamd_link_media == DL_WIFI) {
				/*
				 * Do rescan.  If the current state and the
				 * active priority-group do not allow wireless
				 * network selection, then it won't happen.
				 */
				(void) nwamd_wlan_scan(ncu->ncu_name);
			}
			break;
		case NWAM_NCU_TYPE_INTERFACE:
			/*
			 * If interface NCU is offline*, online or in
			 * maintenance, mark it down (from there, it will be
			 * reinitialized to reapply addresses).
			 */
			if (object->nwamd_object_state != NWAM_STATE_OFFLINE) {
				nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
				    object->nwamd_object_name,
				    NWAM_STATE_ONLINE_TO_OFFLINE,
				    NWAM_AUX_STATE_DOWN);
			} else {
				object->nwamd_object_state = NWAM_STATE_OFFLINE;
				object->nwamd_object_aux_state =
				    NWAM_AUX_STATE_CONDITIONS_NOT_MET;
			}
			break;
		}
	}

done:
	if (type == NWAM_NCU_TYPE_LINK &&
	    !nwamd_event_enqueued(NWAM_EVENT_TYPE_NCU_CHECK,
	    NWAM_OBJECT_TYPE_NCP, NULL)) {
		nwamd_create_ncu_check_event(NEXT_FEW_SECONDS);
	}
	free(name);
	nwamd_object_release(object);
}

void
nwamd_ncu_handle_fini_event(nwamd_event_t event)
{
	nwamd_object_t object;
	nwamd_event_t state_event;

	nlog(LOG_DEBUG, "nwamd_ncu_handle_fini_event(%s)",
	    event->event_object);

	/*
	 * Simulate a state event so that the state machine can correctly
	 * disable the NCU.  Then free up allocated objects.
	 */
	state_event = nwamd_event_init_object_state(NWAM_OBJECT_TYPE_NCU,
	    event->event_object, NWAM_STATE_ONLINE_TO_OFFLINE,
	    NWAM_AUX_STATE_UNINITIALIZED);
	if (state_event == NULL) {
		nwamd_event_do_not_send(event);
		return;
	}
	nwamd_ncu_handle_state_event(state_event);
	nwamd_event_fini(state_event);

	if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU,
	    event->event_object)) == NULL) {
		nlog(LOG_INFO, "nwamd_ncu_handle_fini_event: "
		    "ncu %s not found", event->event_object);
		nwamd_event_do_not_send(event);
		return;
	}
	nwamd_object_release_and_destroy(object);
}

void
nwamd_ncu_handle_action_event(nwamd_event_t event)
{
	nwamd_object_t object;

	(void) pthread_mutex_lock(&active_ncp_mutex);
	if (strcmp(event->event_msg->nwe_data.nwe_object_action.nwe_parent,
	    active_ncp) != 0) {
		nlog(LOG_DEBUG, "nwamd_ncu_handle_action_event: action for "
		    "inactive NCP %s, nothing to do",
		    event->event_msg->nwe_data.nwe_object_action.nwe_parent);
		(void) pthread_mutex_unlock(&active_ncp_mutex);
		return;
	}
	(void) pthread_mutex_unlock(&active_ncp_mutex);

	switch (event->event_msg->nwe_data.nwe_object_action.nwe_action) {
	case NWAM_ACTION_ENABLE:
		object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU,
		    event->event_object);
		if (object == NULL) {
			nlog(LOG_ERR, "nwamd_ncu_handle_action_event: "
			    "could not find ncu %s", event->event_object);
			nwamd_event_do_not_send(event);
			return;
		}
		if (object->nwamd_object_state == NWAM_STATE_ONLINE) {
			nlog(LOG_DEBUG, "nwamd_ncu_handle_action_event: "
			    "ncu %s already online, nothing to do",
			    event->event_object);
			nwamd_object_release(object);
			return;
		}
		nwamd_object_release(object);

		nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
		    event->event_object, NWAM_STATE_OFFLINE_TO_ONLINE,
		    NWAM_AUX_STATE_INITIALIZED);
		break;
	case NWAM_ACTION_DISABLE:
		object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU,
		    event->event_object);
		if (object == NULL) {
			nlog(LOG_ERR, "nwamd_ncu_handle_action_event: "
			    "could not find ncu %s", event->event_object);
			nwamd_event_do_not_send(event);
			return;
		}
		if (object->nwamd_object_state == NWAM_STATE_DISABLED) {
			nlog(LOG_DEBUG, "nwamd_ncu_handle_action_event: "
			    "ncu %s already disabled, nothing to do",
			    event->event_object);
			nwamd_object_release(object);
			return;
		}
		nwamd_object_release(object);

		nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
		    event->event_object, NWAM_STATE_ONLINE_TO_OFFLINE,
		    NWAM_AUX_STATE_MANUAL_DISABLE);
		break;
	case NWAM_ACTION_ADD:
	case NWAM_ACTION_REFRESH:
		nwamd_ncu_handle_init_event(event);
		break;
	case NWAM_ACTION_DESTROY:
		nwamd_ncu_handle_fini_event(event);
		break;
	default:
		nlog(LOG_INFO, "nwam_ncu_handle_action_event: "
		    "unexpected action");
		nwamd_event_do_not_send(event);
		break;
	}
}

void
nwamd_ncu_handle_state_event(nwamd_event_t event)
{
	nwamd_object_t object;
	nwam_state_t old_state, new_state;
	nwam_aux_state_t new_aux_state;
	nwamd_ncu_t *ncu;
	boolean_t is_link, enabled, prioritized = B_FALSE;
	char linkname[NWAM_MAX_NAME_LEN];
	nwam_event_t m = event->event_msg;

	if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU,
	    event->event_object)) == NULL) {
		nlog(LOG_INFO, "nwamd_ncu_handle_state_event %lld: "
		    "state event for nonexistent NCU %s", event->event_id,
		    event->event_object);
		nwamd_event_do_not_send(event);
		return;
	}
	ncu = object->nwamd_object_data;
	old_state = object->nwamd_object_state;
	new_state = event->event_msg->nwe_data.nwe_object_state.nwe_state;
	new_aux_state =
	    event->event_msg->nwe_data.nwe_object_state.nwe_aux_state;

	/*
	 * For NCU state changes, we need to supply the parent NCP name also,
	 * regardless of whether the event is handled or not.  It is best to
	 * fill this in here as we have the object lock - when we create
	 * object state events we sometimes do not have the object lock, but
	 * at this point in consuming the events (and prior to the associated
	 * event message being sent out) we do.
	 */
	(void) strlcpy(m->nwe_data.nwe_object_state.nwe_parent, ncu->ncu_parent,
	    sizeof (m->nwe_data.nwe_object_state.nwe_parent));

	/*
	 * If we receive a state change event moving this NCU to
	 * DHCP_TIMED_OUT or UP state but this NCU is already ONLINE, then
	 * ignore this state change event.
	 */
	if ((new_aux_state == NWAM_AUX_STATE_IF_DHCP_TIMED_OUT ||
	    new_aux_state == NWAM_AUX_STATE_UP) &&
	    object->nwamd_object_state == NWAM_STATE_ONLINE) {
		nlog(LOG_INFO, "nwamd_ncu_handle_state_event: "
		    "NCU %s already online, not going to '%s' state",
		    object->nwamd_object_name,
		    nwam_aux_state_to_string(new_aux_state));
		nwamd_event_do_not_send(event);
		nwamd_object_release(object);
		return;
	}

	if (new_state == object->nwamd_object_state &&
	    new_aux_state == object->nwamd_object_aux_state) {
		nlog(LOG_DEBUG, "nwamd_ncu_handle_state_event: "
		    "NCU %s already in state (%s, %s)",
		    object->nwamd_object_name, nwam_state_to_string(new_state),
		    nwam_aux_state_to_string(new_aux_state));
		nwamd_object_release(object);
		return;
	}

	if (old_state == NWAM_STATE_MAINTENANCE &&
	    (new_state == NWAM_STATE_ONLINE ||
	    (new_state == NWAM_STATE_OFFLINE_TO_ONLINE &&
	    new_aux_state != NWAM_AUX_STATE_INITIALIZED))) {
		nlog(LOG_DEBUG, "nwamd_ncu_handle_state_event: "
		    "NCU %s cannot transition from state %s to state (%s, %s)",
		    object->nwamd_object_name, nwam_state_to_string(old_state),
		    nwam_state_to_string(new_state),
		    nwam_aux_state_to_string(new_aux_state));
		nwamd_event_do_not_send(event);
		nwamd_object_release(object);
		return;
	}

	object->nwamd_object_state = new_state;
	object->nwamd_object_aux_state = new_aux_state;

	nlog(LOG_DEBUG, "nwamd_ncu_handle_state_event: changing state for NCU "
	    "%s to (%s, %s)", object->nwamd_object_name,
	    nwam_state_to_string(object->nwamd_object_state),
	    nwam_aux_state_to_string(object->nwamd_object_aux_state));

	is_link = (ncu->ncu_type == NWAM_NCU_TYPE_LINK);
	if (is_link)
		(void) strlcpy(linkname, ncu->ncu_name, sizeof (linkname));
	prioritized = (ncu->ncu_type == NWAM_NCU_TYPE_LINK &&
	    ncu->ncu_link.nwamd_link_activation_mode ==
	    NWAM_ACTIVATION_MODE_PRIORITIZED);
	enabled = ncu->ncu_enabled;

	nwamd_object_release(object);

	/*
	 * State machine for NCUs
	 */
	switch (new_state) {
	case NWAM_STATE_OFFLINE_TO_ONLINE:
		if (enabled) {
			nwamd_ncu_state_machine(event->event_object);
		} else {
			nlog(LOG_DEBUG, "nwamd_ncu_handle_state_event: "
			    "cannot move disabled NCU %s online",
			    event->event_object);
			nwamd_event_do_not_send(event);
		}
		break;

	case NWAM_STATE_ONLINE_TO_OFFLINE:
		nwamd_ncu_state_machine(event->event_object);
		break;

	case NWAM_STATE_ONLINE:
		/*
		 * We usually don't need to do anything when we're in the
		 * ONLINE state.  However, for  WiFi we can be in INIT or
		 * SCAN aux states while being ONLINE.
		 */
		nwamd_ncu_state_machine(event->event_object);
		break;

	case NWAM_STATE_OFFLINE:
		/* Reassess priority group now member is offline */
		if (prioritized) {
			nwamd_create_ncu_check_event(0);
		}
		break;

	case NWAM_STATE_DISABLED:
	case NWAM_STATE_UNINITIALIZED:
	case NWAM_STATE_MAINTENANCE:
	case NWAM_STATE_DEGRADED:
	default:
		/* do nothing */
		break;
	}

	if (is_link) {
		if ((new_state == NWAM_STATE_ONLINE_TO_OFFLINE &&
		    new_aux_state != NWAM_AUX_STATE_UNINITIALIZED &&
		    new_aux_state != NWAM_AUX_STATE_NOT_FOUND) ||
		    new_state == NWAM_STATE_DISABLED) {
			/*
			 * Going offline, propogate down event to IP NCU.  Do
			 * not propogate event if new aux state is uninitialized
			 * or not found as these auxiliary states signify
			 * that an NCP switch/device removal is in progress.
			 */
			nwamd_propogate_link_up_down_to_ip(linkname, B_FALSE);
		}
		if (new_state == NWAM_STATE_ONLINE) {
			/* gone online, propogate up event to IP NCU */
			nwamd_propogate_link_up_down_to_ip(linkname, B_TRUE);
		}
	} else {
		/* If IP NCU is online, reasses priority group */
		if (new_state == NWAM_STATE_ONLINE)
			nwamd_create_ncu_check_event(0);
	}
}
