mac80211: cleanup TDLS state during failed setup
authorArik Nemtsov <arik@wizery.com>
Wed, 11 Jun 2014 14:18:20 +0000 (17:18 +0300)
committerJohannes Berg <johannes.berg@intel.com>
Mon, 23 Jun 2014 12:24:55 +0000 (14:24 +0200)
When setting up a TDLS session, register a delayed work to remove
the peer if setup times out. Prevent concurrent setups to support this
capacity.

Signed-off-by: Arik Nemtsov <arikx.nemtsov@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
net/mac80211/ieee80211_i.h
net/mac80211/iface.c
net/mac80211/tdls.c

index 1fd50f1..fc687d2 100644 (file)
@@ -794,6 +794,9 @@ struct ieee80211_sub_if_data {
        bool radar_required;
        struct delayed_work dfs_cac_timer_work;
 
+       u8 tdls_peer[ETH_ALEN] __aligned(2);
+       struct delayed_work tdls_peer_del_work;
+
        /*
         * AP this belongs to: self in AP mode and
         * corresponding AP in VLAN mode, NULL for
@@ -1878,3 +1881,4 @@ extern const struct ethtool_ops ieee80211_ethtool_ops;
 #endif
 
 #endif /* IEEE80211_I_H */
+void ieee80211_tdls_peer_del_work(struct work_struct *wk);
index 2a12b8a..bbf51b2 100644 (file)
@@ -1672,6 +1672,8 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name,
                          ieee80211_dfs_cac_timer_work);
        INIT_DELAYED_WORK(&sdata->dec_tailroom_needed_wk,
                          ieee80211_delayed_tailroom_dec);
+       INIT_DELAYED_WORK(&sdata->tdls_peer_del_work,
+                         ieee80211_tdls_peer_del_work);
 
        for (i = 0; i < IEEE80211_NUM_BANDS; i++) {
                struct ieee80211_supported_band *sband;
index 652813b..cafcbde 100644 (file)
 #include <linux/ieee80211.h>
 #include "ieee80211_i.h"
 
+/* give usermode some time for retries in setting up the TDLS session */
+#define TDLS_PEER_SETUP_TIMEOUT        (15 * HZ)
+
+void ieee80211_tdls_peer_del_work(struct work_struct *wk)
+{
+       struct ieee80211_sub_if_data *sdata;
+       struct ieee80211_local *local;
+
+       sdata = container_of(wk, struct ieee80211_sub_if_data,
+                            tdls_peer_del_work.work);
+       local = sdata->local;
+
+       mutex_lock(&local->mtx);
+       if (!is_zero_ether_addr(sdata->tdls_peer)) {
+               tdls_dbg(sdata, "TDLS del peer %pM\n", sdata->tdls_peer);
+               sta_info_destroy_addr(sdata, sdata->tdls_peer);
+               eth_zero_addr(sdata->tdls_peer);
+       }
+       mutex_unlock(&local->mtx);
+}
+
 static void ieee80211_tdls_add_ext_capab(struct sk_buff *skb)
 {
        u8 *pos = (void *)skb_put(skb, 7);
@@ -168,10 +189,12 @@ ieee80211_prep_tdls_direct(struct wiphy *wiphy, struct net_device *dev,
        return 0;
 }
 
-int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev,
-                       const u8 *peer, u8 action_code, u8 dialog_token,
-                       u16 status_code, u32 peer_capability,
-                       const u8 *extra_ies, size_t extra_ies_len)
+static int
+ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev,
+                               const u8 *peer, u8 action_code,
+                               u8 dialog_token, u16 status_code,
+                               u32 peer_capability, const u8 *extra_ies,
+                               size_t extra_ies_len)
 {
        struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
        struct ieee80211_local *local = sdata->local;
@@ -179,17 +202,6 @@ int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev,
        bool send_direct;
        int ret;
 
-       if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS))
-               return -ENOTSUPP;
-
-       /* make sure we are in managed mode, and associated */
-       if (sdata->vif.type != NL80211_IFTYPE_STATION ||
-           !sdata->u.mgd.associated)
-               return -EINVAL;
-
-       tdls_dbg(sdata, "TDLS mgmt action %d peer %pM\n",
-                action_code, peer);
-
        skb = dev_alloc_skb(local->hw.extra_tx_headroom +
                            max(sizeof(struct ieee80211_mgmt),
                                sizeof(struct ieee80211_tdls_data)) +
@@ -284,11 +296,64 @@ fail:
        return ret;
 }
 
