From 2c356609eac47f8870efeb6e6edbe889a8f3dfbf Mon Sep 17 00:00:00 2001
From: Michael Tuexen <tuexen@fh-muenster.de>
Date: Sat, 2 May 2015 22:38:58 +0200
Subject: [PATCH] Add support for gaps and dups in SACK using the syntax
 SACK[tsn=2 gaps=[4:4, 6:6] dups=[8]] This fixes
 https://github.com/nplab/packetdrill/issues/4

---
 gtests/net/packetdrill/lexer.l       |   2 +
 gtests/net/packetdrill/parser.y      |  53 ++++++++-
 gtests/net/packetdrill/sctp_packet.c | 159 +++++++++++++++++++++++----
 gtests/net/packetdrill/sctp_packet.h |  31 +++++-
 4 files changed, 221 insertions(+), 24 deletions(-)

diff --git a/gtests/net/packetdrill/lexer.l b/gtests/net/packetdrill/lexer.l
index d1d3c111..5d177ad1 100644
--- a/gtests/net/packetdrill/lexer.l
+++ b/gtests/net/packetdrill/lexer.l
@@ -235,6 +235,8 @@ tsn			return TSN;
 sid			return SID;
 ssn			return SSN;
 ppid			return PPID;
+gaps			return GAPS;
+dups			return DUPS;
 --[a-zA-Z0-9_]+		yylval.string	= option(yytext); return OPTION;
 [-]?[0-9]*[.][0-9]+	yylval.floating	= atof(yytext);   return FLOAT;
 [-]?[0-9]+		yylval.integer	= atoll(yytext);  return INTEGER;
diff --git a/gtests/net/packetdrill/parser.y b/gtests/net/packetdrill/parser.y
index b6b22e1e..7590b0cc 100644
--- a/gtests/net/packetdrill/parser.y
+++ b/gtests/net/packetdrill/parser.y
@@ -465,6 +465,8 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 	struct packet *packet;
 	struct sctp_chunk_list_item *chunk_list_item;
 	struct sctp_chunk_list *chunk_list;
+	struct sctp_sack_block_list_item *sack_block_list_item;
+	struct sctp_sack_block_list *sack_block_list;
 	struct syscall_spec *syscall;
 	struct command_spec *command;
 	struct code_spec *code;
@@ -497,7 +499,7 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %token <reserved> DATA INIT INIT_ACK HEARTBEAT HEARTBEAT_ACK ABORT
 %token <reserved> SHUTDOWN SHUTDOWN_ACK ERROR COOKIE_ECHO COOKIE_ACK ECNE CWR
 %token <reserved> SHUTDOWN_COMPLETE
-%token <reserved> TAG A_RWND OS IS TSN SID SSN PPID
+%token <reserved> TAG A_RWND OS IS TSN SID SSN PPID GAPS DUPS
 %token <floating> FLOAT
 %token <integer> INTEGER HEX_INTEGER
 %token <string> WORD STRING BACK_QUOTED CODE IPV4_ADDR IPV6_ADDR
@@ -550,6 +552,8 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %type <chunk_list_item> sctp_ecne_chunk_spec sctp_cwr_chunk_spec
 %type <chunk_list_item> sctp_shutdown_complete_chunk_spec
 %type <integer> opt_a_rwnd opt_os opt_is opt_tsn opt_sid opt_ssn opt_ppid
+%type <sack_block_list> opt_gaps gap_list opt_dups dup_list
+%type <sack_block_list_item> gap dup
 
 %%  /* The grammar follows. */
 
@@ -821,6 +825,49 @@ opt_ppid
 	$$ = $3;
 }
 
