[MAC80211]: rework key handling
authorJohannes Berg <johannes@sipsolutions.net>
Tue, 28 Aug 2007 21:01:55 +0000 (17:01 -0400)
committerDavid S. Miller <davem@sunset.davemloft.net>
Wed, 10 Oct 2007 23:48:53 +0000 (16:48 -0700)
This moves all the key handling code out from ieee80211_ioctl.c
into key.c and also does the following changes including documentation
updates in mac80211.h:

 1) Turn off hardware acceleration for keys when the interface
    is down. This is necessary because otherwise monitor
    interfaces could be decrypting frames for other interfaces
    that are down at the moment. Also, it should go some way
    towards better suspend/resume support, in any case the
    routines used here could be used for that as well.
    Additionally, this makes the driver interface nicer, keys
    for a specific local MAC address are only ever present
    while an interface with that MAC address is enabled.

 2) Change driver set_key() callback interface to allow only
    return values of -ENOSPC, -EOPNOTSUPP and 0, warn on all
    other return values. This allows debugging the stack when
    a driver notices it's handed a key while it is down.

 3) Invert the flag meaning to KEY_FLAG_UPLOADED_TO_HARDWARE.

 4) Remove REMOVE_ALL_KEYS command as it isn't used nor do we
    want to use it, we'll use DISABLE_KEY for each key. It is
    hard to use REMOVE_ALL_KEYS because we can handle multiple
    virtual interfaces with different key configuration, so we'd
    have to keep track of a lot of state for this and that isn't
    worth it.

 5) Warn when disabling a key fails, it musn't.

 6) Remove IEEE80211_HW_NO_TKIP_WMM_HWACCEL in favour of per-key
    IEEE80211_KEY_FLAG_WMM_STA to let driver sort it out itself.

 7) Tell driver that a (non-WEP) key is used only for transmission
    by using an all-zeroes station MAC address when configuring.

 8) Change the set_key() callback to have access to the local MAC
    address the key is being added for.

Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Acked-by: Michael Wu <flamingice@sourmilk.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
12 files changed:
include/net/mac80211.h
net/mac80211/debugfs_key.c
net/mac80211/ieee80211.c
net/mac80211/ieee80211_i.h
net/mac80211/ieee80211_iface.c
net/mac80211/ieee80211_ioctl.c
net/mac80211/ieee80211_key.h
net/mac80211/key.c
net/mac80211/rx.c
net/mac80211/sta_info.c
net/mac80211/tx.c
net/mac80211/wpa.c

index 3437fa16eea538a66e0604763f52e5c5aee7f6aa..ec8c7393956b560a2e05e6bf933e6d0f1cf71fd2 100644 (file)
@@ -399,19 +399,34 @@ typedef enum {
        ALG_CCMP,
 } ieee80211_key_alg;
 
+/*
+ * This flag indiciates that the station this key is being
+ * configured for may use QoS. If your hardware cannot handle
+ * that situation it should reject that key.
+ */
+#define IEEE80211_KEY_FLAG_WMM_STA     (1<<0)
+
 struct ieee80211_key_conf {
-       /* shall be changed by the driver to anything but HW_KEY_IDX_INVALID */
+       /*
+        * To be set by the driver to the key index it would like to
+        * get in the ieee80211_tx_control.key_idx which defaults
+        * to HW_KEY_IDX_INVALID so that shouldn't be used.
+        */
        int hw_key_idx;
 
+       /* key algorithm, ALG_NONE should never be seen by the driver */
        ieee80211_key_alg alg;
 
-       int keylen;
+       /* key flags, see above */
+       u8 flags;
+
+       /* key index: 0-3 */
+       s8 keyidx;
 
-#define IEEE80211_KEY_FORCE_SW_ENCRYPT (1<<0) /* to be cleared by low-level
-                                                driver */
-       u32 flags; /* key configuration flags defined above */
+       /* length of key material */
+       u8 keylen;
 
-       s8 keyidx;                      /* WEP key index */
+       /* the key material */
        u8 key[0];
 };
 
