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 956403a..0360924 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 a385224..38c5f34 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 a38d8e9..4065352 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 7c4ff12..018b372 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 3ec8cc9..b5f150d 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 7a9001d..998be62 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 a61d0e2..a6c4a9a 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 */