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

Merge branch 'net-mlx5-hws-improve-ip-version-handling'

Mark Bloch says:

====================
net/mlx5: HWS, Improve IP version handling

This small series hardens our checks against a single matcher containing
rules that match on IPv4 and IPv6. This scenario is not supported by
hardware steering and the implementation now signals this instead of
failing silently.

Patches:
* Patch 1 forbids a single definer to match on mixed IP versions for
  source and destination address.
* Patch 2 reproduces a couple of firmware checks: it forbids creating
  a definer that matches on IP address without matching on IP version,
  and also disallows matching on IPv6 addresses and the IPv4 IHL fields
  in the same definer.
* Patch 3 forbids mixing rules that match on IPv4 and IPv6 addresses in
  the same matcher. The underlying definer mechanism does not support
  that.
====================

Link: https://patch.msgid.link/20250422092540.182091-1-mbloch@nvidia.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents b5cdb9b3 f41f3edf
Loading
Loading
Loading
Loading
+56 −22
Original line number Diff line number Diff line
@@ -509,7 +509,7 @@ static int
hws_definer_conv_outer(struct mlx5hws_definer_conv_data *cd,
		       u32 *match_param)
{
	bool is_s_ipv6, is_d_ipv6, smac_set, dmac_set;
	bool is_ipv6, smac_set, dmac_set, ip_addr_set, ip_ver_set;
	struct mlx5hws_definer_fc *fc = cd->fc;
	struct mlx5hws_definer_fc *curr_fc;
	u32 *s_ipv6, *d_ipv6;
@@ -521,6 +521,20 @@ hws_definer_conv_outer(struct mlx5hws_definer_conv_data *cd,
		return -EINVAL;
	}

	ip_addr_set = HWS_IS_FLD_SET_SZ(match_param,
					outer_headers.src_ipv4_src_ipv6,
					0x80) ||
		      HWS_IS_FLD_SET_SZ(match_param,
					outer_headers.dst_ipv4_dst_ipv6, 0x80);
	ip_ver_set = HWS_IS_FLD_SET(match_param, outer_headers.ip_version) ||
		     HWS_IS_FLD_SET(match_param, outer_headers.ethertype);

	if (ip_addr_set && !ip_ver_set) {
		mlx5hws_err(cd->ctx,
			    "Unsupported match on IP address without version or ethertype\n");
		return -EINVAL;
	}

	/* L2 Check ethertype */
	HWS_SET_HDR(fc, match_param, ETH_TYPE_O,
		    outer_headers.ethertype,
@@ -570,10 +584,16 @@ hws_definer_conv_outer(struct mlx5hws_definer_conv_data *cd,
			      outer_headers.dst_ipv4_dst_ipv6.ipv6_layout);

	/* Assume IPv6 is used if ipv6 bits are set */
	is_s_ipv6 = s_ipv6[0] || s_ipv6[1] || s_ipv6[2];
	is_d_ipv6 = d_ipv6[0] || d_ipv6[1] || d_ipv6[2];
	is_ipv6 = s_ipv6[0] || s_ipv6[1] || s_ipv6[2] ||
		  d_ipv6[0] || d_ipv6[1] || d_ipv6[2];

	if (is_s_ipv6) {
	/* IHL is an IPv4-specific field. */
	if (is_ipv6 && HWS_IS_FLD_SET(match_param, outer_headers.ipv4_ihl)) {
		mlx5hws_err(cd->ctx, "Unsupported match on IPv6 address and IPv4 IHL\n");
		return -EINVAL;
	}

	if (is_ipv6) {
		/* Handle IPv6 source address */
		HWS_SET_HDR(fc, match_param, IPV6_SRC_127_96_O,
			    outer_headers.src_ipv4_src_ipv6.ipv6_simple_layout.ipv6_127_96,
@@ -587,13 +607,6 @@ hws_definer_conv_outer(struct mlx5hws_definer_conv_data *cd,
		HWS_SET_HDR(fc, match_param, IPV6_SRC_31_0_O,
			    outer_headers.src_ipv4_src_ipv6.ipv6_simple_layout.ipv6_31_0,
			    ipv6_src_outer.ipv6_address_31_0);
	} else {
		/* Handle IPv4 source address */
		HWS_SET_HDR(fc, match_param, IPV4_SRC_O,
			    outer_headers.src_ipv4_src_ipv6.ipv6_simple_layout.ipv6_31_0,
			    ipv4_src_dest_outer.source_address);
	}
	if (is_d_ipv6) {
		/* Handle IPv6 destination address */
		HWS_SET_HDR(fc, match_param, IPV6_DST_127_96_O,
			    outer_headers.dst_ipv4_dst_ipv6.ipv6_simple_layout.ipv6_127_96,
@@ -608,6 +621,10 @@ hws_definer_conv_outer(struct mlx5hws_definer_conv_data *cd,
			    outer_headers.dst_ipv4_dst_ipv6.ipv6_simple_layout.ipv6_31_0,
			    ipv6_dst_outer.ipv6_address_31_0);
	} else {
		/* Handle IPv4 source address */
		HWS_SET_HDR(fc, match_param, IPV4_SRC_O,
			    outer_headers.src_ipv4_src_ipv6.ipv6_simple_layout.ipv6_31_0,
			    ipv4_src_dest_outer.source_address);
		/* Handle IPv4 destination address */
		HWS_SET_HDR(fc, match_param, IPV4_DST_O,
			    outer_headers.dst_ipv4_dst_ipv6.ipv6_simple_layout.ipv6_31_0,
@@ -665,7 +682,7 @@ static int
hws_definer_conv_inner(struct mlx5hws_definer_conv_data *cd,
		       u32 *match_param)
{
	bool is_s_ipv6, is_d_ipv6, smac_set, dmac_set;
	bool is_ipv6, smac_set, dmac_set, ip_addr_set, ip_ver_set;
	struct mlx5hws_definer_fc *fc = cd->fc;
	struct mlx5hws_definer_fc *curr_fc;
	u32 *s_ipv6, *d_ipv6;
@@ -677,6 +694,20 @@ hws_definer_conv_inner(struct mlx5hws_definer_conv_data *cd,
		return -EINVAL;
	}

	ip_addr_set = HWS_IS_FLD_SET_SZ(match_param,
					inner_headers.src_ipv4_src_ipv6,
					0x80) ||
		      HWS_IS_FLD_SET_SZ(match_param,
					inner_headers.dst_ipv4_dst_ipv6, 0x80);
	ip_ver_set = HWS_IS_FLD_SET(match_param, inner_headers.ip_version) ||
		     HWS_IS_FLD_SET(match_param, inner_headers.ethertype);

	if (ip_addr_set && !ip_ver_set) {
		mlx5hws_err(cd->ctx,
			    "Unsupported match on IP address without version or ethertype\n");
		return -EINVAL;
	}

	/* L2 Check ethertype */
	HWS_SET_HDR(fc, match_param, ETH_TYPE_I,
		    inner_headers.ethertype,
@@ -728,10 +759,16 @@ hws_definer_conv_inner(struct mlx5hws_definer_conv_data *cd,
			      inner_headers.dst_ipv4_dst_ipv6.ipv6_layout);

	/* Assume IPv6 is used if ipv6 bits are set */
	is_s_ipv6 = s_ipv6[0] || s_ipv6[1] || s_ipv6[2];
	is_d_ipv6 = d_ipv6[0] || d_ipv6[1] || d_ipv6[2];
	is_ipv6 = s_ipv6[0] || s_ipv6[1] || s_ipv6[2] ||
		  d_ipv6[0] || d_ipv6[1] || d_ipv6[2];

	if (is_s_ipv6) {
	/* IHL is an IPv4-specific field. */
	if (is_ipv6 && HWS_IS_FLD_SET(match_param, inner_headers.ipv4_ihl)) {
		mlx5hws_err(cd->ctx, "Unsupported match on IPv6 address and IPv4 IHL\n");
		return -EINVAL;
	}

	if (is_ipv6) {
		/* Handle IPv6 source address */
		HWS_SET_HDR(fc, match_param, IPV6_SRC_127_96_I,
			    inner_headers.src_ipv4_src_ipv6.ipv6_simple_layout.ipv6_127_96,
@@ -745,13 +782,6 @@ hws_definer_conv_inner(struct mlx5hws_definer_conv_data *cd,
		HWS_SET_HDR(fc, match_param, IPV6_SRC_31_0_I,
			    inner_headers.src_ipv4_src_ipv6.ipv6_simple_layout.ipv6_31_0,
			    ipv6_src_inner.ipv6_address_31_0);
	} else {
		/* Handle IPv4 source address */
		HWS_SET_HDR(fc, match_param, IPV4_SRC_I,
			    inner_headers.src_ipv4_src_ipv6.ipv6_simple_layout.ipv6_31_0,
			    ipv4_src_dest_inner.source_address);
	}
	if (is_d_ipv6) {
		/* Handle IPv6 destination address */
		HWS_SET_HDR(fc, match_param, IPV6_DST_127_96_I,
			    inner_headers.dst_ipv4_dst_ipv6.ipv6_simple_layout.ipv6_127_96,
@@ -766,6 +796,10 @@ hws_definer_conv_inner(struct mlx5hws_definer_conv_data *cd,
			    inner_headers.dst_ipv4_dst_ipv6.ipv6_simple_layout.ipv6_31_0,
			    ipv6_dst_inner.ipv6_address_31_0);
	} else {
		/* Handle IPv4 source address */
		HWS_SET_HDR(fc, match_param, IPV4_SRC_I,
			    inner_headers.src_ipv4_src_ipv6.ipv6_simple_layout.ipv6_31_0,
			    ipv4_src_dest_inner.source_address);
		/* Handle IPv4 destination address */
		HWS_SET_HDR(fc, match_param, IPV4_DST_I,
			    inner_headers.dst_ipv4_dst_ipv6.ipv6_simple_layout.ipv6_31_0,
+26 −0
Original line number Diff line number Diff line
@@ -385,6 +385,30 @@ static int hws_matcher_bind_at(struct mlx5hws_matcher *matcher)
	return 0;
}

static void hws_matcher_set_ip_version_match(struct mlx5hws_matcher *matcher)
{
	int i;

	for (i = 0; i < matcher->mt->fc_sz; i++) {
		switch (matcher->mt->fc[i].fname) {
		case MLX5HWS_DEFINER_FNAME_ETH_TYPE_O:
			matcher->matches_outer_ethertype = 1;
			break;
		case MLX5HWS_DEFINER_FNAME_ETH_L3_TYPE_O:
			matcher->matches_outer_ip_version = 1;
			break;
		case MLX5HWS_DEFINER_FNAME_ETH_TYPE_I:
			matcher->matches_inner_ethertype = 1;
			break;
		case MLX5HWS_DEFINER_FNAME_ETH_L3_TYPE_I:
			matcher->matches_inner_ip_version = 1;
			break;
		default:
			break;
		}
	}
}

static int hws_matcher_bind_mt(struct mlx5hws_matcher *matcher)
{
	struct mlx5hws_context *ctx = matcher->tbl->ctx;
@@ -401,6 +425,8 @@ static int hws_matcher_bind_mt(struct mlx5hws_matcher *matcher)
		}
	}

	hws_matcher_set_ip_version_match(matcher);

	/* Create an STE pool per matcher*/
	pool_attr.table_type = matcher->tbl->type;
	pool_attr.pool_type = MLX5HWS_POOL_TYPE_STE;
+12 −0
Original line number Diff line number Diff line
@@ -50,6 +50,12 @@ struct mlx5hws_matcher_match_ste {
	struct mlx5hws_pool *pool;
};

enum {
	MLX5HWS_MATCHER_IPV_UNSET = 0,
	MLX5HWS_MATCHER_IPV_4 = 1,
	MLX5HWS_MATCHER_IPV_6 = 2,
};

struct mlx5hws_matcher {
	struct mlx5hws_table *tbl;
	struct mlx5hws_matcher_attr attr;
@@ -61,6 +67,12 @@ struct mlx5hws_matcher {
	u8 num_of_action_stes;
	/* enum mlx5hws_matcher_flags */
	u8 flags;
	u8 matches_outer_ethertype:1;
	u8 matches_outer_ip_version:1;
	u8 matches_inner_ethertype:1;
	u8 matches_inner_ip_version:1;
	u8 outer_ip_version:2;
	u8 inner_ip_version:2;
	u32 end_ft_id;
	struct mlx5hws_matcher *col_matcher;
	struct mlx5hws_matcher *resize_dst;
+122 −0
Original line number Diff line number Diff line
@@ -655,6 +655,124 @@ int mlx5hws_rule_move_hws_add(struct mlx5hws_rule *rule,
	return 0;
}

static u8 hws_rule_ethertype_to_matcher_ipv(u32 ethertype)
{
	switch (ethertype) {
	case ETH_P_IP:
		return MLX5HWS_MATCHER_IPV_4;
	case ETH_P_IPV6:
		return MLX5HWS_MATCHER_IPV_6;
	default:
		return MLX5HWS_MATCHER_IPV_UNSET;
	}
}

static u8 hws_rule_ip_version_to_matcher_ipv(u32 ip_version)
{
	switch (ip_version) {
	case 4:
		return MLX5HWS_MATCHER_IPV_4;
	case 6:
		return MLX5HWS_MATCHER_IPV_6;
	default:
		return MLX5HWS_MATCHER_IPV_UNSET;
	}
}

static int hws_rule_check_outer_ip_version(struct mlx5hws_matcher *matcher,
					   u32 *match_param)
{
	struct mlx5hws_context *ctx = matcher->tbl->ctx;
	u8 outer_ipv_ether = MLX5HWS_MATCHER_IPV_UNSET;
	u8 outer_ipv_ip = MLX5HWS_MATCHER_IPV_UNSET;
	u8 outer_ipv, ver;

	if (matcher->matches_outer_ethertype) {
		ver = MLX5_GET(fte_match_param, match_param,
			       outer_headers.ethertype);
		outer_ipv_ether = hws_rule_ethertype_to_matcher_ipv(ver);
	}
	if (matcher->matches_outer_ip_version) {
		ver = MLX5_GET(fte_match_param, match_param,
			       outer_headers.ip_version);
		outer_ipv_ip = hws_rule_ip_version_to_matcher_ipv(ver);
	}

	if (outer_ipv_ether != MLX5HWS_MATCHER_IPV_UNSET &&
	    outer_ipv_ip != MLX5HWS_MATCHER_IPV_UNSET &&
	    outer_ipv_ether != outer_ipv_ip) {
		mlx5hws_err(ctx, "Rule matches on inconsistent outer ethertype and ip version\n");
		return -EINVAL;
	}

	outer_ipv = outer_ipv_ether != MLX5HWS_MATCHER_IPV_UNSET ?
		    outer_ipv_ether : outer_ipv_ip;
	if (outer_ipv != MLX5HWS_MATCHER_IPV_UNSET &&
	    matcher->outer_ip_version != MLX5HWS_MATCHER_IPV_UNSET &&
	    outer_ipv != matcher->outer_ip_version) {
		mlx5hws_err(ctx, "Matcher and rule disagree on outer IP version\n");
		return -EINVAL;
	}
	matcher->outer_ip_version = outer_ipv;

	return 0;
}

static int hws_rule_check_inner_ip_version(struct mlx5hws_matcher *matcher,
					   u32 *match_param)
{
	struct mlx5hws_context *ctx = matcher->tbl->ctx;
	u8 inner_ipv_ether = MLX5HWS_MATCHER_IPV_UNSET;
	u8 inner_ipv_ip = MLX5HWS_MATCHER_IPV_UNSET;
	u8 inner_ipv, ver;

	if (matcher->matches_inner_ethertype) {
		ver = MLX5_GET(fte_match_param, match_param,
			       inner_headers.ethertype);
		inner_ipv_ether = hws_rule_ethertype_to_matcher_ipv(ver);
	}
	if (matcher->matches_inner_ip_version) {
		ver = MLX5_GET(fte_match_param, match_param,
			       inner_headers.ip_version);
		inner_ipv_ip = hws_rule_ip_version_to_matcher_ipv(ver);
	}

	if (inner_ipv_ether != MLX5HWS_MATCHER_IPV_UNSET &&
	    inner_ipv_ip != MLX5HWS_MATCHER_IPV_UNSET &&
	    inner_ipv_ether != inner_ipv_ip) {
		mlx5hws_err(ctx, "Rule matches on inconsistent inner ethertype and ip version\n");
		return -EINVAL;
	}

	inner_ipv = inner_ipv_ether != MLX5HWS_MATCHER_IPV_UNSET ?
		    inner_ipv_ether : inner_ipv_ip;
	if (inner_ipv != MLX5HWS_MATCHER_IPV_UNSET &&
	    matcher->inner_ip_version != MLX5HWS_MATCHER_IPV_UNSET &&
	    inner_ipv != matcher->inner_ip_version) {
		mlx5hws_err(ctx, "Matcher and rule disagree on inner IP version\n");
		return -EINVAL;
	}
	matcher->inner_ip_version = inner_ipv;

	return 0;
}

static int hws_rule_check_ip_version(struct mlx5hws_matcher *matcher,
				     u32 *match_param)
{
	int ret;

	ret = hws_rule_check_outer_ip_version(matcher, match_param);
	if (unlikely(ret))
		return ret;

	ret = hws_rule_check_inner_ip_version(matcher, match_param);
	if (unlikely(ret))
		return ret;

	return 0;
}

int mlx5hws_rule_create(struct mlx5hws_matcher *matcher,
			u8 mt_idx,
			u32 *match_param,
@@ -665,6 +783,10 @@ int mlx5hws_rule_create(struct mlx5hws_matcher *matcher,
{
	int ret;

	ret = hws_rule_check_ip_version(matcher, match_param);
	if (unlikely(ret))
		return ret;

	rule_handle->matcher = matcher;

	ret = hws_rule_enqueue_precheck_create(rule_handle, attr);