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