]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
TDLS: Propagate enable/disable channel-switch commands to driver
authorArik Nemtsov <arik@wizery.com>
Mon, 29 Dec 2014 05:20:51 +0000 (00:20 -0500)
committerJouni Malinen <j@w1.fi>
Sun, 4 Jan 2015 18:30:11 +0000 (20:30 +0200)
The supplicant code does not try to control the actual channel of the
radio at any point. It simply passes the target peer and channel
parameters to the driver. It's the driver's responsibility to
periodically initiate TDLS channel-switch operations when TDLS
channel-switching is enabled.

Allow enable/disable operations to be invoked via the control interface.

Signed-off-by: Arik Nemtsov <arikx.nemtsov@intel.com>
src/drivers/driver.h
src/rsn_supp/tdls.c
src/rsn_supp/wpa.h
src/rsn_supp/wpa_i.h
wpa_supplicant/ctrl_iface.c
wpa_supplicant/driver_i.h
wpa_supplicant/wpas_glue.c

index e58ea6cc56f6320addc96cc9a3f04b9cc753a43c..2aa6141e1de497106281d459a74a4dca3ed16efa 100644 (file)
@@ -2992,6 +2992,33 @@ struct wpa_driver_ops {
         */
        int (*del_tx_ts)(void *priv, u8 tsid, const u8 *addr);
 
+       /**
+        * Enable channel-switching with TDLS peer
+        * @priv: Private driver interface data
+        * @addr: MAC address of the TDLS peer
+        * @oper_class: Operating class of the switch channel
+        * @params: Channel specification
+        * Returns: 0 on success, -1 on failure
+        *
+        * The function indicates to driver that it can start switching to a
+        * different channel with a specified TDLS peer. The switching is
+        * assumed on until canceled with tdls_disable_channel_switch().
+        */
+       int (*tdls_enable_channel_switch)(
+               void *priv, const u8 *addr, u8 oper_class,
+               const struct hostapd_freq_params *params);
+
+       /**
+        * Disable channel switching with TDLS peer
+        * @priv: Private driver interface data
+        * @addr: MAC address of the TDLS peer
+        * Returns: 0 on success, -1 on failure
+        *
+        * This function indicates to the driver that it should stop switching
+        * with a given TDLS peer.
+        */
+       int (*tdls_disable_channel_switch)(void *priv, const u8 *addr);
+
        /**
         * start_dfs_cac - Listen for radar interference on the channel
         * @priv: Private driver interface data
index 958c952c7cfcab0267dfd3eea065af9252f889ee..4baeb3b0d4312f731df482d731dd1a5353aaed68 100644 (file)
@@ -148,6 +148,9 @@ struct wpa_tdls_peer {
        size_t supp_oper_classes_len;
 
        u8 wmm_capable;
+
+       /* channel switch currently enabled */
+       int chan_switch_enabled;
 };
 
 
@@ -687,6 +690,7 @@ static void wpa_tdls_peer_clear(struct wpa_sm *sm, struct wpa_tdls_peer *peer)
        peer->qos_info = 0;
        peer->wmm_capable = 0;
        peer->tpk_set = peer->tpk_success = 0;
+       peer->chan_switch_enabled = 0;
        os_memset(&peer->tpk, 0, sizeof(peer->tpk));
        os_memset(peer->inonce, 0, WPA_NONCE_LEN);
        os_memset(peer->rnonce, 0, WPA_NONCE_LEN);
@@ -742,6 +746,13 @@ static int wpa_tdls_send_teardown(struct wpa_sm *sm, const u8 *addr,
                return 0;
        }
 
