Merge branch 'ethtool-rss-support-rss_set-via-netlink'

Jakub Kicinski says:

====================
ethtool: rss: support RSS_SET via Netlink

Support configuring RSS settings via Netlink.
Creating and removing contexts remains for the following series.

v2: https://lore.kernel.org/20250714222729.743282-1-kuba@kernel.org
v1: https://lore.kernel.org/20250711015303.3688717-1-kuba@kernel.org
====================

Link: https://patch.msgid.link/20250716000331.1378807-1-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski
2025-07-17 16:14:01 -07:00
12 changed files with 767 additions and 20 deletions

View File

@@ -158,6 +158,26 @@ definitions:
-
name: pse-event-sw-pw-control-error
doc: PSE faced an error managing the power control from software
-
name: input-xfrm
doc: RSS hash function transformations.
type: flags
enum-name:
name-prefix: rxh-xfrm-
header: linux/ethtool.h
entries:
-
name: sym-xor
doc: >-
XOR the corresponding source and destination fields of each specified
protocol. Both copies of the XOR'ed fields are fed into the RSS and
RXHASH calculation. Note that this XORing reduces the input set
entropy and could be exploited to reduce the RSS queue spread.
-
name: sym-or-xor
doc: >-
Similar to SYM_XOR, except that one copy of the XOR'ed fields is
replaced by an OR of the same fields.
-
name: rxfh-fields
name-prefix: rxh-
@@ -1621,6 +1641,7 @@ attribute-sets:
-
name: input-xfrm
type: u32
enum: input-xfrm
-
name: start-context
type: u32
@@ -2643,6 +2664,22 @@ operations:
attributes:
- header
- events
-
name: rss-set
doc: Set RSS params.
attribute-set: rss
do:
request:
attributes:
- header
- context
- hfunc
- indir
- hkey
- input-xfrm
- flow-hash
-
name: rss-ntf
doc: |

View File

@@ -239,6 +239,7 @@ Userspace to kernel:
``ETHTOOL_MSG_PHY_GET`` get Ethernet PHY information
``ETHTOOL_MSG_TSCONFIG_GET`` get hw timestamping configuration
``ETHTOOL_MSG_TSCONFIG_SET`` set hw timestamping configuration
``ETHTOOL_MSG_RSS_SET`` set RSS settings
===================================== =================================
Kernel to userspace:
@@ -292,6 +293,7 @@ Kernel to userspace:
``ETHTOOL_MSG_TSCONFIG_GET_REPLY`` hw timestamping configuration
``ETHTOOL_MSG_TSCONFIG_SET_REPLY`` new hw timestamping configuration
``ETHTOOL_MSG_PSE_NTF`` PSE events notification
``ETHTOOL_MSG_RSS_NTF`` RSS settings notification
======================================== =================================
``GET`` requests are sent by userspace applications to retrieve device
@@ -1989,6 +1991,29 @@ hfunc. Current supported options are symmetric-xor and symmetric-or-xor.
ETHTOOL_A_RSS_FLOW_HASH carries per-flow type bitmask of which header
fields are included in the hash calculation.
RSS_SET
=======
Request contents:
===================================== ====== ==============================
``ETHTOOL_A_RSS_HEADER`` nested request header
``ETHTOOL_A_RSS_CONTEXT`` u32 context number
``ETHTOOL_A_RSS_HFUNC`` u32 RSS hash func
``ETHTOOL_A_RSS_INDIR`` binary Indir table bytes
``ETHTOOL_A_RSS_HKEY`` binary Hash key bytes
``ETHTOOL_A_RSS_INPUT_XFRM`` u32 RSS input data transformation
``ETHTOOL_A_RSS_FLOW_HASH`` nested Header fields included in hash
===================================== ====== ==============================
``ETHTOOL_A_RSS_INDIR`` is the minimal RSS table the user expects. Kernel and
the device driver may replicate the table if its smaller than smallest table
size supported by the device. For example if user requests ``[0, 1]`` but the
device needs at least 8 entries - the real table in use will end up being
``[0, 1, 0, 1, 0, 1, 0, 1]``. Most devices require the table size to be power
of 2, so tables which size is not a power of 2 will likely be rejected.
Using table of size 0 will reset the indirection table to the default.
PLCA_GET_CFG
============
@@ -2440,7 +2465,7 @@ are netlink only.
``ETHTOOL_GPFLAGS`` ``ETHTOOL_MSG_PRIVFLAGS_GET``
``ETHTOOL_SPFLAGS`` ``ETHTOOL_MSG_PRIVFLAGS_SET``
``ETHTOOL_GRXFH`` ``ETHTOOL_MSG_RSS_GET``
``ETHTOOL_SRXFH`` n/a
``ETHTOOL_SRXFH`` ``ETHTOOL_MSG_RSS_SET``
``ETHTOOL_GGRO`` ``ETHTOOL_MSG_FEATURES_GET``
``ETHTOOL_SGRO`` ``ETHTOOL_MSG_FEATURES_SET``
``ETHTOOL_GRXRINGS`` n/a
@@ -2455,7 +2480,7 @@ are netlink only.
``ETHTOOL_GRXNTUPLE`` n/a
``ETHTOOL_GSSET_INFO`` ``ETHTOOL_MSG_STRSET_GET``
``ETHTOOL_GRXFHINDIR`` ``ETHTOOL_MSG_RSS_GET``
``ETHTOOL_SRXFHINDIR`` n/a
``ETHTOOL_SRXFHINDIR`` ``ETHTOOL_MSG_RSS_SET``
``ETHTOOL_GFEATURES`` ``ETHTOOL_MSG_FEATURES_GET``
``ETHTOOL_SFEATURES`` ``ETHTOOL_MSG_FEATURES_SET``
``ETHTOOL_GCHANNELS`` ``ETHTOOL_MSG_CHANNELS_GET``

