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