TDLS: Update peer STA as soon as full peer info is available
[mech_eap.git] / src / rsn_supp / tdls.c
index eb58791..c1d0c62 100644 (file)
@@ -33,6 +33,7 @@
 #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 */
 
@@ -81,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
@@ -107,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];
@@ -115,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;
@@ -133,6 +140,14 @@ struct wpa_tdls_peer {
 
        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;
 };
 
 
@@ -202,26 +217,27 @@ 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,
+                                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, buf, len);
 }
 
 
 static int wpa_tdls_tpk_send(struct wpa_sm *sm, const u8 *dest, u8 action_code,
-                            u8 dialog_token, u16 status_code,
+                            u8 dialog_token, u16 status_code, u32 peer_capab,
                             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 msg_len=%u",
                   MAC2STR(dest), action_code, dialog_token, status_code,
-                  (unsigned int) msg_len);
+                  peer_capab, (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, msg, msg_len)) {
                wpa_printf(MSG_INFO, "TDLS: Failed to send message "
                           "(action_code=%u)", action_code);
                return -1;
@@ -259,6 +275,7 @@ static int wpa_tdls_tpk_send(struct wpa_sm *sm, const u8 *dest, u8 action_code,
        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);
@@ -276,21 +293,13 @@ static int wpa_tdls_tpk_send(struct wpa_sm *sm, const u8 *dest, u8 action_code,
 
 
 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;
 }
@@ -323,6 +332,7 @@ 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->sm_tmr.buf,
                                          peer->sm_tmr.buf_len)) {
                        wpa_printf(MSG_INFO, "TDLS: Failed to retry "
@@ -338,7 +348,7 @@ static void wpa_tdls_tpk_retry_timeout(void *eloop_ctx, void *timeout_ctx)
 
                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);
        }
 }
 
@@ -616,12 +626,38 @@ 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));
@@ -629,6 +665,7 @@ static void wpa_tdls_peer_free(struct wpa_sm *sm, struct wpa_tdls_peer *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);
@@ -637,8 +674,14 @@ static void wpa_tdls_peer_free(struct wpa_sm *sm, struct wpa_tdls_peer *peer)
        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;
        os_memset(&peer->tpk, 0, sizeof(peer->tpk));
        os_memset(peer->inonce, 0, WPA_NONCE_LEN);
@@ -646,6 +689,14 @@ static void wpa_tdls_peer_free(struct wpa_sm *sm, struct wpa_tdls_peer *peer)
 }
 
 
+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)
 {
@@ -662,7 +713,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;
@@ -741,12 +793,9 @@ skip_ies:
 
        /* request driver to send Teardown using this FTIE */
        wpa_tdls_tpk_send(sm, addr, WLAN_TDLS_TEARDOWN, 0,
-                         reason_code, rbuf, pos - rbuf);
+                         reason_code, 0, rbuf, pos - rbuf);
        os_free(rbuf);
 
-       /* clear the Peerkey statemachine */
-       wpa_tdls_peer_free(sm, peer);
-
        return 0;
 }
 
@@ -775,11 +824,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);
 }
 
 
-void wpa_tdls_disable_link(struct wpa_sm *sm, const u8 *addr)
+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_unreachable_link(struct wpa_sm *sm, const u8 *addr)
 {
        struct wpa_tdls_peer *peer;
 
@@ -788,10 +845,47 @@ 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)) {
+               /*
+                * Disable the link, send a teardown packet through the
+                * AP, and then reset link data.
+                */
                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";
 }
 
 
@@ -864,11 +958,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;
 }
 
@@ -888,7 +978,7 @@ static int wpa_tdls_send_error(struct wpa_sm *sm, const u8 *dst,
                   " (action=%u status=%u)",
                   MAC2STR(dst), tdls_action, status);
        return wpa_tdls_tpk_send(sm, dst, tdls_action, dialog_token, status,
-                                NULL, 0);
+                                0, NULL, 0);
 }
 
 
@@ -1094,7 +1184,7 @@ skip_ies:
                   MAC2STR(peer->addr));
 
        status = wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_SETUP_REQUEST,
-                                  1, 0, rbuf, pos - rbuf);
+                                  1, 0, 0, rbuf, pos - rbuf);
        os_free(rbuf);
 
        return status;
@@ -1175,10 +1265,16 @@ 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:
        status = wpa_tdls_tpk_send(sm, src_addr, WLAN_TDLS_SETUP_RESPONSE,
-                                  dtoken, 0, rbuf, pos - rbuf);
+                                  dtoken, 0, 0, rbuf, pos - rbuf);
        os_free(rbuf);
 
        return status;
@@ -1196,6 +1292,7 @@ static int wpa_tdls_send_tpk_m3(struct wpa_sm *sm,
        struct wpa_tdls_timeoutie timeoutie;
        u32 lifetime;
        int status;
+       u32 peer_capab = 0;
 
        buf_len = 0;
        if (wpa_tdls_get_privacy(sm)) {
@@ -1257,10 +1354,24 @@ 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:
+
+       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, rbuf, pos - rbuf);
+                                  dtoken, 0, peer_capab, rbuf, pos - rbuf);
        os_free(rbuf);
 
        return status;