+       /* Cancel active channel switch before teardown */
+       if (peer->chan_switch_enabled) {
+               wpa_printf(MSG_DEBUG, "TDLS: First returning link with " MACSTR
+                          " to base channel", MAC2STR(addr));
+               wpa_sm_tdls_disable_channel_switch(sm, peer->addr);
+       }
+
        dialog_token = peer->dtoken;
 
        wpa_printf(MSG_DEBUG, "TDLS: TDLS Teardown for " MACSTR,
@@ -858,9 +869,11 @@ void wpa_tdls_disable_unreachable_link(struct wpa_sm *sm, const u8 *addr)
 
        if (wpa_tdls_is_external_setup(sm)) {
                /*
-                * Disable the link, send a teardown packet through the
-                * AP, and then reset link data.
+                * Get us on the base channel, disable the link, send a
+                * teardown packet through the AP, and then reset link data.
                 */
+               if (peer->chan_switch_enabled)
+                       wpa_sm_tdls_disable_channel_switch(sm, peer->addr);
                wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, addr);
                wpa_tdls_send_teardown(sm, addr,
                                       WLAN_REASON_TDLS_TEARDOWN_UNREACHABLE);
@@ -2902,3 +2915,78 @@ int wpa_tdls_is_external_setup(struct wpa_sm *sm)
 {
        return sm->tdls_external_setup;
 }
+
+
+int wpa_tdls_enable_chan_switch(struct wpa_sm *sm, const u8 *addr,
+                               u8 oper_class,
+                               struct hostapd_freq_params *freq_params)
+{
+       struct wpa_tdls_peer *peer;
+       int ret;
+
+       if (sm->tdls_disabled || !sm->tdls_supported)
+               return -1;
+
+       if (!sm->tdls_chan_switch) {
+               wpa_printf(MSG_DEBUG,
+                          "TDLS: Channel switching not supported by the driver");
+               return -1;
+       }
+
+       if (sm->tdls_chan_switch_prohibited) {
+               wpa_printf(MSG_DEBUG,
+                          "TDLS: Channel switching is prohibited in this BSS - reject request to switch channel");
+               return -1;
+       }
+
+       for (peer = sm->tdls; peer; peer = peer->next) {
+               if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0)
+                       break;
+       }
+
+       if (peer == NULL || !peer->tpk_success) {
+               wpa_printf(MSG_ERROR, "TDLS: Peer " MACSTR
+                          " not found for channel switching", MAC2STR(addr));
+               return -1;
+       }
+
+       if (peer->chan_switch_enabled) {
+               wpa_printf(MSG_DEBUG, "TDLS: Peer " MACSTR
+                          " already has channel switching enabled",
+                          MAC2STR(addr));
+               return 0;
+       }
+
+       ret = wpa_sm_tdls_enable_channel_switch(sm, peer->addr,
+                                               oper_class, freq_params);
+       if (!ret)
+               peer->chan_switch_enabled = 1;
+
+       return ret;
+}
+
+
+int wpa_tdls_disable_chan_switch(struct wpa_sm *sm, const u8 *addr)
+{
+       struct wpa_tdls_peer *peer;
+
+       if (sm->tdls_disabled || !sm->tdls_supported)
+               return -1;
+
+       for (peer = sm->tdls; peer; peer = peer->next) {
+               if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0)
+                       break;
+       }
+
+       if (!peer || !peer->chan_switch_enabled) {
+               wpa_printf(MSG_ERROR, "TDLS: Channel switching not enabled for "
+                          MACSTR, MAC2STR(addr));
+               return -1;
+       }
+
+       /* ignore the return value */
+       wpa_sm_tdls_disable_channel_switch(sm, peer->addr);
+
+       peer->chan_switch_enabled = 0;
+       return 0;
+}
index 110677f8314e3dc6453b8875812f25655a931f80..cc128935c1c7bf888246fba5da46ad4c69a32cde 100644 (file)
@@ -17,6 +17,7 @@
 struct wpa_sm;
 struct eapol_sm;
 struct wpa_config_blob;
+struct hostapd_freq_params;
 
 struct wpa_sm_ctx {
        void *ctx; /* pointer to arbitrary upper level context */
@@ -67,6 +68,10 @@ struct wpa_sm_ctx {
                                size_t supp_channels_len,
                                const u8 *supp_oper_classes,
                                size_t supp_oper_classes_len);
+       int (*tdls_enable_channel_switch)(
+               void *ctx, const u8 *addr, u8 oper_class,
+               const struct hostapd_freq_params *params);
+       int (*tdls_disable_channel_switch)(void *ctx, const u8 *addr);
 #endif /* CONFIG_TDLS */
        void (*set_rekey_offload)(void *ctx, const u8 *kek, const u8 *kck,
                                  const u8 *replay_ctr);
@@ -404,6 +409,10 @@ void wpa_tdls_enable(struct wpa_sm *sm, int enabled);
 void wpa_tdls_disable_unreachable_link(struct wpa_sm *sm, const u8 *addr);
 const char * wpa_tdls_get_link_status(struct wpa_sm *sm, const u8 *addr);
 int wpa_tdls_is_external_setup(struct wpa_sm *sm);
+int wpa_tdls_enable_chan_switch(struct wpa_sm *sm, const u8 *addr,
+                               u8 oper_class,
+                               struct hostapd_freq_params *freq_params);
+int wpa_tdls_disable_chan_switch(struct wpa_sm *sm, const u8 *addr);
 
 int wpa_wnmsleep_install_key(struct wpa_sm *sm, u8 subelem_id, u8 *buf);
 
index a7f56196f7c15da2a0c70c63553b6b31a341d9be..07f3692ca2422b11d74df7c80c41d5cbc8291eec 100644 (file)
@@ -315,6 +315,26 @@ wpa_sm_tdls_peer_addset(struct wpa_sm *sm, const u8 *addr, int add,
                                                 supp_oper_classes_len);
        return -1;
 }
