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