Blame SOURCES/0059-exthdr-Implement-SCTP-Chunk-matching.patch

0341a5
From 5a8d6197929e30520bb3839c9165d89930888daf Mon Sep 17 00:00:00 2001
0341a5
From: Phil Sutter <psutter@redhat.com>
0341a5
Date: Tue, 13 Jul 2021 13:54:42 +0200
0341a5
Subject: [PATCH] exthdr: Implement SCTP Chunk matching
0341a5
0341a5
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1979334
0341a5
Upstream Status: nftables commit 0e3871cfd9a1e
0341a5
Conflicts:
0341a5
 * Context changes due to missing other scopes.
0341a5
 * Context change due to missing commit 6e6ef00028f1c
0341a5
   ("exthdr: remove tcp dependency for tcp option matching").
0341a5
0341a5
commit 0e3871cfd9a1e32a4ac041ce87a8057b11a89924
0341a5
Author: Phil Sutter <phil@nwl.cc>
0341a5
Date:   Tue May 4 13:41:38 2021 +0200
0341a5
0341a5
    exthdr: Implement SCTP Chunk matching
0341a5
0341a5
    Extend exthdr expression to support scanning through SCTP packet chunks
0341a5
    and matching on fixed fields' values.
0341a5
0341a5
    Signed-off-by: Phil Sutter <phil@nwl.cc>
0341a5
    Acked-by: Florian Westphal <fw@strlen.de>
0341a5
---
0341a5
 doc/libnftables-json.adoc           |  13 +
0341a5
 doc/payload-expression.txt          |  53 +++
0341a5
 include/linux/netfilter/nf_tables.h |   2 +
0341a5
 include/parser.h                    |   1 +
0341a5
 include/sctp_chunk.h                |  87 +++++
0341a5
 src/Makefile.am                     |   1 +
0341a5
 src/evaluate.c                      |   2 +
0341a5
 src/exthdr.c                        |   8 +
0341a5
 src/json.c                          |   2 +
0341a5
 src/parser_bison.y                  | 148 ++++++++-
0341a5
 src/parser_json.c                   |  49 +++
0341a5
 src/scanner.l                       |  38 +++
0341a5
 src/sctp_chunk.c                    | 261 +++++++++++++++
0341a5
 tests/py/inet/sctp.t                |  37 +++
0341a5
 tests/py/inet/sctp.t.json           | 478 ++++++++++++++++++++++++++++
0341a5
 tests/py/inet/sctp.t.payload        | 155 +++++++++
0341a5
 16 files changed, 1333 insertions(+), 2 deletions(-)
0341a5
 create mode 100644 include/sctp_chunk.h
0341a5
 create mode 100644 src/sctp_chunk.c
0341a5
0341a5
diff --git a/doc/libnftables-json.adoc b/doc/libnftables-json.adoc
0341a5
index 858abbf..fba4cb0 100644
0341a5
--- a/doc/libnftables-json.adoc
0341a5
+++ b/doc/libnftables-json.adoc
0341a5
@@ -1200,6 +1200,19 @@ Create a reference to a field (*field*) of a TCP option header (*name*).
0341a5
 If the *field* property is not given, the expression is to be used as a TCP option
0341a5
 existence check in a *match* statement with a boolean on the right hand side.
0341a5
 
0341a5
+=== SCTP CHUNK
0341a5
+[verse]
0341a5
+*{ "sctp chunk": {
0341a5
+	"name":* 'STRING'*,
0341a5
+	"field":* 'STRING'
0341a5
+*}}*
0341a5
+
0341a5
+Create a reference to a field (*field*) of an SCTP chunk (*name*).
0341a5
+
0341a5
+If the *field* property is not given, the expression is to be used as an SCTP
0341a5
+chunk existence check in a *match* statement with a boolean on the right hand
0341a5
+side.
0341a5
+
0341a5
 === META
0341a5
 [verse]
0341a5
 ____
0341a5
diff --git a/doc/payload-expression.txt b/doc/payload-expression.txt
0341a5
index b6d2a28..bd03ca2 100644
0341a5
--- a/doc/payload-expression.txt
0341a5
+++ b/doc/payload-expression.txt
0341a5
@@ -369,7 +369,33 @@ integer (16 bit)
0341a5
 SCTP HEADER EXPRESSION
0341a5
 ~~~~~~~~~~~~~~~~~~~~~~~
0341a5
 [verse]
0341a5
+____
0341a5
 *sctp* {*sport* | *dport* | *vtag* | *checksum*}
0341a5
+*sctp chunk* 'CHUNK' [ 'FIELD' ]
0341a5
+
0341a5
+'CHUNK' := *data* | *init* | *init-ack* | *sack* | *heartbeat* |
0341a5
+	   *heartbeat-ack* | *abort* | *shutdown* | *shutdown-ack* | *error* |
0341a5
+	   *cookie-echo* | *cookie-ack* | *ecne* | *cwr* | *shutdown-complete*
0341a5
+	   | *asconf-ack* | *forward-tsn* | *asconf*
0341a5
+
0341a5
+'FIELD' := 'COMMON_FIELD' | 'DATA_FIELD' | 'INIT_FIELD' | 'INIT_ACK_FIELD' |
0341a5
+	   'SACK_FIELD' | 'SHUTDOWN_FIELD' | 'ECNE_FIELD' | 'CWR_FIELD' |
0341a5
+	   'ASCONF_ACK_FIELD' | 'FORWARD_TSN_FIELD' | 'ASCONF_FIELD'
0341a5
+
0341a5
+'COMMON_FIELD' := *type* | *flags* | *length*
0341a5
+'DATA_FIELD' := *tsn* | *stream* | *ssn* | *ppid*
0341a5
+'INIT_FIELD' := *init-tag* | *a-rwnd* | *num-outbound-streams* |
0341a5
+		*num-inbound-streams* | *initial-tsn*
0341a5
+'INIT_ACK_FIELD' := 'INIT_FIELD'
0341a5
+'SACK_FIELD' := *cum-tsn-ack* | *a-rwnd* | *num-gap-ack-blocks* |
0341a5
+		*num-dup-tsns*
0341a5
+'SHUTDOWN_FIELD' := *cum-tsn-ack*
0341a5
+'ECNE_FIELD' := *lowest-tsn*
0341a5
+'CWR_FIELD' := *lowest-tsn*
0341a5
+'ASCONF_ACK_FIELD' := *seqno*
0341a5
+'FORWARD_TSN_FIELD' := *new-cum-tsn*
0341a5
+'ASCONF_FIELD' := *seqno*
0341a5
+____
0341a5
 
0341a5
 .SCTP header expression
0341a5
 [options="header"]
0341a5
@@ -387,8 +413,35 @@ integer (32 bit)
0341a5
 |checksum|
0341a5
 Checksum|
0341a5
 integer (32 bit)
0341a5
+|chunk|
0341a5
+Search chunk in packet|
0341a5
+without 'FIELD', boolean indicating existence
0341a5
 |================
0341a5
 
0341a5
+.SCTP chunk fields
0341a5
+[options="header"]
0341a5
+|==================
0341a5
+|Name| Width in bits | Chunk | Notes
0341a5
+|type| 8 | all | not useful, defined by chunk type
0341a5
+|flags| 8 | all | semantics defined on per-chunk basis
0341a5
+|length| 16 | all | length of this chunk in bytes excluding padding
0341a5
+|tsn| 32 | data | transmission sequence number
0341a5
+|stream| 16 | data | stream identifier
0341a5
+|ssn| 16 | data | stream sequence number
0341a5
+|ppid| 32 | data | payload protocol identifier
0341a5
+|init-tag| 32 | init, init-ack | initiate tag
0341a5
+|a-rwnd| 32 | init, init-ack, sack | advertised receiver window credit
0341a5
+|num-outbound-streams| 16 | init, init-ack | number of outbound streams
0341a5
+|num-inbound-streams| 16 | init, init-ack | number of inbound streams
0341a5
+|initial-tsn| 32 | init, init-ack | initial transmit sequence number
0341a5
+|cum-tsn-ack| 32 | sack, shutdown | cumulative transmission sequence number acknowledged
0341a5
+|num-gap-ack-blocks| 16 | sack | number of Gap Ack Blocks included
0341a5
+|num-dup-tsns| 16 | sack | number of duplicate transmission sequence numbers received
0341a5
+|lowest-tsn| 32 | ecne, cwr | lowest transmission sequence number
0341a5
+|seqno| 32 | asconf-ack, asconf | sequence number
0341a5
+|new-cum-tsn| 32 | forward-tsn | new cumulative transmission sequence number
0341a5
+|==================
0341a5
+
0341a5
 DCCP HEADER EXPRESSION
