xref: /titanic_44/usr/src/cmd/cmd-inet/lib/nwamd/ncu.c (revision 8339b41da2395f0525c46ceedfeb01961893ec44)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
24  */
25 
26 #include <arpa/inet.h>
27 #include <assert.h>
28 #include <libdlaggr.h>
29 #include <libdllink.h>
30 #include <libdlstat.h>
31 #include <libnwam.h>
32 #include <libscf.h>
33 #include <netinet/in.h>
34 #include <stdlib.h>
35 #include <strings.h>
36 #include <sys/socket.h>
37 #include <sys/time.h>
38 #include <sys/types.h>
39 #include <values.h>
40 
41 #include "conditions.h"
42 #include "events.h"
43 #include "objects.h"
44 #include "ncp.h"
45 #include "util.h"
46 
47 /*
48  * ncu.c - handles various NCU tasks - intialization/refresh, state machine
49  * for NCUs etc.
50  */
51 
52 #define	VBOX_IFACE_PREFIX	"vboxnet"
53 
54 /*
55  * Find ncu of specified type for link/interface name.
56  */
57 nwamd_object_t
58 nwamd_ncu_object_find(nwam_ncu_type_t type, const char *name)
59 {
60 	nwam_error_t err;
61 	char *object_name;
62 	nwamd_object_t ncu_obj = NULL;
63 
64 	if ((err = nwam_ncu_name_to_typed_name(name, type, &object_name))
65 	    != NWAM_SUCCESS) {
66 		nlog(LOG_ERR, "nwamd_ncu_find: nwam_ncu_name_to_typed_name "
67 		    "returned %s", nwam_strerror(err));
68 		return (NULL);
69 	}
70 	ncu_obj = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, object_name);
71 
72 	free(object_name);
73 	return (ncu_obj);
74 }
75 
76 nwam_error_t
77 nwamd_set_ncu_string(nwam_ncu_handle_t ncuh, char **strval, uint_t cnt,
78     const char *prop)
79 {
80 	nwam_error_t err;
81 	nwam_value_t val;
82 
83 	if ((err = nwam_value_create_string_array(strval, cnt, &val))
84 	    != NWAM_SUCCESS)
85 		return (err);
86 	err = nwam_ncu_set_prop_value(ncuh, prop, val);
87 	nwam_value_free(val);
88 	return (err);
89 }
90 
91 nwam_error_t
92 nwamd_set_ncu_uint(nwam_ncu_handle_t ncuh, uint64_t *uintval, uint_t cnt,
93     const char *prop)
94 {
95 	nwam_error_t err;
96 	nwam_value_t val;
97 
98 	if ((err = nwam_value_create_uint64_array(uintval, cnt, &val))
99 	    != NWAM_SUCCESS)
100 		return (err);
101 	err = nwam_ncu_set_prop_value(ncuh, prop, val);
102 	nwam_value_free(val);
103 	return (err);
104 }
105 
106 nwam_error_t
107 nwamd_get_ncu_string(nwam_ncu_handle_t ncuh, nwam_value_t *val, char ***strval,
108     uint_t *cnt, const char *prop)
109 {
110 	nwam_error_t err;
111 
112 	if ((err = nwam_ncu_get_prop_value(ncuh, prop, val)) != NWAM_SUCCESS)
113 		return (err);
114 	return (nwam_value_get_string_array(*val, strval, cnt));
115 }
116 
117 nwam_error_t
118 nwamd_get_ncu_uint(nwam_ncu_handle_t ncuh, nwam_value_t *val,
119     uint64_t **uintval, uint_t *cnt, const char *prop)
120 {
121 	nwam_error_t err;
122 
123 	if ((err = nwam_ncu_get_prop_value(ncuh, prop, val)) != NWAM_SUCCESS)
124 		return (err);
125 	return (nwam_value_get_uint64_array(*val, uintval, cnt));
126 }
127 
128 /*
129  * Run link/interface state machine in response to a state change
130  * or enable/disable action event.
131  */
132 static void
133 nwamd_ncu_state_machine(const char *object_name)
134 {
135 	nwamd_object_t object;
136 	nwamd_ncu_t *ncu;
137 	link_state_t link_state;
138 	nwamd_event_t event;
139 	nwam_wlan_t key_wlan, connected_wlan;
140 	nwamd_link_t *link;
141 	char linkname[NWAM_MAX_NAME_LEN];
142 	boolean_t up;
143 
144 	if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, object_name))
145 	    == NULL) {
146 		nlog(LOG_ERR, "nwamd_ncu_state_machine: "
147 		    "request for nonexistent NCU %s", object_name);
148 		return;
149 	}
150 
151 	ncu = object->nwamd_object_data;
152 	link = &ncu->ncu_node.u_link;
153 
154 	switch (object->nwamd_object_aux_state) {
155 	case NWAM_AUX_STATE_INITIALIZED:
156 		if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) {
157 			/*
158 			 * For wired/wireless links, need to get link
159 			 * up/down events and even if these are not supported,
160 			 * dlpi_open()ing the link prevents the driver from
161 			 * being unloaded.
162 			 */
163 			nwamd_dlpi_add_link(object);
164 
165 			if (link->nwamd_link_media == DL_WIFI) {
166 				/*
167 				 * First, if we're unexpectedly connected,
168 				 * disconnect.
169 				 */
170 				if (!link->nwamd_link_wifi_connected &&
171 				    nwamd_wlan_connected(object)) {
172 					nlog(LOG_DEBUG,
173 					    "nwamd_ncu_state_machine: "
174 					    "WiFi unexpectedly connected, "
175 					    "disconnecting...");
176 					(void) dladm_wlan_disconnect(dld_handle,
177 					    link->nwamd_link_id);
178 					nwamd_set_selected_connected(ncu,
179 					    B_FALSE, B_FALSE);
180 				}
181 				/* move to scanning aux state */
182 				nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
183 				    object_name, object->nwamd_object_state,
184 				    NWAM_AUX_STATE_LINK_WIFI_SCANNING);
185 			} else {
186 				/*
187 				 * If initial wired link state is unknown, we
188 				 * will need to assume the link is up, since
189 				 * we won´t get DL_NOTE_LINK_UP/DOWN events.
190 				 */
191 				link_state = nwamd_get_link_state
192 				    (ncu->ncu_name);
193 				if (link_state == LINK_STATE_UP ||
194 				    link_state == LINK_STATE_UNKNOWN) {
195 					nwamd_object_set_state
196 					    (NWAM_OBJECT_TYPE_NCU,
197 					    object_name, NWAM_STATE_ONLINE,
198 					    NWAM_AUX_STATE_UP);
199 				} else {
200 					nwamd_object_set_state
201 					    (NWAM_OBJECT_TYPE_NCU,
202 					    object_name,
203 					    NWAM_STATE_ONLINE_TO_OFFLINE,
204 					    NWAM_AUX_STATE_DOWN);
205 				}
206 			}
207 		} else {
208 			/*
209 			 * In the current implementation, initialization has to
210 			 * start from scratch since the complexity of minimizing
211 			 * configuration change is considerable (e.g. if we
212 			 * refresh and had DHCP running on the physical
213 			 * interface, and now have changed to static assignment,
214 			 * we need to remove DHCP etc).  To avoid all this,
215 			 * unplumb before re-plumbing the protocols and
216 			 * addresses we wish to configure.  In the future, it
217 			 * would be good to try and minimize configuration
218 			 * changes.
219 			 */
220 			nwamd_unplumb_interface(ncu, 0, AF_INET);
221 			nwamd_unplumb_interface(ncu, 0, AF_INET6);
222 
223 			/*
224 			 * Enqueue a WAITING_FOR_ADDR aux state change so that
225 			 * we are eligible to receive the IF_STATE events
226 			 * associated with static, DHCP, DHCPv6 and autoconf
227 			 * address assignment.  The latter two can happen
228 			 * quite quickly after plumbing so we need to be ready.
229 			 */
230 			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
231 			    object_name, NWAM_STATE_OFFLINE_TO_ONLINE,
232 			    NWAM_AUX_STATE_IF_WAITING_FOR_ADDR);
233 
234 			if (ncu->ncu_node.u_if.nwamd_if_ipv4)
235 				nwamd_plumb_interface(ncu, 0, AF_INET);
236 
237 			if (ncu->ncu_node.u_if.nwamd_if_ipv6)
238 				nwamd_plumb_interface(ncu, 0, AF_INET6);
239 
240 			/*
241 			 * Configure addresses.  Configure any static addresses
242 			 * and start DHCP if required.  If DHCP is not required,
243 			 * do a DHCPINFORM to get other networking config
244 			 * parameters.  RTM_NEWADDRs - translated into IF_STATE
245 			 * events - will then finish the job of bringing us
246 			 * online.
247 			 */
248 			nwamd_configure_interface_addresses(ncu);
249 
250 			if (ncu->ncu_node.u_if.nwamd_if_dhcp_requested)
251 				nwamd_start_dhcp(ncu);
252 			else
253 				nwamd_dhcp_inform(ncu);
254 		}
255 		break;
256 
257 	case NWAM_AUX_STATE_IF_DHCP_TIMED_OUT:
258 	case NWAM_AUX_STATE_IF_WAITING_FOR_ADDR:
259 		/*
260 		 * nothing to do here - RTM_NEWADDRs will trigger IF_STATE
261 		 * events to move us online.
262 		 */
263 		break;
264 
265 	case NWAM_AUX_STATE_LINK_WIFI_SCANNING:
266 		/* launch scan thread */
267 		(void) strlcpy(linkname, ncu->ncu_name, sizeof (linkname));
268 		(void) nwamd_wlan_scan(linkname);
269 		/* Create periodic scan event */
270 		nwamd_ncu_create_periodic_scan_event(object);
271 		break;
272 
273 	case NWAM_AUX_STATE_LINK_WIFI_NEED_SELECTION:
274 		/* send "need choice" event */
275 		event = nwamd_event_init_wlan
276 		    (ncu->ncu_name, NWAM_EVENT_TYPE_WLAN_NEED_CHOICE, B_FALSE,
277 		    link->nwamd_link_wifi_scan.nwamd_wifi_scan_curr,
278 		    link->nwamd_link_wifi_scan.nwamd_wifi_scan_curr_num);
279 		if (event == NULL)
280 			break;
281 		nwamd_event_enqueue(event);
282 		nwamd_set_selected_connected(ncu, B_FALSE, B_FALSE);
283 		break;
284 
285 	case NWAM_AUX_STATE_LINK_WIFI_NEED_KEY:
286 		/*
287 		 * Send "need key" event.  Set selected to true, connected
288 		 * and have_key to false.  Do not fill in WLAN details as
289 		 * multiple WLANs may match the ESSID name, and each may
290 		 * have a different speed and channel.
291 		 */
292 		bzero(&key_wlan, sizeof (key_wlan));
293 		(void) strlcpy(key_wlan.nww_essid, link->nwamd_link_wifi_essid,
294 		    sizeof (key_wlan.nww_essid));
295 		(void) strlcpy(key_wlan.nww_bssid, link->nwamd_link_wifi_bssid,
296 		    sizeof (key_wlan.nww_bssid));
297 		key_wlan.nww_security_mode =
298 		    link->nwamd_link_wifi_security_mode;
299 		key_wlan.nww_selected = B_TRUE;
300 		key_wlan.nww_connected = B_FALSE;
301 		key_wlan.nww_have_key = B_FALSE;
302 		event = nwamd_event_init_wlan
303 		    (ncu->ncu_name, NWAM_EVENT_TYPE_WLAN_NEED_KEY, B_FALSE,
304 		    &key_wlan, 1);
305 		if (event == NULL)
306 			break;
307 		nwamd_event_enqueue(event);
308 		break;
309 
310 	case NWAM_AUX_STATE_LINK_WIFI_CONNECTING:
311 		(void) strlcpy(linkname, ncu->ncu_name, sizeof (linkname));
312 		nwamd_wlan_connect(linkname);
313 		break;
314 
315 	case NWAM_AUX_STATE_UP:
316 	case NWAM_AUX_STATE_DOWN:
317 		up = (object->nwamd_object_aux_state == NWAM_AUX_STATE_UP);
318 		if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) {
319 			if (link->nwamd_link_media == DL_WIFI) {
320 				/*
321 				 * Connected/disconnected - send WLAN
322 				 * connection report.
323 				 */
324 				link->nwamd_link_wifi_connected = up;
325 				nwamd_set_selected_connected(ncu, B_TRUE, up);
326 
327 				(void) strlcpy(connected_wlan.nww_essid,
328 				    link->nwamd_link_wifi_essid,
329 				    sizeof (connected_wlan.nww_essid));
330 				(void) strlcpy(connected_wlan.nww_bssid,
331 				    link->nwamd_link_wifi_bssid,
332 				    sizeof (connected_wlan.nww_bssid));
333 				connected_wlan.nww_security_mode =
334 				    link->nwamd_link_wifi_security_mode;
335 				event = nwamd_event_init_wlan
336 				    (ncu->ncu_name,
337 				    NWAM_EVENT_TYPE_WLAN_CONNECTION_REPORT, up,
338 				    &connected_wlan, 1);
339 				if (event == NULL)
340 					break;
341 				nwamd_event_enqueue(event);
342 
343 				/*
344 				 * If disconnected, restart the state machine
345 				 * for the WiFi link (WiFi is always trying
346 				 * to connect).
347 				 *
348 				 * If connected, start signal strength
349 				 * monitoring thread.
350 				 */
351 				if (!up && ncu->ncu_enabled) {
352 					nlog(LOG_DEBUG,
353 					    "nwamd_ncu_state_machine: "
354 					    "wifi disconnect - start over "
355 					    "after %dsec interval",
356 					    WIRELESS_RETRY_INTERVAL);
357 					link->nwamd_link_wifi_connected =
358 					    B_FALSE;
359 					/* propogate down event to IP NCU */
360 					nwamd_propogate_link_up_down_to_ip
361 					    (ncu->ncu_name, B_FALSE);
362 					nwamd_object_set_state_timed
363 					    (NWAM_OBJECT_TYPE_NCU, object_name,
364 					    NWAM_STATE_OFFLINE_TO_ONLINE,
365 					    NWAM_AUX_STATE_INITIALIZED,
366 					    WIRELESS_RETRY_INTERVAL);
367 				} else {
368 					nlog(LOG_DEBUG,
369 					    "nwamd_ncu_state_machine: "
370 					    "wifi connected, start monitoring");
371 					(void) strlcpy(linkname, ncu->ncu_name,
372 					    sizeof (linkname));
373 					nwamd_wlan_monitor_signal(linkname);
374 				}
375 			}
376 		}
377 
378 		/* If not in ONLINE/OFFLINE state yet, change state */
379 		if ((up && object->nwamd_object_state != NWAM_STATE_ONLINE) ||
380 		    (!up && object->nwamd_object_state != NWAM_STATE_OFFLINE)) {
381 			nlog(LOG_DEBUG, "nwamd_ncu_state_machine: "
382 			    "%s is moving %s", object_name,
383 			    up ? "online" : "offline");
384 			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
385 			    object_name,
386 			    up ? NWAM_STATE_ONLINE : NWAM_STATE_OFFLINE,
387 			    up ? NWAM_AUX_STATE_UP : NWAM_AUX_STATE_DOWN);
388 
389 			if (ncu->ncu_type == NWAM_NCU_TYPE_INTERFACE) {
390 				if (up) {
391 					/*
392 					 * Moving online, add v4/v6 default
393 					 * routes (if any).
394 					 */
395 					nwamd_add_default_routes(ncu);
396 				} else {
397 					/*
398 					 * If this is an interface NCU and we
399 					 * got a down event, it is a consequence
400 					 * of NCU refresh, so reapply addresses
401 					 * by reinitializing.
402 					 */
403 					nwamd_object_set_state
404 					    (NWAM_OBJECT_TYPE_NCU, object_name,
405 					    NWAM_STATE_OFFLINE_TO_ONLINE,
406 					    NWAM_AUX_STATE_INITIALIZED);
407 				}
408 			}
409 		} else {
410 			nlog(LOG_DEBUG, "nwamd_ncu_state_machine: "
411 			    "%s is %s", object_name,
412 			    up ? "online" : "offline");
413 		}
414 		/*
415 		 * NCU is UP or DOWN, trigger all condition checking, even if
416 		 * the NCU is already in the ONLINE state - an ENM may depend
417 		 * on NCU activity.
418 		 */
419 		nwamd_create_triggered_condition_check_event(NEXT_FEW_SECONDS);
420 		break;
421 
422 	case NWAM_AUX_STATE_CONDITIONS_NOT_MET:
423 		/*
424 		 * Link/interface is moving offline.  Nothing to do except
425 		 * for WiFi, where we disconnect.  Don't unplumb IP on
426 		 * a link since it may be a transient change.
427 		 */
428 		if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) {
429 			if (link->nwamd_link_media == DL_WIFI) {
430 				(void) dladm_wlan_disconnect(dld_handle,
431 				    link->nwamd_link_id);
432 				link->nwamd_link_wifi_connected = B_FALSE;
433 				nwamd_set_selected_connected(ncu, B_FALSE,
434 				    B_FALSE);
435 			}
436 		} else {
437 			/*
438 			 * Unplumb here. In the future we may elaborate on
439 			 * the approach used and not unplumb for WiFi
440 			 * until we reconnect to a different WLAN (i.e. with
441 			 * a different ESSID).
442 			 */
443 			nwamd_unplumb_interface(ncu, 0, AF_INET);
444 			nwamd_unplumb_interface(ncu, 0, AF_INET6);
445 		}
446 		if (object->nwamd_object_state != NWAM_STATE_OFFLINE) {
447 			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
448 			    object_name, NWAM_STATE_OFFLINE,
449 			    NWAM_AUX_STATE_CONDITIONS_NOT_MET);
450 		}
451 		break;
452 
453 	case NWAM_AUX_STATE_MANUAL_DISABLE:
454 		/* Manual disable, set enabled state appropriately. */
455 		ncu->ncu_enabled = B_FALSE;
456 		/* FALLTHROUGH */
457 	case NWAM_AUX_STATE_UNINITIALIZED:
458 	case NWAM_AUX_STATE_NOT_FOUND:
459 		/*
460 		 * Link/interface NCU has been disabled/deactivated/removed.
461 		 * For WiFi links disconnect, and for IP interfaces we unplumb.
462 		 */
463 		if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) {
464 			if (link->nwamd_link_media == DL_WIFI) {
465 				(void) dladm_wlan_disconnect(dld_handle,
466 				    link->nwamd_link_id);
467 				link->nwamd_link_wifi_connected = B_FALSE;
468 				nwamd_set_selected_connected(ncu, B_FALSE,
469 				    B_FALSE);
470 			}
471 			nwamd_dlpi_delete_link(object);
472 		} else {
473 			/* Unplumb here. */
474 			if (ncu->ncu_node.u_if.nwamd_if_ipv4) {
475 				nwamd_unplumb_interface(ncu, 0, AF_INET);
476 			}
477 			if (ncu->ncu_node.u_if.nwamd_if_ipv6) {
478 				nwamd_unplumb_interface(ncu, 0, AF_INET6);
479 			}
480 			/* trigger location condition checking */
481 			nwamd_create_triggered_condition_check_event(0);
482 		}
483 
484 		switch (object->nwamd_object_aux_state) {
485 		case NWAM_AUX_STATE_MANUAL_DISABLE:
486 			/* Change state to DISABLED if manually disabled */
487 			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
488 			    object_name, NWAM_STATE_DISABLED,
489 			    NWAM_AUX_STATE_MANUAL_DISABLE);
490 			/* Note that NCU has been disabled */
491 			ncu->ncu_enabled = B_FALSE;
492 			break;
493 		case NWAM_AUX_STATE_NOT_FOUND:
494 			/* Change state to UNINITIALIZED for device removal */
495 			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
496 			    object_name, NWAM_STATE_UNINITIALIZED,
497 			    NWAM_AUX_STATE_NOT_FOUND);
498 			break;
499 		default:
500 			break;
501 		}
502 		break;
503 	default:
504 		nlog(LOG_ERR, "nwamd_ncu_state_machine: unexpected state");
505 		break;
506 	}
507 
508 	nwamd_object_release(object);
509 }
510 
511 static int
512 ncu_create_init_fini_event(nwam_ncu_handle_t ncuh, void *data)
513 {
514 	boolean_t *init = data;
515 	char *name, *typedname;
516 	nwam_error_t err;
517 	nwam_value_t typeval = NULL;
518 	uint64_t *type;
519 	uint_t numvalues;
520 	nwamd_event_t ncu_event;
521 
522 	if (nwam_ncu_get_name(ncuh, &name) != NWAM_SUCCESS) {
523 		nlog(LOG_ERR,
524 		    "ncu_create_init_fini_event: could not get NCU name");
525 		return (0);
526 	}
527 
528 	nlog(LOG_DEBUG, "ncu_create_init_fini_event(%s, %p)", name, data);
529 
530 	if ((err = nwamd_get_ncu_uint(ncuh, &typeval, &type, &numvalues,
531 	    NWAM_NCU_PROP_TYPE)) != NWAM_SUCCESS) {
532 		nlog(LOG_ERR, "ncu_create_init_fini_event: "
533 		    "could not get NCU type: %s", nwam_strerror(err));
534 		free(name);
535 		nwam_value_free(typeval);
536 		return (0);
537 	}
538 
539 	/* convert name to typedname for event */
540 	if ((err = nwam_ncu_name_to_typed_name(name, *type, &typedname))
541 	    != NWAM_SUCCESS) {
542 		nlog(LOG_ERR, "ncu_create_init_fini_event: "
543 		    "NCU name translation failed: %s", nwam_strerror(err));
544 		free(name);
545 		return (0);
546 	}
547 	free(name);
548 	nwam_value_free(typeval);
549 
550 	ncu_event = nwamd_event_init(*init ?
551 	    NWAM_EVENT_TYPE_OBJECT_INIT : NWAM_EVENT_TYPE_OBJECT_FINI,
552 	    NWAM_OBJECT_TYPE_NCU, 0, typedname);
553 	if (ncu_event != NULL)
554 		nwamd_event_enqueue(ncu_event);
555 	free(typedname);
556 
557 	return (0);
558 }
559 
560 /*
561  * Initialization - walk the NCUs, creating initialization events for each
562  * NCU.  nwamd_ncu_handle_init_event() will check if the associated
563  * physical link exists or not.
564  */
565 void
566 nwamd_init_ncus(void)
567 {
568 	boolean_t init = B_TRUE;
569 
570 	(void) pthread_mutex_lock(&active_ncp_mutex);
571 	if (active_ncph != NULL) {
572 		nlog(LOG_DEBUG, "nwamd_init_ncus: "
573 		    "(re)intializing NCUs for NCP %s", active_ncp);
574 		(void) nwam_ncp_walk_ncus(active_ncph,
575 		    ncu_create_init_fini_event, &init, NWAM_FLAG_NCU_TYPE_ALL,
576 		    NULL);
577 	}
578 	(void) pthread_mutex_unlock(&active_ncp_mutex);
579 }
580 
581 void
582 nwamd_fini_ncus(void)
583 {
584 	boolean_t init = B_FALSE;
585 
586 	/* We may not have an active NCP on initialization, so skip fini */
587 	(void) pthread_mutex_lock(&active_ncp_mutex);
588 	if (active_ncph != NULL) {
589 		nlog(LOG_DEBUG, "nwamd_fini_ncus: deinitializing NCUs for %s",
590 		    active_ncp);
591 		(void) nwam_ncp_walk_ncus(active_ncph,
592 		    ncu_create_init_fini_event, &init, NWAM_FLAG_NCU_TYPE_ALL,
593 		    NULL);
594 	}
595 	(void) pthread_mutex_unlock(&active_ncp_mutex);
596 }
597 
598 /*
599  * Most properties of this type don't need to be cached locally.  Only those
600  * interesting to the daemon are stored in an nwamd_ncu_t.
601  */
602 static void
603 populate_common_ncu_properties(nwam_ncu_handle_t ncuh, nwamd_ncu_t *ncu_data)
604 {
605 	nwam_value_t ncu_prop;
606 	nwam_error_t err;
607 	boolean_t enablevalue;
608 	uint_t numvalues;
609 	char **parent;
610 
611 	if ((err = nwam_ncu_get_prop_value(ncuh, NWAM_NCU_PROP_ENABLED,
612 	    &ncu_prop)) != NWAM_SUCCESS) {
613 		char *name;
614 		(void) nwam_ncu_name_to_typed_name(ncu_data->ncu_name,
615 		    ncu_data->ncu_type, &name);
616 		nlog(LOG_ERR, "nwam_ncu_get_prop_value %s ENABLED failed: %s",
617 		    name, nwam_strerror(err));
618 		free(name);
619 		ncu_data->ncu_enabled = B_TRUE;
620 	} else {
621 		if ((err = nwam_value_get_boolean(ncu_prop, &enablevalue)) !=
622 		    NWAM_SUCCESS) {
623 			nlog(LOG_ERR, "nwam_value_get_boolean ENABLED failed: "
624 			    "%s", nwam_strerror(err));
625 		} else {
626 			ncu_data->ncu_enabled = enablevalue;
627 		}
628 		nwam_value_free(ncu_prop);
629 	}
630 
631 	if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &parent,
632 	    &numvalues, NWAM_NCU_PROP_PARENT_NCP)) != NWAM_SUCCESS) {
633 		nlog(LOG_ERR, "nwam_ncu_get_prop_value %s PARENT failed: %s",
634 		    ncu_data->ncu_name, nwam_strerror(err));
635 	} else {
636 		(void) strlcpy(ncu_data->ncu_parent, parent[0],
637 		    sizeof (ncu_data->ncu_parent));
638 		nwam_value_free(ncu_prop);
639 	}
640 }
641 
642 /*
643  * Read in link properties.
644  */
645 static void
646 populate_link_ncu_properties(nwam_ncu_handle_t ncuh, nwamd_ncu_t *ncu_data)
647 {
648 	nwam_value_t ncu_prop;
649 	nwam_error_t err;
650 	char **mac_addr;
651 	uint64_t *uintval;
652 	uint_t numvalues;
653 
654 	/* activation-mode */
655 	if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &uintval, &numvalues,
656 	    NWAM_NCU_PROP_ACTIVATION_MODE)) != NWAM_SUCCESS) {
657 		nlog(LOG_ERR,
658 		    "populate_link_ncu_properties: could not get %s value: %s",
659 		    NWAM_NCU_PROP_ACTIVATION_MODE, nwam_strerror(err));
660 	} else {
661 		ncu_data->ncu_node.u_link.nwamd_link_activation_mode =
662 		    uintval[0];
663 		nwam_value_free(ncu_prop);
664 	}
665 
666 	/* priority-group and priority-mode for prioritized activation */
667 	if (ncu_data->ncu_node.u_link.nwamd_link_activation_mode ==
668 	    NWAM_ACTIVATION_MODE_PRIORITIZED) {
669 		/* ncus with prioritized activation are always enabled */
670 		ncu_data->ncu_enabled = B_TRUE;
671 		if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &uintval,
672 		    &numvalues, NWAM_NCU_PROP_PRIORITY_MODE))
673 		    != NWAM_SUCCESS) {
674 			nlog(LOG_ERR, "populate_link_ncu_properties: "
675 			    "could not get %s value: %s",
676 			    NWAM_NCU_PROP_PRIORITY_MODE, nwam_strerror(err));
677 		} else {
678 			ncu_data->ncu_node.u_link.nwamd_link_priority_mode =
679 			    uintval[0];
680 			nwam_value_free(ncu_prop);
681 		}
682 
683 		if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &uintval,
684 		    &numvalues, NWAM_NCU_PROP_PRIORITY_GROUP))
685 		    != NWAM_SUCCESS) {
686 			nlog(LOG_ERR, "populate_link_ncu_properties: "
687 			    "could not get %s value: %s",
688 			    NWAM_NCU_PROP_PRIORITY_GROUP, nwam_strerror(err));
689 		} else {
690 			ncu_data->ncu_node.u_link.nwamd_link_priority_group =
691 			    uintval[0];
692 			nwam_value_free(ncu_prop);
693 		}
694 	}
695 
696 	/* link-mac-addr */
697 	if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &mac_addr, &numvalues,
698 	    NWAM_NCU_PROP_LINK_MAC_ADDR)) != NWAM_SUCCESS) {
699 		nlog(LOG_DEBUG,
700 		    "populate_link_ncu_properties: could not get %s value: %s",
701 		    NWAM_NCU_PROP_LINK_MAC_ADDR, nwam_strerror(err));
702 		ncu_data->ncu_node.u_link.nwamd_link_mac_addr = NULL;
703 	} else {
704 		ncu_data->ncu_node.u_link.nwamd_link_mac_addr =
705 		    strdup(*mac_addr);
706 		ncu_data->ncu_node.u_link.nwamd_link_mac_addr_len =
707 		    strlen(*mac_addr);
708 		nwam_value_free(ncu_prop);
709 	}
710 
711 	/* link-mtu */
712 	if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &uintval, &numvalues,
713 	    NWAM_NCU_PROP_LINK_MTU)) != NWAM_SUCCESS) {
714 		nlog(LOG_DEBUG,
715 		    "populate_link_ncu_properties: could not get %s value: %s",
716 		    NWAM_NCU_PROP_LINK_MTU, nwam_strerror(err));
717 		ncu_data->ncu_node.u_link.nwamd_link_mtu = 0;
718 	} else {
719 		ncu_data->ncu_node.u_link.nwamd_link_mtu = uintval[0];
720 		nwam_value_free(ncu_prop);
721 	}
722 
723 	/* link-autopush */
724 	if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop,
725 	    &ncu_data->ncu_node.u_link.nwamd_link_autopush,
726 	    &ncu_data->ncu_node.u_link.nwamd_link_num_autopush,
727 	    NWAM_NCU_PROP_LINK_AUTOPUSH)) != NWAM_SUCCESS) {
728 		nlog(LOG_DEBUG,
729 		    "populate_link_ncu_properties: could not get %s value: %s",
730 		    NWAM_NCU_PROP_LINK_AUTOPUSH, nwam_strerror(err));
731 		ncu_data->ncu_node.u_link.nwamd_link_num_autopush = 0;
732 	}
733 }
734 
735 static void
736 populate_ip_ncu_properties(nwam_ncu_handle_t ncuh, nwamd_ncu_t *ncu_data)
737 {
738 	nwamd_if_t *nif = &ncu_data->ncu_node.u_if;
739 	struct nwamd_if_address **nifa, *nifai, *nifait;
740 	char *prefix;
741 	boolean_t static_addr = B_FALSE;
742 	uint64_t *addrsrcvalue;
743 	nwam_value_t ncu_prop;
744 	nwam_error_t err;
745 	char **addrvalue;
746 	uint_t numvalues;
747 	uint64_t *ipversion;
748 	int i;
749 
750 	nif->nwamd_if_ipv4 = B_FALSE;
751 	nif->nwamd_if_ipv6 = B_FALSE;
752 	nif->nwamd_if_dhcp_requested = B_FALSE;
753 	nif->nwamd_if_stateful_requested = B_FALSE;
754 	nif->nwamd_if_stateless_requested = B_FALSE;
755 	nif->nwamd_if_ipv4_default_route_set = B_FALSE;
756 	nif->nwamd_if_ipv6_default_route_set = B_FALSE;
757 
758 	/* ip-version */
759 	if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &ipversion, &numvalues,
760 	    NWAM_NCU_PROP_IP_VERSION)) != NWAM_SUCCESS) {
761 		nlog(LOG_ERR,
762 		    "populate_ip_ncu_properties: could not get %s value: %s",
763 		    NWAM_NCU_PROP_IP_VERSION, nwam_strerror(err));
764 	} else {
765 		for (i = 0; i < numvalues; i++) {
766 			switch (ipversion[i]) {
767 			case IPV4_VERSION:
768 				nif->nwamd_if_ipv4 = B_TRUE;
769 				break;
770 			case IPV6_VERSION:
771 				nif->nwamd_if_ipv6 = B_TRUE;
772 				break;
773 			default:
774 				nlog(LOG_ERR, "bogus ip version %lld",
775 				    ipversion[i]);
776 				break;
777 			}
778 		}
779 		nwam_value_free(ncu_prop);
780 	}
781 
782 	/* Free the old list. */
783 	for (nifai = nif->nwamd_if_list; nifai != NULL; nifai = nifait) {
784 		nifait = nifai->next;
785 		nifai->next = NULL;
786 		free(nifai);
787 	}
788 	nif->nwamd_if_list = NULL;
789 	nifa = &(nif->nwamd_if_list);
790 
791 	if (!nif->nwamd_if_ipv4)
792 		goto skip_ipv4;
793 
794 	/* ipv4-addrsrc */
795 	if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &addrsrcvalue,
796 	    &numvalues, NWAM_NCU_PROP_IPV4_ADDRSRC)) != NWAM_SUCCESS) {
797 		nlog(nif->nwamd_if_ipv4 ? LOG_ERR : LOG_DEBUG,
798 		    "populate_ip_ncu_properties: could not get %s value: %s",
799 		    NWAM_NCU_PROP_IPV4_ADDRSRC, nwam_strerror(err));
800 	} else {
801 		for (i = 0; i < numvalues; i++) {
802 			switch (addrsrcvalue[i]) {
803 			case NWAM_ADDRSRC_DHCP:
804 				nif->nwamd_if_dhcp_requested = B_TRUE;
805 				break;
806 			case NWAM_ADDRSRC_STATIC:
807 				static_addr = B_TRUE;
808 				break;
809 			default:
810 				break;
811 			}
812 		}
813 		nwam_value_free(ncu_prop);
814 	}
815 	if (nif->nwamd_if_dhcp_requested) {
816 		if ((*nifa = calloc(sizeof (**nifa), 1)) != NULL) {
817 			(*nifa)->address.sa_family = AF_INET;
818 			(*nifa)->dhcp_if = B_TRUE;
819 			nifa = &((*nifa)->next);
820 			*nifa = NULL;
821 		}
822 	}
823 
824 	/* ipv4-addr */
825 	if (static_addr) {
826 		if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue,
827 		    &numvalues, NWAM_NCU_PROP_IPV4_ADDR)) != NWAM_SUCCESS) {
828 			nlog(LOG_ERR, "populate_ip_ncu_properties: "
829 			    "could not get %s value; %s",
830 			    NWAM_NCU_PROP_IPV4_ADDR, nwam_strerror(err));
831 		} else {
832 			struct sockaddr_in *s;
833 
834 			for (i = 0; i < numvalues; i++) {
835 				if ((*nifa = calloc(sizeof (**nifa), 1))
836 				    == NULL) {
837 					nlog(LOG_ERR, "couldn't allocate nwamd"
838 					    "address");
839 					continue;
840 				}
841 				(*nifa)->address.sa_family = AF_INET;
842 				/*LINTED*/
843 				s = (struct sockaddr_in *)&(*nifa)->address;
844 				s->sin_family = AF_INET;
845 				s->sin_port = 0;
846 				prefix = strchr(addrvalue[i], '/');
847 				if (prefix != NULL) {
848 					*prefix++ = 0;
849 					(*nifa)->prefix = atoi(prefix);
850 				}
851 				(void) inet_pton(AF_INET, addrvalue[i],
852 				    &(s->sin_addr));
853 				nifa = &((*nifa)->next);
854 			}
855 			*nifa = NULL;
856 
857 			nwam_value_free(ncu_prop);
858 		}
859 	}
860 
861 	/* get default route, if any */
862 	if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue,
863 	    &numvalues, NWAM_NCU_PROP_IPV4_DEFAULT_ROUTE)) == NWAM_SUCCESS) {
864 		/* Only one default route is allowed. */
865 		nif->nwamd_if_ipv4_default_route.sin_family = AF_INET;
866 		(void) inet_pton(AF_INET, addrvalue[0],
867 		    &(nif->nwamd_if_ipv4_default_route.sin_addr));
868 		nif->nwamd_if_ipv4_default_route_set = B_TRUE;
869 		nwam_value_free(ncu_prop);
870 	}
871 
872 skip_ipv4:
873 
874 	if (!nif->nwamd_if_ipv6)
875 		goto skip_ipv6;
876 
877 	/* ipv6-addrsrc */
878 	static_addr = B_FALSE;
879 	if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &addrsrcvalue,
880 	    &numvalues, NWAM_NCU_PROP_IPV6_ADDRSRC)) != NWAM_SUCCESS) {
881 		nlog(nif->nwamd_if_ipv6 ? LOG_ERR : LOG_DEBUG,
882 		    "populate_ip_ncu_properties: could not get %s value: %s",
883 		    NWAM_NCU_PROP_IPV6_ADDRSRC, nwam_strerror(err));
884 	} else {
885 		for (i = 0; i < numvalues; i++) {
886 			switch (addrsrcvalue[i]) {
887 			case NWAM_ADDRSRC_DHCP:
888 				nif->nwamd_if_stateful_requested = B_TRUE;
889 				break;
890 			case NWAM_ADDRSRC_AUTOCONF:
891 				nif->nwamd_if_stateless_requested = B_TRUE;
892 				break;
893 			case NWAM_ADDRSRC_STATIC:
894 				static_addr = B_TRUE;
895 				break;
896 			default:
897 				break;
898 			}
899 		}
900 		nwam_value_free(ncu_prop);
901 	}
902 	if (nif->nwamd_if_stateful_requested) {
903 		if ((*nifa = calloc(sizeof (**nifa), 1)) != NULL) {
904 			(*nifa)->address.sa_family = AF_INET6;
905 			(*nifa)->dhcp_if = B_TRUE;
906 			nifa = &((*nifa)->next);
907 			*nifa = NULL;
908 		}
909 	}
910 	if (nif->nwamd_if_stateless_requested) {
911 		if ((*nifa = calloc(sizeof (**nifa), 1)) != NULL) {
912 			(*nifa)->address.sa_family = AF_INET6;
913 			(*nifa)->stateless_if = B_TRUE;
914 			nifa = &((*nifa)->next);
915 			*nifa = NULL;
916 		}
917 	}
918 
919 	/* ipv6-addr */
920 	if (static_addr) {
921 		if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue,
922 		    &numvalues, NWAM_NCU_PROP_IPV6_ADDR)) != NWAM_SUCCESS) {
923 			nlog(LOG_ERR, "populate_ip_ncu_properties: "
924 			    "could not get %s value; %s",
925 			    NWAM_NCU_PROP_IPV6_ADDR, nwam_strerror(err));
926 		} else {
927 			struct sockaddr_in6 *s;
928 
929 			for (i = 0; i < numvalues; i++) {
930 				if ((*nifa = calloc(sizeof (**nifa), 1))
931 				    == NULL) {
932 					nlog(LOG_ERR, "couldn't allocate nwamd"
933 					    "address");
934 					continue;
935 				}
936 				(*nifa)->address.sa_family = AF_INET6;
937 				/*LINTED*/
938 				s = (struct sockaddr_in6 *)&(*nifa)->address;
939 				s->sin6_family = AF_INET6;
940 				s->sin6_port = 0;
941 				prefix = strchr(addrvalue[i], '/');
942 				if (prefix != NULL) {
943 					*prefix++ = 0;
944 					(*nifa)->prefix = atoi(prefix);
945 				}
946 				(void) inet_pton(AF_INET6, addrvalue[i],
947 				    &(s->sin6_addr));
948 				nifa = &((*nifa)->next);
949 			}
950 			*nifa = NULL;
951 
952 			nwam_value_free(ncu_prop);
953 		}
954 	}
955 
956 	/* get default route, if any */
957 	if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue,
958 	    &numvalues, NWAM_NCU_PROP_IPV6_DEFAULT_ROUTE)) == NWAM_SUCCESS) {
959 		/* Only one default route is allowed. */
960 		nif->nwamd_if_ipv6_default_route.sin6_family = AF_INET6;
961 		(void) inet_pton(AF_INET6, addrvalue[0],
962 		    &(nif->nwamd_if_ipv6_default_route.sin6_addr));
963 		nif->nwamd_if_ipv6_default_route_set = B_TRUE;
964 		nwam_value_free(ncu_prop);
965 	}
966 
967 skip_ipv6:
968 	;
969 }
970 
971 static nwamd_ncu_t *
972 nwamd_ncu_init(nwam_ncu_type_t ncu_type, const char *name)
973 {
974 	nwamd_ncu_t *rv;
975 
976 	nlog(LOG_DEBUG, "nwamd_ncu_init(%d, %s)", ncu_type, name);
977 
978 	if ((rv = calloc(1, sizeof (*rv))) == NULL)
979 		return (NULL);
980 
981 	rv->ncu_type = ncu_type;
982 	rv->ncu_name = strdup(name);
983 	rv->ncu_enabled = B_FALSE;
984 
985 	/* Initialize link/interface-specific data */
986 	if (rv->ncu_type == NWAM_NCU_TYPE_LINK) {
987 		(void) bzero(&rv->ncu_node.u_link, sizeof (nwamd_link_t));
988 		(void) dladm_name2info(dld_handle, name,
989 		    &rv->ncu_node.u_link.nwamd_link_id, NULL, NULL,
990 		    &rv->ncu_node.u_link.nwamd_link_media);
991 		(void) pthread_mutex_init(
992 		    &rv->ncu_node.u_link.nwamd_link_wifi_mutex, NULL);
993 		rv->ncu_node.u_link.nwamd_link_wifi_priority = MAXINT;
994 	} else {
995 		(void) bzero(&rv->ncu_node.u_if, sizeof (nwamd_if_t));
996 	}
997 
998 	return (rv);
999 }
1000 
1001 void
1002 nwamd_ncu_free(nwamd_ncu_t *ncu)
1003 {
1004 	if (ncu != NULL) {
1005 		assert(ncu->ncu_type == NWAM_NCU_TYPE_LINK ||
1006 		    ncu->ncu_type == NWAM_NCU_TYPE_INTERFACE);
1007 		if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) {
1008 			struct nwamd_link *l = &ncu->ncu_node.u_link;
1009 			int i;
1010 
1011 			free(l->nwamd_link_wifi_key);
1012 			free(l->nwamd_link_mac_addr);
1013 			for (i = 0; i < l->nwamd_link_num_autopush; i++)
1014 				free(l->nwamd_link_autopush[i]);
1015 		} else if (ncu->ncu_type == NWAM_NCU_TYPE_INTERFACE) {
1016 			struct nwamd_if_address *nifa;
1017 
1018 			nifa = ncu->ncu_node.u_if.nwamd_if_list;
1019 			while (nifa != NULL) {
1020 				struct nwamd_if_address *n;
1021 
1022 				n = nifa;
1023 				nifa = nifa->next;
1024 				free(n);
1025 			}
1026 		}
1027 		free(ncu->ncu_name);
1028 		free(ncu);
1029 	}
1030 }
1031 
1032 static int
1033 nwamd_ncu_display(nwamd_object_t ncu_obj, void *data)
1034 {
1035 	nwamd_ncu_t *ncu = (nwamd_ncu_t *)ncu_obj->nwamd_object_data;
1036 	data = data;
1037 	nlog(LOG_DEBUG, "NCU (%p) %s state %s, %s",
1038 	    (void *)ncu, ncu_obj->nwamd_object_name,
1039 	    nwam_state_to_string(ncu_obj->nwamd_object_state),
1040 	    nwam_aux_state_to_string(ncu_obj->nwamd_object_aux_state));
1041 	return (0);
1042 }
1043 
1044 void
1045 nwamd_log_ncus(void)
1046 {
1047 	nlog(LOG_DEBUG, "NCP %s", active_ncp);
1048 	(void) nwamd_walk_objects(NWAM_OBJECT_TYPE_NCU, nwamd_ncu_display,
1049 	    NULL);
1050 }
1051 
1052 int
1053 nwamd_ncu_action(const char *ncu, const char *parent, nwam_action_t action)
1054 {
1055 	nwamd_event_t ncu_event = nwamd_event_init_object_action
1056 	    (NWAM_OBJECT_TYPE_NCU, ncu, parent, action);
1057 	if (ncu_event == NULL)
1058 		return (1);
1059 	nwamd_event_enqueue(ncu_event);
1060 	return (0);
1061 }
1062 
1063 static void
1064 add_phys_ncu_to_ncp(nwam_ncp_handle_t ncph, const char *name)
1065 {
1066 	dladm_status_t dlrtn;
1067 	uint32_t media;
1068 	boolean_t is_wireless;
1069 	nwam_error_t err;
1070 	nwam_ncu_handle_t ncuh;
1071 	uint64_t uintval;
1072 
1073 	if ((dlrtn = dladm_name2info(dld_handle, name, NULL, NULL, NULL,
1074 	    &media)) != DLADM_STATUS_OK) {
1075 		char errmsg[DLADM_STRSIZE];
1076 		nlog(LOG_ERR, "failed to get media type for %s: %s", name,
1077 		    dladm_status2str(dlrtn, errmsg));
1078 		return;
1079 	}
1080 	is_wireless = (media == DL_WIFI);
1081 
1082 	if ((err = nwam_ncu_create(ncph, name, NWAM_NCU_TYPE_LINK,
1083 	    NWAM_NCU_CLASS_PHYS, &ncuh)) != NWAM_SUCCESS) {
1084 		nlog(LOG_ERR, "failed to create link ncu for %s: %s", name,
1085 		    nwam_strerror(err));
1086 		if (err == NWAM_ENTITY_READ_ONLY) {
1087 			nwamd_event_t retry_event;
1088 
1089 			/*
1090 			 * Root filesystem may be read-only, retry in
1091 			 * a few seconds.
1092 			 */
1093 			nlog(LOG_DEBUG, "Retrying addition of phys ncu for %s",
1094 			    name);
1095 			retry_event = nwamd_event_init_link_action(name,
1096 			    NWAM_ACTION_ADD);
1097 			if (retry_event != NULL) {
1098 				nwamd_event_enqueue_timed(retry_event,
1099 				    NWAMD_READONLY_RETRY_INTERVAL);
1100 			}
1101 		}
1102 		return;
1103 	}
1104 
1105 	uintval = NWAM_ACTIVATION_MODE_PRIORITIZED;
1106 	if ((err = nwamd_set_ncu_uint(ncuh, &uintval, 1,
1107 	    NWAM_NCU_PROP_ACTIVATION_MODE)) != NWAM_SUCCESS) {
1108 		goto finish;
1109 	}
1110 
1111 	uintval = is_wireless ? 1 : 0;
1112 	if ((err = nwamd_set_ncu_uint(ncuh, &uintval, 1,
1113 	    NWAM_NCU_PROP_PRIORITY_GROUP)) != NWAM_SUCCESS) {
1114 		goto finish;
1115 	}
1116 
1117 	uintval = is_wireless ? NWAM_PRIORITY_MODE_EXCLUSIVE :
1118 	    NWAM_PRIORITY_MODE_SHARED;
1119 	if ((err = nwamd_set_ncu_uint(ncuh, &uintval, 1,
1120 	    NWAM_NCU_PROP_PRIORITY_MODE)) != NWAM_SUCCESS) {
1121 		goto finish;
1122 	}
1123 
1124 	err = nwam_ncu_commit(ncuh, 0);
1125 
1126 finish:
1127 	nwam_ncu_free(ncuh);
1128 	if (err != NWAM_SUCCESS) {
1129 		nlog(LOG_ERR,
1130 		    "failed to create automatic link ncu for %s: %s",
1131 		    name, nwam_strerror(err));
1132 	}
1133 }
1134 
1135 static void
1136 add_ip_ncu_to_ncp(nwam_ncp_handle_t ncph, const char *name)
1137 {
1138 	nwam_error_t err;
1139 	nwam_ncu_handle_t ncuh;
1140 
1141 	if ((err = nwam_ncu_create(ncph, name, NWAM_NCU_TYPE_INTERFACE,
1142 	    NWAM_NCU_CLASS_IP, &ncuh)) != NWAM_SUCCESS) {
1143 		nlog(LOG_ERR, "failed to create ip ncu for %s: %s", name,
1144 		    nwam_strerror(err));
1145 		/*
1146 		 * Root filesystem may be read-only, but no need to
1147 		 * retry here since add_phys_ncu_to_ncp() enqueues
1148 		 * a retry event which will lead to add_ip_ncu_to_ncp()
1149 		 * being called.
1150 		 */
1151 		return;
1152 	}
1153 
1154 	/* IP NCU has the default values, so nothing else to do */
1155 	err = nwam_ncu_commit(ncuh, 0);
1156 
1157 finish:
1158 	nwam_ncu_free(ncuh);
1159 	if (err != NWAM_SUCCESS) {
1160 		nlog(LOG_ERR,
1161 		    "failed to create ip ncu for %s: %s", name,
1162 		    nwam_strerror(err));
1163 	}
1164 }
1165 
1166 static void
1167 remove_ncu_from_ncp(nwam_ncp_handle_t ncph, const char *name,
1168     nwam_ncu_type_t type)
1169 {
1170 	nwam_error_t err;
1171 	nwam_ncu_handle_t ncuh;
1172 
1173 	if ((err = nwam_ncu_read(ncph, name, type, 0, &ncuh)) != NWAM_SUCCESS) {
1174 		nlog(LOG_ERR, "failed to read automatic ncu %s: %s", name,
1175 		    nwam_strerror(err));
1176 		return;
1177 	}
1178 
1179 	err = nwam_ncu_destroy(ncuh, 0);
1180 	if (err != NWAM_SUCCESS) {
1181 		nlog(LOG_ERR, "failed to delete automatic ncu %s: %s", name,
1182 		    nwam_strerror(err));
1183 	}
1184 }
1185 
1186 /*
1187  * Device represented by NCU has been added or removed for the active
1188  * User NCP.  If an associated NCU of the given type is found, transition it
1189  * to the appropriate state.
1190  */
1191 void
1192 ncu_action_change_state(nwam_action_t action, nwam_ncu_type_t type,
1193     const char *name)
1194 {
1195 	nwamd_object_t ncu_obj = NULL;
1196 	nwamd_ncu_t *ncu;
1197 
1198 	if ((ncu_obj = nwamd_ncu_object_find(type, name)) == NULL)
1199 		return;
1200 
1201 	ncu = ncu_obj->nwamd_object_data;
1202 
1203 	/*
1204 	 * If device has been added, transition from uninitialized to offline.
1205 	 * If device has been removed, transition to uninitialized (via online*
1206 	 * if the NCU is currently enabled in order to tear down config).
1207 	 */
1208 	if (action == NWAM_ACTION_ADD) {
1209 		nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1210 		    ncu_obj->nwamd_object_name,
1211 		    NWAM_STATE_OFFLINE, NWAM_AUX_STATE_CONDITIONS_NOT_MET);
1212 	} else {
1213 		if (ncu->ncu_enabled) {
1214 			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1215 			    ncu_obj->nwamd_object_name,
1216 			    NWAM_STATE_ONLINE_TO_OFFLINE,
1217 			    NWAM_AUX_STATE_NOT_FOUND);
1218 		} else {
1219 			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1220 			    ncu_obj->nwamd_object_name,
1221 			    NWAM_STATE_UNINITIALIZED,
1222 			    NWAM_AUX_STATE_NOT_FOUND);
1223 		}
1224 	}
1225 	nwamd_object_release(ncu_obj);
1226 }
1227 
1228 /*
1229  * Called with hotplug sysevent or when nwam is started and walking the
1230  * physical interfaces.  Add/remove both link and interface NCUs from the
1231  * Automatic NCP.  Assumes that both link and interface NCUs don't exist.
1232  */
1233 void
1234 nwamd_ncu_handle_link_action_event(nwamd_event_t event)
1235 {
1236 	nwam_ncp_handle_t ncph;
1237 	nwam_ncu_type_t type;
1238 	nwam_action_t action =
1239 	    event->event_msg->nwe_data.nwe_link_action.nwe_action;
1240 	nwam_error_t err;
1241 	char *name;
1242 	boolean_t automatic_ncp_active = B_FALSE;
1243 
1244 	if (action != NWAM_ACTION_ADD && action != NWAM_ACTION_REMOVE) {
1245 		nlog(LOG_ERR, "nwamd_ncu_handle_link_action_event: "
1246 		    "invalid link action %s", nwam_action_to_string(action));
1247 		nwamd_event_do_not_send(event);
1248 		return;
1249 	}
1250 
1251 	nlog(LOG_DEBUG, "nwamd_ncu_handle_link_action_event: "
1252 	    "link action '%s' event on %s", nwam_action_to_string(action),
1253 	    event->event_object[0] == 0 ? "n/a" : event->event_object);
1254 
1255 	if ((err = nwam_ncu_typed_name_to_name(event->event_object, &type,
1256 	    &name)) != NWAM_SUCCESS) {
1257 		nlog(LOG_ERR, "nwamd_ncu_handle_link_action_event: "
1258 		    "translation from typedname error: %s", nwam_strerror(err));
1259 		nwamd_event_do_not_send(event);
1260 		return;
1261 	}
1262 
1263 	(void) pthread_mutex_lock(&active_ncp_mutex);
1264 	if (strcmp(active_ncp, NWAM_NCP_NAME_AUTOMATIC) == 0 &&
1265 	    active_ncph != NULL) {
1266 		automatic_ncp_active = B_TRUE;
1267 	}
1268 	(void) pthread_mutex_unlock(&active_ncp_mutex);
1269 
1270 	/*
1271 	 * We could use active_ncph for cases where the Automatic NCP is active,
1272 	 * but that would involve holding the active_ncp_mutex for too long.
1273 	 */
1274 	if ((err = nwam_ncp_read(NWAM_NCP_NAME_AUTOMATIC, 0, &ncph))
1275 	    == NWAM_ENTITY_NOT_FOUND) {
1276 		/* Automatic NCP doesn't exist, create it */
1277 		err = nwam_ncp_create(NWAM_NCP_NAME_AUTOMATIC, 0, &ncph);
1278 	}
1279 	if (err != NWAM_SUCCESS)
1280 		goto fail;
1281 
1282 	/* add or remove NCUs from Automatic NCP */
1283 	if (action == NWAM_ACTION_ADD) {
1284 		add_phys_ncu_to_ncp(ncph, name);
1285 		add_ip_ncu_to_ncp(ncph, name);
1286 	} else {
1287 		/*
1288 		 * Order is important here, remove IP NCU first to prevent
1289 		 * propogation of down event from link to IP.  No need to
1290 		 * create REFRESH or DESTROY events.  They are generated by
1291 		 * nwam_ncu_commit() and nwam_ncu_destroy().
1292 		 */
1293 		remove_ncu_from_ncp(ncph, name, NWAM_NCU_TYPE_INTERFACE);
1294 		remove_ncu_from_ncp(ncph, name, NWAM_NCU_TYPE_LINK);
1295 	}
1296 	nwam_ncp_free(ncph);
1297 
1298 	/*
1299 	 * If the Automatic NCP is not active, and the associated NCUs
1300 	 * exist, they must be moved into the appropriate states given the
1301 	 * action that has occurred.
1302 	 */
1303 	if (!automatic_ncp_active) {
1304 		ncu_action_change_state(action, NWAM_NCU_TYPE_INTERFACE, name);
1305 		ncu_action_change_state(action, NWAM_NCU_TYPE_LINK, name);
1306 	}
1307 
1308 	/* Need NCU check to evaluate state in light of added/removed NCUs */
1309 	if (!nwamd_event_enqueued(NWAM_EVENT_TYPE_NCU_CHECK,
1310 	    NWAM_OBJECT_TYPE_NCP, NULL)) {
1311 		nwamd_create_ncu_check_event(NEXT_FEW_SECONDS);
1312 	}
1313 
1314 fail:
1315 	free(name);
1316 	if (err != NWAM_SUCCESS) {
1317 		nwamd_event_t retry_event = nwamd_event_init_link_action(name,
1318 		    action);
1319 		if (retry_event == NULL) {
1320 			nlog(LOG_ERR, "nwamd_ncu_handle_link_action_event: "
1321 			    "could not create retry event to read/create "
1322 			    "%s NCP", NWAM_NCP_NAME_AUTOMATIC);
1323 			return;
1324 		}
1325 
1326 		nlog(LOG_ERR, "nwamd_ncu_handle_link_action_event: "
1327 		    "could not read/create %s NCP, retrying in %d seconds",
1328 		    NWAM_NCP_NAME_AUTOMATIC, NWAMD_READONLY_RETRY_INTERVAL);
1329 		nwamd_event_enqueue_timed(retry_event,
1330 		    NWAMD_READONLY_RETRY_INTERVAL);
1331 	}
1332 }
1333 
1334 /*
1335  * Figure out if this link is part of an aggregation.  This is fairly
1336  * inefficient since we generate this list for every query and search
1337  * linearly.  A better way would be to generate the list of links in an
1338  * aggregation once and then check each link against it.
1339  */
1340 struct link_aggr_search_data {
1341 	datalink_id_t linkid;
1342 	boolean_t under;
1343 };
1344 
1345 static int
1346 ncu_aggr_search(const char *name, void *data)
1347 {
1348 	struct link_aggr_search_data *lasd = data;
1349 	dladm_aggr_grp_attr_t ginfo;
1350 	datalink_id_t linkid;
1351 	int i;
1352 
1353 	if (dladm_name2info(dld_handle, name, &linkid, NULL, NULL, NULL) !=
1354 	    DLADM_STATUS_OK)
1355 		return (DLADM_WALK_CONTINUE);
1356 	if (dladm_aggr_info(dld_handle, linkid, &ginfo, DLADM_OPT_ACTIVE)
1357 	    != DLADM_STATUS_OK || ginfo.lg_nports == 0)
1358 		return (DLADM_WALK_CONTINUE);
1359 
1360 	for (i = 0; i < ginfo.lg_nports; i++) {
1361 		if (lasd->linkid == ginfo.lg_ports[i].lp_linkid) {
1362 			lasd->under = B_TRUE;
1363 			return (DLADM_WALK_TERMINATE);
1364 		}
1365 	}
1366 	free(ginfo.lg_ports);
1367 	return (DLADM_WALK_CONTINUE);
1368 }
1369 
1370 static boolean_t
1371 nwamd_link_belongs_to_an_aggr(const char *name)
1372 {
1373 	struct link_aggr_search_data lasd;
1374 
1375 	if (dladm_name2info(dld_handle, name, &lasd.linkid, NULL, NULL, NULL)
1376 	    != DLADM_STATUS_OK)
1377 		return (B_FALSE);
1378 	lasd.under = B_FALSE;
1379 	(void) dladm_walk(ncu_aggr_search, dld_handle, &lasd,
1380 	    DATALINK_CLASS_AGGR, DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);
1381 	return (lasd.under);
1382 }
1383 
1384 /*
1385  * If NCU doesn't exist for interface with given name, enqueue a ADD
1386  * LINK_ACTION event.
1387  */
1388 static int
1389 ncu_create_link_action_event(const char *name, void *data)
1390 {
1391 	nwam_ncp_handle_t ncph = data;
1392 	nwam_ncu_handle_t ncuh;
1393 	nwamd_event_t link_event;
1394 
1395 	/* Do not generate an event if this is a VirtualBox interface. */
1396 	if (strncmp(name, VBOX_IFACE_PREFIX, strlen(VBOX_IFACE_PREFIX)) == 0)
1397 		return (DLADM_WALK_CONTINUE);
1398 
1399 	/* Do not generate an event if this link belongs to another zone. */
1400 	if (!nwamd_link_belongs_to_this_zone(name))
1401 		return (DLADM_WALK_CONTINUE);
1402 
1403 	/* Do not generate an event if this link belongs to an aggregation. */
1404 	if (nwamd_link_belongs_to_an_aggr(name)) {
1405 		return (DLADM_WALK_CONTINUE);
1406 	}
1407 
1408 	/* Don't create an event if the NCU already exists. */
1409 	if (ncph != NULL && nwam_ncu_read(ncph, name, NWAM_NCU_TYPE_LINK, 0,
1410 	    &ncuh) == NWAM_SUCCESS) {
1411 		nwam_ncu_free(ncuh);
1412 		return (DLADM_WALK_CONTINUE);
1413 	}
1414 
1415 	nlog(LOG_DEBUG, "ncu_create_link_action_event: adding ncus for %s",
1416 	    name);
1417 
1418 	link_event = nwamd_event_init_link_action(name, NWAM_ACTION_ADD);
1419 	if (link_event != NULL)
1420 		nwamd_event_enqueue(link_event);
1421 
1422 	return (DLADM_WALK_CONTINUE);
1423 }
1424 
1425 /*
1426  * Check if interface exists for this NCU. If not, enqueue a REMOVE
1427  * LINK_ACTION event.
1428  */
1429 /* ARGSUSED */
1430 static int
1431 nwamd_destroy_ncu(nwam_ncu_handle_t ncuh, void *data)
1432 {
1433 	char *name;
1434 	uint32_t flags;
1435 	nwamd_event_t link_event;
1436 
1437 	if (nwam_ncu_get_name(ncuh, &name) != NWAM_SUCCESS) {
1438 		nlog(LOG_ERR, "nwamd_destroy_ncu: could not get NCU name");
1439 		return (0);
1440 	}
1441 
1442 	/* Interfaces that exist return DLADM_OPT_ACTIVE flag */
1443 	if ((dladm_name2info(dld_handle, name, NULL, &flags, NULL, NULL)
1444 	    == DLADM_STATUS_OK && (flags & DLADM_OPT_ACTIVE)) &&
1445 	    !nwamd_link_belongs_to_an_aggr(name)) {
1446 		free(name);
1447 		return (0);
1448 	}
1449 
1450 	nlog(LOG_DEBUG, "nwamd_destroy_ncu: destroying ncus for %s", name);
1451 
1452 	link_event = nwamd_event_init_link_action(name, NWAM_ACTION_REMOVE);
1453 	if (link_event != NULL)
1454 		nwamd_event_enqueue(link_event);
1455 	free(name);
1456 	return (0);
1457 }
1458 
1459 /*
1460  * Called when nwamd is starting up.
1461  *
1462  * Walk all NCUs and destroy any NCU from the Automatic NCP without an
1463  * underlying interface (assumption here is that the interface was removed
1464  * when nwam was disabled).
1465  *
1466  * Walk the physical interfaces and create ADD LINK_ACTION event, which
1467  * will create appropriate interface and link NCUs in the Automatic NCP.
1468  */
1469 void
1470 nwamd_walk_physical_configuration(void)
1471 {
1472 	nwam_ncp_handle_t ncph;
1473 
1474 	(void) pthread_mutex_lock(&active_ncp_mutex);
1475 	if (strcmp(active_ncp, NWAM_NCP_NAME_AUTOMATIC) == 0 &&
1476 	    active_ncph != NULL) {
1477 		ncph = active_ncph;
1478 	} else {
1479 		if (nwam_ncp_read(NWAM_NCP_NAME_AUTOMATIC, 0, &ncph)
1480 		    != NWAM_SUCCESS) {
1481 			ncph = NULL;
1482 		}
1483 	}
1484 
1485 	/* destroy NCUs for interfaces that don't exist */
1486 	if (ncph != NULL) {
1487 		(void) nwam_ncp_walk_ncus(ncph, nwamd_destroy_ncu, NULL,
1488 		    NWAM_FLAG_NCU_TYPE_LINK, NULL);
1489 	}
1490 
1491 	/* create NCUs for interfaces without NCUs */
1492 	(void) dladm_walk(ncu_create_link_action_event, dld_handle, ncph,
1493 	    DATALINK_CLASS_PHYS, DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);
1494 
1495 	if (strcmp(active_ncp, NWAM_NCP_NAME_AUTOMATIC) != 0 ||
1496 	    active_ncph == NULL) {
1497 		nwam_ncp_free(ncph);
1498 	}
1499 	(void) pthread_mutex_unlock(&active_ncp_mutex);
1500 }
1501 
1502 /*
1503  * Handle NCU initialization/refresh event.
1504  */
1505 void
1506 nwamd_ncu_handle_init_event(nwamd_event_t event)
1507 {
1508 	nwamd_object_t object = NULL;
1509 	nwam_ncu_handle_t ncuh;
1510 	nwamd_ncu_t *ncu = NULL;
1511 	nwam_error_t err;
1512 	nwam_ncu_type_t type;
1513 	char *name;
1514 	uint32_t flags;
1515 	boolean_t new = B_TRUE;
1516 
1517 	nlog(LOG_DEBUG, "nwamd_ncu_handle_init_event(%s)",
1518 	    event->event_object);
1519 
1520 	/* Get base linkname rather than interface:linkname or link:linkname */
1521 	err = nwam_ncu_typed_name_to_name(event->event_object,
1522 	    &type, &name);
1523 	if (err != NWAM_SUCCESS) {
1524 		nlog(LOG_ERR, "nwamd_ncu_handle_init_event: "
1525 		    "nwam_ncu_typed_name_to_name returned %s",
1526 		    nwam_strerror(err));
1527 		nwamd_event_do_not_send(event);
1528 		return;
1529 	}
1530 
1531 	(void) pthread_mutex_lock(&active_ncp_mutex);
1532 	if (active_ncph == NULL) {
1533 		nlog(LOG_DEBUG,
1534 		    "nwamd_ncu_handle_init_event: active NCP handle NULL");
1535 		nwamd_event_do_not_send(event);
1536 		free(name);
1537 		(void) pthread_mutex_unlock(&active_ncp_mutex);
1538 		return;
1539 	}
1540 	err = nwam_ncu_read(active_ncph, event->event_object,
1541 	    type, 0, &ncuh);
1542 	(void) pthread_mutex_unlock(&active_ncp_mutex);
1543 	if (err != NWAM_SUCCESS) {
1544 		nlog(LOG_ERR, "nwamd_ncu_handle_init_event: "
1545 		    "could not read object '%s': %s",
1546 		    event->event_object, nwam_strerror(err));
1547 		free(name);
1548 		nwamd_event_do_not_send(event);
1549 		return;
1550 	}
1551 
1552 	if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU,
1553 	    event->event_object)) != NULL)
1554 		new = B_FALSE;
1555 
1556 	/*
1557 	 * For new NCUs, or interface NCUs, we (re)initialize data from scratch.
1558 	 * For link NCUs, we want to retain object data.
1559 	 */
1560 	switch (type) {
1561 	case NWAM_NCU_TYPE_LINK:
1562 		if (new) {
1563 			ncu = nwamd_ncu_init(type, name);
1564 		} else {
1565 			ncu = object->nwamd_object_data;
1566 			nwam_ncu_free(object->nwamd_object_handle);
1567 		}
1568 		populate_common_ncu_properties(ncuh, ncu);
1569 		populate_link_ncu_properties(ncuh, ncu);
1570 		break;
1571 	case NWAM_NCU_TYPE_INTERFACE:
1572 		if (!new) {
1573 			nwam_ncu_free(object->nwamd_object_handle);
1574 			nwamd_ncu_free(object->nwamd_object_data);
1575 		}
1576 		ncu = nwamd_ncu_init(type, name);
1577 		populate_common_ncu_properties(ncuh, ncu);
1578 		populate_ip_ncu_properties(ncuh, ncu);
1579 		break;
1580 	default:
1581 		nlog(LOG_ERR, "unknown ncu type %d", type);
1582 		free(name);
1583 		nwam_ncu_free(ncuh);
1584 		nwamd_event_do_not_send(event);
1585 		nwamd_object_release(object);
1586 		return;
1587 	}
1588 
1589 	if (new) {
1590 		nlog(LOG_DEBUG, "nwamd_ncu_handle_init_event: didn't find "
1591 		    "ncu so create it %s", name);
1592 		object = nwamd_object_init(NWAM_OBJECT_TYPE_NCU,
1593 		    event->event_object, ncuh, ncu);
1594 	} else {
1595 		nlog(LOG_DEBUG, "nwamd_ncu_handle_init_event: refreshing "
1596 		    "ncu %s", name);
1597 		object->nwamd_object_data = ncu;
1598 		object->nwamd_object_handle = ncuh;
1599 	}
1600 
1601 	/*
1602 	 * If the physical link for this NCU doesn't exist in the system,
1603 	 * the state should be UNINITIALIZED/NOT_FOUND.  Interfaces that
1604 	 * exist return DLADM_OPT_ACTIVE flag.
1605 	 */
1606 	if (dladm_name2info(dld_handle, name, NULL, &flags, NULL, NULL)
1607 	    != DLADM_STATUS_OK || !(flags & DLADM_OPT_ACTIVE)) {
1608 		nlog(LOG_DEBUG, "nwam_ncu_handle_init_event: "
1609 		    "interface for NCU %s doesn't exist",
1610 		    event->event_object);
1611 		nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1612 		    object->nwamd_object_name, NWAM_STATE_UNINITIALIZED,
1613 		    NWAM_AUX_STATE_NOT_FOUND);
1614 		free(name);
1615 		nwamd_object_release(object);
1616 		return;
1617 	}
1618 
1619 	/*
1620 	 * If NCU is being initialized (rather than refreshed), the
1621 	 * object_state is INITIALIZED (from nwamd_object_init()).
1622 	 */
1623 	if (object->nwamd_object_state == NWAM_STATE_INITIALIZED) {
1624 		/*
1625 		 * If the NCU is disabled, initial state should be DISABLED.
1626 		 *
1627 		 * Otherwise, the initial state will be
1628 		 * OFFLINE/CONDITIONS_NOT_MET, and the link selection
1629 		 * algorithm will do the rest.
1630 		 */
1631 		if (!ncu->ncu_enabled) {
1632 			object->nwamd_object_state = NWAM_STATE_DISABLED;
1633 			object->nwamd_object_aux_state =
1634 			    NWAM_AUX_STATE_MANUAL_DISABLE;
1635 		} else {
1636 			object->nwamd_object_state = NWAM_STATE_OFFLINE;
1637 			object->nwamd_object_aux_state =
1638 			    NWAM_AUX_STATE_CONDITIONS_NOT_MET;
1639 		}
1640 	} else {
1641 		nwamd_link_t *link = &ncu->ncu_node.u_link;
1642 
1643 		/*
1644 		 * Refresh NCU.  Deal with disabled cases first, moving NCUs
1645 		 * that are not disabled - but have the enabled value set - to
1646 		 * the disabled state.  Then handle cases where the NCU was
1647 		 * disabled but is no longer.  Finally,  deal with refresh of
1648 		 * link and interface NCUs, as these are handled differently.
1649 		 */
1650 		if (!ncu->ncu_enabled) {
1651 			if (object->nwamd_object_state != NWAM_STATE_DISABLED) {
1652 				nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1653 				    object->nwamd_object_name,
1654 				    NWAM_STATE_ONLINE_TO_OFFLINE,
1655 				    NWAM_AUX_STATE_MANUAL_DISABLE);
1656 			}
1657 			goto done;
1658 		} else {
1659 			if (object->nwamd_object_state == NWAM_STATE_DISABLED) {
1660 				int64_t c;
1661 
1662 				/*
1663 				 * Try to activate the NCU if manual or
1664 				 * prioritized (when priority <= current).
1665 				 */
1666 				(void) pthread_mutex_lock(&active_ncp_mutex);
1667 				c = current_ncu_priority_group;
1668 				(void) pthread_mutex_unlock(&active_ncp_mutex);
1669 				if (link->nwamd_link_activation_mode ==
1670 				    NWAM_ACTIVATION_MODE_MANUAL ||
1671 				    (link->nwamd_link_activation_mode ==
1672 				    NWAM_ACTIVATION_MODE_PRIORITIZED &&
1673 				    link->nwamd_link_priority_mode <= c)) {
1674 					nwamd_object_set_state
1675 					    (NWAM_OBJECT_TYPE_NCU,
1676 					    object->nwamd_object_name,
1677 					    NWAM_STATE_OFFLINE_TO_ONLINE,
1678 					    NWAM_AUX_STATE_INITIALIZED);
1679 				} else {
1680 					nwamd_object_set_state
1681 					    (NWAM_OBJECT_TYPE_NCU,
1682 					    object->nwamd_object_name,
1683 					    NWAM_STATE_OFFLINE_TO_ONLINE,
1684 					    NWAM_AUX_STATE_INITIALIZED);
1685 				}
1686 				goto done;
1687 			}
1688 		}
1689 
1690 		switch (type) {
1691 		case NWAM_NCU_TYPE_LINK:
1692 			if (ncu->ncu_node.u_link.nwamd_link_media == DL_WIFI) {
1693 				/*
1694 				 * Do rescan.  If the current state and the
1695 				 * active priority-group do not allow wireless
1696 				 * network selection, then it won't happen.
1697 				 */
1698 				(void) nwamd_wlan_scan(ncu->ncu_name);
1699 			}
1700 			break;
1701 		case NWAM_NCU_TYPE_INTERFACE:
1702 			/*
1703 			 * If interface NCU is offline*, online or in
1704 			 * maintenance, mark it down (from there, it will be
1705 			 * reinitialized to reapply addresses).
1706 			 */
1707 			if (object->nwamd_object_state != NWAM_STATE_OFFLINE) {
1708 				nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1709 				    object->nwamd_object_name,
1710 				    NWAM_STATE_ONLINE_TO_OFFLINE,
1711 				    NWAM_AUX_STATE_DOWN);
1712 			} else {
1713 				object->nwamd_object_state = NWAM_STATE_OFFLINE;
1714 				object->nwamd_object_aux_state =
1715 				    NWAM_AUX_STATE_CONDITIONS_NOT_MET;
1716 			}
1717 			break;
1718 		}
1719 	}
1720 
1721 done:
1722 	if (type == NWAM_NCU_TYPE_LINK &&
1723 	    !nwamd_event_enqueued(NWAM_EVENT_TYPE_NCU_CHECK,
1724 	    NWAM_OBJECT_TYPE_NCP, NULL)) {
1725 		nwamd_create_ncu_check_event(NEXT_FEW_SECONDS);
1726 	}
1727 	free(name);
1728 	nwamd_object_release(object);
1729 }
1730 
1731 void
1732 nwamd_ncu_handle_fini_event(nwamd_event_t event)
1733 {
1734 	nwamd_object_t object;
1735 	nwamd_event_t state_event;
1736 
1737 	nlog(LOG_DEBUG, "nwamd_ncu_handle_fini_event(%s)",
1738 	    event->event_object);
1739 
1740 	/*
1741 	 * Simulate a state event so that the state machine can correctly
1742 	 * disable the NCU.  Then free up allocated objects.
1743 	 */
1744 	state_event = nwamd_event_init_object_state(NWAM_OBJECT_TYPE_NCU,
1745 	    event->event_object, NWAM_STATE_ONLINE_TO_OFFLINE,
1746 	    NWAM_AUX_STATE_UNINITIALIZED);
1747 	if (state_event == NULL) {
1748 		nwamd_event_do_not_send(event);
1749 		return;
1750 	}
1751 	nwamd_ncu_handle_state_event(state_event);
1752 	nwamd_event_fini(state_event);
1753 
1754 	if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU,
1755 	    event->event_object)) == NULL) {
1756 		nlog(LOG_ERR, "nwamd_ncu_handle_fini_event: "
1757 		    "ncu %s not found", event->event_object);
1758 		nwamd_event_do_not_send(event);
1759 		return;
1760 	}
1761 	nwamd_object_release_and_destroy(object);
1762 }
1763 
1764 void
1765 nwamd_ncu_handle_action_event(nwamd_event_t event)
1766 {
1767 	nwamd_object_t object;
1768 
1769 	(void) pthread_mutex_lock(&active_ncp_mutex);
1770 	if (strcmp(event->event_msg->nwe_data.nwe_object_action.nwe_parent,
1771 	    active_ncp) != 0) {
1772 		nlog(LOG_DEBUG, "nwamd_ncu_handle_action_event: action for "
1773 		    "inactive NCP %s, nothing to do",
1774 		    event->event_msg->nwe_data.nwe_object_action.nwe_parent);
1775 		(void) pthread_mutex_unlock(&active_ncp_mutex);
1776 		return;
1777 	}
1778 	(void) pthread_mutex_unlock(&active_ncp_mutex);
1779 
1780 	switch (event->event_msg->nwe_data.nwe_object_action.nwe_action) {
1781 	case NWAM_ACTION_ENABLE:
1782 		object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU,
1783 		    event->event_object);
1784 		if (object == NULL) {
1785 			nlog(LOG_ERR, "nwamd_ncu_handle_action_event: "
1786 			    "could not find ncu %s", event->event_object);
1787 			nwamd_event_do_not_send(event);
1788 			return;
1789 		}
1790 		if (object->nwamd_object_state == NWAM_STATE_ONLINE) {
1791 			nlog(LOG_DEBUG, "nwamd_ncu_handle_action_event: "
1792 			    "ncu %s already online, nothing to do",
1793 			    event->event_object);
1794 			nwamd_object_release(object);
1795 			return;
1796 		}
1797 		nwamd_object_release(object);
1798 
1799 		nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1800 		    event->event_object, NWAM_STATE_OFFLINE_TO_ONLINE,
1801 		    NWAM_AUX_STATE_INITIALIZED);
1802 		break;
1803 	case NWAM_ACTION_DISABLE:
1804 		object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU,
1805 		    event->event_object);
1806 		if (object == NULL) {
1807 			nlog(LOG_ERR, "nwamd_ncu_handle_action_event: "
1808 			    "could not find ncu %s", event->event_object);
1809 			nwamd_event_do_not_send(event);
1810 			return;
1811 		}
1812 		if (object->nwamd_object_state == NWAM_STATE_DISABLED) {
1813 			nlog(LOG_DEBUG, "nwamd_ncu_handle_action_event: "
1814 			    "ncu %s already disabled, nothing to do",
1815 			    event->event_object);
1816 			nwamd_object_release(object);
1817 			return;
1818 		}
1819 		nwamd_object_release(object);
1820 
1821 		nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1822 		    event->event_object, NWAM_STATE_ONLINE_TO_OFFLINE,
1823 		    NWAM_AUX_STATE_MANUAL_DISABLE);
1824 		break;
1825 	case NWAM_ACTION_ADD:
1826 	case NWAM_ACTION_REFRESH:
1827 		nwamd_ncu_handle_init_event(event);
1828 		break;
1829 	case NWAM_ACTION_DESTROY:
1830 		nwamd_ncu_handle_fini_event(event);
1831 		break;
1832 	default:
1833 		nlog(LOG_INFO, "nwam_ncu_handle_action_event: "
1834 		    "unexpected action");
1835 		nwamd_event_do_not_send(event);
1836 		break;
1837 	}
1838 }
1839 
1840 void
1841 nwamd_ncu_handle_state_event(nwamd_event_t event)
1842 {
1843 	nwamd_object_t object;
1844 	nwam_state_t old_state, new_state;
1845 	nwam_aux_state_t new_aux_state;
1846 	nwamd_ncu_t *ncu;
1847 	boolean_t is_link, enabled, prioritized = B_FALSE;
1848 	char linkname[NWAM_MAX_NAME_LEN];
1849 	nwam_event_t m = event->event_msg;
1850 
1851 	if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU,
1852 	    event->event_object)) == NULL) {
1853 		nlog(LOG_ERR, "nwamd_ncu_handle_state_event %lld: "
1854 		    "state event for nonexistent NCU %s", event->event_id,
1855 		    event->event_object);
1856 		nwamd_event_do_not_send(event);
1857 		return;
1858 	}
1859 	ncu = object->nwamd_object_data;
1860 	old_state = object->nwamd_object_state;
1861 	new_state = event->event_msg->nwe_data.nwe_object_state.nwe_state;
1862 	new_aux_state =
1863 	    event->event_msg->nwe_data.nwe_object_state.nwe_aux_state;
1864 
1865 	/*
1866 	 * For NCU state changes, we need to supply the parent NCP name also,
1867 	 * regardless of whether the event is handled or not.  It is best to
1868 	 * fill this in here as we have the object lock - when we create
1869 	 * object state events we sometimes do not have the object lock, but
1870 	 * at this point in consuming the events (and prior to the associated
1871 	 * event message being sent out) we do.
1872 	 */
1873 	(void) strlcpy(m->nwe_data.nwe_object_state.nwe_parent, ncu->ncu_parent,
1874 	    sizeof (m->nwe_data.nwe_object_state.nwe_parent));
1875 
1876 	/*
1877 	 * If we receive a state change event moving this NCU to
1878 	 * DHCP_TIMED_OUT or UP state but this NCU is already ONLINE, then
1879 	 * ignore this state change event.
1880 	 */
1881 	if ((new_aux_state == NWAM_AUX_STATE_IF_DHCP_TIMED_OUT ||
1882 	    new_aux_state == NWAM_AUX_STATE_UP) &&
1883 	    object->nwamd_object_state == NWAM_STATE_ONLINE) {
1884 		nlog(LOG_INFO, "nwamd_ncu_handle_state_event: "
1885 		    "NCU %s already online, not going to '%s' state",
1886 		    object->nwamd_object_name,
1887 		    nwam_aux_state_to_string(new_aux_state));
1888 		nwamd_event_do_not_send(event);
1889 		nwamd_object_release(object);
1890 		return;
1891 	}
1892 
1893 	if (new_state == object->nwamd_object_state &&
1894 	    new_aux_state == object->nwamd_object_aux_state) {
1895 		nlog(LOG_DEBUG, "nwamd_ncu_handle_state_event: "
1896 		    "NCU %s already in state (%s, %s)",
1897 		    object->nwamd_object_name, nwam_state_to_string(new_state),
1898 		    nwam_aux_state_to_string(new_aux_state));
1899 		nwamd_object_release(object);
1900 		return;
1901 	}
1902 
1903 	if (old_state == NWAM_STATE_MAINTENANCE &&
1904 	    (new_state == NWAM_STATE_ONLINE ||
1905 	    (new_state == NWAM_STATE_OFFLINE_TO_ONLINE &&
1906 	    new_aux_state != NWAM_AUX_STATE_INITIALIZED))) {
1907 		nlog(LOG_DEBUG, "nwamd_ncu_handle_state_event: "
1908 		    "NCU %s cannot transition from state %s to state (%s, %s)",
1909 		    object->nwamd_object_name, nwam_state_to_string(old_state),
1910 		    nwam_state_to_string(new_state),
1911 		    nwam_aux_state_to_string(new_aux_state));
1912 		nwamd_event_do_not_send(event);
1913 		nwamd_object_release(object);
1914 		return;
1915 	}
1916 
1917 	object->nwamd_object_state = new_state;
1918 	object->nwamd_object_aux_state = new_aux_state;
1919 
1920 	nlog(LOG_DEBUG, "nwamd_ncu_handle_state_event: changing state for NCU "
1921 	    "%s to (%s, %s)", object->nwamd_object_name,
1922 	    nwam_state_to_string(object->nwamd_object_state),
1923 	    nwam_aux_state_to_string(object->nwamd_object_aux_state));
1924 
1925 	is_link = (ncu->ncu_type == NWAM_NCU_TYPE_LINK);
1926 	if (is_link)
1927 		(void) strlcpy(linkname, ncu->ncu_name, sizeof (linkname));
1928 	prioritized = (ncu->ncu_type == NWAM_NCU_TYPE_LINK &&
1929 	    ncu->ncu_node.u_link.nwamd_link_activation_mode ==
1930 	    NWAM_ACTIVATION_MODE_PRIORITIZED);
1931 	enabled = ncu->ncu_enabled;
1932 
1933 	nwamd_object_release(object);
1934 
1935 	/*
1936 	 * State machine for NCUs
1937 	 */
1938 	switch (new_state) {
1939 	case NWAM_STATE_OFFLINE_TO_ONLINE:
1940 		if (enabled) {
1941 			nwamd_ncu_state_machine(event->event_object);
1942 		} else {
1943 			nlog(LOG_DEBUG, "nwamd_ncu_handle_state_event: "
1944 			    "cannot move disabled NCU %s online",
1945 			    event->event_object);
1946 			nwamd_event_do_not_send(event);
1947 		}
1948 		break;
1949 
1950 	case NWAM_STATE_ONLINE_TO_OFFLINE:
1951 		nwamd_ncu_state_machine(event->event_object);
1952 		break;
1953 
1954 	case NWAM_STATE_ONLINE:
1955 		/*
1956 		 * We usually don't need to do anything when we're in the
1957 		 * ONLINE state.  However, for  WiFi we can be in INIT or
1958 		 * SCAN aux states while being ONLINE.
1959 		 */
1960 		nwamd_ncu_state_machine(event->event_object);
1961 		break;
1962 
1963 	case NWAM_STATE_OFFLINE:
1964 		/* Reassess priority group now member is offline */
1965 		if (prioritized) {
1966 			nwamd_create_ncu_check_event(0);
1967 		}
1968 		break;
1969 
1970 	case NWAM_STATE_DISABLED:
1971 	case NWAM_STATE_UNINITIALIZED:
1972 	case NWAM_STATE_MAINTENANCE:
1973 	case NWAM_STATE_DEGRADED:
1974 	default:
1975 		/* do nothing */
1976 		break;
1977 	}
1978 
1979 	if (is_link) {
1980 		if ((new_state == NWAM_STATE_ONLINE_TO_OFFLINE &&
1981 		    new_aux_state != NWAM_AUX_STATE_UNINITIALIZED &&
1982 		    new_aux_state != NWAM_AUX_STATE_NOT_FOUND) ||
1983 		    new_state == NWAM_STATE_DISABLED) {
1984 			/*
1985 			 * Going offline, propogate down event to IP NCU.  Do
1986 			 * not propogate event if new aux state is uninitialized
1987 			 * or not found as these auxiliary states signify
1988 			 * that an NCP switch/device removal is in progress.
1989 			 */
1990 			nwamd_propogate_link_up_down_to_ip(linkname, B_FALSE);
1991 		}
1992 		if (new_state == NWAM_STATE_ONLINE) {
1993 			/* gone online, propogate up event to IP NCU */
1994 			nwamd_propogate_link_up_down_to_ip(linkname, B_TRUE);
1995 		}
1996 	} else {
1997 		/* If IP NCU is online, reasses priority group */
1998 		if (new_state == NWAM_STATE_ONLINE)
1999 			nwamd_create_ncu_check_event(0);
2000 	}
2001 }
2002