From 9697436145bf374093dc61e3ad857f7122de08ee Mon Sep 17 00:00:00 2001 From: Phil Sutter 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 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 --- 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