0341a5
 ~~~~~~~~~~~~~~~~~~~~~~
0341a5
 [verse]
0341a5
diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h
0341a5
index 1328b8e..960a5b4 100644
0341a5
--- a/include/linux/netfilter/nf_tables.h
0341a5
+++ b/include/linux/netfilter/nf_tables.h
0341a5
@@ -755,11 +755,13 @@ enum nft_exthdr_flags {
0341a5
  * @NFT_EXTHDR_OP_IPV6: match against ipv6 extension headers
0341a5
  * @NFT_EXTHDR_OP_TCP: match against tcp options
0341a5
  * @NFT_EXTHDR_OP_IPV4: match against ipv4 options
0341a5
+ * @NFT_EXTHDR_OP_SCTP: match against sctp chunks
0341a5
  */
0341a5
 enum nft_exthdr_op {
0341a5
 	NFT_EXTHDR_OP_IPV6,
0341a5
 	NFT_EXTHDR_OP_TCPOPT,
0341a5
 	NFT_EXTHDR_OP_IPV4,
0341a5
+	NFT_EXTHDR_OP_SCTP,
0341a5
 	__NFT_EXTHDR_OP_MAX
0341a5
 };
0341a5
 #define NFT_EXTHDR_OP_MAX	(__NFT_EXTHDR_OP_MAX - 1)
0341a5
diff --git a/include/parser.h b/include/parser.h
0341a5
index 2e6ef4d..99bed3c 100644
0341a5
--- a/include/parser.h
0341a5
+++ b/include/parser.h
0341a5
@@ -33,6 +33,7 @@ enum startcond_type {
0341a5
 	PARSER_SC_SCTP,
0341a5
 	PARSER_SC_EXPR_HASH,
0341a5
 	PARSER_SC_EXPR_NUMGEN,
0341a5
+	PARSER_SC_EXPR_SCTP_CHUNK,
0341a5
 };
0341a5
 
0341a5
 struct mnl_socket;
0341a5
diff --git a/include/sctp_chunk.h b/include/sctp_chunk.h
0341a5
new file mode 100644
0341a5
index 0000000..3819200
0341a5
--- /dev/null
0341a5
+++ b/include/sctp_chunk.h
0341a5
@@ -0,0 +1,87 @@
0341a5
+/*
0341a5
+ * Copyright Red Hat
0341a5
+ *
0341a5
+ * This program is free software; you can redistribute it and/or modify
0341a5
+ * it under the terms of the GNU General Public License version 2 (or any
0341a5
+ * later) as published by the Free Software Foundation.
0341a5
+ */
0341a5
+
0341a5
+#ifndef NFTABLES_SCTP_CHUNK_H
0341a5
+#define NFTABLES_SCTP_CHUNK_H
0341a5
+
0341a5
+/* SCTP chunk types used on wire */
0341a5
+enum sctp_hdr_chunk_types {
0341a5
+	SCTP_CHUNK_TYPE_DATA			= 0,
0341a5
+	SCTP_CHUNK_TYPE_INIT			= 1,
0341a5
+	SCTP_CHUNK_TYPE_INIT_ACK		= 2,
0341a5
+	SCTP_CHUNK_TYPE_SACK			= 3,
0341a5
+	SCTP_CHUNK_TYPE_HEARTBEAT		= 4,
0341a5
+	SCTP_CHUNK_TYPE_HEARTBEAT_ACK		= 5,
0341a5
+	SCTP_CHUNK_TYPE_ABORT			= 6,
0341a5
+	SCTP_CHUNK_TYPE_SHUTDOWN		= 7,
0341a5
+	SCTP_CHUNK_TYPE_SHUTDOWN_ACK		= 8,
0341a5
+	SCTP_CHUNK_TYPE_ERROR			= 9,
0341a5
+	SCTP_CHUNK_TYPE_COOKIE_ECHO		= 10,
0341a5
+	SCTP_CHUNK_TYPE_COOKIE_ACK		= 11,
0341a5
+	SCTP_CHUNK_TYPE_ECNE			= 12,
0341a5
+	SCTP_CHUNK_TYPE_CWR			= 13,
0341a5
+	SCTP_CHUNK_TYPE_SHUTDOWN_COMPLETE	= 14,
0341a5
+	SCTP_CHUNK_TYPE_ASCONF_ACK		= 128,
0341a5
+	SCTP_CHUNK_TYPE_FORWARD_TSN		= 192,
0341a5
+	SCTP_CHUNK_TYPE_ASCONF			= 193,
0341a5
+};
0341a5
+
0341a5
+enum sctp_hdr_chunk_common_fields {
0341a5
+	SCTP_CHUNK_COMMON_TYPE,
0341a5
+	SCTP_CHUNK_COMMON_FLAGS,
0341a5
+	SCTP_CHUNK_COMMON_LENGTH,
0341a5
+	__SCTP_CHUNK_COMMON_MAX,
0341a5
+};
0341a5
+
0341a5
+#define SCTP_CHUNK_START_INDEX	__SCTP_CHUNK_COMMON_MAX
0341a5
+
0341a5
+enum sctp_hdr_chunk_data_fields {
0341a5
+	SCTP_CHUNK_DATA_TSN = SCTP_CHUNK_START_INDEX,
0341a5
+	SCTP_CHUNK_DATA_STREAM,
0341a5
+	SCTP_CHUNK_DATA_SSN,
0341a5
+	SCTP_CHUNK_DATA_PPID,
0341a5
+};
0341a5
+
0341a5
+enum sctp_hdr_chunk_init_fields {
0341a5
+	SCTP_CHUNK_INIT_TAG = SCTP_CHUNK_START_INDEX,
0341a5
+	SCTP_CHUNK_INIT_RWND,
0341a5
+	SCTP_CHUNK_INIT_OSTREAMS,
0341a5
+	SCTP_CHUNK_INIT_ISTREAMS,
0341a5
+	SCTP_CHUNK_INIT_TSN,
0341a5
+};
0341a5
+
0341a5
+enum sctp_hdr_chunk_sack_fields {
0341a5
+	SCTP_CHUNK_SACK_CTSN_ACK = SCTP_CHUNK_START_INDEX,
0341a5
+	SCTP_CHUNK_SACK_RWND,
0341a5
+	SCTP_CHUNK_SACK_GACK_BLOCKS,
0341a5
+	SCTP_CHUNK_SACK_DUP_TSNS,
0341a5
+};
0341a5
+
0341a5
+enum sctp_hdr_chunk_shutdown_fields {
0341a5
+	SCTP_CHUNK_SHUTDOWN_CTSN_ACK = SCTP_CHUNK_START_INDEX,
0341a5
+};
0341a5
+
0341a5
+enum sctp_hdr_chunk_ecne_cwr_fields {
0341a5
+	SCTP_CHUNK_ECNE_CWR_MIN_TSN = SCTP_CHUNK_START_INDEX,
0341a5
+};
0341a5
+
0341a5
+enum sctp_hdr_chunk_asconf_fields {
0341a5
+	SCTP_CHUNK_ASCONF_SEQNO = SCTP_CHUNK_START_INDEX,
0341a5
+};
0341a5
+
0341a5
+enum sctp_hdr_chunk_fwd_tsn_fields {
0341a5
+	SCTP_CHUNK_FORWARD_TSN_NCTSN = SCTP_CHUNK_START_INDEX,
0341a5
+};
0341a5
+
0341a5
+struct expr *sctp_chunk_expr_alloc(const struct location *loc,
0341a5
+				   unsigned int type, unsigned int field);
0341a5
+void sctp_chunk_init_raw(struct expr *expr, uint8_t type, unsigned int off,
0341a5
+			 unsigned int len, uint32_t flags);
0341a5
+const struct exthdr_desc *sctp_chunk_protocol_find(const char *name);
0341a5
+
0341a5
+#endif /* NFTABLES_SCTP_CHUNK_H */
0341a5
diff --git a/src/Makefile.am b/src/Makefile.am
0341a5
index 740c21f..366820b 100644
0341a5
--- a/src/Makefile.am
0341a5
+++ b/src/Makefile.am
0341a5
@@ -64,6 +64,7 @@ libnftables_la_SOURCES =			\
0341a5
 		tcpopt.c			\
0341a5
 		socket.c			\
0341a5
 		print.c				\
0341a5
+		sctp_chunk.c			\
0341a5
 		libnftables.c			\
0341a5
 		libnftables.map
0341a5
 
0341a5
diff --git a/src/evaluate.c b/src/evaluate.c
0341a5
index 99a66c2..00ec20b 100644
0341a5
--- a/src/evaluate.c
0341a5
+++ b/src/evaluate.c
0341a5
@@ -579,6 +579,8 @@ static int expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp)
0341a5
 		dependency = &proto_tcp;
