Updated through tag hostap_2_5 from git://w1.fi/hostap.git
[mech_eap.git] / libeap / src / ap / ieee802_11_ht.c
index 7541b83..11fde2a 100644 (file)
@@ -3,26 +3,22 @@
  * Copyright (c) 2002-2009, Jouni Malinen <j@w1.fi>
  * Copyright (c) 2007-2008, Intel Corporation
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * Alternatively, this software may be distributed under the terms of BSD
- * license.
- *
- * See README and COPYING for more details.
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
  */
 
 #include "utils/includes.h"
 
 #include "utils/common.h"
+#include "utils/eloop.h"
 #include "common/ieee802_11_defs.h"
-#include "drivers/driver.h"
 #include "hostapd.h"
 #include "ap_config.h"
 #include "sta_info.h"
 #include "beacon.h"
 #include "ieee802_11.h"
+#include "hw_features.h"
+#include "ap_drv_ops.h"
 
 
 u8 * hostapd_eid_ht_capabilities(struct hostapd_data *hapd, u8 *eid)
@@ -30,7 +26,8 @@ u8 * hostapd_eid_ht_capabilities(struct hostapd_data *hapd, u8 *eid)
        struct ieee80211_ht_capabilities *cap;
        u8 *pos = eid;
 
-       if (!hapd->iconf->ieee80211n || !hapd->iface->current_mode)
+       if (!hapd->iconf->ieee80211n || !hapd->iface->current_mode ||
+           hapd->conf->disable_11n)
                return eid;
 
        *pos++ = WLAN_EID_HT_CAP;
@@ -49,6 +46,35 @@ u8 * hostapd_eid_ht_capabilities(struct hostapd_data *hapd, u8 *eid)
 
        pos += sizeof(*cap);
 
+       if (hapd->iconf->obss_interval) {
+               struct ieee80211_obss_scan_parameters *scan_params;
+
+               *pos++ = WLAN_EID_OVERLAPPING_BSS_SCAN_PARAMS;
+               *pos++ = sizeof(*scan_params);
+
+               scan_params = (struct ieee80211_obss_scan_parameters *) pos;
+               os_memset(scan_params, 0, sizeof(*scan_params));
+               scan_params->width_trigger_scan_interval =
+                       host_to_le16(hapd->iconf->obss_interval);
+
+               /* Fill in default values for remaining parameters
+                * (IEEE Std 802.11-2012, 8.4.2.61 and MIB defval) */
+               scan_params->scan_passive_dwell =
+                       host_to_le16(20);
+               scan_params->scan_active_dwell =
+                       host_to_le16(10);
+               scan_params->scan_passive_total_per_channel =
+                       host_to_le16(200);
+               scan_params->scan_active_total_per_channel =
+                       host_to_le16(20);
+               scan_params->channel_transition_delay_factor =
+                       host_to_le16(5);
+               scan_params->scan_activity_threshold =
+                       host_to_le16(25);
+
+               pos += sizeof(*scan_params);
+       }
+
        return pos;
 }
 
@@ -58,7 +84,7 @@ u8 * hostapd_eid_ht_operation(struct hostapd_data *hapd, u8 *eid)
        struct ieee80211_ht_operation *oper;
        u8 *pos = eid;
 
-       if (!hapd->iconf->ieee80211n)
+       if (!hapd->iconf->ieee80211n || hapd->conf->disable_11n)
                return eid;
 
        *pos++ = WLAN_EID_HT_OPERATION;
@@ -67,14 +93,14 @@ u8 * hostapd_eid_ht_operation(struct hostapd_data *hapd, u8 *eid)
        oper = (struct ieee80211_ht_operation *) pos;
        os_memset(oper, 0, sizeof(*oper));
 
-       oper->control_chan = hapd->iconf->channel;
+       oper->primary_chan = hapd->iconf->channel;
        oper->operation_mode = host_to_le16(hapd->iface->ht_op_mode);
        if (hapd->iconf->secondary_channel == 1)
                oper->ht_param |= HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE |
