From 31899e086f18a71be6d1e523b6afec23cf056e89 Mon Sep 17 00:00:00 2001
From: Michael Tuexen <tuexen@fh-muenster.de>
Date: Sat, 11 Jul 2015 16:32:11 +0200
Subject: [PATCH] Add support for a generic chunk.

This fixes https://github.com/nplab/packetdrill/issues/18.
---
 gtests/net/packetdrill/lexer.l       |   2 +
 gtests/net/packetdrill/parser.y      |  90 ++++++++++++++---
 gtests/net/packetdrill/sctp_packet.c | 140 ++++++++++++++++++++++++++-
 gtests/net/packetdrill/sctp_packet.h |  32 +++++-
 4 files changed, 247 insertions(+), 17 deletions(-)

diff --git a/gtests/net/packetdrill/lexer.l b/gtests/net/packetdrill/lexer.l
index e462478e..ecf60c4d 100644
--- a/gtests/net/packetdrill/lexer.l
+++ b/gtests/net/packetdrill/lexer.l
@@ -212,6 +212,7 @@ sinit_num_ostreams	return SINIT_NUM_OSTREAMS;
 sinit_max_instreams	return SINIT_MAX_INSTREAMS;
 sinit_max_attempts	return SINIT_MAX_ATTEMPTS;
 sinit_max_init_timeo	return SINIT_MAX_INIT_TIMEO;
+CHUNK			return CHUNK;
 DATA			return DATA;
 INIT			return INIT;
 INIT_ACK		return INIT_ACK;
@@ -228,6 +229,7 @@ ECNE			return ECNE;
 CWR			return CWR;
 SHUTDOWN_COMPLETE	return SHUTDOWN_COMPLETE;
 PAD			return PAD;
+type			return TYPE;
 flgs			return FLAGS;
 len			return LEN;
 tag			return TAG;
diff --git a/gtests/net/packetdrill/parser.y b/gtests/net/packetdrill/parser.y
index da6c940c..f11d5410 100644
--- a/gtests/net/packetdrill/parser.y
+++ b/gtests/net/packetdrill/parser.y
@@ -466,6 +466,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_byte_list_item *byte_list_item;
+	struct sctp_byte_list *byte_list;
 	struct sctp_sack_block_list_item *sack_block_list_item;
 	struct sctp_sack_block_list *sack_block_list;
 	struct sctp_address_type_list_item *address_type_list_item;
@@ -501,10 +503,11 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %token <reserved> SINIT_MAX_INIT_TIMEO
 %token <reserved> ASSOC_VALUE
 %token <reserved> SACK_DELAY SACK_FREQ
-%token <reserved> DATA INIT INIT_ACK HEARTBEAT HEARTBEAT_ACK ABORT
+%token <reserved> CHUNK DATA INIT INIT_ACK HEARTBEAT HEARTBEAT_ACK ABORT
 %token <reserved> SHUTDOWN SHUTDOWN_ACK ERROR COOKIE_ECHO COOKIE_ACK ECNE CWR
 %token <reserved> SHUTDOWN_COMPLETE PAD
-%token <reserved> FLAGS LEN TAG A_RWND OS IS TSN SID SSN PPID CUM_TSN GAPS DUPS
+%token <reserved> TYPE FLAGS LEN
+%token <reserved> TAG A_RWND OS IS TSN SID SSN PPID CUM_TSN GAPS DUPS
 %token <reserved> HEARTBEAT_INFORMATION IPV4_ADDRESS IPV6_ADDRESS STATE_COOKIE
 %token <reserved> UNRECOGNIZED_PARAMETER COOKIE_PRESERVATIVE HOSTNAME_ADDRESS
 %token <reserved> SUPPORTED_ADDRESS_TYPES ECN_CAPABLE
@@ -550,6 +553,7 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %type <errno_info> opt_errno
 %type <chunk_list> sctp_chunk_list_spec
 %type <chunk_list_item> sctp_chunk_spec
