OSDN Git Service

Accumulative patch from commit b618a469c42120e984ab1c85ed6058504d1fca78
[android-x86/external-wpa_supplicant_8.git] / src / rsn_supp / tdls.c
index e751867..09adc19 100644 (file)
@@ -2,14 +2,8 @@
  * wpa_supplicant - TDLS
  * Copyright (c) 2010-2011, Atheros Communications
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * Alternatively, this software may be distributed under the terms of BSD
- * license.
- *
- * See README and COPYING for more details.
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
  */
 
 #include "utils/includes.h"
@@ -88,6 +82,8 @@ static void wpa_tdls_peer_free(struct wpa_sm *sm, struct wpa_tdls_peer *peer);
 
 
 #define TDLS_MAX_IE_LEN 80
+#define IEEE80211_MAX_SUPP_RATES 32
+
 struct wpa_tdls_peer {
        struct wpa_tdls_peer *next;
        int initiator; /* whether this end was initiator for TDLS setup */
@@ -119,6 +115,18 @@ struct wpa_tdls_peer {
                int buf_len;    /* length of TPK message for retransmission */
                u8 *buf;        /* buffer for TPK message */
        } sm_tmr;
+
+       u16 capability;
+
+       u8 supp_rates[IEEE80211_MAX_SUPP_RATES];
+       size_t supp_rates_len;
+
+       struct ieee80211_ht_capabilities *ht_capabilities;
+
+       u8 qos_info;
+
+       u8 *ext_capab;
+       size_t ext_capab_len;
 };
 
 
@@ -214,7 +222,9 @@ static int wpa_tdls_tpk_send(struct wpa_sm *sm, const u8 *dest, u8 action_code,
        }
 
        if (action_code == WLAN_TDLS_SETUP_CONFIRM ||
-           action_code == WLAN_TDLS_TEARDOWN)
+           action_code == WLAN_TDLS_TEARDOWN ||
+           action_code == WLAN_TDLS_DISCOVERY_REQUEST ||
+           action_code == WLAN_TDLS_DISCOVERY_RESPONSE)
                return 0; /* No retries */
 
        for (peer = sm->tdls; peer; peer = peer->next) {
@@ -253,6 +263,27 @@ 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)
+{
+       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);
+
+       return ret;
+}
+
+
 static void wpa_tdls_tpk_retry_timeout(void *eloop_ctx, void *timeout_ctx)
 {
 
@@ -291,15 +322,11 @@ static void wpa_tdls_tpk_retry_timeout(void *eloop_ctx, void *timeout_ctx)
                eloop_register_timeout(peer->sm_tmr.timer / 1000, 0,
                                       wpa_tdls_tpk_retry_timeout, sm, peer);
        } else {
-               wpa_printf(MSG_INFO, "Sending Tear_Down Request");
-               wpa_sm_tdls_oper(sm, TDLS_TEARDOWN, peer->addr);
-
-               wpa_printf(MSG_INFO, "Clearing SM: Peerkey(" MACSTR ")",
-                          MAC2STR(peer->addr));
                eloop_cancel_timeout(wpa_tdls_tpk_retry_timeout, sm, peer);
 
-               /* clear the Peerkey statemachine */
-               wpa_tdls_peer_free(sm, peer);
+               wpa_printf(MSG_DEBUG, "TDLS: Sending Teardown Request");
+               wpa_tdls_do_teardown(sm, peer,
+                                    WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED, 1);
        }
 }
 
@@ -576,7 +603,8 @@ static void wpa_tdls_tpk_timeout(void *eloop_ctx, void *timeout_ctx)
        } else {
                wpa_printf(MSG_DEBUG, "TDLS: TPK lifetime expired for " MACSTR
                           " - tear down", MAC2STR(peer->addr));
-               wpa_sm_tdls_oper(sm, TDLS_TEARDOWN, peer->addr);
+               wpa_tdls_do_teardown(sm, peer,
+                                    WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED, 1);
        }
 }
 
@@ -590,6 +618,10 @@ static void wpa_tdls_peer_free(struct wpa_sm *sm, struct wpa_tdls_peer *peer)
        peer->initiator = 0;
        os_free(peer->sm_tmr.buf);
        peer->sm_tmr.buf = NULL;
