]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
Use SA Query procedure to recovery from AP/STA state mismatch
authorJouni Malinen <j@w1.fi>
Sun, 19 Dec 2010 09:58:00 +0000 (11:58 +0200)
committerJouni Malinen <j@w1.fi>
Sun, 19 Dec 2010 09:58:00 +0000 (11:58 +0200)
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.

src/drivers/driver.h
src/drivers/driver_nl80211.c
wpa_supplicant/events.c
wpa_supplicant/sme.c
wpa_supplicant/sme.h
wpa_supplicant/wpa_supplicant.c
wpa_supplicant/wpa_supplicant_i.h

index 956403a15ecf3c0682bd7d31d145deff0839a806..036092489d7b6fb96659e8a25a582b91751aa4bd 100644 (file)
@@ -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;
 };
 
 /**
index a3852249d871b0671235c51fc77c389c160c6fea..38c5f343bcea842d071d69678bfca065165c9925 100644 (file)
@@ -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)
index a38d8e9e1457d459827f70cad36a5c2f52acb787..406535286b1f9089adddfafa88618fcc2866a97a 100644 (file)
@@ -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;
index 7c4ff12e4140cad9fffbde58e3a552de44246abe..018b372838154a326ca44c2ae45f97daf122c25f 100644 (file)
@@ -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 */
index 3ec8cc936e88887221e76427dd110b51b0b48cb8..b5f150d3ea484ed3b7b996c472236588c153161a 100644 (file)
@@ -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 */
index 7a9001df64b197c6572190822490d61ef0fa8944..998be62cc4d3f41b6cc93f40167dd9a32a4138e7 100644 (file)
@@ -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
index a61d0e2eb27c6132d03181ae3b421fcff891f716..a6c4a9a44b4d2cedd8a8d194af2368194d9fc0bf 100644 (file)
@@ -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 */