diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h index 66801fd5d..25bc5c6a4 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -519,6 +519,9 @@ struct ieee80211_mgmt { } STRUCT_PACKED; +/* Rx MCS bitmask is in the first 77 bits of supported_mcs_set */ +#define IEEE80211_HT_MCS_MASK_LEN 10 + struct ieee80211_ht_capabilities { le16 ht_capabilities_info; u8 a_mpdu_params; diff --git a/src/drivers/driver.h b/src/drivers/driver.h index f56c41b91..8bfb12b74 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -523,6 +523,19 @@ struct wpa_driver_associate_params { * 0 = Do not fix BSSID. */ int fixed_bssid; + + /** + * disable_ht - Disable HT (IEEE 802.11n) for this connection + */ + int disable_ht; + + /** + * HT Capabilities over-rides. Only bits set in the mask will be used, + * and not all values are used by the kernel anyway. Currently, MCS, + * MPDU and MSDU fields are used. + */ + const u8 *htcaps; /* struct ieee80211_ht_capabilities * */ + const u8 *htcaps_mask; /* struct ieee80211_ht_capabilities * */ }; enum hide_ssid { diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 0bf1dca8b..381be6656 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -6648,6 +6648,16 @@ skip_auth_type: NLA_PUT_U32(msg, NL80211_ATTR_AKM_SUITES, mgmt); } + if (params->disable_ht) + NLA_PUT_FLAG(msg, NL80211_ATTR_DISABLE_HT); + + if (params->htcaps && params->htcaps_mask) { + int sz = sizeof(struct ieee80211_ht_capabilities); + NLA_PUT(msg, NL80211_ATTR_HT_CAPABILITY, sz, params->htcaps); + NLA_PUT(msg, NL80211_ATTR_HT_CAPABILITY_MASK, sz, + params->htcaps_mask); + } + ret = nl80211_set_conn_keys(params, msg); if (ret) goto nla_put_failure; @@ -6795,6 +6805,16 @@ static int wpa_driver_nl80211_associate( params->prev_bssid); } + if (params->disable_ht) + NLA_PUT_FLAG(msg, NL80211_ATTR_DISABLE_HT); + + if (params->htcaps && params->htcaps_mask) { + int sz = sizeof(struct ieee80211_ht_capabilities); + NLA_PUT(msg, NL80211_ATTR_HT_CAPABILITY, sz, params->htcaps); + NLA_PUT(msg, NL80211_ATTR_HT_CAPABILITY_MASK, sz, + params->htcaps_mask); + } + if (params->p2p) wpa_printf(MSG_DEBUG, " * P2P group"); diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index fd1c5d3b8..5eef85560 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -113,6 +113,10 @@ ifdef CONFIG_EAPOL_TEST CFLAGS += -Werror -DEAPOL_TEST endif +ifdef CONFIG_HT_OVERRIDES +CFLAGS += -DCONFIG_HT_OVERRIDES +endif + ifndef CONFIG_BACKEND CONFIG_BACKEND=file endif diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index 0fd1f3e0d..89fba408a 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -1598,6 +1598,14 @@ static const struct parse_data ssid_fields[] = { #ifdef CONFIG_P2P { FUNC(p2p_client_list) }, #endif /* CONFIG_P2P */ +#ifdef CONFIG_HT_OVERRIDES + { INT_RANGE(disable_ht, 0, 1) }, + { INT_RANGE(disable_ht40, -1, 1) }, + { INT_RANGE(disable_max_amsdu, -1, 1) }, + { INT_RANGE(ampdu_factor, -1, 3) }, + { INT_RANGE(ampdu_density, -1, 7) }, + { STR(ht_mcs) }, +#endif /* CONFIG_HT_OVERRIDES */ }; #undef OFFSET @@ -1765,6 +1773,9 @@ void wpa_config_free_ssid(struct wpa_ssid *ssid) os_free(ssid->freq_list); os_free(ssid->bgscan); os_free(ssid->p2p_client_list); +#ifdef CONFIG_HT_OVERRIDES + os_free(ssid->ht_mcs); +#endif /* CONFIG_HT_OVERRIDES */ os_free(ssid); } @@ -1952,6 +1963,13 @@ void wpa_config_set_network_defaults(struct wpa_ssid *ssid) ssid->eap_workaround = DEFAULT_EAP_WORKAROUND; ssid->eap.fragment_size = DEFAULT_FRAGMENT_SIZE; #endif /* IEEE8021X_EAPOL */ +#ifdef CONFIG_HT_OVERRIDES + ssid->disable_ht = DEFAULT_DISABLE_HT; + ssid->disable_ht40 = DEFAULT_DISABLE_HT40; + ssid->disable_max_amsdu = DEFAULT_DISABLE_MAX_AMSDU; + ssid->ampdu_factor = DEFAULT_AMPDU_FACTOR; + ssid->ampdu_density = DEFAULT_AMPDU_DENSITY; +#endif /* CONFIG_HT_OVERRIDES */ } diff --git a/wpa_supplicant/config_ssid.h b/wpa_supplicant/config_ssid.h index 8a47c0b9d..9fa7f8bda 100644 --- a/wpa_supplicant/config_ssid.h +++ b/wpa_supplicant/config_ssid.h @@ -31,6 +31,12 @@ WPA_CIPHER_WEP104 | WPA_CIPHER_WEP40) #define DEFAULT_FRAGMENT_SIZE 1398 +#define DEFAULT_DISABLE_HT 0 +#define DEFAULT_DISABLE_HT40 0 +#define DEFAULT_DISABLE_MAX_AMSDU -1 /* no change */ +#define DEFAULT_AMPDU_FACTOR -1 /* no change */ +#define DEFAULT_AMPDU_DENSITY -1 /* no change */ + /** * struct wpa_ssid - Network configuration data * @@ -422,6 +428,54 @@ struct wpa_ssid { * WPS or similar so that they may be exported. */ int export_keys; + +#ifdef CONFIG_HT_OVERRIDES + /** + * disable_ht - Disable HT (IEEE 802.11n) for this network + * + * By default, use it if it is available, but this can be configured + * to 1 to have it disabled. + */ + int disable_ht; + + /** + * disable_ht40 - Disable HT40 for this network + * + * By default, use it if it is available, but this can be configured + * to 1 to have it disabled. + */ + int disable_ht40; + + /** + * disable_max_amsdu - Disable MAX A-MSDU + * + * A-MDSU will be 3839 bytes when disabled, or 7935 + * when enabled (assuming it is otherwise supported) + * -1 (default) means do not apply any settings to the kernel. + */ + int disable_max_amsdu; + + /** + * ampdu_factor - Maximum A-MPDU Length Exponent + * + * Value: 0-3, see 7.3.2.56.3 in IEEE Std 802.11n-2009. + */ + int ampdu_factor; + + /** + * ampdu_density - Minimum A-MPDU Start Spacing + * + * Value: 0-7, see 7.3.2.56.3 in IEEE Std 802.11n-2009. + */ + int ampdu_density; + + /** + * ht_mcs - Allowed HT-MCS rates, in ASCII hex: ffff0000... + * + * By default (empty string): Use whatever the OS has configured. + */ + char *ht_mcs; +#endif /* CONFIG_HT_OVERRIDES */ }; #endif /* CONFIG_SSID_H */ diff --git a/wpa_supplicant/defconfig b/wpa_supplicant/defconfig index cff25d6c8..69a86252b 100644 --- a/wpa_supplicant/defconfig +++ b/wpa_supplicant/defconfig @@ -217,6 +217,9 @@ CONFIG_SMARTCARD=y # Enable this if EAP-SIM or EAP-AKA is included #CONFIG_PCSC=y +# Support HT overrides (disable HT/HT40, mask MCS rates, etc.) +#CONFIG_HT_OVERRIDES=y + # Development testing #CONFIG_EAPOL_TEST=y diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c index fc283477b..c07b7a631 100644 --- a/wpa_supplicant/sme.c +++ b/wpa_supplicant/sme.c @@ -367,6 +367,10 @@ void sme_associate(struct wpa_supplicant *wpa_s, enum wpas_mode mode, { struct wpa_driver_associate_params params; struct ieee802_11_elems elems; +#ifdef CONFIG_HT_OVERRIDES + struct ieee80211_ht_capabilities htcaps; + struct ieee80211_ht_capabilities htcaps_mask; +#endif /* CONFIG_HT_OVERRIDES */ os_memset(¶ms, 0, sizeof(params)); params.bssid = bssid; @@ -378,6 +382,13 @@ void sme_associate(struct wpa_supplicant *wpa_s, enum wpas_mode mode, params.wpa_ie_len = wpa_s->sme.assoc_req_ie_len; params.pairwise_suite = cipher_suite2driver(wpa_s->pairwise_cipher); params.group_suite = cipher_suite2driver(wpa_s->group_cipher); +#ifdef CONFIG_HT_OVERRIDES + os_memset(&htcaps, 0, sizeof(htcaps)); + os_memset(&htcaps_mask, 0, sizeof(htcaps_mask)); + params.htcaps = (u8 *) &htcaps; + params.htcaps_mask = (u8 *) &htcaps_mask; + wpa_supplicant_apply_ht_overrides(wpa_s, wpa_s->current_ssid, ¶ms); +#endif /* CONFIG_HT_OVERRIDES */ #ifdef CONFIG_IEEE80211R if (auth_type == WLAN_AUTH_FT && wpa_s->sme.ft_ies) { params.wpa_ie = wpa_s->sme.ft_ies; diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index 304ae098a..4d3e30735 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -1110,6 +1110,10 @@ void wpa_supplicant_associate(struct wpa_supplicant *wpa_s, struct wpa_driver_capa capa; int assoc_failed = 0; struct wpa_ssid *old_ssid; +#ifdef CONFIG_HT_OVERRIDES + struct ieee80211_ht_capabilities htcaps; + struct ieee80211_ht_capabilities htcaps_mask; +#endif /* CONFIG_HT_OVERRIDES */ #ifdef CONFIG_IBSS_RSN ibss_rsn_deinit(wpa_s->ibss_rsn); @@ -1411,6 +1415,14 @@ void wpa_supplicant_associate(struct wpa_supplicant *wpa_s, else params.uapsd = -1; +#ifdef CONFIG_HT_OVERRIDES + os_memset(&htcaps, 0, sizeof(htcaps)); + os_memset(&htcaps_mask, 0, sizeof(htcaps_mask)); + params.htcaps = (u8 *) &htcaps; + params.htcaps_mask = (u8 *) &htcaps_mask; + wpa_supplicant_apply_ht_overrides(wpa_s, ssid, ¶ms); +#endif /* CONFIG_HT_OVERRIDES */ + ret = wpa_drv_associate(wpa_s, ¶ms); if (ret < 0) { wpa_msg(wpa_s, MSG_INFO, "Association request to the driver " @@ -2165,6 +2177,183 @@ static struct wpa_supplicant * wpa_supplicant_alloc(void) } +#ifdef CONFIG_HT_OVERRIDES + +static int wpa_set_htcap_mcs(struct wpa_supplicant *wpa_s, + struct ieee80211_ht_capabilities *htcaps, + struct ieee80211_ht_capabilities *htcaps_mask, + const char *ht_mcs) +{ + /* parse ht_mcs into hex array */ + int i; + const char *tmp = ht_mcs; + char *end = NULL; + + /* If ht_mcs is null, do not set anything */ + if (!ht_mcs) + return 0; + + /* This is what we are setting in the kernel */ + os_memset(&htcaps->supported_mcs_set, 0, IEEE80211_HT_MCS_MASK_LEN); + + wpa_msg(wpa_s, MSG_DEBUG, "set_htcap, ht_mcs -:%s:-", ht_mcs); + + for (i = 0; i < IEEE80211_HT_MCS_MASK_LEN; i++) { + errno = 0; + long v = strtol(tmp, &end, 16); + if (errno == 0) { + wpa_msg(wpa_s, MSG_DEBUG, + "htcap value[%i]: %ld end: %p tmp: %p", + i, v, end, tmp); + if (end == tmp) + break; + + htcaps->supported_mcs_set[i] = v; + tmp = end; + } else { + wpa_msg(wpa_s, MSG_ERROR, + "Failed to parse ht-mcs: %s, error: %s\n", + ht_mcs, strerror(errno)); + return -1; + } + } + + /* + * If we were able to parse any values, then set mask for the MCS set. + */ + if (i) { + os_memset(&htcaps_mask->supported_mcs_set, 0xff, + IEEE80211_HT_MCS_MASK_LEN - 1); + /* skip the 3 reserved bits */ + htcaps_mask->supported_mcs_set[IEEE80211_HT_MCS_MASK_LEN - 1] = + 0x1f; + } + + return 0; +} + + +static int wpa_disable_max_amsdu(struct wpa_supplicant *wpa_s, + struct ieee80211_ht_capabilities *htcaps, + struct ieee80211_ht_capabilities *htcaps_mask, + int disabled) +{ + u16 msk; + + wpa_msg(wpa_s, MSG_DEBUG, "set_disable_max_amsdu: %d", disabled); + + if (disabled == -1) + return 0; + + msk = host_to_le16(HT_CAP_INFO_MAX_AMSDU_SIZE); + htcaps_mask->ht_capabilities_info |= msk; + if (disabled) + htcaps->ht_capabilities_info &= msk; + else + htcaps->ht_capabilities_info |= msk; + + return 0; +} + + +static int wpa_set_ampdu_factor(struct wpa_supplicant *wpa_s, + struct ieee80211_ht_capabilities *htcaps, + struct ieee80211_ht_capabilities *htcaps_mask, + int factor) +{ + wpa_msg(wpa_s, MSG_DEBUG, "set_ampdu_factor: %d", factor); + + if (factor == -1) + return 0; + + if (factor < 0 || factor > 3) { + wpa_msg(wpa_s, MSG_ERROR, "ampdu_factor: %d out of range. " + "Must be 0-3 or -1", factor); + return -EINVAL; + } + + htcaps_mask->a_mpdu_params |= 0x3; /* 2 bits for factor */ + htcaps->a_mpdu_params &= ~0x3; + htcaps->a_mpdu_params |= factor & 0x3; + + return 0; +} + + +static int wpa_set_ampdu_density(struct wpa_supplicant *wpa_s, + struct ieee80211_ht_capabilities *htcaps, + struct ieee80211_ht_capabilities *htcaps_mask, + int density) +{ + wpa_msg(wpa_s, MSG_DEBUG, "set_ampdu_density: %d", density); + + if (density == -1) + return 0; + + if (density < 0 || density > 7) { + wpa_msg(wpa_s, MSG_ERROR, + "ampdu_density: %d out of range. Must be 0-7 or -1.", + density); + return -EINVAL; + } + + htcaps_mask->a_mpdu_params |= 0x1C; + htcaps->a_mpdu_params &= ~(0x1C); + htcaps->a_mpdu_params |= (density << 2) & 0x1C; + + return 0; +} + + +static int wpa_set_disable_ht40(struct wpa_supplicant *wpa_s, + struct ieee80211_ht_capabilities *htcaps, + struct ieee80211_ht_capabilities *htcaps_mask, + int disabled) +{ + /* Masking these out disables HT40 */ + u16 msk = host_to_le16(HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET | + HT_CAP_INFO_SHORT_GI40MHZ); + + wpa_msg(wpa_s, MSG_DEBUG, "set_disable_ht40: %d", disabled); + + if (disabled) + htcaps->ht_capabilities_info &= ~msk; + else + htcaps->ht_capabilities_info |= msk; + + htcaps_mask->ht_capabilities_info |= msk; + + return 0; +} + + +void wpa_supplicant_apply_ht_overrides( + struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, + struct wpa_driver_associate_params *params) +{ + struct ieee80211_ht_capabilities *htcaps; + struct ieee80211_ht_capabilities *htcaps_mask; + + if (!ssid) + return; + + params->disable_ht = ssid->disable_ht; + if (!params->htcaps || !params->htcaps_mask) + return; + + htcaps = (struct ieee80211_ht_capabilities *) params->htcaps; + htcaps_mask = (struct ieee80211_ht_capabilities *) params->htcaps_mask; + wpa_set_htcap_mcs(wpa_s, htcaps, htcaps_mask, ssid->ht_mcs); + wpa_disable_max_amsdu(wpa_s, htcaps, htcaps_mask, + ssid->disable_max_amsdu); + wpa_set_ampdu_factor(wpa_s, htcaps, htcaps_mask, ssid->ampdu_factor); + wpa_set_ampdu_density(wpa_s, htcaps, htcaps_mask, ssid->ampdu_density); + wpa_set_disable_ht40(wpa_s, htcaps, htcaps_mask, ssid->disable_ht40); +} + +#endif /* CONFIG_HT_OVERRIDES */ + + static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s, struct wpa_interface *iface) { diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index 91982e581..b4d8b280a 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -36,6 +36,7 @@ struct scan_info; struct wpa_bss; struct wpa_scan_results; struct hostapd_hw_modes; +struct wpa_driver_associate_params; /* * Forward declarations of private structures used within the ctrl_iface @@ -528,6 +529,10 @@ struct wpa_supplicant { /* wpa_supplicant.c */ +void wpa_supplicant_apply_ht_overrides( + struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, + struct wpa_driver_associate_params *params); + int wpa_set_wep_keys(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid); int wpa_supplicant_reload_configuration(struct wpa_supplicant *wpa_s);