-                       HT_INFO_HT_PARAM_REC_TRANS_CHNL_WIDTH;
+                       HT_INFO_HT_PARAM_STA_CHNL_WIDTH;
        if (hapd->iconf->secondary_channel == -1)
                oper->ht_param |= HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW |
-                       HT_INFO_HT_PARAM_REC_TRANS_CHNL_WIDTH;
+                       HT_INFO_HT_PARAM_STA_CHNL_WIDTH;
 
        pos += sizeof(*oper);
 
@@ -92,7 +118,6 @@ Set to 1 (HT non-member protection) if there may be non-HT STAs
 Set to 2 if only HT STAs are associated in BSS,
        however and at least one 20 MHz HT STA is associated
 Set to 3 (HT mixed mode) when one or more non-HT STAs are associated
-       (currently non-GF HT station is considered as non-HT STA also)
 */
 int hostapd_ht_operation_update(struct hostapd_iface *iface)
 {
@@ -105,50 +130,40 @@ int hostapd_ht_operation_update(struct hostapd_iface *iface)
        wpa_printf(MSG_DEBUG, "%s current operation mode=0x%X",
                   __func__, iface->ht_op_mode);
 
-       if (!(iface->ht_op_mode & HT_INFO_OPERATION_MODE_NON_GF_DEVS_PRESENT)
+       if (!(iface->ht_op_mode & HT_OPER_OP_MODE_NON_GF_HT_STAS_PRESENT)
            && iface->num_sta_ht_no_gf) {
-               iface->ht_op_mode |=
-                       HT_INFO_OPERATION_MODE_NON_GF_DEVS_PRESENT;
+               iface->ht_op_mode |= HT_OPER_OP_MODE_NON_GF_HT_STAS_PRESENT;
                op_mode_changes++;
        } else if ((iface->ht_op_mode &
-                   HT_INFO_OPERATION_MODE_NON_GF_DEVS_PRESENT) &&
+                   HT_OPER_OP_MODE_NON_GF_HT_STAS_PRESENT) &&
                   iface->num_sta_ht_no_gf == 0) {
-               iface->ht_op_mode &=
-                       ~HT_INFO_OPERATION_MODE_NON_GF_DEVS_PRESENT;
+               iface->ht_op_mode &= ~HT_OPER_OP_MODE_NON_GF_HT_STAS_PRESENT;
                op_mode_changes++;
        }
 
-       if (!(iface->ht_op_mode & HT_INFO_OPERATION_MODE_NON_HT_STA_PRESENT) &&
+       if (!(iface->ht_op_mode & HT_OPER_OP_MODE_OBSS_NON_HT_STAS_PRESENT) &&
            (iface->num_sta_no_ht || iface->olbc_ht)) {
-               iface->ht_op_mode |= HT_INFO_OPERATION_MODE_NON_HT_STA_PRESENT;
+               iface->ht_op_mode |= HT_OPER_OP_MODE_OBSS_NON_HT_STAS_PRESENT;
                op_mode_changes++;
        } else if ((iface->ht_op_mode &
-                   HT_INFO_OPERATION_MODE_NON_HT_STA_PRESENT) &&
+                   HT_OPER_OP_MODE_OBSS_NON_HT_STAS_PRESENT) &&
                   (iface->num_sta_no_ht == 0 && !iface->olbc_ht)) {
-               iface->ht_op_mode &=
-                       ~HT_INFO_OPERATION_MODE_NON_HT_STA_PRESENT;
+               iface->ht_op_mode &= ~HT_OPER_OP_MODE_OBSS_NON_HT_STAS_PRESENT;
                op_mode_changes++;
        }
 
-       /* Note: currently we switch to the MIXED op mode if HT non-greenfield
-        * station is associated. Probably it's a theoretical case, since
-        * it looks like all known HT STAs support greenfield.
-        */
-       new_op_mode = 0;
-       if (iface->num_sta_no_ht ||
-           (iface->ht_op_mode & HT_INFO_OPERATION_MODE_NON_GF_DEVS_PRESENT))
-               new_op_mode = OP_MODE_MIXED;
-       else if ((iface->conf->ht_capab & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET)
-                && iface->num_sta_ht_20mhz)
-               new_op_mode = OP_MODE_20MHZ_HT_STA_ASSOCED;
+       if (iface->num_sta_no_ht)
+               new_op_mode = HT_PROT_NON_HT_MIXED;
+       else if (iface->conf->secondary_channel && iface->num_sta_ht_20mhz)
+               new_op_mode = HT_PROT_20MHZ_PROTECTION;
        else if (iface->olbc_ht)
-               new_op_mode = OP_MODE_MAY_BE_LEGACY_STAS;
+               new_op_mode = HT_PROT_NONMEMBER_PROTECTION;
        else
-               new_op_mode = OP_MODE_PURE;
+               new_op_mode = HT_PROT_NO_PROTECTION;
 
-       cur_op_mode = iface->ht_op_mode & HT_INFO_OPERATION_MODE_OP_MODE_MASK;
+       cur_op_mode = iface->ht_op_mode & HT_OPER_OP_MODE_HT_PROT_MASK;
        if (cur_op_mode != new_op_mode) {
-               iface->ht_op_mode &= ~HT_INFO_OPERATION_MODE_OP_MODE_MASK;
+               iface->ht_op_mode &= ~HT_OPER_OP_MODE_HT_PROT_MASK;
                iface->ht_op_mode |= new_op_mode;
                op_mode_changes++;
        }
@@ -160,11 +175,150 @@ int hostapd_ht_operation_update(struct hostapd_iface *iface)
 }
 
 
