view usr/src/cmd/cmd-inet/lib/nwamd/ncu.c @ 12965:7a5c324190b8

6936702 dlmgmtd restart in global zone leaves exclusive stack zones in down state 6942573 stale dlmgmtd door handle may result in unusable networking features 6938483 NWAM should not ignore VNICs in non-global zones 6931751 DATALINK_CLASS_IPTUN should be 0x80 not 0x60 6939454 dladm show_linkprop -p <private property> truncates DEFAULT value by one character 6928716 dladm delete-secobj passing uninitialized memory 6896251 dladm show-linkprop -P always exits with 1
author Rishi Srivatsavai <Rishi.Srivatsavai@Sun.COM>
date Thu, 29 Jul 2010 02:14:42 -0400
parents eb113dc3db0b
children
line wrap: on
line source

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

/*
 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 */

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

/*
 * 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;
	uint64_t *addrsrcvalue;
	nwam_value_t ncu_prop;
	nwam_error_t err;
	ipadm_addrobj_t ipaddr;
	ipadm_status_t ipstatus;
	char **addrvalue;
	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);
	}

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

finish:
	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);
	}
}