Blob Blame History Raw
From 9697436145bf374093dc61e3ad857f7122de08ee Mon Sep 17 00:00:00 2001
From: Phil Sutter <psutter@redhat.com>
Date: Mon, 12 Jul 2021 17:44:08 +0200
Subject: [PATCH] tcpopt: split tcpopt_hdr_fields into per-option enum

Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1979334
Upstream Status: nftables commit 2e1f821d713aa

commit 2e1f821d713aa44717b38901ee80cac8e2aa0335
Author: Florian Westphal <fw@strlen.de>
Date:   Mon Nov 2 15:22:40 2020 +0100

    tcpopt: split tcpopt_hdr_fields into per-option enum

    Currently we're limited to ten template fields in exthdr_desc struct.
    Using a single enum for all tpc option fields thus won't work
    indefinitely (TCPOPTHDR_FIELD_TSECR is 9) when new option templates get
    added.

    Fortunately we can just use one enum per tcp option to avoid this.
    As a side effect this also allows to simplify the sack offset
    calculations.  Rather than computing that on-the-fly, just add extra
    fields to the SACK template.

    expr->exthdr.offset now holds the 'raw' value, filled in from the option
    template. This would ease implementation of 'raw option matching'
    using offset and length to load from the option.

    Signed-off-by: Florian Westphal <fw@strlen.de>
---
 include/tcpopt.h          |  46 +++++++++++----
 src/evaluate.c            |  16 ++---
 src/exthdr.c              |   1 +
 src/ipopt.c               |   2 +-
 src/netlink_delinearize.c |   2 +-
 src/netlink_linearize.c   |   4 +-
 src/parser_bison.y        |  18 +++---
 src/parser_json.c         |  36 ++++++++++--
 src/tcpopt.c              | 119 ++++++++++++++++----------------------
 9 files changed, 139 insertions(+), 105 deletions(-)

diff --git a/include/tcpopt.h b/include/tcpopt.h
index 7f3fbb8..667c8a7 100644
--- a/include/tcpopt.h
+++ b/include/tcpopt.h
@@ -33,16 +33,42 @@ enum tcpopt_kind {
 	TCPOPT_KIND_SACK3 = 258,
 };
 
-enum tcpopt_hdr_fields {
-	TCPOPTHDR_FIELD_INVALID,
-	TCPOPTHDR_FIELD_KIND,
-	TCPOPTHDR_FIELD_LENGTH,
-	TCPOPTHDR_FIELD_SIZE,
-	TCPOPTHDR_FIELD_COUNT,
-	TCPOPTHDR_FIELD_LEFT,
-	TCPOPTHDR_FIELD_RIGHT,
-	TCPOPTHDR_FIELD_TSVAL,
-	TCPOPTHDR_FIELD_TSECR,
+/* Internal identifiers */
+enum tcpopt_common {
+	TCPOPT_COMMON_KIND,
+	TCPOPT_COMMON_LENGTH,
+};
+
+enum tcpopt_maxseg {
+	TCPOPT_MAXSEG_KIND,
+	TCPOPT_MAXSEG_LENGTH,
+	TCPOPT_MAXSEG_SIZE,
+};
+
+enum tcpopt_timestamp {
+	TCPOPT_TS_KIND,
+	TCPOPT_TS_LENGTH,
+	TCPOPT_TS_TSVAL,
+	TCPOPT_TS_TSECR,
+};
+
+enum tcpopt_windowscale {
+	TCPOPT_WINDOW_KIND,
+	TCPOPT_WINDOW_LENGTH,
+	TCPOPT_WINDOW_COUNT,
+};
+
+enum tcpopt_hdr_field_sack {
+	TCPOPT_SACK_KIND,
+	TCPOPT_SACK_LENGTH,
+	TCPOPT_SACK_LEFT,
+	TCPOPT_SACK_RIGHT,
+	TCPOPT_SACK_LEFT1,
+	TCPOPT_SACK_RIGHT1,
+	TCPOPT_SACK_LEFT2,
+	TCPOPT_SACK_RIGHT2,
+	TCPOPT_SACK_LEFT3,
+	TCPOPT_SACK_RIGHT3,
 };
 
 extern const struct exthdr_desc *tcpopt_protocols[__TCPOPT_KIND_MAX];
diff --git a/src/evaluate.c b/src/evaluate.c
index 0181750..99a66c2 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -474,7 +474,7 @@ static void expr_evaluate_bits(struct eval_ctx *ctx, struct expr **exprp)
 					  &extra_len);
 		break;
 	case EXPR_EXTHDR:
