cfg80211/mac80211: extensible frame processing
[linux-2.6.git] / net / wireless / mlme.c
index 62bc885..8515b1e 100644 (file)
@@ -8,6 +8,7 @@
 #include <linux/module.h>
 #include <linux/netdevice.h>
 #include <linux/nl80211.h>
+#include <linux/slab.h>
 #include <linux/wireless.h>
 #include <net/cfg80211.h>
 #include <net/iw_handler.h>
@@ -43,10 +44,10 @@ void cfg80211_send_rx_auth(struct net_device *dev, const u8 *buf, size_t len)
                }
        }
 
-       WARN_ON(!done);
-
-       nl80211_send_rx_auth(rdev, dev, buf, len, GFP_KERNEL);
-       cfg80211_sme_rx_auth(dev, buf, len);
+       if (done) {
+               nl80211_send_rx_auth(rdev, dev, buf, len, GFP_KERNEL);
+               cfg80211_sme_rx_auth(dev, buf, len);
+       }
 
        wdev_unlock(wdev);
 }
@@ -148,7 +149,7 @@ void __cfg80211_send_deauth(struct net_device *dev,
        struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)buf;
        const u8 *bssid = mgmt->bssid;
        int i;
-       bool found = false;
+       bool found = false, was_current = false;
 
        ASSERT_WDEV_LOCK(wdev);
 