@@ -1275,7 +1386,7 @@ static int wpa_tdls_send_discovery_response(struct wpa_sm *sm,
                   "(peer " MACSTR ")", MAC2STR(peer->addr));
 
        return wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_DISCOVERY_RESPONSE,
-                                dialog_token, 0, NULL, 0);
+                                dialog_token, 0, 0, NULL, 0);
 }
 
 
@@ -1301,10 +1412,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 "
@@ -1336,7 +1454,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, NULL, 0);
 }
 
 
@@ -1436,6 +1554,83 @@ static int copy_peer_ext_capab(const struct wpa_eapol_ie_parse *kde,
 }
 
 
+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_process_tpk_m1(struct wpa_sm *sm, const u8 *src_addr,
                                   const u8 *buf, size_t len)
 {
@@ -1483,28 +1678,16 @@ static int wpa_tdls_process_tpk_m1(struct wpa_sm *sm, const u8 *src_addr,
                        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) {
+                       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 "
@@ -1516,12 +1699,9 @@ static int wpa_tdls_process_tpk_m1(struct wpa_sm *sm, const u8 *src_addr,
                                           MACSTR " (terminate previously "
                                           "initiated negotiation",
                                           MAC2STR(src_addr));
-                               if (sm->tdls_external_setup)
-                                       wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK,
-                                                        src_addr);
-                               else
-                                       wpa_tdls_del_key(sm, peer);
-                               wpa_tdls_peer_free(sm, peer);
+                               wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK,
+                                                peer->addr);
+                               wpa_tdls_peer_clear(sm, peer);
                        }
                }
        }
@@ -1565,8 +1745,18 @@ static int wpa_tdls_process_tpk_m1(struct wpa_sm *sm, const u8 *src_addr,
        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
@@ -1701,7 +1891,6 @@ skip_rsn:
                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;
                }
        }
@@ -1757,12 +1946,24 @@ skip_rsn:
 
 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, 0, NULL, 0, NULL, NULL, 0,
-                               NULL, 0);
+       if (wpa_sm_tdls_peer_addset(sm, peer->addr, 1, peer->aid,
+                                   peer->capability,
+                                   peer->supp_rates, peer->supp_rates_len,
+                                   peer->ht_capabilities,
+                                   peer->vht_capabilities,
+                                   peer->qos_info, peer->ext_capab,
+                                   peer->ext_capab_len,
+                                   peer->supp_channels,
+                                   peer->supp_channels_len,
+                                   peer->supp_oper_classes,
+                                   peer->supp_oper_classes_len))
+               goto error;
+
+       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;
        }
 
@@ -1771,6 +1972,8 @@ skip_rsn_check:
 error:
        wpa_tdls_send_error(sm, src_addr, WLAN_TDLS_SETUP_RESPONSE, dtoken,
                            status);
+       if (peer)
+               wpa_tdls_peer_free(sm, peer);
        return -1;
 }
 
@@ -1778,6 +1981,7 @@ error:
 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;
@@ -1798,16 +2002,6 @@ static int wpa_tdls_enable_link(struct wpa_sm *sm, struct wpa_tdls_peer *peer)
 #endif /* CONFIG_TDLS_TESTING */
        }
 
-       /* add supported rates, capabilities, and qos_info to the TDLS peer */
-       if (wpa_sm_tdls_peer_addset(sm, peer->addr, 0, peer->aid,
-                                   peer->capability,
-                                   peer->supp_rates, peer->supp_rates_len,
-                                   peer->ht_capabilities,
-                                   peer->vht_capabilities,
-                                   peer->qos_info, peer->ext_capab,
-                                   peer->ext_capab_len) < 0)
-               return -1;
-
        if (peer->reconfig_key && wpa_tdls_set_key(sm, peer) < 0) {
                wpa_printf(MSG_INFO, "TDLS: Could not configure key to the "
                           "driver");
@@ -1834,7 +2028,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;
+       int ret = 0;
 
        wpa_printf(MSG_DEBUG, "TDLS: Received TDLS Setup Response / TPK M2 "
                   "(Peer " MACSTR ")", MAC2STR(src_addr));
@@ -1860,7 +2054,7 @@ static int wpa_tdls_process_tpk_m2(struct wpa_sm *sm, const u8 *src_addr,
        wpa_tdls_tpk_retry_timeout_cancel(sm, peer, WLAN_TDLS_SETUP_REQUEST);
 
        if (len < 3 + 2 + 1) {
-               wpa_tdls_disable_link(sm, src_addr);
+               wpa_tdls_disable_peer_link(sm, peer);
                return -1;
        }
 
@@ -1872,7 +2066,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);
-               wpa_tdls_disable_link(sm, src_addr);
+               wpa_tdls_disable_peer_link(sm, peer);
                return -1;
        }
 