0341a5
 		pb = PROTO_BASE_TRANSPORT_HDR;
0341a5
 		break;
0341a5
+	case NFT_EXTHDR_OP_SCTP:
0341a5
+		return __expr_evaluate_exthdr(ctx, exprp);
0341a5
 	case NFT_EXTHDR_OP_IPV4:
0341a5
 		dependency = &proto_ip;
0341a5
 		break;
0341a5
diff --git a/src/exthdr.c b/src/exthdr.c
0341a5
index 5c75720..f5689e7 100644
0341a5
--- a/src/exthdr.c
0341a5
+++ b/src/exthdr.c
0341a5
@@ -22,6 +22,7 @@
0341a5
 #include <headers.h>
0341a5
 #include <expression.h>
0341a5
 #include <statement.h>
0341a5
+#include <sctp_chunk.h>
0341a5
 
0341a5
 static void exthdr_expr_print(const struct expr *expr, struct output_ctx *octx)
0341a5
 {
0341a5
@@ -55,6 +56,11 @@ static void exthdr_expr_print(const struct expr *expr, struct output_ctx *octx)
0341a5
 		if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT)
0341a5
 			return;
0341a5
 		nft_print(octx, " %s", expr->exthdr.tmpl->token);
0341a5
+	} else if (expr->exthdr.op == NFT_EXTHDR_OP_SCTP) {
0341a5
+		nft_print(octx, "sctp chunk %s", expr->exthdr.desc->name);
0341a5
+		if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT)
0341a5
+			return;
0341a5
+		nft_print(octx, " %s", expr->exthdr.tmpl->token);
0341a5
 	} else {
0341a5
 		if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT)
0341a5
 			nft_print(octx, "exthdr %s", expr->exthdr.desc->name);
0341a5
@@ -197,6 +203,8 @@ void exthdr_init_raw(struct expr *expr, uint8_t type,
0341a5
 		return tcpopt_init_raw(expr, type, offset, len, flags);
0341a5
 	if (op == NFT_EXTHDR_OP_IPV4)
0341a5
 		return ipopt_init_raw(expr, type, offset, len, flags, true);
0341a5
+	if (op == NFT_EXTHDR_OP_SCTP)
0341a5
+		return sctp_chunk_init_raw(expr, type, offset, len, flags);
0341a5
 
0341a5
 	expr->len = len;
0341a5
 	expr->exthdr.flags = flags;
0341a5
diff --git a/src/json.c b/src/json.c
0341a5
index a6d0716..dfc9031 100644
0341a5
--- a/src/json.c
0341a5
+++ b/src/json.c
0341a5
@@ -692,6 +692,8 @@ json_t *exthdr_expr_json(const struct expr *expr, struct output_ctx *octx)
0341a5
 	switch (expr->exthdr.op) {
0341a5
 	case NFT_EXTHDR_OP_IPV4:
0341a5
 		return json_pack("{s:o}", "ip option", root);
0341a5
+	case NFT_EXTHDR_OP_SCTP:
0341a5
+		return json_pack("{s:o}", "sctp chunk", root);
0341a5
 	default:
0341a5
 		return json_pack("{s:o}", "exthdr", root);
0341a5
 	}
0341a5
diff --git a/src/parser_bison.y b/src/parser_bison.y
0341a5
index beb5995..5ab5744 100644
0341a5
--- a/src/parser_bison.y
0341a5
+++ b/src/parser_bison.y
0341a5
@@ -38,6 +38,7 @@
0341a5
 #include <utils.h>
0341a5
 #include <parser.h>
0341a5
 #include <erec.h>
0341a5
+#include <sctp_chunk.h>
0341a5
 
0341a5
 #include "parser_bison.h"
0341a5
 
0341a5
@@ -402,6 +403,40 @@ int nft_lex(void *, void *, void *);
0341a5
 %token DCCP			"dccp"
0341a5
 
0341a5
 %token SCTP			"sctp"
0341a5
+%token CHUNK			"chunk"
0341a5
+%token DATA			"data"
0341a5
+%token INIT			"init"
0341a5
+%token INIT_ACK			"init-ack"
0341a5
+%token HEARTBEAT		"heartbeat"
0341a5
+%token HEARTBEAT_ACK		"heartbeat-ack"
0341a5
+%token ABORT			"abort"
0341a5
+%token SHUTDOWN			"shutdown"
0341a5
+%token SHUTDOWN_ACK		"shutdown-ack"
0341a5
+%token ERROR			"error"
0341a5
+%token COOKIE_ECHO		"cookie-echo"
0341a5
+%token COOKIE_ACK		"cookie-ack"
0341a5
+%token ECNE			"ecne"
0341a5
+%token CWR			"cwr"
0341a5
+%token SHUTDOWN_COMPLETE	"shutdown-complete"
0341a5
+%token ASCONF_ACK		"asconf-ack"
0341a5
+%token FORWARD_TSN		"forward-tsn"
0341a5
+%token ASCONF			"asconf"
0341a5
+%token TSN			"tsn"
0341a5
+%token STREAM			"stream"
0341a5
+%token SSN			"ssn"
0341a5
+%token PPID			"ppid"
0341a5
+%token INIT_TAG			"init-tag"
0341a5
+%token A_RWND			"a-rwnd"
0341a5
+%token NUM_OSTREAMS		"num-outbound-streams"
0341a5
+%token NUM_ISTREAMS		"num-inbound-streams"
0341a5
+%token INIT_TSN			"initial-tsn"
0341a5
+%token CUM_TSN_ACK		"cum-tsn-ack"
0341a5
+%token NUM_GACK_BLOCKS		"num-gap-ack-blocks"
0341a5
+%token NUM_DUP_TSNS		"num-dup-tsns"
0341a5
+%token LOWEST_TSN		"lowest-tsn"
0341a5
+%token SEQNO			"seqno"
0341a5
+%token NEW_CUM_TSN		"new-cum-tsn"
0341a5
+
0341a5
 %token VTAG			"vtag"
0341a5
 
0341a5
 %token RT			"rt"
0341a5
@@ -746,9 +781,12 @@ int nft_lex(void *, void *, void *);
0341a5
 %type <expr>			udp_hdr_expr	udplite_hdr_expr
0341a5
 %destructor { expr_free($$); }	udp_hdr_expr	udplite_hdr_expr
0341a5
 %type <val>			udp_hdr_field	udplite_hdr_field
0341a5
-%type <expr>			dccp_hdr_expr	sctp_hdr_expr
0341a5
-%destructor { expr_free($$); }	dccp_hdr_expr	sctp_hdr_expr
0341a5
+%type <expr>			dccp_hdr_expr	sctp_hdr_expr sctp_chunk_alloc
0341a5
+%destructor { expr_free($$); }	dccp_hdr_expr	sctp_hdr_expr sctp_chunk_alloc
0341a5
 %type <val>			dccp_hdr_field	sctp_hdr_field
0341a5
+%type <val>			sctp_chunk_type sctp_chunk_common_field
0341a5
+%type <val>			sctp_chunk_data_field sctp_chunk_init_field
0341a5
+%type <val>			sctp_chunk_sack_field
0341a5
 %type <expr>			th_hdr_expr
0341a5
 %destructor { expr_free($$); }	th_hdr_expr
0341a5
 %type <val>			th_hdr_field
0341a5
@@ -843,6 +881,7 @@ opt_newline		:	NEWLINE
0341a5
 close_scope_hash	: { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_HASH); };
0341a5
 close_scope_numgen	: { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_NUMGEN); };
0341a5
 close_scope_sctp	: { scanner_pop_start_cond(nft->scanner, PARSER_SC_SCTP); };
0341a5
+close_scope_sctp_chunk	: { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_SCTP_CHUNK); };
0341a5
 
0341a5
 common_block		:	INCLUDE		QUOTED_STRING	stmt_separator
