From 71b2d88655a05c9112fe2cf7d644122f0174ad0a Mon Sep 17 00:00:00 2001
From: Aomx <aomx@riseup.net>
Date: Thu, 14 Jul 2016 11:24:17 +0200
Subject: [PATCH] first commit regarding support of nr-sack chunks

---
 gtests/net/packetdrill/lexer.l                |   2 +
 gtests/net/packetdrill/parser.y               |  21 ++-
 gtests/net/packetdrill/run_packet.c           | 131 +++++++++++++++++-
 gtests/net/packetdrill/sctp.h                 |  26 ++++
 gtests/net/packetdrill/sctp_chunk_to_string.c |  53 +++++++
 gtests/net/packetdrill/sctp_packet.c          | 125 +++++++++++++++++
 gtests/net/packetdrill/sctp_packet.h          |  12 ++
 7 files changed, 364 insertions(+), 6 deletions(-)

diff --git a/gtests/net/packetdrill/lexer.l b/gtests/net/packetdrill/lexer.l
index da93842b..62768487 100644
--- a/gtests/net/packetdrill/lexer.l
+++ b/gtests/net/packetdrill/lexer.l
@@ -541,6 +541,7 @@ DATA				return DATA;
 INIT				return INIT;
 INIT_ACK			return INIT_ACK;
 SACK				return SACK;
+NR_SACK				return NR_SACK;
 HEARTBEAT			return HEARTBEAT;
 HEARTBEAT_ACK			return HEARTBEAT_ACK;
 ABORT				return ABORT;
@@ -570,6 +571,7 @@ ppid				return PPID;
 fsn				return FSN;
 cum_tsn				return CUM_TSN;
 gaps				return GAPS;
+nr_gaps				return NR_GAPS;
 dups				return DUPS;
 adaptation_code_point		return ADAPTATION_CODE_POINT;
 OUTGOING_SSN_RESET		return OUTGOING_SSN_RESET;
diff --git a/gtests/net/packetdrill/parser.y b/gtests/net/packetdrill/parser.y
index dc41710d..d89c874e 100644
--- a/gtests/net/packetdrill/parser.y
+++ b/gtests/net/packetdrill/parser.y
@@ -499,7 +499,7 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %token <reserved> MSG_NAME MSG_IOV MSG_FLAGS MSG_CONTROL CMSG_LEN CMSG_LEVEL CMSG_TYPE CMSG_DATA
 %token <reserved> SF_HDTR_HEADERS SF_HDTR_TRAILERS
 %token <reserved> FD EVENTS REVENTS ONOFF LINGER
-%token <reserved> ACK ECR EOL MSS NOP SACK SACKOK TIMESTAMP VAL WIN WSCALE PRO
+%token <reserved> ACK ECR EOL MSS NOP SACK NR_SACK SACKOK TIMESTAMP VAL WIN WSCALE PRO
 %token <reserved> FAST_OPEN
 %token <reserved> IOV_BASE IOV_LEN
 %token <reserved> ECT0 ECT1 CE ECT01 NO_ECN
@@ -521,7 +521,7 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %token <reserved> SHUTDOWN SHUTDOWN_ACK ERROR COOKIE_ECHO COOKIE_ACK ECNE CWR
 %token <reserved> SHUTDOWN_COMPLETE I_DATA PAD RECONFIG
 %token <reserved> TYPE FLAGS LEN
-%token <reserved> TAG A_RWND OS IS TSN SID SSN MID PPID FSN CUM_TSN GAPS DUPS
+%token <reserved> TAG A_RWND OS IS TSN SID SSN MID PPID FSN CUM_TSN GAPS NR_GAPS DUPS
 %token <reserved> PARAMETER HEARTBEAT_INFORMATION IPV4_ADDRESS IPV6_ADDRESS
 %token <reserved> STATE_COOKIE UNRECOGNIZED_PARAMETER COOKIE_PRESERVATIVE
 %token <reserved> HOSTNAME_ADDRESS SUPPORTED_ADDRESS_TYPES ECN_CAPABLE