-u16 copy_sta_ht_capab(struct sta_info *sta, const u8 *ht_capab,
-                     size_t ht_capab_len)
+static int is_40_allowed(struct hostapd_iface *iface, int channel)
 {
+       int pri_freq, sec_freq;
+       int affected_start, affected_end;
+       int pri = 2407 + 5 * channel;
+
+       if (iface->current_mode->mode != HOSTAPD_MODE_IEEE80211G)
+               return 1;
+
+       pri_freq = hostapd_hw_get_freq(iface->bss[0], iface->conf->channel);
+
+       if (iface->conf->secondary_channel > 0)
+               sec_freq = pri_freq + 20;
+       else
+               sec_freq = pri_freq - 20;
+
+       affected_start = (pri_freq + sec_freq) / 2 - 25;
+       affected_end = (pri_freq + sec_freq) / 2 + 25;
+       if ((pri < affected_start || pri > affected_end))
+               return 1; /* not within affected channel range */
+
+       wpa_printf(MSG_ERROR, "40 MHz affected channel range: [%d,%d] MHz",
+                  affected_start, affected_end);
+       wpa_printf(MSG_ERROR, "Neighboring BSS: freq=%d", pri);
+       return 0;
+}
+
+
+void hostapd_2040_coex_action(struct hostapd_data *hapd,
+                             const struct ieee80211_mgmt *mgmt, size_t len)
+{
+       struct hostapd_iface *iface = hapd->iface;
+       struct ieee80211_2040_bss_coex_ie *bc_ie;
+       struct ieee80211_2040_intol_chan_report *ic_report;
+       int is_ht40_allowed = 1;
+       int i;
+       const u8 *start = (const u8 *) mgmt;
+       const u8 *data = start + IEEE80211_HDRLEN + 2;
+
+       hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
+                      HOSTAPD_LEVEL_DEBUG, "hostapd_public_action - action=%d",
+                      mgmt->u.action.u.public_action.action);
+
+       if (!(iface->conf->ht_capab & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET))
+               return;
+
+       if (len < IEEE80211_HDRLEN + 2 + sizeof(*bc_ie))
+               return;
+
+       bc_ie = (struct ieee80211_2040_bss_coex_ie *) data;
+       if (bc_ie->element_id != WLAN_EID_20_40_BSS_COEXISTENCE ||
+           bc_ie->length < 1) {
+               wpa_printf(MSG_DEBUG, "Unexpected IE (%u,%u) in coex report",
+                          bc_ie->element_id, bc_ie->length);
+               return;
+       }
+       if (len < IEEE80211_HDRLEN + 2 + 2 + bc_ie->length)
+               return;
+       data += 2 + bc_ie->length;
+
+       wpa_printf(MSG_DEBUG, "20/40 BSS Coexistence Information field: 0x%x",
+                  bc_ie->coex_param);
+       if (bc_ie->coex_param & WLAN_20_40_BSS_COEX_20MHZ_WIDTH_REQ) {
+               hostapd_logger(hapd, mgmt->sa,
+                              HOSTAPD_MODULE_IEEE80211,
+                              HOSTAPD_LEVEL_DEBUG,
+                              "20 MHz BSS width request bit is set in BSS coexistence information field");
+               is_ht40_allowed = 0;
+       }
+
+       if (bc_ie->coex_param & WLAN_20_40_BSS_COEX_40MHZ_INTOL) {
+               hostapd_logger(hapd, mgmt->sa,
+                              HOSTAPD_MODULE_IEEE80211,
+                              HOSTAPD_LEVEL_DEBUG,
+                              "40 MHz intolerant bit is set in BSS coexistence information field");
+               is_ht40_allowed = 0;
+       }
+
+       if (start + len - data >= 3 &&
+           data[0] == WLAN_EID_20_40_BSS_INTOLERANT && data[1] >= 1) {
+               u8 ielen = data[1];
+
+               if (ielen > start + len - data - 2)
+                       return;
+               ic_report = (struct ieee80211_2040_intol_chan_report *) data;
+               wpa_printf(MSG_DEBUG,
+                          "20/40 BSS Intolerant Channel Report: Operating Class %u",
+                          ic_report->op_class);
+
+               /* Go through the channel report to find any BSS there in the
+                * affected channel range */
+               for (i = 0; i < ielen - 1; i++) {
+                       u8 chan = ic_report->variable[i];
+
+                       if (is_40_allowed(iface, chan))
+                               continue;
+                       hostapd_logger(hapd, mgmt->sa,
+                                      HOSTAPD_MODULE_IEEE80211,
+                                      HOSTAPD_LEVEL_DEBUG,
+                                      "20_40_INTOLERANT channel %d reported",
+                                      chan);
+                       is_ht40_allowed = 0;
+               }
+       }
+       wpa_printf(MSG_DEBUG, "is_ht40_allowed=%d num_sta_ht40_intolerant=%d",
+                  is_ht40_allowed, iface->num_sta_ht40_intolerant);
+
+       if (!is_ht40_allowed &&
+           (iface->drv_flags & WPA_DRIVER_FLAGS_HT_2040_COEX)) {
+               if (iface->conf->secondary_channel) {
+                       hostapd_logger(hapd, mgmt->sa,
+                                      HOSTAPD_MODULE_IEEE80211,
+                                      HOSTAPD_LEVEL_INFO,
+                                      "Switching to 20 MHz operation");
+                       iface->conf->secondary_channel = 0;
+                       ieee802_11_set_beacons(iface);
+               }
+               if (!iface->num_sta_ht40_intolerant &&
+                   iface->conf->obss_interval) {
+                       unsigned int delay_time;
+                       delay_time = OVERLAPPING_BSS_TRANS_DELAY_FACTOR *
+                               iface->conf->obss_interval;
+                       eloop_cancel_timeout(ap_ht2040_timeout, hapd->iface,
+                                            NULL);
+                       eloop_register_timeout(delay_time, 0, ap_ht2040_timeout,
+                                              hapd->iface, NULL);
+                       wpa_printf(MSG_DEBUG,
+                                  "Reschedule HT 20/40 timeout to occur in %u seconds",
+                                  delay_time);
+               }
+       }
+}
+
+
+u16 copy_sta_ht_capab(struct hostapd_data *hapd, struct sta_info *sta,
+                     const u8 *ht_capab)
+{
+       /*
+        * Disable HT caps for STAs associated to no-HT BSSes, or for stations
+        * that did not specify a valid WMM IE in the (Re)Association Request
+        * frame.
+        */
        if (!ht_capab ||
-           ht_capab_len < sizeof(struct ieee80211_ht_capabilities)) {
+           !(sta->flags & WLAN_STA_WMM) || hapd->conf->disable_11n) {
                sta->flags &= ~WLAN_STA_HT;
                os_free(sta->ht_capabilities);
                sta->ht_capabilities = NULL;
@@ -186,6 +340,52 @@ u16 copy_sta_ht_capab(struct sta_info *sta, const u8 *ht_capab,
 }
 
 
+void ht40_intolerant_add(struct hostapd_iface *iface, struct sta_info *sta)
+{
+       if (iface->current_mode->mode != HOSTAPD_MODE_IEEE80211G)
+               return;
+
+       wpa_printf(MSG_INFO, "HT: Forty MHz Intolerant is set by STA " MACSTR
+                  " in Association Request", MAC2STR(sta->addr));
+
+       if (sta->ht40_intolerant_set)
+               return;
+
+       sta->ht40_intolerant_set = 1;
+       iface->num_sta_ht40_intolerant++;
+       eloop_cancel_timeout(ap_ht2040_timeout, iface, NULL);
+
+       if (iface->conf->secondary_channel &&
+           (iface->drv_flags & WPA_DRIVER_FLAGS_HT_2040_COEX)) {
+               iface->conf->secondary_channel = 0;
+               ieee802_11_set_beacons(iface);
+       }
+}
+
+
+void ht40_intolerant_remove(struct hostapd_iface *iface, struct sta_info *sta)
+{
+       if (!sta->ht40_intolerant_set)
+               return;
+
+       sta->ht40_intolerant_set = 0;
+       iface->num_sta_ht40_intolerant--;
+
+       if (iface->num_sta_ht40_intolerant == 0 &&
+           (iface->conf->ht_capab & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET) &&
+           (iface->drv_flags & WPA_DRIVER_FLAGS_HT_2040_COEX)) {
+               unsigned int delay_time = OVERLAPPING_BSS_TRANS_DELAY_FACTOR *
+                       iface->conf->obss_interval;
+               wpa_printf(MSG_DEBUG,
+                          "HT: Start 20->40 MHz transition timer (%d seconds)",
+                          delay_time);
+               eloop_cancel_timeout(ap_ht2040_timeout, iface, NULL);
+               eloop_register_timeout(delay_time, 0, ap_ht2040_timeout,
+                                      iface, NULL);
+       }
+}
+
+
 static void update_sta_ht(struct hostapd_data *hapd, struct sta_info *sta)
 {
        u16 ht_capab;
@@ -213,6 +413,9 @@ static void update_sta_ht(struct hostapd_data *hapd, struct sta_info *sta)
                           __func__, MAC2STR(sta->addr),
                           hapd->iface->num_sta_ht_20mhz);
        }
+
+       if (ht_capab & HT_CAP_INFO_40MHZ_INTOLERANT)
+               ht40_intolerant_add(hapd->iface, sta);
 }
 
 