0341a5
 			{
0341a5
@@ -4783,10 +4822,115 @@ dccp_hdr_field		:	SPORT		{ $$ = DCCPHDR_SPORT; }
0341a5
 			|	TYPE		{ $$ = DCCPHDR_TYPE; }
0341a5
 			;
0341a5
 
0341a5
+sctp_chunk_type		:	DATA		{ $$ = SCTP_CHUNK_TYPE_DATA; }
0341a5
+			|	INIT		{ $$ = SCTP_CHUNK_TYPE_INIT; }
0341a5
+			|	INIT_ACK	{ $$ = SCTP_CHUNK_TYPE_INIT_ACK; }
0341a5
+			|	SACK		{ $$ = SCTP_CHUNK_TYPE_SACK; }
0341a5
+			|	HEARTBEAT	{ $$ = SCTP_CHUNK_TYPE_HEARTBEAT; }
0341a5
+			|	HEARTBEAT_ACK	{ $$ = SCTP_CHUNK_TYPE_HEARTBEAT_ACK; }
0341a5
+			|	ABORT		{ $$ = SCTP_CHUNK_TYPE_ABORT; }
0341a5
+			|	SHUTDOWN	{ $$ = SCTP_CHUNK_TYPE_SHUTDOWN; }
0341a5
+			|	SHUTDOWN_ACK	{ $$ = SCTP_CHUNK_TYPE_SHUTDOWN_ACK; }
0341a5
+			|	ERROR		{ $$ = SCTP_CHUNK_TYPE_ERROR; }
0341a5
+			|	COOKIE_ECHO	{ $$ = SCTP_CHUNK_TYPE_COOKIE_ECHO; }
0341a5
+			|	COOKIE_ACK	{ $$ = SCTP_CHUNK_TYPE_COOKIE_ACK; }
0341a5
+			|	ECNE		{ $$ = SCTP_CHUNK_TYPE_ECNE; }
0341a5
+			|	CWR		{ $$ = SCTP_CHUNK_TYPE_CWR; }
0341a5
+			|	SHUTDOWN_COMPLETE { $$ = SCTP_CHUNK_TYPE_SHUTDOWN_COMPLETE; }
0341a5
+			|	ASCONF_ACK	{ $$ = SCTP_CHUNK_TYPE_ASCONF_ACK; }
0341a5
+			|	FORWARD_TSN	{ $$ = SCTP_CHUNK_TYPE_FORWARD_TSN; }
0341a5
+			|	ASCONF		{ $$ = SCTP_CHUNK_TYPE_ASCONF; }
0341a5
+			;
0341a5
+
0341a5
+sctp_chunk_common_field	:	TYPE	{ $$ = SCTP_CHUNK_COMMON_TYPE; }
0341a5
+			|	FLAGS	{ $$ = SCTP_CHUNK_COMMON_FLAGS; }
0341a5
+			|	LENGTH	{ $$ = SCTP_CHUNK_COMMON_LENGTH; }
0341a5
+			;
0341a5
+
0341a5
+sctp_chunk_data_field	:	TSN	{ $$ = SCTP_CHUNK_DATA_TSN; }
0341a5
+			|	STREAM	{ $$ = SCTP_CHUNK_DATA_STREAM; }
0341a5
+			|	SSN	{ $$ = SCTP_CHUNK_DATA_SSN; }
0341a5
+			|	PPID	{ $$ = SCTP_CHUNK_DATA_PPID; }
0341a5
+			;
0341a5
+
0341a5
+sctp_chunk_init_field	:	INIT_TAG	{ $$ = SCTP_CHUNK_INIT_TAG; }
0341a5
+			|	A_RWND		{ $$ = SCTP_CHUNK_INIT_RWND; }
0341a5
+			|	NUM_OSTREAMS	{ $$ = SCTP_CHUNK_INIT_OSTREAMS; }
0341a5
+			|	NUM_ISTREAMS	{ $$ = SCTP_CHUNK_INIT_ISTREAMS; }
0341a5
+			|	INIT_TSN	{ $$ = SCTP_CHUNK_INIT_TSN; }
0341a5
+			;
0341a5
+
0341a5
+sctp_chunk_sack_field	:	CUM_TSN_ACK	{ $$ = SCTP_CHUNK_SACK_CTSN_ACK; }
0341a5
+			|	A_RWND		{ $$ = SCTP_CHUNK_SACK_RWND; }
0341a5
+			|	NUM_GACK_BLOCKS	{ $$ = SCTP_CHUNK_SACK_GACK_BLOCKS; }
0341a5
+			|	NUM_DUP_TSNS	{ $$ = SCTP_CHUNK_SACK_DUP_TSNS; }
0341a5
+			;
0341a5
+
0341a5
+sctp_chunk_alloc	:	sctp_chunk_type
0341a5
+			{
0341a5
+				$$ = sctp_chunk_expr_alloc(&@$, $1, SCTP_CHUNK_COMMON_TYPE);
0341a5
+				$$->exthdr.flags = NFT_EXTHDR_F_PRESENT;
0341a5
+			}
0341a5
+			|	sctp_chunk_type	sctp_chunk_common_field
0341a5
+			{
0341a5
+				$$ = sctp_chunk_expr_alloc(&@$, $1, $2);
0341a5
+			}
0341a5
+			|	DATA	sctp_chunk_data_field
0341a5
+			{
0341a5
+				$$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_DATA, $2);
0341a5
+			}
0341a5
+			|	INIT	sctp_chunk_init_field
0341a5
+			{
0341a5
+				$$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_INIT, $2);
0341a5
+			}
0341a5
+			|	INIT_ACK	sctp_chunk_init_field
0341a5
+			{
0341a5
+				$$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_INIT_ACK, $2);
0341a5
+			}
0341a5
+			|	SACK	sctp_chunk_sack_field
0341a5
+			{
0341a5
+				$$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_SACK, $2);
0341a5
+			}
0341a5
+			|	SHUTDOWN	CUM_TSN_ACK
0341a5
+			{
0341a5
+				$$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_SHUTDOWN,
0341a5
+							   SCTP_CHUNK_SHUTDOWN_CTSN_ACK);
0341a5
+			}
0341a5
+			|	ECNE	LOWEST_TSN
0341a5
+			{
0341a5
+				$$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_ECNE,
0341a5
+							   SCTP_CHUNK_ECNE_CWR_MIN_TSN);
0341a5
+			}
0341a5
+			|	CWR	LOWEST_TSN
0341a5
+			{
0341a5
+				$$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_CWR,
0341a5
+							   SCTP_CHUNK_ECNE_CWR_MIN_TSN);
0341a5
+			}
0341a5
+			|	ASCONF_ACK	SEQNO
0341a5
+			{
0341a5
+				$$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_ASCONF_ACK,
0341a5
+							   SCTP_CHUNK_ASCONF_SEQNO);
0341a5
+			}
0341a5
+			|	FORWARD_TSN	NEW_CUM_TSN
0341a5
+			{
0341a5
+				$$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_FORWARD_TSN,
0341a5
+							   SCTP_CHUNK_FORWARD_TSN_NCTSN);
0341a5
+			}
0341a5
+			|	ASCONF	SEQNO
0341a5
+			{
0341a5
+				$$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_ASCONF,
0341a5
+							   SCTP_CHUNK_ASCONF_SEQNO);
0341a5
+			}
0341a5
+			;
0341a5
+
0341a5
 sctp_hdr_expr		:	SCTP	sctp_hdr_field	close_scope_sctp
0341a5
 			{
0341a5
 				$$ = payload_expr_alloc(&@$, &proto_sctp, $2);
0341a5
 			}
0341a5
+			|	SCTP	CHUNK	sctp_chunk_alloc close_scope_sctp_chunk close_scope_sctp
0341a5
+			{
0341a5
+				$$ = $3;
0341a5
+			}
0341a5
 			;
0341a5
 
0341a5
 sctp_hdr_field		:	SPORT		{ $$ = SCTPHDR_SPORT; }
0341a5
diff --git a/src/parser_json.c b/src/parser_json.c
0341a5
index fbf7db5..a069a89 100644
0341a5
--- a/src/parser_json.c
0341a5
+++ b/src/parser_json.c
0341a5
@@ -11,6 +11,7 @@
0341a5
 #include <netlink.h>
0341a5
 #include <parser.h>
0341a5
 #include <rule.h>
0341a5
+#include <sctp_chunk.h>
0341a5
 #include <socket.h>
0341a5
 
0341a5
 #include <netdb.h>
0341a5
@@ -705,6 +706,53 @@ static struct expr *json_parse_ip_option_expr(struct json_ctx *ctx,
0341a5
 	return ipopt_expr_alloc(int_loc, descval, fieldval, 0);
0341a5
 }
0341a5
 
