diff --git a/SOURCES/0021-json-Simplify-non-tcpopt-exthdr-printing-a-bit.patch b/SOURCES/0021-json-Simplify-non-tcpopt-exthdr-printing-a-bit.patch new file mode 100644 index 0000000..000a191 --- /dev/null +++ b/SOURCES/0021-json-Simplify-non-tcpopt-exthdr-printing-a-bit.patch @@ -0,0 +1,49 @@ +From 92f73f85dbd6559905679133cdf61e70004c805d Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Tue, 4 May 2021 13:18:11 +0200 +Subject: [PATCH] json: Simplify non-tcpopt exthdr printing a bit + +This was just duplicate code apart from the object's name. + +Signed-off-by: Phil Sutter +(cherry picked from commit fd81d3ec3ae8b8d1d54a708d63b2dab2c8508c90) +--- + src/json.c | 18 +++++++----------- + 1 file changed, 7 insertions(+), 11 deletions(-) + +diff --git a/src/json.c b/src/json.c +index 1fb5015124e16..6607d83f4e8f8 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -696,21 +696,17 @@ json_t *exthdr_expr_json(const struct expr *expr, struct output_ctx *octx) + + return json_pack("{s:o}", "tcp option", root); + } +- if (expr->exthdr.op == NFT_EXTHDR_OP_IPV4) { +- root = json_pack("{s:s}", "name", desc); + +- if (!is_exists) +- json_object_set_new(root, "field", json_string(field)); +- +- return json_pack("{s:o}", "ip option", root); +- } +- +- root = json_pack("{s:s}", +- "name", desc); ++ root = json_pack("{s:s}", "name", desc); + if (!is_exists) + json_object_set_new(root, "field", json_string(field)); + +- return json_pack("{s:o}", "exthdr", root); ++ switch (expr->exthdr.op) { ++ case NFT_EXTHDR_OP_IPV4: ++ return json_pack("{s:o}", "ip option", root); ++ default: ++ return json_pack("{s:o}", "exthdr", root); ++ } + } + + json_t *verdict_expr_json(const struct expr *expr, struct output_ctx *octx) +-- +2.33.0 + diff --git a/SOURCES/0022-scanner-introduce-start-condition-stack.patch b/SOURCES/0022-scanner-introduce-start-condition-stack.patch new file mode 100644 index 0000000..1bc0e4e --- /dev/null +++ b/SOURCES/0022-scanner-introduce-start-condition-stack.patch @@ -0,0 +1,167 @@ +From 80f3c19bc1b989ab7ba2b917193e8bd3f998ba39 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Mon, 8 Mar 2021 18:18:33 +0100 +Subject: [PATCH] scanner: introduce start condition stack + +Add a small initial chunk of flex start conditionals. + +This starts with two low-hanging fruits, numgen and j/symhash. + +NUMGEN and HASH start conditions are entered from flex when +the corresponding expression token is encountered. + +Flex returns to the INIT condition when the bison parser +has seen a complete numgen/hash statement. + +This intentionally uses a stack rather than BEGIN() +to eventually support nested states. + +The scanner_pop_start_cond() function argument is not used yet, but +will need to be used later to deal with nesting. + +Signed-off-by: Florian Westphal +(cherry picked from commit 5896772fe3c5f01696188ea04957a825ee601b12) +--- + include/parser.h | 8 ++++++++ + src/parser_bison.y | 11 +++++++---- + src/scanner.l | 36 +++++++++++++++++++++++++++++------- + 3 files changed, 44 insertions(+), 11 deletions(-) + +diff --git a/include/parser.h b/include/parser.h +index 9baa3a4db789f..b2ebd7aa226c5 100644 +--- a/include/parser.h ++++ b/include/parser.h +@@ -26,6 +26,12 @@ struct parser_state { + struct list_head *cmds; + }; + ++enum startcond_type { ++ PARSER_SC_BEGIN, ++ PARSER_SC_EXPR_HASH, ++ PARSER_SC_EXPR_NUMGEN, ++}; ++ + struct mnl_socket; + + extern void parser_init(struct nft_ctx *nft, struct parser_state *state, +@@ -45,4 +51,6 @@ extern void scanner_push_buffer(void *scanner, + const struct input_descriptor *indesc, + const char *buffer); + ++extern void scanner_pop_start_cond(void *scanner, enum startcond_type sc); ++ + #endif /* NFTABLES_PARSER_H */ +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 8644f66106496..da3fafcd1eeb1 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -857,6 +857,9 @@ opt_newline : NEWLINE + | /* empty */ + ; + ++close_scope_hash : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_HASH); }; ++close_scope_numgen : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_NUMGEN); }; ++ + common_block : INCLUDE QUOTED_STRING stmt_separator + { + if (scanner_include_file(nft, scanner, $2, &@$) < 0) { +@@ -4811,7 +4814,7 @@ numgen_type : INC { $$ = NFT_NG_INCREMENTAL; } + | RANDOM { $$ = NFT_NG_RANDOM; } + ; + +-numgen_expr : NUMGEN numgen_type MOD NUM offset_opt ++numgen_expr : NUMGEN numgen_type MOD NUM offset_opt close_scope_numgen + { + $$ = numgen_expr_alloc(&@$, $2, $4, $5); + } +@@ -4868,17 +4871,17 @@ xfrm_expr : IPSEC xfrm_dir xfrm_spnum xfrm_state_key + } + ; + +-hash_expr : JHASH expr MOD NUM SEED NUM offset_opt ++hash_expr : JHASH expr MOD NUM SEED NUM offset_opt close_scope_hash + { + $$ = hash_expr_alloc(&@$, $4, true, $6, $7, NFT_HASH_JENKINS); + $$->hash.expr = $2; + } +- | JHASH expr MOD NUM offset_opt ++ | JHASH expr MOD NUM offset_opt close_scope_hash + { + $$ = hash_expr_alloc(&@$, $4, false, 0, $5, NFT_HASH_JENKINS); + $$->hash.expr = $2; + } +- | SYMHASH MOD NUM offset_opt ++ | SYMHASH MOD NUM offset_opt close_scope_hash + { + $$ = hash_expr_alloc(&@$, $3, false, 0, $4, NFT_HASH_SYM); + } +diff --git a/src/scanner.l b/src/scanner.l +index 8bde1fbe912d8..ec8f252fbc8c8 100644 +--- a/src/scanner.l ++++ b/src/scanner.l +@@ -98,6 +98,8 @@ static void reset_pos(struct parser_state *state, struct location *loc) + state->indesc->column = 1; + } + ++static void scanner_push_start_cond(void *scanner, enum startcond_type type); ++ + #define YY_USER_ACTION { \ + update_pos(yyget_extra(yyscanner), yylloc, yyleng); \ + update_offset(yyget_extra(yyscanner), yylloc, yyleng); \ +@@ -193,6 +195,9 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) + %option yylineno + %option nodefault + %option warn ++%option stack ++%s SCANSTATE_EXPR_HASH ++%s SCANSTATE_EXPR_NUMGEN + + %% + +@@ -551,15 +556,21 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) + "state" { return STATE; } + "status" { return STATUS; } + +-"numgen" { return NUMGEN; } +-"inc" { return INC; } +-"mod" { return MOD; } +-"offset" { return OFFSET; } ++"numgen" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_NUMGEN); return NUMGEN; } ++{ ++ "inc" { return INC; } ++} + +-"jhash" { return JHASH; } +-"symhash" { return SYMHASH; } +-"seed" { return SEED; } ++"jhash" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_HASH); return JHASH; } ++"symhash" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_HASH); return SYMHASH; } + ++{ ++ "seed" { return SEED; } ++} ++{ ++ "mod" { return MOD; } ++ "offset" { return OFFSET; } ++} + "dup" { return DUP; } + "fwd" { return FWD; } + +@@ -973,3 +984,14 @@ void scanner_destroy(struct nft_ctx *nft) + input_descriptor_list_destroy(state); + yylex_destroy(nft->scanner); + } ++ ++static void scanner_push_start_cond(void *scanner, enum startcond_type type) ++{ ++ yy_push_state((int)type, scanner); ++} ++ ++void scanner_pop_start_cond(void *scanner, enum startcond_type t) ++{ ++ yy_pop_state(scanner); ++ (void)yy_top_state(scanner); /* suppress gcc warning wrt. unused function */ ++} +-- +2.33.0 + diff --git a/SOURCES/0023-scanner-sctp-Move-to-own-scope.patch b/SOURCES/0023-scanner-sctp-Move-to-own-scope.patch new file mode 100644 index 0000000..3aa28a6 --- /dev/null +++ b/SOURCES/0023-scanner-sctp-Move-to-own-scope.patch @@ -0,0 +1,93 @@ +From 5009b467a06a86f5dcc3218fb860cd81bc5e067f Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Tue, 4 May 2021 13:06:32 +0200 +Subject: [PATCH] scanner: sctp: Move to own scope + +This isolates only "vtag" token for now. + +Signed-off-by: Phil Sutter +Reviewed-by: Florian Westphal +(cherry picked from commit 0925d7e214825628e7db4a86d5ebbad578ab0777) + + Conflicts: + include/parser.h + src/parser_bison.y + src/scanner.l + -> Context changes due to missing other scopes. +--- + include/parser.h | 1 + + src/parser_bison.y | 5 +++-- + src/scanner.l | 8 ++++++-- + 3 files changed, 10 insertions(+), 4 deletions(-) + +diff --git a/include/parser.h b/include/parser.h +index b2ebd7aa226c5..4e7b4ef430966 100644 +--- a/include/parser.h ++++ b/include/parser.h +@@ -28,6 +28,7 @@ struct parser_state { + + enum startcond_type { + PARSER_SC_BEGIN, ++ PARSER_SC_SCTP, + PARSER_SC_EXPR_HASH, + PARSER_SC_EXPR_NUMGEN, + }; +diff --git a/src/parser_bison.y b/src/parser_bison.y +index da3fafcd1eeb1..383908fa3742f 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -859,6 +859,7 @@ opt_newline : NEWLINE + + close_scope_hash : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_HASH); }; + close_scope_numgen : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_NUMGEN); }; ++close_scope_sctp : { scanner_pop_start_cond(nft->scanner, PARSER_SC_SCTP); }; + + common_block : INCLUDE QUOTED_STRING stmt_separator + { +@@ -4620,7 +4621,7 @@ primary_rhs_expr : symbol_expr { $$ = $1; } + BYTEORDER_HOST_ENDIAN, + sizeof(data) * BITS_PER_BYTE, &data); + } +- | SCTP ++ | SCTP close_scope_sctp + { + uint8_t data = IPPROTO_SCTP; + $$ = constant_expr_alloc(&@$, &inet_protocol_type, +@@ -5345,7 +5346,7 @@ dccp_hdr_field : SPORT { $$ = DCCPHDR_SPORT; } + | TYPE { $$ = DCCPHDR_TYPE; } + ; + +-sctp_hdr_expr : SCTP sctp_hdr_field ++sctp_hdr_expr : SCTP sctp_hdr_field close_scope_sctp + { + $$ = payload_expr_alloc(&@$, &proto_sctp, $2); + } +diff --git a/src/scanner.l b/src/scanner.l +index ec8f252fbc8c8..c8e74e685f3d7 100644 +--- a/src/scanner.l ++++ b/src/scanner.l +@@ -196,6 +196,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) + %option nodefault + %option warn + %option stack ++%s SCANSTATE_SCTP + %s SCANSTATE_EXPR_HASH + %s SCANSTATE_EXPR_NUMGEN + +@@ -491,8 +492,11 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) + + "dccp" { return DCCP; } + +-"sctp" { return SCTP; } +-"vtag" { return VTAG; } ++"sctp" { scanner_push_start_cond(yyscanner, SCANSTATE_SCTP); return SCTP; } ++ ++{ ++ "vtag" { return VTAG; } ++} + + "rt" { return RT; } + "rt0" { return RT0; } +-- +2.33.0 + diff --git a/SOURCES/0024-exthdr-Implement-SCTP-Chunk-matching.patch b/SOURCES/0024-exthdr-Implement-SCTP-Chunk-matching.patch new file mode 100644 index 0000000..e2b5ba2 --- /dev/null +++ b/SOURCES/0024-exthdr-Implement-SCTP-Chunk-matching.patch @@ -0,0 +1,1622 @@ +From 9a599d5fbc903271281f9d0b9a9b6da4300d7279 Mon Sep 17 00:00:00 2001 +From: Phil Sutter +Date: Tue, 4 May 2021 13:41:38 +0200 +Subject: [PATCH] exthdr: Implement SCTP Chunk matching + +Extend exthdr expression to support scanning through SCTP packet chunks +and matching on fixed fields' values. + +Signed-off-by: Phil Sutter +Acked-by: Florian Westphal +(cherry picked from commit 0e3871cfd9a1e32a4ac041ce87a8057b11a89924) + + Conflicts: + include/parser.h + src/evaluate.c + src/parser_bison.y + src/scanner.l + -> Context changes due to missing other scopes. + -> Context change due to missing commit 6e6ef00028f1c + ("exthdr: remove tcp dependency for tcp option matching"). +--- + doc/libnftables-json.adoc | 13 + + doc/payload-expression.txt | 53 +++ + include/linux/netfilter/nf_tables.h | 2 + + include/parser.h | 1 + + include/sctp_chunk.h | 87 +++++ + src/Makefile.am | 1 + + src/evaluate.c | 2 + + src/exthdr.c | 8 + + src/json.c | 2 + + src/parser_bison.y | 148 ++++++++- + src/parser_json.c | 49 +++ + src/scanner.l | 38 +++ + src/sctp_chunk.c | 261 +++++++++++++++ + tests/py/inet/sctp.t | 37 +++ + tests/py/inet/sctp.t.json | 478 ++++++++++++++++++++++++++++ + tests/py/inet/sctp.t.payload | 155 +++++++++ + 16 files changed, 1333 insertions(+), 2 deletions(-) + create mode 100644 include/sctp_chunk.h + create mode 100644 src/sctp_chunk.c + +diff --git a/doc/libnftables-json.adoc b/doc/libnftables-json.adoc +index 858abbf73fbfc..fba4cb08ccb68 100644 +--- a/doc/libnftables-json.adoc ++++ b/doc/libnftables-json.adoc +@@ -1200,6 +1200,19 @@ Create a reference to a field (*field*) of a TCP option header (*name*). + If the *field* property is not given, the expression is to be used as a TCP option + existence check in a *match* statement with a boolean on the right hand side. + ++=== SCTP CHUNK ++[verse] ++*{ "sctp chunk": { ++ "name":* 'STRING'*, ++ "field":* 'STRING' ++*}}* ++ ++Create a reference to a field (*field*) of an SCTP chunk (*name*). ++ ++If the *field* property is not given, the expression is to be used as an SCTP ++chunk existence check in a *match* statement with a boolean on the right hand ++side. ++ + === META + [verse] + ____ +diff --git a/doc/payload-expression.txt b/doc/payload-expression.txt +index a593e2e7b947d..a338dcf044505 100644 +--- a/doc/payload-expression.txt ++++ b/doc/payload-expression.txt +@@ -369,7 +369,33 @@ integer (16 bit) + SCTP HEADER EXPRESSION + ~~~~~~~~~~~~~~~~~~~~~~~ + [verse] ++____ + *sctp* {*sport* | *dport* | *vtag* | *checksum*} ++*sctp chunk* 'CHUNK' [ 'FIELD' ] ++ ++'CHUNK' := *data* | *init* | *init-ack* | *sack* | *heartbeat* | ++ *heartbeat-ack* | *abort* | *shutdown* | *shutdown-ack* | *error* | ++ *cookie-echo* | *cookie-ack* | *ecne* | *cwr* | *shutdown-complete* ++ | *asconf-ack* | *forward-tsn* | *asconf* ++ ++'FIELD' := 'COMMON_FIELD' | 'DATA_FIELD' | 'INIT_FIELD' | 'INIT_ACK_FIELD' | ++ 'SACK_FIELD' | 'SHUTDOWN_FIELD' | 'ECNE_FIELD' | 'CWR_FIELD' | ++ 'ASCONF_ACK_FIELD' | 'FORWARD_TSN_FIELD' | 'ASCONF_FIELD' ++ ++'COMMON_FIELD' := *type* | *flags* | *length* ++'DATA_FIELD' := *tsn* | *stream* | *ssn* | *ppid* ++'INIT_FIELD' := *init-tag* | *a-rwnd* | *num-outbound-streams* | ++ *num-inbound-streams* | *initial-tsn* ++'INIT_ACK_FIELD' := 'INIT_FIELD' ++'SACK_FIELD' := *cum-tsn-ack* | *a-rwnd* | *num-gap-ack-blocks* | ++ *num-dup-tsns* ++'SHUTDOWN_FIELD' := *cum-tsn-ack* ++'ECNE_FIELD' := *lowest-tsn* ++'CWR_FIELD' := *lowest-tsn* ++'ASCONF_ACK_FIELD' := *seqno* ++'FORWARD_TSN_FIELD' := *new-cum-tsn* ++'ASCONF_FIELD' := *seqno* ++____ + + .SCTP header expression + [options="header"] +@@ -387,8 +413,35 @@ integer (32 bit) + |checksum| + Checksum| + integer (32 bit) ++|chunk| ++Search chunk in packet| ++without 'FIELD', boolean indicating existence + |================ + ++.SCTP chunk fields ++[options="header"] ++|================== ++|Name| Width in bits | Chunk | Notes ++|type| 8 | all | not useful, defined by chunk type ++|flags| 8 | all | semantics defined on per-chunk basis ++|length| 16 | all | length of this chunk in bytes excluding padding ++|tsn| 32 | data | transmission sequence number ++|stream| 16 | data | stream identifier ++|ssn| 16 | data | stream sequence number ++|ppid| 32 | data | payload protocol identifier ++|init-tag| 32 | init, init-ack | initiate tag ++|a-rwnd| 32 | init, init-ack, sack | advertised receiver window credit ++|num-outbound-streams| 16 | init, init-ack | number of outbound streams ++|num-inbound-streams| 16 | init, init-ack | number of inbound streams ++|initial-tsn| 32 | init, init-ack | initial transmit sequence number ++|cum-tsn-ack| 32 | sack, shutdown | cumulative transmission sequence number acknowledged ++|num-gap-ack-blocks| 16 | sack | number of Gap Ack Blocks included ++|num-dup-tsns| 16 | sack | number of duplicate transmission sequence numbers received ++|lowest-tsn| 32 | ecne, cwr | lowest transmission sequence number ++|seqno| 32 | asconf-ack, asconf | sequence number ++|new-cum-tsn| 32 | forward-tsn | new cumulative transmission sequence number ++|================== ++ + DCCP HEADER EXPRESSION + ~~~~~~~~~~~~~~~~~~~~~~ + [verse] +diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h +index b1633e7ba5296..7f9fc203f6f82 100644 +--- a/include/linux/netfilter/nf_tables.h ++++ b/include/linux/netfilter/nf_tables.h +@@ -806,11 +806,13 @@ enum nft_exthdr_flags { + * @NFT_EXTHDR_OP_IPV6: match against ipv6 extension headers + * @NFT_EXTHDR_OP_TCP: match against tcp options + * @NFT_EXTHDR_OP_IPV4: match against ipv4 options ++ * @NFT_EXTHDR_OP_SCTP: match against sctp chunks + */ + enum nft_exthdr_op { + NFT_EXTHDR_OP_IPV6, + NFT_EXTHDR_OP_TCPOPT, + NFT_EXTHDR_OP_IPV4, ++ NFT_EXTHDR_OP_SCTP, + __NFT_EXTHDR_OP_MAX + }; + #define NFT_EXTHDR_OP_MAX (__NFT_EXTHDR_OP_MAX - 1) +diff --git a/include/parser.h b/include/parser.h +index 4e7b4ef430966..f0c5996dd3dd3 100644 +--- a/include/parser.h ++++ b/include/parser.h +@@ -31,6 +31,7 @@ enum startcond_type { + PARSER_SC_SCTP, + PARSER_SC_EXPR_HASH, + PARSER_SC_EXPR_NUMGEN, ++ PARSER_SC_EXPR_SCTP_CHUNK, + }; + + struct mnl_socket; +diff --git a/include/sctp_chunk.h b/include/sctp_chunk.h +new file mode 100644 +index 0000000000000..3819200f9f0a4 +--- /dev/null ++++ b/include/sctp_chunk.h +@@ -0,0 +1,87 @@ ++/* ++ * Copyright Red Hat ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 (or any ++ * later) as published by the Free Software Foundation. ++ */ ++ ++#ifndef NFTABLES_SCTP_CHUNK_H ++#define NFTABLES_SCTP_CHUNK_H ++ ++/* SCTP chunk types used on wire */ ++enum sctp_hdr_chunk_types { ++ SCTP_CHUNK_TYPE_DATA = 0, ++ SCTP_CHUNK_TYPE_INIT = 1, ++ SCTP_CHUNK_TYPE_INIT_ACK = 2, ++ SCTP_CHUNK_TYPE_SACK = 3, ++ SCTP_CHUNK_TYPE_HEARTBEAT = 4, ++ SCTP_CHUNK_TYPE_HEARTBEAT_ACK = 5, ++ SCTP_CHUNK_TYPE_ABORT = 6, ++ SCTP_CHUNK_TYPE_SHUTDOWN = 7, ++ SCTP_CHUNK_TYPE_SHUTDOWN_ACK = 8, ++ SCTP_CHUNK_TYPE_ERROR = 9, ++ SCTP_CHUNK_TYPE_COOKIE_ECHO = 10, ++ SCTP_CHUNK_TYPE_COOKIE_ACK = 11, ++ SCTP_CHUNK_TYPE_ECNE = 12, ++ SCTP_CHUNK_TYPE_CWR = 13, ++ SCTP_CHUNK_TYPE_SHUTDOWN_COMPLETE = 14, ++ SCTP_CHUNK_TYPE_ASCONF_ACK = 128, ++ SCTP_CHUNK_TYPE_FORWARD_TSN = 192, ++ SCTP_CHUNK_TYPE_ASCONF = 193, ++}; ++ ++enum sctp_hdr_chunk_common_fields { ++ SCTP_CHUNK_COMMON_TYPE, ++ SCTP_CHUNK_COMMON_FLAGS, ++ SCTP_CHUNK_COMMON_LENGTH, ++ __SCTP_CHUNK_COMMON_MAX, ++}; ++ ++#define SCTP_CHUNK_START_INDEX __SCTP_CHUNK_COMMON_MAX ++ ++enum sctp_hdr_chunk_data_fields { ++ SCTP_CHUNK_DATA_TSN = SCTP_CHUNK_START_INDEX, ++ SCTP_CHUNK_DATA_STREAM, ++ SCTP_CHUNK_DATA_SSN, ++ SCTP_CHUNK_DATA_PPID, ++}; ++ ++enum sctp_hdr_chunk_init_fields { ++ SCTP_CHUNK_INIT_TAG = SCTP_CHUNK_START_INDEX, ++ SCTP_CHUNK_INIT_RWND, ++ SCTP_CHUNK_INIT_OSTREAMS, ++ SCTP_CHUNK_INIT_ISTREAMS, ++ SCTP_CHUNK_INIT_TSN, ++}; ++ ++enum sctp_hdr_chunk_sack_fields { ++ SCTP_CHUNK_SACK_CTSN_ACK = SCTP_CHUNK_START_INDEX, ++ SCTP_CHUNK_SACK_RWND, ++ SCTP_CHUNK_SACK_GACK_BLOCKS, ++ SCTP_CHUNK_SACK_DUP_TSNS, ++}; ++ ++enum sctp_hdr_chunk_shutdown_fields { ++ SCTP_CHUNK_SHUTDOWN_CTSN_ACK = SCTP_CHUNK_START_INDEX, ++}; ++ ++enum sctp_hdr_chunk_ecne_cwr_fields { ++ SCTP_CHUNK_ECNE_CWR_MIN_TSN = SCTP_CHUNK_START_INDEX, ++}; ++ ++enum sctp_hdr_chunk_asconf_fields { ++ SCTP_CHUNK_ASCONF_SEQNO = SCTP_CHUNK_START_INDEX, ++}; ++ ++enum sctp_hdr_chunk_fwd_tsn_fields { ++ SCTP_CHUNK_FORWARD_TSN_NCTSN = SCTP_CHUNK_START_INDEX, ++}; ++ ++struct expr *sctp_chunk_expr_alloc(const struct location *loc, ++ unsigned int type, unsigned int field); ++void sctp_chunk_init_raw(struct expr *expr, uint8_t type, unsigned int off, ++ unsigned int len, uint32_t flags); ++const struct exthdr_desc *sctp_chunk_protocol_find(const char *name); ++ ++#endif /* NFTABLES_SCTP_CHUNK_H */ +diff --git a/src/Makefile.am b/src/Makefile.am +index 3041a933bc067..253cb995a3f07 100644 +--- a/src/Makefile.am ++++ b/src/Makefile.am +@@ -74,6 +74,7 @@ libnftables_la_SOURCES = \ + tcpopt.c \ + socket.c \ + print.c \ ++ sctp_chunk.c \ + libnftables.c \ + libnftables.map + +diff --git a/src/evaluate.c b/src/evaluate.c +index 2a897f469434a..bba685af720ed 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -583,6 +583,8 @@ static int expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp) + dependency = &proto_tcp; + pb = PROTO_BASE_TRANSPORT_HDR; + break; ++ case NFT_EXTHDR_OP_SCTP: ++ return __expr_evaluate_exthdr(ctx, exprp); + case NFT_EXTHDR_OP_IPV4: + dependency = &proto_ip; + break; +diff --git a/src/exthdr.c b/src/exthdr.c +index b0243adad1da4..22a08b0c9c2bf 100644 +--- a/src/exthdr.c ++++ b/src/exthdr.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + static const struct exthdr_desc *exthdr_definitions[PROTO_DESC_MAX + 1] = { + [EXTHDR_DESC_HBH] = &exthdr_hbh, +@@ -75,6 +76,11 @@ static void exthdr_expr_print(const struct expr *expr, struct output_ctx *octx) + if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT) + return; + nft_print(octx, " %s", expr->exthdr.tmpl->token); ++ } else if (expr->exthdr.op == NFT_EXTHDR_OP_SCTP) { ++ nft_print(octx, "sctp chunk %s", expr->exthdr.desc->name); ++ if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT) ++ return; ++ nft_print(octx, " %s", expr->exthdr.tmpl->token); + } else { + if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT) + nft_print(octx, "exthdr %s", expr->exthdr.desc->name); +@@ -291,6 +297,8 @@ void exthdr_init_raw(struct expr *expr, uint8_t type, + return tcpopt_init_raw(expr, type, offset, len, flags); + if (op == NFT_EXTHDR_OP_IPV4) + return ipopt_init_raw(expr, type, offset, len, flags, true); ++ if (op == NFT_EXTHDR_OP_SCTP) ++ return sctp_chunk_init_raw(expr, type, offset, len, flags); + + expr->len = len; + expr->exthdr.flags = flags; +diff --git a/src/json.c b/src/json.c +index 6607d83f4e8f8..9f345cb486389 100644 +--- a/src/json.c ++++ b/src/json.c +@@ -704,6 +704,8 @@ json_t *exthdr_expr_json(const struct expr *expr, struct output_ctx *octx) + switch (expr->exthdr.op) { + case NFT_EXTHDR_OP_IPV4: + return json_pack("{s:o}", "ip option", root); ++ case NFT_EXTHDR_OP_SCTP: ++ return json_pack("{s:o}", "sctp chunk", root); + default: + return json_pack("{s:o}", "exthdr", root); + } +diff --git a/src/parser_bison.y b/src/parser_bison.y +index 383908fa3742f..44b9107e8a360 100644 +--- a/src/parser_bison.y ++++ b/src/parser_bison.y +@@ -39,6 +39,7 @@ + #include + #include + #include ++#include + + #include "parser_bison.h" + +@@ -417,6 +418,40 @@ int nft_lex(void *, void *, void *); + %token DCCP "dccp" + + %token SCTP "sctp" ++%token CHUNK "chunk" ++%token DATA "data" ++%token INIT "init" ++%token INIT_ACK "init-ack" ++%token HEARTBEAT "heartbeat" ++%token HEARTBEAT_ACK "heartbeat-ack" ++%token ABORT "abort" ++%token SHUTDOWN "shutdown" ++%token SHUTDOWN_ACK "shutdown-ack" ++%token ERROR "error" ++%token COOKIE_ECHO "cookie-echo" ++%token COOKIE_ACK "cookie-ack" ++%token ECNE "ecne" ++%token CWR "cwr" ++%token SHUTDOWN_COMPLETE "shutdown-complete" ++%token ASCONF_ACK "asconf-ack" ++%token FORWARD_TSN "forward-tsn" ++%token ASCONF "asconf" ++%token TSN "tsn" ++%token STREAM "stream" ++%token SSN "ssn" ++%token PPID "ppid" ++%token INIT_TAG "init-tag" ++%token A_RWND "a-rwnd" ++%token NUM_OSTREAMS "num-outbound-streams" ++%token NUM_ISTREAMS "num-inbound-streams" ++%token INIT_TSN "initial-tsn" ++%token CUM_TSN_ACK "cum-tsn-ack" ++%token NUM_GACK_BLOCKS "num-gap-ack-blocks" ++%token NUM_DUP_TSNS "num-dup-tsns" ++%token LOWEST_TSN "lowest-tsn" ++%token SEQNO "seqno" ++%token NEW_CUM_TSN "new-cum-tsn" ++ + %token VTAG "vtag" + + %token RT "rt" +@@ -763,9 +798,12 @@ int nft_lex(void *, void *, void *); + %type udp_hdr_expr udplite_hdr_expr + %destructor { expr_free($$); } udp_hdr_expr udplite_hdr_expr + %type udp_hdr_field udplite_hdr_field +-%type dccp_hdr_expr sctp_hdr_expr +-%destructor { expr_free($$); } dccp_hdr_expr sctp_hdr_expr ++%type dccp_hdr_expr sctp_hdr_expr sctp_chunk_alloc ++%destructor { expr_free($$); } dccp_hdr_expr sctp_hdr_expr sctp_chunk_alloc + %type dccp_hdr_field sctp_hdr_field ++%type sctp_chunk_type sctp_chunk_common_field ++%type sctp_chunk_data_field sctp_chunk_init_field ++%type sctp_chunk_sack_field + %type th_hdr_expr + %destructor { expr_free($$); } th_hdr_expr + %type th_hdr_field +@@ -860,6 +898,7 @@ opt_newline : NEWLINE + close_scope_hash : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_HASH); }; + close_scope_numgen : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_NUMGEN); }; + close_scope_sctp : { scanner_pop_start_cond(nft->scanner, PARSER_SC_SCTP); }; ++close_scope_sctp_chunk : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_SCTP_CHUNK); }; + + common_block : INCLUDE QUOTED_STRING stmt_separator + { +@@ -5346,10 +5385,115 @@ dccp_hdr_field : SPORT { $$ = DCCPHDR_SPORT; } + | TYPE { $$ = DCCPHDR_TYPE; } + ; + ++sctp_chunk_type : DATA { $$ = SCTP_CHUNK_TYPE_DATA; } ++ | INIT { $$ = SCTP_CHUNK_TYPE_INIT; } ++ | INIT_ACK { $$ = SCTP_CHUNK_TYPE_INIT_ACK; } ++ | SACK { $$ = SCTP_CHUNK_TYPE_SACK; } ++ | HEARTBEAT { $$ = SCTP_CHUNK_TYPE_HEARTBEAT; } ++ | HEARTBEAT_ACK { $$ = SCTP_CHUNK_TYPE_HEARTBEAT_ACK; } ++ | ABORT { $$ = SCTP_CHUNK_TYPE_ABORT; } ++ | SHUTDOWN { $$ = SCTP_CHUNK_TYPE_SHUTDOWN; } ++ | SHUTDOWN_ACK { $$ = SCTP_CHUNK_TYPE_SHUTDOWN_ACK; } ++ | ERROR { $$ = SCTP_CHUNK_TYPE_ERROR; } ++ | COOKIE_ECHO { $$ = SCTP_CHUNK_TYPE_COOKIE_ECHO; } ++ | COOKIE_ACK { $$ = SCTP_CHUNK_TYPE_COOKIE_ACK; } ++ | ECNE { $$ = SCTP_CHUNK_TYPE_ECNE; } ++ | CWR { $$ = SCTP_CHUNK_TYPE_CWR; } ++ | SHUTDOWN_COMPLETE { $$ = SCTP_CHUNK_TYPE_SHUTDOWN_COMPLETE; } ++ | ASCONF_ACK { $$ = SCTP_CHUNK_TYPE_ASCONF_ACK; } ++ | FORWARD_TSN { $$ = SCTP_CHUNK_TYPE_FORWARD_TSN; } ++ | ASCONF { $$ = SCTP_CHUNK_TYPE_ASCONF; } ++ ; ++ ++sctp_chunk_common_field : TYPE { $$ = SCTP_CHUNK_COMMON_TYPE; } ++ | FLAGS { $$ = SCTP_CHUNK_COMMON_FLAGS; } ++ | LENGTH { $$ = SCTP_CHUNK_COMMON_LENGTH; } ++ ; ++ ++sctp_chunk_data_field : TSN { $$ = SCTP_CHUNK_DATA_TSN; } ++ | STREAM { $$ = SCTP_CHUNK_DATA_STREAM; } ++ | SSN { $$ = SCTP_CHUNK_DATA_SSN; } ++ | PPID { $$ = SCTP_CHUNK_DATA_PPID; } ++ ; ++ ++sctp_chunk_init_field : INIT_TAG { $$ = SCTP_CHUNK_INIT_TAG; } ++ | A_RWND { $$ = SCTP_CHUNK_INIT_RWND; } ++ | NUM_OSTREAMS { $$ = SCTP_CHUNK_INIT_OSTREAMS; } ++ | NUM_ISTREAMS { $$ = SCTP_CHUNK_INIT_ISTREAMS; } ++ | INIT_TSN { $$ = SCTP_CHUNK_INIT_TSN; } ++ ; ++ ++sctp_chunk_sack_field : CUM_TSN_ACK { $$ = SCTP_CHUNK_SACK_CTSN_ACK; } ++ | A_RWND { $$ = SCTP_CHUNK_SACK_RWND; } ++ | NUM_GACK_BLOCKS { $$ = SCTP_CHUNK_SACK_GACK_BLOCKS; } ++ | NUM_DUP_TSNS { $$ = SCTP_CHUNK_SACK_DUP_TSNS; } ++ ; ++ ++sctp_chunk_alloc : sctp_chunk_type ++ { ++ $$ = sctp_chunk_expr_alloc(&@$, $1, SCTP_CHUNK_COMMON_TYPE); ++ $$->exthdr.flags = NFT_EXTHDR_F_PRESENT; ++ } ++ | sctp_chunk_type sctp_chunk_common_field ++ { ++ $$ = sctp_chunk_expr_alloc(&@$, $1, $2); ++ } ++ | DATA sctp_chunk_data_field ++ { ++ $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_DATA, $2); ++ } ++ | INIT sctp_chunk_init_field ++ { ++ $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_INIT, $2); ++ } ++ | INIT_ACK sctp_chunk_init_field ++ { ++ $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_INIT_ACK, $2); ++ } ++ | SACK sctp_chunk_sack_field ++ { ++ $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_SACK, $2); ++ } ++ | SHUTDOWN CUM_TSN_ACK ++ { ++ $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_SHUTDOWN, ++ SCTP_CHUNK_SHUTDOWN_CTSN_ACK); ++ } ++ | ECNE LOWEST_TSN ++ { ++ $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_ECNE, ++ SCTP_CHUNK_ECNE_CWR_MIN_TSN); ++ } ++ | CWR LOWEST_TSN ++ { ++ $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_CWR, ++ SCTP_CHUNK_ECNE_CWR_MIN_TSN); ++ } ++ | ASCONF_ACK SEQNO ++ { ++ $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_ASCONF_ACK, ++ SCTP_CHUNK_ASCONF_SEQNO); ++ } ++ | FORWARD_TSN NEW_CUM_TSN ++ { ++ $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_FORWARD_TSN, ++ SCTP_CHUNK_FORWARD_TSN_NCTSN); ++ } ++ | ASCONF SEQNO ++ { ++ $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_ASCONF, ++ SCTP_CHUNK_ASCONF_SEQNO); ++ } ++ ; ++ + sctp_hdr_expr : SCTP sctp_hdr_field close_scope_sctp + { + $$ = payload_expr_alloc(&@$, &proto_sctp, $2); + } ++ | SCTP CHUNK sctp_chunk_alloc close_scope_sctp_chunk close_scope_sctp ++ { ++ $$ = $3; ++ } + ; + + sctp_hdr_field : SPORT { $$ = SCTPHDR_SPORT; } +diff --git a/src/parser_json.c b/src/parser_json.c +index 9bba77dad5f0d..0dce6ba57f634 100644 +--- a/src/parser_json.c ++++ b/src/parser_json.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + + #include +@@ -707,6 +708,53 @@ static struct expr *json_parse_ip_option_expr(struct json_ctx *ctx, + return ipopt_expr_alloc(int_loc, descval, fieldval, 0); + } + ++static int json_parse_sctp_chunk_field(const struct exthdr_desc *desc, ++ const char *name, int *val) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < array_size(desc->templates); i++) { ++ if (desc->templates[i].token && ++ !strcmp(desc->templates[i].token, name)) { ++ if (val) ++ *val = i; ++ return 0; ++ } ++ } ++ return 1; ++} ++ ++static struct expr *json_parse_sctp_chunk_expr(struct json_ctx *ctx, ++ const char *type, json_t *root) ++{ ++ const struct exthdr_desc *desc; ++ const char *name, *field; ++ struct expr *expr; ++ int fieldval; ++ ++ if (json_unpack_err(ctx, root, "{s:s}", "name", &name)) ++ return NULL; ++ ++ desc = sctp_chunk_protocol_find(name); ++ if (!desc) { ++ json_error(ctx, "Unknown sctp chunk name '%s'.", name); ++ return NULL; ++ } ++ ++ if (json_unpack(root, "{s:s}", "field", &field)) { ++ expr = sctp_chunk_expr_alloc(int_loc, desc->type, ++ SCTP_CHUNK_COMMON_TYPE); ++ expr->exthdr.flags = NFT_EXTHDR_F_PRESENT; ++ ++ return expr; ++ } ++ if (json_parse_sctp_chunk_field(desc, field, &fieldval)) { ++ json_error(ctx, "Unknown sctp chunk field '%s'.", field); ++ return NULL; ++ } ++ return sctp_chunk_expr_alloc(int_loc, desc->type, fieldval); ++} ++ + static const struct exthdr_desc *exthdr_lookup_byname(const char *name) + { + const struct exthdr_desc *exthdr_tbl[] = { +@@ -1412,6 +1460,7 @@ static struct expr *json_parse_expr(struct json_ctx *ctx, json_t *root) + { "exthdr", json_parse_exthdr_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "tcp option", json_parse_tcp_option_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_CONCAT }, + { "ip option", json_parse_ip_option_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_CONCAT }, ++ { "sctp chunk", json_parse_sctp_chunk_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_CONCAT }, + { "meta", json_parse_meta_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "osf", json_parse_osf_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_MAP | CTX_F_CONCAT }, + { "ipsec", json_parse_xfrm_expr, CTX_F_PRIMARY | CTX_F_MAP | CTX_F_CONCAT }, +diff --git a/src/scanner.l b/src/scanner.l +index c8e74e685f3d7..1bf07ec2d44c4 100644 +--- a/src/scanner.l ++++ b/src/scanner.l +@@ -199,6 +199,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) + %s SCANSTATE_SCTP + %s SCANSTATE_EXPR_HASH + %s SCANSTATE_EXPR_NUMGEN ++%s SCANSTATE_EXPR_SCTP_CHUNK + + %% + +@@ -495,9 +496,46 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) + "sctp" { scanner_push_start_cond(yyscanner, SCANSTATE_SCTP); return SCTP; } + + { ++ "chunk" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_SCTP_CHUNK); return CHUNK; } + "vtag" { return VTAG; } + } + ++{ ++ "data" { return DATA; } ++ "init" { return INIT; } ++ "init-ack" { return INIT_ACK; } ++ "heartbeat" { return HEARTBEAT; } ++ "heartbeat-ack" { return HEARTBEAT_ACK; } ++ "abort" { return ABORT; } ++ "shutdown" { return SHUTDOWN; } ++ "shutdown-ack" { return SHUTDOWN_ACK; } ++ "error" { return ERROR; } ++ "cookie-echo" { return COOKIE_ECHO; } ++ "cookie-ack" { return COOKIE_ACK; } ++ "ecne" { return ECNE; } ++ "cwr" { return CWR; } ++ "shutdown-complete" { return SHUTDOWN_COMPLETE; } ++ "asconf-ack" { return ASCONF_ACK; } ++ "forward-tsn" { return FORWARD_TSN; } ++ "asconf" { return ASCONF; } ++ ++ "tsn" { return TSN; } ++ "stream" { return STREAM; } ++ "ssn" { return SSN; } ++ "ppid" { return PPID; } ++ "init-tag" { return INIT_TAG; } ++ "a-rwnd" { return A_RWND; } ++ "num-outbound-streams" { return NUM_OSTREAMS; } ++ "num-inbound-streams" { return NUM_ISTREAMS; } ++ "initial-tsn" { return INIT_TSN; } ++ "cum-tsn-ack" { return CUM_TSN_ACK; } ++ "num-gap-ack-blocks" { return NUM_GACK_BLOCKS; } ++ "num-dup-tsns" { return NUM_DUP_TSNS; } ++ "lowest-tsn" { return LOWEST_TSN; } ++ "seqno" { return SEQNO; } ++ "new-cum-tsn" { return NEW_CUM_TSN; } ++} ++ + "rt" { return RT; } + "rt0" { return RT0; } + "rt2" { return RT2; } +diff --git a/src/sctp_chunk.c b/src/sctp_chunk.c +new file mode 100644 +index 0000000000000..6e73e72f8308b +--- /dev/null ++++ b/src/sctp_chunk.c +@@ -0,0 +1,261 @@ ++/* ++ * Copyright Red Hat ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 (or any ++ * later) as published by the Free Software Foundation. ++ */ ++ ++#include ++#include ++ ++#include ++ ++#define PHT(__token, __offset, __len) \ ++ PROTO_HDR_TEMPLATE(__token, &integer_type, BYTEORDER_BIG_ENDIAN, \ ++ __offset, __len) ++ ++static const struct exthdr_desc sctp_chunk_data = { ++ .name = "data", ++ .type = SCTP_CHUNK_TYPE_DATA, ++ .templates = { ++ [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), ++ [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), ++ [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), ++ [SCTP_CHUNK_DATA_TSN] = PHT("tsn", 32, 32), ++ [SCTP_CHUNK_DATA_STREAM] = PHT("stream", 64, 16), ++ [SCTP_CHUNK_DATA_SSN] = PHT("ssn", 80, 16), ++ [SCTP_CHUNK_DATA_PPID] = PHT("ppid", 96, 32), ++ }, ++}; ++ ++static const struct exthdr_desc sctp_chunk_init = { ++ .name = "init", ++ .type = SCTP_CHUNK_TYPE_INIT, ++ .templates = { ++ [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), ++ [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), ++ [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), ++ [SCTP_CHUNK_INIT_TAG] = PHT("init-tag", 32, 32), ++ [SCTP_CHUNK_INIT_RWND] = PHT("a-rwnd", 64, 32), ++ [SCTP_CHUNK_INIT_OSTREAMS] = PHT("num-outbound-streams", 96, 16), ++ [SCTP_CHUNK_INIT_ISTREAMS] = PHT("num-inbound-streams", 112, 16), ++ [SCTP_CHUNK_INIT_TSN] = PHT("initial-tsn", 128, 32), ++ }, ++}; ++ ++static const struct exthdr_desc sctp_chunk_init_ack = { ++ .name = "init-ack", ++ .type = SCTP_CHUNK_TYPE_INIT_ACK, ++ .templates = { ++ [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), ++ [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), ++ [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), ++ [SCTP_CHUNK_INIT_TAG] = PHT("init-tag", 32, 32), ++ [SCTP_CHUNK_INIT_RWND] = PHT("a-rwnd", 64, 32), ++ [SCTP_CHUNK_INIT_OSTREAMS] = PHT("num-outbound-streams", 96, 16), ++ [SCTP_CHUNK_INIT_ISTREAMS] = PHT("num-inbound-streams", 112, 16), ++ [SCTP_CHUNK_INIT_TSN] = PHT("initial-tsn", 128, 32), ++ }, ++}; ++ ++static const struct exthdr_desc sctp_chunk_sack = { ++ .name = "sack", ++ .type = SCTP_CHUNK_TYPE_SACK, ++ .templates = { ++ [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), ++ [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), ++ [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), ++ [SCTP_CHUNK_SACK_CTSN_ACK] = PHT("cum-tsn-ack", 32, 32), ++ [SCTP_CHUNK_SACK_RWND] = PHT("a-rwnd", 64, 32), ++ [SCTP_CHUNK_SACK_GACK_BLOCKS] = PHT("num-gap-ack-blocks", 96, 16), ++ [SCTP_CHUNK_SACK_DUP_TSNS] = PHT("num-dup-tsns", 112, 16), ++ }, ++}; ++ ++static const struct exthdr_desc sctp_chunk_shutdown = { ++ .name = "shutdown", ++ .type = SCTP_CHUNK_TYPE_SHUTDOWN, ++ .templates = { ++ [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), ++ [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), ++ [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), ++ [SCTP_CHUNK_SHUTDOWN_CTSN_ACK] = PHT("cum-tsn-ack", 32, 32), ++ }, ++}; ++ ++static const struct exthdr_desc sctp_chunk_ecne = { ++ .name = "ecne", ++ .type = SCTP_CHUNK_TYPE_ECNE, ++ .templates = { ++ [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), ++ [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), ++ [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), ++ [SCTP_CHUNK_ECNE_CWR_MIN_TSN] = PHT("lowest-tsn", 32, 32), ++ }, ++}; ++ ++static const struct exthdr_desc sctp_chunk_cwr = { ++ .name = "cwr", ++ .type = SCTP_CHUNK_TYPE_CWR, ++ .templates = { ++ [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), ++ [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), ++ [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), ++ [SCTP_CHUNK_ECNE_CWR_MIN_TSN] = PHT("lowest-tsn", 32, 32), ++ }, ++}; ++ ++static const struct exthdr_desc sctp_chunk_asconf_ack = { ++ .name = "asconf-ack", ++ .type = SCTP_CHUNK_TYPE_ASCONF_ACK, ++ .templates = { ++ [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), ++ [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), ++ [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), ++ [SCTP_CHUNK_ASCONF_SEQNO] = PHT("seqno", 32, 32), ++ }, ++}; ++ ++static const struct exthdr_desc sctp_chunk_forward_tsn = { ++ .name = "forward-tsn", ++ .type = SCTP_CHUNK_TYPE_FORWARD_TSN, ++ .templates = { ++ [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), ++ [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), ++ [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), ++ [SCTP_CHUNK_FORWARD_TSN_NCTSN] = PHT("new-cum-tsn", 32, 32), ++ }, ++}; ++ ++static const struct exthdr_desc sctp_chunk_asconf = { ++ .name = "asconf", ++ .type = SCTP_CHUNK_TYPE_ASCONF, ++ .templates = { ++ [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), ++ [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), ++ [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), ++ [SCTP_CHUNK_ASCONF_SEQNO] = PHT("seqno", 32, 32), ++ }, ++}; ++ ++#define SCTP_CHUNK_DESC_GENERATOR(descname, hname, desctype) \ ++static const struct exthdr_desc sctp_chunk_##descname = { \ ++ .name = #hname, \ ++ .type = SCTP_CHUNK_TYPE_##desctype, \ ++ .templates = { \ ++ [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), \ ++ [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), \ ++ [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16),\ ++ }, \ ++}; ++ ++SCTP_CHUNK_DESC_GENERATOR(heartbeat, heartbeat, HEARTBEAT) ++SCTP_CHUNK_DESC_GENERATOR(heartbeat_ack, heartbeat-ack, HEARTBEAT_ACK) ++SCTP_CHUNK_DESC_GENERATOR(abort, abort, ABORT) ++SCTP_CHUNK_DESC_GENERATOR(shutdown_ack, shutdown-ack, SHUTDOWN_ACK) ++SCTP_CHUNK_DESC_GENERATOR(error, error, ERROR) ++SCTP_CHUNK_DESC_GENERATOR(cookie_echo, cookie-echo, COOKIE_ECHO) ++SCTP_CHUNK_DESC_GENERATOR(cookie_ack, cookie-ack, COOKIE_ACK) ++SCTP_CHUNK_DESC_GENERATOR(shutdown_complete, shutdown-complete, SHUTDOWN_COMPLETE) ++ ++#undef SCTP_CHUNK_DESC_GENERATOR ++ ++static const struct exthdr_desc *sctp_chunk_protocols[] = { ++ [SCTP_CHUNK_TYPE_DATA] = &sctp_chunk_data, ++ [SCTP_CHUNK_TYPE_INIT] = &sctp_chunk_init, ++ [SCTP_CHUNK_TYPE_INIT_ACK] = &sctp_chunk_init_ack, ++ [SCTP_CHUNK_TYPE_SACK] = &sctp_chunk_sack, ++ [SCTP_CHUNK_TYPE_HEARTBEAT] = &sctp_chunk_heartbeat, ++ [SCTP_CHUNK_TYPE_HEARTBEAT_ACK] = &sctp_chunk_heartbeat_ack, ++ [SCTP_CHUNK_TYPE_ABORT] = &sctp_chunk_abort, ++ [SCTP_CHUNK_TYPE_SHUTDOWN] = &sctp_chunk_shutdown, ++ [SCTP_CHUNK_TYPE_SHUTDOWN_ACK] = &sctp_chunk_shutdown_ack, ++ [SCTP_CHUNK_TYPE_ERROR] = &sctp_chunk_error, ++ [SCTP_CHUNK_TYPE_COOKIE_ECHO] = &sctp_chunk_cookie_echo, ++ [SCTP_CHUNK_TYPE_COOKIE_ACK] = &sctp_chunk_cookie_ack, ++ [SCTP_CHUNK_TYPE_ECNE] = &sctp_chunk_ecne, ++ [SCTP_CHUNK_TYPE_CWR] = &sctp_chunk_cwr, ++ [SCTP_CHUNK_TYPE_SHUTDOWN_COMPLETE] = &sctp_chunk_shutdown_complete, ++ [SCTP_CHUNK_TYPE_ASCONF_ACK] = &sctp_chunk_asconf_ack, ++ [SCTP_CHUNK_TYPE_FORWARD_TSN] = &sctp_chunk_forward_tsn, ++ [SCTP_CHUNK_TYPE_ASCONF] = &sctp_chunk_asconf, ++}; ++ ++const struct exthdr_desc *sctp_chunk_protocol_find(const char *name) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < array_size(sctp_chunk_protocols); i++) { ++ if (sctp_chunk_protocols[i] && ++ !strcmp(sctp_chunk_protocols[i]->name, name)) ++ return sctp_chunk_protocols[i]; ++ } ++ return NULL; ++} ++ ++struct expr *sctp_chunk_expr_alloc(const struct location *loc, ++ unsigned int type, unsigned int field) ++{ ++ const struct proto_hdr_template *tmpl; ++ const struct exthdr_desc *desc = NULL; ++ struct expr *expr; ++ ++ if (type < array_size(sctp_chunk_protocols)) ++ desc = sctp_chunk_protocols[type]; ++ ++ if (!desc) ++ return NULL; ++ ++ tmpl = &desc->templates[field]; ++ if (!tmpl) ++ return NULL; ++ ++ expr = expr_alloc(loc, EXPR_EXTHDR, tmpl->dtype, ++ BYTEORDER_BIG_ENDIAN, tmpl->len); ++ expr->exthdr.desc = desc; ++ expr->exthdr.tmpl = tmpl; ++ expr->exthdr.op = NFT_EXTHDR_OP_SCTP; ++ expr->exthdr.raw_type = desc->type; ++ expr->exthdr.offset = tmpl->offset; ++ ++ return expr; ++} ++ ++void sctp_chunk_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; ++ ++ assert(expr->etype == EXPR_EXTHDR); ++ ++ expr->len = len; ++ expr->exthdr.flags = flags; ++ expr->exthdr.offset = off; ++ expr->exthdr.op = NFT_EXTHDR_OP_SCTP; ++ ++ if (flags & NFT_EXTHDR_F_PRESENT) ++ datatype_set(expr, &boolean_type); ++ else ++ datatype_set(expr, &integer_type); ++ ++ if (type >= array_size(sctp_chunk_protocols)) ++ return; ++ ++ expr->exthdr.desc = sctp_chunk_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]; ++ if (tmpl->offset != off || tmpl->len != len) ++ continue; ++ ++ if ((flags & NFT_EXTHDR_F_PRESENT) == 0) ++ datatype_set(expr, tmpl->dtype); ++ ++ expr->exthdr.tmpl = tmpl; ++ break; ++ } ++} +diff --git a/tests/py/inet/sctp.t b/tests/py/inet/sctp.t +index 5188b57e65085..3d1c2fd6cd2f7 100644 +--- a/tests/py/inet/sctp.t ++++ b/tests/py/inet/sctp.t +@@ -41,3 +41,40 @@ sctp vtag {33, 55, 67, 88};ok + sctp vtag != {33, 55, 67, 88};ok + sctp vtag { 33-55};ok + sctp vtag != { 33-55};ok ++ ++# assert all chunk types are recognized ++sctp chunk data exists;ok ++sctp chunk init exists;ok ++sctp chunk init-ack exists;ok ++sctp chunk sack exists;ok ++sctp chunk heartbeat exists;ok ++sctp chunk heartbeat-ack exists;ok ++sctp chunk abort exists;ok ++sctp chunk shutdown exists;ok ++sctp chunk shutdown-ack exists;ok ++sctp chunk error exists;ok ++sctp chunk cookie-echo exists;ok ++sctp chunk cookie-ack exists;ok ++sctp chunk ecne exists;ok ++sctp chunk cwr exists;ok ++sctp chunk shutdown-complete exists;ok ++sctp chunk asconf-ack exists;ok ++sctp chunk forward-tsn exists;ok ++sctp chunk asconf exists;ok ++ ++# test common header fields in random chunk types ++sctp chunk data type 0;ok ++sctp chunk init flags 23;ok ++sctp chunk init-ack length 42;ok ++ ++# test one custom field in every applicable chunk type ++sctp chunk data stream 1337;ok ++sctp chunk init initial-tsn 5;ok ++sctp chunk init-ack num-outbound-streams 3;ok ++sctp chunk sack a-rwnd 1;ok ++sctp chunk shutdown cum-tsn-ack 65535;ok ++sctp chunk ecne lowest-tsn 5;ok ++sctp chunk cwr lowest-tsn 8;ok ++sctp chunk asconf-ack seqno 12345;ok ++sctp chunk forward-tsn new-cum-tsn 31337;ok ++sctp chunk asconf seqno 12345;ok +diff --git a/tests/py/inet/sctp.t.json b/tests/py/inet/sctp.t.json +index 2684b0349a71b..8135686230129 100644 +--- a/tests/py/inet/sctp.t.json ++++ b/tests/py/inet/sctp.t.json +@@ -608,3 +608,481 @@ + } + ] + ++# sctp chunk data exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "data" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk init exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "init" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk init-ack exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "init-ack" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk sack exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "sack" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk heartbeat exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "heartbeat" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk heartbeat-ack exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "heartbeat-ack" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk abort exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "abort" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk shutdown exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "shutdown" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk shutdown-ack exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "shutdown-ack" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk error exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "error" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk cookie-echo exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "cookie-echo" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk cookie-ack exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "cookie-ack" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk ecne exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "ecne" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk cwr exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "cwr" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk shutdown-complete exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "shutdown-complete" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk asconf-ack exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "asconf-ack" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk forward-tsn exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "forward-tsn" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk asconf exists ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "name": "asconf" ++ } ++ }, ++ "op": "==", ++ "right": true ++ } ++ } ++] ++ ++# sctp chunk data type 0 ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "field": "type", ++ "name": "data" ++ } ++ }, ++ "op": "==", ++ "right": 0 ++ } ++ } ++] ++ ++# sctp chunk init flags 23 ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "field": "flags", ++ "name": "init" ++ } ++ }, ++ "op": "==", ++ "right": 23 ++ } ++ } ++] ++ ++# sctp chunk init-ack length 42 ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "field": "length", ++ "name": "init-ack" ++ } ++ }, ++ "op": "==", ++ "right": 42 ++ } ++ } ++] ++ ++# sctp chunk data stream 1337 ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "field": "stream", ++ "name": "data" ++ } ++ }, ++ "op": "==", ++ "right": 1337 ++ } ++ } ++] ++ ++# sctp chunk init initial-tsn 5 ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "field": "initial-tsn", ++ "name": "init" ++ } ++ }, ++ "op": "==", ++ "right": 5 ++ } ++ } ++] ++ ++# sctp chunk init-ack num-outbound-streams 3 ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "field": "num-outbound-streams", ++ "name": "init-ack" ++ } ++ }, ++ "op": "==", ++ "right": 3 ++ } ++ } ++] ++ ++# sctp chunk sack a-rwnd 1 ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "field": "a-rwnd", ++ "name": "sack" ++ } ++ }, ++ "op": "==", ++ "right": 1 ++ } ++ } ++] ++ ++# sctp chunk shutdown cum-tsn-ack 65535 ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "field": "cum-tsn-ack", ++ "name": "shutdown" ++ } ++ }, ++ "op": "==", ++ "right": 65535 ++ } ++ } ++] ++ ++# sctp chunk ecne lowest-tsn 5 ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "field": "lowest-tsn", ++ "name": "ecne" ++ } ++ }, ++ "op": "==", ++ "right": 5 ++ } ++ } ++] ++ ++# sctp chunk cwr lowest-tsn 8 ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "field": "lowest-tsn", ++ "name": "cwr" ++ } ++ }, ++ "op": "==", ++ "right": 8 ++ } ++ } ++] ++ ++# sctp chunk asconf-ack seqno 12345 ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "field": "seqno", ++ "name": "asconf-ack" ++ } ++ }, ++ "op": "==", ++ "right": 12345 ++ } ++ } ++] ++ ++# sctp chunk forward-tsn new-cum-tsn 31337 ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "field": "new-cum-tsn", ++ "name": "forward-tsn" ++ } ++ }, ++ "op": "==", ++ "right": 31337 ++ } ++ } ++] ++ ++# sctp chunk asconf seqno 12345 ++[ ++ { ++ "match": { ++ "left": { ++ "sctp chunk": { ++ "field": "seqno", ++ "name": "asconf" ++ } ++ }, ++ "op": "==", ++ "right": 12345 ++ } ++ } ++] ++ +diff --git a/tests/py/inet/sctp.t.payload b/tests/py/inet/sctp.t.payload +index ecfcc7252a066..9c4854cfe71c3 100644 +--- a/tests/py/inet/sctp.t.payload ++++ b/tests/py/inet/sctp.t.payload +@@ -274,3 +274,158 @@ inet test-inet input + [ payload load 4b @ transport header + 4 => reg 1 ] + [ lookup reg 1 set __set%d 0x1 ] + ++# sctp chunk data exists ++ip ++ [ exthdr load 1b @ 0 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk init exists ++ip ++ [ exthdr load 1b @ 1 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk init-ack exists ++ip ++ [ exthdr load 1b @ 2 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk sack exists ++ip ++ [ exthdr load 1b @ 3 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk heartbeat exists ++ip ++ [ exthdr load 1b @ 4 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk heartbeat-ack exists ++ip ++ [ exthdr load 1b @ 5 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk abort exists ++ip ++ [ exthdr load 1b @ 6 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk shutdown exists ++ip ++ [ exthdr load 1b @ 7 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk shutdown-ack exists ++ip ++ [ exthdr load 1b @ 8 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk error exists ++ip ++ [ exthdr load 1b @ 9 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk cookie-echo exists ++ip ++ [ exthdr load 1b @ 10 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk cookie-ack exists ++ip ++ [ exthdr load 1b @ 11 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk ecne exists ++ip ++ [ exthdr load 1b @ 12 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk cwr exists ++ip ++ [ exthdr load 1b @ 13 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk shutdown-complete exists ++ip ++ [ exthdr load 1b @ 14 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk asconf-ack exists ++ip ++ [ exthdr load 1b @ 128 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk forward-tsn exists ++ip ++ [ exthdr load 1b @ 192 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk asconf exists ++ip ++ [ exthdr load 1b @ 193 + 0 present => reg 1 ] ++ [ cmp eq reg 1 0x00000001 ] ++ ++# sctp chunk data type 0 ++ip ++ [ exthdr load 1b @ 0 + 0 => reg 1 ] ++ [ cmp eq reg 1 0x00000000 ] ++ ++# sctp chunk init flags 23 ++ip ++ [ exthdr load 1b @ 1 + 1 => reg 1 ] ++ [ cmp eq reg 1 0x00000017 ] ++ ++# sctp chunk init-ack length 42 ++ip ++ [ exthdr load 2b @ 2 + 2 => reg 1 ] ++ [ cmp eq reg 1 0x00002a00 ] ++ ++# sctp chunk data stream 1337 ++ip ++ [ exthdr load 2b @ 0 + 8 => reg 1 ] ++ [ cmp eq reg 1 0x00003905 ] ++ ++# sctp chunk init initial-tsn 5 ++ip ++ [ exthdr load 4b @ 1 + 16 => reg 1 ] ++ [ cmp eq reg 1 0x05000000 ] ++ ++# sctp chunk init-ack num-outbound-streams 3 ++ip ++ [ exthdr load 2b @ 2 + 12 => reg 1 ] ++ [ cmp eq reg 1 0x00000300 ] ++ ++# sctp chunk sack a-rwnd 1 ++ip ++ [ exthdr load 4b @ 3 + 8 => reg 1 ] ++ [ cmp eq reg 1 0x01000000 ] ++ ++# sctp chunk shutdown cum-tsn-ack 65535 ++ip ++ [ exthdr load 4b @ 7 + 4 => reg 1 ] ++ [ cmp eq reg 1 0xffff0000 ] ++ ++# sctp chunk ecne lowest-tsn 5 ++ip ++ [ exthdr load 4b @ 12 + 4 => reg 1 ] ++ [ cmp eq reg 1 0x05000000 ] ++ ++# sctp chunk cwr lowest-tsn 8 ++ip ++ [ exthdr load 4b @ 13 + 4 => reg 1 ] ++ [ cmp eq reg 1 0x08000000 ] ++ ++# sctp chunk asconf-ack seqno 12345 ++ip ++ [ exthdr load 4b @ 128 + 4 => reg 1 ] ++ [ cmp eq reg 1 0x39300000 ] ++ ++# sctp chunk forward-tsn new-cum-tsn 31337 ++ip ++ [ exthdr load 4b @ 192 + 4 => reg 1 ] ++ [ cmp eq reg 1 0x697a0000 ] ++ ++# sctp chunk asconf seqno 12345 ++ip ++ [ exthdr load 4b @ 193 + 4 => reg 1 ] ++ [ cmp eq reg 1 0x39300000 ] ++ +-- +2.33.0 + diff --git a/SOURCES/0025-include-missing-sctp_chunk.h-in-Makefile.am.patch b/SOURCES/0025-include-missing-sctp_chunk.h-in-Makefile.am.patch new file mode 100644 index 0000000..7279044 --- /dev/null +++ b/SOURCES/0025-include-missing-sctp_chunk.h-in-Makefile.am.patch @@ -0,0 +1,29 @@ +From fe19063ce09d40ea94bf57c4af8b6c121aaf89e8 Mon Sep 17 00:00:00 2001 +From: Pablo Neira Ayuso +Date: Tue, 25 May 2021 14:04:36 +0200 +Subject: [PATCH] include: missing sctp_chunk.h in Makefile.am + +Fix make distcheck. + +Fixes: 0e3871cfd9a1 ("exthdr: Implement SCTP Chunk matching") +Signed-off-by: Pablo Neira Ayuso +(cherry picked from commit 117ceb4f527119a6d44bf5e23f2ff7a8d116658a) +--- + include/Makefile.am | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/include/Makefile.am b/include/Makefile.am +index 42f24f35ce7a5..4cd907380ebaa 100644 +--- a/include/Makefile.am ++++ b/include/Makefile.am +@@ -31,6 +31,7 @@ noinst_HEADERS = cli.h \ + osf.h \ + parser.h \ + proto.h \ ++ sctp_chunk.h \ + socket.h \ + rule.h \ + rt.h \ +-- +2.33.0 + diff --git a/SOURCES/0026-evaluate-fix-inet-nat-with-no-layer-3-info.patch b/SOURCES/0026-evaluate-fix-inet-nat-with-no-layer-3-info.patch new file mode 100644 index 0000000..271c271 --- /dev/null +++ b/SOURCES/0026-evaluate-fix-inet-nat-with-no-layer-3-info.patch @@ -0,0 +1,41 @@ +From 0c371aeab906b6e65c4c86174cbe2fbca02891d1 Mon Sep 17 00:00:00 2001 +From: Pablo Neira Ayuso +Date: Tue, 20 Jul 2021 18:59:44 +0200 +Subject: [PATCH] evaluate: fix inet nat with no layer 3 info + +nft currently reports: + + Error: Could not process rule: Protocol error + add rule inet x y meta l4proto tcp dnat to :80 + ^^^^ + +default to NFPROTO_INET family, otherwise kernel bails out EPROTO when +trying to load the conntrack helper. + +Closes: https://bugzilla.netfilter.org/show_bug.cgi?id=1428 +Signed-off-by: Pablo Neira Ayuso +(cherry picked from commit 9a36033ce50638a403d1421935cdd1287ee5de6b) +--- + src/evaluate.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index bba685af720ed..73d6fd0e89bc2 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -2896,9 +2896,10 @@ static int nat_evaluate_family(struct eval_ctx *ctx, struct stmt *stmt) + stmt->nat.family = ctx->pctx.family; + return 0; + case NFPROTO_INET: +- if (!stmt->nat.addr) ++ if (!stmt->nat.addr) { ++ stmt->nat.family = NFPROTO_INET; + return 0; +- ++ } + if (stmt->nat.family != NFPROTO_UNSPEC) + return 0; + +-- +2.34.1 + diff --git a/SOURCES/0027-tests-py-add-dnat-to-port-without-defining-destinati.patch b/SOURCES/0027-tests-py-add-dnat-to-port-without-defining-destinati.patch new file mode 100644 index 0000000..5659ece --- /dev/null +++ b/SOURCES/0027-tests-py-add-dnat-to-port-without-defining-destinati.patch @@ -0,0 +1,75 @@ +From 00d3745306aa87eeb2466dbb5e6958225de3354f Mon Sep 17 00:00:00 2001 +From: Pablo Neira Ayuso +Date: Thu, 22 Jul 2021 17:43:56 +0200 +Subject: [PATCH] tests: py: add dnat to port without defining destination + address + +Add a test to cover dnat to port without destination address. + +Closes: https://bugzilla.netfilter.org/show_bug.cgi?id=1428 +Signed-off-by: Pablo Neira Ayuso +(cherry picked from commit 0f27e258b37a592233d6ad5381cd1fae65e57514) +--- + tests/py/inet/dnat.t | 1 + + tests/py/inet/dnat.t.json | 20 ++++++++++++++++++++ + tests/py/inet/dnat.t.payload | 7 +++++++ + 3 files changed, 28 insertions(+) + +diff --git a/tests/py/inet/dnat.t b/tests/py/inet/dnat.t +index b460af3925570..e4e169f2bc3ec 100644 +--- a/tests/py/inet/dnat.t ++++ b/tests/py/inet/dnat.t +@@ -6,6 +6,7 @@ iifname "foo" tcp dport 80 redirect to :8080;ok + + iifname "eth0" tcp dport 443 dnat ip to 192.168.3.2;ok + iifname "eth0" tcp dport 443 dnat ip6 to [dead::beef]:4443;ok ++meta l4proto tcp dnat to :80;ok;meta l4proto 6 dnat to :80 + + dnat ip to ct mark map { 0x00000014 : 1.2.3.4};ok + dnat ip to ct mark . ip daddr map { 0x00000014 . 1.1.1.1 : 1.2.3.4};ok +diff --git a/tests/py/inet/dnat.t.json b/tests/py/inet/dnat.t.json +index 1b8aba6297d36..c341a0455fea1 100644 +--- a/tests/py/inet/dnat.t.json ++++ b/tests/py/inet/dnat.t.json +@@ -219,3 +219,23 @@ + } + ] + ++# meta l4proto tcp dnat to :80 ++[ ++ { ++ "match": { ++ "left": { ++ "meta": { ++ "key": "l4proto" ++ } ++ }, ++ "op": "==", ++ "right": 6 ++ } ++ }, ++ { ++ "dnat": { ++ "port": 80 ++ } ++ } ++] ++ +diff --git a/tests/py/inet/dnat.t.payload b/tests/py/inet/dnat.t.payload +index a741b9cbdb8d7..be5baf8fd4b47 100644 +--- a/tests/py/inet/dnat.t.payload ++++ b/tests/py/inet/dnat.t.payload +@@ -77,3 +77,10 @@ inet + [ immediate reg 2 0x00005000 ] + [ nat dnat ip addr_min reg 1 addr_max reg 0 proto_min reg 2 proto_max reg 0 flags 0x2 ] + ++# meta l4proto tcp dnat to :80 ++inet ++ [ meta load l4proto => reg 1 ] ++ [ cmp eq reg 1 0x00000006 ] ++ [ immediate reg 1 0x00005000 ] ++ [ nat dnat inet proto_min reg 1 flags 0x2 ] ++ +-- +2.34.1 + diff --git a/SOURCES/0028-evaluate-pick-data-element-byte-order-not-dtype-one.patch b/SOURCES/0028-evaluate-pick-data-element-byte-order-not-dtype-one.patch new file mode 100644 index 0000000..c3ce4f0 --- /dev/null +++ b/SOURCES/0028-evaluate-pick-data-element-byte-order-not-dtype-one.patch @@ -0,0 +1,36 @@ +From d5525024223f324c71edb9135f1938745d45acee Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 3 Feb 2021 17:57:06 +0100 +Subject: [PATCH] evaluate: pick data element byte order, not dtype one + +Some expressions have integer base type, not a specific one, e.g. 'ct zone'. +In that case nft used the wrong byte order. + +Without this, nft adds +elements = { "eth0" : 256, "eth1" : 512, "veth4" : 256 } +instead of 1, 2, 3. + +This is not a 'display bug', the added elements have wrong byte order. + +Signed-off-by: Florian Westphal +(cherry picked from commit 84b1d078e86dea25c93e15c3e5a3160bbf77e4e7) +--- + src/evaluate.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/evaluate.c b/src/evaluate.c +index 73d6fd0e89bc2..0543190fe777a 100644 +--- a/src/evaluate.c ++++ b/src/evaluate.c +@@ -1583,7 +1583,7 @@ static int expr_evaluate_mapping(struct eval_ctx *ctx, struct expr **expr) + else + datalen = set->data->len; + +- expr_set_context(&ctx->ectx, set->data->dtype, datalen); ++ __expr_set_context(&ctx->ectx, set->data->dtype, set->data->byteorder, datalen, 0); + } else { + assert((set->flags & NFT_SET_MAP) == 0); + } +-- +2.34.1 + diff --git a/SPECS/nftables.spec b/SPECS/nftables.spec index 672c934..75b771a 100644 --- a/SPECS/nftables.spec +++ b/SPECS/nftables.spec @@ -1,6 +1,6 @@ Name: nftables Version: 0.9.8 -Release: 10%{?dist} +Release: 12%{?dist} # Upstream released a 0.100 version, then 0.4. Need Epoch to get back on track. Epoch: 1 Summary: Netfilter Tables userspace utillites @@ -34,11 +34,18 @@ Patch17: 0017-tests-cover-baecd1cf2685-segtree-Fix-segfault-when-r.patch Patch18: 0018-doc-nft.8-Extend-monitor-description-by-trace.patch Patch19: 0019-tests-shell-NFT-needs-to-be-invoked-unquoted.patch Patch20: 0020-tests-shell-better-parameters-for-the-interval-stack.patch +Patch21: 0021-json-Simplify-non-tcpopt-exthdr-printing-a-bit.patch +Patch22: 0022-scanner-introduce-start-condition-stack.patch +Patch23: 0023-scanner-sctp-Move-to-own-scope.patch +Patch24: 0024-exthdr-Implement-SCTP-Chunk-matching.patch +Patch25: 0025-include-missing-sctp_chunk.h-in-Makefile.am.patch +Patch26: 0026-evaluate-fix-inet-nat-with-no-layer-3-info.patch +Patch27: 0027-tests-py-add-dnat-to-port-without-defining-destinati.patch +Patch28: 0028-evaluate-pick-data-element-byte-order-not-dtype-one.patch -#BuildRequires: autogen -#BuildRequires: autoconf -#BuildRequires: automake -#BuildRequires: libtool +BuildRequires: autoconf +BuildRequires: automake +BuildRequires: libtool BuildRequires: make BuildRequires: gcc BuildRequires: flex @@ -76,7 +83,8 @@ The nftables python module provides an interface to libnftables via ctypes. %autosetup -p1 %build -#./autogen.sh +autoreconf -fi +rm -Rf autom4te*.cache config.h.in~ %configure --disable-silent-rules --with-xtables --with-json \ --enable-python --with-python-bin=%{__python3} %make_build @@ -143,6 +151,18 @@ sed -i -e 's/\(sofile=\)".*"/\1"'$sofile'"/' \ %{python3_sitelib}/nftables/ %changelog +* Fri Jan 14 2022 Phil Sutter - 1:0.9.8-12 +- evaluate: pick data element byte order, not dtype one + +* Wed Dec 08 2021 Phil Sutter - 1:0.9.8-11 +- tests: py: add dnat to port without defining destination address +- evaluate: fix inet nat with no layer 3 info +- include: missing sctp_chunk.h in Makefile.am +- exthdr: Implement SCTP Chunk matching +- scanner: sctp: Move to own scope +- scanner: introduce start condition stack +- json: Simplify non-tcpopt exthdr printing a bit + * Wed Dec 08 2021 Phil Sutter - 1:0.9.8-10 - tests: shell: better parameters for the interval stack overflow test - tests: shell: $NFT needs to be invoked unquoted