+opt_gaps
+:                           { $$ = NULL; }
+| GAPS '=' '[' gap_list ']' { $$ = $4; }
+;
+
+gap_list
+:                  { $$ = sctp_sack_block_list_new(); }
+| gap              { $$ = sctp_sack_block_list_new();
+                     sctp_sack_block_list_append($$, $1); }
+| gap_list ',' gap { $$ = $1;
+                     sctp_sack_block_list_append($1, $3); }
+;
+
+gap
+: INTEGER ':' INTEGER   { if (!is_valid_u32($1)) {
+                            semantic_error("start value out of range");
+	                  }
+	                  if (!is_valid_u32($3)) {
+                            semantic_error("end value out of range");
+	                  }
+	                  $$ = sctp_sack_block_list_item_gap_new($1, $3); }
+;
+
+opt_dups
+:                           { $$ = NULL; }
+| DUPS '=' '[' dup_list ']' { $$ = $4; }
+;
+
+dup_list
+:                  { $$ = sctp_sack_block_list_new(); }
+| dup              { $$ = sctp_sack_block_list_new();
+                     sctp_sack_block_list_append($$, $1); }
+| dup_list ',' dup { $$ = $1;
+                     sctp_sack_block_list_append($1, $3); }
+;
+
+dup
+: INTEGER   { if (!is_valid_u32($1)) {
+                semantic_error("tsn value out of range");
+	      }
+	      $$ = sctp_sack_block_list_item_dup_new($1); }
+;
+
 sctp_data_chunk_spec
 : DATA '[' opt_tsn opt_sid opt_ssn opt_ppid ']' {
 	$$ = sctp_data_chunk_new($3, $4, $5, $6);
@@ -849,8 +896,8 @@ sctp_init_ack_chunk_spec
 }
 
 sctp_sack_chunk_spec