+%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
@@ -574,11 +578,12 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %type <parameter_list_item> sctp_supported_address_types_parameter_spec
 %type <parameter_list_item> sctp_ecn_capable_parameter_spec
 %type <parameter_list_item> sctp_pad_parameter_spec
-
-%type <integer> opt_flags opt_data_flags opt_abort_flags
+%type <integer> opt_chunk_type opt_flags opt_data_flags opt_abort_flags
 %type <integer> opt_shutdown_complete_flags opt_chunk_len
 %type <integer> opt_tag opt_a_rwnd opt_os opt_is opt_tsn opt_sid opt_ssn
 %type <integer> opt_cum_tsn opt_ppid
+%type <byte_list> opt_val byte_list
+%type <byte_list_item> byte
 %type <sack_block_list> opt_gaps gap_list opt_dups dup_list
 %type <sack_block_list_item> gap dup
 %type <address_type_list> address_types_list
@@ -774,7 +779,8 @@ sctp_chunk_list_spec
 ;
 
 sctp_chunk_spec
-: sctp_data_chunk_spec              { $$ = $1; }
+: sctp_generic_chunk_spec           { $$ = $1; }
+| sctp_data_chunk_spec              { $$ = $1; }
 | sctp_init_chunk_spec              { $$ = $1; }
 | sctp_init_ack_chunk_spec          { $$ = $1; }
 | sctp_sack_chunk_spec              { $$ = $1; }
@@ -792,13 +798,19 @@ sctp_chunk_spec
 | sctp_pad_chunk_spec               { $$ = $1; }
 ;
 