-		shift = expr_offset_shift(expr, expr->exthdr.tmpl->offset,
+		shift = expr_offset_shift(expr, expr->exthdr.offset,
 					  &extra_len);
 		break;
 	default:
@@ -526,18 +526,16 @@ static int __expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp)
 	if (expr_evaluate_primary(ctx, exprp) < 0)
 		return -1;
 
-	if (expr->exthdr.tmpl->offset % BITS_PER_BYTE != 0 ||
+	if (expr->exthdr.offset % BITS_PER_BYTE != 0 ||
 	    expr->len % BITS_PER_BYTE != 0)
 		expr_evaluate_bits(ctx, exprp);
 
 	switch (expr->exthdr.op) {
 	case NFT_EXTHDR_OP_TCPOPT: {
 		static const unsigned int max_tcpoptlen = (15 * 4 - 20) * BITS_PER_BYTE;
-		unsigned int totlen = 0;
+		unsigned int totlen;
 
-		totlen += expr->exthdr.tmpl->offset;
-		totlen += expr->exthdr.tmpl->len;
-		totlen += expr->exthdr.offset;
+		totlen = expr->exthdr.tmpl->len + expr->exthdr.offset;
 
 		if (totlen > max_tcpoptlen)
 			return expr_error(ctx->msgs, expr,
@@ -547,11 +545,9 @@ static int __expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp)
 	}
 	case NFT_EXTHDR_OP_IPV4: {
 		static const unsigned int max_ipoptlen = 40 * BITS_PER_BYTE;
-		unsigned int totlen = 0;
+		unsigned int totlen;
 
-		totlen += expr->exthdr.tmpl->offset;
-		totlen += expr->exthdr.tmpl->len;
-		totlen += expr->exthdr.offset;
+		totlen = expr->exthdr.offset + expr->exthdr.tmpl->len;
 
 		if (totlen > max_ipoptlen)
 			return expr_error(ctx->msgs, expr,
diff --git a/src/exthdr.c b/src/exthdr.c
index e1ec6f3..c28213f 100644
--- a/src/exthdr.c
+++ b/src/exthdr.c
@@ -99,6 +99,7 @@ struct expr *exthdr_expr_alloc(const struct location *loc,
 			  BYTEORDER_BIG_ENDIAN, tmpl->len);
 	expr->exthdr.desc = desc;
 	expr->exthdr.tmpl = tmpl;
+	expr->exthdr.offset = tmpl->offset;
 	return expr;
 }
 
diff --git a/src/ipopt.c b/src/ipopt.c
index b3d0279..7ecb8b9 100644
--- a/src/ipopt.c
+++ b/src/ipopt.c
@@ -102,7 +102,7 @@ struct expr *ipopt_expr_alloc(const struct location *loc, uint8_t type,
 	expr->exthdr.desc   = desc;
 	expr->exthdr.tmpl   = tmpl;
 	expr->exthdr.op     = NFT_EXTHDR_OP_IPV4;
-	expr->exthdr.offset = calc_offset(desc, tmpl, ptr);
+	expr->exthdr.offset = tmpl->offset + calc_offset(desc, tmpl, ptr);
 
 	return expr;
 }
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
index 157a473..790336a 100644
--- a/src/netlink_delinearize.c
+++ b/src/netlink_delinearize.c
@@ -727,8 +727,8 @@ static void netlink_parse_numgen(struct netlink_parse_ctx *ctx,
 				 const struct location *loc,
 				 const struct nftnl_expr *nle)
 {
-	enum nft_registers dreg;
 	uint32_t type, until, offset;
+	enum nft_registers dreg;
 	struct expr *expr;
 
 	type  = nftnl_expr_get_u32(nle, NFTNL_EXPR_NG_TYPE);
diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
index 25be634..9d1a064 100644
--- a/src/netlink_linearize.c
+++ b/src/netlink_linearize.c
@@ -168,7 +168,7 @@ static void netlink_gen_exthdr(struct netlink_linearize_ctx *ctx,
 			       const struct expr *expr,
 			       enum nft_registers dreg)
 {
-	unsigned int offset = expr->exthdr.tmpl->offset + expr->exthdr.offset;
+	unsigned int offset = expr->exthdr.offset;
 	struct nftnl_expr *nle;
 
 	nle = alloc_nft_expr("exthdr");
@@ -896,7 +896,7 @@ static void netlink_gen_exthdr_stmt(struct netlink_linearize_ctx *ctx,
 
 	expr = stmt->exthdr.expr;
 
-	offset = expr->exthdr.tmpl->offset + expr->exthdr.offset;
+	offset = expr->exthdr.offset;
 
 	nle = alloc_nft_expr("exthdr");
 	netlink_put_register(nle, NFTNL_EXPR_EXTHDR_SREG, sreg);
diff --git a/src/parser_bison.y b/src/parser_bison.y
index 8f77766..114b289 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -4715,7 +4715,7 @@ tcp_hdr_expr		:	TCP	tcp_hdr_field
 			}
 			|	TCP	OPTION	tcp_hdr_option_type
 			{
-				$$ = tcpopt_expr_alloc(&@$, $3, TCPOPTHDR_FIELD_KIND);
+				$$ = tcpopt_expr_alloc(&@$, $3, TCPOPT_COMMON_KIND);
 				$$->exthdr.flags = NFT_EXTHDR_F_PRESENT;
 			}
 			;
@@ -4746,14 +4746,14 @@ tcp_hdr_option_type	:	EOL		{ $$ = TCPOPT_KIND_EOL; }
 			|	TIMESTAMP	{ $$ = TCPOPT_KIND_TIMESTAMP; }
 			;
 
-tcp_hdr_option_field	:	KIND		{ $$ = TCPOPTHDR_FIELD_KIND; }
-			|	LENGTH		{ $$ = TCPOPTHDR_FIELD_LENGTH; }
-			|	SIZE		{ $$ = TCPOPTHDR_FIELD_SIZE; }
-			|	COUNT		{ $$ = TCPOPTHDR_FIELD_COUNT; }
-			|	LEFT		{ $$ = TCPOPTHDR_FIELD_LEFT; }
-			|	RIGHT		{ $$ = TCPOPTHDR_FIELD_RIGHT; }
-			|	TSVAL		{ $$ = TCPOPTHDR_FIELD_TSVAL; }
-			|	TSECR		{ $$ = TCPOPTHDR_FIELD_TSECR; }
+tcp_hdr_option_field	:	KIND		{ $$ = TCPOPT_COMMON_KIND; }
+			|	LENGTH		{ $$ = TCPOPT_COMMON_LENGTH; }
+			|	SIZE		{ $$ = TCPOPT_MAXSEG_SIZE; }
+			|	COUNT		{ $$ = TCPOPT_WINDOW_COUNT; }
+			|	LEFT		{ $$ = TCPOPT_SACK_LEFT; }
+			|	RIGHT		{ $$ = TCPOPT_SACK_RIGHT; }
+			|	TSVAL		{ $$ = TCPOPT_TS_TSVAL; }
+			|	TSECR		{ $$ = TCPOPT_TS_TSECR; }
 			;
 
 dccp_hdr_expr		:	DCCP	dccp_hdr_field
diff --git a/src/parser_json.c b/src/parser_json.c
index 44b58a0..ab2375f 100644
--- a/src/parser_json.c
+++ b/src/parser_json.c
@@ -466,8 +466,10 @@ static int json_parse_tcp_option_type(const char *name, int *val)
 	}
 	/* special case for sack0 - sack3 */
 	if (sscanf(name, "sack%u", &i) == 1 && i < 4) {
-		if (val)
-			*val = TCPOPT_KIND_SACK + i;
+		if (val && i == 0)
+			*val = TCPOPT_KIND_SACK;
+		else if (val && i > 0)
+			*val = TCPOPT_KIND_SACK1 + i - 1;
 		return 0;
 	}
 	return 1;
@@ -475,12 +477,38 @@ static int json_parse_tcp_option_type(const char *name, int *val)
 
 static int json_parse_tcp_option_field(int type, const char *name, int *val)
 {
+	const struct exthdr_desc *desc;
+	unsigned int block = 0;
 	unsigned int i;
-	const struct exthdr_desc *desc = tcpopt_protocols[type];
+
+	switch (type) {
+	case TCPOPT_KIND_SACK1:
+		type = TCPOPT_KIND_SACK;
+		block = 1;
+		break;
+	case TCPOPT_KIND_SACK2:
+		type = TCPOPT_KIND_SACK;
+		block = 2;
+		break;
+	case TCPOPT_KIND_SACK3:
+		type = TCPOPT_KIND_SACK;
+		block = 3;
+		break;
+	}
+
+	if (type < 0 || type >= (int)array_size(tcpopt_protocols))
+		return 1;
+
+	desc = tcpopt_protocols[type];
 
 	for (i = 0; i < array_size(desc->templates); i++) {
 		if (desc->templates[i].token &&
 		    !strcmp(desc->templates[i].token, name)) {
+			if (block) {
+				block--;
+				continue;
+			}
+
 			if (val)
 				*val = i;
 			return 0;
@@ -585,7 +613,7 @@ static struct expr *json_parse_tcp_option_expr(struct json_ctx *ctx,
 
 	if (json_unpack(root, "{s:s}", "field", &field)) {
 		expr = tcpopt_expr_alloc(int_loc, descval,
-					 TCPOPTHDR_FIELD_KIND);
+					 TCPOPT_COMMON_KIND);
 		expr->exthdr.flags = NFT_EXTHDR_F_PRESENT;
 
 		return expr;
diff --git a/src/tcpopt.c b/src/tcpopt.c
index 17cb580..d1dd13b 100644
--- a/src/tcpopt.c
+++ b/src/tcpopt.c
@@ -22,7 +22,7 @@ static const struct exthdr_desc tcpopt_eol = {
 	.name		= "eol",
 	.type		= TCPOPT_KIND_EOL,
 	.templates	= {
-		[TCPOPTHDR_FIELD_KIND]		= PHT("kind",  0,    8),
+		[TCPOPT_COMMON_KIND]		= PHT("kind",  0,    8),
 	},
 };
 
@@ -30,7 +30,7 @@ static const struct exthdr_desc tcpopt_nop = {
 	.name		= "nop",
 	.type		= TCPOPT_KIND_NOP,
 	.templates	= {
-		[TCPOPTHDR_FIELD_KIND]		= PHT("kind",   0,   8),
+		[TCPOPT_COMMON_KIND]		= PHT("kind",  0,    8),
 	},
 };
 
@@ -38,9 +38,9 @@ static const struct exthdr_desc tcptopt_maxseg = {
 	.name		= "maxseg",
 	.type		= TCPOPT_KIND_MAXSEG,
 	.templates	= {
-		[TCPOPTHDR_FIELD_KIND]		= PHT("kind",   0,  8),
-		[TCPOPTHDR_FIELD_LENGTH]	= PHT("length", 8,  8),
-		[TCPOPTHDR_FIELD_SIZE]		= PHT("size",  16, 16),
+		[TCPOPT_MAXSEG_KIND]	= PHT("kind",   0,  8),
+		[TCPOPT_MAXSEG_LENGTH]	= PHT("length", 8,  8),
+		[TCPOPT_MAXSEG_SIZE]	= PHT("size",  16, 16),
 	},
 };
 
@@ -48,9 +48,9 @@ static const struct exthdr_desc tcpopt_window = {
 	.name		= "window",
 	.type		= TCPOPT_KIND_WINDOW,
 	.templates	= {
-		[TCPOPTHDR_FIELD_KIND]		= PHT("kind",   0,  8),
-		[TCPOPTHDR_FIELD_LENGTH]	= PHT("length", 8,  8),
-		[TCPOPTHDR_FIELD_COUNT]		= PHT("count", 16,  8),
+		[TCPOPT_WINDOW_KIND]	= PHT("kind",   0,  8),
+		[TCPOPT_WINDOW_LENGTH]	= PHT("length", 8,  8),
+		[TCPOPT_WINDOW_COUNT]	= PHT("count", 16,  8),
 	},
 };
 
@@ -58,8 +58,8 @@ static const struct exthdr_desc tcpopt_sack_permitted = {
 	.name		= "sack-perm",
 	.type		= TCPOPT_KIND_SACK_PERMITTED,
 	.templates	= {
-		[TCPOPTHDR_FIELD_KIND]		= PHT("kind",   0, 8),
-		[TCPOPTHDR_FIELD_LENGTH]	= PHT("length", 8, 8),
+		[TCPOPT_COMMON_KIND]	= PHT("kind",   0, 8),
+		[TCPOPT_COMMON_LENGTH]	= PHT("length", 8, 8),
 	},
 };
 
@@ -67,10 +67,16 @@ static const struct exthdr_desc tcpopt_sack = {
 	.name		= "sack",
 	.type		= TCPOPT_KIND_SACK,
 	.templates	= {
-		[TCPOPTHDR_FIELD_KIND]		= PHT("kind",   0,   8),
-		[TCPOPTHDR_FIELD_LENGTH]		= PHT("length", 8,   8),
-		[TCPOPTHDR_FIELD_LEFT]		= PHT("left",  16,  32),
-		[TCPOPTHDR_FIELD_RIGHT]		= PHT("right", 48,  32),
+		[TCPOPT_SACK_KIND]	= PHT("kind",   0,   8),
+		[TCPOPT_SACK_LENGTH]	= PHT("length", 8,   8),
+		[TCPOPT_SACK_LEFT]	= PHT("left",  16,  32),
+		[TCPOPT_SACK_RIGHT]	= PHT("right", 48,  32),
+		[TCPOPT_SACK_LEFT1]	= PHT("left",  80,  32),
+		[TCPOPT_SACK_RIGHT1]	= PHT("right", 112,  32),
+		[TCPOPT_SACK_LEFT2]	= PHT("left",  144,  32),
+		[TCPOPT_SACK_RIGHT2]	= PHT("right", 176,  32),
+		[TCPOPT_SACK_LEFT3]	= PHT("left",  208,  32),
+		[TCPOPT_SACK_RIGHT3]	= PHT("right", 240,  32),
 	},
 };
 
@@ -78,12 +84,13 @@ static const struct exthdr_desc tcpopt_timestamp = {
 	.name		= "timestamp",
 	.type		= TCPOPT_KIND_TIMESTAMP,
 	.templates	= {
-		[TCPOPTHDR_FIELD_KIND]		= PHT("kind",   0,  8),
-		[TCPOPTHDR_FIELD_LENGTH]	= PHT("length", 8,  8),
-		[TCPOPTHDR_FIELD_TSVAL]		= PHT("tsval",  16, 32),
-		[TCPOPTHDR_FIELD_TSECR]		= PHT("tsecr",  48, 32),
+		[TCPOPT_TS_KIND]	= PHT("kind",   0,  8),
+		[TCPOPT_TS_LENGTH]	= PHT("length", 8,  8),
+		[TCPOPT_TS_TSVAL]	= PHT("tsval",  16, 32),
+		[TCPOPT_TS_TSECR]	= PHT("tsecr",  48, 32),
 	},
 };
+
 #undef PHT
 
 const struct exthdr_desc *tcpopt_protocols[] = {
@@ -96,65 +103,43 @@ const struct exthdr_desc *tcpopt_protocols[] = {
 	[TCPOPT_KIND_TIMESTAMP]		= &tcpopt_timestamp,
 };
 
-static unsigned int calc_offset(const struct exthdr_desc *desc,
-				const struct proto_hdr_template *tmpl,
-				unsigned int num)
-{
-	if (!desc || tmpl == &tcpopt_unknown_template)
-		return 0;
-
-	switch (desc->type) {
-	case TCPOPT_SACK:
-		/* Make sure, offset calculations only apply to left and right
-		 * fields
-		 */
-		return (tmpl->offset < 16) ? 0 : num * 64;
-	default:
-		return 0;
-	}
-}
-
-
-static unsigned int calc_offset_reverse(const struct exthdr_desc *desc,
-					const struct proto_hdr_template *tmpl,
-					unsigned int offset)
-{
-	if (!desc || tmpl == &tcpopt_unknown_template)
-		return offset;
-
-	switch (desc->type) {
-	case TCPOPT_SACK:
-		/* We can safely ignore the first left/right field */
-		return offset < 80 ? offset : (offset % 64);
-	default:
-		return offset;
-	}
-}
-
 struct expr *tcpopt_expr_alloc(const struct location *loc,
 			       unsigned int kind,
 			       unsigned int field)
 {
 	const struct proto_hdr_template *tmpl;
-	const struct exthdr_desc *desc;
-	uint8_t optnum = 0;
+	const struct exthdr_desc *desc = NULL;
 	struct expr *expr;
 
 	switch (kind) {
 	case TCPOPT_KIND_SACK1:
 		kind = TCPOPT_KIND_SACK;
-		optnum = 1;
+		if (field == TCPOPT_SACK_LEFT)
+			field = TCPOPT_SACK_LEFT1;
+		else if (field == TCPOPT_SACK_RIGHT)
+			field = TCPOPT_SACK_RIGHT1;
 		break;
 	case TCPOPT_KIND_SACK2:
 		kind = TCPOPT_KIND_SACK;
-		optnum = 2;
+		if (field == TCPOPT_SACK_LEFT)
+			field = TCPOPT_SACK_LEFT2;
+		else if (field == TCPOPT_SACK_RIGHT)
+			field = TCPOPT_SACK_RIGHT2;
 		break;
 	case TCPOPT_KIND_SACK3:
 		kind = TCPOPT_KIND_SACK;
-		optnum = 3;
+		if (field == TCPOPT_SACK_LEFT)
+			field = TCPOPT_SACK_LEFT3;
+		else if (field == TCPOPT_SACK_RIGHT)
+			field = TCPOPT_SACK_RIGHT3;
+		break;
 	}
 
-	desc = tcpopt_protocols[kind];
+	if (kind < array_size(tcpopt_protocols))
+		desc = tcpopt_protocols[kind];
+
+	if (!desc)
+		return NULL;
 	tmpl = &desc->templates[field];
 	if (!tmpl)
 		return NULL;
@@ -164,34 +149,32 @@ struct expr *tcpopt_expr_alloc(const struct location *loc,
 	expr->exthdr.desc   = desc;
 	expr->exthdr.tmpl   = tmpl;
 	expr->exthdr.op     = NFT_EXTHDR_OP_TCPOPT;
-	expr->exthdr.offset = calc_offset(desc, tmpl, optnum);
+	expr->exthdr.offset = tmpl->offset;
 
 	return expr;
 }
 
-void tcpopt_init_raw(struct expr *expr, uint8_t type, unsigned int offset,
+void tcpopt_init_raw(struct expr *expr, uint8_t type, unsigned int off,
 		     unsigned int len, uint32_t flags)
 {
 	const struct proto_hdr_template *tmpl;
-	unsigned int i, off;
+	unsigned int i;
 
 	assert(expr->etype == EXPR_EXTHDR);
 
 	expr->len = len;
 	expr->exthdr.flags = flags;
-	expr->exthdr.offset = offset;
+	expr->exthdr.offset = off;
+
+	if (type >= array_size(tcpopt_protocols))
+		return;
 
-	assert(type < array_size(tcpopt_protocols));
 	expr->exthdr.desc = tcpopt_protocols[type];
 	expr->exthdr.flags = flags;
 	assert(expr->exthdr.desc != NULL);
 
 	for (i = 0; i < array_size(expr->exthdr.desc->templates); ++i) {
 		tmpl = &expr->exthdr.desc->templates[i];
-		/* We have to reverse calculate the offset for the sack options
-		 * at this point
-		 */
-		off = calc_offset_reverse(expr->exthdr.desc, tmpl, offset);
 		if (tmpl->offset != off || tmpl->len != len)
 			continue;
 
-- 
2.31.1