-: SACK '[' opt_tsn opt_a_rwnd']' {
-	$$ = sctp_sack_chunk_new($3, $4);
+: SACK '[' opt_tsn opt_a_rwnd opt_gaps opt_dups']' {
+	$$ = sctp_sack_chunk_new($3, $4, $5, $6);
 }
 
 sctp_heartbeat_chunk_spec
diff --git a/gtests/net/packetdrill/sctp_packet.c b/gtests/net/packetdrill/sctp_packet.c
index 9d1777e2..6f03f444 100644
--- a/gtests/net/packetdrill/sctp_packet.c
+++ b/gtests/net/packetdrill/sctp_packet.c
@@ -34,7 +34,83 @@
  * - Add support for parameters (fix hard coded state cookie in INIT-ACK)
  * - Add support for error causes
  */
- 
+
+struct sctp_sack_block_list *
+sctp_sack_block_list_new(void)
+{
+	struct sctp_sack_block_list *list;
+
+	list = malloc(sizeof(struct sctp_sack_block_list));
+	assert(list != NULL);
+	list->first = NULL;
+	list->last = NULL;
+	list->nr_entries = 0;
+	return list;
+}
+
+void
+sctp_sack_block_list_append(struct sctp_sack_block_list *list,
+                            struct sctp_sack_block_list_item *item)
+{
+	assert(item->next == NULL);
+	if (list->last == NULL) {
+		assert(list->first == NULL);
+		assert(list->nr_entries == 0);
+		list->first = item;
+	} else {
+		assert(list->first != NULL);
+		list->last->next = item;
+	}
+	list->last = item;
+	list->nr_entries++;
+}
+
+void
+sctp_sctp_sack_block_list_free(struct sctp_sack_block_list *list)
+{
+	struct sctp_sack_block_list_item *current_item, *next_item;
+
+	if (list == NULL) {
+		return;
+	}
+	current_item = list->first;
+	while (current_item != NULL) {
+		assert(list->nr_entries > 0);
+		next_item = current_item->next;
+		assert(next_item != NULL || current_item == list->last);
+		free(current_item);
+		current_item = next_item;
+		list->nr_entries--;
+	}
+	assert(list->nr_entries == 0);
+	free(list);
+}
+
+struct sctp_sack_block_list_item *
+sctp_sack_block_list_item_gap_new(u16 start, u16 end)
+{
+	struct sctp_sack_block_list_item *item;
+
+	item = malloc(sizeof(struct sctp_sack_block_list_item));
+	assert(item != NULL);
+	item->next = NULL;
+	item->block.gap.start = start;
+	item->block.gap.end = end;
+	return item;
+}
+
+struct sctp_sack_block_list_item *
+sctp_sack_block_list_item_dup_new(u32 tsn)
+{
+	struct sctp_sack_block_list_item *item;
+
+	item = malloc(sizeof(struct sctp_sack_block_list_item));
+	assert(item != NULL);
+	item->next = NULL;
+	item->block.tsn = tsn;
+	return item;
+}
+
 struct sctp_chunk_list_item *
 sctp_chunk_list_item_new(struct sctp_chunk *chunk, u32 length, u32 flags)
 {
@@ -182,18 +258,41 @@ sctp_init_ack_chunk_new(s64 tag, s64 a_rwnd, s64 os, s64 is, s64 tsn)
 }
 
 struct sctp_chunk_list_item *
-sctp_sack_chunk_new(s64 cum_tsn, s64 a_rwnd)
+sctp_sack_chunk_new(s64 cum_tsn, s64 a_rwnd,
+                    struct sctp_sack_block_list *gaps,
+                    struct sctp_sack_block_list *dups)
 {
-	struct sctp_chunk_list_item *item;
 	struct sctp_sack_chunk *chunk;
+	struct sctp_sack_block_list_item *item;
 	u32 flags;
+	u32 length;
+	u16 i, nr_gaps, nr_dups;
 
 	flags = 0;
-	chunk = malloc(sizeof(struct sctp_sack_chunk));
+	length = sizeof(struct sctp_sack_chunk);
+	if (gaps == NULL) {
+		nr_gaps = 0;
+		flags |= FLAG_CHUNK_LENGTH_NOCHECK;
+		flags |= FLAG_SACK_CHUNK_GAP_BLOCKS_NOCHECK;
+	} else {
+		nr_gaps = gaps->nr_entries;
+		length += nr_gaps * sizeof(union sctp_sack_block);
+	}
+	if (dups == NULL) {
+		nr_dups = 0;
+		flags |= FLAG_CHUNK_LENGTH_NOCHECK;
+		flags |= FLAG_SACK_CHUNK_DUP_TSNS_NOCHECK;
+	} else {
+		nr_dups = dups->nr_entries;
+		length += nr_dups * sizeof(union sctp_sack_block);
+	}
+	assert(is_valid_u16(length));
+	assert(length % 4 == 0);
+	chunk = malloc(length);
 	assert(chunk != NULL);
 	chunk->type = SCTP_SACK_CHUNK_TYPE;
 	chunk->flags = 0;
-	chunk->length = htons(sizeof(struct sctp_sack_chunk));
+	chunk->length = htons(length);
 	if (cum_tsn == -1) {
 		chunk->cum_tsn = htonl(0);
 		flags |= FLAG_SACK_CHUNK_CUM_TSN_NOCHECK;
@@ -206,12 +305,28 @@ sctp_sack_chunk_new(s64 cum_tsn, s64 a_rwnd)
 	} else {
 		chunk->a_rwnd = htonl((u32)a_rwnd);
 	}
-	chunk->nr_gap_blocks = htons(0);
-	chunk->nr_dup_tsns = htons(0);
-	item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk,
-	                                (u32)sizeof(struct sctp_sack_chunk), 
-	                                flags);
-	return item;
+	chunk->nr_gap_blocks = htons(nr_gaps);
+	chunk->nr_dup_tsns = htons(nr_dups);
+
+	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 (dups != NULL) {
+		for (i = 0, item = dups->first;
+		     (i < nr_dups) && (item != NULL);
+		     i++, item = item->next) {
+			chunk->block[i + 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);
 }
 
 struct sctp_chunk_list_item *
@@ -428,7 +543,8 @@ sctp_shutdown_complete_chunk_new(u8 flags)
 	return item;
 }
 
-struct sctp_chunk_list *sctp_chunk_list_new(void)
+struct sctp_chunk_list *
+sctp_chunk_list_new(void)
 {
 	struct sctp_chunk_list *list;
 
@@ -440,8 +556,9 @@ struct sctp_chunk_list *sctp_chunk_list_new(void)
 	return list;
 }
 
-void sctp_chunk_list_append(struct sctp_chunk_list *list,
-			    struct sctp_chunk_list_item *item)
+void
+sctp_chunk_list_append(struct sctp_chunk_list *list,
+                       struct sctp_chunk_list_item *item)
 {
 	assert(item->next == NULL);
 	if (list->last == NULL) {
@@ -456,7 +573,8 @@ void sctp_chunk_list_append(struct sctp_chunk_list *list,
 	list->length += item->length;
 }
 
-void sctp_chunk_list_free(struct sctp_chunk_list *list)
+void
+sctp_chunk_list_free(struct sctp_chunk_list *list)
 {
 	struct sctp_chunk_list_item *current_item, *next_item;
 
@@ -472,11 +590,12 @@ void sctp_chunk_list_free(struct sctp_chunk_list *list)
 	free(list);
 }
 
-struct packet *new_sctp_packet(int address_family,
-			       enum direction_t direction,
-			       enum ip_ecn_t ecn,
-			       struct sctp_chunk_list *list,
-			       char **error)
+struct packet *
+new_sctp_packet(int address_family,
+                enum direction_t direction,
+                enum ip_ecn_t ecn,
+                struct sctp_chunk_list *list,
+                char **error)
 {
 	struct packet *packet;  /* the newly-allocated result packet */
 	struct header *sctp_header;  /* the SCTP header info */
diff --git a/gtests/net/packetdrill/sctp_packet.h b/gtests/net/packetdrill/sctp_packet.h
index b2e90e85..dfe4eba0 100644
--- a/gtests/net/packetdrill/sctp_packet.h
+++ b/gtests/net/packetdrill/sctp_packet.h
@@ -29,6 +29,33 @@
 #include "packet.h"
 #include "sctp.h"
 
+struct sctp_sack_block_list_item {
+	struct sctp_sack_block_list_item *next;
+	union sctp_sack_block block;
+};
+
+struct sctp_sack_block_list {
+	struct sctp_sack_block_list_item *first;
+	struct sctp_sack_block_list_item *last;
+	u16 nr_entries;
+};
+
+struct sctp_sack_block_list *
+sctp_sack_block_list_new(void);
+
+void
+sctp_sack_block_list_append(struct sctp_sack_block_list *list,
+			    struct sctp_sack_block_list_item *item);
+
+void
+sctp_sctp_sack_block_list_free(struct sctp_sack_block_list *list);
+
+struct sctp_sack_block_list_item *
+sctp_sack_block_list_item_gap_new(u16 start, u16 end);
+
+struct sctp_sack_block_list_item *
+sctp_sack_block_list_item_dup_new(u32 tsn);
+
 struct sctp_chunk_list_item {
 	struct sctp_chunk_list_item *next;
 	struct sctp_chunk *chunk;
@@ -81,7 +108,9 @@ sctp_init_ack_chunk_new(s64 tag, s64 a_rwnd, s64 os, s64 is, s64 tsn);
 #define FLAG_SACK_CHUNK_DUP_TSNS_NOCHECK        0x00000800
 
 struct sctp_chunk_list_item *
-sctp_sack_chunk_new(s64 cum_tsn, s64 a_rwnd);
+sctp_sack_chunk_new(s64 cum_tsn, s64 a_rwnd,
+                    struct sctp_sack_block_list *gaps,
+                    struct sctp_sack_block_list *dups);
 
 struct sctp_chunk_list_item *
 sctp_heartbeat_chunk_new(u8 flags);
-- 
GitLab