diff --git a/hostapd/config_file.c b/hostapd/config_file.c index eaf190b86..a07e71eda 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -4423,6 +4423,10 @@ static int hostapd_config_fill(struct hostapd_config *conf, bss->dhcp_server_port = atoi(pos); } else if (os_strcmp(buf, "dhcp_relay_port") == 0) { bss->dhcp_relay_port = atoi(pos); + } else if (os_strcmp(buf, "fils_discovery_min_interval") == 0) { + bss->fils_discovery_min_int = atoi(pos); + } else if (os_strcmp(buf, "fils_discovery_max_interval") == 0) { + bss->fils_discovery_max_int = atoi(pos); #endif /* CONFIG_FILS */ } else if (os_strcmp(buf, "multicast_to_unicast") == 0) { bss->multicast_to_unicast = atoi(pos); diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 769fab66a..d8a4e9474 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1987,6 +1987,13 @@ own_ip_addr=127.0.0.1 # default: 30 TUs (= 30.72 milliseconds) #fils_hlp_wait_time=30 +# FILS Discovery frame transmission minimum and maximum interval settings. +# If fils_discovery_max_interval is non-zero, the AP enables FILS Discovery +# frame transmission. These values use TUs as the unit and have allowed range +# of 0-10000. fils_discovery_min_interval defaults to 20. +#fils_discovery_min_interval=20 +#fils_discovery_max_interval=0 + # Transition Disable indication # The AP can notify authenticated stations to disable transition mode in their # network profiles when the network has completed transition steps, i.e., once diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index 8b98e6755..419bf7cad 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -131,6 +131,7 @@ void hostapd_config_defaults_bss(struct hostapd_bss_config *bss) bss->fils_hlp_wait_time = 30; bss->dhcp_server_port = DHCP_SERVER_PORT; bss->dhcp_relay_port = DHCP_SERVER_PORT; + bss->fils_discovery_min_int = 20; #endif /* CONFIG_FILS */ bss->broadcast_deauth = 1; diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index cb2bf9b24..23b5b2a5f 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -730,6 +730,8 @@ struct hostapd_bss_config { unsigned int fils_hlp_wait_time; u16 dhcp_server_port; u16 dhcp_relay_port; + u32 fils_discovery_min_int; + u32 fils_discovery_max_int; #endif /* CONFIG_FILS */ int multicast_to_unicast; diff --git a/src/ap/beacon.c b/src/ap/beacon.c index fbbe8f4db..6187c0963 100644 --- a/src/ap/beacon.c +++ b/src/ap/beacon.c @@ -1134,6 +1134,243 @@ void sta_track_del(struct hostapd_sta_info *info) } +#ifdef CONFIG_FILS + +static u16 hostapd_fils_discovery_cap(struct hostapd_data *hapd) +{ + u16 cap_info, phy_index = 0; + u8 chwidth = FD_CAP_BSS_CHWIDTH_20, mcs_nss_size = 4; + struct hostapd_hw_modes *mode = hapd->iface->current_mode; + + cap_info = FD_CAP_ESS; + if (hapd->conf->wpa) + cap_info |= FD_CAP_PRIVACY; + + if (is_6ghz_op_class(hapd->iconf->op_class)) { + phy_index = FD_CAP_PHY_INDEX_HE; + + switch (hapd->iconf->op_class) { + case 135: + mcs_nss_size += 4; + /* fallthrough */ + case 134: + mcs_nss_size += 4; + chwidth = FD_CAP_BSS_CHWIDTH_160_80_80; + break; + case 133: + chwidth = FD_CAP_BSS_CHWIDTH_80; + break; + case 132: + chwidth = FD_CAP_BSS_CHWIDTH_40; + break; + } + } else { + switch (hostapd_get_oper_chwidth(hapd->iconf)) { + case CHANWIDTH_80P80MHZ: + mcs_nss_size += 4; + /* fallthrough */ + case CHANWIDTH_160MHZ: + mcs_nss_size += 4; + chwidth = FD_CAP_BSS_CHWIDTH_160_80_80; + break; + case CHANWIDTH_80MHZ: + chwidth = FD_CAP_BSS_CHWIDTH_80; + break; + case CHANWIDTH_USE_HT: + if (hapd->iconf->secondary_channel) + chwidth = FD_CAP_BSS_CHWIDTH_40; + else + chwidth = FD_CAP_BSS_CHWIDTH_20; + break; + } + +#ifdef CONFIG_IEEE80211AX + if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax) + phy_index = FD_CAP_PHY_INDEX_HE; +#endif /* CONFIG_IEEE80211AX */ +#ifdef CONFIG_IEEE80211AC + if (!phy_index && + hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac) + phy_index = FD_CAP_PHY_INDEX_VHT; +#endif /* CONFIG_IEEE80211AC */ + if (!phy_index && + hapd->iconf->ieee80211n && !hapd->conf->disable_11n) + phy_index = FD_CAP_PHY_INDEX_HT; + } + + cap_info |= phy_index << FD_CAP_PHY_INDEX_SHIFT; + cap_info |= chwidth << FD_CAP_BSS_CHWIDTH_SHIFT; + + if (mode) { + u16 *mcs = (u16 *) mode->he_capab[IEEE80211_MODE_AP].mcs; + int i; + u16 nss = 0; + + for (i = 0; i < HE_NSS_MAX_STREAMS; i++) { + u16 nss_mask = 0x3 << (i * 2); + + if (mcs_nss_size == 4 && + (((mcs[0] & nss_mask) == nss_mask) || + ((mcs[1] & nss_mask) == nss_mask))) + continue; + + if (mcs_nss_size == 8 && + (((mcs[2] & nss_mask) == nss_mask) || + ((mcs[3] & nss_mask) == nss_mask))) + continue; + + if (mcs_nss_size == 12 && + (((mcs[4] & nss_mask) == nss_mask) || + ((mcs[5] & nss_mask) == nss_mask))) + continue; + + nss++; + } + + if (nss > 4) + cap_info |= FD_CAP_NSS_5_8 << FD_CAP_NSS_SHIFT; + else if (nss) + cap_info |= (nss - 1) << FD_CAP_NSS_SHIFT; + } + + return cap_info; +} + + +static u8 * hostapd_gen_fils_discovery(struct hostapd_data *hapd, size_t *len) +{ + struct ieee80211_mgmt *head; + const u8 *mobility_domain; + u8 *pos, *length_pos, buf[200]; + u16 ctl = 0; + u8 fd_rsn_info[5]; + size_t total_len, buf_len; + + total_len = 24 + 2 + 12; + + /* FILS Discovery Frame Control */ + ctl = (sizeof(hapd->conf->ssid.short_ssid) - 1) | + FD_FRAME_CTL_SHORT_SSID_PRESENT | + FD_FRAME_CTL_LENGTH_PRESENT | + FD_FRAME_CTL_CAP_PRESENT; + total_len += 4 + 1 + 2; + + /* Check for optional subfields and calculate length */ + if (wpa_auth_write_fd_rsn_info(hapd->wpa_auth, fd_rsn_info)) { + ctl |= FD_FRAME_CTL_RSN_INFO_PRESENT; + total_len += sizeof(fd_rsn_info); + } + + mobility_domain = hostapd_wpa_ie(hapd, WLAN_EID_MOBILITY_DOMAIN); + if (mobility_domain) { + ctl |= FD_FRAME_CTL_MD_PRESENT; + total_len += 3; + } + + pos = hostapd_eid_fils_indic(hapd, buf, 0); + buf_len = pos - buf; + total_len += buf_len; + + head = os_zalloc(total_len); + if (!head) + return NULL; + + head->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + os_memset(head->da, 0xff, ETH_ALEN); + os_memcpy(head->sa, hapd->own_addr, ETH_ALEN); + os_memcpy(head->bssid, hapd->own_addr, ETH_ALEN); + + head->u.action.category = WLAN_ACTION_PUBLIC; + head->u.action.u.public_action.action = WLAN_PA_FILS_DISCOVERY; + + pos = &head->u.action.u.public_action.variable[0]; + + /* FILS Discovery Information field */ + + /* FILS Discovery Frame Control */ + WPA_PUT_LE16(pos, ctl); + pos += 2; + + /* Hardware or low-level driver will fill in the Timestamp value */ + pos += 8; + + /* Beacon Interval */ + WPA_PUT_LE16(pos, hapd->iconf->beacon_int); + pos += 2; + + /* Short SSID */ + WPA_PUT_LE32(pos, hapd->conf->ssid.short_ssid); + pos += sizeof(hapd->conf->ssid.short_ssid); + + /* Store position of FILS discovery information element Length field */ + length_pos = pos++; + + /* FD Capability */ + WPA_PUT_LE16(pos, hostapd_fils_discovery_cap(hapd)); + pos += 2; + + /* Operating Class - not present */ + + /* Primary Channel - not present */ + + /* AP Configuration Sequence Number - not present */ + + /* Access Network Options - not present */ + + /* FD RSN Information */ + if (ctl & FD_FRAME_CTL_RSN_INFO_PRESENT) { + os_memcpy(pos, fd_rsn_info, sizeof(fd_rsn_info)); + pos += sizeof(fd_rsn_info); + } + + /* Channel Center Frequency Segment 1 - not present */ + + /* Mobility Domain */ + if (ctl & FD_FRAME_CTL_MD_PRESENT) { + os_memcpy(pos, &mobility_domain[2], 3); + pos += 3; + } + + /* Fill in the Length field value */ + *length_pos = pos - (length_pos + 1); + + /* FILS Indication element */ + if (buf_len) { + os_memcpy(pos, buf, buf_len); + pos += buf_len; + } + + *len = pos - (u8 *) head; + wpa_hexdump(MSG_DEBUG, "FILS Discovery frame template", + head, pos - (u8 *) head); + return (u8 *) head; +} + + +/* Configure FILS Discovery frame transmission parameters */ +static u8 * hostapd_fils_discovery(struct hostapd_data *hapd, + struct wpa_driver_ap_params *params) +{ + params->fd_max_int = hapd->conf->fils_discovery_max_int; + if (is_6ghz_op_class(hapd->iconf->op_class) && + params->fd_max_int > FD_MAX_INTERVAL_6GHZ) + params->fd_max_int = FD_MAX_INTERVAL_6GHZ; + + params->fd_min_int = hapd->conf->fils_discovery_min_int; + if (params->fd_min_int > params->fd_max_int) + params->fd_min_int = params->fd_max_int; + + if (params->fd_max_int) + return hostapd_gen_fils_discovery(hapd, + ¶ms->fd_frame_tmpl_len); + + return NULL; +} + +#endif /* CONFIG_FILS */ + + int ieee802_11_build_ap_params(struct hostapd_data *hapd, struct wpa_driver_ap_params *params) { @@ -1478,6 +1715,10 @@ void ieee802_11_free_ap_params(struct wpa_driver_ap_params *params) params->head = NULL; os_free(params->proberesp); params->proberesp = NULL; +#ifdef CONFIG_FILS + os_free(params->fd_frame_tmpl); + params->fd_frame_tmpl = NULL; +#endif /* CONFIG_FILS */ } @@ -1534,6 +1775,10 @@ int ieee802_11_set_beacon(struct hostapd_data *hapd) params.sae_pwe = hapd->conf->sae_pwe; #endif /* CONFIG_SAE */ +#ifdef CONFIG_FILS + params.fd_frame_tmpl = hostapd_fils_discovery(hapd, ¶ms); +#endif /* CONFIG_FILS */ + if (cmode && hostapd_set_freq_params(&freq, iconf->hw_mode, iface->freq, iconf->channel, iconf->enable_edmg, diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h index cf0801dc3..eaa2cafc8 100644 --- a/src/ap/wpa_auth.h +++ b/src/ap/wpa_auth.h @@ -537,6 +537,8 @@ u8 * wpa_auth_write_assoc_resp_owe(struct wpa_state_machine *sm, u8 * wpa_auth_write_assoc_resp_fils(struct wpa_state_machine *sm, u8 *pos, size_t max_len, const u8 *req_ies, size_t req_ies_len); +bool wpa_auth_write_fd_rsn_info(struct wpa_authenticator *wpa_auth, + u8 *fd_rsn_info); void wpa_auth_set_auth_alg(struct wpa_state_machine *sm, u16 auth_alg); void wpa_auth_set_dpp_z(struct wpa_state_machine *sm, const struct wpabuf *z); void wpa_auth_set_transition_disable(struct wpa_authenticator *wpa_auth, diff --git a/src/ap/wpa_auth_ie.c b/src/ap/wpa_auth_ie.c index ab427868a..d4710316a 100644 --- a/src/ap/wpa_auth_ie.c +++ b/src/ap/wpa_auth_ie.c @@ -1125,6 +1125,7 @@ u8 * wpa_auth_write_assoc_resp_owe(struct wpa_state_machine *sm, #ifdef CONFIG_FILS + u8 * wpa_auth_write_assoc_resp_fils(struct wpa_state_machine *sm, u8 *pos, size_t max_len, const u8 *req_ies, size_t req_ies_len) @@ -1141,4 +1142,91 @@ u8 * wpa_auth_write_assoc_resp_fils(struct wpa_state_machine *sm, return pos; return pos + res; } + + +bool wpa_auth_write_fd_rsn_info(struct wpa_authenticator *wpa_auth, + u8 *fd_rsn_info) +{ + struct wpa_auth_config *conf; + u32 selectors = 0; + u8 *pos = fd_rsn_info; + int i, res; + u32 cipher, suite, selector, mask; + u8 tmp[10 * RSN_SELECTOR_LEN]; + + if (!wpa_auth) + return false; + conf = &wpa_auth->conf; + + if (!(conf->wpa & WPA_PROTO_RSN)) + return false; + + /* RSN Capability (B0..B15) */ + WPA_PUT_LE16(pos, wpa_own_rsn_capab(conf)); + pos += 2; + + /* Group Data Cipher Suite Selector (B16..B21) */ + suite = wpa_cipher_to_suite(WPA_PROTO_RSN, conf->wpa_group); + if (suite == RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED) + cipher = 63; /* No cipher suite selected */ + if ((suite >> 8) == 0x000fac && ((suite & 0xff) <= 13)) + cipher = suite & 0xff; + else + cipher = 62; /* vendor specific */ + selectors |= cipher; + + /* Group Management Cipher Suite Selector (B22..B27) */ + cipher = 63; /* Default to no cipher suite selected */ + if (conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) { + switch (conf->group_mgmt_cipher) { + case WPA_CIPHER_AES_128_CMAC: + cipher = RSN_CIPHER_SUITE_AES_128_CMAC & 0xff; + break; + case WPA_CIPHER_BIP_GMAC_128: + cipher = RSN_CIPHER_SUITE_BIP_GMAC_128 & 0xff; + break; + case WPA_CIPHER_BIP_GMAC_256: + cipher = RSN_CIPHER_SUITE_BIP_GMAC_256 & 0xff; + break; + case WPA_CIPHER_BIP_CMAC_256: + cipher = RSN_CIPHER_SUITE_BIP_CMAC_256 & 0xff; + break; + } + } + selectors |= cipher << 6; + + /* Pairwise Cipher Suite Selector (B28..B33) */ + cipher = 63; /* Default to no cipher suite selected */ + res = rsn_cipher_put_suites(tmp, conf->rsn_pairwise); + if (res == 1 && tmp[0] == 0x00 && tmp[1] == 0x0f && tmp[2] == 0xac && + tmp[3] <= 13) + cipher = tmp[3]; + selectors |= cipher << 12; + + /* AKM Suite Selector (B34..B39) */ + selector = 0; /* default to AKM from RSNE in Beacon/Probe Response */ + mask = WPA_KEY_MGMT_FILS_SHA256 | WPA_KEY_MGMT_FILS_SHA384 | + WPA_KEY_MGMT_FT_FILS_SHA384; + if ((conf->wpa_key_mgmt & mask) && (conf->wpa_key_mgmt & ~mask) == 0) { + suite = conf->wpa_key_mgmt & mask; + if (suite == WPA_KEY_MGMT_FILS_SHA256) + selector = 1; /* 00-0f-ac:14 */ + else if (suite == WPA_KEY_MGMT_FILS_SHA384) + selector = 2; /* 00-0f-ac:15 */ + else if (suite == (WPA_KEY_MGMT_FILS_SHA256 | + WPA_KEY_MGMT_FILS_SHA384)) + selector = 3; /* 00-0f-ac:14 or 00-0f-ac:15 */ + else if (suite == WPA_KEY_MGMT_FT_FILS_SHA384) + selector = 4; /* 00-0f-ac:17 */ + } + selectors |= selector << 18; + + for (i = 0; i < 3; i++) { + *pos++ = selectors & 0xff; + selectors >>= 8; + } + + return true; +} + #endif /* CONFIG_FILS */ diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 1a42381d7..2a61b550e 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -1541,6 +1541,26 @@ struct wpa_driver_ap_params { * 2 = both hunting-and-pecking loop and hash-to-element enabled */ int sae_pwe; + + /** + * FILS Discovery frame minimum interval in TUs + */ + u32 fd_min_int; + + /** + * FILS Discovery frame maximum interval in TUs (0 = FD frame disabled) + */ + u32 fd_max_int; + + /** + * FILS Discovery frame template data + */ + u8 *fd_frame_tmpl; + + /** + * FILS Discovery frame template length + */ + size_t fd_frame_tmpl_len; }; struct wpa_driver_mesh_bss_params {