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 e58ea6c..2aa6141 100644 (file)
@@ -2993,6 +2993,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
         * @freq: Channel parameters
index 958c952..4baeb3b 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 110677f..cc12893 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 a7f5619..07f3692 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 2917748..683e64a 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 a92c5f7..8dc48d3 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 3f45e08..3098058 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;