]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
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 ce2bb9162240bdefcbcec8dbf9d7e158a6b511aa..1eab939f88e499a3526c9248a825df099172fc4a 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 4b75a757b826f0b408ce25fa22f8092283d6e0aa..298c0fa133e9ff8969d35bd4cfe3c6e0ed177a32 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 aee29467cb25395404c1ae3686843a8b81612827..1b69ba826fd0fa0d7650ed39fa6581d26d694ef2 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 a06ec9f3434cdc3d59be73a3d7e2278005a37ac6..51b1035b8cb7ab833472608c84d327bb80b2e9a2 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 9a10626b40e566c2f8fb37d87a6e6f819bd6bcc1..3dac6eaef5af4c182a0b6a30ef7c38de5e89b056 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 ca122d933a1fb8986c24a4bcd5f13ca8692025a6..9b2d54f4abb691e3439592b4d7fb925841ac7970 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 0b6e3953d468479d9d601a6cacb0dfcaa7d3d340..b43531018906360876d563e9b711305bd736a9ec 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 688746937f7de8b172d69e61f669f75a5c02e652..ef18dbd4e115b197c3327ee639879a3c3bcf730b 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 74a0b180b0c427c116157d328420da88efb6ece1..f62b8babf9bd73d6f06d21889c0bbfb36fcac36f 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,