+       os_free(peer->ht_capabilities);
+       peer->ht_capabilities = NULL;
+       os_free(peer->ext_capab);
+       peer->ext_capab = NULL;
        peer->rsnie_i_len = peer->rsnie_p_len = 0;
        peer->cipher = 0;
        peer->tpk_set = peer->tpk_success = 0;
@@ -615,8 +647,7 @@ static void wpa_tdls_linkid(struct wpa_sm *sm, struct wpa_tdls_peer *peer,
 }
 
 
-int wpa_tdls_recv_teardown_notify(struct wpa_sm *sm, const u8 *addr,
-                                 u16 reason_code)
+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;
@@ -625,7 +656,7 @@ int wpa_tdls_recv_teardown_notify(struct wpa_sm *sm, const u8 *addr,
        u8 *rbuf, *pos;
        int ielen;
 
-       if (sm->tdls_disabled)
+       if (sm->tdls_disabled || !sm->tdls_supported)
                return -1;
 
        /* Find the node and free from the list */
@@ -660,8 +691,13 @@ int wpa_tdls_recv_teardown_notify(struct wpa_sm *sm, const u8 *addr,
                return -1;
        pos = rbuf;
 
-       if (!wpa_tdls_get_privacy(sm) || !peer->tpk_set || !peer->tpk_success)
+       if (!wpa_tdls_get_privacy(sm) || !peer->tpk_set || !peer->tpk_success) {
+               if (reason_code != WLAN_REASON_DEAUTH_LEAVING) {
+                       /* Overwrite the reason code */
+                       reason_code = WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED;
+               }
                goto skip_ies;
+       }
 
        ftie = (struct wpa_tdls_ftie *) pos;
        ftie->ie_type = WLAN_EID_FAST_BSS_TRANSITION;
@@ -677,10 +713,11 @@ int wpa_tdls_recv_teardown_notify(struct wpa_sm *sm, const u8 *addr,
                ftie->ie_len += 170;
                *pos++ = 255; /* FTIE subelem */
                *pos++ = 168; /* FTIE subelem length */
+               pos += 168;
        }
 #endif /* CONFIG_TDLS_TESTING */
        wpa_hexdump(MSG_DEBUG, "TDLS: FTIE for TDLS Teardown handshake",
-                   (u8 *) ftie, sizeof(*ftie));
+                   (u8 *) ftie, pos - (u8 *) ftie);
 
        /* compute MIC before sending */
        wpa_tdls_linkid(sm, peer, &lnkid);
@@ -694,8 +731,7 @@ 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, rbuf, pos - rbuf);
        os_free(rbuf);
 
        /* clear the Peerkey statemachine */
@@ -705,6 +741,50 @@ skip_ies:
 }
 
 
+int wpa_tdls_teardown_link(struct wpa_sm *sm, const u8 *addr, u16 reason_code)
+{
+       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 == NULL) {
+               wpa_printf(MSG_DEBUG, "TDLS: Could not find peer " MACSTR
+                  " for link Teardown", MAC2STR(addr));
+               return -1;
+       }
+
+       if (!peer->tpk_success) {
+               wpa_printf(MSG_DEBUG, "TDLS: Peer " MACSTR
+                  " not connected - cannot Teardown link", MAC2STR(addr));
+               return -1;
+       }
+
+       return wpa_tdls_do_teardown(sm, peer, reason_code, 0);
+}
+
+
+void wpa_tdls_disable_link(struct wpa_sm *sm, const u8 *addr)
+{
+       struct wpa_tdls_peer *peer;
+
+       for (peer = sm->tdls; peer; peer = peer->next) {
+               if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0)
+                       break;
+       }
+
+       if (peer) {
+               wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, addr);
+               wpa_tdls_peer_free(sm, peer);
+       }
+}
+
+
 static int wpa_tdls_recv_teardown(struct wpa_sm *sm, const u8 *src_addr,
                                  const u8 *buf, size_t len)
 {
@@ -802,6 +882,36 @@ static int wpa_tdls_send_error(struct wpa_sm *sm, const u8 *dst,
 }
 
 
+static struct wpa_tdls_peer *
+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));
+
+       peer = os_zalloc(sizeof(*peer));
+       if (peer == NULL)
+               return NULL;
+
+       os_memcpy(peer->addr, addr, ETH_ALEN);
+       peer->next = sm->tdls;
+       sm->tdls = peer;
+
+       return peer;
+}
+
+
 static int wpa_tdls_send_tpk_m1(struct wpa_sm *sm,
                                struct wpa_tdls_peer *peer)
 {
@@ -972,7 +1082,7 @@ skip_ies:
                   "Handshake Message 1 (peer " MACSTR ")",
                   MAC2STR(peer->addr));
 