@@ -1884,7 +2078,7 @@ 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) {
-               wpa_tdls_disable_link(sm, src_addr);
+               wpa_tdls_disable_peer_link(sm, peer);
                return -1;
        }
 
@@ -1933,8 +2127,18 @@ static int wpa_tdls_process_tpk_m2(struct wpa_sm *sm, const u8 *src_addr,
        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)) {
@@ -1952,6 +2156,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
@@ -2027,9 +2238,7 @@ 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;
        }
 
@@ -2046,25 +2255,45 @@ static int wpa_tdls_process_tpk_m2(struct wpa_sm *sm, const u8 *src_addr,
 skip_rsn:
        peer->dtoken = dtoken;
 
+       /* add supported rates, capabilities, and qos_info to the TDLS peer */
+       if (wpa_sm_tdls_peer_addset(sm, peer->addr, 0, peer->aid,
+                                   peer->capability,
+                                   peer->supp_rates, peer->supp_rates_len,
+                                   peer->ht_capabilities,
+                                   peer->vht_capabilities,
+                                   peer->qos_info, peer->ext_capab,
+                                   peer->ext_capab_len,
+                                   peer->supp_channels,
+                                   peer->supp_channels_len,
+                                   peer->supp_oper_classes,
+                                   peer->supp_oper_classes_len) < 0)
+               goto error;
+
        wpa_printf(MSG_DEBUG, "TDLS: Sending TDLS Setup Confirm / "
                   "TPK Handshake Message 3");
-       if (wpa_tdls_send_tpk_m3(sm, src_addr, dtoken, lnkid, peer) < 0) {
-               wpa_tdls_disable_link(sm, peer->addr);
-               return -1;
-       }
+       if (wpa_tdls_send_tpk_m3(sm, src_addr, dtoken, lnkid, peer) < 0)
+               goto error;
 
-       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, 1);
+       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,
                            status);
-       wpa_tdls_disable_link(sm, peer->addr);
+       wpa_tdls_disable_peer_link(sm, peer);
        return -1;
 }
 
@@ -2081,7 +2310,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;
+       int ret = 0;
 
        wpa_printf(MSG_DEBUG, "TDLS: Received TDLS Setup Confirm / TPK M3 "
                   "(Peer " MACSTR ")", MAC2STR(src_addr));
@@ -2111,9 +2340,16 @@ static int wpa_tdls_process_tpk_m3(struct wpa_sm *sm, const u8 *src_addr,
        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");
-               goto error;
+               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) {
@@ -2198,15 +2434,37 @@ static int wpa_tdls_process_tpk_m3(struct wpa_sm *sm, const u8 *src_addr,
        }
 
 skip_rsn:
-       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, 1);
+       /* add supported rates, capabilities, and qos_info to the TDLS peer */
+       if (wpa_sm_tdls_peer_addset(sm, peer->addr, 0, peer->aid,
+                                   peer->capability,
+                                   peer->supp_rates, peer->supp_rates_len,
+                                   peer->ht_capabilities,
+                                   peer->vht_capabilities,
+                                   peer->qos_info, peer->ext_capab,
+                                   peer->ext_capab_len,
+                                   peer->supp_channels,
+                                   peer->supp_channels_len,
+                                   peer->supp_oper_classes,
+                                   peer->supp_oper_classes_len) < 0)
+               goto error;
+
+       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");
+                       wpa_tdls_do_teardown(
+                               sm, peer,
+                               WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED);
+               }
        }
        return ret;
 error:
-       wpa_tdls_disable_link(sm, peer->addr);
+       wpa_tdls_disable_peer_link(sm, peer);
        return -1;
 }
 
@@ -2261,14 +2519,21 @@ int wpa_tdls_start(struct wpa_sm *sm, const u8 *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, 0, NULL, 0, NULL, NULL, 0,
-                               NULL, 0);
+                               NULL, 0, NULL, 0, NULL, 0);
+
+       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;
        }
 
@@ -2296,7 +2561,8 @@ void wpa_tdls_remove(struct wpa_sm *sm, const u8 *addr)
                 * 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);
        }
 }
 
@@ -2412,22 +2678,23 @@ int wpa_tdls_init(struct wpa_sm *sm)
 
 void wpa_tdls_teardown_peers(struct wpa_sm *sm)
 {
-       struct wpa_tdls_peer *peer;
+       struct wpa_tdls_peer *peer, *tmp;
 
        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_send_teardown(sm, peer->addr,
-                                              WLAN_REASON_DEAUTH_LEAVING);
+                       wpa_tdls_do_teardown(sm, peer,
+                                            WLAN_REASON_DEAUTH_LEAVING);
                else
                        wpa_sm_tdls_oper(sm, TDLS_TEARDOWN, peer->addr);
 
-               peer = peer->next;
+               peer = tmp;
        }
 }
 
@@ -2437,7 +2704,6 @@ 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;
@@ -2446,7 +2712,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;
        }
 }