@@ -419,7 +434,7 @@ struct ieee80211_key_conf {
 #define IEEE80211_SEQ_COUNTER_TX       1
 
 typedef enum {
-       SET_KEY, DISABLE_KEY, REMOVE_ALL_KEYS,
+       SET_KEY, DISABLE_KEY,
 } set_key_cmd;
 
 /* This is driver-visible part of the per-hw state the stack keeps. */
@@ -492,8 +507,7 @@ struct ieee80211_hw {
 
 /* hole at 6 */
 
-       /* Force software encryption for TKIP packets if WMM is enabled. */
-#define IEEE80211_HW_NO_TKIP_WMM_HWACCEL (1<<7)
+/* hole at 7 */
 
        /*
         * Some devices handle Michael MIC internally and do not include MIC in
@@ -627,12 +641,31 @@ struct ieee80211_ops {
         *
         * This is called to enable hardware acceleration of encryption and
         * decryption. The address will be the broadcast address for default
-        * keys and the other station's hardware address for individual keys.
+        * keys, the other station's hardware address for individual keys or
+        * the zero address for keys that will be used only for transmission.
+        *
+        * The local_address parameter will always be set to our own address,
+        * this is only relevant if you support multiple local addresses.
+        *
         * When transmitting, the TX control data will use the hw_key_idx
         * selected by the low-level driver.
+        *
+        * Return 0 if the key is now in use, -EOPNOTSUPP or -ENOSPC if it
+        * couldn't be added; if you return 0 then hw_key_idx must be
+        * assigned to something other than HW_KEY_IDX_INVALID. When the cmd
+        * is DISABLE_KEY then it must succeed.
+        *
+        * This callback can sleep, and is only called between add_interface
+        * and remove_interface calls, i.e. while the interface with the
+        * given local_address is enabled.
+        *
+        * The ieee80211_key_conf structure pointed to by the key parameter
+        * is guaranteed to be valid until another call to set_key removes
+        * it, but it can only be used as a cookie to differentiate keys.
         */
        int (*set_key)(struct ieee80211_hw *hw, set_key_cmd cmd,
-                      u8 *address, struct ieee80211_key_conf *key);
+                      const u8 *local_address, const u8 *address,
+                      struct ieee80211_key_conf *key);
 
        /*
         * Set TX key index for default/broadcast keys. This is needed in cases
@@ -640,6 +673,10 @@ struct ieee80211_ops {
         * is not set), in other cases, this function pointer can be set to
         * NULL since the IEEE 802.11 module takes care of selecting the key
         * index for each TX frame.
+        *
+        * TODO: If you use this callback in your driver tell us if you need
+        *       any other information from it to make it easier, like the
+        *       key_conf instead.
         */
        int (*set_key_idx)(struct ieee80211_hw *hw, int idx);
 
index 246938c32d4df6b8a3d27c27e3cf892a9c3dc8a9..36e7812da55670c7d0a600ba1773d6ec5ecc97b1 100644 (file)
@@ -25,6 +25,7 @@ static ssize_t key_##name##_read(struct file *file,                   \
        return simple_read_from_buffer(userbuf, count, ppos, buf, res); \
 }
 #define KEY_READ_D(name) KEY_READ(name, name, 20, "%d\n")
+#define KEY_READ_X(name) KEY_READ(name, name, 20, "0x%x\n")
 
 #define KEY_OPS(name)                                                  \
 static const struct file_operations key_ ##name## _ops = {             \