+
+static inline int
+wpa_sm_tdls_enable_channel_switch(struct wpa_sm *sm, const u8 *addr,
+                                 u8 oper_class,
+                                 const struct hostapd_freq_params *freq_params)
+{
+       if (sm->ctx->tdls_enable_channel_switch)
+               return sm->ctx->tdls_enable_channel_switch(sm->ctx->ctx, addr,
+                                                          oper_class,
+                                                          freq_params);
+       return -1;
+}
+
+static inline int
+wpa_sm_tdls_disable_channel_switch(struct wpa_sm *sm, const u8 *addr)
+{
+       if (sm->ctx->tdls_disable_channel_switch)
+               return sm->ctx->tdls_disable_channel_switch(sm->ctx->ctx, addr);
+       return -1;
+}
 #endif /* CONFIG_TDLS */
 
 static inline int wpa_sm_key_mgmt_set_pmk(struct wpa_sm *sm,
index 29177484fd0c36b0fdba1824ec0936781bfc019b..683e64afa36c54e3f7fc85181af834db8b62abe2 100644 (file)
@@ -653,6 +653,104 @@ static int ctrl_iface_get_capability_tdls(
        return ret;
 }
 
+
+static int wpa_supplicant_ctrl_iface_tdls_chan_switch(
+       struct wpa_supplicant *wpa_s, char *cmd)
+{
+       u8 peer[ETH_ALEN];
+       struct hostapd_freq_params freq_params;
+       u8 oper_class;
+       char *pos, *end;
+
+       if (!wpa_tdls_is_external_setup(wpa_s->wpa)) {
+               wpa_printf(MSG_INFO,
+                          "tdls_chanswitch: Only supported with external setup");
+               return -1;
+       }
+
+       os_memset(&freq_params, 0, sizeof(freq_params));
+
+       pos = os_strchr(cmd, ' ');
+       if (pos == NULL)
+               return -1;
+       *pos++ = '\0';
+
+       oper_class = strtol(pos, &end, 10);
+       if (pos == end) {
+               wpa_printf(MSG_INFO,
+                          "tdls_chanswitch: Invalid op class provided");
+               return -1;
+       }
+
+       pos = end;
+       freq_params.freq = atoi(pos);
+       if (freq_params.freq == 0) {
+               wpa_printf(MSG_INFO, "tdls_chanswitch: Invalid freq provided");
+               return -1;
+       }
+
+#define SET_FREQ_SETTING(str) \
+       do { \
+               const char *pos2 = os_strstr(pos, " " #str "="); \
+               if (pos2) { \
+                       pos2 += sizeof(" " #str "=") - 1; \
+                       freq_params.str = atoi(pos2); \
+               } \
+       } while (0)
+
+       SET_FREQ_SETTING(center_freq1);
+       SET_FREQ_SETTING(center_freq2);
+       SET_FREQ_SETTING(bandwidth);
+       SET_FREQ_SETTING(sec_channel_offset);
+#undef SET_FREQ_SETTING
+
+       freq_params.ht_enabled = !!os_strstr(pos, " ht");
+       freq_params.vht_enabled = !!os_strstr(pos, " vht");
+
+       if (hwaddr_aton(cmd, peer)) {
+               wpa_printf(MSG_DEBUG,
+                          "CTRL_IFACE TDLS_CHAN_SWITCH: Invalid address '%s'",
+                          cmd);
+               return -1;
+       }
+
+       wpa_printf(MSG_DEBUG, "CTRL_IFACE TDLS_CHAN_SWITCH " MACSTR
+                  " OP CLASS %d FREQ %d CENTER1 %d CENTER2 %d BW %d SEC_OFFSET %d%s%s",
+                  MAC2STR(peer), oper_class, freq_params.freq,
+                  freq_params.center_freq1, freq_params.center_freq2,
+                  freq_params.bandwidth, freq_params.sec_channel_offset,
+                  freq_params.ht_enabled ? " HT" : "",
+                  freq_params.vht_enabled ? " VHT" : "");
+
+       return wpa_tdls_enable_chan_switch(wpa_s->wpa, peer, oper_class,
+                                          &freq_params);
+}
+
+
+static int wpa_supplicant_ctrl_iface_tdls_cancel_chan_switch(
+       struct wpa_supplicant *wpa_s, char *cmd)
+{
+       u8 peer[ETH_ALEN];
+
+       if (!wpa_tdls_is_external_setup(wpa_s->wpa)) {
+               wpa_printf(MSG_INFO,
+                          "tdls_chanswitch: Only supported with external setup");
+               return -1;
+       }
+
+       if (hwaddr_aton(cmd, peer)) {
+               wpa_printf(MSG_DEBUG,
+                          "CTRL_IFACE TDLS_CANCEL_CHAN_SWITCH: Invalid address '%s'",
+                          cmd);
+               return -1;
+       }
+
+       wpa_printf(MSG_DEBUG, "CTRL_IFACE TDLS_CANCEL_CHAN_SWITCH " MACSTR,
+                  MAC2STR(peer));
+
+       return wpa_tdls_disable_chan_switch(wpa_s->wpa, peer);
+}
+
 #endif /* CONFIG_TDLS */
 
 
@@ -7550,6 +7648,14 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
        } else if (os_strncmp(buf, "TDLS_TEARDOWN ", 14) == 0) {
                if (wpa_supplicant_ctrl_iface_tdls_teardown(wpa_s, buf + 14))
                        reply_len = -1;
+       } else if (os_strncmp(buf, "TDLS_CHAN_SWITCH ", 17) == 0) {
+               if (wpa_supplicant_ctrl_iface_tdls_chan_switch(wpa_s,
+                                                              buf + 17))
+                       reply_len = -1;
+       } else if (os_strncmp(buf, "TDLS_CANCEL_CHAN_SWITCH ", 24) == 0) {
+               if (wpa_supplicant_ctrl_iface_tdls_cancel_chan_switch(wpa_s,
+                                                                     buf + 24))
+                       reply_len = -1;
 #endif /* CONFIG_TDLS */
        } else if (os_strcmp(buf, "WMM_AC_STATUS") == 0) {
                reply_len = wpas_wmm_ac_status(wpa_s, reply, reply_size);
index a92c5f76cb7df1a84ca19f65023ff433777005e6..8dc48d389ec9472ddc956fb066f33b32bf410add 100644 (file)
@@ -607,6 +607,27 @@ static inline int wpa_drv_del_ts(struct wpa_supplicant *wpa_s, u8 tid,
        return wpa_s->driver->del_tx_ts(wpa_s->drv_priv, tid, address);
 }
 
+static inline int wpa_drv_tdls_enable_channel_switch(
+       struct wpa_supplicant *wpa_s, const u8 *addr, u8 oper_class,
+       const struct hostapd_freq_params *freq_params)
+{
+       if (!wpa_s->driver->tdls_enable_channel_switch)
+               return -1;
+       return wpa_s->driver->tdls_enable_channel_switch(wpa_s->drv_priv, addr,
+                                                        oper_class,
+                                                        freq_params);
+}
+
+static inline int
+wpa_drv_tdls_disable_channel_switch(struct wpa_supplicant *wpa_s,
+                                   const u8 *addr)
+{
+       if (!wpa_s->driver->tdls_disable_channel_switch)
+               return -1;
+       return wpa_s->driver->tdls_disable_channel_switch(wpa_s->drv_priv,
+                                                         addr);
+}
+
 static inline int wpa_drv_wnm_oper(struct wpa_supplicant *wpa_s,
                                   enum wnm_oper oper, const u8 *peer,
                                   u8 *buf, u16 *buf_len)
index 3f45e08b6b93c422455275cb2f40d3df02cf97cd..3098058b1fb210f6c3c34b2c8075a1deffceb5e7 100644 (file)
@@ -697,6 +697,25 @@ static int wpa_supplicant_tdls_peer_addset(
        return wpa_drv_sta_add(wpa_s, &params);
 }
 
+
+static int wpa_supplicant_tdls_enable_channel_switch(
+       void *ctx, const u8 *addr, u8 oper_class,
+       const struct hostapd_freq_params *params)
+{
+       struct wpa_supplicant *wpa_s = ctx;
+
+       return wpa_drv_tdls_enable_channel_switch(wpa_s, addr, oper_class,
+                                                 params);
+}
+
+
+static int wpa_supplicant_tdls_disable_channel_switch(void *ctx, const u8 *addr)
+{
+       struct wpa_supplicant *wpa_s = ctx;
+
+       return wpa_drv_tdls_disable_channel_switch(wpa_s, addr);
+}
+
 #endif /* CONFIG_TDLS */
 
 #endif /* CONFIG_NO_WPA */
@@ -1009,6 +1028,10 @@ int wpa_supplicant_init_wpa(struct wpa_supplicant *wpa_s)
        ctx->send_tdls_mgmt = wpa_supplicant_send_tdls_mgmt;
        ctx->tdls_oper = wpa_supplicant_tdls_oper;
        ctx->tdls_peer_addset = wpa_supplicant_tdls_peer_addset;
+       ctx->tdls_enable_channel_switch =
+               wpa_supplicant_tdls_enable_channel_switch;
+       ctx->tdls_disable_channel_switch =
+               wpa_supplicant_tdls_disable_channel_switch;
 #endif /* CONFIG_TDLS */
        ctx->set_rekey_offload = wpa_supplicant_set_rekey_offload;
        ctx->key_mgmt_set_pmk = wpa_supplicant_key_mgmt_set_pmk;