From bf281c129f0e25066b94b732d61fa142f293c494 Mon Sep 17 00:00:00 2001 From: Andrei Otcheretianski Date: Thu, 14 Nov 2013 12:28:31 +0200 Subject: [PATCH] Add AP channel switch mechanism Build CSA settings and call the driver to perform the switch. Construct Beacon, Probe Response, and (Re)Association Response frames both for CSA period and for the new channel. These frames are built based on the current configuration. Add CSA IE in Beacon and Probe Response frames. Signed-hostap: Andrei Otcheretianski --- src/ap/ap_drv_ops.h | 9 ++ src/ap/beacon.c | 41 ++++++- src/ap/drv_callbacks.c | 7 ++ src/ap/hostapd.c | 227 +++++++++++++++++++++++++++++++++++ src/ap/hostapd.h | 13 ++ src/common/ieee802_11_defs.h | 4 + src/common/wpa_ctrl.h | 2 + wpa_supplicant/ap.c | 14 +++ wpa_supplicant/ap.h | 2 + 9 files changed, 317 insertions(+), 2 deletions(-) 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,