summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Carlson <james.d.carlson@sun.com>2008-12-04 00:00:24 -0500
committerJames Carlson <james.d.carlson@sun.com>2008-12-04 00:00:24 -0500
commitab32bdf2f746488f918233b2d8cabd5835efe9f3 (patch)
tree053e34a412a8482412dd81fd3cd0d85991764b67
parent5c9d25d25ae7531d61aca4904f76e3dae2f457bf (diff)
downloadillumos-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.c2
-rw-r--r--usr/src/cmd/cmd-inet/lib/nwamd/events.c15
-rw-r--r--usr/src/cmd/cmd-inet/lib/nwamd/functions.h5
-rw-r--r--usr/src/cmd/cmd-inet/lib/nwamd/interface.c25
-rw-r--r--usr/src/cmd/cmd-inet/lib/nwamd/main.c59
-rw-r--r--usr/src/cmd/cmd-inet/lib/nwamd/state_machine.c41
-rw-r--r--usr/src/cmd/cmd-inet/lib/nwamd/structures.h1
-rw-r--r--usr/src/cmd/cmd-inet/lib/nwamd/variables.h1
-rw-r--r--usr/src/cmd/cmd-inet/lib/nwamd/wireless.c818
-rw-r--r--usr/src/cmd/svc/milestone/network-physical.xml1
-rw-r--r--usr/src/lib/libnwam/common/door.c13
-rw-r--r--usr/src/lib/libnwam/common/libnwam.h3
-rw-r--r--usr/src/lib/libnwam/common/mapfile-vers1
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;