@@ -39,7 +40,6 @@ static const struct file_operations key_ ##name## _ops = {            \
 #define KEY_CONF_READ(name, buflen, format_string)                     \
        KEY_READ(conf_##name, conf.name, buflen, format_string)
 #define KEY_CONF_READ_D(name) KEY_CONF_READ(name, 20, "%d\n")
-#define KEY_CONF_READ_X(name) KEY_CONF_READ(name, 20, "0x%x\n")
 
 #define KEY_CONF_OPS(name)                                             \
 static const struct file_operations key_ ##name## _ops = {             \
@@ -54,7 +54,7 @@ static const struct file_operations key_ ##name## _ops = {            \
 KEY_CONF_FILE(keylen, D);
 KEY_CONF_FILE(keyidx, D);
 KEY_CONF_FILE(hw_key_idx, D);
-KEY_CONF_FILE(flags, X);
+KEY_FILE(flags, X);
 KEY_FILE(tx_rx_count, D);
 
 static ssize_t key_algorithm_read(struct file *file,
index 5d5034f36fde1a740f8ec42b56ff9c5cdc1a9a44..73e314e33de2aec24556124f6e1a7a4e8f72d6e7 100644 (file)
@@ -442,6 +442,7 @@ static int ieee80211_open(struct net_device *dev)
        } else {
                ieee80211_if_config(dev);
                ieee80211_reset_erp_info(dev);
+               ieee80211_enable_keys(sdata);
        }
 
        if (sdata->type == IEEE80211_IF_TYPE_STA &&
@@ -510,6 +511,9 @@ static int ieee80211_stop(struct net_device *dev)
                local->monitors--;
                if (!local->monitors)
                        local->hw.conf.flags &= ~IEEE80211_CONF_RADIOTAP;
+       } else {
+               /* disable all keys for as long as this netdev is down */
+               ieee80211_disable_keys(sdata);
        }
 
        local->open_count--;
@@ -908,7 +912,7 @@ static void ieee80211_remove_tx_extra(struct ieee80211_local *local,
        }
 
        if (skb->len >= mic_len &&
-           (key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT))
+           !(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE))
                skb_trim(skb, skb->len - mic_len);
        if (skb->len >= iv_len && skb->len > hdrlen) {
                memmove(skb->data + iv_len, skb->data, hdrlen);
index 7b5cc146c81b661bf72cd27f75e2b31fa6585624..0149f9055918e740eedb5a84f3fe36d5cd14add8 100644 (file)
@@ -291,6 +291,9 @@ struct ieee80211_sub_if_data {
 
        struct wireless_dev wdev;
 
+       /* keys */
+       struct list_head key_list;
+
        struct net_device *dev;
        struct ieee80211_local *local;
 
@@ -810,11 +813,6 @@ int ieee80211_monitor_start_xmit(struct sk_buff *skb, struct net_device *dev);
 int ieee80211_subif_start_xmit(struct sk_buff *skb, struct net_device *dev);
 int ieee80211_mgmt_start_xmit(struct sk_buff *skb, struct net_device *dev);
 
-/* key handling */
-struct ieee80211_key *ieee80211_key_alloc(struct ieee80211_sub_if_data *sdata,
-                                         int idx, size_t key_len, gfp_t flags);
-void ieee80211_key_free(struct ieee80211_key *key);
-
 /* utility functions/constants */
 extern void *mac80211_wiphy_privid; /* for wiphy privid */
 extern const unsigned char rfc1042_header[6];
index 8bb85f19438599dc23bfedb085db8b74ff670736..f9c74bb09d31bc665fba6d3019613102ea95122f 100644 (file)
@@ -25,6 +25,8 @@ void ieee80211_if_sdata_init(struct ieee80211_sub_if_data *sdata)
        sdata->eapol = 1;
        for (i = 0; i < IEEE80211_FRAGMENT_MAX; i++)
                skb_queue_head_init(&sdata->fragments[i].skb_list);
+
+       INIT_LIST_HEAD(&sdata->key_list);
 }
 
 static void ieee80211_if_sdata_deinit(struct ieee80211_sub_if_data *sdata)
@@ -210,25 +212,12 @@ void ieee80211_if_reinit(struct net_device *dev)
        struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
        struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
        struct sta_info *sta;
-       int i;
 
        ASSERT_RTNL();
+
+       ieee80211_free_keys(sdata);
+
        ieee80211_if_sdata_deinit(sdata);
-       for (i = 0; i < NUM_DEFAULT_KEYS; i++) {
-               if (!sdata->keys[i])
-                       continue;
-#if 0
-               /* The interface is down at the moment, so there is not
-                * really much point in disabling the keys at this point. */
-               memset(addr, 0xff, ETH_ALEN);
-               if (local->ops->set_key)
-                       local->ops->set_key(local_to_hw(local), DISABLE_KEY, addr,
-                                           local->keys[i],
-                                           local->default_wep_only);
-#endif
-               ieee80211_key_free(sdata->keys[i]);
-               sdata->keys[i] = NULL;
-       }
 
        switch (sdata->type) {
        case IEEE80211_IF_TYPE_AP: {
index dc05bc66fbb882f3bb2777b4fa85e785879cb0ec..8296e7de12c7a7cffcd7efe07afa7c5f2704cad9 100644 (file)
 #include "ieee80211_rate.h"
 #include "wpa.h"
 #include "aes_ccm.h"
-#include "debugfs_key.h"
-
-static void ieee80211_set_hw_encryption(struct net_device *dev,
-                                       struct sta_info *sta, u8 addr[ETH_ALEN],
-                                       struct ieee80211_key *key)
-{
-       struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
-
-       /* default to sw encryption; this will be cleared by low-level
-        * driver if the hw supports requested encryption */
-       if (key)
-               key->conf.flags |= IEEE80211_KEY_FORCE_SW_ENCRYPT;
-
-       if (key && local->ops->set_key) {
-               if (local->ops->set_key(local_to_hw(local), SET_KEY, addr,
-                                       &key->conf)) {
-                       key->conf.flags |= IEEE80211_KEY_FORCE_SW_ENCRYPT;
-                       key->conf.hw_key_idx = HW_KEY_IDX_INVALID;
-               }
-       }
-}
-
 
 static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr,
                                    int idx, int alg, int set_tx_key,
@@ -55,8 +33,7 @@ static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr,
        struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
        int ret = 0;
        struct sta_info *sta;
-       struct ieee80211_key *key, *old_key;
-       int try_hwaccel = 1;
+       struct ieee80211_key *key;
        struct ieee80211_sub_if_data *sdata;
 
        sdata = IEEE80211_DEV_TO_SUB_IF(dev);
@@ -69,16 +46,6 @@ static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr,
                        return -EINVAL;
                }
                key = sdata->keys[idx];
-
-               /* TODO: consider adding hwaccel support for these; at least
-                * Atheros key cache should be able to handle this since AP is
-                * only transmitting frames with default keys. */
-               /* FIX: hw key cache can be used when only one virtual
-                * STA is associated with each AP. If more than one STA
-                * is associated to the same AP, software encryption
-                * must be used. This should be done automatically
-                * based on configured station devices. For the time
-                * being, this can be only set at compile time. */
        } else {
                set_tx_key = 0;
                if (idx != 0) {
@@ -101,139 +68,28 @@ static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr,
                key = sta->key;
        }
 
-       /* FIX:
-        * Cannot configure default hwaccel keys with WEP algorithm, if
-        * any of the virtual interfaces is using static WEP
-        * configuration because hwaccel would otherwise try to decrypt
-        * these frames.
-        *
-        * For now, just disable WEP hwaccel for broadcast when there is
-        * possibility of conflict with default keys. This can maybe later be
-        * optimized by using non-default keys (at least with Atheros ar521x).
-        */
-       if (!sta && alg == ALG_WEP &&
-           sdata->type != IEEE80211_IF_TYPE_IBSS &&
-           sdata->type != IEEE80211_IF_TYPE_AP) {
-               try_hwaccel = 0;
-       }
-
-       if (local->hw.flags & IEEE80211_HW_DEVICE_HIDES_WEP) {
-               /* Software encryption cannot be used with devices that hide
-                * encryption from the host system, so always try to use
-                * hardware acceleration with such devices. */
-               try_hwaccel = 1;
-       }
-
-       if ((local->hw.flags & IEEE80211_HW_NO_TKIP_WMM_HWACCEL) &&
-           alg == ALG_TKIP) {
-               if (sta && (sta->flags & WLAN_STA_WME)) {
-               /* Hardware does not support hwaccel with TKIP when using WMM.
-                */
-                       try_hwaccel = 0;
-               }
-               else if (sdata->type == IEEE80211_IF_TYPE_STA) {
-                       sta = sta_info_get(local, sdata->u.sta.bssid);
-                       if (sta) {
-                               if (sta->flags & WLAN_STA_WME) {
-                                       try_hwaccel = 0;
-                               }
-                               sta_info_put(sta);
-                               sta = NULL;
-                       }
-               }
-       }
-
        if (alg == ALG_NONE) {
-               if (try_hwaccel && key &&
-                   key->conf.hw_key_idx != HW_KEY_IDX_INVALID &&
-                   local->ops->set_key &&
-                   local->ops->set_key(local_to_hw(local), DISABLE_KEY,
-                                       sta_addr, &key->conf)) {
-                       printk(KERN_DEBUG "%s: set_encrypt - low-level disable"
-                              " failed\n", dev->name);
-                       ret = -EINVAL;
-               }
-
-               if (set_tx_key || sdata->default_key == key) {
-                       ieee80211_debugfs_key_remove_default(sdata);
-                       sdata->default_key = NULL;
-               }
-               ieee80211_debugfs_key_remove(key);
-               if (sta)
-                       sta->key = NULL;
-               else
-                       sdata->keys[idx] = NULL;
                ieee80211_key_free(key);
                key = NULL;
        } else {
-               old_key = key;
-               key = ieee80211_key_alloc(sta ? NULL : sdata, idx, key_len,
-                                         GFP_KERNEL);
+               /*
+                * Need to free it before allocating a new one with
+                * with the same index or the ordering to the driver's
+                * set_key() callback becomes confused.
+                */
+               ieee80211_key_free(key);
+               key = ieee80211_key_alloc(sdata, sta, alg, idx, key_len, _key);
                if (!key) {
                        ret = -ENOMEM;
                        goto err_out;
                }
-
-               /* default to sw encryption; low-level driver sets these if the
-                * requested encryption is supported */
-               key->conf.hw_key_idx = HW_KEY_IDX_INVALID;
-               key->conf.flags |= IEEE80211_KEY_FORCE_SW_ENCRYPT;
-
-               key->conf.alg = alg;
-               key->conf.keyidx = idx;
-               key->conf.keylen = key_len;
-               memcpy(key->conf.key, _key, key_len);
-
-               if (alg == ALG_CCMP) {
-                       /* Initialize AES key state here as an optimization
-                        * so that it does not need to be initialized for every
-                        * packet. */
-                       key->u.ccmp.tfm = ieee80211_aes_key_setup_encrypt(
-                               key->conf.key);
-                       if (!key->u.ccmp.tfm) {
-                               ret = -ENOMEM;
-                               goto err_free;
-                       }
-               }
-
-               if (set_tx_key || sdata->default_key == old_key) {
-                       ieee80211_debugfs_key_remove_default(sdata);
-                       sdata->default_key = NULL;
-               }
-               ieee80211_debugfs_key_remove(old_key);
-               if (sta)
-                       sta->key = key;
-               else
-                       sdata->keys[idx] = key;
-               ieee80211_key_free(old_key);
-               ieee80211_debugfs_key_add(local, key);
-               if (sta)
-                       ieee80211_debugfs_key_sta_link(key, sta);
-
-               if (try_hwaccel &&
-                   (alg == ALG_WEP || alg == ALG_TKIP || alg == ALG_CCMP))
-                       ieee80211_set_hw_encryption(dev, sta, sta_addr, key);
-       }
-
-       if (set_tx_key || (!sta && !sdata->default_key && key)) {
-               sdata->default_key = key;
-               if (key)
-                       ieee80211_debugfs_key_add_default(sdata);
-
-               if (local->ops->set_key_idx &&
-                   local->ops->set_key_idx(local_to_hw(local), idx))
-                       printk(KERN_DEBUG "%s: failed to set TX key idx for "
-                              "low-level driver\n", dev->name);
        }
 
-       if (sta)
-               sta_info_put(sta);
+       if (set_tx_key || (!sta && !sdata->default_key && key))
+               ieee80211_set_default_key(sdata, idx);
 
-       return 0;
-
-err_free:
-       ieee80211_key_free(key);
-err_out:
+       ret = 0;
+ err_out:
        if (sta)
                sta_info_put(sta);
        return ret;
@@ -1181,12 +1037,7 @@ static int ieee80211_ioctl_siwencode(struct net_device *dev,
                alg = ALG_NONE;
        else if (erq->length == 0) {
                /* No key data - just set the default TX key index */
-               if (sdata->default_key != sdata->keys[idx]) {
-                       ieee80211_debugfs_key_remove_default(sdata);
-                       sdata->default_key = sdata->keys[idx];
-                       if (sdata->default_key)
-                               ieee80211_debugfs_key_add_default(sdata);
-               }
+               ieee80211_set_default_key(sdata, idx);
                return 0;
        }
 
@@ -1232,7 +1083,7 @@ static int ieee80211_ioctl_giwencode(struct net_device *dev,
        }
 
        memcpy(key, sdata->keys[idx]->conf.key,
-              min((int)erq->length, sdata->keys[idx]->conf.keylen));
+              min_t(int, erq->length, sdata->keys[idx]->conf.keylen));
        erq->length = sdata->keys[idx]->conf.keylen;
        erq->flags |= IW_ENCODE_ENABLED;
 
index 58e19253082658a3223696b591a04a593f9ab3e8..a4e5fbbe045c34b081434776c0e8a9990c6d1dc8 100644 (file)
 
 #define NUM_RX_DATA_QUEUES 17
 
+struct ieee80211_local;
+struct ieee80211_sub_if_data;
+struct sta_info;
+
+#define KEY_FLAG_UPLOADED_TO_HARDWARE  (1<<0)
+
 struct ieee80211_key {
+       struct ieee80211_local *local;
+       struct ieee80211_sub_if_data *sdata;
+       struct sta_info *sta;
+
+       struct list_head list;
+
+       unsigned int flags;
+
        union {
                struct {
                        /* last used TSC */
@@ -97,4 +111,16 @@ struct ieee80211_key {
        struct ieee80211_key_conf conf;
 };
 
+struct ieee80211_key *ieee80211_key_alloc(struct ieee80211_sub_if_data *sdata,
+                                         struct sta_info *sta,
+                                         ieee80211_key_alg alg,
+                                         int idx,
+                                         size_t key_len,
+                                         const u8 *key_data);
+void ieee80211_key_free(struct ieee80211_key *key);
+void ieee80211_set_default_key(struct ieee80211_sub_if_data *sdata, int idx);
+void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata);
+void ieee80211_enable_keys(struct ieee80211_sub_if_data *sdata);
+void ieee80211_disable_keys(struct ieee80211_sub_if_data *sdata);
+
 #endif /* IEEE80211_KEY_H */
index 843d1577f00fc1f9cf7a31097b5b08e501a34e09..178f00cf61b925110e4efd865ece28854e97f837 100644 (file)
  * Copyright 2002-2005, Instant802 Networks, Inc.
  * Copyright 2005-2006, Devicescape Software, Inc.
  * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
+ * Copyright 2007      Johannes Berg <johannes@sipsolutions.net>
  *
  * 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.
  */
 
+#include <linux/if_ether.h>
+#include <linux/etherdevice.h>
+#include <linux/list.h>
 #include <net/mac80211.h>
 #include "ieee80211_i.h"
 #include "debugfs_key.h"
 #include "aes_ccm.h"
 
+
+/*
+ * Key handling basics
+ *
+ * Key handling in mac80211 is done based on per-interface (sub_if_data)
+ * keys and per-station keys. Since each station belongs to an interface,
+ * each station key also belongs to that interface.
+ *
+ * Hardware acceleration is done on a best-effort basis, for each key
+ * that is eligible the hardware is asked to enable that key but if
+ * it cannot do that they key is simply kept for software encryption.
+ * There is currently no way of knowing this except by looking into
+ * debugfs.
+ *
+ * All operations here are called under RTNL so no extra locking is
+ * required.
+ */
+
+static const u8 bcast_addr[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+static const u8 zero_addr[ETH_ALEN];
+
+static const u8 *get_mac_for_key(struct ieee80211_key *key)
+{
+       const u8 *addr = bcast_addr;
+
+       /*
+        * If we're an AP we won't ever receive frames with a non-WEP
+        * group key so we tell the driver that by using the zero MAC
+        * address to indicate a transmit-only key.
+        */
+       if (key->conf.alg != ALG_WEP &&
+           (key->sdata->type == IEEE80211_IF_TYPE_AP ||
+            key->sdata->type == IEEE80211_IF_TYPE_VLAN))
+               addr = zero_addr;
+
+       if (key->sta)
+               addr = key->sta->addr;
+
+       return addr;
+}
+
+static void ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
+{
+       const u8 *addr;
+       int ret;
+
+       if (!key->local->ops->set_key)
+               return;
+
+       addr = get_mac_for_key(key);
+
+       ret = key->local->ops->set_key(local_to_hw(key->local), SET_KEY,
+                                      key->sdata->dev->dev_addr, addr,
+                                      &key->conf);
+
+       WARN_ON(!ret && (key->conf.hw_key_idx == HW_KEY_IDX_INVALID));
+
+       if (!ret)
+               key->flags |= KEY_FLAG_UPLOADED_TO_HARDWARE;
+
+       if (ret && ret != -ENOSPC && ret != -EOPNOTSUPP)
+               printk(KERN_ERR "mac80211-%s: failed to set key "
+                      "(%d, " MAC_FMT ") to hardware (%d)\n",
+                      wiphy_name(key->local->hw.wiphy),
+                      key->conf.keyidx, MAC_ARG(addr), ret);
+}
+
+static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
+{
+       const u8 *addr;
+       int ret;
+
+       if (!key->local->ops->set_key)
+               return;
+
+       if (!(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE))
+               return;
+
+       addr = get_mac_for_key(key);
+
+       ret = key->local->ops->set_key(local_to_hw(key->local), DISABLE_KEY,
+                                      key->sdata->dev->dev_addr, addr,
+                                      &key->conf);
+
+       if (ret)
+               printk(KERN_ERR "mac80211-%s: failed to remove key "
+                      "(%d, " MAC_FMT ") from hardware (%d)\n",
+                      wiphy_name(key->local->hw.wiphy),
+                      key->conf.keyidx, MAC_ARG(addr), ret);
+
+       key->flags &= ~KEY_FLAG_UPLOADED_TO_HARDWARE;
+       key->conf.hw_key_idx = HW_KEY_IDX_INVALID;
+}
+
 struct ieee80211_key *ieee80211_key_alloc(struct ieee80211_sub_if_data *sdata,
-                                         int idx, size_t key_len, gfp_t flags)
+                                         struct sta_info *sta,
+                                         ieee80211_key_alg alg,
+                                         int idx,
+                                         size_t key_len,
+                                         const u8 *key_data)
 {
        struct ieee80211_key *key;
 
-       key = kzalloc(sizeof(struct ieee80211_key) + key_len, flags);
+       BUG_ON(alg == ALG_NONE);
+
+       key = kzalloc(sizeof(struct ieee80211_key) + key_len, GFP_KERNEL);
        if (!key)
                return NULL;
+
+       /*
+        * Default to software encryption; we'll later upload the
+        * key to the hardware if possible.
+        */
+       key->conf.hw_key_idx = HW_KEY_IDX_INVALID;
+       key->conf.flags = 0;
+       key->flags = 0;
+
+       key->conf.alg = alg;
+       key->conf.keyidx = idx;
+       key->conf.keylen = key_len;
+       memcpy(key->conf.key, key_data, key_len);
+
+       key->local = sdata->local;
+       key->sdata = sdata;
+       key->sta = sta;
+
+       if (alg == ALG_CCMP) {
+               /*
+                * Initialize AES key state here as an optimization so that
+                * it does not need to be initialized for every packet.
+                */
+               key->u.ccmp.tfm = ieee80211_aes_key_setup_encrypt(key_data);
+               if (!key->u.ccmp.tfm) {
+                       ieee80211_key_free(key);
+                       return NULL;
+               }
+       }
+
+       ieee80211_debugfs_key_add(key->local, key);
+
+       if (sta) {
+               ieee80211_debugfs_key_sta_link(key, sta);
+               sta->key = key;
+               /*
+                * some hardware cannot handle TKIP with QoS, so
+                * we indicate whether QoS could be in use.
+                */
+               if (sta->flags & WLAN_STA_WME)
+                       key->conf.flags |= IEEE80211_KEY_FLAG_WMM_STA;
+       } else {
+               if (sdata->type == IEEE80211_IF_TYPE_STA) {
+                       struct sta_info *ap;
+
+                       /* same here, the AP could be using QoS */
+                       ap = sta_info_get(key->local, key->sdata->u.sta.bssid);
+                       if (ap) {
+                               if (ap->flags & WLAN_STA_WME)
+                                       key->conf.flags |=
+                                               IEEE80211_KEY_FLAG_WMM_STA;
+                               sta_info_put(ap);
+                       }
+               }
+
+               if (idx >= 0 && idx < NUM_DEFAULT_KEYS) {
+                       if (!sdata->keys[idx])
+                               sdata->keys[idx] = key;
+                       else
+                               WARN_ON(1);
+               } else
+                       WARN_ON(1);
+       }
+
+       list_add(&key->list, &sdata->key_list);
+
+       if (netif_running(key->sdata->dev))
+               ieee80211_key_enable_hw_accel(key);
+
        return key;
 }
 
@@ -29,8 +202,74 @@ void ieee80211_key_free(struct ieee80211_key *key)
        if (!key)
                return;
 
+       ieee80211_key_disable_hw_accel(key);
+
+       if (key->sta) {
+               key->sta->key = NULL;
+       } else {
+               if (key->sdata->default_key == key)
+                       ieee80211_set_default_key(key->sdata, -1);
+               if (key->conf.keyidx >= 0 &&
+                   key->conf.keyidx < NUM_DEFAULT_KEYS)
+                       key->sdata->keys[key->conf.keyidx] = NULL;
+               else
+                       WARN_ON(1);
+       }
+
        if (key->conf.alg == ALG_CCMP)
                ieee80211_aes_key_free(key->u.ccmp.tfm);
        ieee80211_debugfs_key_remove(key);
+
+       list_del(&key->list);
+
        kfree(key);
 }
+
+void ieee80211_set_default_key(struct ieee80211_sub_if_data *sdata, int idx)
+{
+       struct ieee80211_key *key = NULL;
+
+       if (idx >= 0 && idx < NUM_DEFAULT_KEYS)
+               key = sdata->keys[idx];
+
+       if (sdata->default_key != key) {
+               ieee80211_debugfs_key_remove_default(sdata);
+
+               sdata->default_key = key;
+
+               if (sdata->default_key)
+                       ieee80211_debugfs_key_add_default(sdata);
+
+               if (sdata->local->ops->set_key_idx)
+                       sdata->local->ops->set_key_idx(
+                               local_to_hw(sdata->local), idx);
+       }
+}
+
+void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata)
+{
+       struct ieee80211_key *key, *tmp;
+
+       list_for_each_entry_safe(key, tmp, &sdata->key_list, list)
+               ieee80211_key_free(key);
+}
+
+void ieee80211_enable_keys(struct ieee80211_sub_if_data *sdata)
+{
+       struct ieee80211_key *key;
+
+       WARN_ON(!netif_running(sdata->dev));
+       if (!netif_running(sdata->dev))
+               return;
+
+       list_for_each_entry(key, &sdata->key_list, list)
+               ieee80211_key_enable_hw_accel(key);
+}
+
+void ieee80211_disable_keys(struct ieee80211_sub_if_data *sdata)
+{
+       struct ieee80211_key *key;
+
+       list_for_each_entry(key, &sdata->key_list, list)
+               ieee80211_key_disable_hw_accel(key);
+}
index 976b646a40decebc13f03db55be670bfb12f7833..ba94f58ba02efac65fa04828bb7fb19260124886 100644 (file)
@@ -528,7 +528,7 @@ ieee80211_rx_h_wep_weak_iv_detection(struct ieee80211_txrx_data *rx)
 
        /* Check for weak IVs, if hwaccel did not remove IV from the frame */
        if ((rx->local->hw.flags & IEEE80211_HW_WEP_INCLUDE_IV) ||
-           (rx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT))
+           !(rx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE))
                if (ieee80211_wep_is_weak_iv(rx->skb, rx->key))
                        rx->sta->wep_weak_iv_count++;
 
@@ -553,7 +553,7 @@ ieee80211_rx_h_wep_decrypt(struct ieee80211_txrx_data *rx)
        }
 
        if (!(rx->u.rx.status->flag & RX_FLAG_DECRYPTED) ||
-           (rx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)) {
+           !(rx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)) {
                if (ieee80211_wep_decrypt(rx->local, rx->skb, rx->key)) {
                        if (net_ratelimit())
                                printk(KERN_DEBUG "%s: RX WEP frame, decrypt "
@@ -897,8 +897,7 @@ ieee80211_rx_h_drop_unencrypted(struct ieee80211_txrx_data *rx)
         * uploaded to the hardware.
         */
        if ((rx->local->hw.flags & IEEE80211_HW_DEVICE_HIDES_WEP) &&
-           (!rx->key ||
-            !(rx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)))
+           (!rx->key || (rx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)))
                return TXRX_CONTINUE;
 
        /* Drop unencrypted frames if key is set. */
index fba2d79e4d2b6dd934231bb86fa9fff962a532f5..c17172abb21c4f8e7baca64078a9dedcec0c762a 100644 (file)
@@ -19,7 +19,6 @@
 #include "ieee80211_i.h"
 #include "ieee80211_rate.h"
 #include "sta_info.h"
-#include "debugfs_key.h"
 #include "debugfs_sta.h"
 
 /* Caller must hold local->sta_lock */
@@ -118,8 +117,6 @@ static void sta_info_release(struct kref *kref)
        }
        rate_control_free_sta(sta->rate_ctrl, sta->rate_ctrl_priv);
        rate_control_put(sta->rate_ctrl);
-       if (sta->key)
-               ieee80211_debugfs_key_sta_del(sta->key, sta);
        kfree(sta);
 }
 
