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 2009 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * This file contains all the routines to handle wireless (more 29 * accurately, 802.11 "WiFi" family only at this moment) operations. 30 * This is only phase 0 work so the handling is pretty simple. 31 * 32 * When the daemon starts up, for each WiFi interface detected, it'll 33 * spawn a thread doing an access point (AP) scanning. After the scans 34 * finish and if one of the WiFi interfaces is chosen to be active, the 35 * code will send a message to the GUI, which then must gather the results. 36 * 37 * WEP/WPA is supported to connect to those APs which require it. The code 38 * also maintains a list of known WiFi APs in the file KNOWN_WIFI_NETS. 39 * Whenever the code successfully connects to an AP, the AP's ESSID/BSSID will 40 * be added to that file. This file is used in the following way. 41 * 42 * If the AP scan results contain one known AP (plus any number of unknown 43 * APs), the code will automatically connect to that AP without contacting the 44 * GUI. But if the detected signal strength of that one known AP is weaker 45 * than any of the unknown APs, the code will block on the GUI. 46 * 47 * If the AP scan results contain more than one known APs or no known APs, the 48 * GUI is notified. 49 * 50 * Note that not all APs broadcast the Beacon. And some events may 51 * happen during the AP scan such that not all available APs are found. 52 * Thus, the GUI can specify an AP's data. 53 * 54 * The code also periodically (specified by wlan_scan_interval) checks 55 * for the health of the AP connection. If the signal strength of the 56 * connected AP drops below a threshold (specified by wireless_scan_level), 57 * the code will try to do another scan to find out other APs available. 58 * If there is currently no connected AP, a scan will also be done 59 * periodically to look for available APs. In both cases, if there are 60 * new APs, the above AP connection procedure will be performed. 61 * 62 * As a way to deal with the innumerable bugs that seem to plague wireless 63 * interfaces with respect to concurrent operations, we completely exclude all 64 * connect operations on all interfaces when another connect or scan is 65 * running, and exclude all scans on all interfaces when another connect or 66 * scan is running. This is done using wifi_scan_intf. 67 * 68 * Much of the BSSID handling logic in this module is questionable due to 69 * underlying bugs such as CR 6772510. There's likely little that we can do 70 * about this. 71 * 72 * Lock ordering note: wifi_mutex and wifi_init_mutex are not held at the same 73 * time. 74 */ 75 76 #include <unistd.h> 77 #include <ctype.h> 78 #include <stdlib.h> 79 #include <stdio.h> 80 #include <strings.h> 81 #include <syslog.h> 82 #include <limits.h> 83 #include <errno.h> 84 #include <sys/stat.h> 85 #include <syslog.h> 86 #include <pthread.h> 87 #include <sys/wait.h> 88 #include <stropts.h> 89 #include <sys/types.h> 90 #include <fcntl.h> 91 #include <libdladm.h> 92 #include <libdllink.h> 93 #include <libinetutil.h> 94 #include <libgen.h> 95 96 #include "defines.h" 97 #include "structures.h" 98 #include "functions.h" 99 #include "variables.h" 100 101 #define WLAN_ENC(sec) \ 102 ((sec == DLADM_WLAN_SECMODE_WPA ? "WPA" : \ 103 (sec == DLADM_WLAN_SECMODE_WEP ? "WEP" : "none"))) 104 105 #define NEED_ENC(sec) \ 106 (sec == DLADM_WLAN_SECMODE_WPA || sec == DLADM_WLAN_SECMODE_WEP) 107 108 static pthread_mutex_t wifi_mutex; 109 110 typedef enum { 111 ESSID = 0, 112 BSSID, 113 MAX_FIELDS 114 } known_wifi_nets_fields_t; 115 116 /* 117 * List of wireless interfaces; protected by wifi_mutex. 118 */ 119 static struct qelem wi_list; 120 static uint_t wi_link_count; 121 122 /* 123 * Is a wireless interface doing a scan currently? We only allow one 124 * wireless interface to do a scan at any one time. This is to 125 * avoid unnecessary interference. The following variable is used 126 * to store the interface doing the scan. It is protected by 127 * wifi_init_mutex. 128 */ 129 static const char *wifi_scan_intf; 130 static boolean_t connect_running; 131 static pthread_mutex_t wifi_init_mutex = PTHREAD_MUTEX_INITIALIZER; 132 static pthread_cond_t wifi_init_cond = PTHREAD_COND_INITIALIZER; 133 134 /* 135 * Array of wireless LAN entries; protected by wifi_mutex. 136 */ 137 static struct wireless_lan *wlans; 138 static uint_t wireless_lan_count; /* allocated */ 139 static uint_t wireless_lan_used; /* used entries */ 140 static boolean_t new_ap_found; 141 142 static int key_string_to_secobj_value(char *, uint8_t *, uint_t *, 143 dladm_secobj_class_t); 144 static int store_key(struct wireless_lan *); 145 static dladm_wlan_key_t *retrieve_key(const char *, const char *, 146 dladm_secobj_class_t); 147 148 static struct wireless_lan *add_wlan_entry(const char *, const char *, 149 const char *, dladm_wlan_attr_t *); 150 static boolean_t check_wlan(const wireless_if_t *, const char *, const char *, 151 boolean_t); 152 static struct wireless_lan *find_wlan_entry(const char *, const char *, 153 const char *); 154 static void free_wireless_lan(struct wireless_lan *); 155 static return_vals_t get_user_key(struct wireless_lan *); 156 static boolean_t wlan_autoconf(const wireless_if_t *); 157 static boolean_t get_scan_results(void *, dladm_wlan_attr_t *); 158 static int add_known_wifi_nets_file(const char *, const char *); 159 static boolean_t known_wifi_nets_lookup(const char *, const char *, char *); 160 static return_vals_t connect_chosen_lan(struct wireless_lan *, wireless_if_t *); 161 162 #define WIRELESS_LAN_INIT_COUNT 8 163 164 /* 165 * The variable wlan_scan_interval controls the interval in seconds 166 * between periodic scans. 167 */ 168 uint_t wlan_scan_interval = 120; 169 170 /* 171 * The variable wireless_scan_level specifies the lowest signal level 172 * when a periodic wireless scan needs to be done. 173 */ 174 dladm_wlan_strength_t wireless_scan_level = DLADM_WLAN_STRENGTH_VERY_WEAK; 175 176 /* 177 * This controls whether we are strict about matching BSSID in the known wifi 178 * networks file. By default, we're not strict. 179 */ 180 boolean_t strict_bssid; 181 182 void 183 initialize_wireless(void) 184 { 185 pthread_mutexattr_t wifi_mutex_attr; 186 187 (void) pthread_mutexattr_init(&wifi_mutex_attr); 188 (void) pthread_mutexattr_settype(&wifi_mutex_attr, 189 PTHREAD_MUTEX_RECURSIVE); 190 (void) pthread_mutex_init(&wifi_mutex, &wifi_mutex_attr); 191 wi_list.q_forw = wi_list.q_back = &wi_list; 192 } 193 194 void 195 add_wireless_if(const char *ifname) 196 { 197 wireless_if_t *wip; 198 199 if ((wip = calloc(1, sizeof (*wip))) != NULL) { 200 (void) strlcpy(wip->wi_name, ifname, sizeof (wip->wi_name)); 201 (void) dladm_name2info(dld_handle, ifname, &wip->wi_linkid, 202 NULL, NULL, NULL); 203 if (pthread_mutex_lock(&wifi_mutex) == 0) { 204 insque(&wip->wi_links, wi_list.q_back); 205 wi_link_count++; 206 (void) pthread_mutex_unlock(&wifi_mutex); 207 } else { 208 free(wip); 209 } 210 } 211 } 212 213 static wireless_if_t * 214 find_wireless_if(const char *ifname) 215 { 216 wireless_if_t *wip; 217 218 for (wip = (wireless_if_t *)wi_list.q_forw; 219 wip != (wireless_if_t *)&wi_list; 220 wip = (wireless_if_t *)wip->wi_links.q_forw) { 221 if (strcmp(wip->wi_name, ifname) == 0) 222 return (wip); 223 } 224 return (NULL); 225 } 226 227 void 228 remove_wireless_if(const char *ifname) 229 { 230 wireless_if_t *wip; 231 232 if (pthread_mutex_lock(&wifi_mutex) == 0) { 233 if ((wip = find_wireless_if(ifname)) != NULL) { 234 remque(&wip->wi_links); 235 wi_link_count--; 236 } 237 (void) pthread_mutex_unlock(&wifi_mutex); 238 free(wip); 239 } 240 } 241 242 /* 243 * wlan is expected to be non-NULL. 244 */ 245 static return_vals_t 246 get_user_key(struct wireless_lan *wlan) 247 { 248 dladm_secobj_class_t class; 249 250 /* 251 * First, test if we have key stored as secobj. If so, 252 * no need to prompt for it. 253 */ 254 class = (wlan->attrs.wa_secmode == DLADM_WLAN_SECMODE_WEP ? 255 DLADM_SECOBJ_CLASS_WEP : DLADM_SECOBJ_CLASS_WPA); 256 wlan->cooked_key = retrieve_key(wlan->essid, wlan->bssid, class); 257 if (wlan->cooked_key != NULL) { 258 dprintf("get_user_key: retrieve_key() returns non NULL"); 259 return (SUCCESS); 260 } else if (request_wlan_key(wlan)) { 261 return (WAITING); 262 } else { 263 return (FAILURE); 264 } 265 } 266 267 /* 268 * This function assumes that wifi_mutex is held. If bssid is specified, then 269 * an exact match is returned. If it's not specified, then the best match is 270 * returned. 271 */ 272 static struct wireless_lan * 273 find_wlan_entry(const char *ifname, const char *essid, const char *bssid) 274 { 275 struct wireless_lan *wlan, *best; 276 277 best = NULL; 278 for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) { 279 if (strcmp(wlan->essid, essid) != 0 || 280 strcmp(wlan->wl_if_name, ifname) != 0) 281 continue; 282 if (bssid[0] == '\0') { 283 if (best == NULL || 284 wlan->attrs.wa_strength > best->attrs.wa_strength) 285 best = wlan; 286 } else { 287 if (strcmp(wlan->bssid, bssid) == 0) 288 return (wlan); 289 } 290 } 291 return (best); 292 } 293 294 static void 295 free_wireless_lan(struct wireless_lan *wlp) 296 { 297 free(wlp->essid); 298 wlp->essid = NULL; 299 /* empty string is not allocated */ 300 if (wlp->bssid != NULL && wlp->bssid[0] != '\0') 301 free(wlp->bssid); 302 wlp->bssid = NULL; 303 free(wlp->signal_strength); 304 wlp->signal_strength = NULL; 305 free(wlp->raw_key); 306 wlp->raw_key = NULL; 307 free(wlp->cooked_key); 308 wlp->cooked_key = NULL; 309 } 310 311 /* 312 * This function assumes that wifi_mutex is held. 313 */ 314 static struct wireless_lan * 315 add_wlan_entry(const char *ifname, const char *essid, const char *bssid, 316 dladm_wlan_attr_t *attrp) 317 { 318 char strength[DLADM_STRSIZE]; 319 struct wireless_lan *wlan; 320 321 if (wireless_lan_used == wireless_lan_count) { 322 int newcnt; 323 324 newcnt = (wireless_lan_count == 0) ? 325 WIRELESS_LAN_INIT_COUNT : wireless_lan_count * 2; 326 wlan = realloc(wlans, newcnt * sizeof (*wlans)); 327 if (wlan == NULL) { 328 syslog(LOG_ERR, "add_wlan_entry: realloc failed"); 329 return (NULL); 330 } 331 wireless_lan_count = newcnt; 332 wlans = wlan; 333 } 334 335 (void) dladm_wlan_strength2str(&attrp->wa_strength, strength); 336 337 wlan = wlans + wireless_lan_used; 338 (void) memset(wlan, 0, sizeof (*wlan)); 339 wlan->attrs = *attrp; 340 wlan->essid = strdup(essid); 341 /* do not do allocation for zero-length */ 342 wlan->bssid = *bssid == '\0' ? "" : strdup(bssid); 343 wlan->signal_strength = strdup(strength); 344 (void) strlcpy(wlan->wl_if_name, ifname, sizeof (wlan->wl_if_name)); 345 wlan->scanned = B_TRUE; 346 if (wlan->essid == NULL || wlan->bssid == NULL || 347 wlan->signal_strength == NULL) { 348 syslog(LOG_ERR, "add_wlan_entry: strdup failed"); 349 free_wireless_lan(wlan); 350 return (NULL); 351 } 352 wireless_lan_used++; 353 new_ap_found = B_TRUE; 354 return (wlan); 355 } 356 357 /* 358 * Remove entries that are no longer seen on the network. The caller does not 359 * hold wifi_mutex, but is the only thread that can modify the wlan list. 360 * 361 * Retain connected entries, as lack of visibility in a scan may just be a 362 * temporary condition (driver problem) and may not reflect an actual 363 * disconnect, but only if there are no scanned connected entries. 364 */ 365 static boolean_t 366 clear_unscanned_entries(const char *ifname) 367 { 368 struct wireless_lan *wlan, *wlput; 369 boolean_t has_unscanned_connected; 370 boolean_t copied_scanned_connected; 371 uint_t dropcnt; 372 373 if (pthread_mutex_lock(&wifi_mutex) != 0) 374 return (B_FALSE); 375 wlput = wlans; 376 dropcnt = 0; 377 has_unscanned_connected = copied_scanned_connected = B_FALSE; 378 for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) { 379 if (strcmp(ifname, wlan->wl_if_name) != 0) { 380 if (wlput != wlan) 381 *wlput = *wlan; 382 wlput++; 383 } else if (wlan->scanned) { 384 if (wlan->connected) 385 copied_scanned_connected = B_TRUE; 386 if (wlput != wlan) 387 *wlput = *wlan; 388 wlput++; 389 } else { 390 if (wlan->connected) 391 has_unscanned_connected = B_TRUE; 392 dprintf("dropping unseen AP %s %s", wlan->essid, 393 wlan->bssid); 394 dropcnt++; 395 free_wireless_lan(wlan); 396 } 397 } 398 if (has_unscanned_connected && !copied_scanned_connected) { 399 for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) { 400 if (strcmp(ifname, wlan->wl_if_name) == 0 && 401 wlan->connected) { 402 dprintf("keeping unscanned but connected AP " 403 "%s %s", wlan->essid, wlan->bssid); 404 if (wlput != wlan) 405 *wlput = *wlan; 406 wlput++; 407 dropcnt--; 408 } 409 } 410 } 411 wireless_lan_used = wlput - wlans; 412 (void) pthread_mutex_unlock(&wifi_mutex); 413 return (dropcnt != 0); 414 } 415 416 /* 417 * Verify if a WiFi NIC is associated with the given ESSID and BSSID. If the 418 * given ESSID is NULL, and if the NIC is already connected, return true. 419 * Otherwise, 420 * 421 * 1. If the NIC is associated with the given ESSID/BSSID, return true. 422 * 2. If the NIC is not associated with any AP, return false. 423 * 3. If the NIC is associated with a different AP, tear down IP interface, 424 * tell the driver to disassociate with AP, and then return false. 425 */ 426 static boolean_t 427 check_wlan(const wireless_if_t *wip, const char *exp_essid, 428 const char *exp_bssid, boolean_t sendevent) 429 { 430 dladm_wlan_linkattr_t attr; 431 dladm_status_t status; 432 char cur_essid[DLADM_STRSIZE]; 433 char cur_bssid[DLADM_STRSIZE]; 434 char errmsg[DLADM_STRSIZE]; 435 436 status = dladm_wlan_get_linkattr(dld_handle, wip->wi_linkid, &attr); 437 if (status != DLADM_STATUS_OK) { 438 dprintf("check_wlan: dladm_wlan_get_linkattr() for %s " 439 "failed: %s", wip->wi_name, 440 dladm_status2str(status, errmsg)); 441 return (B_FALSE); 442 } 443 if (attr.la_status == DLADM_WLAN_LINK_DISCONNECTED) 444 return (B_FALSE); 445 446 /* If we're expecting "any" connection, then we're done. */ 447 if (exp_essid == NULL) 448 return (B_TRUE); 449 450 /* Is the NIC associated with the expected access point? */ 451 (void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid, cur_essid); 452 if (strcmp(cur_essid, exp_essid) != 0) { 453 dprintf("wrong ESSID: have %s expect %s; taking down", 454 cur_essid, exp_essid); 455 goto unexpected; 456 } 457 458 if (exp_bssid == NULL) 459 return (B_TRUE); 460 461 (void) dladm_wlan_bssid2str(&attr.la_wlan_attr.wa_bssid, cur_bssid); 462 if (strcmp(cur_bssid, exp_bssid) == 0) 463 return (B_TRUE); 464 dprintf("wrong BSSID: have %s expect %s; taking down", 465 cur_bssid, exp_bssid); 466 467 unexpected: 468 if (sendevent) { 469 /* If not, then shut the interface down normally */ 470 (void) np_queue_add_event(EV_TAKEDOWN, wip->wi_name); 471 (void) dladm_wlan_disconnect(dld_handle, wip->wi_linkid); 472 } 473 return (B_FALSE); 474 } 475 476 /* 477 * Examine all WLANs associated with an interface, verify the expected WLAN, 478 * and update the 'connected' attribute appropriately. The caller holds 479 * wifi_mutex and deals with the 'known' flag. If the expected WLAN is NULL, 480 * then we expect to be connected to just "any" (autoconf) network. 481 */ 482 static boolean_t 483 update_connected_wlan(wireless_if_t *wip, struct wireless_lan *exp_wlan) 484 { 485 dladm_wlan_linkattr_t attr; 486 struct wireless_lan *wlan, *lastconn, *newconn; 487 char essid[DLADM_STRSIZE]; 488 char bssid[DLADM_STRSIZE]; 489 boolean_t connected, wasconn; 490 491 if (dladm_wlan_get_linkattr(dld_handle, wip->wi_linkid, &attr) != 492 DLADM_STATUS_OK) 493 attr.la_status = DLADM_WLAN_LINK_DISCONNECTED; 494 if (attr.la_status == DLADM_WLAN_LINK_CONNECTED) { 495 (void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid, essid); 496 (void) dladm_wlan_bssid2str(&attr.la_wlan_attr.wa_bssid, bssid); 497 connected = B_TRUE; 498 wip->wi_wireless_done = B_TRUE; 499 dprintf("update: %s reports connection to %s %s", wip->wi_name, 500 essid, bssid); 501 } else { 502 connected = B_FALSE; 503 dprintf("update: %s is currently unconnected", wip->wi_name); 504 } 505 506 /* 507 * First, verify that if we're connected, then we should be and that 508 * we're connected to the expected AP. 509 */ 510 if (exp_wlan != NULL) { 511 /* 512 * If we're connected to the wrong one, then disconnect. Note: 513 * we'd like to verify BSSID, but we cannot due to CR 6772510. 514 */ 515 if (connected && strcmp(exp_wlan->essid, essid) != 0) { 516 dprintf("update: wrong AP on %s; expected %s %s", 517 exp_wlan->wl_if_name, exp_wlan->essid, 518 exp_wlan->bssid); 519 (void) dladm_wlan_disconnect(dld_handle, 520 wip->wi_linkid); 521 connected = B_FALSE; 522 } 523 /* If we're not in the expected state, then report disconnect */ 524 if (exp_wlan->connected != connected) { 525 exp_wlan->connected = B_FALSE; 526 if (connected) { 527 dprintf("update: unexpected connection to %s " 528 "%s; clearing", essid, bssid); 529 (void) dladm_wlan_disconnect(dld_handle, 530 wip->wi_linkid); 531 } else { 532 dprintf("update: not connected to %s %s as " 533 "expected", exp_wlan->essid, 534 exp_wlan->bssid); 535 report_wlan_disconnect(exp_wlan); 536 } 537 connected = B_FALSE; 538 } 539 } 540 541 /* 542 * State is now known to be good, so make the list entries match. 543 */ 544 wasconn = B_FALSE; 545 lastconn = newconn = NULL; 546 for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) { 547 if (strcmp(wlan->wl_if_name, wip->wi_name) != 0) 548 continue; 549 /* missing bssid check */ 550 if (connected && strcmp(wlan->essid, essid) == 0) { 551 wasconn = wlan->connected; 552 wlan->connected = connected; 553 newconn = wlan; 554 } else if (wlan->connected) { 555 lastconn = wlan; 556 wlan->connected = B_FALSE; 557 } 558 } 559 if (newconn == NULL && connected) { 560 newconn = add_wlan_entry(wip->wi_name, essid, bssid, 561 &attr.la_wlan_attr); 562 if (newconn != NULL) 563 newconn->connected = connected; 564 } 565 if (lastconn != NULL) 566 report_wlan_disconnect(lastconn); 567 if (newconn != NULL && !wasconn && connected) 568 report_wlan_connected(newconn); 569 return (connected); 570 } 571 572 /* 573 * If there is already a scan or connect in progress, defer until the operation 574 * is done to avoid radio interference *and* significant driver bugs. 575 * 576 * Returns B_TRUE when the lock is taken and the caller must call 577 * scanconnect_exit. Returns B_FALSE when lock not taken; caller must not call 578 * scanconnect_exit. 579 * 580 * If we happen to be doing a scan, and the interface doing the scan is the 581 * same as the one requesting a new scan, then wait for it to finish, and then 582 * report that we're done by returning B_FALSE (no lock taken). 583 */ 584 static boolean_t 585 scanconnect_entry(const char *ifname, boolean_t is_connect) 586 { 587 boolean_t already_done; 588 589 if (pthread_mutex_lock(&wifi_init_mutex) != 0) 590 return (B_FALSE); 591 already_done = B_FALSE; 592 while (wifi_scan_intf != NULL) { 593 dprintf("%s in progress on %s; blocking %s of %s", 594 connect_running ? "connect" : "scan", wifi_scan_intf, 595 is_connect ? "connect" : "scan", ifname); 596 if (!is_connect && !connect_running && 597 strcmp(wifi_scan_intf, ifname) == 0) 598 already_done = B_TRUE; 599 (void) pthread_cond_wait(&wifi_init_cond, &wifi_init_mutex); 600 if (already_done || shutting_down) { 601 (void) pthread_mutex_unlock(&wifi_init_mutex); 602 return (B_FALSE); 603 } 604 } 605 dprintf("now exclusively %s on %s", 606 is_connect ? "connecting" : "scanning", ifname); 607 wifi_scan_intf = ifname; 608 connect_running = is_connect; 609 (void) pthread_mutex_unlock(&wifi_init_mutex); 610 return (B_TRUE); 611 } 612 613 static void 614 scanconnect_exit(void) 615 { 616 (void) pthread_mutex_lock(&wifi_init_mutex); 617 dprintf("done exclusively %s on %s", 618 connect_running ? "connecting" : "scanning", wifi_scan_intf); 619 wifi_scan_intf = NULL; 620 (void) pthread_cond_broadcast(&wifi_init_cond); 621 (void) pthread_mutex_unlock(&wifi_init_mutex); 622 } 623 624 /* 625 * Return B_TRUE if we're in the midst of connecting on a given wireless 626 * interface. We shouldn't try to take such an interface down. 627 */ 628 static boolean_t 629 connecting_on(const char *ifname) 630 { 631 boolean_t in_progress; 632 633 if (pthread_mutex_lock(&wifi_init_mutex) != 0) 634 return (B_FALSE); 635 in_progress = (wifi_scan_intf != NULL && connect_running && 636 strcmp(ifname, wifi_scan_intf) == 0); 637 (void) pthread_mutex_unlock(&wifi_init_mutex); 638 return (in_progress); 639 } 640 641 /* 642 * Terminate all waiting transient threads as soon as possible. This assumes 643 * that the shutting_down flag has already been set. 644 */ 645 void 646 terminate_wireless(void) 647 { 648 (void) pthread_cond_broadcast(&wifi_init_cond); 649 } 650 651 /* 652 * Given a wireless interface, use it to scan for available networks. The 653 * caller must not hold wifi_mutex. 654 */ 655 static void 656 scan_wireless_nets(const char *ifname) 657 { 658 boolean_t dropped; 659 boolean_t new_found; 660 dladm_status_t status; 661 int i; 662 datalink_id_t linkid; 663 wireless_if_t *wip; 664 665 /* 666 * Wait for scan/connect to finish, and return if error or if this 667 * interface is already done. 668 */ 669 if (!scanconnect_entry(ifname, B_FALSE)) 670 return; 671 672 /* Grab the linkid from the wireless interface */ 673 if (pthread_mutex_lock(&wifi_mutex) != 0) 674 goto scan_end; 675 if ((wip = find_wireless_if(ifname)) == NULL) { 676 (void) pthread_mutex_unlock(&wifi_mutex); 677 dprintf("aborted scan on %s; unable to locate interface", 678 ifname); 679 goto scan_end; 680 } 681 linkid = wip->wi_linkid; 682 (void) pthread_mutex_unlock(&wifi_mutex); 683 684 /* 685 * Since only one scan is allowed at any one time, and only scans can 686 * modify the list, there's no need to grab a lock in checking 687 * wireless_lan_used or the wlans list itself, or for the new_ap_found 688 * global. 689 * 690 * All other threads must hold the mutex when reading this data, and 691 * this thread must hold the mutex only when writing portions that 692 * those other threads may read. 693 */ 694 for (i = 0; i < wireless_lan_used; i++) 695 wlans[i].scanned = B_FALSE; 696 new_ap_found = B_FALSE; 697 dprintf("starting scan on %s", ifname); 698 status = dladm_wlan_scan(dld_handle, linkid, (char *)ifname, 699 get_scan_results); 700 if (status == DLADM_STATUS_OK) { 701 dropped = clear_unscanned_entries(ifname); 702 } else { 703 dropped = B_FALSE; 704 syslog(LOG_NOTICE, "cannot scan link '%s'", ifname); 705 } 706 707 scan_end: 708 /* Need to sample this global before clearing out scan lock */ 709 new_found = new_ap_found; 710 711 /* 712 * Due to common driver bugs, it's necessary to check the state of the 713 * interface right after doing a scan. If it's connected and we didn't 714 * expect it to be, or if we're accidentally connected to the wrong AP, 715 * then disconnect now and reconnect. 716 */ 717 if (pthread_mutex_lock(&wifi_mutex) == 0) { 718 if ((wip = find_wireless_if(ifname)) != NULL) { 719 dladm_wlan_linkattr_t attr; 720 struct wireless_lan *wlan; 721 char essid[DLADM_STRSIZE]; 722 char bssid[DLADM_STRSIZE]; 723 boolean_t connected; 724 int retries = 0; 725 726 wip->wi_scan_running = B_FALSE; 727 728 /* 729 * This is awful, but some wireless drivers 730 * (particularly 'ath') will erroneously report 731 * "disconnected" if queried right after a scan. If we 732 * see 'down' reported here, we retry a few times to 733 * make sure it's really down. 734 */ 735 while (retries++ < 4) { 736 if (dladm_wlan_get_linkattr(dld_handle, 737 wip->wi_linkid, &attr) != DLADM_STATUS_OK) 738 attr.la_status = 739 DLADM_WLAN_LINK_DISCONNECTED; 740 else if (attr.la_status == 741 DLADM_WLAN_LINK_CONNECTED) 742 break; 743 } 744 if (attr.la_status == DLADM_WLAN_LINK_CONNECTED) { 745 (void) dladm_wlan_essid2str( 746 &attr.la_wlan_attr.wa_essid, essid); 747 (void) dladm_wlan_bssid2str( 748 &attr.la_wlan_attr.wa_bssid, bssid); 749 connected = B_TRUE; 750 dprintf("scan: %s reports connection to %s " 751 "%s", ifname, essid, bssid); 752 } else { 753 connected = B_FALSE; 754 dprintf("scan: %s is currently unconnected", 755 ifname); 756 } 757 /* Disconnect from wrong AP first */ 758 for (wlan = wlans; wlan < wlans + wireless_lan_used; 759 wlan++) { 760 if (strcmp(wlan->wl_if_name, ifname) != 0) 761 continue; 762 /* missing bssid check */ 763 if (strcmp(wlan->essid, essid) == 0) { 764 /* 765 * This is the one we are currently 766 * connected to. See if we should be 767 * here. 768 */ 769 if (!connected || !wlan->connected) 770 (void) dladm_wlan_disconnect( 771 dld_handle, linkid); 772 break; 773 } 774 } 775 /* Connect to right AP by reporting disconnect */ 776 for (wlan = wlans; wlan < wlans + wireless_lan_used; 777 wlan++) { 778 if (strcmp(wlan->wl_if_name, ifname) != 0) 779 continue; 780 if (wlan->connected) { 781 /* missing bssid check */ 782 if (connected && 783 strcmp(wlan->essid, essid) == 0) 784 break; 785 /* 786 * We weren't where we were supposed to 787 * be. Try to reconnect now. 788 */ 789 (void) np_queue_add_event(EV_LINKDISC, 790 ifname); 791 } 792 } 793 } 794 (void) pthread_mutex_unlock(&wifi_mutex); 795 } 796 797 scanconnect_exit(); 798 799 if (status == DLADM_STATUS_OK) 800 report_scan_complete(ifname, dropped || new_found, wlans, 801 wireless_lan_used); 802 803 if (new_found) { 804 dprintf("new AP added: %s", ifname); 805 (void) np_queue_add_event(EV_NEWAP, ifname); 806 } 807 } 808 809 /* 810 * Rescan all wireless interfaces. This routine intentionally does not hold 811 * wifi_mutex during the scan, as scans can take a long time to accomplish, and 812 * there may be more than one wireless interface. The counter is used to make 813 * sure that we don't run "forever" if the list is changing quickly. 814 */ 815 static void 816 rescan_wifi_no_lock(void) 817 { 818 uint_t cnt = 0; 819 wireless_if_t *wip; 820 char ifname[LIFNAMSIZ]; 821 822 if (pthread_mutex_lock(&wifi_mutex) != 0) 823 return; 824 wip = (wireless_if_t *)wi_list.q_forw; 825 while (cnt++ < wi_link_count && wip != (wireless_if_t *)&wi_list) { 826 (void) strlcpy(ifname, wip->wi_name, sizeof (ifname)); 827 dprintf("periodic wireless scan: %s", ifname); 828 /* Even less than "very weak" */ 829 wip->wi_strength = 0; 830 wip->wi_scan_running = B_TRUE; 831 (void) pthread_mutex_unlock(&wifi_mutex); 832 833 scan_wireless_nets(ifname); 834 835 if (pthread_mutex_lock(&wifi_mutex) != 0) 836 return; 837 if ((wip = find_wireless_if(ifname)) == NULL) 838 wip = (wireless_if_t *)&wi_list; 839 else 840 wip = (wireless_if_t *)wip->wi_links.q_forw; 841 } 842 (void) pthread_mutex_unlock(&wifi_mutex); 843 } 844 845 /* 846 * This thread is given the name of the interface to scan, and must free that 847 * name when done. 848 */ 849 static void * 850 scan_thread(void *arg) 851 { 852 char *ifname = arg; 853 854 scan_wireless_nets(ifname); 855 free(ifname); 856 857 return (NULL); 858 } 859 860 /* 861 * Launch a thread to scan the given wireless interface. We copy the interface 862 * name over to allocated storage because it's not possible to hand off a lock 863 * on the interface list to the new thread, and the caller's storage (our input 864 * argument) isn't guaranteed to be stable after we return to the caller. 865 */ 866 int 867 launch_wireless_scan(const char *ifname) 868 { 869 int retv; 870 wireless_if_t *wip; 871 pthread_t if_thr; 872 pthread_attr_t attr; 873 char *winame; 874 875 if ((winame = strdup(ifname)) == NULL) 876 return (ENOMEM); 877 878 if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0) { 879 free(winame); 880 return (retv); 881 } 882 883 if ((wip = find_wireless_if(ifname)) == NULL) { 884 retv = ENXIO; 885 } else if (wip->wi_scan_running) { 886 retv = EINPROGRESS; 887 } else { 888 (void) pthread_attr_init(&attr); 889 (void) pthread_attr_setdetachstate(&attr, 890 PTHREAD_CREATE_DETACHED); 891 retv = pthread_create(&if_thr, &attr, scan_thread, winame); 892 if (retv == 0) 893 wip->wi_scan_running = B_TRUE; 894 } 895 (void) pthread_mutex_unlock(&wifi_mutex); 896 897 /* If thread not started, then discard the name. */ 898 if (retv != 0) 899 free(winame); 900 901 return (retv); 902 } 903 904 /* 905 * Caller does not hold wifi_mutex. 906 */ 907 static boolean_t 908 get_scan_results(void *arg, dladm_wlan_attr_t *attrp) 909 { 910 const char *ifname = arg; 911 wireless_if_t *wip; 912 struct wireless_lan *wlan; 913 char essid_name[DLADM_STRSIZE]; 914 char bssid_name[DLADM_STRSIZE]; 915 boolean_t retv; 916 917 (void) dladm_wlan_essid2str(&attrp->wa_essid, essid_name); 918 (void) dladm_wlan_bssid2str(&attrp->wa_bssid, bssid_name); 919 920 /* 921 * Check whether ESSID is "hidden". 922 * If so try to substitute it with the ESSID from the 923 * known_wifi_nets with the same BSSID 924 */ 925 if (essid_name[0] == '\0') { 926 if (known_wifi_nets_lookup(essid_name, bssid_name, 927 essid_name) && 928 dladm_wlan_str2essid(essid_name, &attrp->wa_essid) == 929 DLADM_STATUS_OK) { 930 dprintf("Using ESSID %s with BSSID %s", 931 essid_name, bssid_name); 932 } 933 } 934 935 if (pthread_mutex_lock(&wifi_mutex) != 0) 936 return (B_FALSE); 937 938 if ((wip = find_wireless_if(ifname)) == NULL) { 939 (void) pthread_mutex_unlock(&wifi_mutex); 940 return (B_FALSE); 941 } 942 943 /* Remember the strongest we encounter */ 944 if (attrp->wa_strength > wip->wi_strength) 945 wip->wi_strength = attrp->wa_strength; 946 947 wlan = find_wlan_entry(ifname, essid_name, bssid_name); 948 if (wlan != NULL) { 949 if (wlan->rescan) 950 new_ap_found = B_TRUE; 951 wlan->rescan = B_FALSE; 952 wlan->scanned = B_TRUE; 953 wlan->attrs = *attrp; 954 retv = B_TRUE; 955 } else if ((wlan = add_wlan_entry(ifname, essid_name, bssid_name, 956 attrp)) != NULL) { 957 /* search cannot return NULL at this point due to add */ 958 wlan->connected = 959 find_wlan_entry(ifname, essid_name, "")->connected; 960 retv = B_TRUE; 961 } else { 962 retv = B_FALSE; 963 } 964 (void) pthread_mutex_unlock(&wifi_mutex); 965 return (retv); 966 } 967 968 /* 969 * This is called when IP reports that the link layer is down. It just 970 * verifies that we're still connected as expected. If not, then cover for the 971 * known driver bugs (by disconnecting) and send an event so that we'll attempt 972 * to recover. No scan is done; if a scan is needed, we'll do one the next 973 * time the timer pops. 974 * 975 * Note that we don't retry in case of error. Since IP has reported the 976 * interface as down, the best case here is that we detect a link failure and 977 * start the connection process over again. 978 */ 979 void 980 wireless_verify(const char *ifname) 981 { 982 datalink_id_t linkid; 983 dladm_wlan_linkattr_t attr; 984 wireless_if_t *wip; 985 struct wireless_lan *wlan; 986 boolean_t is_failure; 987 988 /* 989 * If these calls fail, it means that the wireless link is down. 990 */ 991 if (dladm_name2info(dld_handle, ifname, &linkid, NULL, NULL, NULL) != 992 DLADM_STATUS_OK || 993 dladm_wlan_get_linkattr(dld_handle, linkid, &attr) != 994 DLADM_STATUS_OK) { 995 attr.la_status = DLADM_WLAN_LINK_DISCONNECTED; 996 } 997 998 /* 999 * If the link is down, then work around a known driver bug (by forcing 1000 * disconnect), and then deliver an event so that the state machine can 1001 * retry. 1002 */ 1003 if (attr.la_status != DLADM_WLAN_LINK_CONNECTED) { 1004 if (connecting_on(ifname)) 1005 return; 1006 is_failure = B_TRUE; 1007 if (pthread_mutex_lock(&wifi_mutex) == 0) { 1008 if ((wip = find_wireless_if(ifname)) != NULL) { 1009 /* 1010 * Link down while waiting for user to supply 1011 * key is *not* a failure case. 1012 */ 1013 if (!wip->wi_wireless_done && 1014 wip->wi_need_key) { 1015 is_failure = B_FALSE; 1016 } else { 1017 wip->wi_wireless_done = B_FALSE; 1018 wip->wi_need_key = B_FALSE; 1019 } 1020 } 1021 if (is_failure) { 1022 for (wlan = wlans; 1023 wlan < wlans + wireless_lan_used; wlan++) { 1024 if (strcmp(wlan->wl_if_name, ifname) == 1025 0) { 1026 if (wlan->connected) 1027 report_wlan_disconnect( 1028 wlan); 1029 wlan->connected = B_FALSE; 1030 } 1031 } 1032 } 1033 (void) pthread_mutex_unlock(&wifi_mutex); 1034 } 1035 if (is_failure) { 1036 dprintf("wireless check indicates disconnect"); 1037 (void) dladm_wlan_disconnect(dld_handle, linkid); 1038 (void) np_queue_add_event(EV_LINKDISC, ifname); 1039 } 1040 } 1041 } 1042 1043 /* ARGSUSED */ 1044 void * 1045 periodic_wireless_scan(void *arg) 1046 { 1047 for (;;) { 1048 int ret, intv; 1049 dladm_wlan_linkattr_t attr; 1050 char ifname[LIFNAMSIZ]; 1051 libnwam_interface_type_t ift; 1052 datalink_id_t linkid; 1053 char essid[DLADM_STRSIZE]; 1054 struct wireless_lan *wlan; 1055 1056 /* 1057 * Stop the scanning process if the user changes the interval 1058 * to zero dynamically. Reset the thread ID to a known-invalid 1059 * value. (Copy to a local variable to avoid race condition in 1060 * case SIGINT hits between this test and the call to poll().) 1061 */ 1062 if ((intv = wlan_scan_interval) == 0) { 1063 dprintf("periodic wireless scan halted"); 1064 break; 1065 } 1066 1067 ret = poll(NULL, 0, intv * MILLISEC); 1068 if (ret == -1) { 1069 if (errno == EINTR) 1070 continue; 1071 syslog(LOG_INFO, "periodic_wireless_scan: poll failed"); 1072 break; 1073 } 1074 1075 /* 1076 * Just one more check before doing a scan that might now be 1077 * unwanted 1078 */ 1079 if (wlan_scan_interval == 0) { 1080 dprintf("periodic wireless scan halted"); 1081 break; 1082 } 1083 1084 /* Get current profile name, if any */ 1085 llp_get_name_and_type(ifname, sizeof (ifname), &ift); 1086 1087 /* 1088 * We do a scan if 1089 * 1090 * 1. There is no active profile. Or 1091 * 2. Profile is wireless and we're not connected to the AP. Or 1092 * 3. The signal strength falls below a certain specified level. 1093 */ 1094 if (ifname[0] != '\0') { 1095 if (ift != IF_WIRELESS) 1096 continue; 1097 1098 /* 1099 * If these things fail, it means that our wireless 1100 * link isn't viable. Proceed in that way. 1101 */ 1102 if (dladm_name2info(dld_handle, ifname, &linkid, NULL, 1103 NULL, NULL) != DLADM_STATUS_OK || 1104 dladm_wlan_get_linkattr(dld_handle, linkid, 1105 &attr) != DLADM_STATUS_OK) { 1106 attr.la_status = DLADM_WLAN_LINK_DISCONNECTED; 1107 attr.la_wlan_attr.wa_strength = 0; 1108 } 1109 1110 if (attr.la_status == DLADM_WLAN_LINK_CONNECTED && 1111 attr.la_wlan_attr.wa_strength > 1112 wireless_scan_level) { 1113 /* 1114 * Double-check the ESSID. Some drivers 1115 * (notably 'iwh') have a habit of randomly 1116 * reconnecting themselves to APs that you 1117 * never requested. 1118 */ 1119 (void) dladm_wlan_essid2str( 1120 &attr.la_wlan_attr.wa_essid, essid); 1121 if (pthread_mutex_lock(&wifi_mutex) != 0) 1122 continue; 1123 for (wlan = wlans; 1124 wlan < wlans + wireless_lan_used; wlan++) { 1125 if (wlan->connected && 1126 strcmp(wlan->wl_if_name, ifname) == 1127 0) 1128 break; 1129 } 1130 if (wlan >= wlans + wireless_lan_used || 1131 strcmp(wlan->essid, essid) == 0) { 1132 (void) pthread_mutex_unlock( 1133 &wifi_mutex); 1134 continue; 1135 } 1136 dprintf("%s is connected to %s instead of %s", 1137 ifname, essid, wlan->essid); 1138 (void) pthread_mutex_unlock(&wifi_mutex); 1139 } 1140 } 1141 1142 /* Rescan the wireless interfaces */ 1143 rescan_wifi_no_lock(); 1144 1145 if (ifname[0] != '\0') { 1146 wireless_if_t *wip; 1147 1148 /* 1149 * If we're still connected and there's nothing better 1150 * around, then there's no point in switching now. 1151 */ 1152 if (pthread_mutex_lock(&wifi_mutex) != 0) 1153 continue; 1154 if ((wip = find_wireless_if(ifname)) != NULL) { 1155 if (attr.la_status == 1156 DLADM_WLAN_LINK_CONNECTED && 1157 wip->wi_strength <= 1158 attr.la_wlan_attr.wa_strength) { 1159 (void) pthread_mutex_unlock(& 1160 wifi_mutex); 1161 continue; 1162 } 1163 wip->wi_wireless_done = B_FALSE; 1164 wip->wi_need_key = B_FALSE; 1165 } 1166 (void) pthread_mutex_unlock(&wifi_mutex); 1167 1168 /* 1169 * Try to work around known driver bugs: if the driver 1170 * says we're disconnected, then tell it to disconnect 1171 * for sure. 1172 */ 1173 (void) dladm_wlan_disconnect(dld_handle, linkid); 1174 1175 /* 1176 * Tell the state machine that we've lost this link so 1177 * that it can do something about the problem. 1178 */ 1179 (void) np_queue_add_event( 1180 (attr.la_status == DLADM_WLAN_LINK_CONNECTED ? 1181 EV_LINKFADE : EV_LINKDISC), ifname); 1182 } 1183 } 1184 scan = 0; 1185 (void) pthread_detach(pthread_self()); 1186 return (NULL); 1187 } 1188 1189 /* 1190 * Below are functions used to handle storage/retrieval of keys 1191 * for a given WLAN. The keys are stored/retrieved using dladm_set_secobj() 1192 * and dladm_get_secobj(). 1193 */ 1194 1195 /* 1196 * Convert key hexascii string to raw secobj value. This 1197 * code is very similar to convert_secobj() in dladm.c, it would 1198 * be good to have a libdladm function to convert values. 1199 */ 1200 static int 1201 key_string_to_secobj_value(char *buf, uint8_t *obj_val, uint_t *obj_lenp, 1202 dladm_secobj_class_t class) 1203 { 1204 size_t buf_len = strlen(buf); 1205 1206 dprintf("before: key_string_to_secobj_value: buf_len = %d", buf_len); 1207 if (buf_len == 0) { 1208 /* length zero means "delete" */ 1209 return (0); 1210 } 1211 1212 if (buf[buf_len - 1] == '\n') 1213 buf[--buf_len] = '\0'; 1214 1215 dprintf("after: key_string_to_secobj_value: buf_len = %d", buf_len); 1216 1217 if (class == DLADM_SECOBJ_CLASS_WPA) { 1218 /* 1219 * Per IEEE802.11i spec, the Pre-shared key (PSK) length should 1220 * be between 8 and 63. 1221 */ 1222 if (buf_len < 8 || buf_len > 63) { 1223 syslog(LOG_ERR, 1224 "key_string_to_secobj_value:" 1225 " invalid WPA key length: buf_len = %d", buf_len); 1226 return (-1); 1227 } 1228 (void) memcpy(obj_val, buf, (uint_t)buf_len); 1229 *obj_lenp = buf_len; 1230 return (0); 1231 } 1232 1233 switch (buf_len) { 1234 case 5: /* ASCII key sizes */ 1235 case 13: 1236 (void) memcpy(obj_val, buf, (uint_t)buf_len); 1237 *obj_lenp = (uint_t)buf_len; 1238 break; 1239 case 10: 1240 case 26: /* Hex key sizes, not preceded by 0x */ 1241 if (hexascii_to_octet(buf, (uint_t)buf_len, obj_val, obj_lenp) 1242 != 0) { 1243 syslog(LOG_ERR, 1244 "key_string_to_secobj_value: invalid WEP key"); 1245 return (-1); 1246 } 1247 break; 1248 case 12: 1249 case 28: /* Hex key sizes, preceded by 0x */ 1250 if (strncmp(buf, "0x", 2) != 0 || 1251 hexascii_to_octet(buf + 2, (uint_t)buf_len - 2, obj_val, 1252 obj_lenp) != 0) { 1253 syslog(LOG_ERR, 1254 "key_string_to_secobj_value: invalid WEP key"); 1255 return (-1); 1256 } 1257 break; 1258 default: 1259 syslog(LOG_ERR, 1260 "key_string_to_secobj_value: invalid WEP key length"); 1261 return (-1); 1262 } 1263 return (0); 1264 } 1265 1266 /* 1267 * Print the key name format into the appropriate field, then convert any ":" 1268 * characters to ".", as ":[1-4]" is the slot indicator, which otherwise 1269 * would trip us up. Invalid characters for secobj names are ignored. 1270 * The fourth parameter is expected to be of size DLADM_SECOBJ_NAME_MAX. 1271 * 1272 * (Note that much of the system uses DLADM_WLAN_MAX_KEYNAME_LEN, which is 64 1273 * rather than 32, but that dladm_get_secobj will fail if a length greater than 1274 * DLD_SECOBJ_NAME_MAX is seen, and that's 32. This is all horribly broken.) 1275 */ 1276 static void 1277 set_key_name(const char *essid, const char *bssid, char *name, size_t nsz) 1278 { 1279 int i, j; 1280 char secobj_name[DLADM_WLAN_MAX_KEYNAME_LEN]; 1281 1282 /* create a concatenated string with essid and bssid */ 1283 if (bssid[0] == '\0') { 1284 (void) snprintf(secobj_name, sizeof (secobj_name), "nwam-%s", 1285 essid); 1286 } else { 1287 (void) snprintf(secobj_name, sizeof (secobj_name), "nwam-%s-%s", 1288 essid, bssid); 1289 } 1290 1291 /* copy only valid chars to the return string, terminating with \0 */ 1292 i = 0; /* index into secobj_name */ 1293 j = 0; /* index into name */ 1294 while (secobj_name[i] != '\0') { 1295 if (j == nsz - 1) 1296 break; 1297 1298 if (secobj_name[i] == ':') { 1299 name[j] = '.'; 1300 j++; 1301 } else if (isalnum(secobj_name[i]) || 1302 secobj_name[i] == '.' || secobj_name[i] == '-' || 1303 secobj_name[i] == '_') { 1304 name[j] = secobj_name[i]; 1305 j++; 1306 } 1307 i++; 1308 } 1309 name[j] = '\0'; 1310 } 1311 1312 static int 1313 store_key(struct wireless_lan *wlan) 1314 { 1315 uint8_t obj_val[DLADM_SECOBJ_VAL_MAX]; 1316 uint_t obj_len = sizeof (obj_val); 1317 char obj_name[DLADM_SECOBJ_NAME_MAX]; 1318 dladm_status_t status; 1319 char errmsg[DLADM_STRSIZE]; 1320 dladm_secobj_class_t class; 1321 1322 /* 1323 * Name key object for this WLAN so it can be later retrieved 1324 * (name is unique for each ESSID/BSSID combination). 1325 */ 1326 set_key_name(wlan->essid, wlan->bssid, obj_name, sizeof (obj_name)); 1327 dprintf("store_key: obj_name is %s", obj_name); 1328 1329 class = (wlan->attrs.wa_secmode == DLADM_WLAN_SECMODE_WEP ? 1330 DLADM_SECOBJ_CLASS_WEP : DLADM_SECOBJ_CLASS_WPA); 1331 if (key_string_to_secobj_value(wlan->raw_key, obj_val, &obj_len, 1332 class) != 0) { 1333 /* above function logs internally on failure */ 1334 return (-1); 1335 } 1336 1337 /* we've validated the new key, so remove the old one */ 1338 status = dladm_unset_secobj(dld_handle, obj_name, 1339 DLADM_OPT_ACTIVE | DLADM_OPT_PERSIST); 1340 if (status != DLADM_STATUS_OK && status != DLADM_STATUS_NOTFOUND) { 1341 syslog(LOG_ERR, "store_key: could not remove old secure object " 1342 "'%s' for key: %s", obj_name, 1343 dladm_status2str(status, errmsg)); 1344 return (-1); 1345 } 1346 1347 /* if we're just deleting the key, then we're done */ 1348 if (wlan->raw_key[0] == '\0') 1349 return (0); 1350 1351 status = dladm_set_secobj(dld_handle, obj_name, class, 1352 obj_val, obj_len, 1353 DLADM_OPT_CREATE | DLADM_OPT_PERSIST | DLADM_OPT_ACTIVE); 1354 if (status != DLADM_STATUS_OK) { 1355 syslog(LOG_ERR, "store_key: could not create secure object " 1356 "'%s' for key: %s", obj_name, 1357 dladm_status2str(status, errmsg)); 1358 return (-1); 1359 } 1360 /* 1361 * We don't really need to retrieve the key we just stored, but 1362 * we do need to set the cooked key, and the function below takes 1363 * care of allocating memory and setting the length and slot ID 1364 * besides just copying the value, so it is simpler just to call 1365 * the retrieve function instead of doing it all here. 1366 * 1367 * Since we just stored the key, retrieve_key() "shouldn't" 1368 * fail. If it does fail, it's not the end of the world; a NULL 1369 * value for wlan->cooked_key simply means this particular 1370 * attempt to connect will fail, and alternative connection 1371 * options will be used. 1372 */ 1373 wlan->cooked_key = retrieve_key(wlan->essid, wlan->bssid, class); 1374 return (0); 1375 } 1376 1377 /* 1378 * retrieve_key returns NULL if no key was recovered from libdladm 1379 */ 1380 static dladm_wlan_key_t * 1381 retrieve_key(const char *essid, const char *bssid, dladm_secobj_class_t req) 1382 { 1383 dladm_status_t status; 1384 char errmsg[DLADM_STRSIZE]; 1385 dladm_wlan_key_t *cooked_key; 1386 dladm_secobj_class_t class; 1387 1388 /* 1389 * Newly-allocated key must be freed by caller, or by 1390 * subsequent call to retrieve_key(). 1391 */ 1392 if ((cooked_key = malloc(sizeof (dladm_wlan_key_t))) == NULL) { 1393 syslog(LOG_ERR, "retrieve_key: malloc failed"); 1394 return (NULL); 1395 } 1396 1397 /* 1398 * Set name appropriately to retrieve key for this WLAN. Note that we 1399 * cannot use the actual wk_name buffer size, as it's two times too 1400 * large for dladm_get_secobj. 1401 */ 1402 set_key_name(essid, bssid, cooked_key->wk_name, DLADM_SECOBJ_NAME_MAX); 1403 dprintf("retrieve_key: len = %d, object = %s\n", 1404 strlen(cooked_key->wk_name), cooked_key->wk_name); 1405 cooked_key->wk_len = sizeof (cooked_key->wk_val); 1406 cooked_key->wk_idx = 1; 1407 1408 /* Try the kernel first, then fall back to persistent storage. */ 1409 status = dladm_get_secobj(dld_handle, cooked_key->wk_name, &class, 1410 cooked_key->wk_val, &cooked_key->wk_len, 1411 DLADM_OPT_ACTIVE); 1412 if (status != DLADM_STATUS_OK) { 1413 dprintf("retrieve_key: dladm_get_secobj(TEMP) failed: %s", 1414 dladm_status2str(status, errmsg)); 1415 status = dladm_get_secobj(dld_handle, cooked_key->wk_name, 1416 &class, cooked_key->wk_val, &cooked_key->wk_len, 1417 DLADM_OPT_PERSIST); 1418 } 1419 1420 switch (status) { 1421 case DLADM_STATUS_OK: 1422 dprintf("retrieve_key: dladm_get_secobj succeeded: len %d", 1423 cooked_key->wk_len); 1424 break; 1425 case DLADM_STATUS_NOTFOUND: 1426 /* 1427 * We do not want an error in the case that the secobj 1428 * is not found, since we then prompt for it. 1429 */ 1430 free(cooked_key); 1431 return (NULL); 1432 default: 1433 syslog(LOG_ERR, "retrieve_key: could not get key " 1434 "from secure object '%s': %s", cooked_key->wk_name, 1435 dladm_status2str(status, errmsg)); 1436 free(cooked_key); 1437 return (NULL); 1438 } 1439 1440 if (class != req) { /* the key mismatch */ 1441 syslog(LOG_ERR, "retrieve_key: key type mismatch" 1442 " from secure object '%s'", cooked_key->wk_name); 1443 free(cooked_key); 1444 return (NULL); 1445 } 1446 1447 return (cooked_key); 1448 } 1449 1450 /* 1451 * Add an entry to known_wifi_nets file given the parameters. The caller holds 1452 * wifi_mutex. 1453 */ 1454 static int 1455 add_known_wifi_nets_file(const char *essid, const char *bssid) 1456 { 1457 int retv; 1458 FILE *fp = NULL; 1459 1460 dprintf("add_known_wifi_nets_file(%s, %s)", essid, bssid); 1461 1462 /* Create the NWAM directory in case it does not exist. */ 1463 if (mkdir(LLPDIRNAME, LLPDIRMODE) != 0 && 1464 errno != EEXIST) { 1465 retv = errno; 1466 syslog(LOG_ERR, "could not create %s: %m", LLPDIRNAME); 1467 } else if ((fp = fopen(KNOWN_WIFI_NETS, "a+")) == NULL) { 1468 retv = errno; 1469 syslog(LOG_ERR, "fopen(%s) failed: %m", KNOWN_WIFI_NETS); 1470 } else if (known_wifi_nets_lookup(essid, bssid, NULL)) { 1471 retv = EEXIST; 1472 } else { 1473 /* now add this to the file */ 1474 (void) fprintf(fp, "%s\t%s\n", essid, bssid); 1475 retv = 0; 1476 } 1477 if (fp != NULL) 1478 (void) fclose(fp); 1479 return (retv); 1480 } 1481 1482 static int 1483 delete_known_wifi_nets_file(const char *essid, const char *bssid) 1484 { 1485 FILE *fpin, *fpout; 1486 char line[LINE_MAX]; 1487 char *cp; 1488 int retv; 1489 size_t essidlen, bssidlen; 1490 boolean_t found; 1491 1492 if ((fpin = fopen(KNOWN_WIFI_NETS, "r")) == NULL) 1493 return (errno); 1494 1495 if ((fpout = fopen(KNOWN_WIFI_TMP, "w")) == NULL) { 1496 retv = errno; 1497 (void) fclose(fpin); 1498 return (retv); 1499 } 1500 1501 found = B_FALSE; 1502 essidlen = strlen(essid); 1503 bssidlen = strlen(bssid); 1504 while (fgets(line, sizeof (line), fpin) != NULL) { 1505 cp = line; 1506 while (isspace(*cp)) 1507 cp++; 1508 1509 if (*cp == '#' || *cp == '\0' || 1510 strncmp(essid, cp, essidlen) != 0 || 1511 (cp[essidlen] != '\0' && !isspace(cp[essidlen]))) { 1512 (void) fputs(line, fpout); 1513 continue; 1514 } 1515 1516 /* skip over the essid to examine bssid */ 1517 while (*cp != '\0' && !isspace(*cp)) 1518 cp++; 1519 while (isspace(*cp)) 1520 cp++; 1521 1522 /* 1523 * Deleting with bssid empty means "all entries under this 1524 * essid." As a result, deleting a wildcard entry for a bssid 1525 * means deleting all entries for that bssid. 1526 */ 1527 1528 if (bssidlen == 0 || 1529 (strncmp(bssid, cp, bssidlen) == 0 && 1530 (cp[bssidlen] == '\0' || isspace(cp[bssidlen])))) { 1531 /* delete this entry */ 1532 found = B_TRUE; 1533 continue; 1534 } 1535 1536 (void) fputs(line, fpout); 1537 } 1538 1539 (void) fclose(fpin); 1540 (void) fclose(fpout); 1541 1542 if (found) { 1543 if (rename(KNOWN_WIFI_TMP, KNOWN_WIFI_NETS) == 0) { 1544 retv = 0; 1545 } else { 1546 retv = errno; 1547 (void) unlink(KNOWN_WIFI_TMP); 1548 } 1549 } else { 1550 retv = ENXIO; 1551 (void) unlink(KNOWN_WIFI_TMP); 1552 } 1553 1554 return (retv); 1555 } 1556 1557 /* 1558 * Check if the given AP (ESSID, BSSID pair) is on the known AP list. 1559 * If found_essid is non-NULL and the match is found (B_TRUE is returned) 1560 * the matched ESSID is copied out into buffer pointed by found_essid. 1561 * The buffer is expected to be at least DLADM_STRSIZE bytes long. 1562 */ 1563 static boolean_t 1564 known_wifi_nets_lookup(const char *new_essid, const char *new_bssid, 1565 char *found_essid) 1566 { 1567 FILE *fp; 1568 char line[LINE_MAX]; 1569 char *cp; 1570 char *tok[MAX_FIELDS]; 1571 int line_num; 1572 boolean_t found = B_FALSE; 1573 1574 /* 1575 * For now the file format is: 1576 * essid\tbssid 1577 * (essid followed by tab followed by bssid) 1578 */ 1579 fp = fopen(KNOWN_WIFI_NETS, "r"); 1580 if (fp == NULL) 1581 return (B_FALSE); 1582 for (line_num = 1; fgets(line, sizeof (line), fp) != NULL; line_num++) { 1583 1584 cp = line; 1585 while (isspace(*cp)) 1586 cp++; 1587 1588 if (*cp == '#' || *cp == '\0') 1589 continue; 1590 1591 if (bufsplit(cp, MAX_FIELDS, tok) != MAX_FIELDS) { 1592 syslog(LOG_ERR, "%s:%d: wrong number of tokens; " 1593 "ignoring entry", KNOWN_WIFI_NETS, line_num); 1594 continue; 1595 } 1596 1597 /* 1598 * If we're searching on ESSID alone, then any match on a 1599 * specific ESSID will do. 1600 */ 1601 if (*new_bssid == '\0') { 1602 if (*new_essid != '\0' && 1603 strcmp(tok[ESSID], new_essid) == 0) { 1604 found = B_TRUE; 1605 break; 1606 } 1607 } 1608 /* 1609 * If BSSID match is found we check ESSID, which should 1610 * either match as well, or be an empty string. 1611 * In latter case we'll retrieve the ESSID from known_wifi_nets 1612 * later. 1613 */ 1614 else if (strcmp(tok[BSSID], new_bssid) == 0) { 1615 /* 1616 * Got BSSID match, either ESSID was not specified, 1617 * or it should match 1618 */ 1619 if (*new_essid == '\0' || 1620 strcmp(tok[ESSID], new_essid) == 0) { 1621 found = B_TRUE; 1622 break; 1623 } 1624 } 1625 } 1626 1627 if (found) { 1628 if (found_essid != NULL) 1629 (void) strlcpy(found_essid, tok[ESSID], DLADM_STRSIZE); 1630 } 1631 1632 (void) fclose(fp); 1633 return (found); 1634 } 1635 1636 static uint_t 1637 extract_known_aps(FILE *fp, libnwam_known_ap_t *kap, char *sbuf, size_t *totstr) 1638 { 1639 char line[LINE_MAX]; 1640 char *cp; 1641 char *tok[MAX_FIELDS]; 1642 size_t accstr = 0; 1643 uint_t count = 0; 1644 char key[DLADM_SECOBJ_NAME_MAX]; 1645 uint8_t keyval[DLADM_SECOBJ_VAL_MAX]; 1646 dladm_secobj_class_t class; 1647 uint_t keylen; 1648 1649 while (fgets(line, sizeof (line), fp) != NULL) { 1650 cp = line; 1651 while (isspace(*cp)) 1652 cp++; 1653 1654 if (*cp == '#' || *cp == '\0') 1655 continue; 1656 1657 if (bufsplit(cp, MAX_FIELDS, tok) != MAX_FIELDS) 1658 continue; 1659 1660 if (totstr != NULL) 1661 accstr += strlen(tok[BSSID]) + strlen(tok[ESSID]) + 2; 1662 count++; 1663 1664 if (kap != NULL) { 1665 kap->ka_essid = strcpy(sbuf, tok[ESSID]); 1666 sbuf += strlen(sbuf) + 1; 1667 kap->ka_bssid = strcpy(sbuf, tok[BSSID]); 1668 sbuf += strlen(sbuf) + 1; 1669 set_key_name(tok[ESSID], tok[BSSID], key, sizeof (key)); 1670 keylen = sizeof (keyval); 1671 if (dladm_get_secobj(dld_handle, key, &class, keyval, 1672 &keylen, DLADM_OPT_ACTIVE) == DLADM_STATUS_OK) 1673 kap->ka_haskey = B_TRUE; 1674 else 1675 kap->ka_haskey = B_FALSE; 1676 kap++; 1677 } 1678 } 1679 if (totstr != NULL) 1680 *totstr = accstr; 1681 return (count); 1682 } 1683 1684 libnwam_known_ap_t * 1685 get_known_ap_list(size_t *kasizep, uint_t *countp) 1686 { 1687 FILE *fp; 1688 libnwam_known_ap_t *kap = NULL; 1689 size_t kasize; 1690 uint_t count; 1691 int retv; 1692 1693 if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0) { 1694 errno = retv; 1695 return (kap); 1696 } 1697 if ((fp = fopen(KNOWN_WIFI_NETS, "r")) != NULL) { 1698 count = extract_known_aps(fp, NULL, NULL, &kasize); 1699 rewind(fp); 1700 kasize += count * sizeof (*kap); 1701 if (count != 0 && (kap = malloc(kasize)) != NULL) { 1702 (void) extract_known_aps(fp, kap, (char *)(kap + count), 1703 NULL); 1704 *kasizep = kasize; 1705 *countp = count; 1706 } 1707 (void) fclose(fp); 1708 } 1709 (void) pthread_mutex_unlock(&wifi_mutex); 1710 return (kap); 1711 } 1712 1713 int 1714 add_known_ap(const char *essid, const char *bssid) 1715 { 1716 int retv; 1717 char ifname[LIFNAMSIZ]; 1718 libnwam_interface_type_t ift; 1719 struct wireless_lan *wlan, *savedwlan; 1720 1721 /* 1722 * First check the current LLP. If there is one, then its connection 1723 * state determines what to do after adding the known AP to the list. 1724 * If not, then we act if there are no connected APs. 1725 */ 1726 llp_get_name_and_type(ifname, sizeof (ifname), &ift); 1727 1728 if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0) 1729 return (retv); 1730 1731 retv = add_known_wifi_nets_file(essid, bssid); 1732 if (retv == 0 && (ift == IF_UNKNOWN || ift == IF_WIRELESS)) { 1733 boolean_t any_connected, one_matches; 1734 1735 /* 1736 * If this is in our list of scanned APs and if no interface is 1737 * connected, then we have a reevaluation event. 1738 */ 1739 any_connected = one_matches = B_FALSE; 1740 for (wlan = wlans; wlan < wlans + wireless_lan_used; 1741 wlan++) { 1742 /* 1743 * If LLP is selected, then ignore all others. Only 1744 * the state of this one interface is at issue. 1745 */ 1746 if (ifname[0] != '\0' && 1747 strcmp(ifname, wlan->wl_if_name) != 0) 1748 continue; 1749 if (wlan->connected) 1750 any_connected = B_TRUE; 1751 if (strcmp(essid, wlan->essid) == 0 && 1752 (bssid[0] == '\0' || 1753 strcmp(bssid, wlan->bssid) == 0)) { 1754 one_matches = B_TRUE; 1755 savedwlan = wlan; 1756 } 1757 } 1758 if (!any_connected && one_matches) { 1759 (void) np_queue_add_event(EV_RESELECT, 1760 savedwlan->wl_if_name); 1761 } 1762 } 1763 (void) pthread_mutex_unlock(&wifi_mutex); 1764 return (retv); 1765 } 1766 1767 int 1768 delete_known_ap(const char *essid, const char *bssid) 1769 { 1770 int retv; 1771 struct wireless_lan *wlan; 1772 wireless_if_t *wip; 1773 1774 if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0) 1775 return (retv); 1776 1777 retv = delete_known_wifi_nets_file(essid, bssid); 1778 if (retv == 0) { 1779 for (wlan = wlans; wlan < wlans + wireless_lan_used; 1780 wlan++) { 1781 if (wlan->connected && 1782 strcmp(essid, wlan->essid) == 0 && 1783 (bssid[0] == '\0' || 1784 strcmp(bssid, wlan->bssid) == 0)) { 1785 wlan->connected = B_FALSE; 1786 report_wlan_disconnect(wlan); 1787 wip = find_wireless_if(wlan->wl_if_name); 1788 if (wip != NULL) { 1789 wip->wi_wireless_done = B_FALSE; 1790 wip->wi_need_key = B_FALSE; 1791 (void) dladm_wlan_disconnect(dld_handle, 1792 wip->wi_linkid); 1793 } 1794 (void) np_queue_add_event(EV_RESELECT, 1795 wlan->wl_if_name); 1796 } 1797 } 1798 } 1799 (void) pthread_mutex_unlock(&wifi_mutex); 1800 return (retv); 1801 } 1802 1803 /* 1804 * reqlan->essid is required (i.e., cannot be zero-length) 1805 * reqlan->bssid is optional (i.e., may be zero-length) 1806 */ 1807 static return_vals_t 1808 connect_chosen_lan(struct wireless_lan *reqlan, wireless_if_t *wip) 1809 { 1810 uint_t keycount; 1811 dladm_wlan_key_t *key; 1812 dladm_wlan_attr_t attr; 1813 dladm_status_t status; 1814 uint_t flags = DLADM_WLAN_CONNECT_NOSCAN; 1815 int timeout = DLADM_WLAN_CONNECT_TIMEOUT_DEFAULT; 1816 char errmsg[DLADM_STRSIZE]; 1817 return_vals_t rval; 1818 1819 wip->wi_need_key = B_FALSE; 1820 1821 (void) memset(&attr, 0, sizeof (attr)); 1822 /* try to apply essid selected by the user */ 1823 if (reqlan->essid == NULL) 1824 return (FAILURE); 1825 dprintf("connect_chosen_lan(%s, %s, %s)", reqlan->essid, 1826 reqlan->bssid, wip->wi_name); 1827 1828 /* If it is already connected to the required AP, just return. */ 1829 if (check_wlan(wip, reqlan->essid, NULL, B_TRUE)) 1830 return (SUCCESS); 1831 1832 if (dladm_wlan_str2essid(reqlan->essid, &attr.wa_essid) != 1833 DLADM_STATUS_OK) { 1834 syslog(LOG_ERR, 1835 "connect_chosen_lan: invalid ESSID '%s' for '%s'", 1836 reqlan->essid, wip->wi_name); 1837 return (FAILURE); 1838 } 1839 attr.wa_valid = DLADM_WLAN_ATTR_ESSID; 1840 1841 /* note: bssid logic here is non-functional */ 1842 if (reqlan->bssid[0] != '\0') { 1843 if (dladm_wlan_str2bssid(reqlan->bssid, &attr.wa_bssid) != 1844 DLADM_STATUS_OK) { 1845 syslog(LOG_ERR, 1846 "connect_chosen_lan: invalid BSSID '%s' for '%s'", 1847 reqlan->bssid, wip->wi_name); 1848 return (FAILURE); 1849 } 1850 attr.wa_valid |= DLADM_WLAN_ATTR_BSSID; 1851 } 1852 1853 /* First check for the key */ 1854 if (NEED_ENC(reqlan->attrs.wa_secmode)) { 1855 /* Note that this happens only for known APs from the list */ 1856 if ((rval = get_user_key(reqlan)) != SUCCESS) { 1857 if (rval == WAITING) 1858 wip->wi_need_key = B_TRUE; 1859 return (rval); 1860 } 1861 attr.wa_valid |= DLADM_WLAN_ATTR_SECMODE; 1862 attr.wa_secmode = reqlan->attrs.wa_secmode; 1863 key = reqlan->cooked_key; 1864 keycount = 1; 1865 dprintf("connect_chosen_lan: retrieved key"); 1866 } else { 1867 key = NULL; 1868 keycount = 0; 1869 } 1870 1871 /* 1872 * Connect; only scan if a bssid was not specified. 1873 * If it times out and we were trying with a bssid, 1874 * try a second time with just the ESSID. 1875 */ 1876 1877 status = dladm_wlan_connect(dld_handle, wip->wi_linkid, &attr, timeout, 1878 key, keycount, flags); 1879 dprintf("connect_chosen_lan: dladm_wlan_connect returned %s", 1880 dladm_status2str(status, errmsg)); 1881 /* 1882 * This doesn't work due to CR 6772510. 1883 */ 1884 #ifdef CR6772510_FIXED 1885 if (status == DLADM_STATUS_TIMEDOUT && reqlan->bssid[0] != '\0') { 1886 syslog(LOG_INFO, "connect_chosen_lan: failed for (%s, %s), " 1887 "trying again with just (%s)", 1888 reqlan->essid, reqlan->bssid, reqlan->essid); 1889 attr.wa_valid &= ~DLADM_WLAN_ATTR_BSSID; 1890 flags = 0; 1891 status = dladm_wlan_connect(dld_handle, wip->wi_linkid, &attr, 1892 timeout, key, keycount, flags); 1893 } 1894 #endif /* CR6772510_FIXED */ 1895 if (status == DLADM_STATUS_OK) { 1896 return (SUCCESS); 1897 } else { 1898 syslog(LOG_ERR, 1899 "connect_chosen_lan: connect to '%s' failed on '%s': %s", 1900 reqlan->essid, wip->wi_name, 1901 dladm_status2str(status, errmsg)); 1902 return (FAILURE); 1903 } 1904 } 1905 1906 /* 1907 * Check that the wireless LAN is connected to the desired ESSID/BSSID. This 1908 * is used by the GUI to check for connectivity before doing anything 1909 * destructive. 1910 */ 1911 boolean_t 1912 check_wlan_connected(const char *ifname, const char *essid, const char *bssid) 1913 { 1914 wireless_if_t *wip; 1915 boolean_t retv; 1916 1917 if (pthread_mutex_lock(&wifi_mutex) != 0) 1918 return (B_FALSE); 1919 1920 if ((wip = find_wireless_if(ifname)) == NULL) { 1921 retv = B_FALSE; 1922 } else { 1923 if (essid[0] == '\0' && bssid[0] == '\0') 1924 essid = NULL; 1925 retv = check_wlan(wip, essid, bssid, B_FALSE); 1926 } 1927 (void) pthread_mutex_unlock(&wifi_mutex); 1928 return (retv); 1929 } 1930 1931 /* 1932 * This thread performs the blocking actions related to a wireless connection 1933 * request. The attempt to connect isn't started until all other connects and 1934 * scans have finished, and while the connect is in progress, no new connects 1935 * or scans can be started. 1936 */ 1937 static void * 1938 connect_thread(void *arg) 1939 { 1940 struct wireless_lan *req_wlan = arg; 1941 wireless_if_t *wip; 1942 struct wireless_lan *wlan = NULL; 1943 1944 if (!scanconnect_entry(req_wlan->wl_if_name, B_TRUE)) 1945 goto failure_noentry; 1946 1947 if (pthread_mutex_lock(&wifi_mutex) != 0) 1948 goto failure_unlocked; 1949 1950 if ((wip = find_wireless_if(req_wlan->wl_if_name)) == NULL) 1951 goto failure; 1952 1953 /* This is an autoconf request. */ 1954 if (req_wlan->essid[0] == '\0' && req_wlan->bssid[0] == '\0') { 1955 if (!wlan_autoconf(wip) && !update_connected_wlan(wip, NULL)) 1956 goto failure; 1957 else 1958 goto done; 1959 } 1960 1961 wlan = find_wlan_entry(req_wlan->wl_if_name, req_wlan->essid, 1962 req_wlan->bssid); 1963 if (wlan == NULL) 1964 wlan = req_wlan; 1965 1966 /* 1967 * now attempt to connect to selection 1968 */ 1969 switch (connect_chosen_lan(wlan, wip)) { 1970 case WAITING: 1971 break; 1972 1973 case SUCCESS: { 1974 dladm_status_t status; 1975 dladm_wlan_linkattr_t attr; 1976 char lclssid[DLADM_STRSIZE]; 1977 char unnecessary_buf[DLADM_STRSIZE]; 1978 1979 /* 1980 * Successful connection to user-chosen AP; add entry to 1981 * known_essid_list_file. First make sure the wlan->bssid 1982 * isn't empty. Note that empty bssid is never allocated. 1983 * 1984 * We would like to query the driver only in the case where the 1985 * BSSID is not known, but it turns out that due to CR 6772510, 1986 * the actual BSSID we connect to is arbitrary. Nothing we can 1987 * do about that; just get the new value and live with it. 1988 */ 1989 status = dladm_wlan_get_linkattr(dld_handle, wip->wi_linkid, 1990 &attr); 1991 if (status != DLADM_STATUS_OK) { 1992 dprintf("failed to get linkattr on %s after connecting " 1993 "to %s: %s", wlan->wl_if_name, wlan->essid, 1994 dladm_status2str(status, unnecessary_buf)); 1995 goto failure; 1996 } 1997 (void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid, 1998 lclssid); 1999 if (strcmp(req_wlan->essid, lclssid) != 0) { 2000 dprintf("connected to strange network: expected %s got " 2001 "%s", req_wlan->essid, lclssid); 2002 goto failure; 2003 } 2004 (void) dladm_wlan_bssid2str(&attr.la_wlan_attr.wa_bssid, 2005 lclssid); 2006 if (wlan == req_wlan || strcmp(wlan->bssid, lclssid) != 0) { 2007 wlan = add_wlan_entry(req_wlan->wl_if_name, 2008 req_wlan->essid, lclssid, &attr.la_wlan_attr); 2009 if (wlan == NULL) 2010 goto failure; 2011 } 2012 if (wlan->bssid[0] == '\0' && lclssid[0] != '\0') 2013 wlan->bssid = strdup(lclssid); 2014 if (wlan->bssid == NULL || wlan->bssid[0] == '\0') { 2015 /* Don't leave it as NULL (for simplicity) */ 2016 wlan->bssid = ""; 2017 goto failure; 2018 } 2019 wlan->connected = B_TRUE; 2020 if (!update_connected_wlan(wip, wlan)) 2021 goto failure; 2022 wlan->known = B_TRUE; 2023 (void) add_known_wifi_nets_file(wlan->essid, wlan->bssid); 2024 /* We're done; trigger IP bring-up. */ 2025 (void) np_queue_add_event(EV_RESELECT, wlan->wl_if_name); 2026 report_wlan_connected(wlan); 2027 break; 2028 } 2029 2030 default: 2031 goto failure; 2032 } 2033 2034 done: 2035 (void) pthread_mutex_unlock(&wifi_mutex); 2036 scanconnect_exit(); 2037 free_wireless_lan(req_wlan); 2038 return (NULL); 2039 2040 failure: 2041 /* 2042 * Failed to connect. Set 'rescan' flag so that we treat this AP as 2043 * new if it's seen again, because the wireless radio may have just 2044 * been off briefly while we were trying to connect. 2045 */ 2046 if (wip != NULL) { 2047 wip->wi_need_key = B_FALSE; 2048 wip->wi_wireless_done = B_FALSE; 2049 (void) dladm_wlan_disconnect(dld_handle, wip->wi_linkid); 2050 } 2051 if (wlan != NULL) 2052 wlan->rescan = B_TRUE; 2053 (void) pthread_mutex_unlock(&wifi_mutex); 2054 2055 failure_unlocked: 2056 scanconnect_exit(); 2057 failure_noentry: 2058 syslog(LOG_WARNING, "could not connect to chosen WLAN %s on %s", 2059 req_wlan->essid, req_wlan->wl_if_name); 2060 report_wlan_connect_fail(req_wlan->wl_if_name); 2061 free_wireless_lan(req_wlan); 2062 return (NULL); 2063 } 2064 2065 /* 2066 * This is the entry point for GUI "select access point" requests. It verifies 2067 * the parameters and then launches a new thread to perform the connect 2068 * operation. When it returns success (0), the user should expect future 2069 * events indicating progress. 2070 * 2071 * Returns: 2072 * 0 - ok (or more data requested with new event) 2073 * ENXIO - no such interface 2074 * ENODEV - interface is not wireless 2075 * EINVAL - failed to perform requested action 2076 */ 2077 int 2078 set_specific_lan(const char *ifname, const char *essid, const char *bssid) 2079 { 2080 libnwam_interface_type_t ift; 2081 pthread_t conn_thr; 2082 pthread_attr_t attr; 2083 struct wireless_lan *wlan; 2084 int retv; 2085 2086 if ((ift = get_if_type(ifname)) == IF_UNKNOWN) 2087 return (ENXIO); 2088 if (ift != IF_WIRELESS) 2089 return (EINVAL); 2090 2091 if ((wlan = calloc(1, sizeof (struct wireless_lan))) == NULL) 2092 return (ENOMEM); 2093 (void) strlcpy(wlan->wl_if_name, ifname, sizeof (wlan->wl_if_name)); 2094 wlan->essid = strdup(essid); 2095 wlan->bssid = *bssid == '\0' ? "" : strdup(bssid); 2096 if (wlan->essid == NULL || wlan->bssid == NULL) { 2097 free_wireless_lan(wlan); 2098 return (ENOMEM); 2099 } 2100 (void) pthread_attr_init(&attr); 2101 (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 2102 retv = pthread_create(&conn_thr, &attr, connect_thread, wlan); 2103 if (retv == 0) 2104 dprintf("started connect thread %d for %s %s %s", conn_thr, 2105 ifname, essid, bssid); 2106 else 2107 free_wireless_lan(wlan); 2108 return (retv); 2109 } 2110 2111 int 2112 set_wlan_key(const char *ifname, const char *essid, const char *bssid, 2113 const char *key, const char *secmode) 2114 { 2115 libnwam_interface_type_t ift; 2116 struct wireless_lan *wlan, local_wlan; 2117 wireless_if_t *wip; 2118 int retv; 2119 boolean_t need_key; 2120 dladm_wlan_secmode_t smode = DLADM_WLAN_SECMODE_WEP; 2121 2122 ift = get_if_type(ifname); 2123 if (ift == IF_UNKNOWN) 2124 return (ENXIO); 2125 if (ift != IF_WIRELESS) 2126 return (EINVAL); 2127 2128 if (*secmode != '\0' && 2129 dladm_wlan_str2secmode(secmode, &smode) != DLADM_STATUS_OK) 2130 return (EINVAL); 2131 2132 if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0) 2133 return (retv); 2134 2135 if ((wlan = find_wlan_entry(ifname, essid, bssid)) == NULL) { 2136 /* If not seen in scan, then secmode is required */ 2137 if (*secmode == '\0') { 2138 retv = ENODEV; 2139 goto done; 2140 } 2141 /* Prohibit a completely blank entry */ 2142 if (*essid == '\0' && *bssid == '\0') { 2143 retv = EINVAL; 2144 goto done; 2145 } 2146 (void) memset(&local_wlan, 0, sizeof (local_wlan)); 2147 wlan = &local_wlan; 2148 (void) strlcpy(wlan->wl_if_name, ifname, 2149 sizeof (wlan->wl_if_name)); 2150 wlan->essid = (char *)essid; 2151 wlan->bssid = (char *)bssid; 2152 wlan->raw_key = (char *)key; 2153 wlan->attrs.wa_secmode = smode; 2154 } else { 2155 /* If seen in scan, then secmode given (if any) must match */ 2156 if (*secmode != '\0' && smode != wlan->attrs.wa_secmode) { 2157 retv = EINVAL; 2158 goto done; 2159 } 2160 /* save a copy of the new key in the scan entry */ 2161 if ((wlan->raw_key = strdup(key)) == NULL) { 2162 retv = ENOMEM; 2163 goto done; 2164 } 2165 } 2166 2167 if (store_key(wlan) != 0) 2168 retv = EINVAL; 2169 else 2170 retv = 0; 2171 2172 done: 2173 wip = find_wireless_if(ifname); 2174 need_key = wip != NULL && wip->wi_need_key; 2175 (void) pthread_mutex_unlock(&wifi_mutex); 2176 2177 if (retv == 0 && need_key) 2178 retv = set_specific_lan(ifname, essid, bssid); 2179 2180 return (retv); 2181 } 2182 2183 static boolean_t 2184 wlan_autoconf(const wireless_if_t *wip) 2185 { 2186 dladm_status_t status; 2187 boolean_t autoconf; 2188 2189 if (lookup_boolean_property(OUR_PG, "autoconf", &autoconf) == 0) { 2190 if (!autoconf) 2191 return (B_FALSE); 2192 } 2193 2194 /* If the NIC is already associated with something, just return. */ 2195 if (check_wlan(wip, NULL, NULL, B_TRUE)) 2196 return (B_TRUE); 2197 2198 /* 2199 * Do autoconf, relying on the heuristics used by dladm_wlan_connect() 2200 * to cycle through WLANs detected in priority order, attempting 2201 * to connect. 2202 */ 2203 status = dladm_wlan_connect(dld_handle, wip->wi_linkid, NULL, 2204 DLADM_WLAN_CONNECT_TIMEOUT_DEFAULT, NULL, 0, 0); 2205 if (status != DLADM_STATUS_OK) { 2206 char errmsg[DLADM_STRSIZE]; 2207 2208 syslog(LOG_ERR, 2209 "wlan_autoconf: dladm_wlan_connect failed for '%s': %s", 2210 wip->wi_name, dladm_status2str(status, errmsg)); 2211 return (B_FALSE); 2212 } 2213 return (B_TRUE); 2214 } 2215 2216 /* 2217 * This function searches through the wlans[] array and determines which ones 2218 * have been visited before. 2219 * 2220 * If exactly one has been visited before, and it has the highest signal 2221 * strength, then we attempt to connect to it right away. 2222 * 2223 * In all other cases -- if none have been visited before, or more than one was 2224 * visited, or if the one that was visited doesn't have the highest signal 2225 * strength, or if the automatic connect attempt fails for any reason -- then 2226 * we hand over the data to the GUI for resolution. The user will have to be 2227 * prompted for a choice. 2228 * 2229 * If no GUI exists, we'll get back FAILURE (instead of WAITING), which will 2230 * cause the autoconf mechanism to run instead. 2231 */ 2232 return_vals_t 2233 handle_wireless_lan(const char *ifname) 2234 { 2235 wireless_if_t *wip; 2236 struct wireless_lan *cur_wlan, *max_wlan, *strong_wlan = NULL; 2237 struct wireless_lan *most_recent; 2238 boolean_t many_present; 2239 dladm_wlan_strength_t strongest = DLADM_WLAN_STRENGTH_VERY_WEAK; 2240 return_vals_t connect_result = FAILURE; 2241 2242 /* 2243 * We wait while a scan or another connect is in progress, and then 2244 * block other connects/scans. Since we allow a user to initiate a 2245 * re-scan, we can proceed even when no scan has yet been done to fill 2246 * in the AP list. 2247 */ 2248 if (!scanconnect_entry(ifname, B_TRUE)) 2249 return (FAILURE); 2250 2251 if (pthread_mutex_lock(&wifi_mutex) != 0) { 2252 scanconnect_exit(); 2253 return (FAILURE); 2254 } 2255 2256 if ((wip = find_wireless_if(ifname)) == NULL) 2257 goto finished; 2258 2259 if (wip->wi_wireless_done) { 2260 dprintf("handle_wireless_lan: skipping policy scan; done"); 2261 /* special case; avoid interface update */ 2262 (void) pthread_mutex_unlock(&wifi_mutex); 2263 scanconnect_exit(); 2264 return (SUCCESS); 2265 } 2266 2267 dprintf("handle_wireless_lan: starting policy scan"); 2268 cur_wlan = wlans; 2269 max_wlan = wlans + wireless_lan_used; 2270 most_recent = NULL; 2271 many_present = B_FALSE; 2272 2273 /* 2274 * Try to see if any of the wifi nets currently available 2275 * has been used previously. If more than one available 2276 * nets has been used before, then prompt user with 2277 * all the applicable previously wifi nets, and ask which 2278 * one to connect to. 2279 */ 2280 for (; cur_wlan < max_wlan; cur_wlan++) { 2281 /* Find the AP with the highest signal. */ 2282 if (cur_wlan->attrs.wa_strength > strongest) { 2283 strongest = cur_wlan->attrs.wa_strength; 2284 strong_wlan = cur_wlan; 2285 } 2286 2287 if (known_wifi_nets_lookup(cur_wlan->essid, cur_wlan->bssid, 2288 NULL)) 2289 cur_wlan->known = B_TRUE; 2290 2291 if (!cur_wlan->known && !strict_bssid && 2292 known_wifi_nets_lookup(cur_wlan->essid, "", NULL)) { 2293 dprintf("noticed new BSSID %s for ESSID %s on %s", 2294 cur_wlan->bssid, cur_wlan->essid, ifname); 2295 if (add_known_wifi_nets_file(cur_wlan->essid, 2296 cur_wlan->bssid) == 0) 2297 cur_wlan->known = B_TRUE; 2298 } 2299 2300 if (cur_wlan->known || cur_wlan->connected) { 2301 /* 2302 * The ESSID comparison here mimics what the "already 2303 * in visited wlan list" function once did, but 2304 * slightly better as we also pay attention to signal 2305 * strength to pick the best of the duplicates. 2306 */ 2307 if (most_recent == NULL) { 2308 most_recent = cur_wlan; 2309 } else if (strcmp(cur_wlan->essid, 2310 most_recent->essid) != 0) { 2311 if (!many_present) 2312 dprintf("both %s and %s are known and " 2313 "present on %s", cur_wlan->essid, 2314 most_recent->essid, ifname); 2315 many_present = B_TRUE; 2316 } else if (cur_wlan->attrs.wa_strength > 2317 most_recent->attrs.wa_strength) { 2318 if (most_recent->connected) { 2319 dprintf("found better BSS %s for ESS " 2320 "%s; disconnecting %s on %s", 2321 cur_wlan->bssid, cur_wlan->essid, 2322 most_recent->bssid, ifname); 2323 (void) dladm_wlan_disconnect(dld_handle, 2324 wip->wi_linkid); 2325 most_recent->connected = B_FALSE; 2326 report_wlan_disconnect(most_recent); 2327 wip->wi_wireless_done = B_FALSE; 2328 } 2329 most_recent = cur_wlan; 2330 } else if (cur_wlan->attrs.wa_strength < 2331 most_recent->attrs.wa_strength && 2332 cur_wlan->connected) { 2333 dprintf("found better BSS %s for ESS %s; " 2334 "disconnecting %s on %s", 2335 most_recent->bssid, most_recent->essid, 2336 cur_wlan->bssid, ifname); 2337 (void) dladm_wlan_disconnect(dld_handle, 2338 wip->wi_linkid); 2339 cur_wlan->connected = B_FALSE; 2340 report_wlan_disconnect(cur_wlan); 2341 wip->wi_wireless_done = B_FALSE; 2342 } 2343 } 2344 2345 /* Reset any security information we may have had. */ 2346 free(cur_wlan->raw_key); 2347 cur_wlan->raw_key = NULL; 2348 free(cur_wlan->cooked_key); 2349 cur_wlan->cooked_key = NULL; 2350 } 2351 2352 /* 2353 * The Three Rules: 2354 * 2355 * 1. If no known AP is in range, then seek help. 2356 * 2357 * 2. If two or more known APs are in range, then seek help. 2358 * 2359 * 3. If a known AP is in range, and its signal strength is "weak" 2360 * or lower, and the strongest available is "very good" or 2361 * better, then seek help. 2362 */ 2363 if (most_recent != NULL && (!many_present || most_recent->connected) && 2364 (most_recent->attrs.wa_strength > DLADM_WLAN_STRENGTH_WEAK || 2365 strongest < DLADM_WLAN_STRENGTH_VERY_GOOD)) { 2366 if (most_recent->connected) { 2367 dprintf("%s already connected to %s", ifname, 2368 most_recent->essid); 2369 connect_result = SUCCESS; 2370 } else { 2371 dprintf("%s connecting automatically to %s", ifname, 2372 most_recent->essid); 2373 connect_result = connect_chosen_lan(most_recent, wip); 2374 switch (connect_result) { 2375 case FAILURE: 2376 report_wlan_connect_fail(wip->wi_name); 2377 most_recent->rescan = B_TRUE; 2378 syslog(LOG_WARNING, "could not connect to " 2379 "chosen WLAN %s on %s, going to auto-conf", 2380 most_recent->essid, ifname); 2381 connect_result = wlan_autoconf(wip) ? SUCCESS : 2382 FAILURE; 2383 most_recent = NULL; 2384 break; 2385 case SUCCESS: 2386 most_recent->connected = B_TRUE; 2387 report_wlan_connected(most_recent); 2388 break; 2389 } 2390 } 2391 } else if (request_wlan_selection(ifname, wlans, wireless_lan_used)) { 2392 if (most_recent == NULL) 2393 dprintf("%s has no known WLANs; requested help", 2394 ifname); 2395 else if (many_present && !most_recent->connected) 2396 dprintf("%s has multiple known WLANs and is not " 2397 "connected; requested help", ifname); 2398 else 2399 dprintf("%s has known WLAN %s, but not strongest %s; " 2400 "requested help", ifname, most_recent->essid, 2401 strong_wlan->essid); 2402 connect_result = WAITING; 2403 } else { 2404 dprintf("%s has no connected AP or GUI; try auto", ifname); 2405 connect_result = wlan_autoconf(wip) ? SUCCESS : FAILURE; 2406 most_recent = NULL; 2407 } 2408 2409 finished: 2410 if (connect_result == SUCCESS && 2411 !update_connected_wlan(wip, most_recent)) 2412 connect_result = FAILURE; 2413 (void) pthread_mutex_unlock(&wifi_mutex); 2414 scanconnect_exit(); 2415 2416 return (connect_result); 2417 } 2418 2419 void 2420 disconnect_wlan(const char *ifname) 2421 { 2422 wireless_if_t *wip; 2423 struct wireless_lan *wlan; 2424 2425 if (pthread_mutex_lock(&wifi_mutex) == 0) { 2426 if ((wip = find_wireless_if(ifname)) != NULL) { 2427 wip->wi_wireless_done = B_FALSE; 2428 wip->wi_need_key = B_FALSE; 2429 (void) dladm_wlan_disconnect(dld_handle, 2430 wip->wi_linkid); 2431 } 2432 for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) { 2433 if (strcmp(ifname, wlan->wl_if_name) == 0 && 2434 wlan->connected) { 2435 wlan->connected = B_FALSE; 2436 report_wlan_disconnect(wlan); 2437 } 2438 } 2439 (void) pthread_mutex_unlock(&wifi_mutex); 2440 } 2441 } 2442 2443 void 2444 get_wireless_state(const char *ifname, boolean_t *need_wlan, 2445 boolean_t *need_key) 2446 { 2447 wireless_if_t *wip; 2448 2449 *need_wlan = *need_key = B_FALSE; 2450 if (pthread_mutex_lock(&wifi_mutex) == 0) { 2451 if ((wip = find_wireless_if(ifname)) != NULL) { 2452 *need_key = wip->wi_need_key; 2453 if (!wip->wi_need_key && !wip->wi_wireless_done) 2454 *need_wlan = B_TRUE; 2455 } 2456 (void) pthread_mutex_unlock(&wifi_mutex); 2457 } 2458 } 2459 2460 void 2461 print_wireless_status(void) 2462 { 2463 wireless_if_t *wip; 2464 struct wireless_lan *wlan; 2465 char strength[DLADM_STRSIZE]; 2466 2467 if (pthread_mutex_lock(&wifi_mutex) == 0) { 2468 for (wip = (wireless_if_t *)wi_list.q_forw; 2469 wip != (wireless_if_t *)&wi_list; 2470 wip = (wireless_if_t *)wip->wi_links.q_forw) { 2471 (void) dladm_wlan_strength2str(&wip->wi_strength, 2472 strength); 2473 dprintf("WIF %s linkid %d scan %srunning " 2474 "wireless %sdone %sneed key strength %s", 2475 wip->wi_name, wip->wi_linkid, 2476 wip->wi_scan_running ? "" : "not ", 2477 wip->wi_wireless_done ? "" : "not ", 2478 wip->wi_need_key ? "" : "don't ", 2479 strength); 2480 } 2481 for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) { 2482 dprintf("WLAN I/F %s ESS %s BSS %s signal %s key %sset " 2483 "%sknown %sconnected %sscanned", 2484 wlan->wl_if_name, wlan->essid, wlan->bssid, 2485 wlan->signal_strength, 2486 wlan->raw_key == NULL ? "un" : "", 2487 wlan->known ? "" : "not ", 2488 wlan->connected ? "" : "not ", 2489 wlan->scanned ? "" : "not "); 2490 } 2491 (void) pthread_mutex_unlock(&wifi_mutex); 2492 } 2493 } 2494