@@ -253,8 +456,14 @@ void hostapd_get_ht_capab(struct hostapd_data *hapd,
                return;
        os_memcpy(neg_ht_cap, ht_cap, sizeof(*neg_ht_cap));
        cap = le_to_host16(neg_ht_cap->ht_capabilities_info);
-       cap &= hapd->iconf->ht_capab;
-       cap |= (hapd->iconf->ht_capab & HT_CAP_INFO_SMPS_DISABLED);
+
+       /*
+        * Mask out HT features we don't support, but don't overwrite
+        * non-symmetric features like STBC and SMPS. Just because
+        * we're not in dynamic SMPS mode the STA might still be.
+        */
+       cap &= (hapd->iconf->ht_capab | HT_CAP_INFO_RX_STBC_MASK |
+               HT_CAP_INFO_TX_STBC | HT_CAP_INFO_SMPS_MASK);
 
        /*
         * STBC needs to be handled specially
@@ -268,3 +477,14 @@ void hostapd_get_ht_capab(struct hostapd_data *hapd,
 
        neg_ht_cap->ht_capabilities_info = host_to_le16(cap);
 }
+
+
+void ap_ht2040_timeout(void *eloop_data, void *user_data)
+{
+       struct hostapd_iface *iface = eloop_data;
+
+       wpa_printf(MSG_INFO, "Switching to 40 MHz operation");
+
+       iface->conf->secondary_channel = iface->secondary_ch;
+       ieee802_11_set_beacons(iface);
+}