]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
ethtool: rss: support setting flow hashing fields
authorJakub Kicinski <kuba@kernel.org>
Wed, 16 Jul 2025 00:03:30 +0000 (17:03 -0700)
committerJakub Kicinski <kuba@kernel.org>
Thu, 17 Jul 2025 23:13:59 +0000 (16:13 -0700)
Add support for ETHTOOL_SRXFH (setting hashing fields) in RSS_SET.

The tricky part is dealing with symmetric hashing. In netlink user
can change the hashing fields and symmetric hash in one request,
in IOCTL the two used to be set via different uAPI requests.
Since fields and hash function config are still separate driver
callbacks - changes to the two are not atomic. Keep things simple
and validate the settings against both pre- and post- change ones.
Meaning that we will reject the config request if user tries
to correct the flow fields and set input_xfrm in one request,
or disables input_xfrm and makes flow fields non-symmetric.

We can adjust it later if there's a real need. Starting simple feels
right, and potentially partially applying the settings isn't nice,
either.

Reviewed-by: Gal Pressman <gal@nvidia.com>
Link: https://patch.msgid.link/20250716000331.1378807-11-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Documentation/netlink/specs/ethtool.yaml
Documentation/networking/ethtool-netlink.rst
net/ethtool/netlink.h
net/ethtool/rss.c

index 41f26d58f2f9408255b6b28d46eac0a8df6db7a9..069269edde016df9ea66f7965caca3904801b931 100644 (file)
@@ -2679,6 +2679,7 @@ operations:
             - indir
             - hkey
             - input-xfrm
+            - flow-hash
     -
       name: rss-ntf
       doc: |
index 2214d2ce346ab1bcae36bdb9b8b95e0a82d58a72..056832c77ffd249cc1a1ef3a36e7d60a6898f24a 100644 (file)
@@ -2003,6 +2003,7 @@ Request contents:
   ``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
@@ -2464,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
index 620dd1ab9b3bd5290e81b5ed3bdd48ceb2778717..ddb2fb00f929a1b20498f81e23d71b2ff71d2b9c 100644 (file)
@@ -484,7 +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_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];
index 79de013da28804c55c22ad6f2a2f1d22b4530578..bf45ebc223471d05153f26336d8375ddbc861b09 100644 (file)
@@ -472,7 +472,41 @@ void ethtool_rss_notify(struct net_device *dev, u32 rss_context)
 
 /* RSS_SET */
 
-const struct nla_policy ethnl_rss_set_policy[ETHTOOL_A_RSS_START_CONTEXT + 1] = {
+#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),
@@ -480,6 +514,7 @@ const struct nla_policy ethnl_rss_set_policy[ETHTOOL_A_RSS_START_CONTEXT + 1] =
        [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
@@ -504,6 +539,12 @@ ethnl_rss_set_validate(struct ethnl_req_info *req_info, struct genl_info *info)
        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;
@@ -644,6 +685,59 @@ rss_check_rxfh_fields_sym(struct net_device *dev, struct genl_info *info,
        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)
@@ -673,11 +767,11 @@ ethnl_rss_set(struct ethnl_req_info *req_info, struct genl_info *info)
        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;
-       bool mod = false;
        int ret;
 
        ops = dev->ethtool_ops;
@@ -710,14 +804,10 @@ ethnl_rss_set(struct ethnl_req_info *req_info, struct genl_info *info)
         * symmetric hashing is requested.
         */
        if (!request->rss_context || ops->rxfh_per_ctx_key)
-               xfrm_sym = !!rxfh.input_xfrm;
+               xfrm_sym = rxfh.input_xfrm || data.input_xfrm;
        if (rxfh.input_xfrm == data.input_xfrm)
                rxfh.input_xfrm = RXH_XFRM_NO_CHANGE;
 
-       ret = rss_check_rxfh_fields_sym(dev, info, &data, xfrm_sym);
-       if (ret)
-               goto exit_clean_data;
-
        mutex_lock(&dev->ethtool->rss_lock);
        if (request->rss_context) {
                ctx = xa_load(&dev->ethtool->rss_ctx, request->rss_context);
@@ -727,6 +817,11 @@ ethnl_rss_set(struct ethnl_req_info *req_info, struct genl_info *info)
                }
        }
 
+       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)
@@ -753,7 +848,7 @@ exit_free_indir:
 exit_clean_data:
        rss_cleanup_data(&data.base);
 
-       return ret ?: mod;
+       return ret ?: mod || fields_mod;
 }
 
 const struct ethnl_request_ops ethnl_rss_request_ops = {