-       wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_SETUP_REQUEST, 0, 0,
+       wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_SETUP_REQUEST, 1, 0,
                          rbuf, pos - rbuf);
        os_free(rbuf);
 
@@ -1144,6 +1254,146 @@ skip_ies:
 }
 
 
+static int wpa_tdls_send_discovery_response(struct wpa_sm *sm,
+                                           struct wpa_tdls_peer *peer,
+                                           u8 dialog_token)
+{
+       wpa_printf(MSG_DEBUG, "TDLS: Sending TDLS Discovery Response "
+                  "(peer " MACSTR ")", MAC2STR(peer->addr));
+
+       return wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_DISCOVERY_RESPONSE,
+                                dialog_token, 0, NULL, 0);
+}
+
+
+static int
+wpa_tdls_process_discovery_request(struct wpa_sm *sm, const u8 *addr,
+                                  const u8 *buf, size_t len)
+{
+       struct wpa_eapol_ie_parse kde;
+       const struct wpa_tdls_lnkid *lnkid;
+       struct wpa_tdls_peer *peer;
+       size_t min_req_len = sizeof(struct wpa_tdls_frame) +
+               1 /* dialog token */ + sizeof(struct wpa_tdls_lnkid);
+       u8 dialog_token;
+
+       wpa_printf(MSG_DEBUG, "TDLS: Discovery Request from " MACSTR,
+                  MAC2STR(addr));
+
+       if (len < min_req_len) {
+               wpa_printf(MSG_DEBUG, "TDLS Discovery Request is too short: "
+                          "%d", (int) len);
+               return -1;
+       }
+
+       dialog_token = buf[sizeof(struct wpa_tdls_frame)];
+
+       if (wpa_supplicant_parse_ies(buf + sizeof(struct wpa_tdls_frame) + 1,
+                                    len - (sizeof(struct wpa_tdls_frame) + 1),
+                                    &kde) < 0)
+               return -1;
+
+       if (!kde.lnkid) {
+               wpa_printf(MSG_DEBUG, "TDLS: Link ID not found in Discovery "
+                          "Request");
+               return -1;
+       }
+
+       lnkid = (const struct wpa_tdls_lnkid *) kde.lnkid;
+
+       if (os_memcmp(sm->bssid, lnkid->bssid, ETH_ALEN) != 0) {
+               wpa_printf(MSG_DEBUG, "TDLS: Discovery Request from different "
+                          " BSS " MACSTR, MAC2STR(lnkid->bssid));
+               return -1;
+       }
+
+       peer = wpa_tdls_add_peer(sm, addr, NULL);
+       if (peer == NULL)
+               return -1;
+
+       return wpa_tdls_send_discovery_response(sm, peer, dialog_token);
+}
+
+
+int wpa_tdls_send_discovery_request(struct wpa_sm *sm, const u8 *addr)
+{
+       if (sm->tdls_disabled || !sm->tdls_supported)
+               return -1;
+
+       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);
+}
+
+
+static int copy_supp_rates(const struct wpa_eapol_ie_parse *kde,
+                          struct wpa_tdls_peer *peer)
+{
+       if (!kde->supp_rates) {
+               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 + 2, 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_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 wpa_tdls_process_tpk_m1(struct wpa_sm *sm, const u8 *src_addr,
                                   const u8 *buf, size_t len)
 {
@@ -1166,6 +1416,7 @@ static int wpa_tdls_process_tpk_m1(struct wpa_sm *sm, const u8 *src_addr,
        u16 ielen;
        u16 status = WLAN_STATUS_UNSPECIFIED_FAILURE;
        int tdls_prohibited = sm->tdls_prohibited;
+       int existing_peer = 0;
 
        if (len < 3 + 3)
                return -1;
@@ -1178,7 +1429,13 @@ 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);
 
-       cpos += 2; /* capability information */
+       peer = wpa_tdls_add_peer(sm, src_addr, &existing_peer);
+       if (peer == NULL)
+               goto error;
+
+       /* capability information */
+       peer->capability = WPA_GET_LE16(cpos);
+       cpos += 2;
 
        ielen = len - (cpos - buf); /* start of IE in buf */
        if (wpa_supplicant_parse_ies(cpos, ielen, &kde) < 0) {
@@ -1203,20 +1460,22 @@ static int wpa_tdls_process_tpk_m1(struct wpa_sm *sm, const u8 *src_addr,
        wpa_printf(MSG_DEBUG, "TDLS: TPK M1 - TPK initiator " MACSTR,
                   MAC2STR(src_addr));
 
+       if (copy_supp_rates(&kde, peer) < 0)
+               goto error;
+
+       if (copy_peer_ht_capab(&kde, peer) < 0)
+               goto error;
+
+       if (copy_peer_ext_capab(&kde, peer) < 0)
+               goto error;
+
+       peer->qos_info = kde.qosinfo;
+
 #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 = os_zalloc(sizeof(*peer));
-                       if (peer == NULL)
-                               goto error;
-                       os_memcpy(peer->addr, src_addr, ETH_ALEN);
-                       peer->next = sm->tdls;
-                       sm->tdls = peer;
-               }
+               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;
@@ -1302,26 +1561,10 @@ static int wpa_tdls_process_tpk_m1(struct wpa_sm *sm, const u8 *src_addr,
        }
 
 skip_rsn:
-       /* Find existing entry and if found, use that instead of adding
-        * a new one; how to handle the case where both ends initiate at the
+       /* If found, use existing entry instead of adding a new one;
+        * how to handle the case where both ends initiate at the
         * same time? */
-       for (peer = sm->tdls; peer; peer = peer->next) {
-               if (os_memcmp(peer->addr, src_addr, ETH_ALEN) == 0)
-                       break;
-       }
-
-       if (peer == NULL) {
-               wpa_printf(MSG_INFO, "TDLS: No matching entry found for "
-                          "peer, creating one for " MACSTR,
-                          MAC2STR(src_addr));
-               peer = os_malloc(sizeof(*peer));
-               if (peer == NULL)
-                       goto error;
-               os_memset(peer, 0, sizeof(*peer));
-               os_memcpy(peer->addr, src_addr, ETH_ALEN);
-               peer->next = sm->tdls;
-               sm->tdls = peer;
-       } else {
+       if (existing_peer) {
                if (peer->tpk_success) {
                        wpa_printf(MSG_DEBUG, "TDLS: TDLS Setup Request while "
                                   "direct link is enabled - tear down the "
@@ -1332,7 +1575,11 @@ skip_rsn:
                         * some drivers handling the new request frame. */
                        wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, src_addr);
 #else
-                       wpa_tdls_del_key(sm, peer);
+                       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);
                }
@@ -1360,6 +1607,19 @@ skip_rsn:
                }
        }
 
+#ifdef CONFIG_TDLS_TESTING
+       if (tdls_testing & TDLS_TESTING_CONCURRENT_INIT) {
+               if (os_memcmp(sm->own_addr, peer->addr, ETH_ALEN) < 0) {
+                       /*
+                        * The request frame from us is going to win, so do not
+                        * replace information based on this request frame from
+                        * the peer.
+                        */
+                       goto skip_rsn_check;
+               }
+       }
+#endif /* CONFIG_TDLS_TESTING */
+
        peer->initiator = 0; /* Need to check */
        peer->dtoken = dtoken;
 
@@ -1433,8 +1693,15 @@ 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, NULL, 0,
+                               NULL, 0);
+
        wpa_printf(MSG_DEBUG, "TDLS: Sending TDLS Setup Response / TPK M2");
-       wpa_tdls_send_tpk_m2(sm, src_addr, dtoken, lnkid, peer);
+       if (wpa_tdls_send_tpk_m2(sm, src_addr, dtoken, lnkid, peer) < 0) {
+               wpa_tdls_disable_link(sm, peer->addr);
+               goto error;
+       }
 
        return 0;
 
@@ -1467,6 +1734,13 @@ static void 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 */
+       wpa_sm_tdls_peer_addset(sm, peer->addr, 0, peer->capability,
+                               peer->supp_rates, peer->supp_rates_len,
+                               peer->ht_capabilities, peer->qos_info,
+                               peer->ext_capab, peer->ext_capab_len);
+
        wpa_sm_tdls_oper(sm, TDLS_ENABLE_LINK, peer->addr);
 }
 
@@ -1510,6 +1784,8 @@ 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);
                return -1;
        }
 
