wifi: mac80211: optionally implement MLO multicast TX

For drivers using software encryption for multicast TX, such
as mac80211_hwsim, mac80211 needs to duplicate the multicast
frames on each link, if MLO is enabled. Do this, but don't
just make it dependent on the key but provide a separate flag
for drivers to opt out of this.

This is not very efficient, I expect that drivers will do it
in firmware/hardware or at least with DMA engine assistence,
so this is mostly for hwsim.

To make this work, also implement the SNS11 sequence number
space that an AP MLD shall have, and modify the API to the
__ieee80211_subif_start_xmit() function to always require the
link ID bits to be set.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
Johannes Berg 2022-07-21 10:56:48 +02:00
parent e1e68b14c5
commit 963d0e8d08
5 changed files with 97 additions and 11 deletions

View file

@ -882,6 +882,8 @@ enum mac80211_tx_info_flags {
* @IEEE80211_TX_CTRL_DONT_REORDER: This frame should not be reordered
* relative to other frames that have this flag set, independent
* of their QoS TID or other priority field values.
* @IEEE80211_TX_CTRL_MCAST_MLO_FIRST_TX: first MLO TX, used mostly internally
* for sequence number assignment
* @IEEE80211_TX_CTRL_MLO_LINK: If not @IEEE80211_LINK_UNSPECIFIED, this
* frame should be transmitted on the specific link. This really is
* only relevant for frames that do not have data present, and is
@ -901,10 +903,14 @@ enum mac80211_tx_control_flags {
IEEE80211_TX_INTCFL_NEED_TXPROCESSING = BIT(6),
IEEE80211_TX_CTRL_NO_SEQNO = BIT(7),
IEEE80211_TX_CTRL_DONT_REORDER = BIT(8),
IEEE80211_TX_CTRL_MCAST_MLO_FIRST_TX = BIT(9),
IEEE80211_TX_CTRL_MLO_LINK = 0xf0000000,
};
#define IEEE80211_LINK_UNSPECIFIED 0xf
#define IEEE80211_TX_CTRL_MLO_LINK_UNSPEC \
u32_encode_bits(IEEE80211_LINK_UNSPECIFIED, \
IEEE80211_TX_CTRL_MLO_LINK)
/**
* enum mac80211_tx_status_flags - flags to describe transmit status
@ -2524,6 +2530,9 @@ struct ieee80211_txq {
* @IEEE80211_HW_DETECTS_COLOR_COLLISION: HW/driver has support for BSS color
* collision detection and doesn't need it in software.
*
* @IEEE80211_HW_MLO_MCAST_MULTI_LINK_TX: Hardware/driver handles transmitting
* multicast frames on all links, mac80211 should not do that.
*
* @NUM_IEEE80211_HW_FLAGS: number of hardware flags, used for sizing arrays
*/
enum ieee80211_hw_flags {
@ -2580,6 +2589,7 @@ enum ieee80211_hw_flags {
IEEE80211_HW_SUPPORTS_RX_DECAP_OFFLOAD,
IEEE80211_HW_SUPPORTS_CONC_MON_RX_DECAP,
IEEE80211_HW_DETECTS_COLOR_COLLISION,
IEEE80211_HW_MLO_MCAST_MULTI_LINK_TX,
/* keep last, obviously */
NUM_IEEE80211_HW_FLAGS

View file

@ -4,7 +4,7 @@
*
* Copyright 2007 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2018 - 2019, 2021 Intel Corporation
* Copyright (C) 2018 - 2019, 2021-2022 Intel Corporation
*/
#include <linux/debugfs.h>
@ -495,6 +495,7 @@ static const char *hw_flag_names[] = {
FLAG(SUPPORTS_RX_DECAP_OFFLOAD),
FLAG(SUPPORTS_CONC_MON_RX_DECAP),
FLAG(DETECTS_COLOR_COLLISION),
FLAG(MLO_MCAST_MULTI_LINK_TX),
#undef FLAG
};

View file

@ -1029,6 +1029,7 @@ struct ieee80211_sub_if_data {
struct ieee80211_key __rcu *default_unicast_key;
u16 sequence_number;
u16 mld_mcast_seq;
__be16 control_port_protocol;
bool control_port_no_encrypt;
bool control_port_no_preauth;

View file

@ -1054,7 +1054,8 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev,
/* disable bottom halves when entering the Tx path */
local_bh_disable();
__ieee80211_subif_start_xmit(skb, dev, flags, 0, NULL);
__ieee80211_subif_start_xmit(skb, dev, flags,
IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, NULL);
local_bh_enable();
return ret;

View file

@ -822,6 +822,16 @@ ieee80211_tx_h_sequence(struct ieee80211_tx_data *tx)
if (info->control.flags & IEEE80211_TX_CTRL_NO_SEQNO)
return TX_CONTINUE;
/* SNS11 from 802.11be 10.3.2.14 */
if (unlikely(is_multicast_ether_addr(hdr->addr1) &&
info->control.vif->valid_links &&
info->control.vif->type == NL80211_IFTYPE_AP)) {
if (info->control.flags & IEEE80211_TX_CTRL_MCAST_MLO_FIRST_TX)
tx->sdata->mld_mcast_seq += 0x10;
hdr->seq_ctrl = cpu_to_le16(tx->sdata->mld_mcast_seq);
return TX_CONTINUE;
}
/*
* Anything but QoS data that has a sequence number field
* (is long enough) gets a sequence number from the global
@ -2570,7 +2580,7 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata,
struct ieee80211_chanctx_conf *chanctx_conf = NULL;
enum nl80211_band band;
int ret;
u8 link_id = IEEE80211_LINK_UNSPECIFIED;
u8 link_id = u32_get_bits(ctrl_flags, IEEE80211_TX_CTRL_MLO_LINK);
if (IS_ERR(sta))
sta = NULL;
@ -2630,8 +2640,18 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata,
goto free;
}
memcpy(hdr.addr2, link->conf->addr, ETH_ALEN);
} else {
} else if (link_id == IEEE80211_LINK_UNSPECIFIED) {
memcpy(hdr.addr2, sdata->vif.addr, ETH_ALEN);
} else {
struct ieee80211_bss_conf *conf;
conf = rcu_dereference(sdata->vif.link_conf[link_id]);
if (unlikely(!conf)) {
ret = -ENOLINK;
goto free;
}
memcpy(hdr.addr2, conf->addr, ETH_ALEN);
}
memcpy(hdr.addr3, skb->data + ETH_ALEN, ETH_ALEN);
@ -4222,9 +4242,6 @@ static bool ieee80211_multicast_to_unicast(struct sk_buff *skb,
const struct vlan_ethhdr *ethvlan = (void *)skb->data;
__be16 ethertype;
if (likely(!is_multicast_ether_addr(eth->h_dest)))
return false;
switch (sdata->vif.type) {
case NL80211_IFTYPE_AP_VLAN:
if (sdata->u.vlan.sta)
@ -4308,6 +4325,44 @@ ieee80211_convert_to_unicast(struct sk_buff *skb, struct net_device *dev,
rcu_read_unlock();
}
static void ieee80211_mlo_multicast_tx_one(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb, u32 ctrl_flags,
unsigned int link_id)
{
struct sk_buff *out;
out = skb_copy(skb, GFP_ATOMIC);
if (!out)
return;
ctrl_flags |= u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK);
__ieee80211_subif_start_xmit(out, sdata->dev, 0, ctrl_flags, NULL);
}
static void ieee80211_mlo_multicast_tx(struct net_device *dev,
struct sk_buff *skb)
{
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
unsigned long links = sdata->vif.valid_links;
unsigned int link;
u32 ctrl_flags = IEEE80211_TX_CTRL_MCAST_MLO_FIRST_TX;
if (hweight16(links) == 1) {
ctrl_flags |= u32_encode_bits(ffs(links) - 1,
IEEE80211_TX_CTRL_MLO_LINK);
__ieee80211_subif_start_xmit(skb, sdata->dev, 0, ctrl_flags,
NULL);
return;
}
for_each_set_bit(link, &links, IEEE80211_MLD_MAX_NUM_LINKS) {
ieee80211_mlo_multicast_tx_one(sdata, skb, ctrl_flags, link);
ctrl_flags = 0;
}
kfree_skb(skb);
}
/**
* ieee80211_subif_start_xmit - netif start_xmit function for 802.3 vifs
* @skb: packet to be sent
@ -4318,15 +4373,30 @@ ieee80211_convert_to_unicast(struct sk_buff *skb, struct net_device *dev,
netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
const struct ethhdr *eth = (void *)skb->data;
if (likely(!is_multicast_ether_addr(eth->h_dest)))
goto normal;
if (unlikely(ieee80211_multicast_to_unicast(skb, dev))) {
struct sk_buff_head queue;
__skb_queue_head_init(&queue);
ieee80211_convert_to_unicast(skb, dev, &queue);
while ((skb = __skb_dequeue(&queue)))
__ieee80211_subif_start_xmit(skb, dev, 0, 0, NULL);
__ieee80211_subif_start_xmit(skb, dev, 0,
IEEE80211_TX_CTRL_MLO_LINK_UNSPEC,
NULL);
} else if (sdata->vif.valid_links &&
sdata->vif.type == NL80211_IFTYPE_AP &&
!ieee80211_hw_check(&sdata->local->hw, MLO_MCAST_MULTI_LINK_TX)) {
ieee80211_mlo_multicast_tx(dev, skb);
} else {
__ieee80211_subif_start_xmit(skb, dev, 0, 0, NULL);
normal:
__ieee80211_subif_start_xmit(skb, dev, 0,
IEEE80211_TX_CTRL_MLO_LINK_UNSPEC,
NULL);
}
return NETDEV_TX_OK;
@ -4410,7 +4480,9 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata,
if (tid_tx) {
if (!test_bit(HT_AGG_STATE_OPERATIONAL, &tid_tx->state)) {
/* fall back to non-offload slow path */
__ieee80211_subif_start_xmit(skb, dev, 0, 0, NULL);
__ieee80211_subif_start_xmit(skb, dev, 0,
IEEE80211_TX_CTRL_MLO_LINK_UNSPEC,
NULL);
return;
}
@ -4512,7 +4584,8 @@ ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata,
goto out;
}
skb = ieee80211_build_hdr(sdata, skb, info_flags, sta, 0, NULL);
skb = ieee80211_build_hdr(sdata, skb, info_flags, sta,
IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, NULL);
if (IS_ERR(skb))
goto out;