nl80211: Work around EALREADY from connect command

cfg80211 does not currently allow NL80211_CMD_CONNECT when there
is already an association. This can result in wpa_supplicant being
unable to request a connection if the interface is already in
connected state. Work around this by requesting disconnection if
the connect command fails with EALREADY error. This allows
wpa_supplicant to recover from the unexpected state and complete
the connect request on next attempt.
This commit is contained in:
Jouni Malinen 2011-09-18 21:26:55 +03:00
parent 1b414f59fc
commit 20f5a4c24c

View file

@ -248,6 +248,7 @@ struct nl80211_bss_info_arg {
struct wpa_driver_nl80211_data *drv;
struct wpa_scan_results *res;
unsigned int assoc_freq;
u8 assoc_bssid[ETH_ALEN];
};
static int bss_info_handler(struct nl_msg *msg, void *arg);
@ -757,6 +758,29 @@ static void mlme_event_connect(struct wpa_driver_nl80211_data *drv,
}
static void mlme_event_disconnect(struct wpa_driver_nl80211_data *drv,
struct nlattr *reason, struct nlattr *addr)
{
union wpa_event_data data;
if (drv->capa.flags & WPA_DRIVER_FLAGS_SME) {
/*
* Avoid reporting two disassociation events that could
* confuse the core code.
*/
wpa_printf(MSG_DEBUG, "nl80211: Ignore disconnect "
"event when using userspace SME");
return;
}
drv->associated = 0;
os_memset(&data, 0, sizeof(data));
if (reason)
data.disassoc_info.reason_code = nla_get_u16(reason);
wpa_supplicant_event(drv->ctx, EVENT_DISASSOC, &data);
}
static void mlme_timeout_event(struct wpa_driver_nl80211_data *drv,
enum nl80211_commands cmd, struct nlattr *addr)
{
@ -1452,7 +1476,6 @@ static int process_event(struct nl_msg *msg, void *arg)
struct wpa_driver_nl80211_data *drv = arg;
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
struct nlattr *tb[NL80211_ATTR_MAX + 1];
union wpa_event_data data;
nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
@ -1518,21 +1541,8 @@ static int process_event(struct nl_msg *msg, void *arg)
tb[NL80211_ATTR_RESP_IE]);
break;
case NL80211_CMD_DISCONNECT:
if (drv->capa.flags & WPA_DRIVER_FLAGS_SME) {
/*
* Avoid reporting two disassociation events that could
* confuse the core code.
*/
wpa_printf(MSG_DEBUG, "nl80211: Ignore disconnect "
"event when using userspace SME");
break;
}
drv->associated = 0;
os_memset(&data, 0, sizeof(data));
if (tb[NL80211_ATTR_REASON_CODE])
data.disassoc_info.reason_code =
nla_get_u16(tb[NL80211_ATTR_REASON_CODE]);
wpa_supplicant_event(drv->ctx, EVENT_DISASSOC, &data);
mlme_event_disconnect(drv, tb[NL80211_ATTR_REASON_CODE],
tb[NL80211_ATTR_MAC]);
break;
case NL80211_CMD_MICHAEL_MIC_FAILURE:
mlme_event_michael_mic_failure(drv, tb);
@ -2648,6 +2658,13 @@ static int bss_info_handler(struct nl_msg *msg, void *arg)
wpa_printf(MSG_DEBUG, "nl80211: Associated on %u MHz",
_arg->assoc_freq);
}
if (status == NL80211_BSS_STATUS_ASSOCIATED &&
bss[NL80211_BSS_BSSID]) {
os_memcpy(_arg->assoc_bssid,
nla_data(bss[NL80211_BSS_BSSID]), ETH_ALEN);
wpa_printf(MSG_DEBUG, "nl80211: Associated with "
MACSTR, MAC2STR(_arg->assoc_bssid));
}
}
if (!res)
return NL_SKIP;
@ -4978,6 +4995,56 @@ nla_put_failure:
}
static unsigned int nl80211_get_assoc_bssid(struct wpa_driver_nl80211_data *drv,
u8 *bssid)
{
struct nl_msg *msg;
int ret;
struct nl80211_bss_info_arg arg;
os_memset(&arg, 0, sizeof(arg));
msg = nlmsg_alloc();
if (!msg)
goto nla_put_failure;
genlmsg_put(msg, 0, 0, genl_family_get_id(drv->nl80211), 0, NLM_F_DUMP,
NL80211_CMD_GET_SCAN, 0);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex);
arg.drv = drv;
ret = send_and_recv_msgs(drv, msg, bss_info_handler, &arg);
msg = NULL;
if (ret == 0) {
if (is_zero_ether_addr(arg.assoc_bssid))
return -ENOTCONN;
os_memcpy(bssid, arg.assoc_bssid, ETH_ALEN);
return 0;
}
wpa_printf(MSG_DEBUG, "nl80211: Scan result fetch failed: ret=%d "
"(%s)", ret, strerror(-ret));
nla_put_failure:
nlmsg_free(msg);
return drv->assoc_freq;
}
static int nl80211_disconnect(struct wpa_driver_nl80211_data *drv,
const u8 *bssid)
{
u8 addr[ETH_ALEN];
if (bssid == NULL) {
int res = nl80211_get_assoc_bssid(drv, addr);
if (res)
return res;
bssid = addr;
}
return wpa_driver_nl80211_disconnect(drv, bssid,
WLAN_REASON_PREV_AUTH_NOT_VALID);
}
static int wpa_driver_nl80211_connect(
struct wpa_driver_nl80211_data *drv,
struct wpa_driver_associate_params *params)
@ -5127,6 +5194,14 @@ 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)
nl80211_disconnect(drv, params->bssid);
goto nla_put_failure;
}
ret = 0;