Use SA Query procedure to recovery from AP/STA state mismatch
If a station received unprotected Deauthentication or Disassociation frame with reason code 6 or 7 from the current AP, there may be a mismatch in association state between the AP and STA. Verify whether this is the case by using SA Query procedure. If not response is received from the AP, deauthenticate. This implementation is only for user space SME with driver_nl80211.c.
This commit is contained in:
parent
5efa9e2a4b
commit
7d878ca769
7 changed files with 302 additions and 1 deletions
|
@ -2266,7 +2266,27 @@ enum wpa_event_type {
|
|||
* (e.g., based on RSSI or channel use). This information can be used
|
||||
* to improve channel selection for a new AP/P2P group.
|
||||
*/
|
||||
EVENT_BEST_CHANNEL
|
||||
EVENT_BEST_CHANNEL,
|
||||
|
||||
/**
|
||||
* EVENT_UNPROT_DEAUTH - Unprotected Deauthentication frame received
|
||||
*
|
||||
* This event should be called when a Deauthentication frame is dropped
|
||||
* due to it not being protected (MFP/IEEE 802.11w).
|
||||
* union wpa_event_data::unprot_deauth is required to provide more
|
||||
* details of the frame.
|
||||
*/
|
||||
EVENT_UNPROT_DEAUTH,
|
||||
|
||||
/**
|
||||
* EVENT_UNPROT_DISASSOC - Unprotected Disassociation frame received
|
||||
*
|
||||
* This event should be called when a Disassociation frame is dropped
|
||||
* due to it not being protected (MFP/IEEE 802.11w).
|
||||
* union wpa_event_data::unprot_disassoc is required to provide more
|
||||
* details of the frame.
|
||||
*/
|
||||
EVENT_UNPROT_DISASSOC,
|
||||
};
|
||||
|
||||
|
||||
|
@ -2700,6 +2720,18 @@ union wpa_event_data {
|
|||
int freq_5;
|
||||
int freq_overall;
|
||||
} best_chan;
|
||||
|
||||
struct unprot_deauth {
|
||||
const u8 *sa;
|
||||
const u8 *da;
|
||||
u16 reason_code;
|
||||
} unprot_deauth;
|
||||
|
||||
struct unprot_disassoc {
|
||||
const u8 *sa;
|
||||
const u8 *da;
|
||||
u16 reason_code;
|
||||
} unprot_disassoc;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -835,6 +835,38 @@ static void mlme_event_deauth_disassoc(struct wpa_driver_nl80211_data *drv,
|
|||
}
|
||||
|
||||
|
||||
static void mlme_event_unprot_disconnect(struct wpa_driver_nl80211_data *drv,
|
||||
enum wpa_event_type type,
|
||||
const u8 *frame, size_t len)
|
||||
{
|
||||
const struct ieee80211_mgmt *mgmt;
|
||||
union wpa_event_data event;
|
||||
u16 reason_code = 0;
|
||||
|
||||
if (len < 24)
|
||||
return;
|
||||
|
||||
mgmt = (const struct ieee80211_mgmt *) frame;
|
||||
|
||||
os_memset(&event, 0, sizeof(event));
|
||||
/* Note: Same offset for Reason Code in both frame subtypes */
|
||||
if (len >= 24 + sizeof(mgmt->u.deauth))
|
||||
reason_code = le_to_host16(mgmt->u.deauth.reason_code);
|
||||
|
||||
if (type == EVENT_UNPROT_DISASSOC) {
|
||||
event.unprot_disassoc.sa = mgmt->sa;
|
||||
event.unprot_disassoc.da = mgmt->da;
|
||||
event.unprot_disassoc.reason_code = reason_code;
|
||||
} else {
|
||||
event.unprot_deauth.sa = mgmt->sa;
|
||||
event.unprot_deauth.da = mgmt->da;
|
||||
event.unprot_deauth.reason_code = reason_code;
|
||||
}
|
||||
|
||||
wpa_supplicant_event(drv->ctx, type, &event);
|
||||
}
|
||||
|
||||
|
||||
static void mlme_event(struct wpa_driver_nl80211_data *drv,
|
||||
enum nl80211_commands cmd, struct nlattr *frame,
|
||||
struct nlattr *addr, struct nlattr *timed_out,
|
||||
|
@ -878,6 +910,14 @@ static void mlme_event(struct wpa_driver_nl80211_data *drv,
|
|||
mlme_event_action_tx_status(drv, cookie, nla_data(frame),
|
||||
nla_len(frame), ack);
|
||||
break;
|
||||
case NL80211_CMD_UNPROT_DEAUTHENTICATE:
|
||||
mlme_event_unprot_disconnect(drv, EVENT_UNPROT_DEAUTH,
|
||||
nla_data(frame), nla_len(frame));
|
||||
break;
|
||||
case NL80211_CMD_UNPROT_DISASSOCIATE:
|
||||
mlme_event_unprot_disconnect(drv, EVENT_UNPROT_DISASSOC,
|
||||
nla_data(frame), nla_len(frame));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -1294,6 +1334,8 @@ static int process_event(struct nl_msg *msg, void *arg)
|
|||
case NL80211_CMD_DISASSOCIATE:
|
||||
case NL80211_CMD_FRAME:
|
||||
case NL80211_CMD_FRAME_TX_STATUS:
|
||||
case NL80211_CMD_UNPROT_DEAUTHENTICATE:
|
||||
case NL80211_CMD_UNPROT_DISASSOCIATE:
|
||||
mlme_event(drv, gnlh->cmd, tb[NL80211_ATTR_FRAME],
|
||||
tb[NL80211_ATTR_MAC], tb[NL80211_ATTR_TIMED_OUT],
|
||||
tb[NL80211_ATTR_WIPHY_FREQ], tb[NL80211_ATTR_ACK],
|
||||
|
@ -1873,6 +1915,11 @@ static int nl80211_register_action_frames(struct wpa_driver_nl80211_data *drv)
|
|||
5) < 0)
|
||||
return -1;
|
||||
#endif /* CONFIG_P2P */
|
||||
#ifdef CONFIG_IEEE80211W
|
||||
/* SA Query Response */
|
||||
if (nl80211_register_action_frame(drv, (u8 *) "\x08\x01", 2) < 0)
|
||||
return -1;
|
||||
#endif /* CONFIG_IEEE80211W */
|
||||
|
||||
/* FT Action frames */
|
||||
if (nl80211_register_action_frame(drv, (u8 *) "\x06", 1) < 0)
|
||||
|
|
|
@ -1641,6 +1641,32 @@ static void ft_rx_action(struct wpa_supplicant *wpa_s, const u8 *data,
|
|||
#endif /* CONFIG_IEEE80211R */
|
||||
|
||||
|
||||
static void wpa_supplicant_event_unprot_deauth(struct wpa_supplicant *wpa_s,
|
||||
struct unprot_deauth *e)
|
||||
{
|
||||
#ifdef CONFIG_IEEE80211W
|
||||
wpa_printf(MSG_DEBUG, "Unprotected Deauthentication frame "
|
||||
"dropped: " MACSTR " -> " MACSTR
|
||||
" (reason code %u)",
|
||||
MAC2STR(e->sa), MAC2STR(e->da), e->reason_code);
|
||||
sme_event_unprot_disconnect(wpa_s, e->sa, e->da, e->reason_code);
|
||||
#endif /* CONFIG_IEEE80211W */
|
||||
}
|
||||
|
||||
|
||||
static void wpa_supplicant_event_unprot_disassoc(struct wpa_supplicant *wpa_s,
|
||||
struct unprot_disassoc *e)
|
||||
{
|
||||
#ifdef CONFIG_IEEE80211W
|
||||
wpa_printf(MSG_DEBUG, "Unprotected Disassociation frame "
|
||||
"dropped: " MACSTR " -> " MACSTR
|
||||
" (reason code %u)",
|
||||
MAC2STR(e->sa), MAC2STR(e->da), e->reason_code);
|
||||
sme_event_unprot_disconnect(wpa_s, e->sa, e->da, e->reason_code);
|
||||
#endif /* CONFIG_IEEE80211W */
|
||||
}
|
||||
|
||||
|
||||
void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
|
||||
union wpa_event_data *data)
|
||||
{
|
||||
|
@ -1886,6 +1912,16 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
|
|||
break;
|
||||
}
|
||||
#endif /* CONFIG_IEEE80211R */
|
||||
#ifdef CONFIG_IEEE80211W
|
||||
#ifdef CONFIG_SME
|
||||
if (data->rx_action.category == WLAN_ACTION_SA_QUERY) {
|
||||
sme_sa_query_rx(wpa_s, data->rx_action.sa,
|
||||
data->rx_action.data,
|
||||
data->rx_action.len);
|
||||
break;
|
||||
}
|
||||
#endif /* CONFIG_SME */
|
||||
#endif /* CONFIG_IEEE80211W */
|
||||
#ifdef CONFIG_P2P
|
||||
wpas_p2p_rx_action(wpa_s, data->rx_action.da,
|
||||
data->rx_action.sa,
|
||||
|
@ -1982,6 +2018,14 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
|
|||
data->best_chan.freq_overall);
|
||||
#endif /* CONFIG_P2P */
|
||||
break;
|
||||
case EVENT_UNPROT_DEAUTH:
|
||||
wpa_supplicant_event_unprot_deauth(wpa_s,
|
||||
&data->unprot_deauth);
|
||||
break;
|
||||
case EVENT_UNPROT_DISASSOC:
|
||||
wpa_supplicant_event_unprot_disassoc(wpa_s,
|
||||
&data->unprot_disassoc);
|
||||
break;
|
||||
default:
|
||||
wpa_printf(MSG_INFO, "Unknown event %d", event);
|
||||
break;
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "includes.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "utils/eloop.h"
|
||||
#include "common/ieee802_11_defs.h"
|
||||
#include "common/ieee802_11_common.h"
|
||||
#include "eapol_supp/eapol_supp_sm.h"
|
||||
|
@ -500,3 +501,160 @@ void sme_event_disassoc(struct wpa_supplicant *wpa_s,
|
|||
WLAN_REASON_DEAUTH_LEAVING);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_IEEE80211W
|
||||
|
||||
static const unsigned int sa_query_max_timeout = 1000;
|
||||
static const unsigned int sa_query_retry_timeout = 201;
|
||||
|
||||
static int sme_check_sa_query_timeout(struct wpa_supplicant *wpa_s)
|
||||
{
|
||||
u32 tu;
|
||||
struct os_time now, passed;
|
||||
os_get_time(&now);
|
||||
os_time_sub(&now, &wpa_s->sme.sa_query_start, &passed);
|
||||
tu = (passed.sec * 1000000 + passed.usec) / 1024;
|
||||
if (sa_query_max_timeout < tu) {
|
||||
wpa_printf(MSG_DEBUG, "SME: SA Query timed out");
|
||||
sme_stop_sa_query(wpa_s);
|
||||
wpa_supplicant_deauthenticate(
|
||||
wpa_s, WLAN_REASON_PREV_AUTH_NOT_VALID);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void sme_send_sa_query_req(struct wpa_supplicant *wpa_s,
|
||||
const u8 *trans_id)
|
||||
{
|
||||
u8 req[2 + WLAN_SA_QUERY_TR_ID_LEN];
|
||||
wpa_printf(MSG_DEBUG, "SME: Sending SA Query Request to "
|
||||
MACSTR, MAC2STR(wpa_s->bssid));
|
||||
wpa_hexdump(MSG_DEBUG, "SME: SA Query Transaction ID",
|
||||
trans_id, WLAN_SA_QUERY_TR_ID_LEN);
|
||||
req[0] = WLAN_ACTION_SA_QUERY;
|
||||
req[1] = WLAN_SA_QUERY_REQUEST;
|
||||
os_memcpy(req + 2, trans_id, WLAN_SA_QUERY_TR_ID_LEN);
|
||||
if (wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, wpa_s->bssid,
|
||||
wpa_s->own_addr, wpa_s->bssid,
|
||||
req, sizeof(req)) < 0)
|
||||
wpa_printf(MSG_INFO, "SME: Failed to send SA Query Request");
|
||||
}
|
||||
|
||||
|
||||
static void sme_sa_query_timer(void *eloop_ctx, void *timeout_ctx)
|
||||
{
|
||||
struct wpa_supplicant *wpa_s = eloop_ctx;
|
||||
unsigned int timeout, sec, usec;
|
||||
u8 *trans_id, *nbuf;
|
||||
|
||||
if (wpa_s->sme.sa_query_count > 0 &&
|
||||
sme_check_sa_query_timeout(wpa_s))
|
||||
return;
|
||||
|
||||
nbuf = os_realloc(wpa_s->sme.sa_query_trans_id,
|
||||
(wpa_s->sme.sa_query_count + 1) *
|
||||
WLAN_SA_QUERY_TR_ID_LEN);
|
||||
if (nbuf == NULL)
|
||||
return;
|
||||
if (wpa_s->sme.sa_query_count == 0) {
|
||||
/* Starting a new SA Query procedure */
|
||||
os_get_time(&wpa_s->sme.sa_query_start);
|
||||
}
|
||||
trans_id = nbuf + wpa_s->sme.sa_query_count * WLAN_SA_QUERY_TR_ID_LEN;
|
||||
wpa_s->sme.sa_query_trans_id = nbuf;
|
||||
wpa_s->sme.sa_query_count++;
|
||||
|
||||
os_get_random(trans_id, WLAN_SA_QUERY_TR_ID_LEN);
|
||||
|
||||
timeout = sa_query_retry_timeout;
|
||||
sec = ((timeout / 1000) * 1024) / 1000;
|
||||
usec = (timeout % 1000) * 1024;
|
||||
eloop_register_timeout(sec, usec, sme_sa_query_timer, wpa_s, NULL);
|
||||
|
||||
wpa_printf(MSG_DEBUG, "SME: Association SA Query attempt %d",
|
||||
wpa_s->sme.sa_query_count);
|
||||
|
||||
sme_send_sa_query_req(wpa_s, trans_id);
|
||||
}
|
||||
|
||||
|
||||
static void sme_start_sa_query(struct wpa_supplicant *wpa_s)
|
||||
{
|
||||
sme_sa_query_timer(wpa_s, NULL);
|
||||
}
|
||||
|
||||
|
||||
void sme_stop_sa_query(struct wpa_supplicant *wpa_s)
|
||||
{
|
||||
eloop_cancel_timeout(sme_sa_query_timer, wpa_s, NULL);
|
||||
os_free(wpa_s->sme.sa_query_trans_id);
|
||||
wpa_s->sme.sa_query_trans_id = NULL;
|
||||
wpa_s->sme.sa_query_count = 0;
|
||||
}
|
||||
|
||||
|
||||
void sme_event_unprot_disconnect(struct wpa_supplicant *wpa_s, const u8 *sa,
|
||||
const u8 *da, u16 reason_code)
|
||||
{
|
||||
struct wpa_ssid *ssid;
|
||||
|
||||
if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME))
|
||||
return;
|
||||
if (wpa_s->wpa_state != WPA_COMPLETED)
|
||||
return;
|
||||
ssid = wpa_s->current_ssid;
|
||||
if (ssid == NULL || ssid->ieee80211w == 0)
|
||||
return;
|
||||
if (os_memcmp(sa, wpa_s->bssid, ETH_ALEN) != 0)
|
||||
return;
|
||||
if (reason_code != WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA &&
|
||||
reason_code != WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA)
|
||||
return;
|
||||
if (wpa_s->sme.sa_query_count > 0)
|
||||
return;
|
||||
|
||||
wpa_printf(MSG_DEBUG, "SME: Unprotected disconnect dropped - possible "
|
||||
"AP/STA state mismatch - trigger SA Query");
|
||||
sme_start_sa_query(wpa_s);
|
||||
}
|
||||
|
||||
|
||||
void sme_sa_query_rx(struct wpa_supplicant *wpa_s, const u8 *sa,
|
||||
const u8 *data, size_t len)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (wpa_s->sme.sa_query_trans_id == NULL ||
|
||||
len < 1 + WLAN_SA_QUERY_TR_ID_LEN ||
|
||||
data[0] != WLAN_SA_QUERY_RESPONSE)
|
||||
return;
|
||||
wpa_printf(MSG_DEBUG, "SME: Received SA Query response from " MACSTR
|
||||
" (trans_id %02x%02x)",
|
||||
MAC2STR(sa), data[1], data[2]);
|
||||
|
||||
if (os_memcmp(sa, wpa_s->bssid, ETH_ALEN) != 0)
|
||||
return;
|
||||
|
||||
for (i = 0; i < wpa_s->sme.sa_query_count; i++) {
|
||||
if (os_memcmp(wpa_s->sme.sa_query_trans_id +
|
||||
i * WLAN_SA_QUERY_TR_ID_LEN,
|
||||
data + 1, WLAN_SA_QUERY_TR_ID_LEN) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i >= wpa_s->sme.sa_query_count) {
|
||||
wpa_printf(MSG_DEBUG, "SME: No matching SA Query "
|
||||
"transaction identifier found");
|
||||
return;
|
||||
}
|
||||
|
||||
wpa_printf(MSG_DEBUG, "SME: Reply to pending SA Query received from "
|
||||
MACSTR, MAC2STR(sa));
|
||||
sme_stop_sa_query(wpa_s);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_IEEE80211W */
|
||||
|
|
|
@ -32,6 +32,11 @@ void sme_event_assoc_timed_out(struct wpa_supplicant *wpa_s,
|
|||
union wpa_event_data *data);
|
||||
void sme_event_disassoc(struct wpa_supplicant *wpa_s,
|
||||
union wpa_event_data *data);
|
||||
void sme_event_unprot_disconnect(struct wpa_supplicant *wpa_s, const u8 *sa,
|
||||
const u8 *da, u16 reason_code);
|
||||
void sme_stop_sa_query(struct wpa_supplicant *wpa_s);
|
||||
void sme_sa_query_rx(struct wpa_supplicant *wpa_s, const u8 *sa,
|
||||
const u8 *data, size_t len);
|
||||
|
||||
#else /* CONFIG_SME */
|
||||
|
||||
|
@ -73,6 +78,12 @@ static inline void sme_event_disassoc(struct wpa_supplicant *wpa_s,
|
|||
{
|
||||
}
|
||||
|
||||
static inline void sme_event_unprot_disconnect(struct wpa_supplicant *wpa_s,
|
||||
const u8 *sa, const u8 *da,
|
||||
u16 reason_code)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SME */
|
||||
|
||||
#endif /* SME_H */
|
||||
|
|
|
@ -424,6 +424,7 @@ static void wpa_supplicant_cleanup(struct wpa_supplicant *wpa_s)
|
|||
os_free(wpa_s->sme.ft_ies);
|
||||
wpa_s->sme.ft_ies = NULL;
|
||||
wpa_s->sme.ft_ies_len = 0;
|
||||
sme_stop_sa_query(wpa_s);
|
||||
#endif /* CONFIG_SME */
|
||||
|
||||
#ifdef CONFIG_AP
|
||||
|
|
|
@ -447,6 +447,14 @@ struct wpa_supplicant {
|
|||
u8 prev_bssid[ETH_ALEN];
|
||||
int prev_bssid_set;
|
||||
int auth_alg;
|
||||
|
||||
int sa_query_count; /* number of pending SA Query requests;
|
||||
* 0 = no SA Query in progress */
|
||||
int sa_query_timed_out;
|
||||
u8 *sa_query_trans_id; /* buffer of WLAN_SA_QUERY_TR_ID_LEN *
|
||||
* sa_query_count octets of pending
|
||||
* SA Query transaction identifiers */
|
||||
struct os_time sa_query_start;
|
||||
} sme;
|
||||
#endif /* CONFIG_SME */
|
||||
|
||||
|
|
Loading…
Reference in a new issue