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