diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h index ce2bb9162..1eab939f8 100644 --- a/src/ap/ap_drv_ops.h +++ b/src/ap/ap_drv_ops.h @@ -257,4 +257,13 @@ static inline const char * hostapd_drv_get_radio_name(struct hostapd_data *hapd) return hapd->driver->get_radio_name(hapd->drv_priv); } +static inline int hostapd_drv_switch_channel(struct hostapd_data *hapd, + struct csa_settings *settings) +{ + if (hapd->driver == NULL || hapd->driver->switch_channel == NULL) + return -ENOTSUP; + + return hapd->driver->switch_channel(hapd->drv_priv, settings); +} + #endif /* AP_DRV_OPS */ diff --git a/src/ap/beacon.c b/src/ap/beacon.c index 4b75a757b..298c0fa13 100644 --- a/src/ap/beacon.c +++ b/src/ap/beacon.c @@ -203,13 +203,34 @@ static u8 * hostapd_eid_wpa(struct hostapd_data *hapd, u8 *eid, size_t len) } +static u8 * hostapd_eid_csa(struct hostapd_data *hapd, u8 *eid) +{ + u8 chan; + + if (!hapd->iface->cs_freq) + return eid; + + if (ieee80211_freq_to_chan(hapd->iface->cs_freq, &chan) == + NUM_HOSTAPD_MODES) + return eid; + + *eid++ = WLAN_EID_CHANNEL_SWITCH; + *eid++ = 3; + *eid++ = hapd->iface->cs_block_tx; + *eid++ = chan; + *eid++ = hapd->iface->cs_count; + + return eid; +} + + static u8 * hostapd_gen_probe_resp(struct hostapd_data *hapd, struct sta_info *sta, const struct ieee80211_mgmt *req, int is_p2p, size_t *resp_len) { struct ieee80211_mgmt *resp; - u8 *pos, *epos; + u8 *pos, *epos, *old_pos; size_t buflen; #define MAX_PROBERESP_LEN 768 @@ -283,6 +304,13 @@ static u8 * hostapd_gen_probe_resp(struct hostapd_data *hapd, pos = hostapd_eid_adv_proto(hapd, pos); pos = hostapd_eid_roaming_consortium(hapd, pos); + old_pos = pos; + pos = hostapd_eid_csa(hapd, pos); + + /* save an offset to the counter - should be last byte */ + hapd->iface->cs_c_off_proberesp = (pos != old_pos) ? + pos - (u8 *) resp - 1 : 0; + #ifdef CONFIG_IEEE80211AC pos = hostapd_eid_vht_capabilities(hapd, pos); pos = hostapd_eid_vht_operation(hapd, pos); @@ -598,7 +626,7 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, size_t resp_len = 0; #ifdef NEED_AP_MLME u16 capab_info; - u8 *pos, *tailpos; + u8 *pos, *tailpos, *old_pos; #define BEACON_HEAD_BUF_SIZE 256 #define BEACON_TAIL_BUF_SIZE 512 @@ -693,6 +721,10 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, tailpos = hostapd_eid_interworking(hapd, tailpos); tailpos = hostapd_eid_adv_proto(hapd, tailpos); tailpos = hostapd_eid_roaming_consortium(hapd, tailpos); + old_pos = tailpos; + tailpos = hostapd_eid_csa(hapd, tailpos); + hapd->iface->cs_c_off_beacon = (old_pos != tailpos) ? + tailpos - tail - 1 : 0; #ifdef CONFIG_IEEE80211AC tailpos = hostapd_eid_vht_capabilities(hapd, tailpos); @@ -817,6 +849,11 @@ void ieee802_11_set_beacon(struct hostapd_data *hapd) struct wpa_driver_ap_params params; struct wpabuf *beacon, *proberesp, *assocresp; + if (hapd->iface->csa_in_progress) { + wpa_printf(MSG_ERROR, "Cannot set beacons during CSA period"); + return; + } + hapd->beacon_set_done = 1; if (ieee802_11_build_ap_params(hapd, ¶ms) < 0) diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c index aee29467c..1b69ba826 100644 --- a/src/ap/drv_callbacks.c +++ b/src/ap/drv_callbacks.c @@ -403,6 +403,13 @@ void hostapd_event_ch_switch(struct hostapd_data *hapd, int freq, int ht, hapd->iconf->channel = channel; hapd->iconf->ieee80211n = ht; hapd->iconf->secondary_channel = offset; + + if (hapd->iface->csa_in_progress && freq == hapd->iface->cs_freq) { + hostapd_cleanup_cs_params(hapd); + + wpa_msg(hapd->msg_ctx, MSG_INFO, AP_CSA_FINISHED "freq=%d", + freq); + } #endif /* NEED_AP_MLME */ } diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index a06ec9f34..51b1035b8 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -2014,3 +2014,230 @@ void hostapd_set_state(struct hostapd_iface *iface, enum hostapd_iface_state s) hostapd_state_text(s)); iface->state = s; } + + +#ifdef NEED_AP_MLME + +static void free_beacon_data(struct beacon_data *beacon) +{ + os_free(beacon->head); + beacon->head = NULL; + os_free(beacon->tail); + beacon->tail = NULL; + os_free(beacon->probe_resp); + beacon->probe_resp = NULL; + os_free(beacon->beacon_ies); + beacon->beacon_ies = NULL; + os_free(beacon->proberesp_ies); + beacon->proberesp_ies = NULL; + os_free(beacon->assocresp_ies); + beacon->assocresp_ies = NULL; +} + + +static int hostapd_build_beacon_data(struct hostapd_iface *iface, + struct beacon_data *beacon) +{ + struct wpabuf *beacon_extra, *proberesp_extra, *assocresp_extra; + struct wpa_driver_ap_params params; + int ret; + struct hostapd_data *hapd = iface->bss[0]; + + ret = ieee802_11_build_ap_params(hapd, ¶ms); + if (ret < 0) + return ret; + + ret = hostapd_build_ap_extra_ies(hapd, &beacon_extra, + &proberesp_extra, + &assocresp_extra); + if (ret) + goto free_ap_params; + + ret = -1; + beacon->head = os_malloc(params.head_len); + if (!beacon->head) + goto free_ap_extra_ies; + + os_memcpy(beacon->head, params.head, params.head_len); + beacon->head_len = params.head_len; + + beacon->tail = os_malloc(params.tail_len); + if (!beacon->tail) + goto free_beacon; + + os_memcpy(beacon->tail, params.tail, params.tail_len); + beacon->tail_len = params.tail_len; + + if (params.proberesp != NULL) { + beacon->probe_resp = os_malloc(params.proberesp_len); + if (!beacon->probe_resp) + goto free_beacon; + + os_memcpy(beacon->probe_resp, params.proberesp, + params.proberesp_len); + beacon->probe_resp_len = params.proberesp_len; + } + + /* copy the extra ies */ + if (beacon_extra) { + beacon->beacon_ies = os_malloc(wpabuf_len(beacon_extra)); + if (!beacon->beacon_ies) + goto free_beacon; + + os_memcpy(beacon->beacon_ies, + beacon_extra->buf, wpabuf_len(beacon_extra)); + beacon->beacon_ies_len = wpabuf_len(beacon_extra); + } + + if (proberesp_extra) { + beacon->proberesp_ies = + os_malloc(wpabuf_len(proberesp_extra)); + if (!beacon->proberesp_ies) + goto free_beacon; + + os_memcpy(beacon->proberesp_ies, proberesp_extra->buf, + wpabuf_len(proberesp_extra)); + beacon->proberesp_ies_len = wpabuf_len(proberesp_extra); + } + + if (assocresp_extra) { + beacon->assocresp_ies = + os_malloc(wpabuf_len(assocresp_extra)); + if (!beacon->assocresp_ies) + goto free_beacon; + + os_memcpy(beacon->assocresp_ies, assocresp_extra->buf, + wpabuf_len(assocresp_extra)); + beacon->assocresp_ies_len = wpabuf_len(assocresp_extra); + } + + ret = 0; +free_beacon: + /* if the function fails, the caller should not free beacon data */ + if (ret) + free_beacon_data(beacon); + +free_ap_extra_ies: + hostapd_free_ap_extra_ies(hapd, beacon_extra, proberesp_extra, + assocresp_extra); +free_ap_params: + ieee802_11_free_ap_params(¶ms); + return ret; +} + + +/* + * TODO: This flow currently supports only changing frequency within the + * same hw_mode. Any other changes to MAC parameters or provided settings (even + * width) are not supported. + */ +static int hostapd_change_config_freq(struct hostapd_data *hapd, + struct hostapd_config *conf, + struct hostapd_freq_params *params, + struct hostapd_freq_params *old_params) +{ + int channel; + + if (!params->channel) { + /* check if the new channel is supported by hw */ + channel = hostapd_hw_get_channel(hapd, params->freq); + if (!channel) + return -1; + } else { + channel = params->channel; + } + + /* if a pointer to old_params is provided we save previous state */ + if (old_params) { + old_params->channel = conf->channel; + old_params->ht_enabled = conf->ieee80211n; + old_params->sec_channel_offset = conf->secondary_channel; + } + + conf->channel = channel; + conf->ieee80211n = params->ht_enabled; + conf->secondary_channel = params->sec_channel_offset; + + /* TODO: maybe call here hostapd_config_check here? */ + + return 0; +} + + +static int hostapd_fill_csa_settings(struct hostapd_iface *iface, + struct csa_settings *settings) +{ + struct hostapd_freq_params old_freq; + int ret; + + os_memset(&old_freq, 0, sizeof(old_freq)); + if (!iface || !iface->freq || iface->csa_in_progress) + return -1; + + ret = hostapd_change_config_freq(iface->bss[0], iface->conf, + &settings->freq_params, + &old_freq); + if (ret) + return ret; + + ret = hostapd_build_beacon_data(iface, &settings->beacon_after); + + /* change back the configuration */ + hostapd_change_config_freq(iface->bss[0], iface->conf, + &old_freq, NULL); + + if (ret) + return ret; + + /* set channel switch parameters for csa ie */ + iface->cs_freq = settings->freq_params.freq; + iface->cs_count = settings->cs_count; + iface->cs_block_tx = settings->block_tx; + + ret = hostapd_build_beacon_data(iface, &settings->beacon_csa); + if (ret) { + free_beacon_data(&settings->beacon_after); + return ret; + } + + settings->counter_offset_beacon = iface->cs_c_off_beacon; + settings->counter_offset_presp = iface->cs_c_off_proberesp; + + return 0; +} + + +void hostapd_cleanup_cs_params(struct hostapd_data *hapd) +{ + hapd->iface->cs_freq = 0; + hapd->iface->cs_count = 0; + hapd->iface->cs_block_tx = 0; + hapd->iface->cs_c_off_beacon = 0; + hapd->iface->cs_c_off_proberesp = 0; + hapd->iface->csa_in_progress = 0; +} + + +int hostapd_switch_channel(struct hostapd_data *hapd, + struct csa_settings *settings) +{ + int ret; + ret = hostapd_fill_csa_settings(hapd->iface, settings); + if (ret) + return ret; + + ret = hostapd_drv_switch_channel(hapd, settings); + free_beacon_data(&settings->beacon_csa); + free_beacon_data(&settings->beacon_after); + + if (ret) { + /* if we failed, clean cs parameters */ + hostapd_cleanup_cs_params(hapd); + return ret; + } + + hapd->iface->csa_in_progress = 1; + return 0; +} + +#endif /* NEED_AP_MLME */ diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index 9a10626b4..3dac6eaef 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -27,6 +27,8 @@ union wps_event_data; struct hostapd_iface; struct hostapd_dynamic_iface; +struct csa_settings; + struct hapd_interfaces { int (*reload_config)(struct hostapd_iface *iface); struct hostapd_config * (*config_read_cb)(const char *config_fname); @@ -332,6 +334,14 @@ struct hostapd_iface { /* lowest observed noise floor in dBm */ s8 lowest_nf; + /* channel switch parameters */ + int cs_freq; + u8 cs_count; + int cs_block_tx; + unsigned int cs_c_off_beacon; + unsigned int cs_c_off_proberesp; + int csa_in_progress; + #ifdef CONFIG_ACS unsigned int acs_num_completed_scans; #endif /* CONFIG_ACS */ @@ -378,6 +388,9 @@ int hostapd_remove_iface(struct hapd_interfaces *ifaces, char *buf); void hostapd_channel_list_updated(struct hostapd_iface *iface, int initiator); void hostapd_set_state(struct hostapd_iface *iface, enum hostapd_iface_state s); const char * hostapd_state_text(enum hostapd_iface_state s); +int hostapd_switch_channel(struct hostapd_data *hapd, + struct csa_settings *settings); +void hostapd_cleanup_cs_params(struct hostapd_data *hapd); /* utils.c */ int hostapd_register_probereq_cb(struct hostapd_data *hapd, diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h index ca122d933..9b2d54f4a 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -1157,4 +1157,8 @@ enum wnm_sleep_mode_subelement_id { WNM_SLEEP_SUBELEM_IGTK = 1 }; +/* Channel Switch modes (802.11h) */ +#define CHAN_SWITCH_MODE_ALLOW_TX 0 +#define CHAN_SWITCH_MODE_BLOCK_TX 1 + #endif /* IEEE802_11_DEFS_H */ diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index 0b6e3953d..b43531018 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -176,6 +176,8 @@ extern "C" { #define DFS_EVENT_CAC_COMPLETED "DFS-CAC-COMPLETED " #define DFS_EVENT_NOP_FINISHED "DFS-NOP-FINISHED " +#define AP_CSA_FINISHED "AP-CSA-FINISHED " + /* BSS command information masks */ #define WPA_BSS_MASK_ALL 0xFFFDFFFF diff --git a/wpa_supplicant/ap.c b/wpa_supplicant/ap.c index 688746937..ef18dbd4e 100644 --- a/wpa_supplicant/ap.c +++ b/wpa_supplicant/ap.c @@ -1058,6 +1058,20 @@ int wpa_supplicant_ap_update_beacon(struct wpa_supplicant *wpa_s) } +int ap_switch_channel(struct wpa_supplicant *wpa_s, + struct csa_settings *settings) +{ +#ifdef NEED_AP_MLME + if (!wpa_s->ap_iface || !wpa_s->ap_iface->bss[0]) + return -1; + + return hostapd_switch_channel(wpa_s->ap_iface->bss[0], settings); +#else /* NEED_AP_MLME */ + return -1; +#endif /* NEED_AP_MLME */ +} + + void wpas_ap_ch_switch(struct wpa_supplicant *wpa_s, int freq, int ht, int offset) { diff --git a/wpa_supplicant/ap.h b/wpa_supplicant/ap.h index 74a0b180b..f62b8babf 100644 --- a/wpa_supplicant/ap.h +++ b/wpa_supplicant/ap.h @@ -50,6 +50,8 @@ int wpa_supplicant_ap_update_beacon(struct wpa_supplicant *wpa_s); int wpa_supplicant_ap_mac_addr_filter(struct wpa_supplicant *wpa_s, const u8 *addr); void wpa_supplicant_ap_pwd_auth_fail(struct wpa_supplicant *wpa_s); +int ap_switch_channel(struct wpa_supplicant *wpa_s, + struct csa_settings *settings); void wpas_ap_ch_switch(struct wpa_supplicant *wpa_s, int freq, int ht, int offset); struct wpabuf * wpas_ap_wps_nfc_config_token(struct wpa_supplicant *wpa_s,