0341a5
+static int json_parse_sctp_chunk_field(const struct exthdr_desc *desc,
0341a5
+				       const char *name, int *val)
0341a5
+{
0341a5
+	unsigned int i;
0341a5
+
0341a5
+	for (i = 0; i < array_size(desc->templates); i++) {
0341a5
+		if (desc->templates[i].token &&
0341a5
+		    !strcmp(desc->templates[i].token, name)) {
0341a5
+			if (val)
0341a5
+				*val = i;
0341a5
+			return 0;
0341a5
+		}
0341a5
+	}
0341a5
+	return 1;
0341a5
+}
0341a5
+
0341a5
+static struct expr *json_parse_sctp_chunk_expr(struct json_ctx *ctx,
0341a5
+					       const char *type, json_t *root)
0341a5
+{
0341a5
+	const struct exthdr_desc *desc;
0341a5
+	const char *name, *field;
0341a5
+	struct expr *expr;
0341a5
+	int fieldval;
0341a5
+
0341a5
+	if (json_unpack_err(ctx, root, "{s:s}", "name", &name))
0341a5
+		return NULL;
0341a5
+
0341a5
+	desc = sctp_chunk_protocol_find(name);
0341a5
+	if (!desc) {
0341a5
+		json_error(ctx, "Unknown sctp chunk name '%s'.", name);
0341a5
+		return NULL;
0341a5
+	}
0341a5
+
0341a5
+	if (json_unpack(root, "{s:s}", "field", &field)) {
0341a5
+		expr = sctp_chunk_expr_alloc(int_loc, desc->type,
0341a5
+					     SCTP_CHUNK_COMMON_TYPE);
0341a5
+		expr->exthdr.flags = NFT_EXTHDR_F_PRESENT;
0341a5
+
0341a5
+		return expr;
0341a5
+	}
0341a5
+	if (json_parse_sctp_chunk_field(desc, field, &fieldval)) {
0341a5
+		json_error(ctx, "Unknown sctp chunk field '%s'.", field);
0341a5
+		return NULL;
0341a5
+	}
0341a5
+	return sctp_chunk_expr_alloc(int_loc, desc->type, fieldval);
0341a5
+}
0341a5
+
0341a5
 static const struct exthdr_desc *exthdr_lookup_byname(const char *name)
