diff options
author | James Carlson <james.d.carlson@sun.com> | 2008-12-04 00:00:24 -0500 |
---|---|---|
committer | James Carlson <james.d.carlson@sun.com> | 2008-12-04 00:00:24 -0500 |
commit | ab32bdf2f746488f918233b2d8cabd5835efe9f3 (patch) | |
tree | 053e34a412a8482412dd81fd3cd0d85991764b67 | |
parent | 5c9d25d25ae7531d61aca4904f76e3dae2f457bf (diff) | |
download | illumos-joyent-ab32bdf2f746488f918233b2d8cabd5835efe9f3.tar.gz |
PSARC 2008/736 NWAM Picea Addenda
6761570 switching llp leaves unusable IPv6 addresses and routes configured
6766807 nwamd should trigger wireless scan/check on link down
6770812 nwamd can get trapped with link stuck down
6772544 nwamd errantly adds auto-conf to known WiFi list
6773115 nwamd needs to deal with scanning-related instability
6773627 nwamd should be less interested in BSSID
6776888 libnwam needs new API to allow keys to be set for hidden APs
-rw-r--r-- | usr/src/cmd/cmd-inet/lib/nwamd/door.c | 2 | ||||
-rw-r--r-- | usr/src/cmd/cmd-inet/lib/nwamd/events.c | 15 | ||||
-rw-r--r-- | usr/src/cmd/cmd-inet/lib/nwamd/functions.h | 5 | ||||
-rw-r--r-- | usr/src/cmd/cmd-inet/lib/nwamd/interface.c | 25 | ||||
-rw-r--r-- | usr/src/cmd/cmd-inet/lib/nwamd/main.c | 59 | ||||
-rw-r--r-- | usr/src/cmd/cmd-inet/lib/nwamd/state_machine.c | 41 | ||||
-rw-r--r-- | usr/src/cmd/cmd-inet/lib/nwamd/structures.h | 1 | ||||
-rw-r--r-- | usr/src/cmd/cmd-inet/lib/nwamd/variables.h | 1 | ||||
-rw-r--r-- | usr/src/cmd/cmd-inet/lib/nwamd/wireless.c | 818 | ||||
-rw-r--r-- | usr/src/cmd/svc/milestone/network-physical.xml | 1 | ||||
-rw-r--r-- | usr/src/lib/libnwam/common/door.c | 13 | ||||
-rw-r--r-- | usr/src/lib/libnwam/common/libnwam.h | 3 | ||||
-rw-r--r-- | usr/src/lib/libnwam/common/mapfile-vers | 1 |
13 files changed, 762 insertions, 223 deletions
diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/door.c b/usr/src/cmd/cmd-inet/lib/nwamd/door.c index c2481a93f9..6143487040 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/door.c +++ b/usr/src/cmd/cmd-inet/lib/nwamd/door.c @@ -824,7 +824,7 @@ nwam_door_server(void *cookie, char *argp, size_t arg_size, door_desc_t *dp, dprintf("door: selecting WLAN key on %s for %s %s", ndc->ndc_interface, ndc->ndc_essid, ndc->ndc_bssid); retv = set_wlan_key(ndc->ndc_interface, ndc->ndc_essid, - ndc->ndc_bssid, ndc->ndc_key); + ndc->ndc_bssid, ndc->ndc_key, ndc->ndc_secmode); (void) pthread_mutex_unlock(&machine_lock); break; diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/events.c b/usr/src/cmd/cmd-inet/lib/nwamd/events.c index 845e98fa97..743726ba55 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/events.c +++ b/usr/src/cmd/cmd-inet/lib/nwamd/events.c @@ -632,12 +632,17 @@ start_event_collection(void) dprintf("routing thread: %d", routing); } - if (err = pthread_create(&scan, NULL, periodic_wireless_scan, NULL)) { - syslog(LOG_ERR, "pthread_create wireless scan: %s", - strerror(err)); - exit(EXIT_FAILURE); + if (wlan_scan_interval != 0) { + err = pthread_create(&scan, NULL, periodic_wireless_scan, NULL); + if (err != 0) { + syslog(LOG_ERR, "pthread_create wireless scan: %s", + strerror(err)); + exit(EXIT_FAILURE); + } else { + dprintf("wireless scan thread: %d", scan); + } } else { - dprintf("scan thread: %d", scan); + dprintf("periodic wireless scan disabled"); } /* diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/functions.h b/usr/src/cmd/cmd-inet/lib/nwamd/functions.h index 2c429fcefb..3b9fcbcfa7 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/functions.h +++ b/usr/src/cmd/cmd-inet/lib/nwamd/functions.h @@ -85,6 +85,7 @@ extern void print_interface_status(void); /* wireless.c: wifi link handling */ extern void initialize_wireless(void); +extern void terminate_wireless(void); extern void add_wireless_if(const char *); extern void remove_wireless_if(const char *); extern struct wireless_lan *prompt_for_visited(void); @@ -92,10 +93,12 @@ extern return_vals_t handle_wireless_lan(const char *); extern libnwam_known_ap_t *get_known_ap_list(size_t *, uint_t *); extern int add_known_ap(const char *, const char *); extern int delete_known_ap(const char *, const char *); +extern void wireless_verify(const char *); extern void *periodic_wireless_scan(void *); extern boolean_t check_wlan_connected(const char *, const char *, const char *); extern int set_specific_lan(const char *, const char *, const char *); -extern int set_wlan_key(const char *, const char *, const char *, const char *); +extern int set_wlan_key(const char *, const char *, const char *, const char *, + const char *); extern int launch_wireless_scan(const char *); extern void disconnect_wlan(const char *); extern void get_wireless_state(const char *, boolean_t *, boolean_t *); diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/interface.c b/usr/src/cmd/cmd-inet/lib/nwamd/interface.c index 397990d935..877a07b1e0 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/interface.c +++ b/usr/src/cmd/cmd-inet/lib/nwamd/interface.c @@ -41,6 +41,11 @@ * IPv6 is brought up in addition to IPv4 (assuming the LLP configuration * includes IPv6; this is the default for automatic configuration). * + * When an interface is taken down, we unplumb the IPv6 link-local interface + * completely, so that dhcpagent and in.ndpd will remove any addresses they've + * added. Events are watched on the IPv4 interface alone, which is always + * present for this version of NWAM. + * * Interfaces are brought up and torn down by a sequence of ifconfig * commands (currently posix_spawn'd() by nwamd; the longer-term direction * here is to use libinetcfg). @@ -656,17 +661,28 @@ takedowninterface(const char *ifname, libnwam_diag_cause_t cause) report_interface_down(ifname, cause); if (ifp != NULL) { - if (ifp->if_type == IF_WIRELESS) + /* We're no longer expecting the interface to be up */ + ifp->if_flags = flags & ~IFF_UP; + if (ifp->if_type == IF_WIRELESS) { + /* and if it's wireless, it's not running, either */ + ifp->if_flags &= ~IFF_RUNNING; disconnect_wlan(ifp->if_name); + } dprintf("takedown interface, zero cached ip address"); - ifp->if_flags = flags; ifp->if_lflags &= ~IF_DHCPSTARTED & ~IF_DHCPACQUIRED; ifp->if_ipv4addr = INADDR_ANY; ifp->if_up_attempted = B_FALSE; } } -/* Called only in the main thread */ +/* + * Called only in the main thread + * + * For IPv6, unplumbing the link local interface causes dhcp and ndpd to remove + * other addresses they have added. We watch for routing socket events on the + * IPv4 interface, which is always enabled, so no need to keep IPv6 around on a + * switch. + */ void clear_cached_address(const char *ifname) { @@ -676,8 +692,11 @@ clear_cached_address(const char *ifname) if ((ifp = get_interface(ifname)) == NULL) { dprintf("clear_cached_address: can't find interface struct " "for %s", ifname); + (void) start_child(IFCONFIG, ifname, "inet6", "unplumb", NULL); return; } + if (ifp->if_v6onlink) + (void) start_child(IFCONFIG, ifname, "inet6", "unplumb", NULL); ifflags = get_ifflags(ifname, AF_INET); if ((ifflags & IFF_UP) && !(ifflags & IFF_RUNNING)) zero_out_v4addr(ifname); diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/main.c b/usr/src/cmd/cmd-inet/lib/nwamd/main.c index 0b78b0aa2e..ccc110ce09 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/main.c +++ b/usr/src/cmd/cmd-inet/lib/nwamd/main.c @@ -161,6 +161,7 @@ lookup_daemon_properties(void) boolean_t debug_set; uint64_t scan_interval; uint64_t idle_time; + boolean_t strict_bssid_set; if (lookup_boolean_property(OUR_PG, "debug", &debug_set) == 0) debug = debug_set; @@ -168,6 +169,9 @@ lookup_daemon_properties(void) wlan_scan_interval = scan_interval; if (lookup_count_property(OUR_PG, "idle_time", &idle_time) == 0) door_idle_time = idle_time; + if (lookup_boolean_property(OUR_PG, "strict_bssid", + &strict_bssid_set) == 0) + strict_bssid = strict_bssid_set; dprintf("Read daemon configuration properties."); } @@ -176,7 +180,7 @@ static void * sighandler(void *arg) { sigset_t sigset; - int sig; + int sig, err; uint32_t now; (void) sigfillset(&sigset); @@ -201,6 +205,21 @@ sighandler(void *arg) * Refresh action - reread configuration properties. */ lookup_daemon_properties(); + /* + * Check if user restarted scanning. + */ + if (scan == 0 && wlan_scan_interval != 0) { + err = pthread_create(&scan, NULL, + periodic_wireless_scan, NULL); + if (err != 0) { + syslog(LOG_NOTICE, + "pthread_create wireless scan: %s", + strerror(err)); + } else { + dprintf("wireless scan thread: %d", + scan); + } + } break; case SIGINT: /* @@ -210,6 +229,23 @@ sighandler(void *arg) print_interface_status(); print_wireless_status(); break; + case SIGTHAW: + /* + * It seems unlikely that this is helpful, but it can't + * hurt: when waking up from a sleep, check if the + * wireless interface is still viable. There've been + * bugs in this area. + */ + if (pthread_mutex_lock(&machine_lock) == 0) { + if (link_layer_profile != NULL && + link_layer_profile->llp_type == + IF_WIRELESS) { + wireless_verify( + link_layer_profile->llp_lname); + } + (void) pthread_mutex_unlock(&machine_lock); + } + break; default: syslog(LOG_NOTICE, "%s received, shutting down", strsignal(sig)); @@ -225,14 +261,29 @@ sighandler(void *arg) return (NULL); } +/* ARGSUSED */ +static void +sigdummy(int sig) +{ +} + static void init_signalhandling(void) { + struct sigaction act; pthread_attr_t attr; pthread_t sighand; int err; sigset_t new; + /* + * The default is to ignore, so we need a dummy handler. + */ + (void) memset(&act, 0, sizeof (act)); + act.sa_handler = sigdummy; + act.sa_flags = SA_RESTART; + (void) sigaction(SIGTHAW, &act, NULL); + (void) sigfillset(&new); (void) pthread_sigmask(SIG_BLOCK, &new, &original_sigmask); (void) pthread_attr_init(&attr); @@ -399,9 +450,11 @@ main(int argc, char *argv[]) } syslog(LOG_DEBUG, "terminating routing and scanning threads"); (void) pthread_cancel(routing); - (void) pthread_cancel(scan); (void) pthread_join(routing, NULL); - (void) pthread_join(scan, NULL); + if (scan != 0) { + (void) pthread_cancel(scan); + (void) pthread_join(scan, NULL); + } syslog(LOG_INFO, "nwamd shutting down"); return (EXIT_SUCCESS); } diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/state_machine.c b/usr/src/cmd/cmd-inet/lib/nwamd/state_machine.c index 81bf6210c7..469f5f54f2 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/state_machine.c +++ b/usr/src/cmd/cmd-inet/lib/nwamd/state_machine.c @@ -186,6 +186,42 @@ state_machine(struct np_event *e) e->npe_type == EV_LINKDISC ? dcGone : e->npe_type == EV_USER ? dcUser : dcBetter); + } else if (prefllp == evllp) { + /* + * If this is a negative event on our preferred link, + * then we need to pay closer attention. (We can + * ignore negative events on other links.) + */ + switch (e->npe_type) { + case EV_LINKFADE: + case EV_LINKDISC: + /* + * If the link has faded or disconnected, then + * it's a wireless link, and something has gone + * wrong with the connection to the AP. The + * above tests mean that we do intend to stay + * with this link for now, so we have to + * recover it by attempting to reconnect. + * Invoking reselect will do that by calling + * bringupinterface. + */ + dprintf("disconnect on preferred llp; " + "attempting reconnect"); + prefllp->llp_waiting = B_TRUE; + llp_reselect(); + break; + case EV_LINKDROP: + /* + * If link status has dropped on a wireless + * interface, then we need to check whether + * we're still connected. We're probably not, + * and this will cause us to attempt + * reconnection. + */ + if (prefllp->llp_type == IF_WIRELESS) + wireless_verify(e->npe_name); + break; + } } if (e->npe_type == EV_USER) llp_write_changed_priority(evllp); @@ -376,10 +412,11 @@ state_machine(struct np_event *e) void cleanup(void) { + terminate_wireless(); deactivate_upper_layer_profile(); - if (link_layer_profile != NULL) { + if (link_layer_profile != NULL) takedowninterface(link_layer_profile->llp_lname, dcShutdown); - } + /* * Since actions taken in nwamd result in dhcpagent being * launched, it's under our contract. Thus, it needs to be diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/structures.h b/usr/src/cmd/cmd-inet/lib/nwamd/structures.h index 94d750a3fe..0679df44ce 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/structures.h +++ b/usr/src/cmd/cmd-inet/lib/nwamd/structures.h @@ -246,6 +246,7 @@ typedef struct nwam_door_cmd_s { char ndc_essid[DLADM_STRSIZE]; char ndc_bssid[DLADM_STRSIZE]; char ndc_key[DLADM_STRSIZE]; + char ndc_secmode[DLADM_STRSIZE]; } nwam_door_cmd_t; typedef struct nwam_llp_data_s { diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/variables.h b/usr/src/cmd/cmd-inet/lib/nwamd/variables.h index a9efd4ee4c..6e3d3a366f 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/variables.h +++ b/usr/src/cmd/cmd-inet/lib/nwamd/variables.h @@ -48,6 +48,7 @@ extern uint32_t timer_expire; extern uint_t wlan_scan_interval; extern dladm_wlan_strength_t wireless_scan_level; +extern boolean_t strict_bssid; extern uint_t door_idle_time; diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c b/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c index 4be6af66c8..4e739ee61a 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c +++ b/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c @@ -59,6 +59,16 @@ * periodically to look for available APs. In both cases, if there are * new APs, the above AP connection procedure will be performed. * + * As a way to deal with the innumerable bugs that seem to plague wireless + * interfaces with respect to concurrent operations, we completely exclude all + * connect operations on all interfaces when another connect or scan is + * running, and exclude all scans on all interfaces when another connect or + * scan is running. This is done using wifi_scan_intf. + * + * Much of the BSSID handling logic in this module is questionable due to + * underlying bugs such as CR 6772510. There's likely little that we can do + * about this. + * * Lock ordering note: wifi_mutex and wifi_init_mutex are not held at the same * time. */ @@ -116,7 +126,8 @@ static uint_t wi_link_count; * to store the interface doing the scan. It is protected by * wifi_init_mutex. */ -static char wifi_scan_intf[LIFNAMSIZ]; +static const char *wifi_scan_intf; +static boolean_t connect_running; static pthread_mutex_t wifi_init_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t wifi_init_cond = PTHREAD_COND_INITIALIZER; @@ -138,8 +149,6 @@ static struct wireless_lan *add_wlan_entry(const char *, const char *, const char *, dladm_wlan_attr_t *); static boolean_t check_wlan(const wireless_if_t *, const char *, const char *, boolean_t); -static return_vals_t connect_or_autoconf(struct wireless_lan *, - wireless_if_t *); static struct wireless_lan *find_wlan_entry(const char *, const char *, const char *); static void free_wireless_lan(struct wireless_lan *); @@ -164,6 +173,12 @@ uint_t wlan_scan_interval = 120; */ dladm_wlan_strength_t wireless_scan_level = DLADM_WLAN_STRENGTH_VERY_WEAK; +/* + * This controls whether we are strict about matching BSSID in the known wifi + * networks file. By default, we're not strict. + */ +boolean_t strict_bssid; + void initialize_wireless(void) { @@ -435,55 +450,82 @@ unexpected: } /* - * Examine all WLANs associated with an interface, and update the 'connected' - * and 'known' attributes appropriately. The caller holds wifi_mutex. + * Examine all WLANs associated with an interface, verify the expected WLAN, + * and update the 'connected' attribute appropriately. The caller holds + * wifi_mutex and deals with the 'known' flag. If the expected WLAN is NULL, + * then we expect to be connected to just "any" (autoconf) network. */ static boolean_t -update_connected_wlan(wireless_if_t *wip) +update_connected_wlan(wireless_if_t *wip, struct wireless_lan *exp_wlan) { dladm_wlan_linkattr_t attr; struct wireless_lan *wlan, *lastconn, *newconn; char essid[DLADM_STRSIZE]; char bssid[DLADM_STRSIZE]; boolean_t connected, wasconn; - int retries = 0; - /* - * This is awful, but some wireless drivers (particularly 'ath') will - * erroneously report "disconnected" if queried right after a scan. If - * we see 'down' reported here, we retry a few times to make sure. - */ - while (retries++ < 4) { - if (dladm_wlan_get_linkattr(wip->wi_linkid, &attr) != - DLADM_STATUS_OK) - attr.la_status = DLADM_WLAN_LINK_DISCONNECTED; - else if (attr.la_status == DLADM_WLAN_LINK_CONNECTED) - break; - } + if (dladm_wlan_get_linkattr(wip->wi_linkid, &attr) != DLADM_STATUS_OK) + attr.la_status = DLADM_WLAN_LINK_DISCONNECTED; if (attr.la_status == DLADM_WLAN_LINK_CONNECTED) { (void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid, essid); (void) dladm_wlan_bssid2str(&attr.la_wlan_attr.wa_bssid, bssid); connected = B_TRUE; wip->wi_wireless_done = B_TRUE; - dprintf("update: %s is connected to %s %s", wip->wi_name, essid, - bssid); + dprintf("update: %s reports connection to %s %s", wip->wi_name, + essid, bssid); } else { connected = B_FALSE; dprintf("update: %s is currently unconnected", wip->wi_name); } + + /* + * First, verify that if we're connected, then we should be and that + * we're connected to the expected AP. + */ + if (exp_wlan != NULL) { + /* + * If we're connected to the wrong one, then disconnect. Note: + * we'd like to verify BSSID, but we cannot due to CR 6772510. + */ + if (connected && strcmp(exp_wlan->essid, essid) != 0) { + dprintf("update: wrong AP on %s; expected %s %s", + exp_wlan->wl_if_name, exp_wlan->essid, + exp_wlan->bssid); + (void) dladm_wlan_disconnect(wip->wi_linkid); + connected = B_FALSE; + } + /* If we're not in the expected state, then report disconnect */ + if (exp_wlan->connected != connected) { + exp_wlan->connected = B_FALSE; + if (connected) { + dprintf("update: unexpected connection to %s " + "%s; clearing", essid, bssid); + (void) dladm_wlan_disconnect(wip->wi_linkid); + } else { + dprintf("update: not connected to %s %s as " + "expected", exp_wlan->essid, + exp_wlan->bssid); + report_wlan_disconnect(exp_wlan); + } + connected = B_FALSE; + } + } + + /* + * State is now known to be good, so make the list entries match. + */ wasconn = B_FALSE; lastconn = newconn = NULL; for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) { if (strcmp(wlan->wl_if_name, wip->wi_name) != 0) continue; - if (connected && strcmp(wlan->essid, essid) == 0 && - strcmp(wlan->bssid, bssid) == 0) { + /* missing bssid check */ + if (connected && strcmp(wlan->essid, essid) == 0) { wasconn = wlan->connected; wlan->connected = connected; newconn = wlan; - } else { - if (wlan->connected) - lastconn = wlan; + } else if (wlan->connected) { + lastconn = wlan; wlan->connected = B_FALSE; } } @@ -495,30 +537,97 @@ update_connected_wlan(wireless_if_t *wip) } if (lastconn != NULL) report_wlan_disconnect(lastconn); - if (newconn != NULL && !wasconn && connected) { - /* - * If we're already connected but not yet known, then it's - * clear that this is a "known wlan" for the user. He must - * have issued a dladm connect-wifi command to get here. - */ - if (!newconn->known) { - newconn->known = B_TRUE; - (void) add_known_wifi_nets_file(newconn->essid, - newconn->bssid); - } + if (newconn != NULL && !wasconn && connected) report_wlan_connected(newconn); - } return (connected); } /* + * If there is already a scan or connect in progress, defer until the operation + * is done to avoid radio interference *and* significant driver bugs. + * + * Returns B_TRUE when the lock is taken and the caller must call + * scanconnect_exit. Returns B_FALSE when lock not taken; caller must not call + * scanconnect_exit. + * + * If we happen to be doing a scan, and the interface doing the scan is the + * same as the one requesting a new scan, then wait for it to finish, and then + * report that we're done by returning B_FALSE (no lock taken). + */ +static boolean_t +scanconnect_entry(const char *ifname, boolean_t is_connect) +{ + boolean_t already_done; + + if (pthread_mutex_lock(&wifi_init_mutex) != 0) + return (B_FALSE); + already_done = B_FALSE; + while (wifi_scan_intf != NULL) { + dprintf("%s in progress on %s; blocking %s of %s", + connect_running ? "connect" : "scan", wifi_scan_intf, + is_connect ? "connect" : "scan", ifname); + if (!is_connect && !connect_running && + strcmp(wifi_scan_intf, ifname) == 0) + already_done = B_TRUE; + (void) pthread_cond_wait(&wifi_init_cond, &wifi_init_mutex); + if (already_done || shutting_down) { + (void) pthread_mutex_unlock(&wifi_init_mutex); + return (B_FALSE); + } + } + dprintf("now exclusively %s on %s", + is_connect ? "connecting" : "scanning", ifname); + wifi_scan_intf = ifname; + connect_running = is_connect; + (void) pthread_mutex_unlock(&wifi_init_mutex); + return (B_TRUE); +} + +static void +scanconnect_exit(void) +{ + (void) pthread_mutex_lock(&wifi_init_mutex); + dprintf("done exclusively %s on %s", + connect_running ? "connecting" : "scanning", wifi_scan_intf); + wifi_scan_intf = NULL; + (void) pthread_cond_broadcast(&wifi_init_cond); + (void) pthread_mutex_unlock(&wifi_init_mutex); +} + +/* + * Return B_TRUE if we're in the midst of connecting on a given wireless + * interface. We shouldn't try to take such an interface down. + */ +static boolean_t +connecting_on(const char *ifname) +{ + boolean_t in_progress; + + if (pthread_mutex_lock(&wifi_init_mutex) != 0) + return (B_FALSE); + in_progress = (wifi_scan_intf != NULL && connect_running && + strcmp(ifname, wifi_scan_intf) == 0); + (void) pthread_mutex_unlock(&wifi_init_mutex); + return (in_progress); +} + +/* + * Terminate all waiting transient threads as soon as possible. This assumes + * that the shutting_down flag has already been set. + */ +void +terminate_wireless(void) +{ + (void) pthread_cond_broadcast(&wifi_init_cond); +} + +/* * Given a wireless interface, use it to scan for available networks. The * caller must not hold wifi_mutex. */ static void scan_wireless_nets(const char *ifname) { - boolean_t already_done; boolean_t dropped; boolean_t new_found; dladm_status_t status; @@ -527,27 +636,11 @@ scan_wireless_nets(const char *ifname) wireless_if_t *wip; /* - * If there is already a scan in progress, defer until the scan is done - * to avoid radio interference. But if the interface doing the scan is - * the same as the one requesting the new scan, then wait for it to - * finish, and then we're done. + * Wait for scan/connect to finish, and return if error or if this + * interface is already done. */ - if (pthread_mutex_lock(&wifi_init_mutex) != 0) + if (!scanconnect_entry(ifname, B_FALSE)) return; - already_done = B_FALSE; - while (wifi_scan_intf[0] != '\0') { - dprintf("scan_wireless_nets in progress: old %s new %s", - wifi_scan_intf, ifname); - if (strcmp(wifi_scan_intf, ifname) == 0) - already_done = B_TRUE; - (void) pthread_cond_wait(&wifi_init_cond, &wifi_init_mutex); - if (already_done) { - (void) pthread_mutex_unlock(&wifi_init_mutex); - return; - } - } - (void) strlcpy(wifi_scan_intf, ifname, sizeof (wifi_scan_intf)); - (void) pthread_mutex_unlock(&wifi_init_mutex); /* Grab the linkid from the wireless interface */ if (pthread_mutex_lock(&wifi_mutex) != 0) @@ -587,18 +680,93 @@ scan_end: /* Need to sample this global before clearing out scan lock */ new_found = new_ap_found; + /* + * Due to common driver bugs, it's necessary to check the state of the + * interface right after doing a scan. If it's connected and we didn't + * expect it to be, or if we're accidentally connected to the wrong AP, + * then disconnect now and reconnect. + */ if (pthread_mutex_lock(&wifi_mutex) == 0) { if ((wip = find_wireless_if(ifname)) != NULL) { + dladm_wlan_linkattr_t attr; + struct wireless_lan *wlan; + char essid[DLADM_STRSIZE]; + char bssid[DLADM_STRSIZE]; + boolean_t connected; + int retries = 0; + wip->wi_scan_running = B_FALSE; - (void) update_connected_wlan(wip); + + /* + * This is awful, but some wireless drivers + * (particularly 'ath') will erroneously report + * "disconnected" if queried right after a scan. If we + * see 'down' reported here, we retry a few times to + * make sure it's really down. + */ + while (retries++ < 4) { + if (dladm_wlan_get_linkattr(wip->wi_linkid, + &attr) != DLADM_STATUS_OK) + attr.la_status = + DLADM_WLAN_LINK_DISCONNECTED; + else if (attr.la_status == + DLADM_WLAN_LINK_CONNECTED) + break; + } + if (attr.la_status == DLADM_WLAN_LINK_CONNECTED) { + (void) dladm_wlan_essid2str( + &attr.la_wlan_attr.wa_essid, essid); + (void) dladm_wlan_bssid2str( + &attr.la_wlan_attr.wa_bssid, bssid); + connected = B_TRUE; + dprintf("scan: %s reports connection to %s " + "%s", ifname, essid, bssid); + } else { + connected = B_FALSE; + dprintf("scan: %s is currently unconnected", + ifname); + } + /* Disconnect from wrong AP first */ + for (wlan = wlans; wlan < wlans + wireless_lan_used; + wlan++) { + if (strcmp(wlan->wl_if_name, ifname) != 0) + continue; + /* missing bssid check */ + if (strcmp(wlan->essid, essid) == 0) { + /* + * This is the one we are currently + * connected to. See if we should be + * here. + */ + if (!connected || !wlan->connected) + (void) dladm_wlan_disconnect( + linkid); + break; + } + } + /* Connect to right AP by reporting disconnect */ + for (wlan = wlans; wlan < wlans + wireless_lan_used; + wlan++) { + if (strcmp(wlan->wl_if_name, ifname) != 0) + continue; + if (wlan->connected) { + /* missing bssid check */ + if (connected && + strcmp(wlan->essid, essid) == 0) + break; + /* + * We weren't where we were supposed to + * be. Try to reconnect now. + */ + (void) np_queue_add_event(EV_LINKDISC, + ifname); + } + } } (void) pthread_mutex_unlock(&wifi_mutex); } - (void) pthread_mutex_lock(&wifi_init_mutex); - wifi_scan_intf[0] = '\0'; - (void) pthread_cond_broadcast(&wifi_init_cond); - (void) pthread_mutex_unlock(&wifi_init_mutex); + scanconnect_exit(); if (status == DLADM_STATUS_OK) report_scan_complete(ifname, dropped || new_found, wlans, @@ -766,25 +934,105 @@ get_scan_results(void *arg, dladm_wlan_attr_t *attrp) return (retv); } -/* ARGSUSED */ -void * -periodic_wireless_scan(void *arg) +/* + * This is called when IP reports that the link layer is down. It just + * verifies that we're still connected as expected. If not, then cover for the + * known driver bugs (by disconnecting) and send an event so that we'll attempt + * to recover. No scan is done; if a scan is needed, we'll do one the next + * time the timer pops. + * + * Note that we don't retry in case of error. Since IP has reported the + * interface as down, the best case here is that we detect a link failure and + * start the connection process over again. + */ +void +wireless_verify(const char *ifname) { + datalink_id_t linkid; + dladm_wlan_linkattr_t attr; + wireless_if_t *wip; + struct wireless_lan *wlan; + boolean_t is_failure; + /* - * No periodic scan if the "-i" option is used to change the - * interval to 0. + * If these calls fail, it means that the wireless link is down. */ - if (wlan_scan_interval == 0) - return (NULL); + if (dladm_name2info(ifname, &linkid, NULL, NULL, NULL) != + DLADM_STATUS_OK || + dladm_wlan_get_linkattr(linkid, &attr) != DLADM_STATUS_OK) { + attr.la_status = DLADM_WLAN_LINK_DISCONNECTED; + } + + /* + * If the link is down, then work around a known driver bug (by forcing + * disconnect), and then deliver an event so that the state machine can + * retry. + */ + if (attr.la_status != DLADM_WLAN_LINK_CONNECTED) { + if (connecting_on(ifname)) + return; + is_failure = B_TRUE; + if (pthread_mutex_lock(&wifi_mutex) == 0) { + if ((wip = find_wireless_if(ifname)) != NULL) { + /* + * Link down while waiting for user to supply + * key is *not* a failure case. + */ + if (!wip->wi_wireless_done && + wip->wi_need_key) { + is_failure = B_FALSE; + } else { + wip->wi_wireless_done = B_FALSE; + wip->wi_need_key = B_FALSE; + } + } + if (is_failure) { + for (wlan = wlans; + wlan < wlans + wireless_lan_used; wlan++) { + if (strcmp(wlan->wl_if_name, ifname) == + 0) { + if (wlan->connected) + report_wlan_disconnect( + wlan); + wlan->connected = B_FALSE; + } + } + } + (void) pthread_mutex_unlock(&wifi_mutex); + } + if (is_failure) { + dprintf("wireless check indicates disconnect"); + (void) dladm_wlan_disconnect(linkid); + (void) np_queue_add_event(EV_LINKDISC, ifname); + } + } +} +/* ARGSUSED */ +void * +periodic_wireless_scan(void *arg) +{ for (;;) { - int ret; + int ret, intv; dladm_wlan_linkattr_t attr; char ifname[LIFNAMSIZ]; libnwam_interface_type_t ift; datalink_id_t linkid; + char essid[DLADM_STRSIZE]; + struct wireless_lan *wlan; - ret = poll(NULL, 0, wlan_scan_interval * MILLISEC); + /* + * Stop the scanning process if the user changes the interval + * to zero dynamically. Reset the thread ID to a known-invalid + * value. (Copy to a local variable to avoid race condition in + * case SIGINT hits between this test and the call to poll().) + */ + if ((intv = wlan_scan_interval) == 0) { + dprintf("periodic wireless scan halted"); + break; + } + + ret = poll(NULL, 0, intv * MILLISEC); if (ret == -1) { if (errno == EINTR) continue; @@ -792,6 +1040,15 @@ periodic_wireless_scan(void *arg) break; } + /* + * Just one more check before doing a scan that might now be + * unwanted + */ + if (wlan_scan_interval == 0) { + dprintf("periodic wireless scan halted"); + break; + } + /* Get current profile name, if any */ llp_get_name_and_type(ifname, sizeof (ifname), &ift); @@ -821,7 +1078,32 @@ periodic_wireless_scan(void *arg) if (attr.la_status == DLADM_WLAN_LINK_CONNECTED && attr.la_wlan_attr.wa_strength > wireless_scan_level) { - continue; + /* + * Double-check the ESSID. Some drivers + * (notably 'iwh') have a habit of randomly + * reconnecting themselves to APs that you + * never requested. + */ + (void) dladm_wlan_essid2str( + &attr.la_wlan_attr.wa_essid, essid); + if (pthread_mutex_lock(&wifi_mutex) != 0) + continue; + for (wlan = wlans; + wlan < wlans + wireless_lan_used; wlan++) { + if (wlan->connected && + strcmp(wlan->wl_if_name, ifname) == + 0) + break; + } + if (wlan >= wlans + wireless_lan_used || + strcmp(wlan->essid, essid) == 0) { + (void) pthread_mutex_unlock( + &wifi_mutex); + continue; + } + dprintf("%s is connected to %s instead of %s", + ifname, essid, wlan->essid); + (void) pthread_mutex_unlock(&wifi_mutex); } } @@ -846,6 +1128,8 @@ periodic_wireless_scan(void *arg) wifi_mutex); continue; } + wip->wi_wireless_done = B_FALSE; + wip->wi_need_key = B_FALSE; } (void) pthread_mutex_unlock(&wifi_mutex); @@ -857,17 +1141,16 @@ periodic_wireless_scan(void *arg) (void) dladm_wlan_disconnect(linkid); /* - * Deactivate the original AP. If we reached this - * point, we either were not connected, or were - * connected with "very weak" signal strength; so we're - * assuming that having this llp active was not very - * useful. So we deactivate. + * Tell the state machine that we've lost this link so + * that it can do something about the problem. */ (void) np_queue_add_event( (attr.la_status == DLADM_WLAN_LINK_CONNECTED ? EV_LINKFADE : EV_LINKDISC), ifname); } } + scan = 0; + (void) pthread_detach(pthread_self()); return (NULL); } @@ -1259,12 +1542,23 @@ known_wifi_nets_lookup(const char *new_essid, const char *new_bssid, } /* + * If we're searching on ESSID alone, then any match on a + * specific ESSID will do. + */ + if (*new_bssid == '\0') { + if (*new_essid != '\0' && + strcmp(tok[ESSID], new_essid) == 0) { + found = B_TRUE; + break; + } + } + /* * If BSSID match is found we check ESSID, which should * either match as well, or be an empty string. * In latter case we'll retrieve the ESSID from known_wifi_nets * later. */ - if (strcmp(tok[BSSID], new_bssid) == 0) { + else if (strcmp(tok[BSSID], new_bssid) == 0) { /* * Got BSSID match, either ESSID was not specified, * or it should match @@ -1490,6 +1784,8 @@ connect_chosen_lan(struct wireless_lan *reqlan, wireless_if_t *wip) return (FAILURE); } attr.wa_valid = DLADM_WLAN_ATTR_ESSID; + + /* note: bssid logic here is non-functional */ if (reqlan->bssid[0] != '\0') { if (dladm_wlan_str2bssid(reqlan->bssid, &attr.wa_bssid) != DLADM_STATUS_OK) { @@ -1529,6 +1825,10 @@ connect_chosen_lan(struct wireless_lan *reqlan, wireless_if_t *wip) keycount, flags); dprintf("connect_chosen_lan: dladm_wlan_connect returned %s", dladm_status2str(status, errmsg)); + /* + * This doesn't work due to CR 6772510. + */ +#ifdef CR6772510_FIXED if (status == DLADM_STATUS_TIMEDOUT && reqlan->bssid[0] != '\0') { syslog(LOG_INFO, "connect_chosen_lan: failed for (%s, %s), " "trying again with just (%s)", @@ -1538,6 +1838,7 @@ connect_chosen_lan(struct wireless_lan *reqlan, wireless_if_t *wip) status = dladm_wlan_connect(wip->wi_linkid, &attr, timeout, key, keycount, flags); } +#endif /* CR6772510_FIXED */ if (status == DLADM_STATUS_OK) { return (SUCCESS); } else { @@ -1550,27 +1851,6 @@ connect_chosen_lan(struct wireless_lan *reqlan, wireless_if_t *wip) } /* - * First attempt to connect to the network specified by essid. - * If that fails, attempt to connect using autoconf. - */ -static return_vals_t -connect_or_autoconf(struct wireless_lan *reqlan, wireless_if_t *wip) -{ - return_vals_t rval; - - rval = connect_chosen_lan(reqlan, wip); - if (rval == FAILURE) { - report_wlan_connect_fail(wip->wi_name); - reqlan->rescan = B_TRUE; - syslog(LOG_WARNING, - "Could not connect to chosen WLAN %s, going to auto-conf", - reqlan->essid); - rval = (wlan_autoconf(wip) ? SUCCESS : FAILURE); - } - return (rval); -} - -/* * Check that the wireless LAN is connected to the desired ESSID/BSSID. This * is used by the GUI to check for connectivity before doing anything * destructive. @@ -1596,132 +1876,194 @@ check_wlan_connected(const char *ifname, const char *essid, const char *bssid) } /* - * This is the entry point for GUI "select access point" requests. We attempt - * to do what the GUI requested. If we fail, then there will be a new request - * enqueued for the GUI to act on. - * Returns: - * 0 - ok (or more data requested with new event) - * ENXIO - no such interface - * ENODEV - requested access point unknown - * EINVAL - failed to perform requested action + * This thread performs the blocking actions related to a wireless connection + * request. The attempt to connect isn't started until all other connects and + * scans have finished, and while the connect is in progress, no new connects + * or scans can be started. */ -int -set_specific_lan(const char *ifname, const char *essid, const char *bssid) +static void * +connect_thread(void *arg) { - libnwam_interface_type_t ift; + struct wireless_lan *req_wlan = arg; wireless_if_t *wip; - struct wireless_lan *wlan, local_wlan; - int retv; - boolean_t key_wait = B_FALSE; + struct wireless_lan *wlan = NULL; - ift = get_if_type(ifname); - if (ift != IF_UNKNOWN && ift != IF_WIRELESS) - return (EINVAL); + if (!scanconnect_entry(req_wlan->wl_if_name, B_TRUE)) + goto failure_noentry; - if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0) - return (retv); + if (pthread_mutex_lock(&wifi_mutex) != 0) + goto failure_unlocked; - if ((wip = find_wireless_if(ifname)) == NULL) { - retv = ENXIO; - goto done; - } + if ((wip = find_wireless_if(req_wlan->wl_if_name)) == NULL) + goto failure; /* This is an autoconf request. */ - if (essid[0] == '\0' && bssid[0] == '\0') { - retv = (wlan_autoconf(wip) ? 0 : EINVAL); - goto done; - } - - if ((wlan = find_wlan_entry(ifname, essid, bssid)) == NULL) { - local_wlan.essid = (char *)essid; - local_wlan.bssid = (char *)bssid; - wlan = &local_wlan; + if (req_wlan->essid[0] == '\0' && req_wlan->bssid[0] == '\0') { + if (!wlan_autoconf(wip) && !update_connected_wlan(wip, NULL)) + goto failure; + else + goto done; } - retv = 0; + wlan = find_wlan_entry(req_wlan->wl_if_name, req_wlan->essid, + req_wlan->bssid); + if (wlan == NULL) + wlan = req_wlan; /* * now attempt to connect to selection */ switch (connect_chosen_lan(wlan, wip)) { case WAITING: - key_wait = B_TRUE; break; - case SUCCESS: + case SUCCESS: { + dladm_status_t status; + dladm_wlan_linkattr_t attr; + char lclssid[DLADM_STRSIZE]; + char unnecessary_buf[DLADM_STRSIZE]; + /* - * Succeeded, so add entry to known_essid_list_file; - * but first make sure the wlan->bssid isn't empty. - * Note that empty bssid is never allocated. + * Successful connection to user-chosen AP; add entry to + * known_essid_list_file. First make sure the wlan->bssid + * isn't empty. Note that empty bssid is never allocated. + * + * We would like to query the driver only in the case where the + * BSSID is not known, but it turns out that due to CR 6772510, + * the actual BSSID we connect to is arbitrary. Nothing we can + * do about that; just get the new value and live with it. */ - if (wlan->bssid[0] == '\0') { - dladm_status_t status; - dladm_wlan_linkattr_t attr; - char lclbssid[DLADM_STRSIZE]; - - status = dladm_wlan_get_linkattr(wip->wi_linkid, - &attr); - - if (status == DLADM_STATUS_OK) { - (void) dladm_wlan_bssid2str( - &attr.la_wlan_attr.wa_bssid, lclbssid); - wlan->bssid = strdup(lclbssid); - } else { - dprintf("failed to get linkattr after " - "connecting to %s", wlan->essid); - } + status = dladm_wlan_get_linkattr(wip->wi_linkid, &attr); + if (status != DLADM_STATUS_OK) { + dprintf("failed to get linkattr on %s after connecting " + "to %s: %s", wlan->wl_if_name, wlan->essid, + dladm_status2str(status, unnecessary_buf)); + goto failure; } - if (wlan->bssid != NULL && wlan->bssid[0] != '\0') { - wlan->known = B_TRUE; - (void) add_known_wifi_nets_file(wlan->essid, - wlan->bssid); - if (wlan == &local_wlan && local_wlan.bssid != bssid) - free(local_wlan.bssid); - } else { + (void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid, + lclssid); + if (strcmp(req_wlan->essid, lclssid) != 0) { + dprintf("connected to strange network: expected %s got " + "%s", req_wlan->essid, lclssid); + goto failure; + } + (void) dladm_wlan_bssid2str(&attr.la_wlan_attr.wa_bssid, + lclssid); + if (wlan == req_wlan || strcmp(wlan->bssid, lclssid) != 0) { + wlan = add_wlan_entry(req_wlan->wl_if_name, + req_wlan->essid, lclssid, &attr.la_wlan_attr); + if (wlan == NULL) + goto failure; + } + if (wlan->bssid[0] == '\0' && lclssid[0] != '\0') + wlan->bssid = strdup(lclssid); + if (wlan->bssid == NULL || wlan->bssid[0] == '\0') { /* Don't leave it as NULL (for simplicity) */ wlan->bssid = ""; - retv = EINVAL; + goto failure; } + wlan->connected = B_TRUE; + if (!update_connected_wlan(wip, wlan)) + goto failure; + wlan->known = B_TRUE; + (void) add_known_wifi_nets_file(wlan->essid, wlan->bssid); + /* We're done; trigger IP bring-up. */ + (void) np_queue_add_event(EV_RESELECT, wlan->wl_if_name); + report_wlan_connected(wlan); break; + } default: - retv = EINVAL; - break; + goto failure; } done: - if (retv == 0 && !update_connected_wlan(wip)) - retv = EINVAL; - if (retv != 0) { - /* - * Failed to connect. Set 'rescan' flag so that we treat this - * AP as new if it's seen again, because the wireless radio may - * have just been off briefly while we were trying to connect. - */ - syslog(LOG_WARNING, "Could not connect to chosen WLAN %s", - wlan->essid); - report_wlan_connect_fail(ifname); + (void) pthread_mutex_unlock(&wifi_mutex); + scanconnect_exit(); + free_wireless_lan(req_wlan); + return (NULL); + +failure: + /* + * Failed to connect. Set 'rescan' flag so that we treat this AP as + * new if it's seen again, because the wireless radio may have just + * been off briefly while we were trying to connect. + */ + if (wip != NULL) { wip->wi_need_key = B_FALSE; wip->wi_wireless_done = B_FALSE; - wlan->rescan = B_TRUE; + (void) dladm_wlan_disconnect(wip->wi_linkid); } + if (wlan != NULL) + wlan->rescan = B_TRUE; (void) pthread_mutex_unlock(&wifi_mutex); - /* - * If this is the selected profile, then go ahead and bring up IP now. - */ - if (retv == 0 && !key_wait) - (void) np_queue_add_event(EV_RESELECT, ifname); +failure_unlocked: + scanconnect_exit(); +failure_noentry: + syslog(LOG_WARNING, "could not connect to chosen WLAN %s on %s", + req_wlan->essid, req_wlan->wl_if_name); + report_wlan_connect_fail(req_wlan->wl_if_name); + free_wireless_lan(req_wlan); + return (NULL); +} + +/* + * This is the entry point for GUI "select access point" requests. It verifies + * the parameters and then launches a new thread to perform the connect + * operation. When it returns success (0), the user should expect future + * events indicating progress. + * + * Returns: + * 0 - ok (or more data requested with new event) + * ENXIO - no such interface + * ENODEV - interface is not wireless + * EINVAL - failed to perform requested action + */ +int +set_specific_lan(const char *ifname, const char *essid, const char *bssid) +{ + libnwam_interface_type_t ift; + pthread_t conn_thr; + pthread_attr_t attr; + struct wireless_lan *wlan; + int retv; + + if ((ift = get_if_type(ifname)) == IF_UNKNOWN) + return (ENXIO); + if (ift != IF_WIRELESS) + return (EINVAL); + + if ((wlan = calloc(1, sizeof (struct wireless_lan))) == NULL) + return (ENOMEM); + (void) strlcpy(wlan->wl_if_name, ifname, sizeof (wlan->wl_if_name)); + wlan->essid = strdup(essid); + wlan->bssid = *bssid == '\0' ? "" : strdup(bssid); + if (wlan->essid == NULL || wlan->bssid == NULL) { + free_wireless_lan(wlan); + return (ENOMEM); + } + (void) pthread_attr_init(&attr); + (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + retv = pthread_create(&conn_thr, &attr, connect_thread, wlan); + if (retv == 0) + dprintf("started connect thread %d for %s %s %s", conn_thr, + ifname, essid, bssid); + else + free_wireless_lan(wlan); return (retv); } int set_wlan_key(const char *ifname, const char *essid, const char *bssid, - const char *key) + const char *key, const char *secmode) { libnwam_interface_type_t ift; - struct wireless_lan *wlan; + struct wireless_lan *wlan, local_wlan; + wireless_if_t *wip; int retv; + boolean_t need_key; + dladm_wlan_secmode_t smode = DLADM_WLAN_SECMODE_WEP; ift = get_if_type(ifname); if (ift == IF_UNKNOWN) @@ -1729,20 +2071,56 @@ set_wlan_key(const char *ifname, const char *essid, const char *bssid, if (ift != IF_WIRELESS) return (EINVAL); + if (*secmode != '\0' && + dladm_wlan_str2secmode(secmode, &smode) != DLADM_STATUS_OK) + return (EINVAL); + if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0) return (retv); - if ((wlan = find_wlan_entry(ifname, essid, bssid)) == NULL) - retv = ENODEV; - else if ((wlan->raw_key = strdup(key)) == NULL) - retv = ENOMEM; - else if (store_key(wlan) != 0) + if ((wlan = find_wlan_entry(ifname, essid, bssid)) == NULL) { + /* If not seen in scan, then secmode is required */ + if (*secmode == '\0') { + retv = ENODEV; + goto done; + } + /* Prohibit a completely blank entry */ + if (*essid == '\0' && *bssid == '\0') { + retv = EINVAL; + goto done; + } + (void) memset(&local_wlan, 0, sizeof (local_wlan)); + wlan = &local_wlan; + (void) strlcpy(wlan->wl_if_name, ifname, + sizeof (wlan->wl_if_name)); + wlan->essid = (char *)essid; + wlan->bssid = (char *)bssid; + wlan->raw_key = (char *)key; + wlan->attrs.wa_secmode = smode; + } else { + /* If seen in scan, then secmode given (if any) must match */ + if (*secmode != '\0' && smode != wlan->attrs.wa_secmode) { + retv = EINVAL; + goto done; + } + /* save a copy of the new key in the scan entry */ + if ((wlan->raw_key = strdup(key)) == NULL) { + retv = ENOMEM; + goto done; + } + } + + if (store_key(wlan) != 0) retv = EINVAL; else retv = 0; + +done: + wip = find_wireless_if(ifname); + need_key = wip != NULL && wip->wi_need_key; (void) pthread_mutex_unlock(&wifi_mutex); - if (retv == 0) + if (retv == 0 && need_key) retv = set_specific_lan(ifname, essid, bssid); return (retv); @@ -1805,31 +2183,31 @@ handle_wireless_lan(const char *ifname) struct wireless_lan *most_recent; boolean_t many_present; dladm_wlan_strength_t strongest = DLADM_WLAN_STRENGTH_VERY_WEAK; - return_vals_t connect_result; + return_vals_t connect_result = FAILURE; /* - * We wait while a scan is in progress. Since we allow a user - * to initiate a re-scan, we can proceed even when no scan - * has been done to fill in the AP list. + * We wait while a scan or another connect is in progress, and then + * block other connects/scans. Since we allow a user to initiate a + * re-scan, we can proceed even when no scan has yet been done to fill + * in the AP list. */ - if (pthread_mutex_lock(&wifi_init_mutex) != 0) + if (!scanconnect_entry(ifname, B_TRUE)) return (FAILURE); - while (wifi_scan_intf[0] != '\0') - (void) pthread_cond_wait(&wifi_init_cond, &wifi_init_mutex); - (void) pthread_mutex_unlock(&wifi_init_mutex); - if (pthread_mutex_lock(&wifi_mutex) != 0) + if (pthread_mutex_lock(&wifi_mutex) != 0) { + scanconnect_exit(); return (FAILURE); + } - if ((wip = find_wireless_if(ifname)) == NULL) { - connect_result = FAILURE; + if ((wip = find_wireless_if(ifname)) == NULL) goto finished; - } if (wip->wi_wireless_done) { dprintf("handle_wireless_lan: skipping policy scan; done"); - connect_result = SUCCESS; - goto finished; + /* special case; avoid interface update */ + (void) pthread_mutex_unlock(&wifi_mutex); + scanconnect_exit(); + return (SUCCESS); } dprintf("handle_wireless_lan: starting policy scan"); @@ -1854,6 +2232,15 @@ handle_wireless_lan(const char *ifname) NULL)) cur_wlan->known = B_TRUE; + if (!cur_wlan->known && !strict_bssid && + known_wifi_nets_lookup(cur_wlan->essid, "", NULL)) { + dprintf("noticed new BSSID %s for ESSID %s on %s", + cur_wlan->bssid, cur_wlan->essid, ifname); + if (add_known_wifi_nets_file(cur_wlan->essid, + cur_wlan->bssid) == 0) + cur_wlan->known = B_TRUE; + } + if (cur_wlan->known || cur_wlan->connected) { /* * The ESSID comparison here mimics what the "already @@ -1893,9 +2280,25 @@ handle_wireless_lan(const char *ifname) most_recent->essid); connect_result = SUCCESS; } else { - dprintf("%s auto-connect to %s", ifname, + dprintf("%s connecting automatically to %s", ifname, most_recent->essid); - connect_result = connect_or_autoconf(most_recent, wip); + connect_result = connect_chosen_lan(most_recent, wip); + switch (connect_result) { + case FAILURE: + report_wlan_connect_fail(wip->wi_name); + most_recent->rescan = B_TRUE; + syslog(LOG_WARNING, "could not connect to " + "chosen WLAN %s on %s, going to auto-conf", + most_recent->essid, ifname); + connect_result = wlan_autoconf(wip) ? SUCCESS : + FAILURE; + most_recent = NULL; + break; + case SUCCESS: + most_recent->connected = B_TRUE; + report_wlan_connected(most_recent); + break; + } } } else if (request_wlan_selection(ifname, wlans, wireless_lan_used)) { dprintf("%s is unknown and not connected; requested help", @@ -1904,12 +2307,15 @@ handle_wireless_lan(const char *ifname) } else { dprintf("%s has no connected AP or GUI; try auto", ifname); connect_result = wlan_autoconf(wip) ? SUCCESS : FAILURE; + most_recent = NULL; } finished: - if (connect_result == SUCCESS && !update_connected_wlan(wip)) + if (connect_result == SUCCESS && + !update_connected_wlan(wip, most_recent)) connect_result = FAILURE; (void) pthread_mutex_unlock(&wifi_mutex); + scanconnect_exit(); return (connect_result); } diff --git a/usr/src/cmd/svc/milestone/network-physical.xml b/usr/src/cmd/svc/milestone/network-physical.xml index ae967e1e1a..cad5d60978 100644 --- a/usr/src/cmd/svc/milestone/network-physical.xml +++ b/usr/src/cmd/svc/milestone/network-physical.xml @@ -113,6 +113,7 @@ <propval name='dhcp_wait_time' type='count' value='60' /> <propval name='scan_interval' type='count' value='120' /> <propval name='idle_time' type='count' value='10' /> + <propval name='strict_bssid' type='boolean' value='false' /> <propval name='value_authorization' type='astring' value='solaris.smf.value.nwam' /> </property_group> diff --git a/usr/src/lib/libnwam/common/door.c b/usr/src/lib/libnwam/common/door.c index a48dd69aa9..f0c49e21b6 100644 --- a/usr/src/lib/libnwam/common/door.c +++ b/usr/src/lib/libnwam/common/door.c @@ -611,8 +611,8 @@ libnwam_select_wlan(const char *ifname, const char *essid, const char *bssid) * Set the encryption key needed for a given AP. The key string is cleartext. */ int -libnwam_wlan_key(const char *ifname, const char *essid, const char *bssid, - const char *key) +libnwam_wlan_key_secmode(const char *ifname, const char *essid, + const char *bssid, const char *key, const char *secmode) { nwam_door_cmd_t cmd; uintptr_t cmd_buf[BUFFER_SIZE]; @@ -626,10 +626,19 @@ libnwam_wlan_key(const char *ifname, const char *essid, const char *bssid, (void) strlcpy(cmd.ndc_essid, essid, sizeof (cmd.ndc_essid)); (void) strlcpy(cmd.ndc_bssid, bssid, sizeof (cmd.ndc_bssid)); (void) strlcpy(cmd.ndc_key, key, sizeof (cmd.ndc_key)); + (void) strlcpy(cmd.ndc_secmode, secmode, sizeof (cmd.ndc_secmode)); retv = make_door_call(&cmd, &cmd_ret, &cmd_size, &cmd_rsize); return (handle_errors(retv, cmd_ret, cmd_buf, cmd_size, cmd_rsize)); } +/* For compatibility with old nwam-manager binaries */ +int +libnwam_wlan_key(const char *ifname, const char *essid, const char *bssid, + const char *key) +{ + return (libnwam_wlan_key_secmode(ifname, essid, bssid, key, "")); +} + /* Initiate wireless scan on the indicated interface */ int libnwam_start_rescan(const char *ifname) diff --git a/usr/src/lib/libnwam/common/libnwam.h b/usr/src/lib/libnwam/common/libnwam.h index 88e4afc8d4..168beba2b4 100644 --- a/usr/src/lib/libnwam/common/libnwam.h +++ b/usr/src/lib/libnwam/common/libnwam.h @@ -160,6 +160,9 @@ extern int libnwam_delete_known_ap(const char *, const char *); extern int libnwam_select_wlan(const char *, const char *, const char *); extern int libnwam_wlan_key(const char *, const char *, const char *, const char *); +#pragma weak libnwam_wlan_key_secmode +extern int libnwam_wlan_key_secmode(const char *, const char *, const char *, + const char *, const char *); extern int libnwam_start_rescan(const char *); extern int libnwam_fini(void); extern int libnwam_init(int); diff --git a/usr/src/lib/libnwam/common/mapfile-vers b/usr/src/lib/libnwam/common/mapfile-vers index c3293a9228..5fbcb8418d 100644 --- a/usr/src/lib/libnwam/common/mapfile-vers +++ b/usr/src/lib/libnwam/common/mapfile-vers @@ -39,6 +39,7 @@ SUNWprivate_1.1 { libnwam_delete_known_ap; libnwam_select_wlan; libnwam_wlan_key; + libnwam_wlan_key_secmode; libnwam_start_rescan; libnwam_fini; libnwam_init; |