View File

@@ -840,6 +840,7 @@ enum {
ETHTOOL_MSG_PHY_GET,
ETHTOOL_MSG_TSCONFIG_GET,
ETHTOOL_MSG_TSCONFIG_SET,
ETHTOOL_MSG_RSS_SET,
__ETHTOOL_MSG_USER_CNT,
ETHTOOL_MSG_USER_MAX = (__ETHTOOL_MSG_USER_CNT - 1)

View File

@@ -806,6 +806,21 @@ int ethtool_check_rss_ctx_busy(struct net_device *dev, u32 rss_context)
return rc;
}
/* Check if fields configured for flow hash are symmetric - if src is included
* so is dst and vice versa.
*/
int ethtool_rxfh_config_is_sym(u64 rxfh)
{
bool sym;
sym = rxfh == (rxfh & (RXH_IP_SRC | RXH_IP_DST |
RXH_L4_B_0_1 | RXH_L4_B_2_3));
sym &= !!(rxfh & RXH_IP_SRC) == !!(rxfh & RXH_IP_DST);
sym &= !!(rxfh & RXH_L4_B_0_1) == !!(rxfh & RXH_L4_B_2_3);
return sym;
}
int ethtool_check_ops(const struct ethtool_ops *ops)
{
if (WARN_ON(ops->set_coalesce && !ops->supported_coalesce_params))

View File

@@ -44,6 +44,7 @@ int ethtool_check_max_channel(struct net_device *dev,
struct ethtool_channels channels,
struct genl_info *info);
int ethtool_check_rss_ctx_busy(struct net_device *dev, u32 rss_context);
int ethtool_rxfh_config_is_sym(u64 rxfh);
void ethtool_ringparam_get_cfg(struct net_device *dev,
struct ethtool_ringparam *param,

View File

@@ -1027,9 +1027,7 @@ static int ethtool_check_xfrm_rxfh(u32 input_xfrm, u64 rxfh)
*/
if ((input_xfrm != RXH_XFRM_NO_CHANGE &&
input_xfrm & (RXH_XFRM_SYM_XOR | RXH_XFRM_SYM_OR_XOR)) &&
((rxfh & ~(RXH_IP_SRC | RXH_IP_DST | RXH_L4_B_0_1 | RXH_L4_B_2_3)) ||
(!!(rxfh & RXH_IP_SRC) ^ !!(rxfh & RXH_IP_DST)) ||
(!!(rxfh & RXH_L4_B_0_1) ^ !!(rxfh & RXH_L4_B_2_3))))
!ethtool_rxfh_config_is_sym(rxfh))
return -EINVAL;
return 0;

View File

@@ -405,6 +405,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = {
[ETHTOOL_MSG_PSE_GET] = &ethnl_pse_request_ops,
[ETHTOOL_MSG_PSE_SET] = &ethnl_pse_request_ops,
[ETHTOOL_MSG_RSS_GET] = &ethnl_rss_request_ops,
[ETHTOOL_MSG_RSS_SET] = &ethnl_rss_request_ops,
[ETHTOOL_MSG_PLCA_GET_CFG] = &ethnl_plca_cfg_request_ops,
[ETHTOOL_MSG_PLCA_SET_CFG] = &ethnl_plca_cfg_request_ops,
[ETHTOOL_MSG_PLCA_GET_STATUS] = &ethnl_plca_status_request_ops,
@@ -1504,6 +1505,13 @@ static const struct genl_ops ethtool_genl_ops[] = {
.policy = ethnl_tsconfig_set_policy,
.maxattr = ARRAY_SIZE(ethnl_tsconfig_set_policy) - 1,
},
{
.cmd = ETHTOOL_MSG_RSS_SET,
.flags = GENL_UNS_ADMIN_PERM,
.doit = ethnl_default_set_doit,
.policy = ethnl_rss_set_policy,
.maxattr = ARRAY_SIZE(ethnl_rss_set_policy) - 1,
},
};
static const struct genl_multicast_group ethtool_nl_mcgrps[] = {

View File

@@ -484,6 +484,7 @@ extern const struct nla_policy ethnl_module_set_policy[ETHTOOL_A_MODULE_POWER_MO
extern const struct nla_policy ethnl_pse_get_policy[ETHTOOL_A_PSE_HEADER + 1];
extern const struct nla_policy ethnl_pse_set_policy[ETHTOOL_A_PSE_MAX + 1];
extern const struct nla_policy ethnl_rss_get_policy[ETHTOOL_A_RSS_START_CONTEXT + 1];
extern const struct nla_policy ethnl_rss_set_policy[ETHTOOL_A_RSS_FLOW_HASH + 1];
extern const struct nla_policy ethnl_plca_get_cfg_policy[ETHTOOL_A_PLCA_HEADER + 1];
extern const struct nla_policy ethnl_plca_set_cfg_policy[ETHTOOL_A_PLCA_MAX + 1];
extern const struct nla_policy ethnl_plca_get_status_policy[ETHTOOL_A_PLCA_HEADER + 1];

View File

@@ -218,6 +218,10 @@ rss_prepare(const struct rss_req_info *request, struct net_device *dev,
{
rss_prepare_flow_hash(request, dev, data, info);
/* Coming from RSS_SET, driver may only have flow_hash_fields ops */
if (!dev->ethtool_ops->get_rxfh)
return 0;
if (request->rss_context)
return rss_prepare_ctx(request, dev, data, info);
return rss_prepare_get(request, dev, data, info);
@@ -466,6 +470,387 @@ void ethtool_rss_notify(struct net_device *dev, u32 rss_context)
ethnl_notify(dev, ETHTOOL_MSG_RSS_NTF, &req_info.base);
}
/* RSS_SET */
#define RFH_MASK (RXH_L2DA | RXH_VLAN | RXH_IP_SRC | RXH_IP_DST | \
RXH_L3_PROTO | RXH_L4_B_0_1 | RXH_L4_B_2_3 | \
RXH_GTP_TEID | RXH_DISCARD)
static const struct nla_policy ethnl_rss_flows_policy[] = {
[ETHTOOL_A_FLOW_ETHER] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_IP4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_IP6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_TCP4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_UDP4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_SCTP4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_AH_ESP4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_TCP6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_UDP6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_SCTP6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_AH_ESP6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_AH4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_ESP4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_AH6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_ESP6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_GTPU4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_GTPU6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_GTPC4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_GTPC6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_GTPC_TEID4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_GTPC_TEID6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_GTPU_EH4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_GTPU_EH6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_GTPU_UL4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_GTPU_UL6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_GTPU_DL4] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
[ETHTOOL_A_FLOW_GTPU_DL6] = NLA_POLICY_MASK(NLA_UINT, RFH_MASK),
};
const struct nla_policy ethnl_rss_set_policy[ETHTOOL_A_RSS_FLOW_HASH + 1] = {
[ETHTOOL_A_RSS_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy),
[ETHTOOL_A_RSS_CONTEXT] = { .type = NLA_U32, },
[ETHTOOL_A_RSS_HFUNC] = NLA_POLICY_MIN(NLA_U32, 1),
[ETHTOOL_A_RSS_INDIR] = { .type = NLA_BINARY, },
[ETHTOOL_A_RSS_HKEY] = NLA_POLICY_MIN(NLA_BINARY, 1),
[ETHTOOL_A_RSS_INPUT_XFRM] =
NLA_POLICY_MAX(NLA_U32, RXH_XFRM_SYM_OR_XOR),
[ETHTOOL_A_RSS_FLOW_HASH] = NLA_POLICY_NESTED(ethnl_rss_flows_policy),
};
static int
ethnl_rss_set_validate(struct ethnl_req_info *req_info, struct genl_info *info)
{
const struct ethtool_ops *ops = req_info->dev->ethtool_ops;
struct rss_req_info *request = RSS_REQINFO(req_info);
struct nlattr **tb = info->attrs;
struct nlattr *bad_attr = NULL;
u32 input_xfrm;
if (request->rss_context && !ops->create_rxfh_context)
bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_CONTEXT];
if (request->rss_context && !ops->rxfh_per_ctx_key) {
bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_HFUNC];
bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_HKEY];
bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_INPUT_XFRM];
}
input_xfrm = nla_get_u32_default(tb[ETHTOOL_A_RSS_INPUT_XFRM], 0);
if (input_xfrm & ~ops->supported_input_xfrm)
bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_INPUT_XFRM];
if (tb[ETHTOOL_A_RSS_FLOW_HASH] && !ops->set_rxfh_fields)
bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_FLOW_HASH];
if (request->rss_context &&
tb[ETHTOOL_A_RSS_FLOW_HASH] && !ops->rxfh_per_ctx_fields)
bad_attr = bad_attr ?: tb[ETHTOOL_A_RSS_FLOW_HASH];
if (bad_attr) {
NL_SET_BAD_ATTR(info->extack, bad_attr);
return -EOPNOTSUPP;
}
return 1;
}
static int
rss_set_prep_indir(struct net_device *dev, struct genl_info *info,
struct rss_reply_data *data, struct ethtool_rxfh_param *rxfh,
bool *reset, bool *mod)
{
const struct ethtool_ops *ops = dev->ethtool_ops;
struct netlink_ext_ack *extack = info->extack;
struct nlattr **tb = info->attrs;
struct ethtool_rxnfc rx_rings;
size_t alloc_size;
u32 user_size;
int i, err;
if (!tb[ETHTOOL_A_RSS_INDIR])
return 0;
if (!data->indir_size || !ops->get_rxnfc)
return -EOPNOTSUPP;
rx_rings.cmd = ETHTOOL_GRXRINGS;
err = ops->get_rxnfc(dev, &rx_rings, NULL);
if (err)
return err;
if (nla_len(tb[ETHTOOL_A_RSS_INDIR]) % 4) {
NL_SET_BAD_ATTR(info->extack, tb[ETHTOOL_A_RSS_INDIR]);
return -EINVAL;
}
user_size = nla_len(tb[ETHTOOL_A_RSS_INDIR]) / 4;
if (!user_size) {
if (rxfh->rss_context) {
NL_SET_ERR_MSG_ATTR(extack, tb[ETHTOOL_A_RSS_INDIR],
"can't reset table for a context");
return -EINVAL;
}
*reset = true;
} else if (data->indir_size % user_size) {
NL_SET_ERR_MSG_ATTR_FMT(extack, tb[ETHTOOL_A_RSS_INDIR],
"size (%d) mismatch with device indir table (%d)",
user_size, data->indir_size);
return -EINVAL;
}
rxfh->indir_size = data->indir_size;
alloc_size = array_size(data->indir_size, sizeof(rxfh->indir[0]));
rxfh->indir = kzalloc(alloc_size, GFP_KERNEL);
if (!rxfh->indir)
return -ENOMEM;
nla_memcpy(rxfh->indir, tb[ETHTOOL_A_RSS_INDIR], alloc_size);
for (i = 0; i < user_size; i++) {
if (rxfh->indir[i] < rx_rings.data)
continue;
NL_SET_ERR_MSG_ATTR_FMT(extack, tb[ETHTOOL_A_RSS_INDIR],
"entry %d: queue out of range (%d)",
i, rxfh->indir[i]);
err = -EINVAL;
goto err_free;
}
if (user_size) {
/* Replicate the user-provided table to fill the device table */
for (i = user_size; i < data->indir_size; i++)
rxfh->indir[i] = rxfh->indir[i % user_size];
} else {
for (i = 0; i < data->indir_size; i++)
rxfh->indir[i] =
ethtool_rxfh_indir_default(i, rx_rings.data);
}
*mod |= memcmp(rxfh->indir, data->indir_table, data->indir_size);
return 0;
err_free:
kfree(rxfh->indir);
rxfh->indir = NULL;
return err;
}
static int
rss_set_prep_hkey(struct net_device *dev, struct genl_info *info,
struct rss_reply_data *data, struct ethtool_rxfh_param *rxfh,
bool *mod)
{
struct nlattr **tb = info->attrs;
if (!tb[ETHTOOL_A_RSS_HKEY])
return 0;
if (nla_len(tb[ETHTOOL_A_RSS_HKEY]) != data->hkey_size) {
NL_SET_BAD_ATTR(info->extack, tb[ETHTOOL_A_RSS_HKEY]);
return -EINVAL;
}
rxfh->key_size = data->hkey_size;
rxfh->key = kmemdup(data->hkey, data->hkey_size, GFP_KERNEL);
if (!rxfh->key)
return -ENOMEM;
ethnl_update_binary(rxfh->key, rxfh->key_size, tb[ETHTOOL_A_RSS_HKEY],
mod);
return 0;
}
static int
rss_check_rxfh_fields_sym(struct net_device *dev, struct genl_info *info,
struct rss_reply_data *data, bool xfrm_sym)
{
struct nlattr **tb = info->attrs;
int i;
if (!xfrm_sym)
return 0;
if (!data->has_flow_hash) {
NL_SET_ERR_MSG_ATTR(info->extack, tb[ETHTOOL_A_RSS_INPUT_XFRM],
"hash field config not reported");
return -EINVAL;
}
for (i = 1; i < __ETHTOOL_A_FLOW_CNT; i++)
if (data->flow_hash[i] >= 0 &&
!ethtool_rxfh_config_is_sym(data->flow_hash[i])) {
NL_SET_ERR_MSG_ATTR(info->extack,
tb[ETHTOOL_A_RSS_INPUT_XFRM],
"hash field config is not symmetric");
return -EINVAL;
}
return 0;
}
static int
ethnl_set_rss_fields(struct net_device *dev, struct genl_info *info,
u32 rss_context, struct rss_reply_data *data,
bool xfrm_sym, bool *mod)
{
struct nlattr *flow_nest = info->attrs[ETHTOOL_A_RSS_FLOW_HASH];
struct nlattr *flows[ETHTOOL_A_FLOW_MAX + 1];
const struct ethtool_ops *ops;
int i, ret;
ops = dev->ethtool_ops;
ret = rss_check_rxfh_fields_sym(dev, info, data, xfrm_sym);
if (ret)
return ret;
if (!flow_nest)
return 0;
ret = nla_parse_nested(flows, ARRAY_SIZE(ethnl_rss_flows_policy) - 1,
flow_nest, ethnl_rss_flows_policy, info->extack);
if (ret < 0)
return ret;
for (i = 1; i < __ETHTOOL_A_FLOW_CNT; i++) {
struct ethtool_rxfh_fields fields = {
.flow_type = ethtool_rxfh_ft_nl2ioctl[i],
.rss_context = rss_context,
};
if (!flows[i])
continue;
fields.data = nla_get_u32(flows[i]);
if (data->has_flow_hash && data->flow_hash[i] == fields.data)
continue;
if (xfrm_sym && !ethtool_rxfh_config_is_sym(fields.data)) {
NL_SET_ERR_MSG_ATTR(info->extack, flows[i],
"conflict with xfrm-input");
return -EINVAL;
}
ret = ops->set_rxfh_fields(dev, &fields, info->extack);
if (ret)
return ret;
*mod = true;
}
return 0;
}
static void
rss_set_ctx_update(struct ethtool_rxfh_context *ctx, struct nlattr **tb,
struct rss_reply_data *data, struct ethtool_rxfh_param *rxfh)
{
int i;
if (rxfh->indir) {
for (i = 0; i < data->indir_size; i++)
ethtool_rxfh_context_indir(ctx)[i] = rxfh->indir[i];
ctx->indir_configured = !!nla_len(tb[ETHTOOL_A_RSS_INDIR]);
}
if (rxfh->key) {
memcpy(ethtool_rxfh_context_key(ctx), rxfh->key,
data->hkey_size);
ctx->key_configured = !!rxfh->key_size;
}
if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE)
ctx->hfunc = rxfh->hfunc;
if (rxfh->input_xfrm != RXH_XFRM_NO_CHANGE)
ctx->input_xfrm = rxfh->input_xfrm;
}
static int
ethnl_rss_set(struct ethnl_req_info *req_info, struct genl_info *info)
{
bool indir_reset = false, indir_mod, xfrm_sym = false;
struct rss_req_info *request = RSS_REQINFO(req_info);
struct ethtool_rxfh_context *ctx = NULL;
struct net_device *dev = req_info->dev;
bool mod = false, fields_mod = false;
struct ethtool_rxfh_param rxfh = {};
struct nlattr **tb = info->attrs;
struct rss_reply_data data = {};
const struct ethtool_ops *ops;
int ret;
ops = dev->ethtool_ops;
data.base.dev = dev;
ret = rss_prepare(request, dev, &data, info);
if (ret)
return ret;
rxfh.rss_context = request->rss_context;
ret = rss_set_prep_indir(dev, info, &data, &rxfh, &indir_reset, &mod);
if (ret)
goto exit_clean_data;
indir_mod = !!tb[ETHTOOL_A_RSS_INDIR];
rxfh.hfunc = data.hfunc;
ethnl_update_u8(&rxfh.hfunc, tb[ETHTOOL_A_RSS_HFUNC], &mod);
if (rxfh.hfunc == data.hfunc)
rxfh.hfunc = ETH_RSS_HASH_NO_CHANGE;
ret = rss_set_prep_hkey(dev, info, &data, &rxfh, &mod);
if (ret)
goto exit_free_indir;
rxfh.input_xfrm = data.input_xfrm;
ethnl_update_u8(&rxfh.input_xfrm, tb[ETHTOOL_A_RSS_INPUT_XFRM], &mod);
/* For drivers which don't support input_xfrm it will be set to 0xff
* in the RSS context info. In all other case input_xfrm != 0 means
* symmetric hashing is requested.
*/
if (!request->rss_context || ops->rxfh_per_ctx_key)
xfrm_sym = rxfh.input_xfrm || data.input_xfrm;
if (rxfh.input_xfrm == data.input_xfrm)
rxfh.input_xfrm = RXH_XFRM_NO_CHANGE;
mutex_lock(&dev->ethtool->rss_lock);
if (request->rss_context) {
ctx = xa_load(&dev->ethtool->rss_ctx, request->rss_context);
if (!ctx) {
ret = -ENOENT;
goto exit_unlock;
}
}
ret = ethnl_set_rss_fields(dev, info, request->rss_context,
&data, xfrm_sym, &fields_mod);
if (ret)
goto exit_unlock;
if (!mod)
ret = 0; /* nothing to tell the driver */
else if (!ops->set_rxfh)
ret = -EOPNOTSUPP;
else if (!rxfh.rss_context)
ret = ops->set_rxfh(dev, &rxfh, info->extack);
else
ret = ops->modify_rxfh_context(dev, ctx, &rxfh, info->extack);
if (ret)
goto exit_unlock;
if (ctx)
rss_set_ctx_update(ctx, tb, &data, &rxfh);
else if (indir_reset)
dev->priv_flags &= ~IFF_RXFH_CONFIGURED;
else if (indir_mod)
dev->priv_flags |= IFF_RXFH_CONFIGURED;
exit_unlock:
mutex_unlock(&dev->ethtool->rss_lock);
kfree(rxfh.key);
exit_free_indir:
kfree(rxfh.indir);
exit_clean_data:
rss_cleanup_data(&data.base);
return ret ?: mod || fields_mod;
}
const struct ethnl_request_ops ethnl_rss_request_ops = {
.request_cmd = ETHTOOL_MSG_RSS_GET,
.reply_cmd = ETHTOOL_MSG_RSS_GET_REPLY,
@@ -478,4 +863,8 @@ const struct ethnl_request_ops ethnl_rss_request_ops = {
.reply_size = rss_reply_size,
.fill_reply = rss_fill_reply,
.cleanup_data = rss_cleanup_data,
.set_validate = ethnl_rss_set_validate,
.set = ethnl_rss_set,
.set_ntf_cmd = ETHTOOL_MSG_RSS_NTF,
};

View File

@@ -575,7 +575,9 @@ class YnlFamily(SpecFamily):
elif attr["type"] == 'string':
attr_payload = str(value).encode('ascii') + b'\x00'
elif attr["type"] == 'binary':
if isinstance(value, bytes):
if value is None:
attr_payload = b''
elif isinstance(value, bytes):
attr_payload = value
elif isinstance(value, str):
if attr.display_hint:
@@ -584,6 +586,9 @@ class YnlFamily(SpecFamily):
attr_payload = bytes.fromhex(value)
elif isinstance(value, dict) and attr.struct_name:
attr_payload = self._encode_struct(attr.struct_name, value)
elif isinstance(value, list) and attr.sub_type in NlAttr.type_formats:
format = NlAttr.get_format(attr.sub_type)
attr_payload = b''.join([format.pack(x) for x in value])
else:
raise Exception(f'Unknown type for binary attribute, value: {value}')
elif attr['type'] in NlAttr.type_formats or attr.is_auto_scalar:

View File

@@ -6,13 +6,21 @@ API level tests for RSS (mostly Netlink vs IOCTL).
"""
import glob
from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_is, ksft_ne
import random
from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_is, ksft_ne, ksft_raises
from lib.py import KsftSkipEx, KsftFailEx
from lib.py import defer, ethtool
from lib.py import EthtoolFamily
from lib.py import defer, ethtool, CmdExitFailure
from lib.py import EthtoolFamily, NlError
from lib.py import NetDrvEnv
def _require_2qs(cfg):
qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*"))
if qcnt < 2:
raise KsftSkipEx(f"Local has only {qcnt} queues")
return qcnt
def _ethtool_create(cfg, act, opts):
output = ethtool(f"{act} {cfg.ifname} {opts}").stdout
# Output will be something like: "New RSS context is 1" or
@@ -52,15 +60,101 @@ def _ethtool_get_cfg(cfg, fl_type, to_nl=False):
return ret
def test_rxfh_nl_set_fail(cfg):
"""
Test error path of Netlink SET.
"""
_require_2qs(cfg)
ethnl = EthtoolFamily()
ethnl.ntf_subscribe("monitor")
with ksft_raises(NlError):
ethnl.rss_set({"header": {"dev-name": "lo"},
"indir": None})
with ksft_raises(NlError):
ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
"indir": [100000]})
ntf = next(ethnl.poll_ntf(duration=0.2), None)
ksft_is(ntf, None)
def test_rxfh_nl_set_indir(cfg):
"""
Test setting indirection table via Netlink.
"""
qcnt = _require_2qs(cfg)
# Test some SETs with a value
reset = defer(cfg.ethnl.rss_set,
{"header": {"dev-index": cfg.ifindex}, "indir": None})
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
"indir": [1]})
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
ksft_eq(set(rss.get("indir", [-1])), {1})
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
"indir": [0, 1]})
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
ksft_eq(set(rss.get("indir", [-1])), {0, 1})
# Make sure we can't set the queue count below max queue used
with ksft_raises(CmdExitFailure):
ethtool(f"-L {cfg.ifname} combined 0 rx 1")
with ksft_raises(CmdExitFailure):
ethtool(f"-L {cfg.ifname} combined 1 rx 0")
# Test reset back to default
reset.exec()
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
ksft_eq(set(rss.get("indir", [-1])), set(range(qcnt)))
def test_rxfh_nl_set_indir_ctx(cfg):
"""
Test setting indirection table for a custom context via Netlink.
"""
_require_2qs(cfg)
# Get setting for ctx 0, we'll make sure they don't get clobbered
dflt = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
# Create context
ctx_id = _ethtool_create(cfg, "-X", "context new")
defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
"context": ctx_id, "indir": [1]})
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex},
"context": ctx_id})
ksft_eq(set(rss.get("indir", [-1])), {1})
ctx0 = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
ksft_eq(ctx0, dflt)
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
"context": ctx_id, "indir": [0, 1]})
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex},
"context": ctx_id})
ksft_eq(set(rss.get("indir", [-1])), {0, 1})
ctx0 = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
ksft_eq(ctx0, dflt)
# Make sure we can't set the queue count below max queue used
with ksft_raises(CmdExitFailure):
ethtool(f"-L {cfg.ifname} combined 0 rx 1")
with ksft_raises(CmdExitFailure):
ethtool(f"-L {cfg.ifname} combined 1 rx 0")
def test_rxfh_indir_ntf(cfg):
"""
Check that Netlink notifications are generated when RSS indirection
table was modified.
"""
qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*"))
if qcnt < 2:
raise KsftSkipEx(f"Local has only {qcnt} queues")
_require_2qs(cfg)
ethnl = EthtoolFamily()
ethnl.ntf_subscribe("monitor")
@@ -88,10 +182,7 @@ def test_rxfh_indir_ctx_ntf(cfg):
Check that Netlink notifications are generated when RSS indirection
table was modified on an additional RSS context.
"""
qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*"))
if qcnt < 2:
raise KsftSkipEx(f"Local has only {qcnt} queues")
_require_2qs(cfg)
ctx_id = _ethtool_create(cfg, "-X", "context new")
defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
@@ -109,6 +200,38 @@ def test_rxfh_indir_ctx_ntf(cfg):
ksft_eq(set(ntf["msg"]["indir"]), {1})
def test_rxfh_nl_set_key(cfg):
"""
Test setting hashing key via Netlink.
"""
dflt = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
defer(cfg.ethnl.rss_set,
{"header": {"dev-index": cfg.ifindex},
"hkey": dflt["hkey"], "indir": None})
# Empty key should error out
with ksft_raises(NlError) as cm:
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
"hkey": None})
ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.hkey')
# Set key to random
mod = random.randbytes(len(dflt["hkey"]))
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
"hkey": mod})
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
ksft_eq(rss.get("hkey", [-1]), mod)
# Set key to random and indir tbl to something at once
mod = random.randbytes(len(dflt["hkey"]))
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
"indir": [0, 1], "hkey": mod})
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
ksft_eq(rss.get("hkey", [-1]), mod)
ksft_eq(set(rss.get("indir", [-1])), {0, 1})
def test_rxfh_fields(cfg):
"""
Test reading Rx Flow Hash over Netlink.
@@ -124,10 +247,154 @@ def test_rxfh_fields(cfg):
comment="Config for " + fl_type)
def test_rxfh_fields_set(cfg):
""" Test configuring Rx Flow Hash over Netlink. """
flow_types = ["tcp4", "tcp6", "udp4", "udp6"]
# Collect current settings
cfg_old = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
# symmetric hashing is config-order-sensitive make sure we leave
# symmetric mode, or make the flow-hash sym-compatible first
changes = [{"flow-hash": cfg_old["flow-hash"],},
{"input-xfrm": cfg_old.get("input-xfrm", {}),}]
if cfg_old.get("input-xfrm"):
changes = list(reversed(changes))
for old in changes:
defer(cfg.ethnl.rss_set, {"header": {"dev-index": cfg.ifindex},} | old)
# symmetric hashing prevents some of the configs below
if cfg_old.get("input-xfrm"):
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
"input-xfrm": {}})
for fl_type in flow_types:
cur = _ethtool_get_cfg(cfg, fl_type)
if cur == "sdfn":
change_nl = {"ip-src", "ip-dst"}
change_ic = "sd"
else:
change_nl = {"l4-b-0-1", "l4-b-2-3", "ip-src", "ip-dst"}
change_ic = "sdfn"
cfg.ethnl.rss_set({
"header": {"dev-index": cfg.ifindex},
"flow-hash": {fl_type: change_nl}
})
reset = defer(ethtool, f"--disable-netlink -N {cfg.ifname} "
f"rx-flow-hash {fl_type} {cur}")
cfg_nl = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
ksft_eq(change_nl, cfg_nl["flow-hash"][fl_type],
comment=f"Config for {fl_type} over Netlink")
cfg_ic = _ethtool_get_cfg(cfg, fl_type)
ksft_eq(change_ic, cfg_ic,
comment=f"Config for {fl_type} over IOCTL")
reset.exec()
cfg_nl = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
ksft_eq(cfg_old["flow-hash"][fl_type], cfg_nl["flow-hash"][fl_type],
comment=f"Un-config for {fl_type} over Netlink")
cfg_ic = _ethtool_get_cfg(cfg, fl_type)
ksft_eq(cur, cfg_ic, comment=f"Un-config for {fl_type} over IOCTL")
# Try to set multiple at once, the defer was already installed at the start
change = {"ip-src"}
if change == cfg_old["flow-hash"]["tcp4"]:
change = {"ip-dst"}
cfg.ethnl.rss_set({
"header": {"dev-index": cfg.ifindex},
"flow-hash": {x: change for x in flow_types}
})
cfg_nl = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
for fl_type in flow_types:
ksft_eq(change, cfg_nl["flow-hash"][fl_type],
comment=f"multi-config for {fl_type} over Netlink")
def test_rxfh_fields_set_xfrm(cfg):
""" Test changing Rx Flow Hash vs xfrm_input at once. """
def set_rss(cfg, xfrm, fh):
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
"input-xfrm": xfrm, "flow-hash": fh})
# Install the reset handler
cfg_old = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
# symmetric hashing is config-order-sensitive make sure we leave
# symmetric mode, or make the flow-hash sym-compatible first
changes = [{"flow-hash": cfg_old["flow-hash"],},
{"input-xfrm": cfg_old.get("input-xfrm", {}),}]
if cfg_old.get("input-xfrm"):
changes = list(reversed(changes))
for old in changes:
defer(cfg.ethnl.rss_set, {"header": {"dev-index": cfg.ifindex},} | old)
# Make sure we start with input-xfrm off, and tcp4 config non-sym
set_rss(cfg, {}, {})
set_rss(cfg, {}, {"tcp4": {"ip-src"}})
# Setting sym and fixing tcp4 config not expected to pass right now
with ksft_raises(NlError):
set_rss(cfg, {"sym-xor"}, {"tcp4": {"ip-src", "ip-dst"}})
# One at a time should work, hopefully
set_rss(cfg, 0, {"tcp4": {"ip-src", "ip-dst"}})
no_support = False
try:
set_rss(cfg, {"sym-xor"}, {})
except NlError:
try:
set_rss(cfg, {"sym-or-xor"}, {})
except NlError:
no_support = True
if no_support:
raise KsftSkipEx("no input-xfrm supported")
# Disabling two at once should not work either without kernel changes
with ksft_raises(NlError):
set_rss(cfg, {}, {"tcp4": {"ip-src"}})
def test_rxfh_fields_ntf(cfg):
""" Test Rx Flow Hash notifications. """
cur = _ethtool_get_cfg(cfg, "tcp4")
if cur == "sdfn":
change = {"ip-src", "ip-dst"}
else:
change = {"l4-b-0-1", "l4-b-2-3", "ip-src", "ip-dst"}
ethnl = EthtoolFamily()
ethnl.ntf_subscribe("monitor")
ethnl.rss_set({
"header": {"dev-index": cfg.ifindex},
"flow-hash": {"tcp4": change}
})
reset = defer(ethtool,
f"--disable-netlink -N {cfg.ifname} rx-flow-hash tcp4 {cur}")
ntf = next(ethnl.poll_ntf(duration=0.2), None)
if ntf is None:
raise KsftFailEx("No notification received after IOCTL change")
ksft_eq(ntf["name"], "rss-ntf")
ksft_eq(ntf["msg"]["flow-hash"]["tcp4"], change)
ksft_eq(next(ethnl.poll_ntf(duration=0.01), None), None)
reset.exec()
ntf = next(ethnl.poll_ntf(duration=0.2), None)
if ntf is None:
raise KsftFailEx("No notification received after Netlink change")
ksft_eq(ntf["name"], "rss-ntf")
ksft_ne(ntf["msg"]["flow-hash"]["tcp4"], change)
ksft_eq(next(ethnl.poll_ntf(duration=0.01), None), None)
def main() -> None:
""" Ksft boiler plate main """
with NetDrvEnv(__file__, nsim_test=False) as cfg:
cfg.ethnl = EthtoolFamily()
ksft_run(globs=globals(), case_pfx={"test_"}, args=(cfg, ))
ksft_exit()

View File

@@ -37,11 +37,11 @@ def test_rss_input_xfrm(cfg, ipver):
if not hasattr(socket, "SO_INCOMING_CPU"):
raise KsftSkipEx("socket.SO_INCOMING_CPU was added in Python 3.11")
input_xfrm = cfg.ethnl.rss_get(
{'header': {'dev-name': cfg.ifname}}).get('input-xfrm')
rss = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}})
input_xfrm = set(filter(lambda x: 'sym' in x, rss.get('input-xfrm', {})))
# Check for symmetric xor/or-xor
if not input_xfrm or (input_xfrm != 1 and input_xfrm != 2):
if not input_xfrm:
raise KsftSkipEx("Symmetric RSS hash not requested")
cpus = set()