@@ -664,7 +664,7 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %type <chunk_list_item> sctp_generic_chunk_spec
 %type <chunk_list_item> sctp_data_chunk_spec
 %type <chunk_list_item> sctp_init_chunk_spec sctp_init_ack_chunk_spec
-%type <chunk_list_item> sctp_sack_chunk_spec
+%type <chunk_list_item> sctp_sack_chunk_spec sctp_nr_sack_chunk_spec
 %type <chunk_list_item> sctp_heartbeat_chunk_spec sctp_heartbeat_ack_chunk_spec
 %type <chunk_list_item> sctp_abort_chunk_spec
 %type <chunk_list_item> sctp_shutdown_chunk_spec
@@ -720,7 +720,7 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %type <byte_list_item> byte
 %type <u16_list> u16_list
 %type <u16_item> u16_item
-%type <sack_block_list> opt_gaps gap_list opt_dups dup_list
+%type <sack_block_list> opt_gaps opt_nr_gaps gap_list opt_dups dup_list
 %type <sack_block_list_item> gap dup
 %type <address_type_list> address_types_list
 %type <address_type_list_item> address_type
@@ -1039,6 +1039,7 @@ sctp_chunk_spec
 | sctp_init_chunk_spec              { $$ = $1; }
 | sctp_init_ack_chunk_spec          { $$ = $1; }
 | sctp_sack_chunk_spec              { $$ = $1; }
+| sctp_nr_sack_chunk_spec           { $$ = $1; }
 | sctp_heartbeat_chunk_spec         { $$ = $1; }
 | sctp_heartbeat_ack_chunk_spec     { $$ = $1; }
 | sctp_abort_chunk_spec             { $$ = $1; }
@@ -1521,6 +1522,12 @@ opt_gaps
 | GAPS '=' '[' gap_list ']' { $$ = $4; }
 ;
 
+opt_nr_gaps
+: NR_GAPS '=' ELLIPSIS         { $$ = NULL; }
+| NR_GAPS '=' '[' ELLIPSIS ']' { $$ = NULL; }
+| NR_GAPS '=' '[' gap_list ']' { $$ = $4; }
+;
+
 gap_list
 :                  { $$ = sctp_sack_block_list_new(); }
 | gap              { $$ = sctp_sack_block_list_new();
@@ -1604,6 +1611,12 @@ sctp_sack_chunk_spec
 	$$ = sctp_sack_chunk_new($3, $5, $7, $9, $11);
 }
 