@@ -230,11 +227,8 @@ void sta_info_free(struct sta_info *sta)
               local->mdev->name, MAC_ARG(sta->addr));
 #endif /* CONFIG_MAC80211_VERBOSE_DEBUG */
 
-       if (sta->key) {
-               ieee80211_debugfs_key_remove(sta->key);
-               ieee80211_key_free(sta->key);
-               sta->key = NULL;
-       }
+       ieee80211_key_free(sta->key);
+       sta->key = NULL;
 
        rate_control_remove_sta_debugfs(sta);
        ieee80211_sta_debugfs_remove(sta);
index d70140cbd66a610585e85399ed5a2f819febf710..b65ff653624464f7817670fe10e0fc4540aadb77 100644 (file)
@@ -536,7 +536,7 @@ ieee80211_tx_h_fragment(struct ieee80211_txrx_data *tx)
 
 static int wep_encrypt_skb(struct ieee80211_txrx_data *tx, struct sk_buff *skb)
 {
-       if (tx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT) {
+       if (!(tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)) {
                if (ieee80211_wep_encrypt(tx->local, skb, tx->key))
                        return -1;
        } else {
@@ -832,7 +832,7 @@ __ieee80211_parse_tx_radiotap(
         */
 
        control->retry_limit = 1; /* no retry */
-       control->key_idx = -1; /* no encryption key */
+       control->key_idx = HW_KEY_IDX_INVALID;
        control->flags &= ~(IEEE80211_TXCTL_USE_RTS_CTS |
                            IEEE80211_TXCTL_USE_CTS_PROTECT);
        control->flags |= IEEE80211_TXCTL_DO_NOT_ENCRYPT |
index 4a2a9aa638b30bff53b64fd716677131444bb919..b6cd66e0ee5852c4fcc2fbb141c0f849f07ee3d4 100644 (file)
@@ -89,7 +89,7 @@ ieee80211_tx_h_michael_mic_add(struct ieee80211_txrx_data *tx)
        if (ieee80211_get_hdr_info(skb, &sa, &da, &qos_tid, &data, &data_len))
                return TXRX_DROP;
 
-       if (!(tx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT) &&
+       if ((tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) &&
            !(tx->flags & IEEE80211_TXRXD_FRAGMENTED) &&
            !(tx->local->hw.flags & IEEE80211_HW_TKIP_INCLUDE_MMIC) &&
            !wpa_test) {
@@ -146,7 +146,7 @@ ieee80211_rx_h_michael_mic_verify(struct ieee80211_txrx_data *rx)
                return TXRX_CONTINUE;
 
        if ((rx->u.rx.status->flag & RX_FLAG_DECRYPTED) &&
-           !(rx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)) {
+           (rx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)) {
                if (rx->local->hw.flags & IEEE80211_HW_WEP_INCLUDE_IV) {
                        if (skb->len < MICHAEL_MIC_LEN)
                                return TXRX_DROP;
@@ -205,10 +205,10 @@ static int tkip_encrypt_skb(struct ieee80211_txrx_data *tx,
        hdrlen = ieee80211_get_hdrlen(fc);
        len = skb->len - hdrlen;
 
-       if (tx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)
-               tailneed = TKIP_ICV_LEN;
-       else
+       if (tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)
                tailneed = 0;
+       else
+               tailneed = TKIP_ICV_LEN;
 
        if ((skb_headroom(skb) < TKIP_IV_LEN ||
             skb_tailroom(skb) < tailneed)) {
@@ -227,7 +227,7 @@ static int tkip_encrypt_skb(struct ieee80211_txrx_data *tx,
        if (key->u.tkip.iv16 == 0)
                key->u.tkip.iv32++;
 
-       if (!(tx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)) {
+       if (tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) {
                u32 flags = tx->local->hw.flags;
                hdr = (struct ieee80211_hdr *)skb->data;
 
@@ -286,7 +286,7 @@ ieee80211_tx_h_tkip_encrypt(struct ieee80211_txrx_data *tx)
        tx->u.tx.control->iv_len = TKIP_IV_LEN;
        ieee80211_tx_set_iswep(tx);
 
-       if (!(tx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT) &&
+       if ((tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) &&
            !(tx->local->hw.flags & IEEE80211_HW_WEP_INCLUDE_IV) &&
            !wpa_test) {
                /* hwaccel - with no need for preallocated room for IV/ICV */
@@ -331,7 +331,7 @@ ieee80211_rx_h_tkip_decrypt(struct ieee80211_txrx_data *rx)
                return TXRX_DROP;
 
        if ((rx->u.rx.status->flag & RX_FLAG_DECRYPTED) &&
-           !(key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)) {
+           (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)) {
                if (!(rx->local->hw.flags & IEEE80211_HW_WEP_INCLUDE_IV)) {
                        /* Hardware takes care of all processing, including
                         * replay protection, so no need to continue here. */
@@ -475,10 +475,10 @@ static int ccmp_encrypt_skb(struct ieee80211_txrx_data *tx,
        hdrlen = ieee80211_get_hdrlen(fc);
        len = skb->len - hdrlen;
 
-       if (key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)
-               tailneed = CCMP_MIC_LEN;
-       else
+       if (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)
                tailneed = 0;
+       else
+               tailneed = CCMP_MIC_LEN;
 
        if ((skb_headroom(skb) < CCMP_HDR_LEN ||
             skb_tailroom(skb) < tailneed)) {
@@ -504,7 +504,7 @@ static int ccmp_encrypt_skb(struct ieee80211_txrx_data *tx,
 
        ccmp_pn2hdr(pos, pn, key->conf.keyidx);
 
-       if (!(key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)) {
+       if (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) {
                /* hwaccel - with preallocated room for CCMP header */
                tx->u.tx.control->key_idx = key->conf.hw_key_idx;
                return 0;
@@ -537,7 +537,7 @@ ieee80211_tx_h_ccmp_encrypt(struct ieee80211_txrx_data *tx)
        tx->u.tx.control->iv_len = CCMP_HDR_LEN;
        ieee80211_tx_set_iswep(tx);
 
-       if (!(tx->key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT) &&
+       if ((tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) &&
            !(tx->local->hw.flags & IEEE80211_HW_WEP_INCLUDE_IV)) {
                /* hwaccel - with no need for preallocated room for CCMP "
                 * header or MIC fields */
@@ -586,7 +586,7 @@ ieee80211_rx_h_ccmp_decrypt(struct ieee80211_txrx_data *rx)
                return TXRX_DROP;
 
        if ((rx->u.rx.status->flag & RX_FLAG_DECRYPTED) &&
-           !(key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT) &&
+           (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) &&
            !(rx->local->hw.flags & IEEE80211_HW_WEP_INCLUDE_IV))
                return TXRX_CONTINUE;
 
@@ -607,7 +607,7 @@ ieee80211_rx_h_ccmp_decrypt(struct ieee80211_txrx_data *rx)
        }
 
        if ((rx->u.rx.status->flag & RX_FLAG_DECRYPTED) &&
-           !(key->conf.flags & IEEE80211_KEY_FORCE_SW_ENCRYPT)) {
+           (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)) {
                /* hwaccel has already decrypted frame and verified MIC */
        } else {
                u8 *scratch, *b_0, *aad;