@@ -1522,7 +1798,10 @@ static int wpa_tdls_process_tpk_m2(struct wpa_sm *sm, const u8 *src_addr,
 
        if (len < 3 + 2 + 1 + 2)
                return -1;
-       pos += 2; /* capability information */
+
+       /* 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) {
@@ -1553,6 +1832,17 @@ static int wpa_tdls_process_tpk_m2(struct wpa_sm *sm, const u8 *src_addr,
                goto error;
        }
 
+       if (copy_supp_rates(&kde, peer) < 0)
+               goto error;
+
+       if (copy_peer_ht_capab(&kde, peer) < 0)
+               goto error;
+
+       if (copy_peer_ext_capab(&kde, peer) < 0)
+               goto error;
+
+       peer->qos_info = kde.qosinfo;
+
        if (!wpa_tdls_get_privacy(sm)) {
                peer->rsnie_p_len = 0;
                peer->cipher = WPA_CIPHER_NONE;
@@ -1644,6 +1934,8 @@ static int wpa_tdls_process_tpk_m2(struct wpa_sm *sm, const u8 *src_addr,
                /* 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);
                return -1;
        }
 
@@ -1663,6 +1955,8 @@ skip_rsn:
 error:
        wpa_tdls_send_error(sm, src_addr, WLAN_TDLS_SETUP_CONFIRM, dtoken,
                            status);
+       if (sm->tdls_external_setup)
+               wpa_sm_tdls_oper(sm, TDLS_DISABLE_LINK, src_addr);
        return -1;
 }
 
@@ -1703,6 +1997,8 @@ 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;
        }
        pos += 2 /* status code */ + 1 /* dialog token */;
@@ -1775,6 +2071,8 @@ 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;
        }
 