0341a5
 {
0341a5
 	const struct exthdr_desc *exthdr_tbl[] = {
0341a5
@@ -1410,6 +1458,7 @@ static struct expr *json_parse_expr(struct json_ctx *ctx, json_t *root)
0341a5
 		{ "exthdr", json_parse_exthdr_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT },
0341a5
 		{ "tcp option", json_parse_tcp_option_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_CONCAT },
0341a5
 		{ "ip option", json_parse_ip_option_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_CONCAT },
0341a5
+		{ "sctp chunk", json_parse_sctp_chunk_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_CONCAT },
0341a5
 		{ "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 },
0341a5
 		{ "osf", json_parse_osf_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_MAP | CTX_F_CONCAT },
0341a5
 		{ "ipsec", json_parse_xfrm_expr, CTX_F_PRIMARY | CTX_F_MAP | CTX_F_CONCAT },
0341a5
diff --git a/src/scanner.l b/src/scanner.l
0341a5
index b79ae55..fe1b8ad 100644
0341a5
--- a/src/scanner.l
0341a5
+++ b/src/scanner.l
0341a5
@@ -199,6 +199,7 @@ addrstring	({macaddr}|{ip4addr}|{ip6addr})
0341a5
 %s SCANSTATE_SCTP
0341a5
 %s SCANSTATE_EXPR_HASH
0341a5
 %s SCANSTATE_EXPR_NUMGEN
0341a5
+%s SCANSTATE_EXPR_SCTP_CHUNK
0341a5
 
0341a5
 %%
0341a5
 
0341a5
@@ -492,9 +493,46 @@ addrstring	({macaddr}|{ip4addr}|{ip6addr})
0341a5
 "sctp"			{ scanner_push_start_cond(yyscanner, SCANSTATE_SCTP); return SCTP; }
0341a5
 
0341a5
 <SCANSTATE_SCTP>{
0341a5
+	"chunk"			{ scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_SCTP_CHUNK); return CHUNK; }
0341a5
 	"vtag"			{ return VTAG; }
0341a5
 }
0341a5
 
0341a5
+<SCANSTATE_EXPR_SCTP_CHUNK>{
0341a5
+	"data"			{ return DATA; }
0341a5
+	"init"			{ return INIT; }
0341a5
+	"init-ack"		{ return INIT_ACK; }
0341a5
+	"heartbeat"		{ return HEARTBEAT; }
0341a5
+	"heartbeat-ack"		{ return HEARTBEAT_ACK; }
0341a5
+	"abort"			{ return ABORT; }
0341a5
+	"shutdown"		{ return SHUTDOWN; }
0341a5
+	"shutdown-ack"		{ return SHUTDOWN_ACK; }
0341a5
+	"error"			{ return ERROR; }
0341a5
+	"cookie-echo"		{ return COOKIE_ECHO; }
0341a5
+	"cookie-ack"		{ return COOKIE_ACK; }
0341a5
+	"ecne"			{ return ECNE; }
0341a5
+	"cwr"			{ return CWR; }
0341a5
+	"shutdown-complete"	{ return SHUTDOWN_COMPLETE; }
0341a5
+	"asconf-ack"		{ return ASCONF_ACK; }
0341a5
+	"forward-tsn"		{ return FORWARD_TSN; }
0341a5
+	"asconf"		{ return ASCONF; }
0341a5
+
0341a5
+	"tsn"			{ return TSN; }
0341a5
+	"stream"		{ return STREAM; }
0341a5
+	"ssn"			{ return SSN; }
0341a5
+	"ppid"			{ return PPID; }
0341a5
+	"init-tag"		{ return INIT_TAG; }
0341a5
+	"a-rwnd"		{ return A_RWND; }
0341a5
+	"num-outbound-streams"	{ return NUM_OSTREAMS; }
0341a5
+	"num-inbound-streams"	{ return NUM_ISTREAMS; }
0341a5
+	"initial-tsn"		{ return INIT_TSN; }
0341a5
+	"cum-tsn-ack"		{ return CUM_TSN_ACK; }
0341a5
+	"num-gap-ack-blocks"	{ return NUM_GACK_BLOCKS; }
0341a5
+	"num-dup-tsns"		{ return NUM_DUP_TSNS; }
0341a5
+	"lowest-tsn"		{ return LOWEST_TSN; }
0341a5
+	"seqno"			{ return SEQNO; }
0341a5
+	"new-cum-tsn"		{ return NEW_CUM_TSN; }
0341a5
+}
0341a5
+
0341a5
 "rt"			{ return RT; }
0341a5
 "rt0"			{ return RT0; }
0341a5
 "rt2"			{ return RT2; }
0341a5
diff --git a/src/sctp_chunk.c b/src/sctp_chunk.c
0341a5
new file mode 100644
0341a5
index 0000000..6e73e72
0341a5
--- /dev/null
0341a5
+++ b/src/sctp_chunk.c
0341a5
@@ -0,0 +1,261 @@
0341a5
+/*
0341a5
+ * Copyright Red Hat
0341a5
+ *
0341a5
+ * This program is free software; you can redistribute it and/or modify
0341a5
+ * it under the terms of the GNU General Public License version 2 (or any
0341a5
+ * later) as published by the Free Software Foundation.
0341a5
+ */
0341a5
+
0341a5
+#include <exthdr.h>
0341a5
+#include <sctp_chunk.h>
0341a5
+
0341a5
+#include <string.h>
0341a5
+
0341a5
+#define PHT(__token, __offset, __len) \
0341a5
+	PROTO_HDR_TEMPLATE(__token, &integer_type, BYTEORDER_BIG_ENDIAN, \
0341a5
+			   __offset, __len)
0341a5
+
0341a5
+static const struct exthdr_desc sctp_chunk_data = {
0341a5
+	.name	= "data",
0341a5
+	.type	= SCTP_CHUNK_TYPE_DATA,
0341a5
+	.templates = {
0341a5
+		[SCTP_CHUNK_COMMON_TYPE]	= PHT("type", 0, 8),
0341a5
+		[SCTP_CHUNK_COMMON_FLAGS]	= PHT("flags", 8, 8),
0341a5
+		[SCTP_CHUNK_COMMON_LENGTH]	= PHT("length", 16, 16),
0341a5
+		[SCTP_CHUNK_DATA_TSN]		= PHT("tsn", 32, 32),
0341a5
+		[SCTP_CHUNK_DATA_STREAM]	= PHT("stream", 64, 16),
0341a5
+		[SCTP_CHUNK_DATA_SSN]		= PHT("ssn", 80, 16),
0341a5
+		[SCTP_CHUNK_DATA_PPID]		= PHT("ppid", 96, 32),
0341a5
+	},
0341a5
+};
0341a5
+
0341a5
+static const struct exthdr_desc sctp_chunk_init = {
0341a5
+	.name	= "init",
0341a5
+	.type	= SCTP_CHUNK_TYPE_INIT,
0341a5
+	.templates = {
0341a5
+		[SCTP_CHUNK_COMMON_TYPE]	= PHT("type", 0, 8),
0341a5
+		[SCTP_CHUNK_COMMON_FLAGS]	= PHT("flags", 8, 8),
0341a5
+		[SCTP_CHUNK_COMMON_LENGTH]	= PHT("length", 16, 16),
0341a5
+		[SCTP_CHUNK_INIT_TAG]		= PHT("init-tag", 32, 32),
0341a5
+		[SCTP_CHUNK_INIT_RWND]		= PHT("a-rwnd", 64, 32),
0341a5
+		[SCTP_CHUNK_INIT_OSTREAMS]	= PHT("num-outbound-streams", 96, 16),
0341a5
+		[SCTP_CHUNK_INIT_ISTREAMS]	= PHT("num-inbound-streams", 112, 16),
0341a5
+		[SCTP_CHUNK_INIT_TSN]		= PHT("initial-tsn", 128, 32),
0341a5
+	},
0341a5
+};
0341a5
+
0341a5
+static const struct exthdr_desc sctp_chunk_init_ack = {
0341a5
+	.name	= "init-ack",
0341a5
+	.type	= SCTP_CHUNK_TYPE_INIT_ACK,
0341a5
+	.templates = {
0341a5
+		[SCTP_CHUNK_COMMON_TYPE]	= PHT("type", 0, 8),
0341a5
+		[SCTP_CHUNK_COMMON_FLAGS]	= PHT("flags", 8, 8),
0341a5
+		[SCTP_CHUNK_COMMON_LENGTH]	= PHT("length", 16, 16),
0341a5
+		[SCTP_CHUNK_INIT_TAG]		= PHT("init-tag", 32, 32),
0341a5
+		[SCTP_CHUNK_INIT_RWND]		= PHT("a-rwnd", 64, 32),
0341a5
+		[SCTP_CHUNK_INIT_OSTREAMS]	= PHT("num-outbound-streams", 96, 16),
0341a5
+		[SCTP_CHUNK_INIT_ISTREAMS]	= PHT("num-inbound-streams", 112, 16),
0341a5
+		[SCTP_CHUNK_INIT_TSN]		= PHT("initial-tsn", 128, 32),
0341a5
+	},
0341a5
+};
0341a5
+
0341a5
+static const struct exthdr_desc sctp_chunk_sack = {
0341a5
+	.name	= "sack",
0341a5
+	.type	= SCTP_CHUNK_TYPE_SACK,
0341a5
+	.templates = {
0341a5
+		[SCTP_CHUNK_COMMON_TYPE]	= PHT("type", 0, 8),
0341a5
+		[SCTP_CHUNK_COMMON_FLAGS]	= PHT("flags", 8, 8),
0341a5
+		[SCTP_CHUNK_COMMON_LENGTH]	= PHT("length", 16, 16),
0341a5
+		[SCTP_CHUNK_SACK_CTSN_ACK]	= PHT("cum-tsn-ack", 32, 32),
0341a5
+		[SCTP_CHUNK_SACK_RWND]		= PHT("a-rwnd", 64, 32),
0341a5
+		[SCTP_CHUNK_SACK_GACK_BLOCKS]	= PHT("num-gap-ack-blocks", 96, 16),
0341a5
+		[SCTP_CHUNK_SACK_DUP_TSNS]	= PHT("num-dup-tsns", 112, 16),
0341a5
+	},
0341a5
+};
0341a5
+
0341a5
+static const struct exthdr_desc sctp_chunk_shutdown = {
0341a5
+	.name	= "shutdown",
0341a5
+	.type	= SCTP_CHUNK_TYPE_SHUTDOWN,
0341a5
+	.templates = {
0341a5
+		[SCTP_CHUNK_COMMON_TYPE]	= PHT("type", 0, 8),
0341a5
+		[SCTP_CHUNK_COMMON_FLAGS]	= PHT("flags", 8, 8),
0341a5
+		[SCTP_CHUNK_COMMON_LENGTH]	= PHT("length", 16, 16),
0341a5
+		[SCTP_CHUNK_SHUTDOWN_CTSN_ACK]	= PHT("cum-tsn-ack", 32, 32),
0341a5
+	},
0341a5
+};
0341a5
+
0341a5
+static const struct exthdr_desc sctp_chunk_ecne = {
0341a5
+	.name	= "ecne",
0341a5
+	.type	= SCTP_CHUNK_TYPE_ECNE,
0341a5
+	.templates = {
0341a5
+		[SCTP_CHUNK_COMMON_TYPE]	= PHT("type", 0, 8),
0341a5
+		[SCTP_CHUNK_COMMON_FLAGS]	= PHT("flags", 8, 8),
0341a5
+		[SCTP_CHUNK_COMMON_LENGTH]	= PHT("length", 16, 16),
0341a5
+		[SCTP_CHUNK_ECNE_CWR_MIN_TSN]	= PHT("lowest-tsn", 32, 32),
0341a5
+	},
0341a5
+};
0341a5
+
0341a5
+static const struct exthdr_desc sctp_chunk_cwr = {
0341a5
+	.name	= "cwr",
0341a5
+	.type	= SCTP_CHUNK_TYPE_CWR,
0341a5
+	.templates = {
0341a5
+		[SCTP_CHUNK_COMMON_TYPE]	= PHT("type", 0, 8),
0341a5
+		[SCTP_CHUNK_COMMON_FLAGS]	= PHT("flags", 8, 8),
0341a5
+		[SCTP_CHUNK_COMMON_LENGTH]	= PHT("length", 16, 16),
0341a5
+		[SCTP_CHUNK_ECNE_CWR_MIN_TSN]	= PHT("lowest-tsn", 32, 32),
0341a5
+	},
0341a5
+};
0341a5
+
0341a5
+static const struct exthdr_desc sctp_chunk_asconf_ack = {
0341a5
+	.name	= "asconf-ack",
0341a5
+	.type	= SCTP_CHUNK_TYPE_ASCONF_ACK,
0341a5
+	.templates = {
0341a5
+		[SCTP_CHUNK_COMMON_TYPE]	= PHT("type", 0, 8),
0341a5
+		[SCTP_CHUNK_COMMON_FLAGS]	= PHT("flags", 8, 8),
0341a5
+		[SCTP_CHUNK_COMMON_LENGTH]	= PHT("length", 16, 16),
0341a5
+		[SCTP_CHUNK_ASCONF_SEQNO]	= PHT("seqno", 32, 32),
0341a5
+	},
0341a5
+};
0341a5
+
0341a5
+static const struct exthdr_desc sctp_chunk_forward_tsn = {
0341a5
+	.name	= "forward-tsn",
0341a5
+	.type	= SCTP_CHUNK_TYPE_FORWARD_TSN,
0341a5
+	.templates = {
0341a5
+		[SCTP_CHUNK_COMMON_TYPE]	= PHT("type", 0, 8),
0341a5
+		[SCTP_CHUNK_COMMON_FLAGS]	= PHT("flags", 8, 8),
0341a5
+		[SCTP_CHUNK_COMMON_LENGTH]	= PHT("length", 16, 16),
0341a5
+		[SCTP_CHUNK_FORWARD_TSN_NCTSN]	= PHT("new-cum-tsn", 32, 32),
0341a5
+	},
0341a5
+};
0341a5
+
0341a5
+static const struct exthdr_desc sctp_chunk_asconf = {
0341a5
+	.name	= "asconf",
0341a5
+	.type	= SCTP_CHUNK_TYPE_ASCONF,
0341a5
+	.templates = {
0341a5
+		[SCTP_CHUNK_COMMON_TYPE]	= PHT("type", 0, 8),
0341a5
+		[SCTP_CHUNK_COMMON_FLAGS]	= PHT("flags", 8, 8),
0341a5
+		[SCTP_CHUNK_COMMON_LENGTH]	= PHT("length", 16, 16),
0341a5
+		[SCTP_CHUNK_ASCONF_SEQNO]	= PHT("seqno", 32, 32),
0341a5
+	},
0341a5
+};
0341a5
+
0341a5
+#define SCTP_CHUNK_DESC_GENERATOR(descname, hname, desctype)		\
0341a5
+static const struct exthdr_desc sctp_chunk_##descname = {		\
0341a5
+	.name	= #hname,						\
0341a5
+	.type	= SCTP_CHUNK_TYPE_##desctype,				\
0341a5
+	.templates = {							\
0341a5
+		[SCTP_CHUNK_COMMON_TYPE]	= PHT("type", 0, 8),	\
0341a5
+		[SCTP_CHUNK_COMMON_FLAGS]	= PHT("flags", 8, 8),	\
0341a5
+		[SCTP_CHUNK_COMMON_LENGTH]	= PHT("length", 16, 16),\
0341a5
+	},								\
0341a5
+};
0341a5
+
0341a5
+SCTP_CHUNK_DESC_GENERATOR(heartbeat, heartbeat, HEARTBEAT)
0341a5
+SCTP_CHUNK_DESC_GENERATOR(heartbeat_ack, heartbeat-ack, HEARTBEAT_ACK)
0341a5
+SCTP_CHUNK_DESC_GENERATOR(abort, abort, ABORT)
0341a5
+SCTP_CHUNK_DESC_GENERATOR(shutdown_ack, shutdown-ack, SHUTDOWN_ACK)
0341a5
+SCTP_CHUNK_DESC_GENERATOR(error, error, ERROR)
0341a5
+SCTP_CHUNK_DESC_GENERATOR(cookie_echo, cookie-echo, COOKIE_ECHO)
0341a5
+SCTP_CHUNK_DESC_GENERATOR(cookie_ack, cookie-ack, COOKIE_ACK)
0341a5
+SCTP_CHUNK_DESC_GENERATOR(shutdown_complete, shutdown-complete, SHUTDOWN_COMPLETE)
0341a5
+
0341a5
+#undef SCTP_CHUNK_DESC_GENERATOR
0341a5
+
0341a5
+static const struct exthdr_desc *sctp_chunk_protocols[] = {
0341a5
+	[SCTP_CHUNK_TYPE_DATA]			= &sctp_chunk_data,
0341a5
+	[SCTP_CHUNK_TYPE_INIT]			= &sctp_chunk_init,
0341a5
+	[SCTP_CHUNK_TYPE_INIT_ACK]		= &sctp_chunk_init_ack,
0341a5
+	[SCTP_CHUNK_TYPE_SACK]			= &sctp_chunk_sack,
0341a5
+	[SCTP_CHUNK_TYPE_HEARTBEAT]		= &sctp_chunk_heartbeat,
0341a5
+	[SCTP_CHUNK_TYPE_HEARTBEAT_ACK]		= &sctp_chunk_heartbeat_ack,
0341a5
+	[SCTP_CHUNK_TYPE_ABORT]			= &sctp_chunk_abort,
0341a5
+	[SCTP_CHUNK_TYPE_SHUTDOWN]		= &sctp_chunk_shutdown,
0341a5
+	[SCTP_CHUNK_TYPE_SHUTDOWN_ACK]		= &sctp_chunk_shutdown_ack,
0341a5
+	[SCTP_CHUNK_TYPE_ERROR]			= &sctp_chunk_error,
0341a5
+	[SCTP_CHUNK_TYPE_COOKIE_ECHO]		= &sctp_chunk_cookie_echo,
0341a5
+	[SCTP_CHUNK_TYPE_COOKIE_ACK]		= &sctp_chunk_cookie_ack,
0341a5
+	[SCTP_CHUNK_TYPE_ECNE]			= &sctp_chunk_ecne,
0341a5
+	[SCTP_CHUNK_TYPE_CWR]			= &sctp_chunk_cwr,
0341a5
+	[SCTP_CHUNK_TYPE_SHUTDOWN_COMPLETE]	= &sctp_chunk_shutdown_complete,
0341a5
+	[SCTP_CHUNK_TYPE_ASCONF_ACK]		= &sctp_chunk_asconf_ack,
0341a5
+	[SCTP_CHUNK_TYPE_FORWARD_TSN]		= &sctp_chunk_forward_tsn,
0341a5
+	[SCTP_CHUNK_TYPE_ASCONF]		= &sctp_chunk_asconf,
0341a5
+};
0341a5
+
0341a5
+const struct exthdr_desc *sctp_chunk_protocol_find(const char *name)
0341a5
+{
0341a5
+	unsigned int i;
0341a5
+
0341a5
+	for (i = 0; i < array_size(sctp_chunk_protocols); i++) {
0341a5
+		if (sctp_chunk_protocols[i] &&
0341a5
+		    !strcmp(sctp_chunk_protocols[i]->name, name))
0341a5
+			return sctp_chunk_protocols[i];
0341a5
+	}
0341a5
+	return NULL;
0341a5
+}
0341a5
+
0341a5
+struct expr *sctp_chunk_expr_alloc(const struct location *loc,
0341a5
+				   unsigned int type, unsigned int field)
0341a5
+{
0341a5
+	const struct proto_hdr_template *tmpl;
0341a5
+	const struct exthdr_desc *desc = NULL;
0341a5
+	struct expr *expr;
0341a5
+
0341a5
+	if (type < array_size(sctp_chunk_protocols))
0341a5
+		desc = sctp_chunk_protocols[type];
0341a5
+
0341a5
+	if (!desc)
0341a5
+		return NULL;
0341a5
+
0341a5
+	tmpl = &desc->templates[field];
0341a5
+	if (!tmpl)
0341a5
+		return NULL;
0341a5
+
0341a5
+	expr = expr_alloc(loc, EXPR_EXTHDR, tmpl->dtype,
0341a5
+			  BYTEORDER_BIG_ENDIAN, tmpl->len);
0341a5
+	expr->exthdr.desc	= desc;
0341a5
+	expr->exthdr.tmpl	= tmpl;
0341a5
+	expr->exthdr.op		= NFT_EXTHDR_OP_SCTP;
0341a5
+	expr->exthdr.raw_type	= desc->type;
0341a5
+	expr->exthdr.offset	= tmpl->offset;
0341a5
+
0341a5
+	return expr;
0341a5
+}
0341a5
+
0341a5
+void sctp_chunk_init_raw(struct expr *expr, uint8_t type, unsigned int off,
0341a5
+			 unsigned int len, uint32_t flags)
0341a5
+{
0341a5
+	const struct proto_hdr_template *tmpl;
0341a5
+	unsigned int i;
0341a5
+
0341a5
+	assert(expr->etype == EXPR_EXTHDR);
0341a5
+
0341a5
+	expr->len = len;
0341a5
+	expr->exthdr.flags = flags;
0341a5
+	expr->exthdr.offset = off;
0341a5
+	expr->exthdr.op = NFT_EXTHDR_OP_SCTP;
0341a5
+
0341a5
+	if (flags & NFT_EXTHDR_F_PRESENT)
0341a5
+		datatype_set(expr, &boolean_type);
0341a5
+	else
0341a5
+		datatype_set(expr, &integer_type);
0341a5
+
0341a5
+	if (type >= array_size(sctp_chunk_protocols))
0341a5
+		return;
0341a5
+
0341a5
+	expr->exthdr.desc = sctp_chunk_protocols[type];
0341a5
+	expr->exthdr.flags = flags;
0341a5
+	assert(expr->exthdr.desc != NULL);
0341a5
+
0341a5
+	for (i = 0; i < array_size(expr->exthdr.desc->templates); ++i) {
0341a5
+		tmpl = &expr->exthdr.desc->templates[i];
0341a5
+		if (tmpl->offset != off || tmpl->len != len)
0341a5
+			continue;
0341a5
+
0341a5
+		if ((flags & NFT_EXTHDR_F_PRESENT) == 0)
0341a5
+			datatype_set(expr, tmpl->dtype);
0341a5
+
0341a5
+		expr->exthdr.tmpl = tmpl;
0341a5
+		break;
0341a5
+	}
0341a5
+}
0341a5
diff --git a/tests/py/inet/sctp.t b/tests/py/inet/sctp.t
0341a5
index 5188b57..3d1c2fd 100644
0341a5
--- a/tests/py/inet/sctp.t
0341a5
+++ b/tests/py/inet/sctp.t
0341a5
@@ -41,3 +41,40 @@ sctp vtag {33, 55, 67, 88};ok
0341a5
 sctp vtag != {33, 55, 67, 88};ok
0341a5
 sctp vtag { 33-55};ok
0341a5
 sctp vtag != { 33-55};ok
0341a5
+
0341a5
+# assert all chunk types are recognized
0341a5
+sctp chunk data exists;ok
0341a5
+sctp chunk init exists;ok
0341a5
+sctp chunk init-ack exists;ok
0341a5
+sctp chunk sack exists;ok
0341a5
+sctp chunk heartbeat exists;ok
0341a5
+sctp chunk heartbeat-ack exists;ok
0341a5
+sctp chunk abort exists;ok
0341a5
+sctp chunk shutdown exists;ok
0341a5
+sctp chunk shutdown-ack exists;ok
0341a5
+sctp chunk error exists;ok
0341a5
+sctp chunk cookie-echo exists;ok
0341a5
+sctp chunk cookie-ack exists;ok
0341a5
+sctp chunk ecne exists;ok
0341a5
+sctp chunk cwr exists;ok
0341a5
+sctp chunk shutdown-complete exists;ok
0341a5
+sctp chunk asconf-ack exists;ok
0341a5
+sctp chunk forward-tsn exists;ok
0341a5
+sctp chunk asconf exists;ok
0341a5
+
0341a5
+# test common header fields in random chunk types
0341a5
+sctp chunk data type 0;ok
0341a5
+sctp chunk init flags 23;ok
0341a5
+sctp chunk init-ack length 42;ok
0341a5
+
0341a5
+# test one custom field in every applicable chunk type
0341a5
+sctp chunk data stream 1337;ok
0341a5
+sctp chunk init initial-tsn 5;ok
0341a5
+sctp chunk init-ack num-outbound-streams 3;ok
0341a5
+sctp chunk sack a-rwnd 1;ok
0341a5
+sctp chunk shutdown cum-tsn-ack 65535;ok
0341a5
+sctp chunk ecne lowest-tsn 5;ok
0341a5
+sctp chunk cwr lowest-tsn 8;ok
0341a5
+sctp chunk asconf-ack seqno 12345;ok
0341a5
+sctp chunk forward-tsn new-cum-tsn 31337;ok
0341a5
+sctp chunk asconf seqno 12345;ok
0341a5
diff --git a/tests/py/inet/sctp.t.json b/tests/py/inet/sctp.t.json
0341a5
index 2684b03..8135686 100644
0341a5
--- a/tests/py/inet/sctp.t.json
0341a5
+++ b/tests/py/inet/sctp.t.json
0341a5
@@ -608,3 +608,481 @@
0341a5
     }
