Skip to content

Commit

Permalink
Merge branch 'net-fib_rules-add-port-mask-support'
Browse files Browse the repository at this point in the history
Ido Schimmel says:

====================
net: fib_rules: Add port mask support

In some deployments users would like to encode path information into
certain bits of the IPv6 flow label, the UDP source port and the DSCP
field and use this information to route packets accordingly.

Redirecting traffic to a routing table based on specific bits in the UDP
source port is not currently possible. Only exact match and range are
currently supported by FIB rules.

This patchset extends FIB rules to match on layer 4 ports with an
optional mask. The mask is not supported when matching on a range. A
future patchset will add support for matching on the DSCP field with an
optional mask.

Patches #1-#6 gradually extend FIB rules to match on layer 4 ports with
an optional mask.

Patches #7-#8 add test cases for FIB rule port matching.

iproute2 support can be found here [1].

[1] https://github.com/idosch/iproute2/tree/submit/fib_rule_mask_v1
====================

Link: https://patch.msgid.link/20250217134109.311176-1-idosch@nvidia.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
  • Loading branch information
Jakub Kicinski committed Feb 20, 2025
2 parents dfc4b67 + f5d783c commit a60a27c
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 9 deletions.
10 changes: 10 additions & 0 deletions Documentation/netlink/specs/rt_rule.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -215,6 +223,8 @@ operations:
- dscp
- flowlabel
- flowlabel-mask
- sport-mask
- dport-mask
-
name: newrule-ntf
doc: Notify a rule creation
Expand Down
19 changes: 19 additions & 0 deletions include/net/fib_rules.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand Down Expand Up @@ -146,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 &&
Expand All @@ -159,6 +172,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 ||
Expand Down
2 changes: 2 additions & 0 deletions include/uapi/linux/fib_rules.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand Down
69 changes: 68 additions & 1 deletion net/core/fib_rules.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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]) {
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -784,6 +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_U16 },
[FRA_DPORT_MASK] = { .type = NLA_U16 },
};

int fib_newrule(struct net *net, struct sk_buff *skb, struct nlmsghdr *nlh,
Expand Down Expand Up @@ -1049,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);
Expand Down Expand Up @@ -1117,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;

Expand Down
8 changes: 4 additions & 4 deletions net/ipv4/fib_rules.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions net/ipv6/fib6_rules.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
36 changes: 36 additions & 0 deletions tools/testing/selftests/net/fib_rule_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,24 @@ 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

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"
Expand Down Expand Up @@ -525,6 +543,24 @@ 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

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"
Expand Down

0 comments on commit a60a27c

Please sign in to comment.