@@ -1823,7 +2121,7 @@ int wpa_tdls_start(struct wpa_sm *sm, const u8 *addr)
        struct wpa_tdls_peer *peer;
        int tdls_prohibited = sm->tdls_prohibited;
 
-       if (sm->tdls_disabled)
+       if (sm->tdls_disabled || !sm->tdls_supported)
                return -1;
 
 #ifdef CONFIG_TDLS_TESTING
@@ -1841,37 +2139,31 @@ 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;
-       }
-
-       if (peer == NULL) {
-               wpa_printf(MSG_INFO, "TDLS: No matching entry found for "
-                          "peer, creating one for " MACSTR, MAC2STR(addr));
-               peer = os_malloc(sizeof(*peer));
-               if (peer == NULL)
-                       return -1;
-               os_memset(peer, 0, sizeof(*peer));
-               os_memcpy(peer->addr, addr, ETH_ALEN);
-               peer->next = sm->tdls;
-               sm->tdls = peer;
-       }
+       peer = wpa_tdls_add_peer(sm, addr, NULL);
+       if (peer == NULL)
+               return -1;
 
        peer->initiator = 1;
 
-       return wpa_tdls_send_tpk_m1(sm, peer);
+       /* add the peer to the driver as a "setup in progress" peer */
+       wpa_sm_tdls_peer_addset(sm, peer->addr, 1, 0, NULL, 0, NULL, 0,
+                               NULL, 0);
+
+       if (wpa_tdls_send_tpk_m1(sm, peer) < 0) {
+               wpa_tdls_disable_link(sm, peer->addr);
+               return -1;
+       }
+
+       return 0;
 }
 
 