0341a5
 ]
0341a5
 
0341a5
+# sctp chunk data exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "data"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk init exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "init"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk init-ack exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "init-ack"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk sack exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "sack"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk heartbeat exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "heartbeat"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk heartbeat-ack exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "heartbeat-ack"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk abort exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "abort"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk shutdown exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "shutdown"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk shutdown-ack exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "shutdown-ack"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk error exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "error"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk cookie-echo exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "cookie-echo"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk cookie-ack exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "cookie-ack"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk ecne exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "ecne"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk cwr exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "cwr"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk shutdown-complete exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "shutdown-complete"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk asconf-ack exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "asconf-ack"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk forward-tsn exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "forward-tsn"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk asconf exists
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "name": "asconf"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": true
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk data type 0
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "field": "type",
0341a5
+                    "name": "data"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": 0
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk init flags 23
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "field": "flags",
0341a5
+                    "name": "init"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": 23
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk init-ack length 42
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "field": "length",
0341a5
+                    "name": "init-ack"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": 42
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk data stream 1337
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "field": "stream",
0341a5
+                    "name": "data"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": 1337
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk init initial-tsn 5
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "field": "initial-tsn",
0341a5
+                    "name": "init"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": 5
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk init-ack num-outbound-streams 3
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "field": "num-outbound-streams",
0341a5
+                    "name": "init-ack"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": 3
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk sack a-rwnd 1
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "field": "a-rwnd",
0341a5
+                    "name": "sack"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": 1
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk shutdown cum-tsn-ack 65535
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "field": "cum-tsn-ack",
0341a5
+                    "name": "shutdown"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": 65535
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk ecne lowest-tsn 5
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "field": "lowest-tsn",
0341a5
+                    "name": "ecne"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": 5
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk cwr lowest-tsn 8
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "field": "lowest-tsn",
0341a5
+                    "name": "cwr"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": 8
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk asconf-ack seqno 12345
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "field": "seqno",
0341a5
+                    "name": "asconf-ack"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": 12345
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk forward-tsn new-cum-tsn 31337
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "field": "new-cum-tsn",
0341a5
+                    "name": "forward-tsn"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": 31337
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
+# sctp chunk asconf seqno 12345
0341a5
+[
0341a5
+    {
0341a5
+        "match": {
0341a5
+            "left": {
0341a5
+                "sctp chunk": {
0341a5
+                    "field": "seqno",
0341a5
+                    "name": "asconf"
0341a5
+                }
0341a5
+            },
0341a5
+            "op": "==",
0341a5
+            "right": 12345
0341a5
+        }
0341a5
+    }
0341a5
+]
0341a5
+
0341a5
diff --git a/tests/py/inet/sctp.t.payload b/tests/py/inet/sctp.t.payload
0341a5
index ecfcc72..9c4854c 100644
0341a5
--- a/tests/py/inet/sctp.t.payload
0341a5
+++ b/tests/py/inet/sctp.t.payload
0341a5
@@ -274,3 +274,158 @@ inet test-inet input
0341a5
   [ payload load 4b @ transport header + 4 => reg 1 ]
