xref: /illumos-gate/usr/src/cmd/cmd-inet/lib/nwamd/ncu_phys.c (revision e0731422366620894c16c1ee6515551c5f00733d)
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 <assert.h>
27 #include <ctype.h>
28 #include <err.h>
29 #include <errno.h>
30 #include <execinfo.h>
31 #include <kstat.h>
32 #include <libdladm.h>
33 #include <libdllink.h>
34 #include <libdlstat.h>
35 #include <libdlwlan.h>
36 #include <libnwam.h>
37 #include <limits.h>
38 #include <pthread.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <strings.h>
43 #include <sys/stat.h>
44 #include <sys/time.h>
45 #include <sys/types.h>
46 #include <unistd.h>
47 #include <libdlpi.h>
48 #include <ucontext.h>
49 
50 #include "events.h"
51 #include "llp.h"
52 #include "objects.h"
53 #include "ncp.h"
54 #include "ncu.h"
55 #include "known_wlans.h"
56 #include "util.h"
57 
58 /*
59  * ncu_phys.c - contains routines that are physical-link specific.
60  * Mostly WiFi code.
61  */
62 
63 /*
64  * Get link state from kstats. Used to determine initial link state for
65  * cases where drivers do not support DL_NOTE_LINK_UP/DOWN.  If link
66  * state is LINK_STATE_UNKNOWN, we assume the link is up and the IP NCU
67  * timeout will cause us to move on to other links.
68  */
69 link_state_t
70 nwamd_get_link_state(const char *name)
71 {
72 	kstat_ctl_t *kcp;
73 	kstat_t *ksp;
74 	char module[DLPI_LINKNAME_MAX];
75 	uint_t instance;
76 	link_state_t link_state = LINK_STATE_UNKNOWN;
77 
78 	if ((kcp = kstat_open()) == NULL)
79 		return (link_state);
80 
81 	if (dlpi_parselink(name, module, &instance) != DLPI_SUCCESS)
82 		goto out;
83 
84 	if ((ksp = kstat_lookup(kcp, module, instance, "mac")) == NULL) {
85 		/*
86 		 * The kstat query could fail if the underlying MAC
87 		 * driver was already detached.
88 		 */
89 		goto out;
90 	}
91 
92 	if (kstat_read(kcp, ksp, NULL) == -1)
93 		goto out;
94 
95 	(void) dladm_kstat_value(ksp, "link_state", KSTAT_DATA_UINT32,
96 	    &link_state);
97 
98 out:
99 	(void) kstat_close(kcp);
100 
101 	return (link_state);
102 }
103 
104 /*
105  * Set/unset link propeties.  At present, these are MAC address, link MTU and
106  * autopush modules.  We set MAC address last as setting it may cause a chip
107  * reset which can prevent other device property setting succeeding.
108  */
109 void
110 nwamd_set_unset_link_properties(nwamd_ncu_t *ncu, boolean_t set)
111 {
112 	dlpi_handle_t dh = ncu->ncu_link.nwamd_link_dhp;
113 	char *addr = set ? ncu->ncu_link.nwamd_link_mac_addr : NULL;
114 	uint64_t mtu = set ? ncu->ncu_link.nwamd_link_mtu : 0;
115 	char **autopush = set ? ncu->ncu_link.nwamd_link_autopush : NULL;
116 	uint_t num_autopush = set ? ncu->ncu_link.nwamd_link_num_autopush : 0;
117 	uchar_t *hwaddr = NULL, curraddr[DLPI_PHYSADDR_MAX];
118 	size_t hwaddrlen = DLPI_PHYSADDR_MAX;
119 	int retval;
120 	dladm_status_t status;
121 	char mtustr[DLADM_PROP_VAL_MAX];
122 	char *cp;
123 	char errmsg[DLADM_STRSIZE];
124 	uint_t cnt = 1;
125 
126 	/*
127 	 * Set MTU here - either default value (if mtu == 0 indicating it has
128 	 * not been set) or specified value.
129 	 */
130 	if (mtu == 0) {
131 		cp = mtustr;
132 		status = dladm_get_linkprop(dld_handle,
133 		    ncu->ncu_link.nwamd_link_id, DLADM_PROP_VAL_DEFAULT, "mtu",
134 		    &cp, &cnt);
135 		if (status != DLADM_STATUS_OK) {
136 			nlog(LOG_ERR, "nwamd_set_unset_link_properties: "
137 			    "dladm_get_linkprop failed: %s",
138 			    dladm_status2str(status, errmsg));
139 			return;
140 		}
141 	} else {
142 		(void) snprintf(mtustr, DLADM_PROP_VAL_MAX, "%lld", mtu);
143 	}
144 
145 	cp = mtustr;
146 
147 	nlog(LOG_DEBUG, "nwamd_set_unset_link_properties: setting MTU of %s "
148 	    "for link %s", mtustr, ncu->ncu_name);
149 	status = dladm_set_linkprop(dld_handle, ncu->ncu_link.nwamd_link_id,
150 	    "mtu", &cp, 1, DLADM_OPT_ACTIVE);
151 	if (status != DLADM_STATUS_OK) {
152 		nlog(LOG_ERR, "nwamd_set_unset_link_properties: "
153 		    "dladm_set_linkprop failed: %s",
154 		    dladm_status2str(status, errmsg));
155 	}
156 
157 	nlog(LOG_DEBUG, "nwamd_set_unset_link_properties: setting %d "
158 	    "autopush module for link %s", num_autopush, ncu->ncu_name);
159 	status = dladm_set_linkprop(dld_handle, ncu->ncu_link.nwamd_link_id,
160 	    "autopush", autopush, num_autopush, DLADM_OPT_ACTIVE);
161 	if (status != DLADM_STATUS_OK) {
162 		nlog(LOG_ERR, "nwamd_set_unset_link_properties: "
163 		    "dladm_set_linkprop failed for autopush property: %s",
164 		    dladm_status2str(status, errmsg));
165 	}
166 
167 	/*
168 	 * Set physical address - either factory (if link_mac_addr is NULL
169 	 * or we are unsetting properties) or specified MAC address string.
170 	 */
171 	if (addr == NULL) {
172 		if ((hwaddr = calloc(1, DLPI_PHYSADDR_MAX)) == NULL) {
173 			nlog(LOG_ERR,
174 			    "nwamd_set_unset_link_properties: malloc() failed");
175 			return;
176 		}
177 		if ((retval = dlpi_get_physaddr(dh, DL_FACT_PHYS_ADDR,
178 		    hwaddr, &hwaddrlen)) != DLPI_SUCCESS) {
179 			nlog(LOG_ERR, "nwamd_set_unset_link_properties: "
180 			    "could not get physical address for %s: %s",
181 			    ncu->ncu_name, dlpi_strerror(retval));
182 			free(hwaddr);
183 			return;
184 		}
185 	} else {
186 		int addrlen = hwaddrlen;
187 		if ((hwaddr = _link_aton(addr, &addrlen)) == NULL) {
188 			if (addrlen == -1) {
189 				nlog(LOG_ERR,
190 				    "nwamd_set_unset_link_properties: "
191 				    "%s: bad address for %s",
192 				    addr, ncu->ncu_name);
193 				return;
194 			} else {
195 				nlog(LOG_ERR, "nwamd_set_unset_link_properties:"
196 				    " malloc() failed");
197 				return;
198 			}
199 		}
200 		hwaddrlen = addrlen;
201 	}
202 
203 	/*
204 	 * Only set physical address if desired address differs from current -
205 	 * this avoids unnecessary chip resets for some drivers.
206 	 */
207 	retval = dlpi_get_physaddr(dh, DL_CURR_PHYS_ADDR, curraddr,
208 	    &hwaddrlen);
209 	if (retval != DLPI_SUCCESS || bcmp(curraddr, hwaddr, hwaddrlen) != 0) {
210 		retval = dlpi_set_physaddr(dh, DL_CURR_PHYS_ADDR, hwaddr,
211 		    hwaddrlen);
212 		if (retval != DLPI_SUCCESS) {
213 			nlog(LOG_ERR, "nwamd_set_unset_link_properties:"
214 			    "failed setting mac address on %s: %s",
215 			    ncu->ncu_name, dlpi_strerror(retval));
216 		}
217 	}
218 	free(hwaddr);
219 }
220 
221 #define	WLAN_ENC(sec)						\
222 	((sec == DLADM_WLAN_SECMODE_WPA ? "WPA" : 		\
223 	(sec == DLADM_WLAN_SECMODE_WEP ? "WEP" : "none")))
224 
225 #define	NEED_ENC(sec)						\
226 	(sec == DLADM_WLAN_SECMODE_WPA || sec == DLADM_WLAN_SECMODE_WEP)
227 
228 #define	WIRELESS_LAN_INIT_COUNT	8
229 
230 /*
231  * The variable wireless_scan_level specifies the signal level
232  * that we will initiate connections to previously-visited APs
233  * at when we are in the connected state.
234  */
235 dladm_wlan_strength_t wireless_scan_level = DLADM_WLAN_STRENGTH_WEAK;
236 
237 /*
238  * The variable wireless_scan_interval specifies how often the periodic
239  * scan occurs.
240  */
241 uint64_t wireless_scan_interval = WIRELESS_SCAN_INTERVAL_DEFAULT;
242 
243 /*
244  * The variable wireless_autoconf specifies if we use dladm_wlan_autoconf()
245  * to connect.
246  */
247 boolean_t wireless_autoconf = B_FALSE;
248 
249 /*
250  * The variable wireless_strict_bssid specifies if we only connect
251  * to WLANs with BSSIDs that we previously connected to.
252  */
253 boolean_t wireless_strict_bssid = B_FALSE;
254 
255 /*
256  * We need to ensure scan or connect threads do not run concurrently
257  * on any links - otherwise we get radio interference.  Acquire this
258  * lock on entering scan/connect threads to prevent this.
259  */
260 pthread_mutex_t wireless_mutex = PTHREAD_MUTEX_INITIALIZER;
261 
262 static void
263 scanconnect_entry(void)
264 {
265 	(void) pthread_mutex_lock(&wireless_mutex);
266 }
267 
268 static void
269 scanconnect_exit(void)
270 {
271 	(void) pthread_mutex_unlock(&wireless_mutex);
272 }
273 
274 /*
275  * Below are functions used to handle storage/retrieval of keys
276  * for a given WLAN. The keys are stored/retrieved using dladm_set_secobj()
277  * and dladm_get_secobj().
278  */
279 
280 /*
281  * Convert key hexascii string to raw secobj value. This
282  * code is very similar to convert_secobj() in dladm.c, it would
283  * be good to have a libdladm function to convert values.
284  */
285 static int
286 key_string_to_secobj_value(char *buf, uint8_t *obj_val, uint_t *obj_lenp,
287     dladm_secobj_class_t class)
288 {
289 	size_t buf_len = strlen(buf);
290 
291 	nlog(LOG_DEBUG, "before: key_string_to_secobj_value: buf_len = %d",
292 	    buf_len);
293 	if (buf_len == 0) {
294 		/* length zero means "delete" */
295 		return (0);
296 	}
297 
298 	if (buf[buf_len - 1] == '\n')
299 		buf[--buf_len] = '\0';
300 
301 	nlog(LOG_DEBUG, "after: key_string_to_secobj_value: buf_len = %d",
302 	    buf_len);
303 
304 	if (class == DLADM_SECOBJ_CLASS_WPA) {
305 		/*
306 		 * Per IEEE802.11i spec, the Pre-shared key (PSK) length should
307 		 * be between 8 and 63.
308 		 */
309 		if (buf_len < 8 || buf_len > 63) {
310 			nlog(LOG_ERR,
311 			    "key_string_to_secobj_value:"
312 			    " invalid WPA key length: buf_len = %d", buf_len);
313 			return (-1);
314 		}
315 		(void) memcpy(obj_val, buf, (uint_t)buf_len);
316 		*obj_lenp = buf_len;
317 		return (0);
318 	}
319 
320 	switch (buf_len) {
321 	case 5:		/* ASCII key sizes */
322 	case 13:
323 		(void) memcpy(obj_val, buf, (uint_t)buf_len);
324 		*obj_lenp = (uint_t)buf_len;
325 		break;
326 	case 10:
327 	case 26:	/* Hex key sizes, not preceded by 0x */
328 		if (hexascii_to_octet(buf, (uint_t)buf_len, obj_val, obj_lenp)
329 		    != 0) {
330 			nlog(LOG_ERR,
331 			    "key_string_to_secobj_value: invalid WEP key");
332 			return (-1);
333 		}
334 		break;
335 	case 12:
336 	case 28:	/* Hex key sizes, preceded by 0x */
337 		if (strncmp(buf, "0x", 2) != 0 ||
338 		    hexascii_to_octet(buf + 2, (uint_t)buf_len - 2, obj_val,
339 		    obj_lenp) != 0) {
340 			nlog(LOG_ERR,
341 			    "key_string_to_secobj_value: invalid WEP key");
342 			return (-1);
343 		}
344 		break;
345 	default:
346 		syslog(LOG_ERR,
347 		    "key_string_to_secobj_value: invalid WEP key length");
348 		return (-1);
349 	}
350 	return (0);
351 }
352 
353 /*
354  * Print the key name format into the appropriate field, then convert any ":"
355  * characters to ".", as ":[1-4]" is the slot indicator, which otherwise
356  * would trip us up.  Invalid characters for secobj names are ignored.
357  * The fourth parameter is expected to be of size DLADM_SECOBJ_NAME_MAX.
358  *
359  * (Note that much of the system uses DLADM_WLAN_MAX_KEYNAME_LEN, which is 64
360  * rather than 32, but that dladm_get_secobj will fail if a length greater than
361  * DLD_SECOBJ_NAME_MAX is seen, and that's 32.  This is all horribly broken.)
362  */
363 void
364 nwamd_set_key_name(const char *essid, const char *bssid, char *name, size_t nsz)
365 {
366 	int i, j;
367 	char secobj_name[DLADM_WLAN_MAX_KEYNAME_LEN];
368 
369 	/* create a concatenated string with essid and bssid */
370 	if (bssid == NULL || bssid[0] == '\0') {
371 		(void) snprintf(secobj_name, sizeof (secobj_name), "nwam-%s",
372 		    essid);
373 	} else {
374 		(void) snprintf(secobj_name, sizeof (secobj_name), "nwam-%s-%s",
375 		    essid, bssid);
376 	}
377 
378 	/* copy only valid chars to the return string, terminating with \0 */
379 	i = 0; /* index into secobj_name */
380 	j = 0; /* index into name */
381 	while (secobj_name[i] != '\0') {
382 		if (j == nsz - 1)
383 			break;
384 
385 		if (secobj_name[i] == ':') {
386 			name[j] = '.';
387 			j++;
388 		} else if (isalnum(secobj_name[i]) ||
389 		    secobj_name[i] == '.' || secobj_name[i] == '-' ||
390 		    secobj_name[i] == '_') {
391 			name[j] = secobj_name[i];
392 			j++;
393 		}
394 		i++;
395 	}
396 	name[j] = '\0';
397 }
398 
399 nwam_error_t
400 nwamd_wlan_set_key(const char *linkname, const char *essid, const char *bssid,
401     uint32_t security_mode, uint_t keyslot, char *raw_key)
402 {
403 	nwamd_object_t ncu_obj;
404 	nwamd_ncu_t *ncu;
405 	nwamd_link_t *link;
406 	uint8_t obj_val[DLADM_SECOBJ_VAL_MAX];
407 	uint_t obj_len = sizeof (obj_val);
408 	char obj_name[DLADM_SECOBJ_NAME_MAX];
409 	dladm_status_t status;
410 	char errmsg[DLADM_STRSIZE];
411 	dladm_secobj_class_t class;
412 
413 	if ((ncu_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_LINK, linkname))
414 	    == NULL) {
415 		nlog(LOG_ERR, "nwamd_wlan_set_key: could not find object  "
416 		    "for link %s", linkname);
417 		return (NWAM_ENTITY_NOT_FOUND);
418 	}
419 	ncu = ncu_obj->nwamd_object_data;
420 	link = &ncu->ncu_link;
421 
422 	nlog(LOG_DEBUG, "nwamd_wlan_set_key: running for link %s", linkname);
423 	/*
424 	 * Name key object for this WLAN so it can be later retrieved
425 	 * (name is unique for each ESSID/BSSID combination).
426 	 */
427 	nwamd_set_key_name(essid, bssid, obj_name, sizeof (obj_name));
428 	nlog(LOG_DEBUG, "store_key: obj_name is %s", obj_name);
429 
430 	class = (security_mode == DLADM_WLAN_SECMODE_WEP ?
431 	    DLADM_SECOBJ_CLASS_WEP : DLADM_SECOBJ_CLASS_WPA);
432 	if (key_string_to_secobj_value(raw_key, obj_val, &obj_len,
433 	    class) != 0) {
434 		/* above function logs internally on failure */
435 		nwamd_object_release(ncu_obj);
436 		return (NWAM_ERROR_INTERNAL);
437 	}
438 
439 	/* we've validated the new key, so remove the old one */
440 	status = dladm_unset_secobj(dld_handle, obj_name,
441 	    DLADM_OPT_ACTIVE | DLADM_OPT_PERSIST);
442 	if (status != DLADM_STATUS_OK && status != DLADM_STATUS_NOTFOUND) {
443 		nlog(LOG_ERR, "store_key: could not remove old secure object "
444 		    "'%s' for key: %s", obj_name,
445 		    dladm_status2str(status, errmsg));
446 		nwamd_object_release(ncu_obj);
447 		return (NWAM_ERROR_INTERNAL);
448 	}
449 
450 	/* if we're just deleting the key, then we're done */
451 	if (raw_key[0] == '\0') {
452 		nwamd_object_release(ncu_obj);
453 		return (NWAM_SUCCESS);
454 	}
455 
456 	status = dladm_set_secobj(dld_handle, obj_name, class,
457 	    obj_val, obj_len,
458 	    DLADM_OPT_CREATE | DLADM_OPT_PERSIST | DLADM_OPT_ACTIVE);
459 	if (status != DLADM_STATUS_OK) {
460 		nlog(LOG_ERR, "store_key: could not create secure object "
461 		    "'%s' for key: %s", obj_name,
462 		    dladm_status2str(status, errmsg));
463 		nwamd_object_release(ncu_obj);
464 		return (NWAM_ERROR_INTERNAL);
465 	}
466 	link->nwamd_link_wifi_key = nwamd_wlan_get_key_named(obj_name,
467 	    security_mode);
468 	(void) strlcpy(link->nwamd_link_wifi_keyname, obj_name,
469 	    sizeof (link->nwamd_link_wifi_keyname));
470 	link->nwamd_link_wifi_security_mode = security_mode;
471 	if (security_mode == DLADM_WLAN_SECMODE_WEP) {
472 		link->nwamd_link_wifi_key->wk_idx =
473 		    (keyslot >= 1 && keyslot <= 4) ? keyslot : 1;
474 	}
475 
476 	/* If link NCU is offline* or online, (re)connect. */
477 	switch (ncu_obj->nwamd_object_state) {
478 	case NWAM_STATE_ONLINE:
479 		/* if changing the key of the connected WLAN, reconnect */
480 		if (strcmp(essid, link->nwamd_link_wifi_essid) == 0)
481 			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
482 			    ncu_obj->nwamd_object_name, NWAM_STATE_ONLINE,
483 			    NWAM_AUX_STATE_LINK_WIFI_CONNECTING);
484 		break;
485 	case NWAM_STATE_OFFLINE_TO_ONLINE:
486 		/* if we are waiting for the key, connect */
487 		if (ncu_obj->nwamd_object_aux_state ==
488 		    NWAM_AUX_STATE_LINK_WIFI_NEED_KEY)
489 			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
490 			    ncu_obj->nwamd_object_name,
491 			    NWAM_STATE_OFFLINE_TO_ONLINE,
492 			    NWAM_AUX_STATE_LINK_WIFI_CONNECTING);
493 		break;
494 	default:
495 		break;
496 	}
497 	nwamd_object_release(ncu_obj);
498 
499 	return (NWAM_SUCCESS);
500 }
501 
502 /*
503  * returns NULL if no key was recovered from libdladm.  Passing in
504  * security mode of 0 means we don't care what key type it is.
505  */
506 dladm_wlan_key_t *
507 nwamd_wlan_get_key_named(const char *name, uint32_t security_mode)
508 {
509 	dladm_status_t status;
510 	char errmsg[DLADM_STRSIZE];
511 	dladm_wlan_key_t *cooked_key;
512 	dladm_secobj_class_t class;
513 
514 	if (security_mode == DLADM_WLAN_SECMODE_NONE)
515 		return (NULL);
516 
517 	/*
518 	 * Newly-allocated key must be freed by caller, or by
519 	 * subsequent call to nwamd_wlan_get_key_named().
520 	 */
521 	if ((cooked_key = malloc(sizeof (dladm_wlan_key_t))) == NULL) {
522 		nlog(LOG_ERR, "nwamd_wlan_get_key_named: malloc failed");
523 		return (NULL);
524 	}
525 
526 	/*
527 	 * Set name appropriately to retrieve key for this WLAN.  Note that we
528 	 * cannot use the actual wk_name buffer size, as it's two times too
529 	 * large for dladm_get_secobj.
530 	 */
531 	(void) strlcpy(cooked_key->wk_name, name, DLADM_SECOBJ_NAME_MAX);
532 	nlog(LOG_DEBUG, "nwamd_wlan_get_key_named: len = %d, object = %s\n",
533 	    strlen(cooked_key->wk_name), cooked_key->wk_name);
534 	cooked_key->wk_len = sizeof (cooked_key->wk_val);
535 	cooked_key->wk_idx = 1;
536 
537 	/* Try the kernel first, then fall back to persistent storage. */
538 	status = dladm_get_secobj(dld_handle, cooked_key->wk_name, &class,
539 	    cooked_key->wk_val, &cooked_key->wk_len,
540 	    DLADM_OPT_ACTIVE);
541 	if (status != DLADM_STATUS_OK) {
542 		nlog(LOG_DEBUG, "nwamd_wlan_get_key_named: "
543 		    "dladm_get_secobj(TEMP) failed: %s",
544 		    dladm_status2str(status, errmsg));
545 		status = dladm_get_secobj(dld_handle, cooked_key->wk_name,
546 		    &class, cooked_key->wk_val, &cooked_key->wk_len,
547 		    DLADM_OPT_PERSIST);
548 	}
549 
550 	switch (status) {
551 	case DLADM_STATUS_OK:
552 		nlog(LOG_DEBUG, "nwamd_wlan_get_key_named: "
553 		    "dladm_get_secobj succeeded: len %d", cooked_key->wk_len);
554 		break;
555 	case DLADM_STATUS_NOTFOUND:
556 		/*
557 		 * We do not want an error in the case that the secobj
558 		 * is not found, since we then prompt for it.
559 		 */
560 		free(cooked_key);
561 		return (NULL);
562 	default:
563 		nlog(LOG_ERR, "nwamd_wlan_get_key_named: could not get key "
564 		    "from secure object '%s': %s", cooked_key->wk_name,
565 		    dladm_status2str(status, errmsg));
566 		free(cooked_key);
567 		return (NULL);
568 	}
569 
570 	if (security_mode != 0) {
571 		switch (class) {
572 		case DLADM_SECOBJ_CLASS_WEP:
573 			if (security_mode == DLADM_WLAN_SECMODE_WEP)
574 				return (cooked_key);
575 			break;
576 		case DLADM_SECOBJ_CLASS_WPA:
577 			if (security_mode == DLADM_WLAN_SECMODE_WPA)
578 				return (cooked_key);
579 			break;
580 		default:
581 			/* shouldn't happen */
582 			nlog(LOG_ERR, "nwamd_wlan_get_key: invalid class %d",
583 			    class);
584 			break;
585 		}
586 		/* key type mismatch */
587 		nlog(LOG_ERR, "nwamd_wlan_get_key: key type mismatch"
588 		    " from secure object '%s'", cooked_key->wk_name);
589 		free(cooked_key);
590 		return (NULL);
591 	}
592 
593 	return (cooked_key);
594 }
595 
596 static dladm_wlan_key_t *
597 nwamd_wlan_get_key(const char *essid, const char *bssid, uint32_t security_mode)
598 {
599 	char keyname[DLADM_SECOBJ_NAME_MAX];
600 
601 	nwamd_set_key_name(essid, bssid, keyname, DLADM_SECOBJ_NAME_MAX);
602 
603 	return (nwamd_wlan_get_key_named(keyname, security_mode));
604 }
605 
606 /*
607  * Checks if a wireless network can be selected or not.  A wireless network
608  * CANNOT be selected if the NCU is DISABLED, or the NCU is OFFLINE or
609  * ONLINE* and has lower priority than the currently active priority-group.
610  * Called with object lock held.
611  */
612 static boolean_t
613 wireless_selection_possible(nwamd_object_t object)
614 {
615 	nwamd_ncu_t *ncu = object->nwamd_object_data;
616 
617 	if (ncu->ncu_link.nwamd_link_media != DL_WIFI)
618 		return (B_FALSE);
619 
620 	(void) pthread_mutex_lock(&active_ncp_mutex);
621 	if (object->nwamd_object_state == NWAM_STATE_DISABLED ||
622 	    ((object->nwamd_object_state == NWAM_STATE_OFFLINE ||
623 	    object->nwamd_object_state == NWAM_STATE_ONLINE_TO_OFFLINE) &&
624 	    ncu->ncu_link.nwamd_link_activation_mode ==
625 	    NWAM_ACTIVATION_MODE_PRIORITIZED &&
626 	    (current_ncu_priority_group == INVALID_PRIORITY_GROUP ||
627 	    ncu->ncu_link.nwamd_link_priority_group >
628 	    current_ncu_priority_group))) {
629 		(void) pthread_mutex_unlock(&active_ncp_mutex);
630 		return (B_FALSE);
631 	}
632 	(void) pthread_mutex_unlock(&active_ncp_mutex);
633 
634 	return (B_TRUE);
635 }
636 
637 /*
638  * Update the selected and/or connected values for the
639  * scan data.  If these change, we need to trigger a scan
640  * event since the updated values need to be communicated
641  * to the GUI.
642  */
643 void
644 nwamd_set_selected_connected(nwamd_ncu_t *ncu, boolean_t selected,
645     boolean_t connected)
646 {
647 	nwamd_link_t *link = &ncu->ncu_link;
648 	nwamd_wifi_scan_t *s = &link->nwamd_link_wifi_scan;
649 	int i;
650 	boolean_t trigger_scan_event = B_FALSE;
651 
652 	for (i = 0; i < s->nwamd_wifi_scan_curr_num; i++) {
653 		if (strcmp(s->nwamd_wifi_scan_curr[i].nww_essid,
654 		    link->nwamd_link_wifi_essid) != 0 ||
655 		    (link->nwamd_link_wifi_bssid[0] != '\0' &&
656 		    strcmp(s->nwamd_wifi_scan_curr[i].nww_bssid,
657 		    link->nwamd_link_wifi_bssid) != 0))
658 			continue;
659 		if (selected) {
660 			if (!s->nwamd_wifi_scan_curr[i].nww_selected)
661 				trigger_scan_event = B_TRUE;
662 			s->nwamd_wifi_scan_curr[i].nww_selected = B_TRUE;
663 		} else {
664 			if (s->nwamd_wifi_scan_curr[i].nww_selected)
665 				trigger_scan_event = B_TRUE;
666 			s->nwamd_wifi_scan_curr[i].nww_selected = B_FALSE;
667 		}
668 		if (connected) {
669 			if (!s->nwamd_wifi_scan_curr[i].nww_connected)
670 				trigger_scan_event = B_TRUE;
671 			s->nwamd_wifi_scan_curr[i].nww_connected = B_TRUE;
672 		} else {
673 			if (s->nwamd_wifi_scan_curr[i].nww_connected)
674 				trigger_scan_event = B_TRUE;
675 			s->nwamd_wifi_scan_curr[i].nww_connected = B_FALSE;
676 		}
677 	}
678 
679 	if (trigger_scan_event || s->nwamd_wifi_scan_changed) {
680 		nwamd_event_t scan_event = nwamd_event_init_wlan
681 		    (ncu->ncu_name, NWAM_EVENT_TYPE_WLAN_SCAN_REPORT, connected,
682 		    s->nwamd_wifi_scan_curr, s->nwamd_wifi_scan_curr_num);
683 		if (scan_event != NULL) {
684 			/* Avoid sending same scan data multiple times */
685 			s->nwamd_wifi_scan_changed = B_FALSE;
686 			nwamd_event_enqueue(scan_event);
687 		}
688 	}
689 }
690 
691 /*
692  * Callback used on each known WLAN - if the BSSID is matched, set
693  * the ESSID of the hidden WLAN to the known WLAN name.
694  */
695 static int
696 find_bssid_cb(nwam_known_wlan_handle_t kwh, void *data)
697 {
698 	nwamd_link_t *link = data;
699 	nwam_error_t err;
700 	nwam_value_t bssidval;
701 	char **bssids, *name;
702 	uint_t num_bssids, i;
703 
704 	if ((err = nwam_known_wlan_get_prop_value(kwh,
705 	    NWAM_KNOWN_WLAN_PROP_BSSIDS, &bssidval)) != NWAM_SUCCESS) {
706 		nlog(LOG_ERR, "find_bssid_cb: nwam_known_wlan_get_prop: %s",
707 		    nwam_strerror(err));
708 		return (0);
709 	}
710 	if ((err = nwam_value_get_string_array(bssidval, &bssids, &num_bssids))
711 	    != NWAM_SUCCESS) {
712 		nlog(LOG_ERR, "find_bssid_cb: nwam_value_get_string_array: %s",
713 		    nwam_strerror(err));
714 		nwam_value_free(bssidval);
715 		return (0);
716 	}
717 	for (i = 0; i < num_bssids; i++) {
718 		if (strcmp(bssids[i], link->nwamd_link_wifi_bssid) == 0) {
719 			if ((err = nwam_known_wlan_get_name(kwh, &name))
720 			    != NWAM_SUCCESS) {
721 				nlog(LOG_ERR, "find_bssid_cb: "
722 				    "nwam_known_wlan_get_name: %s",
723 				    nwam_strerror(err));
724 				continue;
725 			}
726 			(void) strlcpy(link->nwamd_link_wifi_essid, name,
727 			    sizeof (link->nwamd_link_wifi_essid));
728 			free(name);
729 			nwam_value_free(bssidval);
730 			/* Found ESSID for BSSID so terminate walk */
731 			return (1);
732 		}
733 	}
734 	nwam_value_free(bssidval);
735 
736 	return (0);
737 }
738 
739 /*
740  * We may have encountered a BSSID for a hidden WLAN before and as a result
741  * may have a known WLAN entry with this BSSID.  Walk known WLANs, searching
742  * for a BSSID match.  Called with object lock held.
743  */
744 static void
745 check_if_hidden_wlan_was_visited(nwamd_link_t *link)
746 {
747 	(void) nwam_walk_known_wlans(find_bssid_cb, link,
748 	    NWAM_FLAG_KNOWN_WLAN_WALK_PRIORITY_ORDER, NULL);
749 }
750 
751 nwam_error_t
752 nwamd_wlan_select(const char *linkname, const char *essid, const char *bssid,
753     uint32_t security_mode, boolean_t add_to_known_wlans)
754 {
755 	nwamd_object_t ncu_obj;
756 	nwamd_ncu_t *ncu;
757 	nwamd_link_t *link;
758 	char key[DLADM_STRSIZE];
759 	boolean_t found_old_key = B_FALSE, found_key = B_FALSE;
760 
761 	if ((ncu_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_LINK, linkname))
762 	    == NULL) {
763 		nlog(LOG_ERR, "nwamd_wlan_select: could not find object  "
764 		    "for link %s", linkname);
765 		return (NWAM_ENTITY_NOT_FOUND);
766 	}
767 	ncu = ncu_obj->nwamd_object_data;
768 	link = &ncu->ncu_link;
769 
770 	/*
771 	 * If wireless selection is not possible because of the current
772 	 * state or priority-group, then stop.
773 	 */
774 	if (!wireless_selection_possible(ncu_obj)) {
775 		nwamd_object_release(ncu_obj);
776 		return (NWAM_ENTITY_INVALID_STATE);
777 	}
778 
779 	/* unset selected, connected flag for previously connected wlan */
780 	nwamd_set_selected_connected(ncu, B_FALSE, B_FALSE);
781 
782 	/* Disconnect to allow new selection to go ahead */
783 	(void) dladm_wlan_disconnect(dld_handle, link->nwamd_link_id);
784 
785 	(void) strlcpy(link->nwamd_link_wifi_essid, essid,
786 	    sizeof (link->nwamd_link_wifi_essid));
787 	(void) strlcpy(link->nwamd_link_wifi_bssid, bssid,
788 	    sizeof (link->nwamd_link_wifi_bssid));
789 	link->nwamd_link_wifi_security_mode = security_mode;
790 	link->nwamd_link_wifi_add_to_known_wlans = add_to_known_wlans;
791 
792 	/* If this is a hidden wlan, then essid is empty */
793 	if (link->nwamd_link_wifi_essid[0] == '\0')
794 		check_if_hidden_wlan_was_visited(link);
795 
796 	/* set selected flag for newly-selected WLAN */
797 	nwamd_set_selected_connected(ncu, B_TRUE, B_FALSE);
798 
799 	/* does this WLAN require a key? If so go to NEED_KEY */
800 	if (NEED_ENC(link->nwamd_link_wifi_security_mode)) {
801 		/*
802 		 * First, if a key name may have been specified for a
803 		 * known WLAN.  If so, use it.  Otherwise, try both the
804 		 * new nwamd key name format (ESSID) and old (ESSID/BSSID).
805 		 * The user may have set the key without adding a known WLAN,
806 		 * so we need to try all these options to save going to
807 		 * NEED_KEY state.
808 		 */
809 		if (known_wlan_get_keyname(link->nwamd_link_wifi_essid,
810 		    link->nwamd_link_wifi_keyname) == NWAM_SUCCESS &&
811 		    (link->nwamd_link_wifi_key = nwamd_wlan_get_key_named
812 		    (link->nwamd_link_wifi_keyname,
813 		    link->nwamd_link_wifi_security_mode)) != NULL) {
814 			(void) known_wlan_get_keyslot
815 			    (link->nwamd_link_wifi_essid,
816 			    &link->nwamd_link_wifi_key->wk_idx);
817 			nlog(LOG_DEBUG, "nwamd_wlan_select: got known WLAN "
818 			    "key %s, slot %d", link->nwamd_link_wifi_keyname,
819 			    link->nwamd_link_wifi_key->wk_idx);
820 			found_key = B_TRUE;
821 		} else if ((link->nwamd_link_wifi_key = nwamd_wlan_get_key
822 		    (link->nwamd_link_wifi_essid, NULL,
823 		    link->nwamd_link_wifi_security_mode)) != NULL) {
824 			nwamd_set_key_name(link->nwamd_link_wifi_essid, NULL,
825 			    link->nwamd_link_wifi_keyname,
826 			    DLADM_SECOBJ_NAME_MAX);
827 			nlog(LOG_DEBUG, "nwamd_wlan_select: got WLAN key %s",
828 			    link->nwamd_link_wifi_keyname);
829 			found_key = B_TRUE;
830 		} else if ((link->nwamd_link_wifi_key = nwamd_wlan_get_key
831 		    (link->nwamd_link_wifi_essid, link->nwamd_link_wifi_bssid,
832 		    link->nwamd_link_wifi_security_mode)) != NULL) {
833 			/*
834 			 * Found old key format - prepare to save
835 			 * it as new ESSID-only key, but don't
836 			 * do it until we're released the object
837 			 * lock (since nwamd_wlan_set_key()
838 			 * takes the object lock).
839 			 */
840 			(void) strlcpy(key,
841 			    (char *)link->nwamd_link_wifi_key->wk_val,
842 			    link->nwamd_link_wifi_key->wk_len + 1);
843 			found_old_key = B_TRUE;
844 			found_key = B_TRUE;
845 			nwamd_set_key_name(link->nwamd_link_wifi_essid, NULL,
846 			    link->nwamd_link_wifi_keyname,
847 			    DLADM_SECOBJ_NAME_MAX);
848 			nlog(LOG_DEBUG, "nwamd_wlan_select: got old format "
849 			    "WLAN key, converting to %s",
850 			    link->nwamd_link_wifi_keyname);
851 		} else {
852 			nlog(LOG_ERR, "nwamd_wlan_select: could not "
853 			    "find key for WLAN '%s'",
854 			    link->nwamd_link_wifi_essid);
855 		}
856 	} else {
857 		free(link->nwamd_link_wifi_key);
858 		link->nwamd_link_wifi_key = NULL;
859 		link->nwamd_link_wifi_keyname[0] = '\0';
860 	}
861 
862 	if (NEED_ENC(link->nwamd_link_wifi_security_mode) && !found_key) {
863 		nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
864 		    ncu_obj->nwamd_object_name,
865 		    NWAM_STATE_OFFLINE_TO_ONLINE,
866 		    NWAM_AUX_STATE_LINK_WIFI_NEED_KEY);
867 	} else {
868 		nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
869 		    ncu_obj->nwamd_object_name, NWAM_STATE_OFFLINE_TO_ONLINE,
870 		    NWAM_AUX_STATE_LINK_WIFI_CONNECTING);
871 	}
872 	nwamd_object_release(ncu_obj);
873 
874 	if (found_old_key) {
875 		(void) nwamd_wlan_set_key(linkname, essid, NULL, security_mode,
876 		    1, key);
877 	}
878 	return (NWAM_SUCCESS);
879 }
880 
881 /*
882  * See if BSSID is in visited list of BSSIDs for known WLAN. Used for
883  * strict BSSID matching (depends on wireless_strict_bssid property value).
884  */
885 static boolean_t
886 bssid_match(nwam_known_wlan_handle_t kwh, const char *bssid)
887 {
888 	nwam_value_t bssidsval;
889 	nwam_error_t err;
890 	char **bssids;
891 	uint_t nelem, i;
892 	boolean_t found = B_FALSE;
893 
894 	if ((err = nwam_known_wlan_get_prop_value(kwh,
895 	    NWAM_KNOWN_WLAN_PROP_BSSIDS, &bssidsval)) != NWAM_SUCCESS) {
896 		nlog(LOG_ERR, "bssid_match: %s", nwam_strerror(err));
897 		return (B_FALSE);
898 	}
899 	if ((err = nwam_value_get_string_array(bssidsval, &bssids, &nelem))
900 	    != NWAM_SUCCESS) {
901 		nwam_value_free(bssidsval);
902 		return (B_FALSE);
903 	}
904 	for (i = 0; i < nelem; i++) {
905 		if (strcmp(bssid, bssids[i]) == 0) {
906 			found = B_TRUE;
907 			break;
908 		}
909 	}
910 	nwam_value_free(bssidsval);
911 
912 	return (found);
913 }
914 
915 /* Find most prioritized AP with strongest signal in scan data. */
916 static int
917 find_best_wlan_cb(nwam_known_wlan_handle_t kwh, void *data)
918 {
919 	nwamd_ncu_t *ncu = data;
920 	nwamd_link_t *link = &ncu->ncu_link;
921 	nwamd_wifi_scan_t *s = &link->nwamd_link_wifi_scan;
922 	nwam_error_t err;
923 	char *name = NULL;
924 	int i;
925 	dladm_wlan_strength_t curr_strength = 0;
926 	dladm_wlan_strength_t max_strength = 0;
927 	boolean_t found = B_FALSE;
928 
929 	if ((err = nwam_known_wlan_get_name(kwh, &name)) != NWAM_SUCCESS) {
930 		nlog(LOG_ERR, "find_best_wlan_cb: could not look up name: %s",
931 		    nwam_strerror(err));
932 		return (0);
933 	}
934 
935 	if (link->nwamd_link_wifi_connected) {
936 		(void) dladm_wlan_str2strength
937 		    (link->nwamd_link_wifi_signal_strength, &curr_strength);
938 	}
939 
940 	/*
941 	 * If we're >= scan level, don't pick another Known WLAN if still
942 	 * connected (even if a Known WLAN with higher priority is available).
943 	 * If the user wants to connect to a different Known WLAN, it can be
944 	 * done from the GUI or select-wifi subcommand of nwamadm(1M).
945 	 */
946 	if (curr_strength >= wireless_scan_level &&
947 	    link->nwamd_link_wifi_connected) {
948 		free(name);
949 		return (1);
950 	}
951 
952 	for (i = 0; i < s->nwamd_wifi_scan_curr_num; i++) {
953 		nwam_wlan_t *cur_wlan = &(s->nwamd_wifi_scan_curr[i]);
954 		boolean_t b_match = bssid_match(kwh, cur_wlan->nww_bssid);
955 
956 		/*
957 		 * We need to either match the scanned essid, or in the case
958 		 * where the essid was not broadcast, match the scanned bssid.
959 		 */
960 		if (strcmp(cur_wlan->nww_essid, name) != 0 &&
961 		    !(cur_wlan->nww_essid[0] == '\0' && b_match))
962 			continue;
963 		/*
964 		 * If wireless_strict_bssid is specified, need to match
965 		 * BSSID too.
966 		 */
967 		if (wireless_strict_bssid && !b_match)
968 			continue;
969 		/*
970 		 * Found a match. Since we walk known WLANs in
971 		 * priority order, it's guaranteed to be the
972 		 * most prioritized. It may not be the strongest though -
973 		 * we continue the walk and record the strength along
974 		 * with the ESSID and BSSID, so that if we encounter
975 		 * another AP with the same ESSID but a higher signal strength,
976 		 * we will choose it - but only if the currently-connected
977 		 * WLAN is at or below wireless_scan_level.
978 		 */
979 		(void) dladm_wlan_str2strength
980 		    (cur_wlan->nww_signal_strength, &curr_strength);
981 
982 		if (curr_strength > max_strength) {
983 			(void) strlcpy(link->nwamd_link_wifi_essid,
984 			    cur_wlan->nww_essid,
985 			    sizeof (link->nwamd_link_wifi_essid));
986 			/*
987 			 * Set BSSID if wireless_strict_bssid is specified or
988 			 * if this is a hidden WLAN.  Store the BSSID here and
989 			 * then later determine the hidden WLAN's name in the
990 			 * connect thread.
991 			 */
992 			if (wireless_strict_bssid ||
993 			    cur_wlan->nww_essid[0] == '\0') {
994 				(void) strlcpy(link->nwamd_link_wifi_bssid,
995 				    cur_wlan->nww_bssid,
996 				    sizeof (link->nwamd_link_wifi_bssid));
997 			}
998 			(void) strlcpy(link->nwamd_link_wifi_signal_strength,
999 			    cur_wlan->nww_signal_strength,
1000 			    sizeof (link->nwamd_link_wifi_signal_strength));
1001 			link->nwamd_link_wifi_security_mode =
1002 			    cur_wlan->nww_security_mode;
1003 			found = B_TRUE;
1004 		}
1005 		(void) dladm_wlan_str2strength
1006 		    (link->nwamd_link_wifi_signal_strength, &max_strength);
1007 	}
1008 	free(name);
1009 	return (found ? 1 : 0);
1010 }
1011 
1012 static boolean_t
1013 nwamd_find_known_wlan(nwamd_object_t ncu_obj)
1014 {
1015 	nwamd_ncu_t *ncu = ncu_obj->nwamd_object_data;
1016 	int ret;
1017 
1018 	/*
1019 	 * Walk known WLANs, finding lowest priority (preferred) WLAN
1020 	 * in our scan results.
1021 	 */
1022 	(void) nwam_walk_known_wlans(find_best_wlan_cb, ncu,
1023 	    NWAM_FLAG_KNOWN_WLAN_WALK_PRIORITY_ORDER, &ret);
1024 
1025 	return (ret == 1);
1026 }
1027 
1028 /*
1029  * WLAN scan code for WIFI link NCUs.
1030  */
1031 
1032 /* Create periodic scan event for object.  Called with object lock held. */
1033 void
1034 nwamd_ncu_create_periodic_scan_event(nwamd_object_t ncu_obj)
1035 {
1036 	nwamd_event_t scan_event;
1037 
1038 	if (wireless_scan_interval == 0) {
1039 		nlog(LOG_DEBUG, "nwamd_ncu_create_periodic_scan_event: "
1040 		    "wireless_scan_interval set to 0 so no periodic scanning");
1041 		return;
1042 	}
1043 	scan_event = nwamd_event_init(NWAM_EVENT_TYPE_PERIODIC_SCAN,
1044 	    NWAM_OBJECT_TYPE_NCU, 0, ncu_obj->nwamd_object_name);
1045 	if (scan_event != NULL) {
1046 		nwamd_event_enqueue_timed(scan_event,
1047 		    wireless_scan_interval > WIRELESS_SCAN_INTERVAL_MIN ?
1048 		    wireless_scan_interval : WIRELESS_SCAN_INTERVAL_MIN);
1049 	}
1050 }
1051 
1052 /* Handle periodic scan event (which puts link into WIFI_INIT state */
1053 void
1054 nwamd_ncu_handle_periodic_scan_event(nwamd_event_t event)
1055 {
1056 	nwamd_object_t ncu_obj;
1057 	nwamd_ncu_t *ncu;
1058 
1059 	ncu_obj = nwamd_object_find(NWAM_OBJECT_TYPE_NCU,
1060 	    event->event_object);
1061 	if (ncu_obj == NULL) {
1062 		nlog(LOG_ERR, "nwamd_ncu_handle_periodic_scan_event: "
1063 		    "no object %s", event->event_object);
1064 		return;
1065 	}
1066 	ncu = ncu_obj->nwamd_object_data;
1067 
1068 	/* Only rescan if state is offline* or online */
1069 	nlog(LOG_DEBUG, "nwamd_ncu_handle_periodic_scan_event: doing rescan..");
1070 
1071 	if (ncu_obj->nwamd_object_state == NWAM_STATE_OFFLINE_TO_ONLINE ||
1072 	    ncu_obj->nwamd_object_state == NWAM_STATE_ONLINE) {
1073 		/* rescan, then create periodic scan event */
1074 		(void) nwamd_wlan_scan(ncu->ncu_name);
1075 		nwamd_ncu_create_periodic_scan_event(ncu_obj);
1076 	}
1077 	nwamd_object_release(ncu_obj);
1078 }
1079 
1080 static boolean_t
1081 get_scan_results(void *arg, dladm_wlan_attr_t *attrp)
1082 {
1083 	nwamd_wifi_scan_t *s = arg;
1084 	const char *linkname = s->nwamd_wifi_scan_link;
1085 	char essid_name[DLADM_STRSIZE];
1086 	char bssid_name[DLADM_STRSIZE];
1087 	char strength[DLADM_STRSIZE];
1088 	uint_t i, index = 0;
1089 	boolean_t found = B_FALSE;
1090 
1091 	(void) dladm_wlan_essid2str(&attrp->wa_essid, essid_name);
1092 	(void) dladm_wlan_bssid2str(&attrp->wa_bssid, bssid_name);
1093 	(void) dladm_wlan_strength2str(&attrp->wa_strength, strength);
1094 
1095 	index = s->nwamd_wifi_scan_curr_num;
1096 	if (index == NWAMD_MAX_NUM_WLANS) {
1097 		nlog(LOG_ERR, "get_scan_results: truncating WLAN scan results "
1098 		    "for link %s: ommiting (%s, %s)", linkname, essid_name,
1099 		    bssid_name);
1100 		return (B_TRUE);
1101 	}
1102 
1103 	(void) strlcpy(s->nwamd_wifi_scan_curr[index].nww_essid, essid_name,
1104 	    sizeof (s->nwamd_wifi_scan_curr[index].nww_essid));
1105 	(void) strlcpy(s->nwamd_wifi_scan_curr[index].nww_bssid, bssid_name,
1106 	    sizeof (s->nwamd_wifi_scan_curr[index].nww_bssid));
1107 	(void) strlcpy(s->nwamd_wifi_scan_curr[index].nww_signal_strength,
1108 	    strength,
1109 	    sizeof (s->nwamd_wifi_scan_curr[index].nww_signal_strength));
1110 	s->nwamd_wifi_scan_curr[index].nww_security_mode = attrp->wa_secmode;
1111 	s->nwamd_wifi_scan_curr[index].nww_speed = attrp->wa_speed;
1112 	s->nwamd_wifi_scan_curr[index].nww_channel = attrp->wa_channel;
1113 	s->nwamd_wifi_scan_curr[index].nww_bsstype = attrp->wa_bsstype;
1114 
1115 	/*
1116 	 * We fill in actual values for selected/connected/key later when we
1117 	 * reacquire the object lock.
1118 	 */
1119 	s->nwamd_wifi_scan_curr[index].nww_selected = B_FALSE;
1120 	s->nwamd_wifi_scan_curr[index].nww_connected = B_FALSE;
1121 	s->nwamd_wifi_scan_curr[index].nww_have_key = B_FALSE;
1122 	s->nwamd_wifi_scan_curr[index].nww_keyindex = 1;
1123 	s->nwamd_wifi_scan_curr_num++;
1124 
1125 	/* Check if this AP was in previous scan results */
1126 	for (i = 0; i < s->nwamd_wifi_scan_last_num; i++) {
1127 		found = (strcmp(s->nwamd_wifi_scan_last[i].nww_essid,
1128 		    essid_name) == 0 &&
1129 		    strcmp(s->nwamd_wifi_scan_last[i].nww_bssid,
1130 		    bssid_name) == 0);
1131 		if (found)
1132 			break;
1133 	}
1134 	if (!found)
1135 		s->nwamd_wifi_scan_changed = B_TRUE;
1136 
1137 	nlog(LOG_DEBUG, "get_scan_results(%s, %d): ESSID %s, BSSID %s",
1138 	    linkname, index, essid_name, bssid_name);
1139 
1140 	return (B_TRUE);
1141 }
1142 
1143 /*
1144  * Check if we're connected to the expected WLAN, or in the case of autoconf
1145  * record the WLAN we're connected to.
1146  */
1147 boolean_t
1148 nwamd_wlan_connected(nwamd_object_t ncu_obj)
1149 {
1150 	nwamd_ncu_t *ncu = ncu_obj->nwamd_object_data;
1151 	nwamd_link_t *link = &ncu->ncu_link;
1152 	dladm_wlan_linkattr_t attr;
1153 	char essid[DLADM_STRSIZE];
1154 	char bssid[DLADM_STRSIZE];
1155 	boolean_t connected = B_FALSE;
1156 	int retries = 0;
1157 
1158 	/*
1159 	 * This is awful, but some wireless drivers
1160 	 * (particularly 'ath') will erroneously report
1161 	 * "disconnected" if queried right after a scan.  If we
1162 	 * see 'down' reported here, we retry a few times to
1163 	 * make sure it's really down.
1164 	 */
1165 	while (retries++ < 4) {
1166 		if (dladm_wlan_get_linkattr(dld_handle, link->nwamd_link_id,
1167 		    &attr) != DLADM_STATUS_OK) {
1168 			attr.la_status = DLADM_WLAN_LINK_DISCONNECTED;
1169 		} else if (attr.la_status == DLADM_WLAN_LINK_CONNECTED) {
1170 			break;
1171 		}
1172 	}
1173 
1174 	if (attr.la_status == DLADM_WLAN_LINK_CONNECTED) {
1175 		(void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid, essid);
1176 		(void) dladm_wlan_bssid2str(&attr.la_wlan_attr.wa_bssid, bssid);
1177 		connected = B_TRUE;
1178 		nlog(LOG_DEBUG, "nwamd_wlan_connected: %s connected to %s %s",
1179 		    ncu->ncu_name, essid, bssid);
1180 	} else {
1181 		return (B_FALSE);
1182 	}
1183 	/*
1184 	 * If we're using autoconf,  we have no control over what we connect to,
1185 	 * so rather than verifying ESSSID, simply record ESSID/BSSID.
1186 	 */
1187 	if (link->nwamd_link_wifi_autoconf) {
1188 		(void) strlcpy(link->nwamd_link_wifi_essid, essid,
1189 		    sizeof (link->nwamd_link_wifi_essid));
1190 		(void) strlcpy(link->nwamd_link_wifi_bssid, bssid,
1191 		    sizeof (link->nwamd_link_wifi_bssid));
1192 	}
1193 	/*
1194 	 * Are we connected to expected WLAN? Note:
1195 	 * we'd like to verify BSSID, but we cannot due to CR 6772510.
1196 	 */
1197 	if (strcmp(essid, link->nwamd_link_wifi_essid) == 0) {
1198 		/* Update connected signal strength */
1199 		(void) dladm_wlan_strength2str(&attr.la_wlan_attr.wa_strength,
1200 		    link->nwamd_link_wifi_signal_strength);
1201 
1202 		/* Store current BSSID */
1203 		(void) strlcpy(link->nwamd_link_wifi_bssid, bssid,
1204 		    sizeof (link->nwamd_link_wifi_bssid));
1205 
1206 		if (attr.la_wlan_attr.wa_strength < wireless_scan_level) {
1207 			/*
1208 			 * We're connected, but we've dropped below
1209 			 * scan threshold.  Initiate a scan.
1210 			 */
1211 			nlog(LOG_DEBUG, "nwamd_wlan_connected: "
1212 			    "connected but signal under threshold...");
1213 			(void) nwamd_wlan_scan(ncu->ncu_name);
1214 		}
1215 		return (connected);
1216 	} else if (strlen(essid) == 0) {
1217 		/*
1218 		 * For hidden WLANs, no ESSID is specified, so we cannot verify
1219 		 * WLAN name.
1220 		 */
1221 		nlog(LOG_DEBUG,
1222 		    "nwamd_wlan_connected: connected to hidden WLAN, cannot "
1223 		    "verify connection details");
1224 		return (connected);
1225 	} else {
1226 		(void) nlog(LOG_ERR,
1227 		    "nwamd_wlan_connected: wrong AP on %s; expected %s %s",
1228 		    ncu->ncu_name, link->nwamd_link_wifi_essid,
1229 		    link->nwamd_link_wifi_bssid);
1230 		(void) dladm_wlan_disconnect(dld_handle, link->nwamd_link_id);
1231 		link->nwamd_link_wifi_connected = B_FALSE;
1232 		return (B_FALSE);
1233 	}
1234 }
1235 
1236 /*
1237  * WLAN scan thread. Called with the per-link WiFi mutex held.
1238  */
1239 static void *
1240 wlan_scan_thread(void *arg)
1241 {
1242 	char *linkname = arg;
1243 	nwamd_object_t ncu_obj;
1244 	nwamd_ncu_t *ncu;
1245 	nwamd_link_t *link;
1246 	dladm_status_t status;
1247 	char essid[DLADM_STRSIZE];
1248 	char bssid[DLADM_STRSIZE];
1249 	uint32_t now, link_id;
1250 	nwamd_wifi_scan_t s;
1251 	int i;
1252 
1253 	if ((ncu_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_LINK, linkname))
1254 	    == NULL) {
1255 		nlog(LOG_ERR, "wlan_scan_thread: could not find object  "
1256 		    "for link %s", linkname);
1257 		free(linkname);
1258 		return (NULL);
1259 	}
1260 
1261 	ncu = ncu_obj->nwamd_object_data;
1262 	link = &ncu->ncu_link;
1263 
1264 	/*
1265 	 * It is possible multiple scan threads have queued up waiting for the
1266 	 * object lock.  We try to prevent excessive scanning by limiting the
1267 	 * interval between scans to WIRELESS_SCAN_REQUESTED_INTERVAL_MIN sec.
1268 	 */
1269 	now = NSEC_TO_SEC(gethrtime());
1270 	if ((now - link->nwamd_link_wifi_scan.nwamd_wifi_scan_last_time) <
1271 	    WIRELESS_SCAN_REQUESTED_INTERVAL_MIN) {
1272 		nlog(LOG_DEBUG, "wlan_scan_thread: last scan for %s "
1273 		    "was < %d sec ago, ignoring scan request",
1274 		    linkname, WIRELESS_SCAN_REQUESTED_INTERVAL_MIN);
1275 		nwamd_object_release(ncu_obj);
1276 		free(linkname);
1277 		return (NULL);
1278 	}
1279 
1280 	/*
1281 	 * Prepare scan data - copy link name and copy previous "current"
1282 	 * scan results from the nwamd_link_t to the last scan results for
1283 	 * the next scan so that we can compare results to find if things
1284 	 * have changed since last time.
1285 	 */
1286 	(void) bzero(&s, sizeof (nwamd_wifi_scan_t));
1287 	(void) strlcpy(s.nwamd_wifi_scan_link, ncu->ncu_name,
1288 	    sizeof (s.nwamd_wifi_scan_link));
1289 	s.nwamd_wifi_scan_last_num =
1290 	    link->nwamd_link_wifi_scan.nwamd_wifi_scan_curr_num;
1291 	if (s.nwamd_wifi_scan_last_num > 0) {
1292 		(void) memcpy(s.nwamd_wifi_scan_last,
1293 		    link->nwamd_link_wifi_scan.nwamd_wifi_scan_curr,
1294 		    s.nwamd_wifi_scan_last_num * sizeof (nwam_wlan_t));
1295 	}
1296 	link_id = link->nwamd_link_id;
1297 	nwamd_object_release(ncu_obj);
1298 
1299 	nlog(LOG_DEBUG, "wlan_scan_thread: initiating scan on %s",
1300 	    s.nwamd_wifi_scan_link);
1301 
1302 	scanconnect_entry();
1303 	status = dladm_wlan_scan(dld_handle, link_id, &s, get_scan_results);
1304 	s.nwamd_wifi_scan_last_time = NSEC_TO_SEC(gethrtime());
1305 	if (!s.nwamd_wifi_scan_changed) {
1306 		/* Scan may have lost WLANs, if so this qualifies as change */
1307 		s.nwamd_wifi_scan_changed = (s.nwamd_wifi_scan_curr_num !=
1308 		    s.nwamd_wifi_scan_last_num);
1309 	}
1310 	scanconnect_exit();
1311 
1312 	if (status != DLADM_STATUS_OK) {
1313 		nlog(LOG_ERR, "wlan_scan_thread: cannot scan link %s",
1314 		    s.nwamd_wifi_scan_link);
1315 		free(linkname);
1316 		return (NULL);
1317 	}
1318 
1319 	if ((ncu_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_LINK, linkname))
1320 	    == NULL) {
1321 		nlog(LOG_ERR, "wlan_scan_thread: could not find object  "
1322 		    "for link %s after doing scan", linkname);
1323 		free(linkname);
1324 		return (NULL);
1325 	}
1326 	ncu = ncu_obj->nwamd_object_data;
1327 	link = &ncu->ncu_link;
1328 
1329 	/* For new scan data, add key info from known WLANs */
1330 	for (i = 0; i < s.nwamd_wifi_scan_curr_num; i++) {
1331 		if (NEED_ENC(s.nwamd_wifi_scan_curr[i].nww_security_mode)) {
1332 			char keyname[NWAM_MAX_VALUE_LEN];
1333 			dladm_wlan_key_t *key = NULL;
1334 
1335 			if (known_wlan_get_keyname
1336 			    (s.nwamd_wifi_scan_curr[i].nww_essid, keyname)
1337 			    == NWAM_SUCCESS &&
1338 			    (key = nwamd_wlan_get_key_named(keyname,
1339 			    s.nwamd_wifi_scan_curr[i].nww_security_mode))
1340 			    != NULL) {
1341 				s.nwamd_wifi_scan_curr[i].nww_have_key =
1342 				    B_TRUE;
1343 				s.nwamd_wifi_scan_curr[i].nww_keyindex =
1344 				    s.nwamd_wifi_scan_curr[i].
1345 				    nww_security_mode ==
1346 				    DLADM_WLAN_SECMODE_WEP ?
1347 				    key->wk_idx : 1;
1348 				free(key);
1349 			}
1350 		}
1351 	}
1352 	/* Copy scan data into nwamd_link_t */
1353 	link->nwamd_link_wifi_scan = s;
1354 	/* Set selected, connected and send scan event if we've got new data */
1355 	nwamd_set_selected_connected(ncu,
1356 	    link->nwamd_link_wifi_essid[0] != '\0',
1357 	    link->nwamd_link_wifi_connected);
1358 
1359 	/*
1360 	 * If wireless selection is not possible because of the current
1361 	 * state or priority-group, then this was just a scan request.
1362 	 * Nothing else to do.
1363 	 */
1364 	if (!wireless_selection_possible(ncu_obj)) {
1365 		nwamd_object_release(ncu_obj);
1366 		free(linkname);
1367 		return (NULL);
1368 	}
1369 
1370 	/*
1371 	 * Check if WLAN is on our known WLAN list. If no
1372 	 * previously-visited WLANs are found in scan data, set
1373 	 * new state to NEED_SELECTION (provided we're not currently
1374 	 * connected, as can be the case during a periodic scan or
1375 	 * monitor-triggered scan where the signal strength recovers.
1376 	 */
1377 	if (!nwamd_find_known_wlan(ncu_obj)) {
1378 		if (!nwamd_wlan_connected(ncu_obj)) {
1379 			if (link->nwamd_link_wifi_connected) {
1380 				nlog(LOG_DEBUG, "wlan_scan_thread: "
1381 				    "unexpected disconnect after scan");
1382 				nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1383 				    ncu_obj->nwamd_object_name,
1384 				    NWAM_STATE_ONLINE_TO_OFFLINE,
1385 				    NWAM_AUX_STATE_DOWN);
1386 			} else {
1387 				nlog(LOG_DEBUG, "wlan_scan_thread: "
1388 				    "no known WLANs - ask user");
1389 				nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1390 				    ncu_obj->nwamd_object_name,
1391 				    NWAM_STATE_OFFLINE_TO_ONLINE,
1392 				    NWAM_AUX_STATE_LINK_WIFI_NEED_SELECTION);
1393 			}
1394 		} else {
1395 			/* still connected. if not online, change to online */
1396 			nlog(LOG_DEBUG, "wlan_scan_thread: still connected to "
1397 			    "%s %s", link->nwamd_link_wifi_essid,
1398 			    link->nwamd_link_wifi_bssid);
1399 			if (ncu_obj->nwamd_object_state != NWAM_STATE_ONLINE) {
1400 				nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1401 				    ncu_obj->nwamd_object_name,
1402 				    NWAM_STATE_OFFLINE_TO_ONLINE,
1403 				    NWAM_AUX_STATE_UP);
1404 			}
1405 		}
1406 		nwamd_object_release(ncu_obj);
1407 
1408 	} else {
1409 		nlog(LOG_DEBUG, "wlan_scan_thread: found known WLAN %s %s",
1410 		    link->nwamd_link_wifi_essid, link->nwamd_link_wifi_bssid);
1411 
1412 		if (!nwamd_wlan_connected(ncu_obj)) {
1413 			/* Copy selected ESSID/BSSID, unlock, call select */
1414 			(void) strlcpy(essid, link->nwamd_link_wifi_essid,
1415 			    sizeof (essid));
1416 			(void) strlcpy(bssid, link->nwamd_link_wifi_bssid,
1417 			    sizeof (bssid));
1418 			nwamd_object_release(ncu_obj);
1419 			(void) nwamd_wlan_select(linkname, essid, bssid,
1420 			    link->nwamd_link_wifi_security_mode, B_TRUE);
1421 		} else {
1422 			/* still connected.  if not online, change to online */
1423 			nlog(LOG_DEBUG, "wlan_scan_thread: still connected to "
1424 			    "known WLAN %s %s", link->nwamd_link_wifi_essid,
1425 			    link->nwamd_link_wifi_bssid);
1426 			if (ncu_obj->nwamd_object_state != NWAM_STATE_ONLINE) {
1427 				nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1428 				    ncu_obj->nwamd_object_name,
1429 				    NWAM_STATE_OFFLINE_TO_ONLINE,
1430 				    NWAM_AUX_STATE_UP);
1431 			}
1432 			nwamd_object_release(ncu_obj);
1433 		}
1434 	}
1435 	free(linkname);
1436 	return (NULL);
1437 }
1438 
1439 nwam_error_t
1440 nwamd_wlan_scan(const char *linkname)
1441 {
1442 	pthread_t wifi_thread;
1443 	char *link = strdup(linkname);
1444 
1445 	if (link == NULL) {
1446 		nlog(LOG_ERR, "nwamd_wlan_scan: out of memory");
1447 		return (NWAM_NO_MEMORY);
1448 	}
1449 
1450 	nlog(LOG_DEBUG, "nwamd_wlan_scan: WLAN scan for %s",
1451 	    link);
1452 
1453 	if (pthread_create(&wifi_thread, NULL, wlan_scan_thread,
1454 	    link) != 0) {
1455 		nlog(LOG_ERR, "nwamd_wlan_scan: could not start scan");
1456 		free(link);
1457 		return (NWAM_ERROR_INTERNAL);
1458 	}
1459 	/* detach thread so that it doesn't become a zombie */
1460 	(void) pthread_detach(wifi_thread);
1461 	return (NWAM_SUCCESS);
1462 }
1463 
1464 /*
1465  * WLAN connection code.
1466  */
1467 
1468 static dladm_status_t
1469 do_connect(uint32_t link_id, dladm_wlan_attr_t *attrp, dladm_wlan_key_t *key,
1470     uint_t keycount, uint_t flags)
1471 {
1472 	dladm_status_t status;
1473 	char errmsg[DLADM_STRSIZE];
1474 
1475 	scanconnect_entry();
1476 	status = dladm_wlan_connect(dld_handle, link_id, attrp,
1477 	    DLADM_WLAN_CONNECT_TIMEOUT_DEFAULT, key, keycount, flags);
1478 	scanconnect_exit();
1479 
1480 	nlog(LOG_DEBUG, "nwamd_do_connect: dladm_wlan_connect returned %s",
1481 	    dladm_status2str(status, errmsg));
1482 
1483 	return (status);
1484 }
1485 
1486 static void *
1487 wlan_connect_thread(void *arg)
1488 {
1489 	char *linkname = arg;
1490 	nwamd_object_t ncu_obj;
1491 	nwamd_ncu_t *ncu;
1492 	nwamd_link_t *link;
1493 	nwam_error_t err;
1494 	uint_t	keycount;
1495 	uint32_t link_id;
1496 	dladm_wlan_key_t *key = NULL;
1497 	dladm_wlan_attr_t attr;
1498 	dladm_status_t status;
1499 	boolean_t autoconf = B_FALSE;
1500 
1501 	if ((ncu_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_LINK, linkname))
1502 	    == NULL) {
1503 		nlog(LOG_ERR, "wlan_connect_thread: could not find object  "
1504 		    "for link %s", linkname);
1505 		free(linkname);
1506 		return (NULL);
1507 	}
1508 
1509 	ncu = ncu_obj->nwamd_object_data;
1510 	link = &ncu->ncu_link;
1511 
1512 	if (!wireless_selection_possible(ncu_obj)) {
1513 		nlog(LOG_DEBUG, "wlan_connect_thread: %s in invalid state or "
1514 		    "has lower priority", ncu->ncu_name);
1515 		goto done;
1516 	}
1517 
1518 	/* If it is already connected to the required AP, just return. */
1519 	if (nwamd_wlan_connected(ncu_obj)) {
1520 		nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1521 		    ncu_obj->nwamd_object_name,
1522 		    ncu_obj->nwamd_object_state, NWAM_AUX_STATE_UP);
1523 		goto done;
1524 	}
1525 
1526 	(void) memset(&attr, 0, sizeof (attr));
1527 	if (dladm_wlan_str2essid(link->nwamd_link_wifi_essid, &attr.wa_essid)
1528 	    != DLADM_STATUS_OK) {
1529 		nlog(LOG_ERR, "wlan_connect_thread: invalid ESSID '%s' "
1530 		    "for '%s'", link->nwamd_link_wifi_essid, ncu->ncu_name);
1531 		goto done;
1532 	}
1533 	attr.wa_valid = DLADM_WLAN_ATTR_ESSID;
1534 
1535 	/* note: bssid logic here is non-functional */
1536 	if (link->nwamd_link_wifi_bssid[0] != '\0') {
1537 		if (dladm_wlan_str2bssid(link->nwamd_link_wifi_bssid,
1538 		    &attr.wa_bssid) != DLADM_STATUS_OK) {
1539 			nlog(LOG_ERR, "wlan_connect_thread: invalid BSSID '%s'",
1540 			    "for '%s'", link->nwamd_link_wifi_bssid,
1541 			    ncu->ncu_name);
1542 		} else {
1543 			attr.wa_valid |= DLADM_WLAN_ATTR_BSSID;
1544 		}
1545 	}
1546 
1547 	/* First check for the key */
1548 	if (NEED_ENC(link->nwamd_link_wifi_security_mode)) {
1549 		if (link->nwamd_link_wifi_key == NULL) {
1550 			nlog(LOG_ERR, "wlan_connect_thread: could not find "
1551 			    "key for WLAN '%s'", link->nwamd_link_wifi_essid);
1552 			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1553 			    ncu_obj->nwamd_object_name,
1554 			    NWAM_STATE_OFFLINE_TO_ONLINE,
1555 			    NWAM_AUX_STATE_LINK_WIFI_NEED_KEY);
1556 			goto done;
1557 		}
1558 		/* Make a copy of the key as we need to unlock the object */
1559 		if ((key = calloc(1, sizeof (dladm_wlan_key_t))) == NULL) {
1560 			nlog(LOG_ERR, "wlan_connect_thread: out of memory");
1561 			goto done;
1562 		}
1563 		(void) memcpy(key, link->nwamd_link_wifi_key,
1564 		    sizeof (dladm_wlan_key_t));
1565 
1566 		attr.wa_valid |= DLADM_WLAN_ATTR_SECMODE;
1567 		attr.wa_secmode = link->nwamd_link_wifi_security_mode;
1568 		keycount = 1;
1569 		nlog(LOG_DEBUG, "wlan_connect_thread: retrieved key");
1570 	} else {
1571 		key = NULL;
1572 		keycount = 0;
1573 	}
1574 
1575 	/*
1576 	 * Connect; only scan if a bssid was not specified.  If it times out,
1577 	 * try a second time using autoconf.  Drop the object lock during the
1578 	 * connect attempt since connecting may take some time, and access to
1579 	 * the link object during that period would be impossible if we held the
1580 	 * lock.
1581 	 */
1582 
1583 	link->nwamd_link_wifi_autoconf = B_FALSE;
1584 	link_id = link->nwamd_link_id;
1585 
1586 	nwamd_object_release(ncu_obj);
1587 
1588 	status = do_connect(link_id, &attr, key, keycount,
1589 	    DLADM_WLAN_CONNECT_NOSCAN);
1590 	if (status != DLADM_STATUS_OK) {
1591 		/* Connect failed, try autoconf */
1592 		if (!wireless_autoconf || (status = do_connect(link_id, &attr,
1593 		    NULL, 0, 0)) != DLADM_STATUS_OK) {
1594 			nlog(LOG_ERR, "wlan_connect_thread: connect failed for "
1595 			    "%s", linkname);
1596 			goto done_unlocked;
1597 		}
1598 		if (status == DLADM_STATUS_OK)
1599 			autoconf = B_TRUE;
1600 	}
1601 
1602 	/* Connect succeeded, reacquire object */
1603 	if ((ncu_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_LINK, linkname))
1604 	    == NULL) {
1605 		nlog(LOG_ERR, "wlan_connect_thread: could not find object  "
1606 		    "for link %s", linkname);
1607 		goto done_unlocked;
1608 	}
1609 
1610 	ncu = ncu_obj->nwamd_object_data;
1611 	link = &ncu->ncu_link;
1612 
1613 	if (autoconf)
1614 		link->nwamd_link_wifi_autoconf = B_TRUE;
1615 
1616 	/*
1617 	 * If WLAN is WEP/WPA, we would like to test the connection as the key
1618 	 * may be wrong.  It is difficult to find a reliable test that works
1619 	 * across APs however.  Do nothing for now.
1620 	 */
1621 	link->nwamd_link_wifi_connected = nwamd_wlan_connected(ncu_obj);
1622 
1623 	if (link->nwamd_link_wifi_connected) {
1624 		if (link->nwamd_link_wifi_add_to_known_wlans) {
1625 			/* add to known WLANs */
1626 			nlog(LOG_DEBUG, "wlan_connect_thread: "
1627 			    "add '%s' to known WLANs",
1628 			    link->nwamd_link_wifi_essid);
1629 			if ((err = nwam_known_wlan_add_to_known_wlans
1630 			    (link->nwamd_link_wifi_essid,
1631 			    link->nwamd_link_wifi_bssid[0] != '\0' ?
1632 			    link->nwamd_link_wifi_bssid : NULL,
1633 			    link->nwamd_link_wifi_security_mode,
1634 			    link->nwamd_link_wifi_security_mode ==
1635 			    DLADM_WLAN_SECMODE_WEP ?
1636 			    (uint_t)link->nwamd_link_wifi_key->wk_idx : 1,
1637 			    NEED_ENC(link->nwamd_link_wifi_security_mode) ?
1638 			    link->nwamd_link_wifi_keyname : NULL))
1639 			    != NWAM_SUCCESS) {
1640 				nlog(LOG_ERR, "wlan_connect_thread: "
1641 				    "could not add to known WLANs: %s",
1642 				    nwam_strerror(err));
1643 			}
1644 		}
1645 		nwamd_set_selected_connected(ncu, B_TRUE, B_TRUE);
1646 		nlog(LOG_DEBUG, "wlan_connect_thread: connect "
1647 		    "succeeded, setting state online");
1648 		nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1649 		    ncu_obj->nwamd_object_name, NWAM_STATE_ONLINE,
1650 		    NWAM_AUX_STATE_UP);
1651 	}
1652 
1653 done:
1654 	nwamd_object_release(ncu_obj);
1655 done_unlocked:
1656 	free(linkname);
1657 	free(key);
1658 
1659 	return (NULL);
1660 }
1661 
1662 void
1663 nwamd_wlan_connect(const char *linkname)
1664 {
1665 	pthread_t wifi_thread;
1666 	char *link = strdup(linkname);
1667 
1668 	if (link == NULL) {
1669 		nlog(LOG_ERR, "nwamd_wlan_connect: out of memory");
1670 		return;
1671 	}
1672 
1673 	nlog(LOG_DEBUG, "nwamd_wlan_connect: WLAN connect for %s",
1674 	    link);
1675 
1676 	if (pthread_create(&wifi_thread, NULL, wlan_connect_thread, link) != 0)
1677 		nlog(LOG_ERR, "nwamd_wlan_connect: could not start connect");
1678 
1679 	/* detach thread so that it doesn't become a zombie */
1680 	(void) pthread_detach(wifi_thread);
1681 }
1682 
1683 /*
1684  * Launch signal strength-monitoring thread which periodically
1685  * checks connection and signal strength.  If we become disconnected
1686  * or signal drops below threshold specified by wireless_scan_level,
1687  * initiate a scan.  The scan initiation is taken care of by
1688  * the call to nwamd_wlan_connected().
1689  */
1690 static void *
1691 wlan_monitor_signal_thread(void *arg)
1692 {
1693 	char *linkname = arg;
1694 	nwamd_object_t ncu_obj;
1695 	nwamd_ncu_t *ncu;
1696 	nwamd_link_t *link;
1697 	boolean_t first_time = B_TRUE;
1698 
1699 	for (;;) {
1700 		if ((ncu_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_LINK,
1701 		    linkname)) == NULL) {
1702 			nlog(LOG_ERR, "wlan_monitor_signal_thread: could "
1703 			    "not find object for link %s", linkname);
1704 			break;
1705 		}
1706 		ncu = ncu_obj->nwamd_object_data;
1707 		link = &ncu->ncu_link;
1708 
1709 		/* If the NCU is DISABLED/OFFLINE, exit the monitoring thread */
1710 		if (ncu_obj->nwamd_object_state == NWAM_STATE_OFFLINE ||
1711 		    ncu_obj->nwamd_object_state == NWAM_STATE_DISABLED) {
1712 			nlog(LOG_INFO, "wlan_monitor_signal_thread: "
1713 			    "%s is %s, stopping thread", linkname,
1714 			    nwam_state_to_string(ncu_obj->nwamd_object_state));
1715 			link->nwamd_link_wifi_monitor_thread = 0;
1716 			nwamd_object_release(ncu_obj);
1717 			break;
1718 		}
1719 
1720 		/*
1721 		 * First time thru loop, we check if there is another
1722 		 * link monitoring thread in operation - if so exit this
1723 		 * thread.
1724 		 */
1725 		if (first_time) {
1726 			first_time = B_FALSE;
1727 
1728 			if (link->nwamd_link_wifi_monitor_thread != 0) {
1729 				/* Already have a monitor thread for link? */
1730 				nwamd_object_release(ncu_obj);
1731 				break;
1732 			} else {
1733 				link->nwamd_link_wifi_monitor_thread =
1734 				    pthread_self();
1735 			}
1736 		}
1737 		if (!nwamd_wlan_connected(ncu_obj)) {
1738 			nlog(LOG_ERR, "wlan_monitor_signal_thread: "
1739 			    "disconnect occured for WLAN on link %s", linkname);
1740 			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1741 			    ncu_obj->nwamd_object_name,
1742 			    NWAM_STATE_ONLINE_TO_OFFLINE,
1743 			    NWAM_AUX_STATE_DOWN);
1744 			link->nwamd_link_wifi_monitor_thread = 0;
1745 			nwamd_object_release(ncu_obj);
1746 			break;
1747 		}
1748 		nwamd_object_release(ncu_obj);
1749 		(void) sleep(WIRELESS_MONITOR_SIGNAL_INTERVAL);
1750 	}
1751 	free(linkname);
1752 
1753 	return (NULL);
1754 }
1755 
1756 void
1757 nwamd_wlan_monitor_signal(const char *linkname)
1758 {
1759 	pthread_t wifi_thread;
1760 	char *link = strdup(linkname);
1761 
1762 	if (link == NULL) {
1763 		nlog(LOG_ERR, "nwamd_wlan_monitor_signal: out of memory");
1764 		return;
1765 	}
1766 
1767 	nlog(LOG_DEBUG, "nwamd_wlan_monitor_signal: WLAN monitor for %s",
1768 	    link);
1769 
1770 	if (pthread_create(&wifi_thread, NULL, wlan_monitor_signal_thread,
1771 	    link) != 0) {
1772 		nlog(LOG_ERR, "nwamd_wlan_monitor_signal: could not monitor "
1773 		    "link %s", link);
1774 		free(link);
1775 		return;
1776 	}
1777 
1778 	/* detach thread so that it doesn't become a zombie */
1779 	(void) pthread_detach(wifi_thread);
1780 }
1781 
1782 void
1783 nwamd_ncu_handle_link_state_event(nwamd_event_t event)
1784 {
1785 	nwam_event_t evm;
1786 	nwamd_object_t ncu_obj;
1787 	nwamd_ncu_t *ncu;
1788 	nwamd_link_t *link;
1789 
1790 	ncu_obj = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, event->event_object);
1791 	if (ncu_obj == NULL) {
1792 		nlog(LOG_INFO, "nwamd_ncu_handle_link_state_event: no object "
1793 		    "%s", event->event_object);
1794 		nwamd_event_do_not_send(event);
1795 		return;
1796 	}
1797 	ncu = ncu_obj->nwamd_object_data;
1798 	link = &ncu->ncu_link;
1799 	evm = event->event_msg;
1800 
1801 	/*
1802 	 * We ignore link state events for WiFi because it is very flaky.
1803 	 * Instead we use the monitor thread and drive WiFi state changes from
1804 	 * there.
1805 	 */
1806 	if (link->nwamd_link_media == DL_WIFI) {
1807 		nwamd_object_release(ncu_obj);
1808 		return;
1809 	}
1810 
1811 	/*
1812 	 * If it's a link up event and we're not disabled, go online.
1813 	 */
1814 	if (evm->nwe_data.nwe_link_state.nwe_link_up &&
1815 	    ncu_obj->nwamd_object_state != NWAM_STATE_DISABLED) {
1816 
1817 		if (link->nwamd_link_activation_mode ==
1818 		    NWAM_ACTIVATION_MODE_PRIORITIZED) {
1819 			int64_t priority_group;
1820 
1821 			(void) pthread_mutex_lock(&active_ncp_mutex);
1822 			priority_group = current_ncu_priority_group;
1823 			(void) pthread_mutex_unlock(&active_ncp_mutex);
1824 
1825 			/* compare priority groups */
1826 			if (link->nwamd_link_priority_group > priority_group) {
1827 				nlog(LOG_DEBUG,
1828 				    "nwamd_ncu_handle_link_state_event: "
1829 				    "got LINK UP event for priority group "
1830 				    "%lld, less preferred than current %lld, "
1831 				    "ignoring",
1832 				    link->nwamd_link_priority_group,
1833 				    priority_group);
1834 
1835 			} else if (link->nwamd_link_priority_group ==
1836 			    priority_group) {
1837 				nlog(LOG_DEBUG,
1838 				    "nwamd_ncu_handle_link_state_event: "
1839 				    "got LINK UP event for priority group "
1840 				    "%lld, same as current %lld",
1841 				    link->nwamd_link_priority_group,
1842 				    priority_group);
1843 				/*
1844 				 * Change link state to UP.  It will be
1845 				 * propagated to IP state machine.  Only do
1846 				 * the NCU check if and when the interface
1847 				 * NCU is online.
1848 				 */
1849 				nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1850 				    event->event_object,
1851 				    NWAM_STATE_OFFLINE_TO_ONLINE,
1852 				    NWAM_AUX_STATE_UP);
1853 			} else {
1854 				nlog(LOG_DEBUG,
1855 				    "nwamd_ncu_handle_link_state_event: "
1856 				    "got LINK UP event for priority group "
1857 				    "%lld, more preferred than current %lld",
1858 				    link->nwamd_link_priority_group,
1859 				    priority_group);
1860 
1861 				/*
1862 				 * We need to mark the link as up so that when
1863 				 * it is activated we will bring the interface
1864 				 * up.
1865 				 */
1866 				nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1867 				    event->event_object,
1868 				    NWAM_STATE_OFFLINE_TO_ONLINE,
1869 				    NWAM_AUX_STATE_UP);
1870 				nwamd_object_release(ncu_obj);
1871 				nwamd_ncp_deactivate_priority_group
1872 				    (priority_group);
1873 				nwamd_ncp_activate_priority_group
1874 				    (link->nwamd_link_priority_group);
1875 				return;
1876 			}
1877 
1878 		} else if (link->nwamd_link_activation_mode ==
1879 		    NWAM_ACTIVATION_MODE_MANUAL) {
1880 			nlog(LOG_DEBUG, "nwamd_ncu_handle_link_state_event: "
1881 			    "got LINK UP event for manual NCU %s",
1882 			    ncu_obj->nwamd_object_name);
1883 
1884 			nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1885 			    event->event_object, NWAM_STATE_OFFLINE_TO_ONLINE,
1886 			    NWAM_AUX_STATE_UP);
1887 		}
1888 	}
1889 
1890 	/*
1891 	 * If the link is down then start or continue transition down.
1892 	 */
1893 	if (!evm->nwe_data.nwe_link_state.nwe_link_up &&
1894 	    (ncu_obj->nwamd_object_state == NWAM_STATE_ONLINE ||
1895 	    ncu_obj->nwamd_object_state == NWAM_STATE_OFFLINE_TO_ONLINE)) {
1896 
1897 		if (link->nwamd_link_activation_mode ==
1898 		    NWAM_ACTIVATION_MODE_PRIORITIZED) {
1899 			nlog(LOG_DEBUG,
1900 			    "nwamd_ncu_handle_link_state_event: "
1901 			    "got LINK DOWN for priority group %lld",
1902 			    link->nwamd_link_priority_group);
1903 			/* Moving to offline checks priority group */
1904 		} else {
1905 			nlog(LOG_DEBUG, "nwamd_ncu_handle_link_state_event: "
1906 			    "got LINK DOWN event for manual NCU %s",
1907 			    ncu_obj->nwamd_object_name);
1908 		}
1909 		nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU,
1910 		    event->event_object, NWAM_STATE_ONLINE_TO_OFFLINE,
1911 		    NWAM_AUX_STATE_DOWN);
1912 	}
1913 
1914 	nwamd_object_release(ncu_obj);
1915 }
1916