From 39f970aead3c5afd81d7f4e866a8f3f8fec3dabc Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Mon, 17 Feb 2025 15:41:02 +0200 Subject: [PATCH 1/8] net: fib_rules: Add port mask attributes Add attributes that allow matching on source and destination ports with a mask. Matching on the source port with a mask is needed in deployments where users encode path information into certain bits of the UDP source port. Temporarily set the type of the attributes to 'NLA_REJECT' while support is being added. Reviewed-by: Petr Machata Signed-off-by: Ido Schimmel Reviewed-by: Guillaume Nault Reviewed-by: David Ahern Link: https://patch.msgid.link/20250217134109.311176-2-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- include/uapi/linux/fib_rules.h | 2 ++ net/core/fib_rules.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/include/uapi/linux/fib_rules.h b/include/uapi/linux/fib_rules.h index 00e9890ca3c05..95ec01b15c654 100644 --- a/include/uapi/linux/fib_rules.h +++ b/include/uapi/linux/fib_rules.h @@ -70,6 +70,8 @@ enum { FRA_DSCP, /* dscp */ FRA_FLOWLABEL, /* flowlabel */ FRA_FLOWLABEL_MASK, /* flowlabel mask */ + FRA_SPORT_MASK, /* sport mask */ + FRA_DPORT_MASK, /* dport mask */ __FRA_MAX }; diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index 424b4cd4e9e5d..f5b1900770ec6 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -784,6 +784,8 @@ static const struct nla_policy fib_rule_policy[FRA_MAX + 1] = { [FRA_DSCP] = NLA_POLICY_MAX(NLA_U8, INET_DSCP_MASK >> 2), [FRA_FLOWLABEL] = { .type = NLA_BE32 }, [FRA_FLOWLABEL_MASK] = { .type = NLA_BE32 }, + [FRA_SPORT_MASK] = { .type = NLA_REJECT }, + [FRA_DPORT_MASK] = { .type = NLA_REJECT }, }; int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, From da7665947b668ef7882b40888171e941db11f06a Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Mon, 17 Feb 2025 15:41:03 +0200 Subject: [PATCH 2/8] net: fib_rules: Add port mask support Add support for configuring and deleting rules that match on source and destination ports using a mask as well as support for dumping such rules to user space. Reviewed-by: Petr Machata Signed-off-by: Ido Schimmel Reviewed-by: Guillaume Nault Reviewed-by: David Ahern Link: https://patch.msgid.link/20250217134109.311176-3-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- include/net/fib_rules.h | 8 +++++ net/core/fib_rules.c | 67 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/include/net/fib_rules.h b/include/net/fib_rules.h index 710caacad9da1..cfeb2fd0f5db5 100644 --- a/include/net/fib_rules.h +++ b/include/net/fib_rules.h @@ -43,6 +43,8 @@ struct fib_rule { struct fib_kuid_range uid_range; struct fib_rule_port_range sport_range; struct fib_rule_port_range dport_range; + u16 sport_mask; + u16 dport_mask; struct rcu_head rcu; }; @@ -159,6 +161,12 @@ static inline bool fib_rule_port_range_compare(struct fib_rule_port_range *a, a->end == b->end; } +static inline bool +fib_rule_port_is_range(const struct fib_rule_port_range *range) +{ + return range->start != range->end; +} + static inline bool fib_rule_requires_fldissect(struct fib_rule *rule) { return rule->iifindex != LOOPBACK_IFINDEX && (rule->ip_proto || diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index f5b1900770ec6..ba6beaa63f44e 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -481,11 +481,17 @@ static struct fib_rule *rule_find(struct fib_rules_ops *ops, &rule->sport_range)) continue; + if (rule->sport_mask && r->sport_mask != rule->sport_mask) + continue; + if (fib_rule_port_range_set(&rule->dport_range) && !fib_rule_port_range_compare(&r->dport_range, &rule->dport_range)) continue; + if (rule->dport_mask && r->dport_mask != rule->dport_mask) + continue; + if (!ops->compare(r, frh, tb)) continue; return r; @@ -515,6 +521,33 @@ static int fib_nl2rule_l3mdev(struct nlattr *nla, struct fib_rule *nlrule, } #endif +static int fib_nl2rule_port_mask(const struct nlattr *mask_attr, + const struct fib_rule_port_range *range, + u16 *port_mask, + struct netlink_ext_ack *extack) +{ + if (!fib_rule_port_range_valid(range)) { + NL_SET_ERR_MSG_ATTR(extack, mask_attr, + "Cannot specify port mask without port value"); + return -EINVAL; + } + + if (fib_rule_port_is_range(range)) { + NL_SET_ERR_MSG_ATTR(extack, mask_attr, + "Cannot specify port mask for port range"); + return -EINVAL; + } + + if (range->start & ~nla_get_u16(mask_attr)) { + NL_SET_ERR_MSG_ATTR(extack, mask_attr, "Invalid port mask"); + return -EINVAL; + } + + *port_mask = nla_get_u16(mask_attr); + + return 0; +} + static int fib_nl2rule(struct net *net, struct nlmsghdr *nlh, struct netlink_ext_ack *extack, struct fib_rules_ops *ops, @@ -644,6 +677,16 @@ static int fib_nl2rule(struct net *net, struct nlmsghdr *nlh, NL_SET_ERR_MSG(extack, "Invalid sport range"); goto errout_free; } + if (!fib_rule_port_is_range(&nlrule->sport_range)) + nlrule->sport_mask = U16_MAX; + } + + if (tb[FRA_SPORT_MASK]) { + err = fib_nl2rule_port_mask(tb[FRA_SPORT_MASK], + &nlrule->sport_range, + &nlrule->sport_mask, extack); + if (err) + goto errout_free; } if (tb[FRA_DPORT_RANGE]) { @@ -653,6 +696,16 @@ static int fib_nl2rule(struct net *net, struct nlmsghdr *nlh, NL_SET_ERR_MSG(extack, "Invalid dport range"); goto errout_free; } + if (!fib_rule_port_is_range(&nlrule->dport_range)) + nlrule->dport_mask = U16_MAX; + } + + if (tb[FRA_DPORT_MASK]) { + err = fib_nl2rule_port_mask(tb[FRA_DPORT_MASK], + &nlrule->dport_range, + &nlrule->dport_mask, extack); + if (err) + goto errout_free; } *rule = nlrule; @@ -751,10 +804,16 @@ static int rule_exists(struct fib_rules_ops *ops, struct fib_rule_hdr *frh, &rule->sport_range)) continue; + if (r->sport_mask != rule->sport_mask) + continue; + if (!fib_rule_port_range_compare(&r->dport_range, &rule->dport_range)) continue; + if (r->dport_mask != rule->dport_mask) + continue; + if (!ops->compare(r, frh, tb)) continue; return 1; @@ -1051,7 +1110,9 @@ static inline size_t fib_rule_nlmsg_size(struct fib_rules_ops *ops, + nla_total_size(1) /* FRA_PROTOCOL */ + nla_total_size(1) /* FRA_IP_PROTO */ + nla_total_size(sizeof(struct fib_rule_port_range)) /* FRA_SPORT_RANGE */ - + nla_total_size(sizeof(struct fib_rule_port_range)); /* FRA_DPORT_RANGE */ + + nla_total_size(sizeof(struct fib_rule_port_range)) /* FRA_DPORT_RANGE */ + + nla_total_size(2) /* FRA_SPORT_MASK */ + + nla_total_size(2); /* FRA_DPORT_MASK */ if (ops->nlmsg_payload) payload += ops->nlmsg_payload(rule); @@ -1119,8 +1180,12 @@ static int fib_nl_fill_rule(struct sk_buff *skb, struct fib_rule *rule, nla_put_uid_range(skb, &rule->uid_range)) || (fib_rule_port_range_set(&rule->sport_range) && nla_put_port_range(skb, FRA_SPORT_RANGE, &rule->sport_range)) || + (rule->sport_mask && nla_put_u16(skb, FRA_SPORT_MASK, + rule->sport_mask)) || (fib_rule_port_range_set(&rule->dport_range) && nla_put_port_range(skb, FRA_DPORT_RANGE, &rule->dport_range)) || + (rule->dport_mask && nla_put_u16(skb, FRA_DPORT_MASK, + rule->dport_mask)) || (rule->ip_proto && nla_put_u8(skb, FRA_IP_PROTO, rule->ip_proto))) goto nla_put_failure; From 79a4e21584b7d36df51d452f4dc43221b463a26f Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Mon, 17 Feb 2025 15:41:04 +0200 Subject: [PATCH 3/8] ipv4: fib_rules: Add port mask matching Extend IPv4 FIB rules to match on source and destination ports using a mask. Note that the mask is only set when not matching on a range. Reviewed-by: Petr Machata Signed-off-by: Ido Schimmel Reviewed-by: Guillaume Nault Reviewed-by: David Ahern Link: https://patch.msgid.link/20250217134109.311176-4-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- include/net/fib_rules.h | 11 +++++++++++ net/ipv4/fib_rules.c | 8 ++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/include/net/fib_rules.h b/include/net/fib_rules.h index cfeb2fd0f5db5..5927910ec06e5 100644 --- a/include/net/fib_rules.h +++ b/include/net/fib_rules.h @@ -148,6 +148,17 @@ static inline bool fib_rule_port_inrange(const struct fib_rule_port_range *a, ntohs(port) <= a->end; } +static inline bool fib_rule_port_match(const struct fib_rule_port_range *range, + u16 port_mask, __be16 port) +{ + if ((range->start ^ ntohs(port)) & port_mask) + return false; + if (!port_mask && fib_rule_port_range_set(range) && + !fib_rule_port_inrange(range, port)) + return false; + return true; +} + static inline bool fib_rule_port_range_valid(const struct fib_rule_port_range *a) { return a->start != 0 && a->end != 0 && a->end < 0xffff && diff --git a/net/ipv4/fib_rules.c b/net/ipv4/fib_rules.c index 041c46787d941..6b3d6a957822b 100644 --- a/net/ipv4/fib_rules.c +++ b/net/ipv4/fib_rules.c @@ -201,12 +201,12 @@ INDIRECT_CALLABLE_SCOPE int fib4_rule_match(struct fib_rule *rule, if (rule->ip_proto && (rule->ip_proto != fl4->flowi4_proto)) return 0; - if (fib_rule_port_range_set(&rule->sport_range) && - !fib_rule_port_inrange(&rule->sport_range, fl4->fl4_sport)) + if (!fib_rule_port_match(&rule->sport_range, rule->sport_mask, + fl4->fl4_sport)) return 0; - if (fib_rule_port_range_set(&rule->dport_range) && - !fib_rule_port_inrange(&rule->dport_range, fl4->fl4_dport)) + if (!fib_rule_port_match(&rule->dport_range, rule->dport_mask, + fl4->fl4_dport)) return 0; return 1; From fc1266a061641ee10feb68ba2c33b85904413606 Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Mon, 17 Feb 2025 15:41:05 +0200 Subject: [PATCH 4/8] ipv6: fib_rules: Add port mask matching Extend IPv6 FIB rules to match on source and destination ports using a mask. Note that the mask is only set when not matching on a range. Reviewed-by: Petr Machata Signed-off-by: Ido Schimmel Reviewed-by: Guillaume Nault Reviewed-by: David Ahern Link: https://patch.msgid.link/20250217134109.311176-5-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- net/ipv6/fib6_rules.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net/ipv6/fib6_rules.c b/net/ipv6/fib6_rules.c index 40af8fd6efa70..0144d01417d9e 100644 --- a/net/ipv6/fib6_rules.c +++ b/net/ipv6/fib6_rules.c @@ -340,12 +340,12 @@ INDIRECT_CALLABLE_SCOPE int fib6_rule_match(struct fib_rule *rule, if (rule->ip_proto && (rule->ip_proto != fl6->flowi6_proto)) return 0; - if (fib_rule_port_range_set(&rule->sport_range) && - !fib_rule_port_inrange(&rule->sport_range, fl6->fl6_sport)) + if (!fib_rule_port_match(&rule->sport_range, rule->sport_mask, + fl6->fl6_sport)) return 0; - if (fib_rule_port_range_set(&rule->dport_range) && - !fib_rule_port_inrange(&rule->dport_range, fl6->fl6_dport)) + if (!fib_rule_port_match(&rule->dport_range, rule->dport_mask, + fl6->fl6_dport)) return 0; return 1; From 34e406a8492832d30491905a878a2ac853e9ef0f Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Mon, 17 Feb 2025 15:41:06 +0200 Subject: [PATCH 5/8] net: fib_rules: Enable port mask usage Allow user space to configure FIB rules that match on the source and destination ports with a mask, now that support has been added to the FIB rule core and the IPv4 and IPv6 address families. Reviewed-by: Petr Machata Signed-off-by: Ido Schimmel Reviewed-by: Guillaume Nault Reviewed-by: David Ahern Link: https://patch.msgid.link/20250217134109.311176-6-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- net/core/fib_rules.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index ba6beaa63f44e..5ddd34cbe7f6d 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -843,8 +843,8 @@ static const struct nla_policy fib_rule_policy[FRA_MAX + 1] = { [FRA_DSCP] = NLA_POLICY_MAX(NLA_U8, INET_DSCP_MASK >> 2), [FRA_FLOWLABEL] = { .type = NLA_BE32 }, [FRA_FLOWLABEL_MASK] = { .type = NLA_BE32 }, - [FRA_SPORT_MASK] = { .type = NLA_REJECT }, - [FRA_DPORT_MASK] = { .type = NLA_REJECT }, + [FRA_SPORT_MASK] = { .type = NLA_U16 }, + [FRA_DPORT_MASK] = { .type = NLA_U16 }, }; int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh, From ab35ebfabb530409d608a1e53138831e346243f0 Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Mon, 17 Feb 2025 15:41:07 +0200 Subject: [PATCH 6/8] netlink: specs: Add FIB rule port mask attributes Add new port mask attributes to the spec. Example: # ./tools/net/ynl/cli.py --spec Documentation/netlink/specs/rt_rule.yaml \ --do newrule \ --json '{"family": 2, "sport-range": { "start": 12345, "end": 12345 }, "sport-mask": 65535, "action": 1, "table": 1}' None # ./tools/net/ynl/cli.py --spec Documentation/netlink/specs/rt_rule.yaml \ --do newrule \ --json '{"family": 2, "dport-range": { "start": 54321, "end": 54321 }, "dport-mask": 65535, "action": 1, "table": 2}' None $ ./tools/net/ynl/cli.py --spec Documentation/netlink/specs/rt_rule.yaml \ --dump getrule --json '{"family": 2}' --output-json | jq '.[]' [...] { "table": 2, "suppress-prefixlen": "0xffffffff", "protocol": 0, "priority": 32764, "dport-range": { "start": 54321, "end": 54321 }, "dport-mask": "0xffff", "family": 2, "dst-len": 0, "src-len": 0, "tos": 0, "action": "to-tbl", "flags": 0 } { "table": 1, "suppress-prefixlen": "0xffffffff", "protocol": 0, "priority": 32765, "sport-range": { "start": 12345, "end": 12345 }, "sport-mask": "0xffff", "family": 2, "dst-len": 0, "src-len": 0, "tos": 0, "action": "to-tbl", "flags": 0 } [...] Reviewed-by: Petr Machata Signed-off-by: Ido Schimmel Reviewed-by: Guillaume Nault Reviewed-by: David Ahern Link: https://patch.msgid.link/20250217134109.311176-7-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/rt_rule.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/netlink/specs/rt_rule.yaml b/Documentation/netlink/specs/rt_rule.yaml index a9debac3058a0..b30c924087fa8 100644 --- a/Documentation/netlink/specs/rt_rule.yaml +++ b/Documentation/netlink/specs/rt_rule.yaml @@ -182,6 +182,14 @@ attribute-sets: type: u32 byte-order: big-endian display-hint: hex + - + name: sport-mask + type: u16 + display-hint: hex + - + name: dport-mask + type: u16 + display-hint: hex operations: enum-model: directional @@ -215,6 +223,8 @@ operations: - dscp - flowlabel - flowlabel-mask + - sport-mask + - dport-mask - name: newrule-ntf doc: Notify a rule creation From 94694aa64100dc5521477828f5dfd367af158674 Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Mon, 17 Feb 2025 15:41:08 +0200 Subject: [PATCH 7/8] selftests: fib_rule_tests: Add port range match tests Currently, only matching on specific ports is tested. Add port range testing to make sure this use case does not regress. Reviewed-by: Petr Machata Signed-off-by: Ido Schimmel Reviewed-by: Guillaume Nault Reviewed-by: David Ahern Link: https://patch.msgid.link/20250217134109.311176-8-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/fib_rule_tests.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tools/testing/selftests/net/fib_rule_tests.sh b/tools/testing/selftests/net/fib_rule_tests.sh index 847936363a12b..12a6e219d683f 100755 --- a/tools/testing/selftests/net/fib_rule_tests.sh +++ b/tools/testing/selftests/net/fib_rule_tests.sh @@ -256,6 +256,14 @@ fib_rule6_test() fib_rule6_test_match_n_redirect "$match" "$match" \ "$getnomatch" "sport and dport redirect to table" \ "sport and dport no redirect to table" + + match="sport 100-200 dport 300-400" + getmatch="sport 100 dport 400" + getnomatch="sport 100 dport 401" + fib_rule6_test_match_n_redirect "$match" "$getmatch" \ + "$getnomatch" \ + "sport and dport range redirect to table" \ + "sport and dport range no redirect to table" fi fib_check_iproute_support "ipproto" "ipproto" @@ -525,6 +533,14 @@ fib_rule4_test() fib_rule4_test_match_n_redirect "$match" "$match" \ "$getnomatch" "sport and dport redirect to table" \ "sport and dport no redirect to table" + + match="sport 100-200 dport 300-400" + getmatch="sport 100 dport 400" + getnomatch="sport 100 dport 401" + fib_rule4_test_match_n_redirect "$match" "$getmatch" \ + "$getnomatch" \ + "sport and dport range redirect to table" \ + "sport and dport range no redirect to table" fi fib_check_iproute_support "ipproto" "ipproto" From f5d783c088754eb3dc32b3d927229d6e6c6c0f4e Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Mon, 17 Feb 2025 15:41:09 +0200 Subject: [PATCH 8/8] selftests: fib_rule_tests: Add port mask match tests Add tests for FIB rules that match on source and destination ports with a mask. Test both good and bad flows. # ./fib_rule_tests.sh IPv6 FIB rule tests [...] TEST: rule6 check: sport and dport redirect to table [ OK ] TEST: rule6 check: sport and dport no redirect to table [ OK ] TEST: rule6 del by pref: sport and dport redirect to table [ OK ] TEST: rule6 check: sport and dport range redirect to table [ OK ] TEST: rule6 check: sport and dport range no redirect to table [ OK ] TEST: rule6 del by pref: sport and dport range redirect to table [ OK ] TEST: rule6 check: sport and dport masked redirect to table [ OK ] TEST: rule6 check: sport and dport masked no redirect to table [ OK ] TEST: rule6 del by pref: sport and dport masked redirect to table [ OK ] [...] Tests passed: 292 Tests failed: 0 Reviewed-by: Petr Machata Signed-off-by: Ido Schimmel Reviewed-by: Guillaume Nault Reviewed-by: David Ahern Link: https://patch.msgid.link/20250217134109.311176-9-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/fib_rule_tests.sh | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tools/testing/selftests/net/fib_rule_tests.sh b/tools/testing/selftests/net/fib_rule_tests.sh index 12a6e219d683f..06c51d7ceb4a1 100755 --- a/tools/testing/selftests/net/fib_rule_tests.sh +++ b/tools/testing/selftests/net/fib_rule_tests.sh @@ -266,6 +266,16 @@ fib_rule6_test() "sport and dport range no redirect to table" fi + ip rule help 2>&1 | grep sport | grep -q MASK + if [ $? -eq 0 ]; then + match="sport 0x0f00/0xff00 dport 0x000f/0x00ff" + getmatch="sport 0x0f11 dport 0x220f" + getnomatch="sport 0x1f11 dport 0x221f" + fib_rule6_test_match_n_redirect "$match" "$getmatch" \ + "$getnomatch" "sport and dport masked redirect to table" \ + "sport and dport masked no redirect to table" + fi + fib_check_iproute_support "ipproto" "ipproto" if [ $? -eq 0 ]; then match="ipproto tcp" @@ -543,6 +553,16 @@ fib_rule4_test() "sport and dport range no redirect to table" fi + ip rule help 2>&1 | grep sport | grep -q MASK + if [ $? -eq 0 ]; then + match="sport 0x0f00/0xff00 dport 0x000f/0x00ff" + getmatch="sport 0x0f11 dport 0x220f" + getnomatch="sport 0x1f11 dport 0x221f" + fib_rule4_test_match_n_redirect "$match" "$getmatch" \ + "$getnomatch" "sport and dport masked redirect to table" \ + "sport and dport masked no redirect to table" + fi + fib_check_iproute_support "ipproto" "ipproto" if [ $? -eq 0 ]; then match="ipproto tcp"