+sctp_nr_sack_chunk_spec
+: NR_SACK '[' opt_flags ',' opt_cum_tsn ',' opt_a_rwnd ',' opt_gaps ',' opt_nr_gaps ',' opt_dups']' {
+	$$ = sctp_nr_sack_chunk_new($3, $5, $7, $9, $11, $13);
+}
+
+
 sctp_heartbeat_chunk_spec
 : HEARTBEAT '[' opt_flags ',' sctp_heartbeat_information_parameter_spec ']' {
 	$$ = sctp_heartbeat_chunk_new($3, $5);
diff --git a/gtests/net/packetdrill/run_packet.c b/gtests/net/packetdrill/run_packet.c
index 56fd664b..df55aa96 100644
--- a/gtests/net/packetdrill/run_packet.c
+++ b/gtests/net/packetdrill/run_packet.c
@@ -646,6 +646,7 @@ static int map_inbound_sctp_packet(
 	struct sctp_init_chunk *init;
 	struct sctp_init_ack_chunk *init_ack;
 	struct sctp_sack_chunk *sack;
+	struct sctp_nr_sack_chunk *nr_sack;
 	struct sctp_abort_chunk *abort;
 	struct sctp_shutdown_chunk *shutdown;
 	struct sctp_ecne_chunk *ecne;
@@ -655,7 +656,7 @@ static int map_inbound_sctp_packet(
 	struct sctp_reconfig_chunk *reconfig;
 	u32 local_diff, remote_diff;
 	u32 v_tag;
-	u16 nr_gap_blocks, nr_dup_tsns, i;
+	u16 nr_gap_blocks, number_of_nr_gap_blocks, nr_dup_tsns, i;
 	bool reflect_v_tag;
 	bool contains_init_chunk;
 
@@ -710,6 +711,22 @@ static int map_inbound_sctp_packet(
 				}
 			}
 			break;
+		case SCTP_NR_SACK_CHUNK_TYPE:
+			nr_sack = (struct sctp_nr_sack_chunk *)chunk;
+			DEBUGP("Old SACK cum TSN %d\n", ntohl(nr_sack->cum_tsn));
+			nr_sack->cum_tsn = htonl(ntohl(nr_sack->cum_tsn) + local_diff);
+			DEBUGP("New SACK cum TSN %d\n", ntohl(nr_sack->cum_tsn));
+			nr_gap_blocks = ntohs(nr_sack->nr_gap_blocks);
+			number_of_nr_gap_blocks = ntohs(nr_sack->nr_of_nr_gap_blocks);
+			nr_dup_tsns = ntohs(nr_sack->nr_dup_tsns);
+
+			if (ntohs(nr_sack->length) == sizeof(struct sctp_nr_sack_chunk) + sizeof(union sctp_nr_sack_block) * (nr_dup_tsns+nr_gap_blocks)) {
+				for (i = 0; i < nr_dup_tsns; i++) {
+					u16 offset = nr_gap_blocks + number_of_nr_gap_blocks;
+					nr_sack->block[i + offset].tsn = htonl(ntohl(nr_sack->block[i + offset].tsn) + local_diff);
+				}
+			}
+			break;
 		case SCTP_ABORT_CHUNK_TYPE:
 			abort = (struct sctp_abort_chunk *)chunk;
 			if (abort->flags & SCTP_ABORT_CHUNK_T_BIT) {
@@ -943,13 +960,14 @@ static int map_outbound_live_sctp_packet(
 	struct sctp_init_chunk *init;
 	struct sctp_init_ack_chunk *init_ack;
 	struct sctp_sack_chunk *sack;
+	struct sctp_nr_sack_chunk *nr_sack;
 	struct sctp_shutdown_chunk *shutdown;
 	struct sctp_ecne_chunk *ecne;
 	struct sctp_cwr_chunk *cwr;
 	struct sctp_i_data_chunk *i_data;
 	struct sctp_reconfig_chunk *reconfig;
 	u32 local_diff, remote_diff;
-	u16 nr_gap_blocks, nr_dup_tsns, i;
+	u16 nr_gap_blocks, nr_dup_tsns, number_of_nr_gap_blocks, i;
 
 	/* FIXME: transform v-tag in the common header*/
 	DEBUGP("map_outbound_live_sctp_packet\n");
@@ -992,6 +1010,17 @@ static int map_outbound_live_sctp_packet(
 				sack->block[i + nr_gap_blocks].tsn = htonl(ntohl(sack->block[i + nr_gap_blocks].tsn) + remote_diff);
 			}
 			break;
+		case SCTP_NR_SACK_CHUNK_TYPE:
+			nr_sack = (struct sctp_nr_sack_chunk *)chunk;
+			nr_sack->cum_tsn = htonl(ntohl(nr_sack->cum_tsn) + remote_diff);
+			nr_gap_blocks = ntohs(nr_sack->nr_gap_blocks);
+			number_of_nr_gap_blocks = ntohs(nr_sack->nr_of_nr_gap_blocks);
+			nr_dup_tsns = ntohs(nr_sack->nr_dup_tsns);
+			for (i = 0; i < nr_dup_tsns; i++) {
+				u16 offset = nr_gap_blocks + number_of_nr_gap_blocks;
+				nr_sack->block[i + offset].tsn = htonl(ntohl(nr_sack->block[i + offset].tsn) + remote_diff);
+			}
+			break;
 		case SCTP_SHUTDOWN_CHUNK_TYPE:
 			shutdown = (struct sctp_shutdown_chunk *)chunk;
 			shutdown->cum_tsn = htonl(ntohl(shutdown->cum_tsn) + remote_diff);
@@ -1812,6 +1841,99 @@ static int verify_sack_chunk(struct sctp_sack_chunk *actual_chunk,
 	return STATUS_OK;
 }
 
+static int verify_nr_sack_chunk(struct sctp_nr_sack_chunk *actual_chunk,
+                             struct sctp_nr_sack_chunk *script_chunk,
+                             u32 flags, char **error)
+{
+	u16 actual_nr_gap_blocks, actual_nr_of_nr_gap_blocks, actual_nr_dup_tsns;
+	u16 script_nr_gap_blocks, script_nr_of_nr_gap_blocks, script_nr_dup_tsns;
+	u16 i, actual_base, script_base;
+
+	actual_nr_gap_blocks = ntohs(actual_chunk->nr_gap_blocks);
+	actual_nr_of_nr_gap_blocks = ntohs(actual_chunk->nr_of_nr_gap_blocks);
+	actual_nr_dup_tsns = ntohs(actual_chunk->nr_dup_tsns);
+	script_nr_gap_blocks = ntohs(script_chunk->nr_gap_blocks);
+	script_nr_of_nr_gap_blocks = ntohs(script_chunk->nr_of_nr_gap_blocks);
+	script_nr_dup_tsns = ntohs(script_chunk->nr_dup_tsns);
+	
+	script_base = 0;
+
+	if ((flags & FLAG_NR_SACK_CHUNK_CUM_TSN_NOCHECK ? STATUS_OK :
+	        check_field("sctp_nr_sack_chunk_cum_tsn",
+		            ntohl(script_chunk->cum_tsn),
+		            ntohl(actual_chunk->cum_tsn),
+		            error)) ||
+	    (flags & FLAG_NR_SACK_CHUNK_A_RWND_NOCHECK ? STATUS_OK :
+	        check_field("sctp_nr_sack_chunk_a_rwnd",
+		            ntohl(script_chunk->a_rwnd),
+		            ntohl(actual_chunk->a_rwnd),
+		            error)) ||
+	    (flags & FLAG_NR_SACK_CHUNK_GAP_BLOCKS_NOCHECK? STATUS_OK :
+		check_field("sctp_nr_sack_chunk_nr_gap_blocks",
+		            script_nr_gap_blocks,
+		            actual_nr_gap_blocks,
+		            error)) ||
+	    (flags & FLAG_NR_SACK_CHUNK_NR_GAP_BLOCKS_NOCHECK? STATUS_OK :
+		check_field("sctp_nr_sack_chunk_nr_of_nr_gap_blocks",
+		            script_nr_of_nr_gap_blocks,
+		            actual_nr_of_nr_gap_blocks,
+		            error)) ||
+	    (flags & FLAG_NR_SACK_CHUNK_DUP_TSNS_NOCHECK? STATUS_OK :
+		check_field("sctp_nr_sack_chunk_nr_dup_tsns",
+		            script_nr_dup_tsns,
+		            actual_nr_dup_tsns,
+		            error))) {
+		return STATUS_ERR;
+	}
+
+	if ((flags & FLAG_NR_SACK_CHUNK_GAP_BLOCKS_NOCHECK) == 0) {
+		for (i = 0; i < script_nr_gap_blocks; i++) {
+			if (check_field("sctp_sack_chunk_gap_block_start",
+		                        ntohs(script_chunk->block[i].gap.start),
+		                        ntohs(actual_chunk->block[i].gap.start),
+		                        error) ||
+		            check_field("sctp_sack_chunk_gap_block_end",
+		                        ntohs(script_chunk->block[i].gap.end),
+		                        ntohs(actual_chunk->block[i].gap.end),
+		                        error)) {
+				return STATUS_ERR;
+			}
+		}
+		script_base += actual_nr_gap_blocks;
+	}
+	
+	if ((flags & FLAG_NR_SACK_CHUNK_NR_GAP_BLOCKS_NOCHECK) == 0) {
+		actual_base = actual_nr_gap_blocks;
+		for (i = 0; i < script_nr_of_nr_gap_blocks; i++) {
+			if (check_field("sctp_sack_chunk_nr_gap_block_start",
+		                        ntohs(script_chunk->block[script_base + i].gap.start),
+		                        ntohs(actual_chunk->block[actual_base + i].gap.start),
+		                        error) ||
+		            check_field("sctp_sack_chunk_nr_gap_block_end",
+		                        ntohs(script_chunk->block[script_base + i].gap.end),
+		                        ntohs(actual_chunk->block[actual_base + i].gap.end),
+		                        error)) {
+				return STATUS_ERR;
+			}
+		}
+		script_base += script_nr_of_nr_gap_blocks;
+	}
+	
+	
+	if ((flags & FLAG_NR_SACK_CHUNK_DUP_TSNS_NOCHECK) == 0) {
+		actual_base = actual_nr_gap_blocks + actual_nr_of_nr_gap_blocks;
+		for (i = 0; i < script_nr_dup_tsns; i++) {
+			if (check_field("sctp_sack_chunk_dup_tsn",
+		                        ntohl(script_chunk->block[script_base + i].tsn),
+		                        ntohl(actual_chunk->block[actual_base + i].tsn),
+		                        error)) {
+				return STATUS_ERR;
+			}
+		}
+	}
+	return STATUS_OK;
+}
+
 static int verify_heartbeat_chunk(struct sctp_heartbeat_chunk *actual_chunk,
                                   struct sctp_heartbeat_chunk *script_chunk,
                                   u32 flags, char **error)
@@ -2111,6 +2233,11 @@ static int verify_sctp(
 			                           (struct sctp_sack_chunk *)script_chunk,
 			                           flags, error);
 			break;
+		case SCTP_NR_SACK_CHUNK_TYPE:
+			result = verify_nr_sack_chunk((struct sctp_nr_sack_chunk *)actual_chunk,
+			                           (struct sctp_nr_sack_chunk *)script_chunk,
+			                           flags, error);
+			break;
 		case SCTP_HEARTBEAT_CHUNK_TYPE:
 			result = verify_heartbeat_chunk((struct sctp_heartbeat_chunk *)actual_chunk,
 			                                (struct sctp_heartbeat_chunk *)script_chunk,
diff --git a/gtests/net/packetdrill/sctp.h b/gtests/net/packetdrill/sctp.h
index 0b95445b..54d87e8b 100644
--- a/gtests/net/packetdrill/sctp.h
+++ b/gtests/net/packetdrill/sctp.h
@@ -41,6 +41,7 @@ struct sctp_common_header {
 #define SCTP_INIT_CHUNK_TYPE				0x01
 #define SCTP_INIT_ACK_CHUNK_TYPE			0x02
 #define SCTP_SACK_CHUNK_TYPE				0x03
+#define SCTP_NR_SACK_CHUNK_TYPE				0x10
 #define SCTP_HEARTBEAT_CHUNK_TYPE			0x04
 #define SCTP_HEARTBEAT_ACK_CHUNK_TYPE			0x05
 #define SCTP_ABORT_CHUNK_TYPE				0x06
@@ -126,6 +127,31 @@ struct sctp_sack_chunk {
 	union sctp_sack_block block[];
 } __packed;
 
+union sctp_nr_sack_block {
+	struct {
+		__be16 start;
+		__be16 end;
+	} gap;
+	struct {
+		__be16 start;
+		__be16 end;
+	} nr_gap;
+	u32 tsn;
+} __packed;
+
+struct sctp_nr_sack_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+	__be32 cum_tsn;
+	__be32 a_rwnd;
+	__be16 nr_gap_blocks;
+	__be16 nr_of_nr_gap_blocks;
+	__be16 nr_dup_tsns;
+	__be16 reserved;
+	union sctp_nr_sack_block block[];
+} __packed;
+
 struct sctp_heartbeat_chunk {
 	__u8 type;
 	__u8 flags;
diff --git a/gtests/net/packetdrill/sctp_chunk_to_string.c b/gtests/net/packetdrill/sctp_chunk_to_string.c
index 5ef8e002..049d9af3 100644
--- a/gtests/net/packetdrill/sctp_chunk_to_string.c
+++ b/gtests/net/packetdrill/sctp_chunk_to_string.c
@@ -251,6 +251,9 @@ static int sctp_supported_extensions_parameter_to_string(
 		case SCTP_SACK_CHUNK_TYPE:
 			fputs("SACK", s);
 			break;
+		case SCTP_NR_SACK_CHUNK_TYPE:
+			fputs("NR-SACK", s);
+			break;
 		case SCTP_HEARTBEAT_CHUNK_TYPE:
 			fputs("HEARTBEAT", s);
 			break;
@@ -1252,6 +1255,52 @@ static int sctp_sack_chunk_to_string(FILE *s,
 	return STATUS_OK;
 }
 
+static int sctp_nr_sack_chunk_to_string(FILE *s,
+				     struct sctp_nr_sack_chunk *chunk,
+				     char **error)
+{
+	u16 length;
+	u16 nr_gaps, nr_of_nr_gaps, nr_dups;
+	u16 i;
+
+	length = ntohs(chunk->length);
+	if (length < sizeof(struct sctp_nr_sack_chunk)) {
+		asprintf(error, "NR-SACK chunk too short (length=%u)", length);
+		return STATUS_ERR;
+	}
+	nr_gaps = ntohs(chunk->nr_gap_blocks);
+	nr_of_nr_gaps = ntohs(chunk->nr_of_nr_gap_blocks);
+	nr_dups = ntohs(chunk->nr_dup_tsns);
+	if (length != sizeof(struct sctp_nr_sack_chunk) +
+		      (nr_gaps + nr_of_nr_gaps +  nr_dups) * sizeof(u32)) {
+		asprintf(error, "NR-SACK chunk length inconsistent");
+		return STATUS_ERR;
+	}
+	fputs("NR-SACK[", s);
+	fprintf(s, "flgs=0x%02x, ", chunk->flags);
+	fprintf(s, "cum_tsn=%u, ", ntohl(chunk->cum_tsn));
+	fprintf(s, "a_rwnd=%u, ", ntohl(chunk->a_rwnd));
+	fputs("gaps=[", s);
+	for (i = 0; i < nr_gaps; i++)
+		fprintf(s, "%s%u:%u",
+			   i > 0 ? ", " : "",
+			   ntohs(chunk->block[i].gap.start),
+			   ntohs(chunk->block[i].gap.end));
+	fputs("], nr-gaps=[", s);
+	for (i = 0; i < nr_of_nr_gaps; i++)
+		fprintf(s, "%s%u:%u",
+			   i > 0 ? ", " : "",
+			   ntohs(chunk->block[i + nr_gaps].gap.start),
+			   ntohs(chunk->block[i + nr_gaps].gap.end));
+	fputs("], dups=[", s);
+	for (i = 0; i < nr_dups; i++)
+		fprintf(s, "%s%u",
+			   i > 0 ? ", " : "",
+			   ntohl(chunk->block[i + nr_gaps + nr_of_nr_gaps].tsn));
+	fputs("]]", s);
+	return STATUS_OK;
+}
+
 static int sctp_heartbeat_chunk_to_string(FILE *s,
 					  struct sctp_heartbeat_chunk *chunk,
 					  char **error)
@@ -1673,6 +1722,10 @@ int sctp_chunk_to_string(FILE *s, struct sctp_chunk *chunk, char **error)
 		result = sctp_sack_chunk_to_string(s,
 			(struct sctp_sack_chunk *)chunk, error);
 		break;
+	case SCTP_NR_SACK_CHUNK_TYPE:
+		result = sctp_nr_sack_chunk_to_string(s,
+			(struct sctp_nr_sack_chunk *)chunk, error);
+		break;
 	case SCTP_HEARTBEAT_CHUNK_TYPE:
 		result = sctp_heartbeat_chunk_to_string(s,
 			(struct sctp_heartbeat_chunk *)chunk, error);
diff --git a/gtests/net/packetdrill/sctp_packet.c b/gtests/net/packetdrill/sctp_packet.c
index 725d59ba..2795253c 100644
--- a/gtests/net/packetdrill/sctp_packet.c
+++ b/gtests/net/packetdrill/sctp_packet.c
@@ -742,6 +742,104 @@ sctp_sack_chunk_new(s64 flgs, s64 cum_tsn, s64 a_rwnd,
 	                                sctp_cause_list_new());
 }
 
+struct sctp_chunk_list_item *
+sctp_nr_sack_chunk_new(s64 flgs, s64 cum_tsn, s64 a_rwnd,
+                    struct sctp_sack_block_list *gaps,
+		    struct sctp_sack_block_list *nr_gaps_list,
+                    struct sctp_sack_block_list *dups) {
+	struct sctp_nr_sack_chunk *chunk;
+	struct sctp_sack_block_list_item *item;
+	u32 flags;
+	u32 length;
+	u16 i, nr_gaps, nr_dups, number_of_nr_gaps;
+
+	flags = 0;
+	length = sizeof(struct sctp_nr_sack_chunk);
+	if (gaps == NULL) {
+		nr_gaps = 0;
+		flags |= FLAG_CHUNK_LENGTH_NOCHECK;
+		flags |= FLAG_NR_SACK_CHUNK_GAP_BLOCKS_NOCHECK;
+	} else {
+		nr_gaps = gaps->nr_entries;
+		length += nr_gaps * sizeof(union sctp_nr_sack_block);
+	}
+	if (nr_gaps_list == NULL) {
+		number_of_nr_gaps = 0;
+		flags |= FLAG_CHUNK_LENGTH_NOCHECK;
+		flags |= FLAG_NR_SACK_CHUNK_NR_GAP_BLOCKS_NOCHECK;
+	}
+	else {
+		number_of_nr_gaps = nr_gaps_list->nr_entries;
+		length += number_of_nr_gaps * sizeof(union sctp_nr_sack_block);
+	}
+	if (dups == NULL) {
+		nr_dups = 0;
+		flags |= FLAG_CHUNK_LENGTH_NOCHECK;
+		flags |= FLAG_NR_SACK_CHUNK_DUP_TSNS_NOCHECK;
+	} else {
+		nr_dups = dups->nr_entries;
+		length += nr_dups * sizeof(union sctp_nr_sack_block);
+	}
+	assert(is_valid_u16(length));
+	assert(length % 4 == 0);
+	chunk = malloc(length);
+	assert(chunk != NULL);
+	chunk->type = SCTP_NR_SACK_CHUNK_TYPE;
+	if (flgs == -1) {
+		chunk->flags = 0;
+		flags |= FLAG_CHUNK_FLAGS_NOCHECK;
+	} else {
+		chunk->flags = (u8)flgs;
+	}
+	chunk->length = htons(length);
+	if (cum_tsn == -1) {
+		chunk->cum_tsn = htonl(0);
+		flags |= FLAG_NR_SACK_CHUNK_CUM_TSN_NOCHECK;
+	} else {
+		chunk->cum_tsn = htonl((u32)cum_tsn);
+	}
+	if (a_rwnd == -1) {
+		chunk->a_rwnd = htonl(0);
+		flags |= FLAG_NR_SACK_CHUNK_A_RWND_NOCHECK;
+	} else {
+		chunk->a_rwnd = htonl((u32)a_rwnd);
+	}
+	chunk->nr_gap_blocks = htons(nr_gaps);
+	chunk->nr_dup_tsns = htons(nr_dups);
+	chunk->nr_of_nr_gap_blocks = htons(number_of_nr_gaps);
+
+	if (gaps != NULL) {
+		for (i = 0, item = gaps->first;
+		     (i < nr_gaps) && (item != NULL);
+		     i++, item = item->next) {
+			chunk->block[i].gap.start = htons(item->block.gap.start);
+			chunk->block[i].gap.end = htons(item->block.gap.end);
+		}
+		assert((i == nr_gaps) && (item == NULL));
+	}
+	if (nr_gaps_list != NULL) {
+		for (i = 0, item = nr_gaps_list->first;
+		     (i < number_of_nr_gaps) && (item != NULL);
+		     i++, item = item->next) {
+			chunk->block[i + nr_gaps].gap.start = htons(item->block.gap.start);
+			chunk->block[i + nr_gaps].gap.end = htons(item->block.gap.end);
+		}
+		assert((i == number_of_nr_gaps) && (item == NULL));
+	}
+	if (dups != NULL) {
+		for (i = 0, item = dups->first;
+		     (i < nr_dups) && (item != NULL);
+		     i++, item = item->next) {
+			chunk->block[i + nr_gaps + number_of_nr_gaps].tsn= htonl(item->block.tsn);
+		}
+		assert((i == nr_dups) && (item == NULL));
+	}
+	return sctp_chunk_list_item_new((struct sctp_chunk *)chunk,
+	                                length, flags,
+	                                sctp_parameter_list_new(),
+	                                sctp_cause_list_new());
+}
+
 struct sctp_chunk_list_item *
 sctp_heartbeat_chunk_new(s64 flgs, struct sctp_parameter_list_item *info)
 {
@@ -2908,6 +3006,33 @@ new_sctp_packet(int address_family,
 					return NULL;
 				}
 				break;
+			case SCTP_NR_SACK_CHUNK_TYPE:
+				if (chunk_item->flags & FLAG_NR_SACK_CHUNK_CUM_TSN_NOCHECK) {
+					asprintf(error,
+						 "CUM_TSN must be specified for inbound packets");
+					return NULL;
+				}
+				if (chunk_item->flags & FLAG_NR_SACK_CHUNK_A_RWND_NOCHECK) {
+					asprintf(error,
+						 "A_RWND must be specified for inbound packets");
+					return NULL;
+				}
+				if (chunk_item->flags & FLAG_NR_SACK_CHUNK_GAP_BLOCKS_NOCHECK) {
+					asprintf(error,
+						 "GAP_BLOCKS must be specified for inbound packets");
+					return NULL;
+				}
+				if (chunk_item->flags & FLAG_NR_SACK_CHUNK_NR_GAP_BLOCKS_NOCHECK) {
+					asprintf(error,
+						 "NR_GAP_BLOCKS must be specified for inbound packets");
+					return NULL;
+				}
+				if (chunk_item->flags & FLAG_NR_SACK_CHUNK_DUP_TSNS_NOCHECK) {
+					asprintf(error,
+						 "DUP_TSNS must be specified for inbound packets");
+					return NULL;
+				}
+				break;
 			case SCTP_HEARTBEAT_CHUNK_TYPE:
 				break;
 			case SCTP_HEARTBEAT_ACK_CHUNK_TYPE:
diff --git a/gtests/net/packetdrill/sctp_packet.h b/gtests/net/packetdrill/sctp_packet.h
index 01922281..9a845e0d 100644
--- a/gtests/net/packetdrill/sctp_packet.h
+++ b/gtests/net/packetdrill/sctp_packet.h
@@ -256,6 +256,18 @@ sctp_sack_chunk_new(s64 flgs, s64 cum_tsn, s64 a_rwnd,
                     struct sctp_sack_block_list *gaps,
                     struct sctp_sack_block_list *dups);
 
+#define FLAG_NR_SACK_CHUNK_CUM_TSN_NOCHECK         0x00000100
+#define FLAG_NR_SACK_CHUNK_A_RWND_NOCHECK          0x00000200
+#define FLAG_NR_SACK_CHUNK_GAP_BLOCKS_NOCHECK      0x00000400
+#define FLAG_NR_SACK_CHUNK_NR_GAP_BLOCKS_NOCHECK   0x00000800
+#define FLAG_NR_SACK_CHUNK_DUP_TSNS_NOCHECK        0x00001000
+
+struct sctp_chunk_list_item *
+sctp_nr_sack_chunk_new(s64 flgs, s64 cum_tsn, s64 a_rwnd,
+                    struct sctp_sack_block_list *gaps,
+		    struct sctp_sack_block_list *nr_gaps,
+                    struct sctp_sack_block_list *dups);
+
 struct sctp_chunk_list_item *
 sctp_heartbeat_chunk_new(s64 flgs, struct sctp_parameter_list_item *info);
 
-- 
GitLab