0341a5
   [ lookup reg 1 set __set%d 0x1 ]
0341a5
 
0341a5
+# sctp chunk data exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 0 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk init exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 1 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk init-ack exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 2 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk sack exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 3 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk heartbeat exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 4 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk heartbeat-ack exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 5 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk abort exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 6 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk shutdown exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 7 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk shutdown-ack exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 8 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk error exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 9 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk cookie-echo exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 10 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk cookie-ack exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 11 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk ecne exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 12 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk cwr exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 13 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk shutdown-complete exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 14 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk asconf-ack exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 128 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk forward-tsn exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 192 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk asconf exists
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 193 + 0 present => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000001 ]
0341a5
+
0341a5
+# sctp chunk data type 0
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 0 + 0 => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000000 ]
0341a5
+
0341a5
+# sctp chunk init flags 23
0341a5
+ip
0341a5
+  [ exthdr load 1b @ 1 + 1 => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000017 ]
0341a5
+
0341a5
+# sctp chunk init-ack length 42
0341a5
+ip
0341a5
+  [ exthdr load 2b @ 2 + 2 => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00002a00 ]
0341a5
+
0341a5
+# sctp chunk data stream 1337
0341a5
+ip
0341a5
+  [ exthdr load 2b @ 0 + 8 => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00003905 ]
0341a5
+
0341a5
+# sctp chunk init initial-tsn 5
0341a5
+ip
0341a5
+  [ exthdr load 4b @ 1 + 16 => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x05000000 ]
0341a5
+
0341a5
+# sctp chunk init-ack num-outbound-streams 3
0341a5
+ip
0341a5
+  [ exthdr load 2b @ 2 + 12 => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x00000300 ]
0341a5
+
0341a5
+# sctp chunk sack a-rwnd 1
0341a5
+ip
0341a5
+  [ exthdr load 4b @ 3 + 8 => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x01000000 ]
0341a5
+
0341a5
+# sctp chunk shutdown cum-tsn-ack 65535
0341a5
+ip
0341a5
+  [ exthdr load 4b @ 7 + 4 => reg 1 ]
0341a5
+  [ cmp eq reg 1 0xffff0000 ]
0341a5
+
0341a5
+# sctp chunk ecne lowest-tsn 5
0341a5
+ip
0341a5
+  [ exthdr load 4b @ 12 + 4 => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x05000000 ]
0341a5
+
0341a5
+# sctp chunk cwr lowest-tsn 8
0341a5
+ip
0341a5
+  [ exthdr load 4b @ 13 + 4 => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x08000000 ]
0341a5
+
0341a5
+# sctp chunk asconf-ack seqno 12345
0341a5
+ip
0341a5
+  [ exthdr load 4b @ 128 + 4 => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x39300000 ]
0341a5
+
0341a5
+# sctp chunk forward-tsn new-cum-tsn 31337
0341a5
+ip
0341a5
+  [ exthdr load 4b @ 192 + 4 => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x697a0000 ]
0341a5
+
0341a5
+# sctp chunk asconf seqno 12345
0341a5
+ip
0341a5
+  [ exthdr load 4b @ 193 + 4 => reg 1 ]
0341a5
+  [ cmp eq reg 1 0x39300000 ]
0341a5
+
0341a5
-- 
0341a5
2.31.1
0341a5