aboutsummaryrefslogtreecommitdiffstats
path: root/net/mac80211/key.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/mac80211/key.c')
-rw-r--r--net/mac80211/key.c212
1 files changed, 188 insertions, 24 deletions
diff --git a/net/mac80211/key.c b/net/mac80211/key.c
index f825e2f..5bb600d 100644
--- a/net/mac80211/key.c
+++ b/net/mac80211/key.c
@@ -15,7 +15,9 @@
#include <linux/rcupdate.h>
#include <linux/rtnetlink.h>
#include <linux/slab.h>
+#include <linux/export.h>
#include <net/mac80211.h>
+#include <asm/unaligned.h>
#include "ieee80211_i.h"
#include "driver-ops.h"
#include "debugfs_key.h"
@@ -53,18 +55,40 @@ static void assert_key_lock(struct ieee80211_local *local)
lockdep_assert_held(&local->key_mtx);
}
-static struct ieee80211_sta *get_sta_for_key(struct ieee80211_key *key)
+static void increment_tailroom_need_count(struct ieee80211_sub_if_data *sdata)
{
- if (key->sta)
- return &key->sta->sta;
+ /*
+ * When this count is zero, SKB resizing for allocating tailroom
+ * for IV or MMIC is skipped. But, this check has created two race
+ * cases in xmit path while transiting from zero count to one:
+ *
+ * 1. SKB resize was skipped because no key was added but just before
+ * the xmit key is added and SW encryption kicks off.
+ *
+ * 2. SKB resize was skipped because all the keys were hw planted but
+ * just before xmit one of the key is deleted and SW encryption kicks
+ * off.
+ *
+ * In both the above case SW encryption will find not enough space for
+ * tailroom and exits with WARN_ON. (See WARN_ONs at wpa.c)
+ *
+ * Solution has been explained at
+ * http://mid.gmane.org/1308590980.4322.19.camel@jlt3.sipsolutions.net
+ */
- return NULL;
+ if (!sdata->crypto_tx_tailroom_needed_cnt++) {
+ /*
+ * Flush all XMIT packets currently using HW encryption or no
+ * encryption at all if the count transition is from 0 -> 1.
+ */
+ synchronize_net();
+ }
}
static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
{
struct ieee80211_sub_if_data *sdata;
- struct ieee80211_sta *sta;
+ struct sta_info *sta;
int ret;
might_sleep();
@@ -74,7 +98,7 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
assert_key_lock(key->local);
- sta = get_sta_for_key(key);
+ sta = key->sta;
/*
* If this is a per-STA GTK, check if it
@@ -84,6 +108,9 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
!(key->local->hw.flags & IEEE80211_HW_SUPPORTS_PER_STA_GTK))
goto out_unsupported;
+ if (sta && !sta->uploaded)
+ goto out_unsupported;
+
sdata = key->sdata;
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
/*
@@ -92,22 +119,30 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
*/
if (!(key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE))
goto out_unsupported;
- sdata = container_of(sdata->bss,
- struct ieee80211_sub_if_data,
- u.ap);
}
- ret = drv_set_key(key->local, SET_KEY, sdata, sta, &key->conf);
+ ret = drv_set_key(key->local, SET_KEY, sdata,
+ sta ? &sta->sta : NULL, &key->conf);
if (!ret) {
key->flags |= KEY_FLAG_UPLOADED_TO_HARDWARE;
+
+ if (!((key->conf.flags & IEEE80211_KEY_FLAG_GENERATE_MMIC) ||
+ (key->conf.flags & IEEE80211_KEY_FLAG_GENERATE_IV) ||
+ (key->conf.flags & IEEE80211_KEY_FLAG_PUT_IV_SPACE)))
+ sdata->crypto_tx_tailroom_needed_cnt--;
+
+ WARN_ON((key->conf.flags & IEEE80211_KEY_FLAG_PUT_IV_SPACE) &&
+ (key->conf.flags & IEEE80211_KEY_FLAG_GENERATE_IV));
+
return 0;
}
if (ret != -ENOSPC && ret != -EOPNOTSUPP)
wiphy_err(key->local->hw.wiphy,
"failed to set key (%d, %pM) to hardware (%d)\n",
- key->conf.keyidx, sta ? sta->addr : bcast_addr, ret);
+ key->conf.keyidx,
+ sta ? sta->sta.addr : bcast_addr, ret);
out_unsupported:
switch (key->conf.cipher) {
@@ -126,7 +161,7 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
{
struct ieee80211_sub_if_data *sdata;
- struct ieee80211_sta *sta;
+ struct sta_info *sta;
int ret;
might_sleep();
@@ -139,21 +174,22 @@ static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
if (!(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE))
return;
- sta = get_sta_for_key(key);
+ sta = key->sta;
sdata = key->sdata;
- if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
- sdata = container_of(sdata->bss,
- struct ieee80211_sub_if_data,
- u.ap);
+ if (!((key->conf.flags & IEEE80211_KEY_FLAG_GENERATE_MMIC) ||
+ (key->conf.flags & IEEE80211_KEY_FLAG_GENERATE_IV) ||
+ (key->conf.flags & IEEE80211_KEY_FLAG_PUT_IV_SPACE)))
+ increment_tailroom_need_count(sdata);
ret = drv_set_key(key->local, DISABLE_KEY, sdata,
- sta, &key->conf);
+ sta ? &sta->sta : NULL, &key->conf);
if (ret)
wiphy_err(key->local->hw.wiphy,
"failed to remove key (%d, %pM) from hardware (%d)\n",
- key->conf.keyidx, sta ? sta->addr : bcast_addr, ret);
+ key->conf.keyidx,
+ sta ? sta->sta.addr : bcast_addr, ret);
key->flags &= ~KEY_FLAG_UPLOADED_TO_HARDWARE;
}
@@ -239,7 +275,7 @@ static void __ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
bool defunikey, defmultikey, defmgmtkey;
if (new)
- list_add(&new->list, &sdata->key_list);
+ list_add_tail(&new->list, &sdata->key_list);
if (sta && pairwise) {
rcu_assign_pointer(sta->ptk, new);
@@ -330,6 +366,7 @@ struct ieee80211_key *ieee80211_key_alloc(u32 cipher, int idx, size_t key_len,
get_unaligned_le16(seq);
}
}
+ spin_lock_init(&key->u.tkip.txlock);
break;
case WLAN_CIPHER_SUITE_CCMP:
key->conf.iv_len = CCMP_HDR_LEN;
@@ -394,8 +431,10 @@ static void __ieee80211_key_destroy(struct ieee80211_key *key)
ieee80211_aes_key_free(key->u.ccmp.tfm);
if (key->conf.cipher == WLAN_CIPHER_SUITE_AES_CMAC)
ieee80211_aes_cmac_key_free(key->u.aes_cmac.tfm);
- if (key->local)
+ if (key->local) {
ieee80211_debugfs_key_remove(key);
+ key->sdata->crypto_tx_tailroom_needed_cnt--;
+ }
kfree(key);
}
@@ -422,7 +461,7 @@ int ieee80211_key_link(struct ieee80211_key *key,
* some hardware cannot handle TKIP with QoS, so
* we indicate whether QoS could be in use.
*/
- if (test_sta_flags(sta, WLAN_STA_WME))
+ if (test_sta_flag(sta, WLAN_STA_WME))
key->conf.flags |= IEEE80211_KEY_FLAG_WMM_STA;
} else {
if (sdata->vif.type == NL80211_IFTYPE_STATION) {
@@ -436,7 +475,7 @@ int ieee80211_key_link(struct ieee80211_key *key,
/* same here, the AP could be using QoS */
ap = sta_info_get(key->sdata, key->sdata->u.mgd.bssid);
if (ap) {
- if (test_sta_flags(ap, WLAN_STA_WME))
+ if (test_sta_flag(ap, WLAN_STA_WME))
key->conf.flags |=
IEEE80211_KEY_FLAG_WMM_STA;
}
@@ -452,6 +491,8 @@ int ieee80211_key_link(struct ieee80211_key *key,
else
old_key = key_mtx_dereference(sdata->local, sdata->keys[idx]);
+ increment_tailroom_need_count(sdata);
+
__ieee80211_key_replace(sdata, sta, pairwise, old_key, key);
__ieee80211_key_destroy(old_key);
@@ -498,12 +539,49 @@ void ieee80211_enable_keys(struct ieee80211_sub_if_data *sdata)
mutex_lock(&sdata->local->key_mtx);
- list_for_each_entry(key, &sdata->key_list, list)
+ sdata->crypto_tx_tailroom_needed_cnt = 0;
+
+ list_for_each_entry(key, &sdata->key_list, list) {
+ increment_tailroom_need_count(sdata);
ieee80211_key_enable_hw_accel(key);
+ }
mutex_unlock(&sdata->local->key_mtx);
}
+void ieee80211_iter_keys(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ void (*iter)(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta,
+ struct ieee80211_key_conf *key,
+ void *data),
+ void *iter_data)
+{
+ struct ieee80211_local *local = hw_to_local(hw);
+ struct ieee80211_key *key;
+ struct ieee80211_sub_if_data *sdata;
+
+ ASSERT_RTNL();
+
+ mutex_lock(&local->key_mtx);
+ if (vif) {
+ sdata = vif_to_sdata(vif);
+ list_for_each_entry(key, &sdata->key_list, list)
+ iter(hw, &sdata->vif,
+ key->sta ? &key->sta->sta : NULL,
+ &key->conf, iter_data);
+ } else {
+ list_for_each_entry(sdata, &local->interfaces, list)
+ list_for_each_entry(key, &sdata->key_list, list)
+ iter(hw, &sdata->vif,
+ key->sta ? &key->sta->sta : NULL,
+ &key->conf, iter_data);
+ }
+ mutex_unlock(&local->key_mtx);
+}
+EXPORT_SYMBOL(ieee80211_iter_keys);
+
void ieee80211_disable_keys(struct ieee80211_sub_if_data *sdata)
{
struct ieee80211_key *key;
@@ -533,3 +611,89 @@ void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata)
mutex_unlock(&sdata->local->key_mtx);
}
+
+
+void ieee80211_gtk_rekey_notify(struct ieee80211_vif *vif, const u8 *bssid,
+ const u8 *replay_ctr, gfp_t gfp)
+{
+ struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+
+ trace_api_gtk_rekey_notify(sdata, bssid, replay_ctr);
+
+ cfg80211_gtk_rekey_notify(sdata->dev, bssid, replay_ctr, gfp);
+}
+EXPORT_SYMBOL_GPL(ieee80211_gtk_rekey_notify);
+
+void ieee80211_get_key_tx_seq(struct ieee80211_key_conf *keyconf,
+ struct ieee80211_key_seq *seq)
+{
+ struct ieee80211_key *key;
+ u64 pn64;
+
+ if (WARN_ON(!(keyconf->flags & IEEE80211_KEY_FLAG_GENERATE_IV)))
+ return;
+
+ key = container_of(keyconf, struct ieee80211_key, conf);
+
+ switch (key->conf.cipher) {
+ case WLAN_CIPHER_SUITE_TKIP:
+ seq->tkip.iv32 = key->u.tkip.tx.iv32;
+ seq->tkip.iv16 = key->u.tkip.tx.iv16;
+ break;
+ case WLAN_CIPHER_SUITE_CCMP:
+ pn64 = atomic64_read(&key->u.ccmp.tx_pn);
+ seq->ccmp.pn[5] = pn64;
+ seq->ccmp.pn[4] = pn64 >> 8;
+ seq->ccmp.pn[3] = pn64 >> 16;
+ seq->ccmp.pn[2] = pn64 >> 24;
+ seq->ccmp.pn[1] = pn64 >> 32;
+ seq->ccmp.pn[0] = pn64 >> 40;
+ break;
+ case WLAN_CIPHER_SUITE_AES_CMAC:
+ pn64 = atomic64_read(&key->u.aes_cmac.tx_pn);
+ seq->ccmp.pn[5] = pn64;
+ seq->ccmp.pn[4] = pn64 >> 8;
+ seq->ccmp.pn[3] = pn64 >> 16;
+ seq->ccmp.pn[2] = pn64 >> 24;
+ seq->ccmp.pn[1] = pn64 >> 32;
+ seq->ccmp.pn[0] = pn64 >> 40;
+ break;
+ default:
+ WARN_ON(1);
+ }
+}
+EXPORT_SYMBOL(ieee80211_get_key_tx_seq);
+
+void ieee80211_get_key_rx_seq(struct ieee80211_key_conf *keyconf,
+ int tid, struct ieee80211_key_seq *seq)
+{
+ struct ieee80211_key *key;
+ const u8 *pn;
+
+ key = container_of(keyconf, struct ieee80211_key, conf);
+
+ switch (key->conf.cipher) {
+ case WLAN_CIPHER_SUITE_TKIP:
+ if (WARN_ON(tid < 0 || tid >= NUM_RX_DATA_QUEUES))
+ return;
+ seq->tkip.iv32 = key->u.tkip.rx[tid].iv32;
+ seq->tkip.iv16 = key->u.tkip.rx[tid].iv16;
+ break;
+ case WLAN_CIPHER_SUITE_CCMP:
+ if (WARN_ON(tid < -1 || tid >= NUM_RX_DATA_QUEUES))
+ return;
+ if (tid < 0)
+ pn = key->u.ccmp.rx_pn[NUM_RX_DATA_QUEUES];
+ else
+ pn = key->u.ccmp.rx_pn[tid];
+ memcpy(seq->ccmp.pn, pn, CCMP_PN_LEN);
+ break;
+ case WLAN_CIPHER_SUITE_AES_CMAC:
+ if (WARN_ON(tid != 0))
+ return;
+ pn = key->u.aes_cmac.rx_pn;
+ memcpy(seq->aes_cmac.pn, pn, CMAC_PN_LEN);
+ break;
+ }
+}
+EXPORT_SYMBOL(ieee80211_get_key_rx_seq);