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