Add AP channel switch mechanism
authorAndrei Otcheretianski <andrei.otcheretianski@intel.com>
Thu, 14 Nov 2013 10:28:31 +0000 (12:28 +0200)
committerJouni Malinen <j@w1.fi>
Sun, 17 Nov 2013 15:12:58 +0000 (17:12 +0200)
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 <andrei.otcheretianski@intel.com>

src/ap/ap_drv_ops.h
src/ap/beacon.c
src/ap/drv_callbacks.c
src/ap/hostapd.c
src/ap/hostapd.h
src/common/ieee802_11_defs.h
src/common/wpa_ctrl.h
wpa_supplicant/ap.c
wpa_supplicant/ap.h

index ce2bb91..1eab939 100644 (file)
@@ -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 */
index 4b75a75..298c0fa 100644 (file)
@@ -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, &params) < 0)
index aee2946..1b69ba8 100644 (file)
@@ -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 */
 }
 
index a06ec9f..51b1035 100644 (file)
@@ -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, &params);
+       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(&params);
+       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 */
index 9a10626..3dac6ea 100644 (file)
@@ -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,
index ca122d9..9b2d54f 100644 (file)
@@ -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 */
index 0b6e395..b435310 100644 (file)
@@ -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
index 6887469..ef18dbd 100644 (file)
@@ -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)
 {
index 74a0b18..f62b8ba 100644 (file)
@@ -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,