-opt_chunk_len
-: LEN '=' ELLIPSIS { $$ = -1; }
-| LEN '=' INTEGER  {
-	if (!is_valid_u16($3)) {
-		semantic_error("length value out of range");
+opt_chunk_type
+: TYPE '=' ELLIPSIS    { $$ = -1; }
+| TYPE '=' HEX_INTEGER {
+	if (!is_valid_u8($3)) {
+		semantic_error("type value out of range");
         }
-        $$ = $3;
+	$$ = $3;
+}
+| TYPE '=' INTEGER     {
+	if (!is_valid_u8($3)) {
+		semantic_error("type value out of range");
+        }
+	$$ = $3;
 }
 ;
 
@@ -818,6 +830,45 @@ opt_flags
 }
 ;
 
+opt_chunk_len
+: LEN '=' ELLIPSIS { $$ = -1; }
+| LEN '=' INTEGER  {
+	if (!is_valid_u16($3)) {
+		semantic_error("length value out of range");
+        }
+        $$ = $3;
+}
+;
+
+opt_val
+: VAL '=' ELLIPSIS          { $$ = NULL; }
+| VAL '=' '[' ELLIPSIS ']'  { $$ = NULL; }
+| VAL '=' '[' byte_list ']' { $$ = $4; }
+;
+
+byte_list
+:                    { $$ = sctp_byte_list_new(); }
+| byte               { $$ = sctp_byte_list_new();
+                       sctp_byte_list_append($$, $1); }
+| byte_list ',' byte { $$ = $1;
+                       sctp_byte_list_append($1, $3); }
+;
+
+byte
+: HEX_INTEGER {
+	if (!is_valid_u8($1)) {
+		semantic_error("byte value out of range");
+	}
+	$$ = sctp_byte_list_item_new($1);
+}
+| INTEGER {
+	if (!is_valid_u8($1)) {
+		semantic_error("byte value out of range");
+	}
+	$$ = sctp_byte_list_item_new($1);
+}
+;
+
 opt_data_flags
 : FLAGS '=' ELLIPSIS    { $$ = -1; }
 | FLAGS '=' HEX_INTEGER {
@@ -924,7 +975,7 @@ opt_shutdown_complete_flags
 | FLAGS '=' INTEGER     {
 	if (!is_valid_u8($3)) {
 		semantic_error("flags value out of range");
-        }
+	}
 	$$ = $3;
 }
 | FLAGS '=' WORD        {
@@ -1089,6 +1140,21 @@ dup
 }
 ;
 
+sctp_generic_chunk_spec
+: CHUNK '[' opt_chunk_type ',' opt_flags ',' opt_chunk_len ',' opt_val ']' {
+	if (($7 != -1) && ($7 < sizeof(struct sctp_chunk))) {
+		semantic_error("length value out of range");
+	}
+	if (($7 != -1) && ($9 != NULL) &&
+	    ($7 != sizeof(struct sctp_chunk) + $9->nr_entries)) {
+		semantic_error("length value incompatible with val");
+	}
+	if (($7 == -1) && ($9 != NULL)) {
+		semantic_error("length needs to be specified");
+	}
+	$$ = sctp_generic_chunk_new($3, $5, $7, $9);
+}
+
 sctp_data_chunk_spec
 : DATA '[' opt_data_flags ',' opt_chunk_len ',' opt_tsn ',' opt_sid ',' opt_ssn ',' opt_ppid ']' {
 	if (($5 != -1) && ($5 < sizeof(struct sctp_data_chunk))) {
diff --git a/gtests/net/packetdrill/sctp_packet.c b/gtests/net/packetdrill/sctp_packet.c
index f3eec362..4ca10243 100644
--- a/gtests/net/packetdrill/sctp_packet.c
+++ b/gtests/net/packetdrill/sctp_packet.c
@@ -29,10 +29,72 @@
 
 /*
  * ToDo:
- * - Add support for parameters (fix hard coded state cookie in INIT-ACK)
  * - Add support for error causes
  */
 
+struct sctp_byte_list *
+sctp_byte_list_new(void)
+{
+	struct sctp_byte_list *list;
+
+	list = malloc(sizeof(struct sctp_byte_list));
+	assert(list != NULL);
+	list->first = NULL;
+	list->last = NULL;
+	list->nr_entries = 0;
+	return list;
+}
+
+void
+sctp_byte_list_append(struct sctp_byte_list *list,
+                      struct sctp_byte_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_byte_list_free(struct sctp_byte_list *list)
+{
+	struct sctp_byte_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_byte_list_item *
+sctp_byte_list_item_new(u8 byte)
+{
+	struct sctp_byte_list_item *item;
+
+	item = malloc(sizeof(struct sctp_byte_list_item));
+	assert(item != NULL);
+	item->next = NULL;
+	item->byte = byte;
+	return item;
+}
+
 struct sctp_sack_block_list *
 sctp_sack_block_list_new(void)
 {
@@ -188,6 +250,64 @@ sctp_chunk_list_item_new(struct sctp_chunk *chunk, u32 length, u32 flags,
 	return item;
 }
 
+struct sctp_chunk_list_item *
+sctp_generic_chunk_new(s64 type, s64 flgs, s64 len,
+                       struct sctp_byte_list *bytes)
+{
+	struct sctp_chunk *chunk;
+	struct sctp_byte_list_item *item;
+	u32 flags;
+	u16 length, padding_length, i;
+
+	flags = 0;
+	if (bytes == NULL) {
+		flags |= FLAG_CHUNK_LENGTH_NOCHECK;
+		flags |= FLAG_CHUNK_VALUE_NOCHECK;
+	}
+	if (len == -1) {
+		length = (u16)sizeof(struct sctp_chunk);
+	} else {
+		length = (u16)len;
+	}
+	padding_length = length % 4;
+	if (padding_length > 0) {
+		padding_length = 4 - padding_length;
+	}
+	chunk = malloc(length + padding_length);
+	assert(chunk != NULL);
+	if (type == -1) {
+		chunk->type = 0;
+		flags |= FLAG_CHUNK_TYPE_NOCHECK;
+	} else {
+		chunk->type = (u8)type;
+	}
+	if (flgs == -1) {
+		chunk->flags = 0;
+		flags |= FLAG_CHUNK_FLAGS_NOCHECK;
+	} else {
+		 chunk->flags = (u8)flgs;
+	}
+	chunk->length = htons(length);
+	if (len == -1) {
+		flags |= FLAG_CHUNK_LENGTH_NOCHECK;
+		flags |= FLAG_CHUNK_VALUE_NOCHECK;
+	}
+	if (bytes != NULL) {
+		for (i = 0, item = bytes->first;
+		     item != NULL;
+		     i++, item = item->next) {
+			chunk->value[i] = item->byte;
+		}
+	} else {
+		memset(chunk->value, 0, length - sizeof(struct sctp_chunk));
+	}
+	memset(chunk->value + (length - sizeof(struct sctp_chunk)),
+	       0, padding_length);
+	return sctp_chunk_list_item_new(chunk,
+	                                length + padding_length,
+	                                flags, sctp_parameter_list_new());
+}
+
 struct sctp_chunk_list_item *
 sctp_data_chunk_new(s64 flgs, s64 len, s64 tsn, s64 sid, s64 ssn, s64 ppid)
 {
@@ -1535,8 +1655,22 @@ new_sctp_packet(int address_family,
 				}
 				break;
 			default:
-				asprintf(error, "Unknown chunk type 0x%02x", chunk_item->chunk->type);
-				return NULL;
+				if (chunk_item->flags & FLAG_CHUNK_TYPE_NOCHECK) {
+					asprintf(error,
+						 "chunk type must be specified for inbound packets");
+					return NULL;
+				}
+				if (chunk_item->flags & FLAG_CHUNK_FLAGS_NOCHECK) {
+					asprintf(error,
+						 "chunk flags must be specified for inbound packets");
+					return NULL;
+				}
+				if (chunk_item->flags & FLAG_CHUNK_LENGTH_NOCHECK) {
+					asprintf(error,
+						 "chunk length must be specified for inbound packets");
+					return NULL;
+				}
+				break;
 			}
 		}
 	}
diff --git a/gtests/net/packetdrill/sctp_packet.h b/gtests/net/packetdrill/sctp_packet.h
index 51b0bcc9..b8cc7ef1 100644
--- a/gtests/net/packetdrill/sctp_packet.h
+++ b/gtests/net/packetdrill/sctp_packet.h
@@ -29,6 +29,30 @@
 #include "packet.h"
 #include "sctp.h"
 
+struct sctp_byte_list_item {
+	struct sctp_byte_list_item *next;
+	u8 byte;
+};
+
+struct sctp_byte_list {
+	struct sctp_byte_list_item *first;
+	struct sctp_byte_list_item *last;
+	u16 nr_entries;
+};
+
+struct sctp_byte_list *
+sctp_byte_list_new(void);
+
+void
+sctp_byte_list_append(struct sctp_byte_list *list,
+                      struct sctp_byte_list_item *item);
+
+void
+sctp_byte_list_free(struct sctp_byte_list *list);
+
+struct sctp_byte_list_item *
+sctp_byte_list_item_new(u8 byte);
+
 struct sctp_sack_block_list_item {
 	struct sctp_sack_block_list_item *next;
 	union sctp_sack_block block;
@@ -113,14 +137,18 @@ struct sctp_chunk_list {
 	u32 length;
 };
 
+struct sctp_chunk_list_item *
+sctp_chunk_list_item_new(struct sctp_chunk *chunk, u32 length, u32 flags,
+                         struct sctp_parameter_list *list);
+
 #define FLAG_CHUNK_TYPE_NOCHECK                 0x00000001
 #define FLAG_CHUNK_FLAGS_NOCHECK                0x00000002
 #define FLAG_CHUNK_LENGTH_NOCHECK               0x00000004
 #define FLAG_CHUNK_VALUE_NOCHECK                0x00000008
 
 struct sctp_chunk_list_item *
-sctp_chunk_list_item_new(struct sctp_chunk *chunk, u32 length, u32 flags,
-                         struct sctp_parameter_list *list);
+sctp_generic_chunk_new(s64 type, s64 flgs, s64 len,
+                       struct sctp_byte_list *bytes);
 
 #define FLAG_DATA_CHUNK_TSN_NOCHECK             0x00000100
 #define FLAG_DATA_CHUNK_SID_NOCHECK             0x00000200
-- 
GitLab