@@ -158,6 +159,7 @@ void __cfg80211_send_deauth(struct net_device *dev,
                cfg80211_put_bss(&wdev->current_bss->pub);
                wdev->current_bss = NULL;
                found = true;
+               was_current = true;
        } else for (i = 0; i < MAX_AUTH_BSSES; i++) {
                if (wdev->auth_bsses[i] &&
                    memcmp(wdev->auth_bsses[i]->pub.bssid, bssid, ETH_ALEN) == 0) {
@@ -182,7 +184,7 @@ void __cfg80211_send_deauth(struct net_device *dev,
 
        nl80211_send_deauth(rdev, dev, buf, len, GFP_KERNEL);
 
-       if (wdev->sme_state == CFG80211_SME_CONNECTED) {
+       if (wdev->sme_state == CFG80211_SME_CONNECTED && was_current) {
                u16 reason_code;
                bool from_ap;
 
@@ -377,7 +379,8 @@ int __cfg80211_mlme_auth(struct cfg80211_registered_device *rdev,
                         const u8 *bssid,
                         const u8 *ssid, int ssid_len,
                         const u8 *ie, int ie_len,
-                        const u8 *key, int key_len, int key_idx)
+                        const u8 *key, int key_len, int key_idx,
+                        bool local_state_change)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        struct cfg80211_auth_request req;
@@ -407,6 +410,7 @@ int __cfg80211_mlme_auth(struct cfg80211_registered_device *rdev,
 
        memset(&req, 0, sizeof(req));
 
+       req.local_state_change = local_state_change;
        req.ie = ie;
        req.ie_len = ie_len;
        req.auth_type = auth_type;
@@ -433,12 +437,18 @@ int __cfg80211_mlme_auth(struct cfg80211_registered_device *rdev,
                goto out;
        }
 
-       wdev->authtry_bsses[slot] = bss;
+       if (local_state_change)
+               wdev->auth_bsses[slot] = bss;
+       else
+               wdev->authtry_bsses[slot] = bss;
        cfg80211_hold_bss(bss);
 
        err = rdev->ops->auth(&rdev->wiphy, dev, &req);
        if (err) {
-               wdev->authtry_bsses[slot] = NULL;
+               if (local_state_change)
+                       wdev->auth_bsses[slot] = NULL;
+               else
+                       wdev->authtry_bsses[slot] = NULL;
                cfg80211_unhold_bss(bss);
        }
 
@@ -453,14 +463,15 @@ int cfg80211_mlme_auth(struct cfg80211_registered_device *rdev,
                       enum nl80211_auth_type auth_type, const u8 *bssid,
                       const u8 *ssid, int ssid_len,
                       const u8 *ie, int ie_len,
-                      const u8 *key, int key_len, int key_idx)
+                      const u8 *key, int key_len, int key_idx,
+                      bool local_state_change)
 {
        int err;
 
        wdev_lock(dev->ieee80211_ptr);
        err = __cfg80211_mlme_auth(rdev, dev, chan, auth_type, bssid,
                                   ssid, ssid_len, ie, ie_len,
-                                  key, key_len, key_idx);
+                                  key, key_len, key_idx, local_state_change);
        wdev_unlock(dev->ieee80211_ptr);
 
        return err;
@@ -554,7 +565,8 @@ int cfg80211_mlme_assoc(struct cfg80211_registered_device *rdev,
 
 int __cfg80211_mlme_deauth(struct cfg80211_registered_device *rdev,
                           struct net_device *dev, const u8 *bssid,
-                          const u8 *ie, int ie_len, u16 reason)
+                          const u8 *ie, int ie_len, u16 reason,
+                          bool local_state_change)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        struct cfg80211_deauth_request req;
@@ -564,6 +576,7 @@ int __cfg80211_mlme_deauth(struct cfg80211_registered_device *rdev,
 
        memset(&req, 0, sizeof(req));
        req.reason_code = reason;
+       req.local_state_change = local_state_change;
        req.ie = ie;
        req.ie_len = ie_len;
        if (wdev->current_bss &&
@@ -590,13 +603,15 @@ int __cfg80211_mlme_deauth(struct cfg80211_registered_device *rdev,
 
 int cfg80211_mlme_deauth(struct cfg80211_registered_device *rdev,
                         struct net_device *dev, const u8 *bssid,
-                        const u8 *ie, int ie_len, u16 reason)
+                        const u8 *ie, int ie_len, u16 reason,
+                        bool local_state_change)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        int err;
 
        wdev_lock(wdev);
-       err = __cfg80211_mlme_deauth(rdev, dev, bssid, ie, ie_len, reason);
+       err = __cfg80211_mlme_deauth(rdev, dev, bssid, ie, ie_len, reason,
+                                    local_state_change);
        wdev_unlock(wdev);
 
        return err;
@@ -604,7 +619,8 @@ int cfg80211_mlme_deauth(struct cfg80211_registered_device *rdev,
 
 static int __cfg80211_mlme_disassoc(struct cfg80211_registered_device *rdev,
                                    struct net_device *dev, const u8 *bssid,
-                                   const u8 *ie, int ie_len, u16 reason)
+                                   const u8 *ie, int ie_len, u16 reason,
+                                   bool local_state_change)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        struct cfg80211_disassoc_request req;
@@ -619,6 +635,7 @@ static int __cfg80211_mlme_disassoc(struct cfg80211_registered_device *rdev,
 
        memset(&req, 0, sizeof(req));
        req.reason_code = reason;
+       req.local_state_change = local_state_change;
        req.ie = ie;
        req.ie_len = ie_len;
        if (memcmp(wdev->current_bss->pub.bssid, bssid, ETH_ALEN) == 0)
@@ -631,13 +648,15 @@ static int __cfg80211_mlme_disassoc(struct cfg80211_registered_device *rdev,
 
 int cfg80211_mlme_disassoc(struct cfg80211_registered_device *rdev,
                           struct net_device *dev, const u8 *bssid,
-                          const u8 *ie, int ie_len, u16 reason)
+                          const u8 *ie, int ie_len, u16 reason,
+                          bool local_state_change)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        int err;
 
        wdev_lock(wdev);
-       err = __cfg80211_mlme_disassoc(rdev, dev, bssid, ie, ie_len, reason);
+       err = __cfg80211_mlme_disassoc(rdev, dev, bssid, ie, ie_len, reason,
+                                      local_state_change);
        wdev_unlock(wdev);
 
        return err;
@@ -729,31 +748,51 @@ void cfg80211_new_sta(struct net_device *dev, const u8 *mac_addr,
 }
 EXPORT_SYMBOL(cfg80211_new_sta);
 
-struct cfg80211_action_registration {
+struct cfg80211_mgmt_registration {
        struct list_head list;
 
        u32 nlpid;
 
        int match_len;
 
+       __le16 frame_type;
+
        u8 match[];
 };
 
-int cfg80211_mlme_register_action(struct wireless_dev *wdev, u32 snd_pid,
-                                 const u8 *match_data, int match_len)
+int cfg80211_mlme_register_mgmt(struct wireless_dev *wdev, u32 snd_pid,
+                               u16 frame_type, const u8 *match_data,
+                               int match_len)
 {
-       struct cfg80211_action_registration *reg, *nreg;
+       struct cfg80211_mgmt_registration *reg, *nreg;
        int err = 0;
+       u16 mgmt_type;
+
+       if (!wdev->wiphy->mgmt_stypes)
+               return -EOPNOTSUPP;
+
+       if ((frame_type & IEEE80211_FCTL_FTYPE) != IEEE80211_FTYPE_MGMT)
+               return -EINVAL;
+
+       if (frame_type & ~(IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE))
+               return -EINVAL;
+
+       mgmt_type = (frame_type & IEEE80211_FCTL_STYPE) >> 4;
+       if (!(wdev->wiphy->mgmt_stypes[wdev->iftype].rx & BIT(mgmt_type)))
+               return -EINVAL;
 
        nreg = kzalloc(sizeof(*reg) + match_len, GFP_KERNEL);
        if (!nreg)
                return -ENOMEM;
 
-       spin_lock_bh(&wdev->action_registrations_lock);
+       spin_lock_bh(&wdev->mgmt_registrations_lock);
 
-       list_for_each_entry(reg, &wdev->action_registrations, list) {
+       list_for_each_entry(reg, &wdev->mgmt_registrations, list) {
                int mlen = min(match_len, reg->match_len);
 
+               if (frame_type != le16_to_cpu(reg->frame_type))
+                       continue;
+
                if (memcmp(reg->match, match_data, mlen) == 0) {
                        err = -EALREADY;
                        break;
@@ -768,129 +807,174 @@ int cfg80211_mlme_register_action(struct wireless_dev *wdev, u32 snd_pid,
        memcpy(nreg->match, match_data, match_len);
        nreg->match_len = match_len;
        nreg->nlpid = snd_pid;
-       list_add(&nreg->list, &wdev->action_registrations);
+       nreg->frame_type = cpu_to_le16(frame_type);
+       list_add(&nreg->list, &wdev->mgmt_registrations);
 
  out:
-       spin_unlock_bh(&wdev->action_registrations_lock);
+       spin_unlock_bh(&wdev->mgmt_registrations_lock);
        return err;
 }
 
-void cfg80211_mlme_unregister_actions(struct wireless_dev *wdev, u32 nlpid)
+void cfg80211_mlme_unregister_socket(struct wireless_dev *wdev, u32 nlpid)
 {
-       struct cfg80211_action_registration *reg, *tmp;
+       struct cfg80211_mgmt_registration *reg, *tmp;
 
-       spin_lock_bh(&wdev->action_registrations_lock);
+       spin_lock_bh(&wdev->mgmt_registrations_lock);
 
-       list_for_each_entry_safe(reg, tmp, &wdev->action_registrations, list) {
+       list_for_each_entry_safe(reg, tmp, &wdev->mgmt_registrations, list) {
                if (reg->nlpid == nlpid) {
                        list_del(&reg->list);
                        kfree(reg);
                }
        }
 
-       spin_unlock_bh(&wdev->action_registrations_lock);
+       spin_unlock_bh(&wdev->mgmt_registrations_lock);
 }
 
-void cfg80211_mlme_purge_actions(struct wireless_dev *wdev)
+void cfg80211_mlme_purge_registrations(struct wireless_dev *wdev)
 {
-       struct cfg80211_action_registration *reg, *tmp;
+       struct cfg80211_mgmt_registration *reg, *tmp;
 
-       spin_lock_bh(&wdev->action_registrations_lock);
+       spin_lock_bh(&wdev->mgmt_registrations_lock);
 
-       list_for_each_entry_safe(reg, tmp, &wdev->action_registrations, list) {
+       list_for_each_entry_safe(reg, tmp, &wdev->mgmt_registrations, list) {
                list_del(&reg->list);
                kfree(reg);
        }
 
-       spin_unlock_bh(&wdev->action_registrations_lock);
+       spin_unlock_bh(&wdev->mgmt_registrations_lock);
 }
 
-int cfg80211_mlme_action(struct cfg80211_registered_device *rdev,
-                        struct net_device *dev,
-                        struct ieee80211_channel *chan,
-                        enum nl80211_channel_type channel_type,
-                        const u8 *buf, size_t len, u64 *cookie)
+int cfg80211_mlme_mgmt_tx(struct cfg80211_registered_device *rdev,
+                         struct net_device *dev,
+                         struct ieee80211_channel *chan,
+                         enum nl80211_channel_type channel_type,
+                         bool channel_type_valid,
+                         const u8 *buf, size_t len, u64 *cookie)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        const struct ieee80211_mgmt *mgmt;
+       u16 stype;
 
-       if (rdev->ops->action == NULL)
+       if (!wdev->wiphy->mgmt_stypes)
                return -EOPNOTSUPP;
+
+       if (!rdev->ops->mgmt_tx)
+               return -EOPNOTSUPP;
+
        if (len < 24 + 1)
                return -EINVAL;
 
        mgmt = (const struct ieee80211_mgmt *) buf;
-       if (!ieee80211_is_action(mgmt->frame_control))
+
+       if (!ieee80211_is_mgmt(mgmt->frame_control))
+               return -EINVAL;
+
+       stype = le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_STYPE;
+       if (!(wdev->wiphy->mgmt_stypes[wdev->iftype].tx & BIT(stype >> 4)))
                return -EINVAL;
-       if (mgmt->u.action.category != WLAN_CATEGORY_PUBLIC) {
+
+       if (ieee80211_is_action(mgmt->frame_control) &&
+           mgmt->u.action.category != WLAN_CATEGORY_PUBLIC) {
                /* Verify that we are associated with the destination AP */
+               wdev_lock(wdev);
+
                if (!wdev->current_bss ||
                    memcmp(wdev->current_bss->pub.bssid, mgmt->bssid,
                           ETH_ALEN) != 0 ||
-                   memcmp(wdev->current_bss->pub.bssid, mgmt->da,
-                          ETH_ALEN) != 0)
+                   (wdev->iftype == NL80211_IFTYPE_STATION &&
+                    memcmp(wdev->current_bss->pub.bssid, mgmt->da,
+                           ETH_ALEN) != 0)) {
+                       wdev_unlock(wdev);
                        return -ENOTCONN;
+               }
+
+               wdev_unlock(wdev);
        }
 
        if (memcmp(mgmt->sa, dev->dev_addr, ETH_ALEN) != 0)
                return -EINVAL;
 
        /* Transmit the Action frame as requested by user space */
-       return rdev->ops->action(&rdev->wiphy, dev, chan, channel_type,
-                                buf, len, cookie);
+       return rdev->ops->mgmt_tx(&rdev->wiphy, dev, chan, channel_type,
+                                 channel_type_valid, buf, len, cookie);
 }
 
-bool cfg80211_rx_action(struct net_device *dev, int freq, const u8 *buf,
-                       size_t len, gfp_t gfp)
+bool cfg80211_rx_mgmt(struct net_device *dev, int freq, const u8 *buf,
+                     size_t len, gfp_t gfp)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        struct wiphy *wiphy = wdev->wiphy;
        struct cfg80211_registered_device *rdev = wiphy_to_dev(wiphy);
-       struct cfg80211_action_registration *reg;
-       const u8 *action_data;
-       int action_data_len;
+       struct cfg80211_mgmt_registration *reg;
+       const struct ieee80211_txrx_stypes *stypes =
+               &wiphy->mgmt_stypes[wdev->iftype];
+       struct ieee80211_mgmt *mgmt = (void *)buf;
+       const u8 *data;
+       int data_len;
        bool result = false;
+       __le16 ftype = mgmt->frame_control &
+               cpu_to_le16(IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE);
+       u16 stype;
+
+       stype = (le16_to_cpu(mgmt->frame_control) & IEEE80211_FCTL_STYPE) >> 4;
 
-       /* frame length - min size excluding category */
-       action_data_len = len - (IEEE80211_MIN_ACTION_SIZE - 1);
+       if (!(stypes->rx & BIT(stype)))
+               return false;
 
-       /* action data starts with category */
-       action_data = buf + IEEE80211_MIN_ACTION_SIZE - 1;
+       data = buf + ieee80211_hdrlen(mgmt->frame_control);
+       data_len = len - ieee80211_hdrlen(mgmt->frame_control);
 
-       spin_lock_bh(&wdev->action_registrations_lock);
+       spin_lock_bh(&wdev->mgmt_registrations_lock);
 
-       list_for_each_entry(reg, &wdev->action_registrations, list) {
-               if (reg->match_len > action_data_len)
+       list_for_each_entry(reg, &wdev->mgmt_registrations, list) {
+               if (reg->frame_type != ftype)
                        continue;
 
-               if (memcmp(reg->match, action_data, reg->match_len))
+               if (reg->match_len > data_len)
+                       continue;
+
+               if (memcmp(reg->match, data, reg->match_len))
                        continue;
 
                /* found match! */
 
                /* Indicate the received Action frame to user space */
-               if (nl80211_send_action(rdev, dev, reg->nlpid, freq,
-                                       buf, len, gfp))
+               if (nl80211_send_mgmt(rdev, dev, reg->nlpid, freq,
+                                     buf, len, gfp))
                        continue;
 
                result = true;
                break;
        }
 
-       spin_unlock_bh(&wdev->action_registrations_lock);
+       spin_unlock_bh(&wdev->mgmt_registrations_lock);
 
        return result;
 }
-EXPORT_SYMBOL(cfg80211_rx_action);
+EXPORT_SYMBOL(cfg80211_rx_mgmt);
 
-void cfg80211_action_tx_status(struct net_device *dev, u64 cookie,
-                              const u8 *buf, size_t len, bool ack, gfp_t gfp)
+void cfg80211_mgmt_tx_status(struct net_device *dev, u64 cookie,
+                            const u8 *buf, size_t len, bool ack, gfp_t gfp)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        struct wiphy *wiphy = wdev->wiphy;
        struct cfg80211_registered_device *rdev = wiphy_to_dev(wiphy);
 
        /* Indicate TX status of the Action frame to user space */
-       nl80211_send_action_tx_status(rdev, dev, cookie, buf, len, ack, gfp);
+       nl80211_send_mgmt_tx_status(rdev, dev, cookie, buf, len, ack, gfp);
+}
+EXPORT_SYMBOL(cfg80211_mgmt_tx_status);
+
+void cfg80211_cqm_rssi_notify(struct net_device *dev,
+                             enum nl80211_cqm_rssi_threshold_event rssi_event,
+                             gfp_t gfp)
+{
+       struct wireless_dev *wdev = dev->ieee80211_ptr;
+       struct wiphy *wiphy = wdev->wiphy;
+       struct cfg80211_registered_device *rdev = wiphy_to_dev(wiphy);
+
+       /* Indicate roaming trigger event to user space */
+       nl80211_send_cqm_rssi_notify(rdev, dev, rssi_event, gfp);
 }
-EXPORT_SYMBOL(cfg80211_action_tx_status);
+EXPORT_SYMBOL(cfg80211_cqm_rssi_notify);