nl80211: Roam correctly through cfg80211 without SME

Change the nl80211 driver in wpa_supplicant to correctly handle
connecting to a new AP through cfg80211 without SME capability. As
before, the driver will disconnect from the previously associated AP,
but now we attempt to immediately connect to our intended AP. This
prevents us from blacklisting the AP we were trying to connect to
because of a semantic mismatch between cfg80211 and wpa_supplicant. The
disconnect/connect patch generates a local disconnect nl80211 event
which we discard because we're already correctly tracking the pending
association request.

In detail:

cfg80211 does not support connecting to a new BSS while already
connected to another BSS, if the underlying driver doesn't support
separate authenticate and associate commands. wpa_supplicant is written
to expect that this is a supported operation, except for a little error
handling that disconnects from the current BSS when roaming fails and
relies on autoconnect logic to reconnect later. However, this failure to
connect is incorrectly attributed to the new AP we attempted to
associate with, rather than a local condition in cfg80211.

The combined effect of these two conditions is that full-mac drivers
accessible through cfg80211 but without SME capability take a long time
to roam across BSS's because wpa_supplicant will:
1) Fail to associate for local reasons
2) Disconnect and return that the association request failed
3) Blacklist the association target (incorrectly)
4) Do a scan
5) Pick a less desirable AP to associate with

Signed-hostap: Christoper Wiley <wiley@chromium.org>
This commit is contained in:
Christopher Wiley 2012-11-11 16:15:29 +02:00 committed by Jouni Malinen
parent c7deed7401
commit a8c5b43ad3

View file

@ -250,6 +250,7 @@ struct wpa_driver_nl80211_data {
unsigned int scan_for_auth:1;
unsigned int retry_auth:1;
unsigned int use_monitor:1;
unsigned int ignore_next_local_disconnect:1;
u64 remain_on_chan_cookie;
u64 send_action_cookie;
@ -1191,6 +1192,7 @@ static void mlme_event_disconnect(struct wpa_driver_nl80211_data *drv,
struct nlattr *by_ap)
{
union wpa_event_data data;
unsigned int locally_generated = by_ap == NULL;
if (drv->capa.flags & WPA_DRIVER_FLAGS_SME) {
/*
@ -1202,6 +1204,18 @@ static void mlme_event_disconnect(struct wpa_driver_nl80211_data *drv,
return;
}
if (drv->ignore_next_local_disconnect) {
drv->ignore_next_local_disconnect = 0;
if (locally_generated) {
wpa_printf(MSG_DEBUG, "nl80211: Ignore disconnect "
"event triggered during reassociation");
return;
}
wpa_printf(MSG_WARNING, "nl80211: Was expecting local "
"disconnect but got another disconnect "
"event first");
}
wpa_printf(MSG_DEBUG, "nl80211: Disconnect event");
drv->associated = 0;
os_memset(&data, 0, sizeof(data));
@ -4509,6 +4523,7 @@ static int wpa_driver_nl80211_disconnect(struct wpa_driver_nl80211_data *drv,
{
wpa_printf(MSG_DEBUG, "%s(reason_code=%d)", __func__, reason_code);
drv->associated = 0;
drv->ignore_next_local_disconnect = 0;
/* Disconnect command doesn't need BSSID - it uses cached value */
return wpa_driver_nl80211_mlme(drv, NULL, NL80211_CMD_DISCONNECT,
reason_code, 0);
@ -6663,7 +6678,7 @@ nla_put_failure:
}
static int wpa_driver_nl80211_connect(
static int wpa_driver_nl80211_try_connect(
struct wpa_driver_nl80211_data *drv,
struct wpa_driver_associate_params *params)
{
@ -6843,15 +6858,6 @@ skip_auth_type:
if (ret) {
wpa_printf(MSG_DEBUG, "nl80211: MLME connect failed: ret=%d "
"(%s)", ret, strerror(-ret));
/*
* cfg80211 does not currently accept new connection if we are
* already connected. As a workaround, force disconnection and
* try again once the driver indicates it completed
* disconnection.
*/
if (ret == -EALREADY)
wpa_driver_nl80211_disconnect(
drv, WLAN_REASON_PREV_AUTH_NOT_VALID);
goto nla_put_failure;
}
ret = 0;
@ -6864,6 +6870,31 @@ nla_put_failure:
}
static int wpa_driver_nl80211_connect(
struct wpa_driver_nl80211_data *drv,
struct wpa_driver_associate_params *params)
{
int ret = wpa_driver_nl80211_try_connect(drv, params);
if (ret == -EALREADY) {
/*
* cfg80211 does not currently accept new connections if
* we are already connected. As a workaround, force
* disconnection and try again.
*/
wpa_printf(MSG_DEBUG, "nl80211: Explicitly "
"disconnecting before reassociation "
"attempt");
if (wpa_driver_nl80211_disconnect(
drv, WLAN_REASON_PREV_AUTH_NOT_VALID))
return -1;
/* Ignore the next local disconnect message. */
drv->ignore_next_local_disconnect = 1;
ret = wpa_driver_nl80211_try_connect(drv, params);
}
return ret;
}
static int wpa_driver_nl80211_associate(
void *priv, struct wpa_driver_associate_params *params)
{