wireless.c revision a9489f613f667faf21ee68381b627b28ddb22188
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* This file contains all the routines to handle wireless (more
* accurately, 802.11 "WiFi" family only at this moment) operations.
* This is only phase 0 work so the handling is pretty simple.
*
* When the daemon starts up, for each WiFi interface detected, it'll
* spawn a thread doing an access point (AP) scanning. After the scans
* finish and if one of the WiFi interfaces is chosen to be active, the
* code will send a message to the GUI, which then must gather the results.
*
* also maintains a list of known WiFi APs in the file KNOWN_WIFI_NETS.
* be added to that file. This file is used in the following way.
*
* If the AP scan results contain one known AP (plus any number of unknown
* APs), the code will automatically connect to that AP without contacting the
* GUI. But if the detected signal strength of that one known AP is weaker
* than any of the unknown APs, the code will block on the GUI.
*
* If the AP scan results contain more than one known APs or no known APs, the
* GUI is notified.
*
* Note that not all APs broadcast the Beacon. And some events may
* happen during the AP scan such that not all available APs are found.
* Thus, the GUI can specify an AP's data.
*
* The code also periodically (specified by wlan_scan_interval) checks
* for the health of the AP connection. If the signal strength of the
* connected AP drops below a threshold (specified by wireless_scan_level),
* the code will try to do another scan to find out other APs available.
* If there is currently no connected AP, a scan will also be done
* 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.
*/
#include <unistd.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <syslog.h>
#include <limits.h>
#include <errno.h>
#include <syslog.h>
#include <pthread.h>
#include <stropts.h>
#include <fcntl.h>
#include <libdladm.h>
#include <libdllink.h>
#include <libinetutil.h>
#include <libgen.h>
#include "defines.h"
#include "structures.h"
#include "functions.h"
#include "variables.h"
static pthread_mutex_t wifi_mutex;
typedef enum {
ESSID = 0,
/*
* List of wireless interfaces; protected by wifi_mutex.
*/
static uint_t wi_link_count;
/*
* Is a wireless interface doing a scan currently? We only allow one
* wireless interface to do a scan at any one time. This is to
* avoid unnecessary interference. The following variable is used
* to store the interface doing the scan. It is protected by
* wifi_init_mutex.
*/
static const char *wifi_scan_intf;
static boolean_t connect_running;
/*
* Array of wireless LAN entries; protected by wifi_mutex.
*/
static struct wireless_lan *wlans;
static boolean_t new_ap_found;
static int store_key(struct wireless_lan *);
static dladm_wlan_key_t *retrieve_key(const char *, const char *,
static struct wireless_lan *add_wlan_entry(const char *, const char *,
const char *, dladm_wlan_attr_t *);
static struct wireless_lan *find_wlan_entry(const char *, const char *,
const char *);
static void free_wireless_lan(struct wireless_lan *);
static int add_known_wifi_nets_file(const char *, const char *);
static boolean_t known_wifi_nets_lookup(const char *, const char *, char *);
#define WIRELESS_LAN_INIT_COUNT 8
/*
* The variable wlan_scan_interval controls the interval in seconds
* between periodic scans.
*/
/*
* The variable wireless_scan_level specifies the lowest signal level
* when a periodic wireless scan needs to be done.
*/
/*
* This controls whether we are strict about matching BSSID in the known wifi
* networks file. By default, we're not strict.
*/
void
initialize_wireless(void)
{
(void) pthread_mutexattr_init(&wifi_mutex_attr);
(void) pthread_mutexattr_settype(&wifi_mutex_attr,
}
void
add_wireless_if(const char *ifname)
{
NULL);
if (pthread_mutex_lock(&wifi_mutex) == 0) {
(void) pthread_mutex_unlock(&wifi_mutex);
} else {
}
}
}
static wireless_if_t *
find_wireless_if(const char *ifname)
{
return (wip);
}
return (NULL);
}
void
remove_wireless_if(const char *ifname)
{
if (pthread_mutex_lock(&wifi_mutex) == 0) {
}
(void) pthread_mutex_unlock(&wifi_mutex);
}
}
/*
* wlan is expected to be non-NULL.
*/
static return_vals_t
{
/*
* First, test if we have key stored as secobj. If so,
* no need to prompt for it.
*/
dprintf("get_user_key: retrieve_key() returns non NULL");
return (SUCCESS);
} else if (request_wlan_key(wlan)) {
return (WAITING);
} else {
return (FAILURE);
}
}
/*
* This function assumes that wifi_mutex is held. If bssid is specified, then
* an exact match is returned. If it's not specified, then the best match is
* returned.
*/
static struct wireless_lan *
{
continue;
if (bssid[0] == '\0') {
} else {
return (wlan);
}
}
return (best);
}
static void
{
/* empty string is not allocated */
}
/*
* This function assumes that wifi_mutex is held.
*/
static struct wireless_lan *
{
char strength[DLADM_STRSIZE];
struct wireless_lan *wlan;
if (wireless_lan_used == wireless_lan_count) {
int newcnt;
newcnt = (wireless_lan_count == 0) ?
return (NULL);
}
}
/* do not do allocation for zero-length */
return (NULL);
}
return (wlan);
}
/*
* Remove entries that are no longer seen on the network. The caller does not
* hold wifi_mutex, but is the only thread that can modify the wlan list.
* Retain connected entries, as lack of visibility in a scan may just be a
* temporary condition (driver problem) and may not reflect an actual
* disconnect.
*/
static boolean_t
clear_unscanned_entries(const char *ifname)
{
if (pthread_mutex_lock(&wifi_mutex) != 0)
return (B_FALSE);
wlput++;
} else {
}
}
(void) pthread_mutex_unlock(&wifi_mutex);
return (dropped);
}
/*
* Verify if a WiFi NIC is associated with the given ESSID and BSSID. If the
* given ESSID is NULL, and if the NIC is already connected, return true.
* Otherwise,
*
* 2. If the NIC is not associated with any AP, return false.
* 3. If the NIC is associated with a different AP, tear down IP interface,
* tell the driver to disassociate with AP, and then return false.
*/
static boolean_t
{
char cur_essid[DLADM_STRSIZE];
char cur_bssid[DLADM_STRSIZE];
char errmsg[DLADM_STRSIZE];
if (status != DLADM_STATUS_OK) {
dprintf("check_wlan: dladm_wlan_get_linkattr() for %s "
return (B_FALSE);
}
return (B_FALSE);
/* If we're expecting "any" connection, then we're done. */
return (B_TRUE);
/* Is the NIC associated with the expected access point? */
dprintf("wrong ESSID: have %s expect %s; taking down",
goto unexpected;
}
return (B_TRUE);
return (B_TRUE);
dprintf("wrong BSSID: have %s expect %s; taking down",
if (sendevent) {
/* If not, then shut the interface down normally */
}
return (B_FALSE);
}
/*
* 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
{
char essid[DLADM_STRSIZE];
char bssid[DLADM_STRSIZE];
} else {
}
/*
* First, verify that if we're connected, then we should be and that
* we're connected to the expected AP.
*/
/*
* If we're connected to the wrong one, then disconnect. Note:
* we'd like to verify BSSID, but we cannot due to CR 6772510.
*/
dprintf("update: wrong AP on %s; expected %s %s",
}
/* If we're not in the expected state, then report disconnect */
if (connected) {
dprintf("update: unexpected connection to %s "
} else {
dprintf("update: not connected to %s %s as "
}
}
}
/*
* State is now known to be good, so make the list entries match.
*/
continue;
/* missing bssid check */
}
}
&attr.la_wlan_attr);
}
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
{
if (pthread_mutex_lock(&wifi_init_mutex) != 0)
return (B_FALSE);
while (wifi_scan_intf != NULL) {
dprintf("%s in progress on %s; blocking %s of %s",
if (!is_connect && !connect_running &&
if (already_done || shutting_down) {
(void) pthread_mutex_unlock(&wifi_init_mutex);
return (B_FALSE);
}
}
dprintf("now exclusively %s on %s",
(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",
(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)
{
if (pthread_mutex_lock(&wifi_init_mutex) != 0)
return (B_FALSE);
(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)
{
int i;
/*
* interface is already done.
*/
return;
/* Grab the linkid from the wireless interface */
if (pthread_mutex_lock(&wifi_mutex) != 0)
goto scan_end;
(void) pthread_mutex_unlock(&wifi_mutex);
dprintf("aborted scan on %s; unable to locate interface",
ifname);
goto scan_end;
}
(void) pthread_mutex_unlock(&wifi_mutex);
/*
* Since only one scan is allowed at any one time, and only scans can
* modify the list, there's no need to grab a lock in checking
* wireless_lan_used or the wlans list itself, or for the new_ap_found
* global.
*
* All other threads must hold the mutex when reading this data, and
* this thread must hold the mutex only when writing portions that
* those other threads may read.
*/
for (i = 0; i < wireless_lan_used; i++)
if (status == DLADM_STATUS_OK) {
} else {
}
/* Need to sample this global before clearing out scan lock */
/*
* 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) {
struct wireless_lan *wlan;
char essid[DLADM_STRSIZE];
char bssid[DLADM_STRSIZE];
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 it's really down.
*/
while (retries++ < 4) {
&attr) != DLADM_STATUS_OK)
break;
}
(void) dladm_wlan_essid2str(
(void) dladm_wlan_bssid2str(
dprintf("scan: %s reports connection to %s "
} else {
dprintf("scan: %s is currently unconnected",
ifname);
}
/* Disconnect from wrong AP first */
wlan++) {
continue;
/* missing bssid check */
/*
* This is the one we are currently
* connected to. See if we should be
* here.
*/
(void) dladm_wlan_disconnect(
linkid);
break;
}
}
/* Connect to right AP by reporting disconnect */
wlan++) {
continue;
/* missing bssid check */
if (connected &&
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);
}
if (status == DLADM_STATUS_OK)
if (new_found) {
}
}
/*
* Rescan all wireless interfaces. This routine intentionally does not hold
* wifi_mutex during the scan, as scans can take a long time to accomplish, and
* there may be more than one wireless interface. The counter is used to make
* sure that we don't run "forever" if the list is changing quickly.
*/
static void
rescan_wifi_no_lock(void)
{
if (pthread_mutex_lock(&wifi_mutex) != 0)
return;
/* Even less than "very weak" */
wip->wi_strength = 0;
(void) pthread_mutex_unlock(&wifi_mutex);
if (pthread_mutex_lock(&wifi_mutex) != 0)
return;
else
}
(void) pthread_mutex_unlock(&wifi_mutex);
}
/*
* This thread is given the name of the interface to scan, and must free that
* name when done.
*/
static void *
scan_thread(void *arg)
{
return (NULL);
}
/*
* Launch a thread to scan the given wireless interface. We copy the interface
* name over to allocated storage because it's not possible to hand off a lock
* on the interface list to the new thread, and the caller's storage (our input
* argument) isn't guaranteed to be stable after we return to the caller.
*/
int
launch_wireless_scan(const char *ifname)
{
int retv;
char *winame;
return (ENOMEM);
return (retv);
}
} else if (wip->wi_scan_running) {
retv = EINPROGRESS;
} else {
(void) pthread_attr_init(&attr);
(void) pthread_attr_setdetachstate(&attr,
if (retv == 0)
}
(void) pthread_mutex_unlock(&wifi_mutex);
/* If thread not started, then discard the name. */
if (retv != 0)
return (retv);
}
/*
* Caller does not hold wifi_mutex.
*/
static boolean_t
{
struct wireless_lan *wlan;
char essid_name[DLADM_STRSIZE];
char bssid_name[DLADM_STRSIZE];
/*
* Check whether ESSID is "hidden".
* If so try to substitute it with the ESSID from the
* known_wifi_nets with the same BSSID
*/
if (essid_name[0] == '\0') {
essid_name) &&
dprintf("Using ESSID %s with BSSID %s",
}
}
if (pthread_mutex_lock(&wifi_mutex) != 0)
return (B_FALSE);
(void) pthread_mutex_unlock(&wifi_mutex);
return (B_FALSE);
}
/* Remember the strongest we encounter */
NULL) {
} else {
}
(void) pthread_mutex_unlock(&wifi_mutex);
return (retv);
}
/*
* 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)
{
struct wireless_lan *wlan;
/*
* If these calls fail, it means that the wireless link is down.
*/
}
/*
* 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 (connecting_on(ifname))
return;
is_failure = B_TRUE;
if (pthread_mutex_lock(&wifi_mutex) == 0) {
/*
* Link down while waiting for user to supply
* key is *not* a failure case.
*/
if (!wip->wi_wireless_done &&
wip->wi_need_key) {
} else {
}
}
if (is_failure) {
0) {
wlan);
}
}
}
(void) pthread_mutex_unlock(&wifi_mutex);
}
if (is_failure) {
dprintf("wireless check indicates disconnect");
(void) dladm_wlan_disconnect(linkid);
}
}
}
/* ARGSUSED */
void *
periodic_wireless_scan(void *arg)
{
for (;;) {
char essid[DLADM_STRSIZE];
struct wireless_lan *wlan;
/*
* 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;
}
if (ret == -1) {
continue;
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 */
/*
* We do a scan if
*
* 1. There is no active profile. Or
* 2. Profile is wireless and we're not connected to the AP. Or
* 3. The signal strength falls below a certain specified level.
*/
if (ifname[0] != '\0') {
if (ift != IF_WIRELESS)
continue;
/*
* If these things fail, it means that our wireless
* link isn't viable. Proceed in that way.
*/
NULL) != DLADM_STATUS_OK ||
}
/*
* 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(
if (pthread_mutex_lock(&wifi_mutex) != 0)
continue;
0)
break;
}
(void) pthread_mutex_unlock(
&wifi_mutex);
continue;
}
dprintf("%s is connected to %s instead of %s",
(void) pthread_mutex_unlock(&wifi_mutex);
}
}
/* Rescan the wireless interfaces */
if (ifname[0] != '\0') {
/*
* If we're still connected and there's nothing better
* around, then there's no point in switching now.
*/
if (pthread_mutex_lock(&wifi_mutex) != 0)
continue;
wip->wi_strength <=
(void) pthread_mutex_unlock(&
continue;
}
}
(void) pthread_mutex_unlock(&wifi_mutex);
/*
* Try to work around known driver bugs: if the driver
* says we're disconnected, then tell it to disconnect
* for sure.
*/
(void) dladm_wlan_disconnect(linkid);
/*
* Tell the state machine that we've lost this link so
* that it can do something about the problem.
*/
(void) np_queue_add_event(
}
}
scan = 0;
(void) pthread_detach(pthread_self());
return (NULL);
}
/*
* and dladm_get_secobj().
*/
/*
* Convert key hexascii string to raw secobj value. This
* code is very similar to convert_secobj() in dladm.c, it would
* be good to have a libdladm function to convert values.
*/
static int
{
if (buf_len == 0) {
/* length zero means "delete" */
return (0);
}
if (class == DLADM_SECOBJ_CLASS_WPA) {
/*
* Per IEEE802.11i spec, the Pre-shared key (PSK) length should
* be between 8 and 63.
*/
"key_string_to_secobj_value:"
" invalid WPA key length: buf_len = %d", buf_len);
return (-1);
}
return (0);
}
switch (buf_len) {
case 5: /* ASCII key sizes */
case 13:
break;
case 10:
case 26: /* Hex key sizes, not preceded by 0x */
!= 0) {
"key_string_to_secobj_value: invalid WEP key");
return (-1);
}
break;
case 12:
case 28: /* Hex key sizes, preceded by 0x */
obj_lenp) != 0) {
"key_string_to_secobj_value: invalid WEP key");
return (-1);
}
break;
default:
"key_string_to_secobj_value: invalid WEP key length");
return (-1);
}
return (0);
}
/*
* Print the key name format into the appropriate field, then convert any ":"
* characters to ".", as ":[1-4]" is the slot indicator, which otherwise
* would trip us up. Invalid characters for secobj names are ignored.
* The fourth parameter is expected to be of size DLADM_SECOBJ_NAME_MAX.
*
* (Note that much of the system uses DLADM_WLAN_MAX_KEYNAME_LEN, which is 64
* rather than 32, but that dladm_get_secobj will fail if a length greater than
* DLD_SECOBJ_NAME_MAX is seen, and that's 32. This is all horribly broken.)
*/
static void
{
int i, j;
/* create a concatenated string with essid and bssid */
if (bssid[0] == '\0') {
essid);
} else {
}
/* copy only valid chars to the return string, terminating with \0 */
i = 0; /* index into secobj_name */
j = 0; /* index into name */
while (secobj_name[i] != '\0') {
if (j == nsz - 1)
break;
if (secobj_name[i] == ':') {
name[j] = '.';
j++;
} else if (isalnum(secobj_name[i]) ||
secobj_name[i] == '_') {
name[j] = secobj_name[i];
j++;
}
i++;
}
name[j] = '\0';
}
static int
{
char obj_name[DLADM_SECOBJ_NAME_MAX];
char errmsg[DLADM_STRSIZE];
/*
* Name key object for this WLAN so it can be later retrieved
*/
class) != 0) {
/* above function logs internally on failure */
return (-1);
}
/* we've validated the new key, so remove the old one */
"'%s' for key: %s", obj_name,
return (-1);
}
/* if we're just deleting the key, then we're done */
return (0);
if (status != DLADM_STATUS_OK) {
"'%s' for key: %s", obj_name,
return (-1);
}
/*
* We don't really need to retrieve the key we just stored, but
* we do need to set the cooked key, and the function below takes
* care of allocating memory and setting the length and slot ID
* besides just copying the value, so it is simpler just to call
* the retrieve function instead of doing it all here.
*
* Since we just stored the key, retrieve_key() "shouldn't"
* fail. If it does fail, it's not the end of the world; a NULL
* value for wlan->cooked_key simply means this particular
* attempt to connect will fail, and alternative connection
* options will be used.
*/
return (0);
}
/*
* retrieve_key returns NULL if no key was recovered from libdladm
*/
static dladm_wlan_key_t *
{
char errmsg[DLADM_STRSIZE];
/*
* Newly-allocated key must be freed by caller, or by
* subsequent call to retrieve_key().
*/
return (NULL);
}
/*
* Set name appropriately to retrieve key for this WLAN. Note that we
* cannot use the actual wk_name buffer size, as it's two times too
* large for dladm_get_secobj.
*/
dprintf("retrieve_key: len = %d, object = %s\n",
/* Try the kernel first, then fall back to persistent storage. */
if (status != DLADM_STATUS_OK) {
dprintf("retrieve_key: dladm_get_secobj(TEMP) failed: %s",
}
switch (status) {
case DLADM_STATUS_OK:
dprintf("retrieve_key: dladm_get_secobj succeeded: len %d",
cooked_key->wk_len);
break;
case DLADM_STATUS_NOTFOUND:
/*
* We do not want an error in the case that the secobj
* is not found, since we then prompt for it.
*/
return (NULL);
default:
return (NULL);
}
return (NULL);
}
return (cooked_key);
}
/*
* Add an entry to known_wifi_nets file given the parameters. The caller holds
* wifi_mutex.
*/
static int
{
int retv;
/* Create the NWAM directory in case it does not exist. */
} else {
/* now add this to the file */
retv = 0;
}
return (retv);
}
static int
{
char *cp;
int retv;
return (errno);
return (retv);
}
cp++;
continue;
}
/* skip over the essid to examine bssid */
cp++;
cp++;
/*
* Deleting with bssid empty means "all entries under this
* essid." As a result, deleting a wildcard entry for a bssid
* means deleting all entries for that bssid.
*/
if (bssidlen == 0 ||
/* delete this entry */
continue;
}
}
if (found) {
retv = 0;
} else {
(void) unlink(KNOWN_WIFI_TMP);
}
} else {
(void) unlink(KNOWN_WIFI_TMP);
}
return (retv);
}
/*
* Check if the given AP (ESSID, BSSID pair) is on the known AP list.
* If found_essid is non-NULL and the match is found (B_TRUE is returned)
* the matched ESSID is copied out into buffer pointed by found_essid.
* The buffer is expected to be at least DLADM_STRSIZE bytes long.
*/
static boolean_t
char *found_essid)
{
char *cp;
char *tok[MAX_FIELDS];
int line_num;
/*
* For now the file format is:
* essid\tbssid
* (essid followed by tab followed by bssid)
*/
return (B_FALSE);
cp++;
continue;
continue;
}
/*
* If we're searching on ESSID alone, then any match on a
* specific ESSID will do.
*/
if (*new_bssid == '\0') {
if (*new_essid != '\0' &&
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.
*/
/*
* Got BSSID match, either ESSID was not specified,
* or it should match
*/
if (*new_essid == '\0' ||
break;
}
}
}
if (found) {
if (found_essid != NULL)
}
return (found);
}
static uint_t
{
char *cp;
char *tok[MAX_FIELDS];
char key[DLADM_SECOBJ_NAME_MAX];
cp++;
continue;
continue;
count++;
else
kap++;
}
}
return (count);
}
{
int retv;
return (kap);
}
NULL);
}
}
(void) pthread_mutex_unlock(&wifi_mutex);
return (kap);
}
int
{
int retv;
/*
* First check the current LLP. If there is one, then its connection
* state determines what to do after adding the known AP to the list.
* If not, then we act if there are no connected APs.
*/
return (retv);
/*
* If this is in our list of scanned APs and if no interface is
* connected, then we have a reevaluation event.
*/
wlan++) {
/*
* If LLP is selected, then ignore all others. Only
* the state of this one interface is at issue.
*/
if (ifname[0] != '\0' &&
continue;
(bssid[0] == '\0' ||
}
}
if (!any_connected && one_matches) {
(void) np_queue_add_event(EV_RESELECT,
}
}
(void) pthread_mutex_unlock(&wifi_mutex);
return (retv);
}
int
{
int retv;
struct wireless_lan *wlan;
return (retv);
if (retv == 0) {
wlan++) {
(bssid[0] == '\0' ||
(void) dladm_wlan_disconnect(wip->
}
(void) np_queue_add_event(EV_RESELECT,
wlan->wl_if_name);
}
}
}
(void) pthread_mutex_unlock(&wifi_mutex);
return (retv);
}
/*
* reqlan->essid is required (i.e., cannot be zero-length)
* reqlan->bssid is optional (i.e., may be zero-length)
*/
static return_vals_t
{
char errmsg[DLADM_STRSIZE];
/* try to apply essid selected by the user */
return (FAILURE);
/* If it is already connected to the required AP, just return. */
return (SUCCESS);
"connect_chosen_lan: invalid ESSID '%s' for '%s'",
return (FAILURE);
}
/* note: bssid logic here is non-functional */
"connect_chosen_lan: invalid BSSID '%s' for '%s'",
return (FAILURE);
}
}
/* First check for the key */
/* Note that this happens only for known APs from the list */
return (rval);
}
keycount = 1;
dprintf("connect_chosen_lan: retrieved key");
} else {
keycount = 0;
}
/*
* Connect; only scan if a bssid was not specified.
* If it times out and we were trying with a bssid,
* try a second time with just the ESSID.
*/
dprintf("connect_chosen_lan: dladm_wlan_connect returned %s",
/*
* This doesn't work due to CR 6772510.
*/
#ifdef CR6772510_FIXED
"trying again with just (%s)",
flags = 0;
}
#endif /* CR6772510_FIXED */
if (status == DLADM_STATUS_OK) {
return (SUCCESS);
} else {
"connect_chosen_lan: connect to '%s' failed on '%s': %s",
return (FAILURE);
}
}
/*
* is used by the GUI to check for connectivity before doing anything
* destructive.
*/
{
if (pthread_mutex_lock(&wifi_mutex) != 0)
return (B_FALSE);
} else {
}
(void) pthread_mutex_unlock(&wifi_mutex);
return (retv);
}
/*
* 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.
*/
static void *
connect_thread(void *arg)
{
goto failure_noentry;
if (pthread_mutex_lock(&wifi_mutex) != 0)
goto failure_unlocked;
goto failure;
/* This is an autoconf request. */
goto failure;
else
goto done;
}
/*
* now attempt to connect to selection
*/
case WAITING:
break;
case SUCCESS: {
char lclssid[DLADM_STRSIZE];
char unnecessary_buf[DLADM_STRSIZE];
/*
* 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 (status != DLADM_STATUS_OK) {
dprintf("failed to get linkattr on %s after connecting "
goto failure;
}
lclssid);
dprintf("connected to strange network: expected %s got "
goto failure;
}
lclssid);
goto failure;
}
/* Don't leave it as NULL (for simplicity) */
goto failure;
}
goto failure;
/* We're done; trigger IP bring-up. */
break;
}
default:
goto failure;
}
done:
(void) pthread_mutex_unlock(&wifi_mutex);
return (NULL);
/*
* 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.
*/
}
(void) pthread_mutex_unlock(&wifi_mutex);
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
{
struct wireless_lan *wlan;
int retv;
return (ENXIO);
if (ift != IF_WIRELESS)
return (EINVAL);
return (ENOMEM);
return (ENOMEM);
}
(void) pthread_attr_init(&attr);
if (retv == 0)
else
return (retv);
}
int
{
int retv;
if (ift == IF_UNKNOWN)
return (ENXIO);
if (ift != IF_WIRELESS)
return (EINVAL);
if (*secmode != '\0' &&
return (EINVAL);
return (retv);
/* If not seen in scan, then secmode is required */
if (*secmode == '\0') {
goto done;
}
/* Prohibit a completely blank entry */
goto done;
}
wlan = &local_wlan;
sizeof (wlan->wl_if_name));
} else {
/* If seen in scan, then secmode given (if any) must match */
goto done;
}
/* save a copy of the new key in the scan entry */
goto done;
}
}
else
retv = 0;
done:
(void) pthread_mutex_unlock(&wifi_mutex);
return (retv);
}
static boolean_t
{
if (!autoconf)
return (B_FALSE);
}
/* If the NIC is already associated with something, just return. */
return (B_TRUE);
/*
* Do autoconf, relying on the heuristics used by dladm_wlan_connect()
* to cycle through WLANs detected in priority order, attempting
* to connect.
*/
DLADM_WLAN_CONNECT_TIMEOUT_DEFAULT, NULL, 0, 0);
if (status != DLADM_STATUS_OK) {
char errmsg[DLADM_STRSIZE];
"wlan_autoconf: dladm_wlan_connect failed for '%s': %s",
return (B_FALSE);
}
return (B_TRUE);
}
/*
* This function searches through the wlans[] array and determines which ones
* have been visited before.
*
* If exactly one has been visited before, and it has the highest signal
* strength, then we attempt to connect to it right away.
*
* In all other cases -- if none have been visited before, or more than one was
* visited, or if the one that was visited doesn't have the highest signal
* strength, or if the automatic connect attempt fails for any reason -- then
* we hand over the data to the GUI for resolution. The user will have to be
* prompted for a choice.
*
* If no GUI exists, we'll get back FAILURE (instead of WAITING), which will
* cause the autoconf mechanism to run instead.
*/
handle_wireless_lan(const char *ifname)
{
struct wireless_lan *most_recent;
/*
* We wait while a scan or another connect is in progress, and then
* re-scan, we can proceed even when no scan has yet been done to fill
* in the AP list.
*/
return (FAILURE);
if (pthread_mutex_lock(&wifi_mutex) != 0) {
return (FAILURE);
}
goto finished;
if (wip->wi_wireless_done) {
dprintf("handle_wireless_lan: skipping policy scan; done");
/* special case; avoid interface update */
(void) pthread_mutex_unlock(&wifi_mutex);
return (SUCCESS);
}
dprintf("handle_wireless_lan: starting policy scan");
most_recent = NULL;
/*
* Try to see if any of the wifi nets currently available
* has been used previously. If more than one available
* nets has been used before, then prompt user with
* all the applicable previously wifi nets, and ask which
* one to connect to.
*/
/* Find the AP with the highest signal. */
NULL))
dprintf("noticed new BSSID %s for ESSID %s on %s",
}
/*
* The ESSID comparison here mimics what the "already
* in visited wlan list" function once did, but
* slightly better as we also pay attention to signal
* strength to pick the best of the duplicates.
*/
if (most_recent == NULL) {
most_recent->essid) != 0) {
if (most_recent->connected) {
(void) dladm_wlan_disconnect(
}
}
}
/* Reset any security information we may have had. */
}
if (most_recent->connected) {
most_recent->essid);
} else {
most_recent->essid);
switch (connect_result) {
case FAILURE:
"chosen WLAN %s on %s, going to auto-conf",
most_recent = NULL;
break;
case SUCCESS:
break;
}
}
dprintf("%s is unknown and not connected; requested help",
ifname);
} else {
most_recent = NULL;
}
if (connect_result == SUCCESS &&
(void) pthread_mutex_unlock(&wifi_mutex);
return (connect_result);
}
void
disconnect_wlan(const char *ifname)
{
struct wireless_lan *wlan;
if (pthread_mutex_lock(&wifi_mutex) == 0) {
}
}
}
(void) pthread_mutex_unlock(&wifi_mutex);
}
}
void
{
if (pthread_mutex_lock(&wifi_mutex) == 0) {
}
(void) pthread_mutex_unlock(&wifi_mutex);
}
}
void
print_wireless_status(void)
{
struct wireless_lan *wlan;
if (pthread_mutex_lock(&wifi_mutex) == 0) {
dprintf("WIF %s linkid %d scan %srunning "
"wireless %sdone %sneed key strength %d",
wip->wi_strength);
}
dprintf("WLAN I/F %s ESS %s BSS %s signal %s key %sset "
"%sknown %sconnected %sscanned",
}
(void) pthread_mutex_unlock(&wifi_mutex);
}
}