summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorStefan Sperling <stsp@cvs.openbsd.org>2020-06-19 11:12:47 +0000
committerStefan Sperling <stsp@cvs.openbsd.org>2020-06-19 11:12:47 +0000
commit8d27665236e337950ab5ab94c78d3541330b705e (patch)
tree20fa175ca00bd037c200e073eae3cba0439d5967 /sys
parentbaa2d8d2d384d0c9d433a5f25c2c4c7f8d304eab (diff)
Add WPA2 (CCMP) crypto offload support to iwx(4).
Much thanks to Sara Sharon who helped me by providing hints about new firmware behaviour. Contrary to older iwn(4) and iwm(4) devices, key material is no longer part of the Tx command. Instead, firmware will encrypt outgoing traffic as soon as the station associated with a Tx queue has an encryption key configured. Each Tx queue is created with an associated station ID (which in our case is a constant and represents the AP) and a traffic identifier (TID). The driver was configuring data Tx queues with the "management TID". This magic TID value bypasses hardware encryption and resulted in plaintext frames being sent while received frames were decrypted as expected since the station had a key. This behaviour looked rather strange and was difficult for me to debug. The clues which Sara provided led to the solution: iwx(4) must configure data Tx queues with the "non-QOS TID" in order to allow for encryption in the firmware's data Tx path. The rest of the offload mechanism works as it did on iwn(4) and iwm(4). Tested by sven falempin and myself.
Diffstat (limited to 'sys')
-rw-r--r--sys/dev/pci/if_iwx.c195
-rw-r--r--sys/dev/pci/if_iwxreg.h46
2 files changed, 216 insertions, 25 deletions
diff --git a/sys/dev/pci/if_iwx.c b/sys/dev/pci/if_iwx.c
index 802619b1f16..1fb37d59725 100644
--- a/sys/dev/pci/if_iwx.c
+++ b/sys/dev/pci/if_iwx.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: if_iwx.c,v 1.27 2020/06/17 08:18:21 stsp Exp $ */
+/* $OpenBSD: if_iwx.c,v 1.28 2020/06/19 11:12:46 stsp Exp $ */
/*
* Copyright (c) 2014, 2016 genua gmbh <info@genua.de>
@@ -349,8 +349,10 @@ int iwx_rxmq_get_signal_strength(struct iwx_softc *, struct iwx_rx_mpdu_desc *);
void iwx_rx_rx_phy_cmd(struct iwx_softc *, struct iwx_rx_packet *,
struct iwx_rx_data *);
int iwx_get_noise(const struct iwx_statistics_rx_non_phy *);
-void iwx_rx_frame(struct iwx_softc *, struct mbuf *, int, int, int, uint32_t,
- struct ieee80211_rxinfo *, struct mbuf_list *);
+int iwx_ccmp_decap(struct iwx_softc *, struct mbuf *,
+ struct ieee80211_node *);
+void iwx_rx_frame(struct iwx_softc *, struct mbuf *, int, uint32_t, int, int,
+ uint32_t, struct ieee80211_rxinfo *, struct mbuf_list *);
void iwx_enable_ht_cck_fallback(struct iwx_softc *, struct iwx_node *);
void iwx_rx_tx_cmd_single(struct iwx_softc *, struct iwx_rx_packet *,
struct iwx_node *, int, int);
@@ -420,6 +422,10 @@ int iwx_disassoc(struct iwx_softc *);
int iwx_run(struct iwx_softc *);
int iwx_run_stop(struct iwx_softc *);
struct ieee80211_node *iwx_node_alloc(struct ieee80211com *);
+int iwx_set_key(struct ieee80211com *, struct ieee80211_node *,
+ struct ieee80211_key *);
+void iwx_delete_key(struct ieee80211com *,
+ struct ieee80211_node *, struct ieee80211_key *);
void iwx_calib_timeout(void *);
int iwx_media_change(struct ifnet *);
void iwx_newstate_task(void *);
@@ -3538,16 +3544,65 @@ iwx_get_noise(const struct iwx_statistics_rx_non_phy *stats)
return (nbant == 0) ? -127 : (total / nbant) - 107;
}
+int
+iwx_ccmp_decap(struct iwx_softc *sc, struct mbuf *m, struct ieee80211_node *ni)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_key *k = &ni->ni_pairwise_key;
+ struct ieee80211_frame *wh;
+ uint64_t pn, *prsc;
+ uint8_t *ivp;
+ uint8_t tid;
+ int hdrlen, hasqos;
+
+ wh = mtod(m, struct ieee80211_frame *);
+ hdrlen = ieee80211_get_hdrlen(wh);
+ ivp = (uint8_t *)wh + hdrlen;
+
+ /* Check that ExtIV bit is be set. */
+ if (!(ivp[3] & IEEE80211_WEP_EXTIV))
+ return 1;
+
+ hasqos = ieee80211_has_qos(wh);
+ tid = hasqos ? ieee80211_get_qos(wh) & IEEE80211_QOS_TID : 0;
+ prsc = &k->k_rsc[tid];
+
+ /* Extract the 48-bit PN from the CCMP header. */
+ pn = (uint64_t)ivp[0] |
+ (uint64_t)ivp[1] << 8 |
+ (uint64_t)ivp[4] << 16 |
+ (uint64_t)ivp[5] << 24 |
+ (uint64_t)ivp[6] << 32 |
+ (uint64_t)ivp[7] << 40;
+ if (pn <= *prsc) {
+ ic->ic_stats.is_ccmp_replays++;
+ return 1;
+ }
+ /* Last seen packet number is updated in ieee80211_inputm(). */
+
+ /*
+ * Some firmware versions strip the MIC, and some don't. It is not
+ * clear which of the capability flags could tell us what to expect.
+ * For now, keep things simple and just leave the MIC in place if
+ * it is present.
+ *
+ * The IV will be stripped by ieee80211_inputm().
+ */
+ return 0;
+}
+
void
iwx_rx_frame(struct iwx_softc *sc, struct mbuf *m, int chanidx,
- int is_shortpre, int rate_n_flags, uint32_t device_timestamp,
- struct ieee80211_rxinfo *rxi, struct mbuf_list *ml)
+ uint32_t rx_pkt_status, int is_shortpre, int rate_n_flags,
+ uint32_t device_timestamp, struct ieee80211_rxinfo *rxi,
+ struct mbuf_list *ml)
{
struct ieee80211com *ic = &sc->sc_ic;
struct ieee80211_frame *wh;
struct ieee80211_node *ni;
struct ieee80211_channel *bss_chan;
uint8_t saved_bssid[IEEE80211_ADDR_LEN] = { 0 };
+ struct ifnet *ifp = IC2IFP(ic);
if (chanidx < 0 || chanidx >= nitems(ic->ic_channels))
chanidx = ieee80211_chan2ieee(ic, ic->ic_ibss_chan);
@@ -3564,6 +3619,40 @@ iwx_rx_frame(struct iwx_softc *sc, struct mbuf *m, int chanidx,
}
ni->ni_chan = &ic->ic_channels[chanidx];
+ /* Handle hardware decryption. */
+ if (((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) != IEEE80211_FC0_TYPE_CTL)
+ && (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) &&
+ (ni->ni_flags & IEEE80211_NODE_RXPROT) &&
+ ni->ni_pairwise_key.k_cipher == IEEE80211_CIPHER_CCMP) {
+ if ((rx_pkt_status & IWX_RX_MPDU_RES_STATUS_SEC_ENC_MSK) !=
+ IWX_RX_MPDU_RES_STATUS_SEC_CCM_ENC) {
+ ic->ic_stats.is_ccmp_dec_errs++;
+ ifp->if_ierrors++;
+ m_freem(m);
+ ieee80211_release_node(ic, ni);
+ return;
+ }
+ /* Check whether decryption was successful or not. */
+ if ((rx_pkt_status &
+ (IWX_RX_MPDU_RES_STATUS_DEC_DONE |
+ IWX_RX_MPDU_RES_STATUS_MIC_OK)) !=
+ (IWX_RX_MPDU_RES_STATUS_DEC_DONE |
+ IWX_RX_MPDU_RES_STATUS_MIC_OK)) {
+ ic->ic_stats.is_ccmp_dec_errs++;
+ ifp->if_ierrors++;
+ m_freem(m);
+ ieee80211_release_node(ic, ni);
+ return;
+ }
+ if (iwx_ccmp_decap(sc, m, ni) != 0) {
+ ifp->if_ierrors++;
+ m_freem(m);
+ ieee80211_release_node(ic, ni);
+ return;
+ }
+ rxi->rxi_flags |= IEEE80211_RXI_HWDEC;
+ }
+
#if NBPFILTER > 0
if (sc->sc_drvbpf != NULL) {
struct iwx_rx_radiotap_header *tap = &sc->sc_rxtap;
@@ -3685,6 +3774,14 @@ iwx_rx_mpdu_mq(struct iwx_softc *sc, struct mbuf *m, void *pktdata,
}
} else
hdrlen = ieee80211_get_hdrlen(wh);
+
+ if ((le16toh(desc->status) &
+ IWX_RX_MPDU_RES_STATUS_SEC_ENC_MSK) ==
+ IWX_RX_MPDU_RES_STATUS_SEC_CCM_ENC) {
+ /* Padding is inserted after the IV. */
+ hdrlen += IEEE80211_CCMP_HDRLEN;
+ }
+
memmove(m->m_data + 2, m->m_data, hdrlen);
m_adj(m, 2);
}
@@ -3702,7 +3799,7 @@ iwx_rx_mpdu_mq(struct iwx_softc *sc, struct mbuf *m, void *pktdata,
rxi.rxi_rssi = rssi;
rxi.rxi_tstamp = le64toh(desc->v1.tsf_on_air_rise);
- iwx_rx_frame(sc, m, chanidx,
+ iwx_rx_frame(sc, m, chanidx, le16toh(desc->status),
(phy_info & IWX_RX_MPDU_PHY_SHORT_PREAMBLE),
rate_n_flags, device_timestamp, &rxi, ml);
}
@@ -4316,7 +4413,6 @@ iwx_tx_fill_cmd(struct iwx_softc *sc, struct iwx_node *in,
ridx = min_ridx;
}
- flags |= IWX_TX_FLAGS_ENCRYPT_DIS;
if ((ic->ic_flags & IEEE80211_F_RSNON) &&
ni->ni_rsn_supp_state == RSNA_SUPP_PTKNEGOTIATING)
flags |= IWX_TX_FLAGS_HIGH_PRI;
@@ -4449,11 +4545,19 @@ iwx_tx(struct iwx_softc *sc, struct mbuf *m, struct ieee80211_node *ni, int ac)
if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
k = ieee80211_get_txkey(ic, wh, ni);
- if ((m = ieee80211_encrypt(ic, m, k)) == NULL)
- return ENOBUFS;
- /* 802.11 header may have moved. */
- wh = mtod(m, struct ieee80211_frame *);
- }
+ if (k->k_cipher != IEEE80211_CIPHER_CCMP) {
+ if ((m = ieee80211_encrypt(ic, m, k)) == NULL)
+ return ENOBUFS;
+ /* 802.11 header may have moved. */
+ wh = mtod(m, struct ieee80211_frame *);
+ tx->flags |= htole32(IWX_TX_FLAGS_ENCRYPT_DIS);
+ } else {
+ k->k_tsc++;
+ /* Hardware increments PN internally and adds IV. */
+ }
+ } else
+ tx->flags |= htole32(IWX_TX_FLAGS_ENCRYPT_DIS);
+
totlen = m->m_pkthdr.len;
if (hdrlen & 3) {
@@ -5737,7 +5841,7 @@ iwx_enable_data_tx_queues(struct iwx_softc *sc)
* Regular data frames use the "MGMT" TID and queue.
* Other TIDs and queues are reserved for frame aggregation.
*/
- err = iwx_enable_txq(sc, IWX_STATION_ID, qid, IWX_MGMT_TID,
+ err = iwx_enable_txq(sc, IWX_STATION_ID, qid, IWX_TID_NON_QOS,
IWX_TX_RING_COUNT);
if (err) {
printf("%s: could not enable Tx queue %d (error %d)\n",
@@ -6216,6 +6320,68 @@ iwx_node_alloc(struct ieee80211com *ic)
return malloc(sizeof (struct iwx_node), M_DEVBUF, M_NOWAIT | M_ZERO);
}
+int
+iwx_set_key(struct ieee80211com *ic, struct ieee80211_node *ni,
+ struct ieee80211_key *k)
+{
+ struct iwx_softc *sc = ic->ic_softc;
+ struct iwx_add_sta_key_cmd cmd;
+
+ if (k->k_cipher != IEEE80211_CIPHER_CCMP) {
+ /* Fallback to software crypto for other ciphers. */
+ return (ieee80211_set_key(ic, ni, k));
+ }
+
+ memset(&cmd, 0, sizeof(cmd));
+
+ cmd.common.key_flags = htole16(IWX_STA_KEY_FLG_CCM |
+ IWX_STA_KEY_FLG_WEP_KEY_MAP |
+ ((k->k_id << IWX_STA_KEY_FLG_KEYID_POS) &
+ IWX_STA_KEY_FLG_KEYID_MSK));
+ if (k->k_flags & IEEE80211_KEY_GROUP) {
+ cmd.common.key_offset = 1;
+ cmd.common.key_flags |= htole16(IWX_STA_KEY_MULTICAST);
+ } else
+ cmd.common.key_offset = 0;
+
+ memcpy(cmd.common.key, k->k_key, MIN(sizeof(cmd.common.key), k->k_len));
+ cmd.common.sta_id = IWX_STATION_ID;
+
+ cmd.transmit_seq_cnt = htole64(k->k_tsc);
+
+ return iwx_send_cmd_pdu(sc, IWX_ADD_STA_KEY, IWX_CMD_ASYNC,
+ sizeof(cmd), &cmd);
+}
+
+void
+iwx_delete_key(struct ieee80211com *ic, struct ieee80211_node *ni,
+ struct ieee80211_key *k)
+{
+ struct iwx_softc *sc = ic->ic_softc;
+ struct iwx_add_sta_key_cmd cmd;
+
+ if (k->k_cipher != IEEE80211_CIPHER_CCMP) {
+ /* Fallback to software crypto for other ciphers. */
+ ieee80211_delete_key(ic, ni, k);
+ return;
+ }
+
+ memset(&cmd, 0, sizeof(cmd));
+
+ cmd.common.key_flags = htole16(IWX_STA_KEY_NOT_VALID |
+ IWX_STA_KEY_FLG_NO_ENC | IWX_STA_KEY_FLG_WEP_KEY_MAP |
+ ((k->k_id << IWX_STA_KEY_FLG_KEYID_POS) &
+ IWX_STA_KEY_FLG_KEYID_MSK));
+ memcpy(cmd.common.key, k->k_key, MIN(sizeof(cmd.common.key), k->k_len));
+ if (k->k_flags & IEEE80211_KEY_GROUP)
+ cmd.common.key_offset = 1;
+ else
+ cmd.common.key_offset = 0;
+ cmd.common.sta_id = IWX_STATION_ID;
+
+ iwx_send_cmd_pdu(sc, IWX_ADD_STA_KEY, IWX_CMD_ASYNC, sizeof(cmd), &cmd);
+}
+
void
iwx_calib_timeout(void *arg)
{
@@ -7452,6 +7618,7 @@ iwx_rx_pkt(struct iwx_softc *sc, struct iwx_rx_data *data, struct mbuf_list *ml)
case IWX_WIDE_ID(IWX_REGULATORY_AND_NVM_GROUP,
IWX_NVM_GET_INFO):
+ case IWX_ADD_STA_KEY:
case IWX_PHY_CONFIGURATION_CMD:
case IWX_TX_ANT_CONFIGURATION_CMD:
case IWX_ADD_STA:
@@ -8228,6 +8395,8 @@ iwx_attach(struct device *parent, struct device *self, void *aux)
/* TODO: background scans trigger firmware errors */
ic->ic_bgscan_start = iwx_bgscan;
#endif
+ ic->ic_set_key = iwx_set_key;
+ ic->ic_delete_key = iwx_delete_key;
/* Override 802.11 state transition machine. */
sc->sc_newstate = ic->ic_newstate;
diff --git a/sys/dev/pci/if_iwxreg.h b/sys/dev/pci/if_iwxreg.h
index f132fc7deb8..1454092b006 100644
--- a/sys/dev/pci/if_iwxreg.h
+++ b/sys/dev/pci/if_iwxreg.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: if_iwxreg.h,v 1.10 2020/06/17 08:18:21 stsp Exp $ */
+/* $OpenBSD: if_iwxreg.h,v 1.11 2020/06/19 11:12:46 stsp Exp $ */
/*-
* Based on BSD-licensed source modules in the Linux iwlwifi driver,
@@ -6023,7 +6023,7 @@ struct iwx_umac_scan_iter_complete_notif {
* @IWX_STA_KEY_FLG_KEYID_MSK: the index of the key
* @IWX_STA_KEY_NOT_VALID: key is invalid
* @IWX_STA_KEY_FLG_WEP_13BYTES: set for 13 bytes WEP key
- * @IWX_STA_KEY_MULTICAST: set for multical key
+ * @IWX_STA_KEY_MULTICAST: set for multicast key
* @IWX_STA_KEY_MFP: key is used for Management Frame Protection
*/
#define IWX_STA_KEY_FLG_NO_ENC (0 << 0)
@@ -6217,28 +6217,49 @@ struct iwx_add_sta_cmd {
#define IWX_STA_AUX_ACTIVITY 4
/**
- * struct iwx_add_sta_key_cmd - add/modify sta key
- * ( IWX_REPLY_ADD_STA_KEY = 0x17 )
+ * struct iwx_add_sta_key_common - add/modify sta key common part
+ * ( REPLY_ADD_STA_KEY = 0x17 )
* @sta_id: index of station in uCode's station table
* @key_offset: key offset in key storage
- * @key_flags: type %iwx_sta_key_flag
+ * @key_flags: IWX_STA_KEY_FLG_*
* @key: key material data
- * @key2: key material data
* @rx_secur_seq_cnt: RX security sequence counter for the key
- * @tkip_rx_tsc_byte2: TSC[2] for key mix ph1 detection
- * @tkip_rx_ttak: 10-byte unicast TKIP TTAK for Rx
*/
-struct iwx_add_sta_key_cmd {
+struct iwx_add_sta_key_common {
uint8_t sta_id;
uint8_t key_offset;
uint16_t key_flags;
- uint8_t key[16];
- uint8_t key2[16];
+ uint8_t key[32];
uint8_t rx_secur_seq_cnt[16];
+} __packed;
+
+/**
+ * struct iwx_add_sta_key_cmd_v1 - add/modify sta key
+ * @common: see &struct iwx_add_sta_key_common
+ * @tkip_rx_tsc_byte2: TSC[2] for key mix ph1 detection
+ * @reserved: reserved
+ * @tkip_rx_ttak: 10-byte unicast TKIP TTAK for Rx
+ */
+struct iwx_add_sta_key_cmd_v1 {
+ struct iwx_add_sta_key_common common;
uint8_t tkip_rx_tsc_byte2;
uint8_t reserved;
uint16_t tkip_rx_ttak[5];
-} __packed; /* IWX_ADD_MODIFY_STA_KEY_API_S_VER_1 */
+} __packed; /* ADD_MODIFY_STA_KEY_API_S_VER_1 */
+
+/**
+ * struct iwx_add_sta_key_cmd - add/modify sta key
+ * @common: see &struct iwx_add_sta_key_common
+ * @rx_mic_key: TKIP RX unicast or multicast key
+ * @tx_mic_key: TKIP TX key
+ * @transmit_seq_cnt: TSC, transmit packet number
+ */
+struct iwx_add_sta_key_cmd {
+ struct iwx_add_sta_key_common common;
+ uint64_t rx_mic_key;
+ uint64_t tx_mic_key;
+ uint64_t transmit_seq_cnt;
+} __packed; /* ADD_MODIFY_STA_KEY_API_S_VER_2 */
/**
* status in the response to ADD_STA command
@@ -6579,6 +6600,7 @@ struct iwx_rx_packet {
#define IWX_FH_RSCSR_FRAME_INVALID 0x55550000
#define IWX_FH_RSCSR_FRAME_ALIGN 0x40
#define IWX_FH_RSCSR_RPA_EN (1 << 25)
+#define IWX_FH_RSCSR_RADA_EN (1 << 26)
#define IWX_FH_RSCSR_RXQ_POS 16
#define IWX_FH_RSCSR_RXQ_MASK 0x3F0000