diff options
Diffstat (limited to 'src/rsn_supp/tdls.c')
-rw-r--r-- | src/rsn_supp/tdls.c | 1155 |
1 files changed, 915 insertions, 240 deletions
diff --git a/src/rsn_supp/tdls.c b/src/rsn_supp/tdls.c index 7646ca88671a3..c1d7749191d75 100644 --- a/src/rsn_supp/tdls.c +++ b/src/rsn_supp/tdls.c @@ -33,12 +33,15 @@ #define TDLS_TESTING_NO_TPK_EXPIRATION BIT(8) #define TDLS_TESTING_DECLINE_RESP BIT(9) #define TDLS_TESTING_IGNORE_AP_PROHIBIT BIT(10) +#define TDLS_TESTING_WRONG_MIC BIT(11) unsigned int tdls_testing = 0; #endif /* CONFIG_TDLS_TESTING */ #define TPK_LIFETIME 43200 /* 12 hours */ -#define TPK_RETRY_COUNT 3 -#define TPK_TIMEOUT 5000 /* in milliseconds */ +#define TPK_M1_RETRY_COUNT 3 +#define TPK_M1_TIMEOUT 5000 /* in milliseconds */ +#define TPK_M2_RETRY_COUNT 10 +#define TPK_M2_TIMEOUT 500 /* in milliseconds */ #define TDLS_MIC_LEN 16 @@ -79,6 +82,10 @@ struct wpa_tdls_frame { static u8 * wpa_add_tdls_timeoutie(u8 *pos, u8 *ie, size_t ie_len, u32 tsecs); static void wpa_tdls_tpk_retry_timeout(void *eloop_ctx, void *timeout_ctx); static void wpa_tdls_peer_free(struct wpa_sm *sm, struct wpa_tdls_peer *peer); +static void wpa_tdls_disable_peer_link(struct wpa_sm *sm, + struct wpa_tdls_peer *peer); +static int wpa_tdls_send_teardown(struct wpa_sm *sm, const u8 *addr, + u16 reason_code); #define TDLS_MAX_IE_LEN 80 @@ -86,6 +93,7 @@ static void wpa_tdls_peer_free(struct wpa_sm *sm, struct wpa_tdls_peer *peer); struct wpa_tdls_peer { struct wpa_tdls_peer *next; + unsigned int reconfig_key:1; int initiator; /* whether this end was initiator for TDLS setup */ u8 addr[ETH_ALEN]; /* other end MAC address */ u8 inonce[WPA_NONCE_LEN]; /* Initiator Nonce */ @@ -104,6 +112,7 @@ struct wpa_tdls_peer { } tpk; int tpk_set; int tpk_success; + int tpk_in_progress; struct tpk_timer { u8 dest[ETH_ALEN]; @@ -112,6 +121,7 @@ struct wpa_tdls_peer { u8 action_code; /* TDLS frame type */ u8 dialog_token; u16 status_code; + u32 peer_capab; int buf_len; /* length of TPK message for retransmission */ u8 *buf; /* buffer for TPK message */ } sm_tmr; @@ -120,6 +130,27 @@ struct wpa_tdls_peer { u8 supp_rates[IEEE80211_MAX_SUPP_RATES]; size_t supp_rates_len; + + struct ieee80211_ht_capabilities *ht_capabilities; + struct ieee80211_vht_capabilities *vht_capabilities; + + u8 qos_info; + + u16 aid; + + u8 *ext_capab; + size_t ext_capab_len; + + u8 *supp_channels; + size_t supp_channels_len; + + u8 *supp_oper_classes; + size_t supp_oper_classes_len; + + u8 wmm_capable; + + /* channel switch currently enabled */ + int chan_switch_enabled; }; @@ -189,26 +220,30 @@ static int wpa_tdls_set_key(struct wpa_sm *sm, struct wpa_tdls_peer *peer) static int wpa_tdls_send_tpk_msg(struct wpa_sm *sm, const u8 *dst, u8 action_code, u8 dialog_token, - u16 status_code, const u8 *buf, size_t len) + u16 status_code, u32 peer_capab, + int initiator, const u8 *buf, size_t len) { return wpa_sm_send_tdls_mgmt(sm, dst, action_code, dialog_token, - status_code, buf, len); + status_code, peer_capab, initiator, buf, + len); } static int wpa_tdls_tpk_send(struct wpa_sm *sm, const u8 *dest, u8 action_code, - u8 dialog_token, u16 status_code, - const u8 *msg, size_t msg_len) + u8 dialog_token, u16 status_code, u32 peer_capab, + int initiator, const u8 *msg, size_t msg_len) { struct wpa_tdls_peer *peer; wpa_printf(MSG_DEBUG, "TDLS: TPK send dest=" MACSTR " action_code=%u " - "dialog_token=%u status_code=%u msg_len=%u", + "dialog_token=%u status_code=%u peer_capab=%u initiator=%d " + "msg_len=%u", MAC2STR(dest), action_code, dialog_token, status_code, - (unsigned int) msg_len); + peer_capab, initiator, (unsigned int) msg_len); if (wpa_tdls_send_tpk_msg(sm, dest, action_code, dialog_token, - status_code, msg, msg_len)) { + status_code, peer_capab, initiator, msg, + msg_len)) { wpa_printf(MSG_INFO, "TDLS: Failed to send message " "(action_code=%u)", action_code); return -1; @@ -233,14 +268,20 @@ static int wpa_tdls_tpk_send(struct wpa_sm *sm, const u8 *dest, u8 action_code, eloop_cancel_timeout(wpa_tdls_tpk_retry_timeout, sm, peer); - peer->sm_tmr.count = TPK_RETRY_COUNT; - peer->sm_tmr.timer = TPK_TIMEOUT; + if (action_code == WLAN_TDLS_SETUP_RESPONSE) { + peer->sm_tmr.count = TPK_M2_RETRY_COUNT; + peer->sm_tmr.timer = TPK_M2_TIMEOUT; + } else { + peer->sm_tmr.count = TPK_M1_RETRY_COUNT; + peer->sm_tmr.timer = TPK_M1_TIMEOUT; + } /* Copy message to resend on timeout */ os_memcpy(peer->sm_tmr.dest, dest, ETH_ALEN); peer->sm_tmr.action_code = action_code; peer->sm_tmr.dialog_token = dialog_token; peer->sm_tmr.status_code = status_code; + peer->sm_tmr.peer_capab = peer_capab; peer->sm_tmr.buf_len = msg_len; os_free(peer->sm_tmr.buf); peer->sm_tmr.buf = os_malloc(msg_len); @@ -250,28 +291,21 @@ static int wpa_tdls_tpk_send(struct wpa_sm *sm, const u8 *dest, u8 action_code, wpa_printf(MSG_DEBUG, "TDLS: Retry timeout registered " "(action_code=%u)", action_code); - eloop_register_timeout(peer->sm_tmr.timer / 1000, 0, + eloop_register_timeout(peer->sm_tmr.timer / 1000, + (peer->sm_tmr.timer % 1000) * 1000, wpa_tdls_tpk_retry_timeout, sm, peer); return 0; } static int wpa_tdls_do_teardown(struct wpa_sm *sm, struct wpa_tdls_peer *peer, - u16 reason_code, int free_peer) + u16 reason_code) { int ret; - if (sm->tdls_external_setup) { - ret = wpa_tdls_send_teardown(sm, peer->addr, reason_code); - - /* disable the link after teardown was sent */ - wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, peer->addr); - } else { - ret = wpa_sm_tdls_oper(sm, TDLS_TEARDOWN, peer->addr); - } - - if (sm->tdls_external_setup || free_peer) - wpa_tdls_peer_free(sm, peer); + ret = wpa_tdls_send_teardown(sm, peer->addr, reason_code); + /* disable the link after teardown was sent */ + wpa_tdls_disable_peer_link(sm, peer); return ret; } @@ -285,7 +319,6 @@ static void wpa_tdls_tpk_retry_timeout(void *eloop_ctx, void *timeout_ctx) if (peer->sm_tmr.count) { peer->sm_tmr.count--; - peer->sm_tmr.timer = TPK_TIMEOUT; wpa_printf(MSG_INFO, "TDLS: Retrying sending of message " "(action_code=%u)", @@ -305,6 +338,8 @@ static void wpa_tdls_tpk_retry_timeout(void *eloop_ctx, void *timeout_ctx) peer->sm_tmr.action_code, peer->sm_tmr.dialog_token, peer->sm_tmr.status_code, + peer->sm_tmr.peer_capab, + peer->initiator, peer->sm_tmr.buf, peer->sm_tmr.buf_len)) { wpa_printf(MSG_INFO, "TDLS: Failed to retry " @@ -312,14 +347,15 @@ static void wpa_tdls_tpk_retry_timeout(void *eloop_ctx, void *timeout_ctx) } eloop_cancel_timeout(wpa_tdls_tpk_retry_timeout, sm, peer); - eloop_register_timeout(peer->sm_tmr.timer / 1000, 0, + eloop_register_timeout(peer->sm_tmr.timer / 1000, + (peer->sm_tmr.timer % 1000) * 1000, wpa_tdls_tpk_retry_timeout, sm, peer); } else { eloop_cancel_timeout(wpa_tdls_tpk_retry_timeout, sm, peer); wpa_printf(MSG_DEBUG, "TDLS: Sending Teardown Request"); wpa_tdls_do_teardown(sm, peer, - WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED, 1); + WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED); } } @@ -535,7 +571,7 @@ static int wpa_supplicant_verify_tdls_mic(u8 trans_seq, wpa_tdls_ftie_mic(peer->tpk.kck, trans_seq, lnkid, peer->rsnie_p, timeoutie, (u8 *) ftie, mic); - if (os_memcmp(mic, ftie->mic, 16) != 0) { + if (os_memcmp_const(mic, ftie->mic, 16) != 0) { wpa_printf(MSG_INFO, "TDLS: Invalid MIC in FTIE - " "dropping packet"); wpa_hexdump(MSG_DEBUG, "TDLS: Received MIC", @@ -562,7 +598,7 @@ static int wpa_supplicant_verify_tdls_mic_teardown( if (peer->tpk_set) { wpa_tdls_key_mic_teardown(peer->tpk.kck, trans_seq, rcode, dtoken, lnkid, (u8 *) ftie, mic); - if (os_memcmp(mic, ftie->mic, 16) != 0) { + if (os_memcmp_const(mic, ftie->mic, 16) != 0) { wpa_printf(MSG_INFO, "TDLS: Invalid MIC in Teardown - " "dropping packet"); return -1; @@ -597,29 +633,78 @@ static void wpa_tdls_tpk_timeout(void *eloop_ctx, void *timeout_ctx) wpa_printf(MSG_DEBUG, "TDLS: TPK lifetime expired for " MACSTR " - tear down", MAC2STR(peer->addr)); wpa_tdls_do_teardown(sm, peer, - WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED, 1); + WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED); } } -static void wpa_tdls_peer_free(struct wpa_sm *sm, struct wpa_tdls_peer *peer) +static void wpa_tdls_peer_remove_from_list(struct wpa_sm *sm, + struct wpa_tdls_peer *peer) +{ + struct wpa_tdls_peer *cur, *prev; + + cur = sm->tdls; + prev = NULL; + while (cur && cur != peer) { + prev = cur; + cur = cur->next; + } + + if (cur != peer) { + wpa_printf(MSG_ERROR, "TDLS: Could not find peer " MACSTR + " to remove it from the list", + MAC2STR(peer->addr)); + return; + } + + if (prev) + prev->next = peer->next; + else + sm->tdls = peer->next; +} + + +static void wpa_tdls_peer_clear(struct wpa_sm *sm, struct wpa_tdls_peer *peer) { wpa_printf(MSG_DEBUG, "TDLS: Clear state for peer " MACSTR, MAC2STR(peer->addr)); eloop_cancel_timeout(wpa_tdls_tpk_timeout, sm, peer); eloop_cancel_timeout(wpa_tdls_tpk_retry_timeout, sm, peer); + peer->reconfig_key = 0; peer->initiator = 0; + peer->tpk_in_progress = 0; os_free(peer->sm_tmr.buf); peer->sm_tmr.buf = NULL; + os_free(peer->ht_capabilities); + peer->ht_capabilities = NULL; + os_free(peer->vht_capabilities); + peer->vht_capabilities = NULL; + os_free(peer->ext_capab); + peer->ext_capab = NULL; + os_free(peer->supp_channels); + peer->supp_channels = NULL; + os_free(peer->supp_oper_classes); + peer->supp_oper_classes = NULL; peer->rsnie_i_len = peer->rsnie_p_len = 0; peer->cipher = 0; + 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); } +static void wpa_tdls_peer_free(struct wpa_sm *sm, struct wpa_tdls_peer *peer) +{ + wpa_tdls_peer_clear(sm, peer); + wpa_tdls_peer_remove_from_list(sm, peer); + os_free(peer); +} + + static void wpa_tdls_linkid(struct wpa_sm *sm, struct wpa_tdls_peer *peer, struct wpa_tdls_lnkid *lnkid) { @@ -636,7 +721,8 @@ static void wpa_tdls_linkid(struct wpa_sm *sm, struct wpa_tdls_peer *peer, } -int wpa_tdls_send_teardown(struct wpa_sm *sm, const u8 *addr, u16 reason_code) +static int wpa_tdls_send_teardown(struct wpa_sm *sm, const u8 *addr, + u16 reason_code) { struct wpa_tdls_peer *peer; struct wpa_tdls_ftie *ftie; @@ -660,6 +746,13 @@ int wpa_tdls_send_teardown(struct wpa_sm *sm, const u8 *addr, u16 reason_code) 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, @@ -715,13 +808,9 @@ skip_ies: /* request driver to send Teardown using this FTIE */ wpa_tdls_tpk_send(sm, addr, WLAN_TDLS_TEARDOWN, 0, - WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED, rbuf, - pos - rbuf); + reason_code, 0, peer->initiator, rbuf, pos - rbuf); os_free(rbuf); - /* clear the Peerkey statemachine */ - wpa_tdls_peer_free(sm, peer); - return 0; } @@ -750,11 +839,19 @@ int wpa_tdls_teardown_link(struct wpa_sm *sm, const u8 *addr, u16 reason_code) return -1; } - return wpa_tdls_do_teardown(sm, peer, reason_code, 0); + return wpa_tdls_do_teardown(sm, peer, reason_code); +} + + +static void wpa_tdls_disable_peer_link(struct wpa_sm *sm, + struct wpa_tdls_peer *peer) +{ + wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, peer->addr); + wpa_tdls_peer_free(sm, peer); } -void wpa_tdls_disable_link(struct wpa_sm *sm, const u8 *addr) +void wpa_tdls_disable_unreachable_link(struct wpa_sm *sm, const u8 *addr) { struct wpa_tdls_peer *peer; @@ -763,13 +860,52 @@ void wpa_tdls_disable_link(struct wpa_sm *sm, const u8 *addr) break; } - if (peer) { + if (!peer || !peer->tpk_success) { + wpa_printf(MSG_DEBUG, "TDLS: Peer " MACSTR + " not connected - cannot teardown unreachable link", + MAC2STR(addr)); + return; + } + + if (wpa_tdls_is_external_setup(sm)) { + /* + * 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); wpa_tdls_peer_free(sm, peer); + } else { + wpa_tdls_disable_peer_link(sm, peer); } } +const char * wpa_tdls_get_link_status(struct wpa_sm *sm, const u8 *addr) +{ + struct wpa_tdls_peer *peer; + + if (sm->tdls_disabled || !sm->tdls_supported) + return "disabled"; + + for (peer = sm->tdls; peer; peer = peer->next) { + if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0) + break; + } + + if (peer == NULL) + return "peer does not exist"; + + if (!peer->tpk_success) + return "peer not connected"; + + return "connected"; +} + + static int wpa_tdls_recv_teardown(struct wpa_sm *sm, const u8 *src_addr, const u8 *buf, size_t len) { @@ -803,10 +939,15 @@ static int wpa_tdls_recv_teardown(struct wpa_sm *sm, const u8 *src_addr, " (reason code %u)", MAC2STR(src_addr), reason_code); ielen = len - (pos - buf); /* start of IE in buf */ - if (wpa_supplicant_parse_ies((const u8 *) pos, ielen, &kde) < 0) { - wpa_printf(MSG_INFO, "TDLS: Failed to parse IEs in Teardown"); - return -1; - } + + /* + * Don't reject the message if failing to parse IEs. The IEs we need are + * explicitly checked below. Some APs may add arbitrary padding to the + * end of short TDLS frames and that would look like invalid IEs. + */ + if (wpa_supplicant_parse_ies((const u8 *) pos, ielen, &kde) < 0) + wpa_printf(MSG_DEBUG, + "TDLS: Failed to parse IEs in Teardown - ignore as an interop workaround"); if (kde.lnkid == NULL || kde.lnkid_len < 3 * ETH_ALEN) { wpa_printf(MSG_INFO, "TDLS: No Link Identifier IE in TDLS " @@ -839,11 +980,7 @@ skip_ftie: * Request the driver to disable the direct link and clear associated * keys. */ - wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, src_addr); - - /* clear the Peerkey statemachine */ - wpa_tdls_peer_free(sm, peer); - + wpa_tdls_disable_peer_link(sm, peer); return 0; } @@ -853,25 +990,37 @@ skip_ftie: * appropriate status code mentioning reason for error/failure. * @dst - MAC addr of Peer station * @tdls_action - TDLS frame type for which error code is sent + * @initiator - was this end the initiator of the connection * @status - status code mentioning reason */ static int wpa_tdls_send_error(struct wpa_sm *sm, const u8 *dst, - u8 tdls_action, u8 dialog_token, u16 status) + u8 tdls_action, u8 dialog_token, int initiator, + u16 status) { wpa_printf(MSG_DEBUG, "TDLS: Sending error to " MACSTR " (action=%u status=%u)", MAC2STR(dst), tdls_action, status); return wpa_tdls_tpk_send(sm, dst, tdls_action, dialog_token, status, - NULL, 0); + 0, initiator, NULL, 0); } static struct wpa_tdls_peer * -wpa_tdls_add_peer(struct wpa_sm *sm, const u8 *addr) +wpa_tdls_add_peer(struct wpa_sm *sm, const u8 *addr, int *existing) { struct wpa_tdls_peer *peer; + if (existing) + *existing = 0; + for (peer = sm->tdls; peer; peer = peer->next) { + if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0) { + if (existing) + *existing = 1; + return peer; /* re-use existing entry */ + } + } + wpa_printf(MSG_INFO, "TDLS: Creating peer entry for " MACSTR, MAC2STR(addr)); @@ -897,6 +1046,7 @@ static int wpa_tdls_send_tpk_m1(struct wpa_sm *sm, u8 *rbuf, *pos, *count_pos; u16 count; struct rsn_ie_hdr *hdr; + int status; if (!wpa_tdls_get_privacy(sm)) { wpa_printf(MSG_DEBUG, "TDLS: No security used on the link"); @@ -1057,11 +1207,11 @@ skip_ies: "Handshake Message 1 (peer " MACSTR ")", MAC2STR(peer->addr)); - wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_SETUP_REQUEST, 1, 0, - rbuf, pos - rbuf); + status = wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_SETUP_REQUEST, + 1, 0, 0, peer->initiator, rbuf, pos - rbuf); os_free(rbuf); - return 0; + return status; } @@ -1075,6 +1225,7 @@ static int wpa_tdls_send_tpk_m2(struct wpa_sm *sm, u32 lifetime; struct wpa_tdls_timeoutie timeoutie; struct wpa_tdls_ftie *ftie; + int status; buf_len = 0; if (wpa_tdls_get_privacy(sm)) { @@ -1138,13 +1289,20 @@ static int wpa_tdls_send_tpk_m2(struct wpa_sm *sm, /* compute MIC before sending */ wpa_tdls_ftie_mic(peer->tpk.kck, 2, (u8 *) lnkid, peer->rsnie_p, (u8 *) &timeoutie, (u8 *) ftie, ftie->mic); +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_WRONG_MIC) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - use wrong MIC"); + ftie->mic[0] ^= 0x01; + } +#endif /* CONFIG_TDLS_TESTING */ skip_ies: - wpa_tdls_tpk_send(sm, src_addr, WLAN_TDLS_SETUP_RESPONSE, dtoken, 0, - rbuf, pos - rbuf); + status = wpa_tdls_tpk_send(sm, src_addr, WLAN_TDLS_SETUP_RESPONSE, + dtoken, 0, 0, peer->initiator, rbuf, + pos - rbuf); os_free(rbuf); - return 0; + return status; } @@ -1158,6 +1316,8 @@ static int wpa_tdls_send_tpk_m3(struct wpa_sm *sm, struct wpa_tdls_ftie *ftie; struct wpa_tdls_timeoutie timeoutie; u32 lifetime; + int status; + u32 peer_capab = 0; buf_len = 0; if (wpa_tdls_get_privacy(sm)) { @@ -1219,13 +1379,28 @@ static int wpa_tdls_send_tpk_m3(struct wpa_sm *sm, /* compute MIC before sending */ wpa_tdls_ftie_mic(peer->tpk.kck, 3, (u8 *) lnkid, peer->rsnie_p, (u8 *) &timeoutie, (u8 *) ftie, ftie->mic); +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_WRONG_MIC) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - use wrong MIC"); + ftie->mic[0] ^= 0x01; + } +#endif /* CONFIG_TDLS_TESTING */ skip_ies: - wpa_tdls_tpk_send(sm, src_addr, WLAN_TDLS_SETUP_CONFIRM, dtoken, 0, - rbuf, pos - rbuf); + + if (peer->vht_capabilities) + peer_capab |= TDLS_PEER_VHT; + if (peer->ht_capabilities) + peer_capab |= TDLS_PEER_HT; + if (peer->wmm_capable) + peer_capab |= TDLS_PEER_WMM; + + status = wpa_tdls_tpk_send(sm, src_addr, WLAN_TDLS_SETUP_CONFIRM, + dtoken, 0, peer_capab, peer->initiator, + rbuf, pos - rbuf); os_free(rbuf); - return 0; + return status; } @@ -1233,11 +1408,85 @@ static int wpa_tdls_send_discovery_response(struct wpa_sm *sm, struct wpa_tdls_peer *peer, u8 dialog_token) { + size_t buf_len = 0; + struct wpa_tdls_timeoutie timeoutie; + u16 rsn_capab; + u8 *rbuf, *pos, *count_pos; + u16 count; + struct rsn_ie_hdr *hdr; + int status; + wpa_printf(MSG_DEBUG, "TDLS: Sending TDLS Discovery Response " "(peer " MACSTR ")", MAC2STR(peer->addr)); + if (!wpa_tdls_get_privacy(sm)) + goto skip_rsn_ies; + + /* Filling RSN IE */ + hdr = (struct rsn_ie_hdr *) peer->rsnie_i; + hdr->elem_id = WLAN_EID_RSN; + WPA_PUT_LE16(hdr->version, RSN_VERSION); + pos = (u8 *) (hdr + 1); + RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED); + pos += RSN_SELECTOR_LEN; + count_pos = pos; + pos += 2; + count = 0; - return wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_DISCOVERY_RESPONSE, - dialog_token, 0, NULL, 0); + /* + * AES-CCMP is the default encryption preferred for TDLS, so + * RSN IE is filled only with CCMP cipher suite. + * Note: TKIP is not used to encrypt TDLS link. + * + * Regardless of the cipher used on the AP connection, select CCMP + * here. + */ + RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_CCMP); + pos += RSN_SELECTOR_LEN; + count++; + WPA_PUT_LE16(count_pos, count); + WPA_PUT_LE16(pos, 1); + pos += 2; + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_TPK_HANDSHAKE); + pos += RSN_SELECTOR_LEN; + + rsn_capab = WPA_CAPABILITY_PEERKEY_ENABLED; + rsn_capab |= RSN_NUM_REPLAY_COUNTERS_16 << 2; + WPA_PUT_LE16(pos, rsn_capab); + pos += 2; + hdr->len = (pos - (u8 *) hdr) - 2; + peer->rsnie_i_len = pos - peer->rsnie_i; + + wpa_hexdump(MSG_DEBUG, "TDLS: RSN IE for Discovery Response", + (u8 *) hdr, hdr->len + 2); +skip_rsn_ies: + buf_len = 0; + if (wpa_tdls_get_privacy(sm)) { + /* Peer RSN IE, Lifetime */ + buf_len += peer->rsnie_i_len + + sizeof(struct wpa_tdls_timeoutie); + } + rbuf = os_zalloc(buf_len + 1); + if (rbuf == NULL) { + wpa_tdls_peer_free(sm, peer); + return -1; + } + pos = rbuf; + + if (!wpa_tdls_get_privacy(sm)) + goto skip_ies; + /* Initiator RSN IE */ + pos = wpa_add_ie(pos, peer->rsnie_i, peer->rsnie_i_len); + /* Lifetime */ + peer->lifetime = TPK_LIFETIME; + pos = wpa_add_tdls_timeoutie(pos, (u8 *) &timeoutie, + sizeof(timeoutie), peer->lifetime); + wpa_printf(MSG_DEBUG, "TDLS: TPK lifetime %u seconds", peer->lifetime); +skip_ies: + status = wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_DISCOVERY_RESPONSE, + dialog_token, 0, 0, 0, rbuf, pos - rbuf); + os_free(rbuf); + + return status; } @@ -1263,10 +1512,17 @@ wpa_tdls_process_discovery_request(struct wpa_sm *sm, const u8 *addr, dialog_token = buf[sizeof(struct wpa_tdls_frame)]; + /* + * Some APs will tack on a weird IE to the end of a TDLS + * discovery request packet. This needn't fail the response, + * since the required IE are verified separately. + */ if (wpa_supplicant_parse_ies(buf + sizeof(struct wpa_tdls_frame) + 1, len - (sizeof(struct wpa_tdls_frame) + 1), - &kde) < 0) - return -1; + &kde) < 0) { + wpa_printf(MSG_DEBUG, + "TDLS: Failed to parse IEs in Discovery Request - ignore as an interop workaround"); + } if (!kde.lnkid) { wpa_printf(MSG_DEBUG, "TDLS: Link ID not found in Discovery " @@ -1282,7 +1538,7 @@ wpa_tdls_process_discovery_request(struct wpa_sm *sm, const u8 *addr, return -1; } - peer = wpa_tdls_add_peer(sm, addr); + peer = wpa_tdls_add_peer(sm, addr, NULL); if (peer == NULL) return -1; @@ -1298,7 +1554,7 @@ int wpa_tdls_send_discovery_request(struct wpa_sm *sm, const u8 *addr) wpa_printf(MSG_DEBUG, "TDLS: Sending Discovery Request to peer " MACSTR, MAC2STR(addr)); return wpa_tdls_tpk_send(sm, addr, WLAN_TDLS_DISCOVERY_REQUEST, - 1, 0, NULL, 0); + 1, 0, 0, 1, NULL, 0); } @@ -1309,25 +1565,189 @@ static int copy_supp_rates(const struct wpa_eapol_ie_parse *kde, wpa_printf(MSG_DEBUG, "TDLS: No supported rates received"); return -1; } + peer->supp_rates_len = merge_byte_arrays( + peer->supp_rates, sizeof(peer->supp_rates), + kde->supp_rates + 2, kde->supp_rates_len - 2, + kde->ext_supp_rates ? kde->ext_supp_rates + 2 : NULL, + kde->ext_supp_rates_len - 2); + return 0; +} + + +static int copy_peer_ht_capab(const struct wpa_eapol_ie_parse *kde, + struct wpa_tdls_peer *peer) +{ + if (!kde->ht_capabilities || + kde->ht_capabilities_len < + sizeof(struct ieee80211_ht_capabilities) ) { + wpa_printf(MSG_DEBUG, "TDLS: No supported ht capabilities " + "received"); + return 0; + } + + if (!peer->ht_capabilities) { + peer->ht_capabilities = + os_zalloc(sizeof(struct ieee80211_ht_capabilities)); + if (peer->ht_capabilities == NULL) + return -1; + } + + os_memcpy(peer->ht_capabilities, kde->ht_capabilities, + sizeof(struct ieee80211_ht_capabilities)); + wpa_hexdump(MSG_DEBUG, "TDLS: Peer HT capabilities", + (u8 *) peer->ht_capabilities, + sizeof(struct ieee80211_ht_capabilities)); + + return 0; +} + + +static int copy_peer_vht_capab(const struct wpa_eapol_ie_parse *kde, + struct wpa_tdls_peer *peer) +{ + if (!kde->vht_capabilities || + kde->vht_capabilities_len < + sizeof(struct ieee80211_vht_capabilities) ) { + wpa_printf(MSG_DEBUG, "TDLS: No supported vht capabilities " + "received"); + return 0; + } + + if (!peer->vht_capabilities) { + peer->vht_capabilities = + os_zalloc(sizeof(struct ieee80211_vht_capabilities)); + if (peer->vht_capabilities == NULL) + return -1; + } + + os_memcpy(peer->vht_capabilities, kde->vht_capabilities, + sizeof(struct ieee80211_vht_capabilities)); + wpa_hexdump(MSG_DEBUG, "TDLS: Peer VHT capabilities", + (u8 *) peer->vht_capabilities, + sizeof(struct ieee80211_vht_capabilities)); + + return 0; +} - peer->supp_rates_len = kde->supp_rates_len - 2; - if (peer->supp_rates_len > IEEE80211_MAX_SUPP_RATES) - peer->supp_rates_len = IEEE80211_MAX_SUPP_RATES; - os_memcpy(peer->supp_rates, kde->supp_rates + 2, peer->supp_rates_len); - if (kde->ext_supp_rates) { - int clen = kde->ext_supp_rates_len - 2; - if (peer->supp_rates_len + clen > IEEE80211_MAX_SUPP_RATES) - clen = IEEE80211_MAX_SUPP_RATES - peer->supp_rates_len; - os_memcpy(peer->supp_rates + peer->supp_rates_len, - kde->ext_supp_rates + 2, clen); - peer->supp_rates_len += clen; +static int copy_peer_ext_capab(const struct wpa_eapol_ie_parse *kde, + struct wpa_tdls_peer *peer) +{ + if (!kde->ext_capab) { + wpa_printf(MSG_DEBUG, "TDLS: No extended capabilities " + "received"); + return 0; + } + + if (!peer->ext_capab || peer->ext_capab_len < kde->ext_capab_len - 2) { + /* Need to allocate buffer to fit the new information */ + os_free(peer->ext_capab); + peer->ext_capab = os_zalloc(kde->ext_capab_len - 2); + if (peer->ext_capab == NULL) + return -1; } + peer->ext_capab_len = kde->ext_capab_len - 2; + os_memcpy(peer->ext_capab, kde->ext_capab + 2, peer->ext_capab_len); + return 0; } +static int copy_peer_wmm_capab(const struct wpa_eapol_ie_parse *kde, + struct wpa_tdls_peer *peer) +{ + struct wmm_information_element *wmm; + + if (!kde->wmm) { + wpa_printf(MSG_DEBUG, "TDLS: No supported WMM capabilities received"); + return 0; + } + + if (kde->wmm_len < sizeof(struct wmm_information_element)) { + wpa_printf(MSG_DEBUG, "TDLS: Invalid supported WMM capabilities received"); + return -1; + } + + wmm = (struct wmm_information_element *) kde->wmm; + peer->qos_info = wmm->qos_info; + + peer->wmm_capable = 1; + + wpa_printf(MSG_DEBUG, "TDLS: Peer WMM QOS Info 0x%x", peer->qos_info); + return 0; +} + + +static int copy_peer_supp_channels(const struct wpa_eapol_ie_parse *kde, + struct wpa_tdls_peer *peer) +{ + if (!kde->supp_channels) { + wpa_printf(MSG_DEBUG, "TDLS: No supported channels received"); + return 0; + } + + if (!peer->supp_channels || + peer->supp_channels_len < kde->supp_channels_len) { + os_free(peer->supp_channels); + peer->supp_channels = os_zalloc(kde->supp_channels_len); + if (peer->supp_channels == NULL) + return -1; + } + + peer->supp_channels_len = kde->supp_channels_len; + + os_memcpy(peer->supp_channels, kde->supp_channels, + peer->supp_channels_len); + wpa_hexdump(MSG_DEBUG, "TDLS: Peer Supported Channels", + (u8 *) peer->supp_channels, peer->supp_channels_len); + return 0; +} + + +static int copy_peer_supp_oper_classes(const struct wpa_eapol_ie_parse *kde, + struct wpa_tdls_peer *peer) +{ + if (!kde->supp_oper_classes) { + wpa_printf(MSG_DEBUG, "TDLS: No supported operating classes received"); + return 0; + } + + if (!peer->supp_oper_classes || + peer->supp_oper_classes_len < kde->supp_oper_classes_len) { + os_free(peer->supp_oper_classes); + peer->supp_oper_classes = os_zalloc(kde->supp_oper_classes_len); + if (peer->supp_oper_classes == NULL) + return -1; + } + + peer->supp_oper_classes_len = kde->supp_oper_classes_len; + os_memcpy(peer->supp_oper_classes, kde->supp_oper_classes, + peer->supp_oper_classes_len); + wpa_hexdump(MSG_DEBUG, "TDLS: Peer Supported Operating Classes", + (u8 *) peer->supp_oper_classes, + peer->supp_oper_classes_len); + return 0; +} + + +static int wpa_tdls_addset_peer(struct wpa_sm *sm, struct wpa_tdls_peer *peer, + int add) +{ + return wpa_sm_tdls_peer_addset(sm, peer->addr, add, peer->aid, + peer->capability, + peer->supp_rates, peer->supp_rates_len, + peer->ht_capabilities, + peer->vht_capabilities, + peer->qos_info, peer->wmm_capable, + peer->ext_capab, peer->ext_capab_len, + peer->supp_channels, + peer->supp_channels_len, + peer->supp_oper_classes, + peer->supp_oper_classes_len); +} + + static int wpa_tdls_process_tpk_m1(struct wpa_sm *sm, const u8 *src_addr, const u8 *buf, size_t len) { @@ -1363,17 +1783,44 @@ static int wpa_tdls_process_tpk_m1(struct wpa_sm *sm, const u8 *src_addr, wpa_printf(MSG_INFO, "TDLS: Dialog Token in TPK M1 %d", dtoken); - for (peer = sm->tdls; peer; peer = peer->next) { - if (os_memcmp(peer->addr, src_addr, ETH_ALEN) == 0) { - existing_peer = 1; - break; - } - } + peer = wpa_tdls_add_peer(sm, src_addr, &existing_peer); + if (peer == NULL) + goto error; - if (peer == NULL) { - peer = wpa_tdls_add_peer(sm, src_addr); - if (peer == NULL) - goto error; + /* If found, use existing entry instead of adding a new one; + * how to handle the case where both ends initiate at the + * same time? */ + if (existing_peer) { + if (peer->tpk_success) { + wpa_printf(MSG_DEBUG, "TDLS: TDLS Setup Request while " + "direct link is enabled - tear down the " + "old link first"); + wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, peer->addr); + wpa_tdls_peer_clear(sm, peer); + } else if (peer->initiator) { + /* + * An entry is already present, so check if we already + * sent a TDLS Setup Request. If so, compare MAC + * addresses and let the STA with the lower MAC address + * continue as the initiator. The other negotiation is + * terminated. + */ + if (os_memcmp(sm->own_addr, src_addr, ETH_ALEN) < 0) { + wpa_printf(MSG_DEBUG, "TDLS: Discard request " + "from peer with higher address " + MACSTR, MAC2STR(src_addr)); + return -1; + } else { + wpa_printf(MSG_DEBUG, "TDLS: Accept request " + "from peer with lower address " + MACSTR " (terminate previously " + "initiated negotiation", + MAC2STR(src_addr)); + wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, + peer->addr); + wpa_tdls_peer_clear(sm, peer); + } + } } /* capability information */ @@ -1381,10 +1828,15 @@ static int wpa_tdls_process_tpk_m1(struct wpa_sm *sm, const u8 *src_addr, cpos += 2; ielen = len - (cpos - buf); /* start of IE in buf */ - if (wpa_supplicant_parse_ies(cpos, ielen, &kde) < 0) { - wpa_printf(MSG_INFO, "TDLS: Failed to parse IEs in TPK M1"); - goto error; - } + + /* + * Don't reject the message if failing to parse IEs. The IEs we need are + * explicitly checked below. Some APs may add arbitrary padding to the + * end of short TDLS frames and that would look like invalid IEs. + */ + if (wpa_supplicant_parse_ies(cpos, ielen, &kde) < 0) + wpa_printf(MSG_DEBUG, + "TDLS: Failed to parse IEs in TPK M1 - ignore as an interop workaround"); if (kde.lnkid == NULL || kde.lnkid_len < 3 * ETH_ALEN) { wpa_printf(MSG_INFO, "TDLS: No valid Link Identifier IE in " @@ -1396,7 +1848,7 @@ static int wpa_tdls_process_tpk_m1(struct wpa_sm *sm, const u8 *src_addr, lnkid = (struct wpa_tdls_lnkid *) kde.lnkid; if (os_memcmp(sm->bssid, lnkid->bssid, ETH_ALEN) != 0) { wpa_printf(MSG_INFO, "TDLS: TPK M1 from diff BSS"); - status = WLAN_STATUS_NOT_IN_SAME_BSS; + status = WLAN_STATUS_REQUEST_DECLINED; goto error; } @@ -1406,20 +1858,39 @@ static int wpa_tdls_process_tpk_m1(struct wpa_sm *sm, const u8 *src_addr, if (copy_supp_rates(&kde, peer) < 0) goto error; + if (copy_peer_ht_capab(&kde, peer) < 0) + goto error; + + if (copy_peer_vht_capab(&kde, peer) < 0) + goto error; + + if (copy_peer_ext_capab(&kde, peer) < 0) + goto error; + + if (copy_peer_supp_channels(&kde, peer) < 0) + goto error; + + if (copy_peer_supp_oper_classes(&kde, peer) < 0) + goto error; + + peer->qos_info = kde.qosinfo; + + /* Overwrite with the qos_info obtained in WMM IE */ + if (copy_peer_wmm_capab(&kde, peer) < 0) + goto error; + + peer->aid = kde.aid; + #ifdef CONFIG_TDLS_TESTING if (tdls_testing & TDLS_TESTING_CONCURRENT_INIT) { - for (peer = sm->tdls; peer; peer = peer->next) { - if (os_memcmp(peer->addr, src_addr, ETH_ALEN) == 0) - break; - } - if (peer == NULL) { - peer = wpa_tdls_add_peer(sm, src_addr); - if (peer == NULL) - goto error; - } + peer = wpa_tdls_add_peer(sm, src_addr, NULL); + if (peer == NULL) + goto error; wpa_printf(MSG_DEBUG, "TDLS: Testing concurrent initiation of " "TDLS setup - send own request"); peer->initiator = 1; + wpa_sm_tdls_peer_addset(sm, peer->addr, 1, 0, 0, NULL, 0, NULL, + NULL, 0, 0, NULL, 0, NULL, 0, NULL, 0); wpa_tdls_send_tpk_m1(sm, peer); } @@ -1502,52 +1973,6 @@ static int wpa_tdls_process_tpk_m1(struct wpa_sm *sm, const u8 *src_addr, } skip_rsn: - /* If found, use existing entry instead of adding a new one; - * how to handle the case where both ends initiate at the - * same time? */ - if (existing_peer) { - if (peer->tpk_success) { - wpa_printf(MSG_DEBUG, "TDLS: TDLS Setup Request while " - "direct link is enabled - tear down the " - "old link first"); -#if 0 - /* TODO: Disabling the link would be more proper - * operation here, but it seems to trigger a race with - * some drivers handling the new request frame. */ - wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, src_addr); -#else - if (sm->tdls_external_setup) - wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, - src_addr); - else - wpa_tdls_del_key(sm, peer); -#endif - wpa_tdls_peer_free(sm, peer); - } - - /* - * An entry is already present, so check if we already sent a - * TDLS Setup Request. If so, compare MAC addresses and let the - * STA with the lower MAC address continue as the initiator. - * The other negotiation is terminated. - */ - if (peer->initiator) { - if (os_memcmp(sm->own_addr, src_addr, ETH_ALEN) < 0) { - wpa_printf(MSG_DEBUG, "TDLS: Discard request " - "from peer with higher address " - MACSTR, MAC2STR(src_addr)); - return -1; - } else { - wpa_printf(MSG_DEBUG, "TDLS: Accept request " - "from peer with lower address " - MACSTR " (terminate previously " - "initiated negotiation", - MAC2STR(src_addr)); - wpa_tdls_peer_free(sm, peer); - } - } - } - #ifdef CONFIG_TDLS_TESTING if (tdls_testing & TDLS_TESTING_CONCURRENT_INIT) { if (os_memcmp(sm->own_addr, peer->addr, ETH_ALEN) < 0) { @@ -1572,16 +1997,26 @@ skip_rsn: } ftie = (struct wpa_tdls_ftie *) kde.ftie; - os_memcpy(peer->inonce, ftie->Snonce, WPA_NONCE_LEN); os_memcpy(peer->rsnie_i, kde.rsn_ie, kde.rsn_ie_len); peer->rsnie_i_len = kde.rsn_ie_len; peer->cipher = cipher; - if (os_get_random(peer->rnonce, WPA_NONCE_LEN)) { - wpa_msg(sm->ctx->ctx, MSG_WARNING, - "TDLS: Failed to get random data for responder nonce"); - wpa_tdls_peer_free(sm, peer); - goto error; + if (os_memcmp(peer->inonce, ftie->Snonce, WPA_NONCE_LEN) != 0) { + /* + * There is no point in updating the RNonce for every obtained + * TPK M1 frame (e.g., retransmission due to timeout) with the + * same INonce (SNonce in FTIE). However, if the TPK M1 is + * retransmitted with a different INonce, update the RNonce + * since this is for a new TDLS session. + */ + wpa_printf(MSG_DEBUG, + "TDLS: New TPK M1 INonce - generate new RNonce"); + os_memcpy(peer->inonce, ftie->Snonce, WPA_NONCE_LEN); + if (os_get_random(peer->rnonce, WPA_NONCE_LEN)) { + wpa_msg(sm->ctx->ctx, MSG_WARNING, + "TDLS: Failed to get random data for responder nonce"); + goto error; + } } #if 0 @@ -1634,27 +2069,41 @@ skip_rsn: wpa_tdls_generate_tpk(peer, sm->own_addr, sm->bssid); skip_rsn_check: - /* add the peer to the driver as a "setup in progress" peer */ - wpa_sm_tdls_peer_addset(sm, peer->addr, 1, 0, NULL, 0); +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_CONCURRENT_INIT) + goto skip_add_peer; +#endif /* CONFIG_TDLS_TESTING */ + + /* add supported rates, capabilities, and qos_info to the TDLS peer */ + if (wpa_tdls_addset_peer(sm, peer, 1) < 0) + goto error; + +#ifdef CONFIG_TDLS_TESTING +skip_add_peer: +#endif /* CONFIG_TDLS_TESTING */ + peer->tpk_in_progress = 1; wpa_printf(MSG_DEBUG, "TDLS: Sending TDLS Setup Response / TPK M2"); if (wpa_tdls_send_tpk_m2(sm, src_addr, dtoken, lnkid, peer) < 0) { - wpa_tdls_disable_link(sm, peer->addr); + wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, peer->addr); goto error; } return 0; error: - wpa_tdls_send_error(sm, src_addr, WLAN_TDLS_SETUP_RESPONSE, dtoken, + wpa_tdls_send_error(sm, src_addr, WLAN_TDLS_SETUP_RESPONSE, dtoken, 0, status); + if (peer) + wpa_tdls_peer_free(sm, peer); return -1; } -static void wpa_tdls_enable_link(struct wpa_sm *sm, struct wpa_tdls_peer *peer) +static int wpa_tdls_enable_link(struct wpa_sm *sm, struct wpa_tdls_peer *peer) { peer->tpk_success = 1; + peer->tpk_in_progress = 0; eloop_cancel_timeout(wpa_tdls_tpk_timeout, sm, peer); if (wpa_tdls_get_privacy(sm)) { u32 lifetime = peer->lifetime; @@ -1675,11 +2124,14 @@ static void wpa_tdls_enable_link(struct wpa_sm *sm, struct wpa_tdls_peer *peer) #endif /* CONFIG_TDLS_TESTING */ } - /* add supported rates and capabilities to the TDLS peer */ - wpa_sm_tdls_peer_addset(sm, peer->addr, 0, peer->capability, - peer->supp_rates, peer->supp_rates_len); + if (peer->reconfig_key && wpa_tdls_set_key(sm, peer) < 0) { + wpa_printf(MSG_INFO, "TDLS: Could not configure key to the " + "driver"); + return -1; + } + peer->reconfig_key = 0; - wpa_sm_tdls_oper(sm, TDLS_ENABLE_LINK, peer->addr); + return wpa_sm_tdls_oper(sm, TDLS_ENABLE_LINK, peer->addr); } @@ -1698,6 +2150,7 @@ static int wpa_tdls_process_tpk_m2(struct wpa_sm *sm, const u8 *src_addr, int ielen; u16 status; const u8 *pos; + int ret = 0; wpa_printf(MSG_DEBUG, "TDLS: Received TDLS Setup Response / TPK M2 " "(Peer " MACSTR ")", MAC2STR(src_addr)); @@ -1710,10 +2163,23 @@ static int wpa_tdls_process_tpk_m2(struct wpa_sm *sm, const u8 *src_addr, "TPK M2: " MACSTR, MAC2STR(src_addr)); return -1; } + if (!peer->initiator) { + /* + * This may happen if both devices try to initiate TDLS at the + * same time and we accept the TPK M1 from the peer in + * wpa_tdls_process_tpk_m1() and clear our previous state. + */ + wpa_printf(MSG_INFO, "TDLS: We were not the initiator, so " + "ignore TPK M2 from " MACSTR, MAC2STR(src_addr)); + return -1; + } wpa_tdls_tpk_retry_timeout_cancel(sm, peer, WLAN_TDLS_SETUP_REQUEST); - if (len < 3 + 2 + 1) + if (len < 3 + 2 + 1) { + wpa_tdls_disable_peer_link(sm, peer); return -1; + } + pos = buf; pos += 1 /* pkt_type */ + 1 /* Category */ + 1 /* Action */; status = WPA_GET_LE16(pos); @@ -1722,8 +2188,7 @@ static int wpa_tdls_process_tpk_m2(struct wpa_sm *sm, const u8 *src_addr, if (status != WLAN_STATUS_SUCCESS) { wpa_printf(MSG_INFO, "TDLS: Status code in TPK M2: %u", status); - if (sm->tdls_external_setup) - wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, src_addr); + wpa_tdls_disable_peer_link(sm, peer); return -1; } @@ -1734,18 +2199,25 @@ static int wpa_tdls_process_tpk_m2(struct wpa_sm *sm, const u8 *src_addr, wpa_printf(MSG_DEBUG, "TDLS: Dialog Token in TPK M2 %d", dtoken); - if (len < 3 + 2 + 1 + 2) + if (len < 3 + 2 + 1 + 2) { + wpa_tdls_disable_peer_link(sm, peer); return -1; + } /* capability information */ peer->capability = WPA_GET_LE16(pos); pos += 2; ielen = len - (pos - buf); /* start of IE in buf */ - if (wpa_supplicant_parse_ies(pos, ielen, &kde) < 0) { - wpa_printf(MSG_INFO, "TDLS: Failed to parse IEs in TPK M2"); - goto error; - } + + /* + * Don't reject the message if failing to parse IEs. The IEs we need are + * explicitly checked below. Some APs may add arbitrary padding to the + * end of short TDLS frames and that would look like invalid IEs. + */ + if (wpa_supplicant_parse_ies(pos, ielen, &kde) < 0) + wpa_printf(MSG_DEBUG, + "TDLS: Failed to parse IEs in TPK M2 - ignore as an interop workaround"); #ifdef CONFIG_TDLS_TESTING if (tdls_testing & TDLS_TESTING_DECLINE_RESP) { @@ -1773,6 +2245,29 @@ static int wpa_tdls_process_tpk_m2(struct wpa_sm *sm, const u8 *src_addr, if (copy_supp_rates(&kde, peer) < 0) goto error; + if (copy_peer_ht_capab(&kde, peer) < 0) + goto error; + + if (copy_peer_vht_capab(&kde, peer) < 0) + goto error; + + if (copy_peer_ext_capab(&kde, peer) < 0) + goto error; + + if (copy_peer_supp_channels(&kde, peer) < 0) + goto error; + + if (copy_peer_supp_oper_classes(&kde, peer) < 0) + goto error; + + peer->qos_info = kde.qosinfo; + + /* Overwrite with the qos_info obtained in WMM IE */ + if (copy_peer_wmm_capab(&kde, peer) < 0) + goto error; + + peer->aid = kde.aid; + if (!wpa_tdls_get_privacy(sm)) { peer->rsnie_p_len = 0; peer->cipher = WPA_CIPHER_NONE; @@ -1788,6 +2283,13 @@ static int wpa_tdls_process_tpk_m2(struct wpa_sm *sm, const u8 *src_addr, wpa_hexdump(MSG_DEBUG, "TDLS: RSN IE Received from TPK M2", kde.rsn_ie, kde.rsn_ie_len); + if (kde.rsn_ie_len > TDLS_MAX_IE_LEN) { + wpa_printf(MSG_INFO, + "TDLS: Too long Responder RSN IE in TPK M2"); + status = WLAN_STATUS_INVALID_RSNIE; + goto error; + } + /* * FIX: bitwise comparison of RSN IE is not the correct way of * validation this. It can be different, but certain fields must @@ -1863,30 +2365,52 @@ static int wpa_tdls_process_tpk_m2(struct wpa_sm *sm, const u8 *src_addr, (u8 *) timeoutie, ftie) < 0) { /* Discard the frame */ wpa_tdls_del_key(sm, peer); - wpa_tdls_peer_free(sm, peer); - if (sm->tdls_external_setup) - wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, src_addr); + wpa_tdls_disable_peer_link(sm, peer); return -1; } - wpa_tdls_set_key(sm, peer); + if (wpa_tdls_set_key(sm, peer) < 0) { + /* + * Some drivers may not be able to config the key prior to full + * STA entry having been configured. + */ + wpa_printf(MSG_DEBUG, "TDLS: Try to configure TPK again after " + "STA entry is complete"); + peer->reconfig_key = 1; + } skip_rsn: peer->dtoken = dtoken; + /* add supported rates, capabilities, and qos_info to the TDLS peer */ + if (wpa_tdls_addset_peer(sm, peer, 0) < 0) + goto error; + wpa_printf(MSG_DEBUG, "TDLS: Sending TDLS Setup Confirm / " "TPK Handshake Message 3"); - wpa_tdls_send_tpk_m3(sm, src_addr, dtoken, lnkid, peer); - - wpa_tdls_enable_link(sm, peer); + if (wpa_tdls_send_tpk_m3(sm, src_addr, dtoken, lnkid, peer) < 0) + goto error; - return 0; + if (!peer->tpk_success) { + /* + * Enable Link only when tpk_success is 0, signifying that this + * processing of TPK M2 frame is not because of a retransmission + * during TDLS setup handshake. + */ + ret = wpa_tdls_enable_link(sm, peer); + if (ret < 0) { + wpa_printf(MSG_DEBUG, "TDLS: Could not enable link"); + wpa_tdls_do_teardown( + sm, peer, + WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED); + } + } + return ret; error: - wpa_tdls_send_error(sm, src_addr, WLAN_TDLS_SETUP_CONFIRM, dtoken, + wpa_tdls_send_error(sm, src_addr, WLAN_TDLS_SETUP_CONFIRM, dtoken, 1, status); - if (sm->tdls_external_setup) - wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, src_addr); + wpa_tdls_disable_peer_link(sm, peer); return -1; } @@ -1903,6 +2427,7 @@ static int wpa_tdls_process_tpk_m3(struct wpa_sm *sm, const u8 *src_addr, u16 status; const u8 *pos; u32 lifetime; + int ret = 0; wpa_printf(MSG_DEBUG, "TDLS: Received TDLS Setup Confirm / TPK M3 " "(Peer " MACSTR ")", MAC2STR(src_addr)); @@ -1918,7 +2443,7 @@ static int wpa_tdls_process_tpk_m3(struct wpa_sm *sm, const u8 *src_addr, wpa_tdls_tpk_retry_timeout_cancel(sm, peer, WLAN_TDLS_SETUP_RESPONSE); if (len < 3 + 3) - return -1; + goto error; pos = buf; pos += 1 /* pkt_type */ + 1 /* Category */ + 1 /* Action */; @@ -1927,21 +2452,26 @@ static int wpa_tdls_process_tpk_m3(struct wpa_sm *sm, const u8 *src_addr, if (status != 0) { wpa_printf(MSG_INFO, "TDLS: Status code in TPK M3: %u", status); - if (sm->tdls_external_setup) - wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, src_addr); - return -1; + goto error; } pos += 2 /* status code */ + 1 /* dialog token */; ielen = len - (pos - buf); /* start of IE in buf */ + + /* + * Don't reject the message if failing to parse IEs. The IEs we need are + * explicitly checked below. Some APs piggy-back broken IEs to the end + * of a TDLS Confirm packet, which will fail the link if we don't ignore + * this error. + */ if (wpa_supplicant_parse_ies((const u8 *) pos, ielen, &kde) < 0) { - wpa_printf(MSG_INFO, "TDLS: Failed to parse KDEs in TPK M3"); - return -1; + wpa_printf(MSG_DEBUG, + "TDLS: Failed to parse KDEs in TPK M3 - ignore as an interop workaround"); } if (kde.lnkid == NULL || kde.lnkid_len < 3 * ETH_ALEN) { wpa_printf(MSG_INFO, "TDLS: No Link Identifier IE in TPK M3"); - return -1; + goto error; } wpa_hexdump(MSG_DEBUG, "TDLS: Link ID Received from TPK M3", (u8 *) kde.lnkid, kde.lnkid_len); @@ -1949,7 +2479,7 @@ static int wpa_tdls_process_tpk_m3(struct wpa_sm *sm, const u8 *src_addr, if (os_memcmp(sm->bssid, lnkid->bssid, ETH_ALEN) != 0) { wpa_printf(MSG_INFO, "TDLS: TPK M3 from diff BSS"); - return -1; + goto error; } if (!wpa_tdls_get_privacy(sm)) @@ -1957,7 +2487,7 @@ static int wpa_tdls_process_tpk_m3(struct wpa_sm *sm, const u8 *src_addr, if (kde.ftie == NULL || kde.ftie_len < sizeof(*ftie)) { wpa_printf(MSG_INFO, "TDLS: No FTIE in TPK M3"); - return -1; + goto error; } wpa_hexdump(MSG_DEBUG, "TDLS: FTIE Received from TPK M3", kde.ftie, sizeof(*ftie)); @@ -1965,7 +2495,7 @@ static int wpa_tdls_process_tpk_m3(struct wpa_sm *sm, const u8 *src_addr, if (kde.rsn_ie == NULL) { wpa_printf(MSG_INFO, "TDLS: No RSN IE in TPK M3"); - return -1; + goto error; } wpa_hexdump(MSG_DEBUG, "TDLS: RSN IE Received from TPK M3", kde.rsn_ie, kde.rsn_ie_len); @@ -1973,24 +2503,24 @@ static int wpa_tdls_process_tpk_m3(struct wpa_sm *sm, const u8 *src_addr, os_memcmp(kde.rsn_ie, peer->rsnie_p, peer->rsnie_p_len) != 0) { wpa_printf(MSG_INFO, "TDLS: RSN IE in TPK M3 does not match " "with the one sent in TPK M2"); - return -1; + goto error; } if (!os_memcmp(peer->rnonce, ftie->Anonce, WPA_NONCE_LEN) == 0) { wpa_printf(MSG_INFO, "TDLS: FTIE ANonce in TPK M3 does " "not match with FTIE ANonce used in TPK M2"); - return -1; + goto error; } if (!os_memcmp(peer->inonce, ftie->Snonce, WPA_NONCE_LEN) == 0) { wpa_printf(MSG_INFO, "TDLS: FTIE SNonce in TPK M3 does not " "match with FTIE SNonce used in TPK M1"); - return -1; + goto error; } if (kde.key_lifetime == NULL) { wpa_printf(MSG_INFO, "TDLS: No Key Lifetime IE in TPK M3"); - return -1; + goto error; } timeoutie = (struct wpa_tdls_timeoutie *) kde.key_lifetime; wpa_hexdump(MSG_DEBUG, "TDLS: Timeout IE Received from TPK M3", @@ -2001,25 +2531,46 @@ static int wpa_tdls_process_tpk_m3(struct wpa_sm *sm, const u8 *src_addr, if (lifetime != peer->lifetime) { wpa_printf(MSG_INFO, "TDLS: Unexpected TPK lifetime %u in " "TPK M3 (expected %u)", lifetime, peer->lifetime); - if (sm->tdls_external_setup) - wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, src_addr); - return -1; + goto error; } if (wpa_supplicant_verify_tdls_mic(3, peer, (u8 *) lnkid, (u8 *) timeoutie, ftie) < 0) { wpa_tdls_del_key(sm, peer); - wpa_tdls_peer_free(sm, peer); - return -1; + goto error; } - if (wpa_tdls_set_key(sm, peer) < 0) - return -1; + if (wpa_tdls_set_key(sm, peer) < 0) { + /* + * Some drivers may not be able to config the key prior to full + * STA entry having been configured. + */ + wpa_printf(MSG_DEBUG, "TDLS: Try to configure TPK again after " + "STA entry is complete"); + peer->reconfig_key = 1; + } skip_rsn: - wpa_tdls_enable_link(sm, peer); + /* add supported rates, capabilities, and qos_info to the TDLS peer */ + if (wpa_tdls_addset_peer(sm, peer, 0) < 0) + goto error; - return 0; + if (!peer->tpk_success) { + /* + * Enable Link only when tpk_success is 0, signifying that this + * processing of TPK M3 frame is not because of a retransmission + * during TDLS setup handshake. + */ + ret = wpa_tdls_enable_link(sm, peer); + if (ret < 0) { + wpa_printf(MSG_DEBUG, "TDLS: Could not enable link"); + goto error; + } + } + return ret; +error: + wpa_tdls_do_teardown(sm, peer, WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED); + return -1; } @@ -2069,26 +2620,28 @@ int wpa_tdls_start(struct wpa_sm *sm, const u8 *addr) return -1; } - /* Find existing entry and if found, use that instead of adding - * a new one */ - for (peer = sm->tdls; peer; peer = peer->next) { - if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0) - break; - } + peer = wpa_tdls_add_peer(sm, addr, NULL); + if (peer == NULL) + return -1; - if (peer == NULL) { - peer = wpa_tdls_add_peer(sm, addr); - if (peer == NULL) - return -1; + if (peer->tpk_in_progress) { + wpa_printf(MSG_DEBUG, "TDLS: Setup is already in progress with the peer"); + return 0; } peer->initiator = 1; /* add the peer to the driver as a "setup in progress" peer */ - wpa_sm_tdls_peer_addset(sm, peer->addr, 1, 0, NULL, 0); + if (wpa_sm_tdls_peer_addset(sm, peer->addr, 1, 0, 0, NULL, 0, NULL, + NULL, 0, 0, NULL, 0, NULL, 0, NULL, 0)) { + wpa_tdls_disable_peer_link(sm, peer); + return -1; + } + + peer->tpk_in_progress = 1; if (wpa_tdls_send_tpk_m1(sm, peer) < 0) { - wpa_tdls_disable_link(sm, peer->addr); + wpa_tdls_disable_peer_link(sm, peer); return -1; } @@ -2096,12 +2649,12 @@ int wpa_tdls_start(struct wpa_sm *sm, const u8 *addr) } -int wpa_tdls_reneg(struct wpa_sm *sm, const u8 *addr) +void wpa_tdls_remove(struct wpa_sm *sm, const u8 *addr) { struct wpa_tdls_peer *peer; if (sm->tdls_disabled || !sm->tdls_supported) - return -1; + return; for (peer = sm->tdls; peer; peer = peer->next) { if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0) @@ -2109,17 +2662,16 @@ int wpa_tdls_reneg(struct wpa_sm *sm, const u8 *addr) } if (peer == NULL || !peer->tpk_success) - return -1; + return; if (sm->tdls_external_setup) { /* * Disable previous link to allow renegotiation to be completed * on AP path. */ - wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, peer->addr); + wpa_tdls_do_teardown(sm, peer, + WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED); } - - return wpa_tdls_start(sm, addr); } @@ -2218,7 +2770,8 @@ int wpa_tdls_init(struct wpa_sm *sm) * are assumed to perform everything internally */ if (wpa_sm_tdls_get_capa(sm, &sm->tdls_supported, - &sm->tdls_external_setup) < 0) { + &sm->tdls_external_setup, + &sm->tdls_chan_switch) < 0) { sm->tdls_supported = 1; sm->tdls_external_setup = 0; } @@ -2227,17 +2780,43 @@ int wpa_tdls_init(struct wpa_sm *sm) "driver", sm->tdls_supported ? "" : " not"); wpa_printf(MSG_DEBUG, "TDLS: Driver uses %s link setup", sm->tdls_external_setup ? "external" : "internal"); + wpa_printf(MSG_DEBUG, "TDLS: Driver %s TDLS channel switching", + sm->tdls_chan_switch ? "supports" : "does not support"); return 0; } +void wpa_tdls_teardown_peers(struct wpa_sm *sm) +{ + struct wpa_tdls_peer *peer, *tmp; + + if (!sm) + return; + peer = sm->tdls; + + wpa_printf(MSG_DEBUG, "TDLS: Tear down peers"); + + while (peer) { + tmp = peer->next; + wpa_printf(MSG_DEBUG, "TDLS: Tear down peer " MACSTR, + MAC2STR(peer->addr)); + if (sm->tdls_external_setup) + wpa_tdls_do_teardown(sm, peer, + WLAN_REASON_DEAUTH_LEAVING); + else + wpa_sm_tdls_oper(sm, TDLS_TEARDOWN, peer->addr); + + peer = tmp; + } +} + + static void wpa_tdls_remove_peers(struct wpa_sm *sm) { struct wpa_tdls_peer *peer, *tmp; peer = sm->tdls; - sm->tdls = NULL; while (peer) { int res; @@ -2246,7 +2825,6 @@ static void wpa_tdls_remove_peers(struct wpa_sm *sm) wpa_printf(MSG_DEBUG, "TDLS: Remove peer " MACSTR " (res=%d)", MAC2STR(peer->addr), res); wpa_tdls_peer_free(sm, peer); - os_free(peer); peer = tmp; } } @@ -2285,39 +2863,61 @@ void wpa_tdls_disassoc(struct wpa_sm *sm) } -static int wpa_tdls_prohibited(const u8 *ies, size_t len) +static int wpa_tdls_prohibited(struct wpa_eapol_ie_parse *elems) { - struct wpa_eapol_ie_parse elems; - - if (ies == NULL) - return 0; + /* bit 38 - TDLS Prohibited */ + return !!(elems->ext_capab[2 + 4] & 0x40); +} - if (wpa_supplicant_parse_ies(ies, len, &elems) < 0) - return 0; - if (elems.ext_capab == NULL || elems.ext_capab_len < 2 + 5) - return 0; - - /* bit 38 - TDLS Prohibited */ - return !!(elems.ext_capab[2 + 4] & 0x40); +static int wpa_tdls_chan_switch_prohibited(struct wpa_eapol_ie_parse *elems) +{ + /* bit 39 - TDLS Channel Switch Prohibited */ + return !!(elems->ext_capab[2 + 4] & 0x80); } void wpa_tdls_ap_ies(struct wpa_sm *sm, const u8 *ies, size_t len) { - sm->tdls_prohibited = wpa_tdls_prohibited(ies, len); + struct wpa_eapol_ie_parse elems; + + sm->tdls_prohibited = 0; + sm->tdls_chan_switch_prohibited = 0; + + if (ies == NULL || wpa_supplicant_parse_ies(ies, len, &elems) < 0 || + elems.ext_capab == NULL || elems.ext_capab_len < 2 + 5) + return; + + sm->tdls_prohibited = wpa_tdls_prohibited(&elems); wpa_printf(MSG_DEBUG, "TDLS: TDLS is %s in the target BSS", sm->tdls_prohibited ? "prohibited" : "allowed"); + sm->tdls_chan_switch_prohibited = + wpa_tdls_chan_switch_prohibited(&elems); + wpa_printf(MSG_DEBUG, "TDLS: TDLS channel switch %s in the target BSS", + sm->tdls_chan_switch_prohibited ? "prohibited" : "allowed"); } void wpa_tdls_assoc_resp_ies(struct wpa_sm *sm, const u8 *ies, size_t len) { - if (!sm->tdls_prohibited && wpa_tdls_prohibited(ies, len)) { + struct wpa_eapol_ie_parse elems; + + if (ies == NULL || wpa_supplicant_parse_ies(ies, len, &elems) < 0 || + elems.ext_capab == NULL || elems.ext_capab_len < 2 + 5) + return; + + if (!sm->tdls_prohibited && wpa_tdls_prohibited(&elems)) { wpa_printf(MSG_DEBUG, "TDLS: TDLS prohibited based on " "(Re)Association Response IEs"); sm->tdls_prohibited = 1; } + + if (!sm->tdls_chan_switch_prohibited && + wpa_tdls_chan_switch_prohibited(&elems)) { + wpa_printf(MSG_DEBUG, + "TDLS: TDLS channel switch prohibited based on (Re)Association Response IEs"); + sm->tdls_chan_switch_prohibited = 1; + } } @@ -2332,3 +2932,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; +} |