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