-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)
-               return -1;
+       if (sm->tdls_disabled || !sm->tdls_supported)
+               return;
 
        for (peer = sm->tdls; peer; peer = peer->next) {
                if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0)
@@ -1879,9 +2171,15 @@ int wpa_tdls_reneg(struct wpa_sm *sm, const u8 *addr)
        }
 
        if (peer == NULL || !peer->tpk_success)
-               return -1;
+               return;
 
-       return wpa_tdls_start(sm, addr);
+       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);
+       }
 }
 
 
@@ -1899,8 +2197,9 @@ static void wpa_supplicant_rx_tdls(void *ctx, const u8 *src_addr,
        wpa_hexdump(MSG_DEBUG, "TDLS: Received Data frame encapsulation",
                    buf, len);
 
-       if (sm->tdls_disabled) {
-               wpa_printf(MSG_DEBUG, "TDLS: Discard message - TDLS disabled");
+       if (sm->tdls_disabled || !sm->tdls_supported) {
+               wpa_printf(MSG_DEBUG, "TDLS: Discard message - TDLS disabled "
+                          "or unsupported by driver");
                return;
        }
 
@@ -1937,6 +2236,9 @@ static void wpa_supplicant_rx_tdls(void *ctx, const u8 *src_addr,
        case WLAN_TDLS_TEARDOWN:
                wpa_tdls_recv_teardown(sm, src_addr, buf, len);
                break;
+       case WLAN_TDLS_DISCOVERY_REQUEST:
+               wpa_tdls_process_discovery_request(sm, src_addr, buf, len);
+               break;
        default:
                /* Kernel code will process remaining frames */
                wpa_printf(MSG_DEBUG, "TDLS: Ignore TDLS frame action code %u",
@@ -1960,7 +2262,9 @@ int wpa_tdls_init(struct wpa_sm *sm)
        if (sm == NULL)
                return -1;
 
-       sm->l2_tdls = l2_packet_init(sm->ifname, sm->own_addr,
+       sm->l2_tdls = l2_packet_init(sm->bridge_ifname ? sm->bridge_ifname :
+                                    sm->ifname,
+                                    sm->own_addr,
                                     ETH_P_80211_ENCAP, wpa_supplicant_rx_tdls,
                                     sm, 0);
        if (sm->l2_tdls == NULL) {
@@ -1969,10 +2273,47 @@ int wpa_tdls_init(struct wpa_sm *sm)
                return -1;
        }
 
+       /*
+        * Drivers that support TDLS but don't implement the get_capa callback
+        * are assumed to perform everything internally
+        */
+       if (wpa_sm_tdls_get_capa(sm, &sm->tdls_supported,
+                                &sm->tdls_external_setup) < 0) {
+               sm->tdls_supported = 1;
+               sm->tdls_external_setup = 0;
+       }
+
+       wpa_printf(MSG_DEBUG, "TDLS: TDLS operation%s supported by "
+                  "driver", sm->tdls_supported ? "" : " not");
+       wpa_printf(MSG_DEBUG, "TDLS: Driver uses %s link setup",
+                  sm->tdls_external_setup ? "external" : "internal");
+
        return 0;
 }
 
 
+void wpa_tdls_teardown_peers(struct wpa_sm *sm)
+{
+       struct wpa_tdls_peer *peer;
+
+       peer = sm->tdls;
+
+       wpa_printf(MSG_DEBUG, "TDLS: Tear down peers");
+
+       while (peer) {
+               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);
+               else
+                       wpa_sm_tdls_oper(sm, TDLS_TEARDOWN, peer->addr);
+
+               peer = peer->next;
+       }
+}
+
+
 static void wpa_tdls_remove_peers(struct wpa_sm *sm)
 {
        struct wpa_tdls_peer *peer, *tmp;
@@ -2067,3 +2408,9 @@ void wpa_tdls_enable(struct wpa_sm *sm, int enabled)
        wpa_printf(MSG_DEBUG, "TDLS: %s", enabled ? "enabled" : "disabled");
        sm->tdls_disabled = !enabled;
 }
+
+
+int wpa_tdls_is_external_setup(struct wpa_sm *sm)
+{
+       return sm->tdls_external_setup;
+}