Commit a60a27c7 authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'net-fib_rules-add-port-mask-support'

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: default avatarJakub Kicinski <kuba@kernel.org>
parents dfc4b67d f5d783c0
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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
+19 −0
Original line number Diff line number Diff line
@@ -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;
};

@@ -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 &&
@@ -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 ||
+2 −0
Original line number Diff line number Diff line
@@ -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
};

+68 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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,
@@ -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);
@@ -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;

+4 −4
Original line number Diff line number Diff line
@@ -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;
Loading