#include "ieee80211_i.h"
#include "debugfs_key.h"
#include "aes_ccm.h"
+#include "aes_cmac.h"
/**
*/
static const u8 bcast_addr[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
-static const u8 zero_addr[ETH_ALEN];
/* key mutex: used to synchronise todo runners */
static DEFINE_MUTEX(key_mutex);
spin_lock(&todo_lock);
key->flags |= flag;
- /* only add if not already added */
- if (list_empty(&key->todo))
- list_add(&key->todo, &todo_list);
+ /*
+ * Remove again if already on the list so that we move it to the end.
+ */
+ if (!list_empty(&key->todo))
+ list_del(&key->todo);
+ list_add_tail(&key->todo, &todo_list);
schedule_work(&todo_work);
spin_unlock(&todo_lock);
}
WARN_ON(!mutex_is_locked(&key_mutex));
}
-static const u8 *get_mac_for_key(struct ieee80211_key *key)
+static struct ieee80211_sta *get_sta_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->vif.type == IEEE80211_IF_TYPE_AP ||
- key->sdata->vif.type == IEEE80211_IF_TYPE_VLAN))
- addr = zero_addr;
-
if (key->sta)
- addr = key->sta->addr;
+ return &key->sta->sta;
- return addr;
+ return NULL;
}
static void ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
{
- const u8 *addr;
+ struct ieee80211_sub_if_data *sdata;
+ struct ieee80211_sta *sta;
int ret;
- DECLARE_MAC_BUF(mac);
assert_key_lock();
might_sleep();
if (!key->local->ops->set_key)
return;
- addr = get_mac_for_key(key);
+ sta = get_sta_for_key(key);
+
+ sdata = key->sdata;
+ if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
+ sdata = container_of(sdata->bss,
+ struct ieee80211_sub_if_data,
+ u.ap);
ret = key->local->ops->set_key(local_to_hw(key->local), SET_KEY,
- key->sdata->dev->dev_addr, addr,
- &key->conf);
+ &sdata->vif, sta, &key->conf);
if (!ret) {
spin_lock(&todo_lock);
if (ret && ret != -ENOSPC && ret != -EOPNOTSUPP)
printk(KERN_ERR "mac80211-%s: failed to set key "
- "(%d, %s) to hardware (%d)\n",
+ "(%d, %pM) to hardware (%d)\n",
wiphy_name(key->local->hw.wiphy),
- key->conf.keyidx, print_mac(mac, addr), ret);
+ key->conf.keyidx, sta ? sta->addr : bcast_addr, ret);
}
static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
{
- const u8 *addr;
+ struct ieee80211_sub_if_data *sdata;
+ struct ieee80211_sta *sta;
int ret;
- DECLARE_MAC_BUF(mac);
assert_key_lock();
might_sleep();
}
spin_unlock(&todo_lock);
- addr = get_mac_for_key(key);
+ sta = get_sta_for_key(key);
+ sdata = key->sdata;
+
+ if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
+ sdata = container_of(sdata->bss,
+ struct ieee80211_sub_if_data,
+ u.ap);
ret = key->local->ops->set_key(local_to_hw(key->local), DISABLE_KEY,
- key->sdata->dev->dev_addr, addr,
- &key->conf);
+ &sdata->vif, sta, &key->conf);
if (ret)
printk(KERN_ERR "mac80211-%s: failed to remove key "
- "(%d, %s) from hardware (%d)\n",
+ "(%d, %pM) from hardware (%d)\n",
wiphy_name(key->local->hw.wiphy),
- key->conf.keyidx, print_mac(mac, addr), ret);
+ key->conf.keyidx, sta ? sta->addr : bcast_addr, ret);
spin_lock(&todo_lock);
key->flags &= ~KEY_FLAG_UPLOADED_TO_HARDWARE;
{
unsigned long flags;
- spin_lock_irqsave(&sdata->local->sta_lock, flags);
+ spin_lock_irqsave(&sdata->local->key_lock, flags);
__ieee80211_set_default_key(sdata, idx);
- spin_unlock_irqrestore(&sdata->local->sta_lock, flags);
+ spin_unlock_irqrestore(&sdata->local->key_lock, flags);
+}
+
+static void
+__ieee80211_set_default_mgmt_key(struct ieee80211_sub_if_data *sdata, int idx)
+{
+ struct ieee80211_key *key = NULL;
+
+ if (idx >= NUM_DEFAULT_KEYS &&
+ idx < NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS)
+ key = sdata->keys[idx];
+
+ rcu_assign_pointer(sdata->default_mgmt_key, key);
+
+ if (key)
+ add_todo(key, KEY_FLAG_TODO_DEFMGMTKEY);
+}
+
+void ieee80211_set_default_mgmt_key(struct ieee80211_sub_if_data *sdata,
+ int idx)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&sdata->local->key_lock, flags);
+ __ieee80211_set_default_mgmt_key(sdata, idx);
+ spin_unlock_irqrestore(&sdata->local->key_lock, flags);
}
struct ieee80211_key *old,
struct ieee80211_key *new)
{
- int idx, defkey;
+ int idx, defkey, defmgmtkey;
if (new)
list_add(&new->list, &sdata->key_list);
idx = new->conf.keyidx;
defkey = old && sdata->default_key == old;
+ defmgmtkey = old && sdata->default_mgmt_key == old;
if (defkey && !new)
__ieee80211_set_default_key(sdata, -1);
+ if (defmgmtkey && !new)
+ __ieee80211_set_default_mgmt_key(sdata, -1);
rcu_assign_pointer(sdata->keys[idx], new);
if (defkey && new)
__ieee80211_set_default_key(sdata, new->conf.keyidx);
+ if (defmgmtkey && new)
+ __ieee80211_set_default_mgmt_key(sdata,
+ new->conf.keyidx);
}
if (old) {
{
struct ieee80211_key *key;
- BUG_ON(idx < 0 || idx >= NUM_DEFAULT_KEYS);
+ BUG_ON(idx < 0 || idx >= NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS);
key = kzalloc(sizeof(struct ieee80211_key) + key_len, GFP_KERNEL);
if (!key)
key->conf.alg = alg;
key->conf.keyidx = idx;
key->conf.keylen = key_len;
+ switch (alg) {
+ case ALG_WEP:
+ key->conf.iv_len = WEP_IV_LEN;
+ key->conf.icv_len = WEP_ICV_LEN;
+ break;
+ case ALG_TKIP:
+ key->conf.iv_len = TKIP_IV_LEN;
+ key->conf.icv_len = TKIP_ICV_LEN;
+ break;
+ case ALG_CCMP:
+ key->conf.iv_len = CCMP_HDR_LEN;
+ key->conf.icv_len = CCMP_MIC_LEN;
+ break;
+ case ALG_AES_CMAC:
+ key->conf.iv_len = 0;
+ key->conf.icv_len = sizeof(struct ieee80211_mmie);
+ break;
+ }
memcpy(key->conf.key, key_data, key_len);
INIT_LIST_HEAD(&key->list);
INIT_LIST_HEAD(&key->todo);
}
}
+ if (alg == ALG_AES_CMAC) {
+ /*
+ * Initialize AES key state here as an optimization so that
+ * it does not need to be initialized for every packet.
+ */
+ key->u.aes_cmac.tfm =
+ ieee80211_aes_cmac_key_setup(key_data);
+ if (!key->u.aes_cmac.tfm) {
+ kfree(key);
+ return NULL;
+ }
+ }
+
return key;
}
* some hardware cannot handle TKIP with QoS, so
* we indicate whether QoS could be in use.
*/
- if (sta->flags & WLAN_STA_WME)
+ if (test_sta_flags(sta, WLAN_STA_WME))
key->conf.flags |= IEEE80211_KEY_FLAG_WMM_STA;
+
+ /*
+ * This key is for a specific sta interface,
+ * inform the driver that it should try to store
+ * this key as pairwise key.
+ */
+ key->conf.flags |= IEEE80211_KEY_FLAG_PAIRWISE;
} else {
- if (sdata->vif.type == IEEE80211_IF_TYPE_STA) {
+ if (sdata->vif.type == NL80211_IFTYPE_STATION) {
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)
+ if (test_sta_flags(ap, WLAN_STA_WME))
key->conf.flags |=
IEEE80211_KEY_FLAG_WMM_STA;
}
}
}
- spin_lock_irqsave(&sdata->local->sta_lock, flags);
+ spin_lock_irqsave(&sdata->local->key_lock, flags);
if (sta)
old_key = sta->key;
__ieee80211_key_replace(sdata, sta, old_key, key);
- spin_unlock_irqrestore(&sdata->local->sta_lock, flags);
+ spin_unlock_irqrestore(&sdata->local->key_lock, flags);
/* free old key later */
add_todo(old_key, KEY_FLAG_TODO_DELETE);
add_todo(key, KEY_FLAG_TODO_ADD_DEBUGFS);
if (netif_running(sdata->dev))
- add_todo(key, KEY_FLAG_TODO_HWACCEL);
+ add_todo(key, KEY_FLAG_TODO_HWACCEL_ADD);
}
-void ieee80211_key_free(struct ieee80211_key *key)
+static void __ieee80211_key_free(struct ieee80211_key *key)
{
- unsigned long flags;
-
- if (!key)
- return;
-
/*
* Replace key with nothingness if it was ever used.
*/
- if (key->sdata) {
- spin_lock_irqsave(&key->sdata->local->sta_lock, flags);
+ if (key->sdata)
__ieee80211_key_replace(key->sdata, key->sta,
key, NULL);
- spin_unlock_irqrestore(&key->sdata->local->sta_lock, flags);
- }
add_todo(key, KEY_FLAG_TODO_DELETE);
}
-void ieee80211_enable_keys(struct ieee80211_sub_if_data *sdata)
+void ieee80211_key_free(struct ieee80211_key *key)
{
- struct ieee80211_key *key;
+ unsigned long flags;
- might_sleep();
+ if (!key)
+ return;
- if (WARN_ON(!netif_running(sdata->dev)))
+ if (!key->sdata) {
+ /* The key has not been linked yet, simply free it
+ * and don't Oops */
+ if (key->conf.alg == ALG_CCMP)
+ ieee80211_aes_key_free(key->u.ccmp.tfm);
+ kfree(key);
return;
+ }
- ieee80211_key_lock();
+ spin_lock_irqsave(&key->sdata->local->key_lock, flags);
+ __ieee80211_key_free(key);
+ spin_unlock_irqrestore(&key->sdata->local->key_lock, flags);
+}
+
+/*
+ * To be safe against concurrent manipulations of the list (which shouldn't
+ * actually happen) we need to hold the spinlock. But under the spinlock we
+ * can't actually do much, so we defer processing to the todo list. Then run
+ * the todo list to be sure the operation and possibly previously pending
+ * operations are completed.
+ */
+static void ieee80211_todo_for_each_key(struct ieee80211_sub_if_data *sdata,
+ u32 todo_flags)
+{
+ struct ieee80211_key *key;
+ unsigned long flags;
+
+ might_sleep();
+ spin_lock_irqsave(&sdata->local->key_lock, flags);
list_for_each_entry(key, &sdata->key_list, list)
- ieee80211_key_enable_hw_accel(key);
+ add_todo(key, todo_flags);
+ spin_unlock_irqrestore(&sdata->local->key_lock, flags);
- ieee80211_key_unlock();
+ ieee80211_key_todo();
}
-void ieee80211_disable_keys(struct ieee80211_sub_if_data *sdata)
+void ieee80211_enable_keys(struct ieee80211_sub_if_data *sdata)
{
- struct ieee80211_key *key;
+ ASSERT_RTNL();
- might_sleep();
+ if (WARN_ON(!netif_running(sdata->dev)))
+ return;
- ieee80211_key_lock();
+ ieee80211_todo_for_each_key(sdata, KEY_FLAG_TODO_HWACCEL_ADD);
+}
- list_for_each_entry(key, &sdata->key_list, list)
- ieee80211_key_disable_hw_accel(key);
+void ieee80211_disable_keys(struct ieee80211_sub_if_data *sdata)
+{
+ ASSERT_RTNL();
- ieee80211_key_unlock();
+ ieee80211_todo_for_each_key(sdata, KEY_FLAG_TODO_HWACCEL_REMOVE);
}
-static void __ieee80211_key_free(struct ieee80211_key *key)
+static void __ieee80211_key_destroy(struct ieee80211_key *key)
{
if (!key)
return;
if (key->conf.alg == ALG_CCMP)
ieee80211_aes_key_free(key->u.ccmp.tfm);
+ if (key->conf.alg == ALG_AES_CMAC)
+ ieee80211_aes_cmac_key_free(key->u.aes_cmac.tfm);
ieee80211_debugfs_key_remove(key);
kfree(key);
list_del_init(&key->todo);
todoflags = key->flags & (KEY_FLAG_TODO_ADD_DEBUGFS |
KEY_FLAG_TODO_DEFKEY |
- KEY_FLAG_TODO_HWACCEL |
+ KEY_FLAG_TODO_DEFMGMTKEY |
+ KEY_FLAG_TODO_HWACCEL_ADD |
+ KEY_FLAG_TODO_HWACCEL_REMOVE |
KEY_FLAG_TODO_DELETE);
key->flags &= ~todoflags;
spin_unlock(&todo_lock);
ieee80211_debugfs_key_add_default(key->sdata);
work_done = true;
}
- if (todoflags & KEY_FLAG_TODO_HWACCEL) {
+ if (todoflags & KEY_FLAG_TODO_DEFMGMTKEY) {
+ ieee80211_debugfs_key_remove_mgmt_default(key->sdata);
+ ieee80211_debugfs_key_add_mgmt_default(key->sdata);
+ work_done = true;
+ }
+ if (todoflags & KEY_FLAG_TODO_HWACCEL_ADD) {
ieee80211_key_enable_hw_accel(key);
work_done = true;
}
+ if (todoflags & KEY_FLAG_TODO_HWACCEL_REMOVE) {
+ ieee80211_key_disable_hw_accel(key);
+ work_done = true;
+ }
if (todoflags & KEY_FLAG_TODO_DELETE) {
- __ieee80211_key_free(key);
+ __ieee80211_key_destroy(key);
work_done = true;
}
void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata)
{
struct ieee80211_key *key, *tmp;
- LIST_HEAD(tmp_list);
+ unsigned long flags;
ieee80211_key_lock();
ieee80211_debugfs_key_remove_default(sdata);
+ ieee80211_debugfs_key_remove_mgmt_default(sdata);
+ spin_lock_irqsave(&sdata->local->key_lock, flags);
list_for_each_entry_safe(key, tmp, &sdata->key_list, list)
- ieee80211_key_free(key);
+ __ieee80211_key_free(key);
+ spin_unlock_irqrestore(&sdata->local->key_lock, flags);
__ieee80211_key_todo();