+int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev,
+                       const u8 *peer, u8 action_code, u8 dialog_token,
+                       u16 status_code, u32 peer_capability,
+                       const u8 *extra_ies, size_t extra_ies_len)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct ieee80211_local *local = sdata->local;
+       int ret;
+
+       if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS))
+               return -ENOTSUPP;
+
+       /* make sure we are in managed mode, and associated */
+       if (sdata->vif.type != NL80211_IFTYPE_STATION ||
+           !sdata->u.mgd.associated)
+               return -EINVAL;
+
+       mutex_lock(&local->mtx);
+
+       /* we don't support concurrent TDLS peer setups */
+       if (!is_zero_ether_addr(sdata->tdls_peer) &&
+           !ether_addr_equal(sdata->tdls_peer, peer) &&
+           (action_code == WLAN_TDLS_SETUP_REQUEST ||
+            action_code == WLAN_TDLS_SETUP_RESPONSE)) {
+               ret = -EBUSY;
+               goto exit;
+       }
+
+       ret = ieee80211_tdls_prep_mgmt_packet(wiphy, dev, peer, action_code,
+                                             dialog_token, status_code,
+                                             peer_capability, extra_ies,
+                                             extra_ies_len);
+       if (ret < 0)
+               goto exit;
+
+       if (action_code == WLAN_TDLS_SETUP_REQUEST ||
+           action_code == WLAN_TDLS_SETUP_RESPONSE) {
+               memcpy(sdata->tdls_peer, peer, ETH_ALEN);
+               ieee80211_queue_delayed_work(&sdata->local->hw,
+                                            &sdata->tdls_peer_del_work,
+                                            TDLS_PEER_SETUP_TIMEOUT);
+       }
+
+exit:
+       mutex_unlock(&local->mtx);
+
+       tdls_dbg(sdata, "TDLS mgmt action %d peer %pM status %d\n",
+                action_code, peer, ret);
+       return ret;
+}
+
 int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev,
                        const u8 *peer, enum nl80211_tdls_operation oper)
 {
        struct sta_info *sta;
        struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct ieee80211_local *local = sdata->local;
+       int ret;
 
        if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS))
                return -ENOTSUPP;
@@ -296,6 +361,18 @@ int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev,
        if (sdata->vif.type != NL80211_IFTYPE_STATION)
                return -EINVAL;
 
+       switch (oper) {
+       case NL80211_TDLS_ENABLE_LINK:
+       case NL80211_TDLS_DISABLE_LINK:
+               break;
+       case NL80211_TDLS_TEARDOWN:
+       case NL80211_TDLS_SETUP:
+       case NL80211_TDLS_DISCOVERY_REQ:
+               /* We don't support in-driver setup/teardown/discovery */
+               return -ENOTSUPP;
+       }
+
+       mutex_lock(&local->mtx);
        tdls_dbg(sdata, "TDLS oper %d peer %pM\n", oper, peer);
 
        switch (oper) {
@@ -304,22 +381,30 @@ int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev,
                sta = sta_info_get(sdata, peer);
                if (!sta) {
                        rcu_read_unlock();
-                       return -ENOLINK;
+                       ret = -ENOLINK;
+                       break;
                }
 
                set_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH);
                rcu_read_unlock();
+
+               WARN_ON_ONCE(is_zero_ether_addr(sdata->tdls_peer) ||
+                            !ether_addr_equal(sdata->tdls_peer, peer));
+               ret = 0;
                break;
        case NL80211_TDLS_DISABLE_LINK:
-               return sta_info_destroy_addr(sdata, peer);
-       case NL80211_TDLS_TEARDOWN:
-       case NL80211_TDLS_SETUP:
-       case NL80211_TDLS_DISCOVERY_REQ:
-               /* We don't support in-driver setup/teardown/discovery */
-               return -ENOTSUPP;
+               ret = sta_info_destroy_addr(sdata, peer);
+               break;
        default:
-               return -ENOTSUPP;
+               ret = -ENOTSUPP;
+               break;
        }
 
-       return 0;
+       if (ret == 0 && ether_addr_equal(sdata->tdls_peer, peer)) {
+               cancel_delayed_work(&sdata->tdls_peer_del_work);
+               eth_zero_addr(sdata->tdls_peer);
+       }
+
+       mutex_unlock(&local->mtx);
+       return ret;
 }