diff --git a/src/drivers/driver.h b/src/drivers/driver.h index e58ea6cc5..2aa6141e1 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -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 diff --git a/src/rsn_supp/tdls.c b/src/rsn_supp/tdls.c index 958c952c7..4baeb3b0d 100644 --- a/src/rsn_supp/tdls.c +++ b/src/rsn_supp/tdls.c @@ -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; +} diff --git a/src/rsn_supp/wpa.h b/src/rsn_supp/wpa.h index 110677f83..cc128935c 100644 --- a/src/rsn_supp/wpa.h +++ b/src/rsn_supp/wpa.h @@ -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); diff --git a/src/rsn_supp/wpa_i.h b/src/rsn_supp/wpa_i.h index a7f56196f..07f3692ca 100644 --- a/src/rsn_supp/wpa_i.h +++ b/src/rsn_supp/wpa_i.h @@ -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, diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 29177484f..683e64afa 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -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); diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index a92c5f76c..8dc48d389 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -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) diff --git a/wpa_supplicant/wpas_glue.c b/wpa_supplicant/wpas_glue.c index 3f45e08b6..3098058b1 100644 --- a/wpa_supplicant/wpas_glue.c +++ b/wpa_supplicant/wpas_glue.c @@ -697,6 +697,25 @@ static int wpa_supplicant_tdls_peer_addset( return wpa_drv_sta_add(wpa_s, ¶ms); } + +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;