diff --git a/gtests/net/packetdrill/Makefile.common b/gtests/net/packetdrill/Makefile.common
index e58ab8540bbe7e2b52732fb2d6ad1534c6fc8235..6490f46f20c496dfb9a22d253b72beb576ec949d 100644
--- a/gtests/net/packetdrill/Makefile.common
+++ b/gtests/net/packetdrill/Makefile.common
@@ -20,7 +20,7 @@ packetdrill-lib := \
          symbols_openbsd.o \
          symbols_netbsd.o \
          gre_packet.o icmp_packet.o ip_packet.o \
-         tcp_packet.o udp_packet.o udplite_packet.o \
+         sctp_packet.o tcp_packet.o udp_packet.o udplite_packet.o \
          mpls_packet.o \
          run.o run_command.o run_packet.o run_system_call.o \
          script.o socket.o system.o \
diff --git a/gtests/net/packetdrill/lexer.l b/gtests/net/packetdrill/lexer.l
index cc57de998593a6e2015746863a668ea865b01cc5..d1d3c111bcf751317b16271ad978698124443a6e 100644
--- a/gtests/net/packetdrill/lexer.l
+++ b/gtests/net/packetdrill/lexer.l
@@ -174,6 +174,7 @@ htons			return _HTONS_;
 ipv4			return IPV4;
 ipv6			return IPV6;
 icmp			return ICMP;
+sctp			return SCTP;
 udp			return UDP;
 udplite			return UDPLITE;
 gre			return GRE;
@@ -211,6 +212,29 @@ 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;
+DATA			return DATA;
+INIT			return INIT;
+INIT_ACK		return INIT_ACK;
+SACK			return SACK;
+HEARTBEAT		return HEARTBEAT;
+HEARTBEAT_ACK		return HEARTBEAT_ACK;
+ABORT			return ABORT;
+SHUTDOWN		return SHUTDOWN;
+SHUTDOWN_ACK		return SHUTDOWN_ACK;
+ERROR			return ERROR;
+COOKIE_ECHO		return COOKIE_ECHO;
+COOKIE_ACK		return COOKIE_ACK;
+ECNE			return ECNE;
+CWR			return CWR;
+SHUTDOWN_COMPLETE	return SHUTDOWN_COMPLETE;
+tag			return TAG;
+a_rwnd			return A_RWND;
+is			return IS;
+os			return OS;
+tsn			return TSN;
+sid			return SID;
+ssn			return SSN;
+ppid			return PPID;
 --[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/netdev.c b/gtests/net/packetdrill/netdev.c
index 4c1f1ce8038e88d87892774f3988f2e74ee3bc4d..a4e82e5d61cbc10bf41fdcfc1444f703526b584b 100644
--- a/gtests/net/packetdrill/netdev.c
+++ b/gtests/net/packetdrill/netdev.c
@@ -347,8 +347,8 @@ static int local_netdev_send(struct netdev *a_netdev,
 	assert(packet->ip_bytes > 0);
 	/* We do IPv4 and IPv6 */
 	assert(packet->ipv4 || packet->ipv6);
-	/* We only do TCP, UDP, UDPLite and ICMP */
-	assert(packet->tcp || packet->udp || packet->udplite ||
+	/* We only do SCTP, TCP, UDP, UDPLite and ICMP */
+	assert(packet->sctp || packet->tcp || packet->udp || packet->udplite ||
 	       packet->icmpv4 || packet->icmpv6);
 
 	DEBUGP("local_netdev_send\n");
diff --git a/gtests/net/packetdrill/packet.c b/gtests/net/packetdrill/packet.c
index dbb33b177c743bdfcb7cc98b53893a34f4bd4398..19b9e6a7b048239443f4c6178ee15ceaa5c9bca0 100644
--- a/gtests/net/packetdrill/packet.c
+++ b/gtests/net/packetdrill/packet.c
@@ -32,6 +32,7 @@
 #include "gre_packet.h"
 #include "ip_packet.h"
 #include "mpls_packet.h"
+#include "sctp_packet.h"
 
 
 /* Info for all types of header we support. */
@@ -54,11 +55,13 @@ struct packet *packet_new(u32 buffer_bytes)
 	struct packet *packet = calloc(1, sizeof(struct packet));
 	packet->buffer = malloc(buffer_bytes);
 	packet->buffer_bytes = buffer_bytes;
+	packet->chunk_list = sctp_chunk_list_new();
 	return packet;
 }
 
 void packet_free(struct packet *packet)
 {
+	sctp_chunk_list_free(packet->chunk_list);
 	free(packet->buffer);
 	memset(packet, 0, sizeof(*packet));  /* paranoia to help catch bugs */
 	free(packet);
@@ -142,9 +145,11 @@ static struct packet *packet_copy_with_headroom(struct packet *old_packet,
 	const int bytes_used = packet_end(old_packet) - old_packet->buffer;
 	assert(bytes_used >= 0);
 	assert(bytes_used <= 128*1024);
-	struct packet *packet = packet_new(bytes_headroom + bytes_used);
+	struct packet *packet = packet_new(max(bytes_headroom + bytes_used, old_packet->buffer_bytes));
 	u8 *old_base = old_packet->buffer;
 	u8 *new_base = packet->buffer + bytes_headroom;
+	struct sctp_chunk_list_item *old_item, *new_item;
+	struct sctp_chunk *new_chunk;
 
 	memcpy(new_base, old_base, bytes_used);
 
@@ -159,6 +164,7 @@ static struct packet *packet_copy_with_headroom(struct packet *old_packet,
 	/* Set up layer 3 header pointer. */
 	packet->ipv4	= offset_ptr(old_base, new_base, old_packet->ipv4);
 	packet->ipv6	= offset_ptr(old_base, new_base, old_packet->ipv6);
+	/* Set up layer 4 header pointer. */
 	packet->sctp	= offset_ptr(old_base, new_base, old_packet->sctp);
 	packet->tcp	= offset_ptr(old_base, new_base, old_packet->tcp);
 	packet->udp	= offset_ptr(old_base, new_base, old_packet->udp);
@@ -166,6 +172,16 @@ static struct packet *packet_copy_with_headroom(struct packet *old_packet,
 	packet->icmpv4	= offset_ptr(old_base, new_base, old_packet->icmpv4);
 	packet->icmpv6	= offset_ptr(old_base, new_base, old_packet->icmpv6);
 
+	for (old_item = old_packet->chunk_list->first;
+	     old_item != NULL;
+	     old_item = old_item->next) {
+		new_chunk = offset_ptr(old_base, new_base, old_item->chunk);
+		new_item = sctp_chunk_list_item_new(new_chunk,
+		                                    old_item->length,
+		                                    old_item->flags);
+		sctp_chunk_list_append(packet->chunk_list, new_item);
+	}
+
 	packet->tcp_ts_val	= offset_ptr(old_base, new_base,
 					     old_packet->tcp_ts_val);
 	packet->tcp_ts_ecr	= offset_ptr(old_base, new_base,
diff --git a/gtests/net/packetdrill/packet.h b/gtests/net/packetdrill/packet.h
index 06ddf02dcdd4f5f1140e757c8297e6d94967f1d9..dfedcd8d5c9b9c0b1121b31810c242fe90232588 100644
--- a/gtests/net/packetdrill/packet.h
+++ b/gtests/net/packetdrill/packet.h
@@ -96,6 +96,7 @@ struct packet {
 	/* Layer 4 */
 	struct sctp_common_header *sctp;
 				/* start of SCTP common header, if present */
+	struct sctp_chunk_list *chunk_list;
 	struct tcp *tcp;	/* start of TCP header, if present */
 	struct udp *udp;	/* start of UDP header, if present */
 	struct udplite *udplite;/* start of UDPLite header, if present */
diff --git a/gtests/net/packetdrill/parser.y b/gtests/net/packetdrill/parser.y
index 32f18e9c6f4be6c0f1b2836a6642e070564b1ffa..b6b22e1ec3e723885420cdb903aeef4329adf9e1 100644
--- a/gtests/net/packetdrill/parser.y
+++ b/gtests/net/packetdrill/parser.y
@@ -97,6 +97,7 @@
 #include "logging.h"
 #include "mpls.h"
 #include "mpls_packet.h"
+#include "sctp_packet.h"
 #include "tcp_packet.h"
 #include "udp_packet.h"
 #include "udplite_packet.h"
@@ -462,6 +463,8 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 	struct option_list *option;
 	struct event *event;
 	struct packet *packet;
+	struct sctp_chunk_list_item *chunk_list_item;
+	struct sctp_chunk_list *chunk_list;
 	struct syscall_spec *syscall;
 	struct command_spec *command;
 	struct code_spec *code;
@@ -483,7 +486,7 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %token <reserved> ACK ECR EOL MSS NOP SACK SACKOK TIMESTAMP VAL WIN WSCALE PRO
 %token <reserved> FAST_OPEN
 %token <reserved> ECT0 ECT1 CE ECT01 NO_ECN
-%token <reserved> IPV4 IPV6 ICMP UDP UDPLITE GRE MTU
+%token <reserved> IPV4 IPV6 ICMP SCTP UDP UDPLITE GRE MTU
 %token <reserved> MPLS LABEL TC TTL
 %token <reserved> OPTION
 %token <reserved> SRTO_INITIAL SRTO_MAX SRTO_MIN
@@ -491,6 +494,10 @@ 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> 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 <floating> FLOAT
 %token <integer> INTEGER HEX_INTEGER
 %token <string> WORD STRING BACK_QUOTED CODE IPV4_ADDR IPV6_ADDR
@@ -500,7 +507,9 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %type <option> option options opt_options
 %type <event> event events event_time action
 %type <time_usecs> time opt_end_time
-%type <packet> packet_spec tcp_packet_spec udp_packet_spec udplite_packet_spec
+%type <packet> packet_spec
+%type <packet> sctp_packet_spec tcp_packet_spec
+%type <packet> udp_packet_spec udplite_packet_spec
 %type <packet> icmp_packet_spec
 %type <packet> packet_prefix
 %type <syscall> syscall_spec
@@ -527,6 +536,20 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %type <expression> inaddr sockaddr msghdr iovec pollfd opt_revents linger
 %type <expression> sctp_rtoinfo sctp_initmsg sctp_assocval sctp_sackinfo
 %type <errno_info> opt_errno
+%type <chunk_list> sctp_chunk_list_spec
+%type <chunk_list_item> sctp_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_heartbeat_chunk_spec sctp_heartbeat_ack_chunk_spec
+%type <chunk_list_item> sctp_abort_chunk_spec
+%type <chunk_list_item> sctp_shutdown_chunk_spec
+%type <chunk_list_item> sctp_shutdown_ack_chunk_spec
+%type <chunk_list_item> sctp_error_chunk_spec
+%type <chunk_list_item> sctp_cookie_echo_chunk_spec sctp_cookie_ack_chunk_spec
+%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
 
 %%  /* The grammar follows. */
 
@@ -684,12 +707,207 @@ action
 ;
 
 packet_spec
-: tcp_packet_spec     { $$ = $1; }
+: sctp_packet_spec    { $$ = $1; }
+| tcp_packet_spec     { $$ = $1; }
 | udp_packet_spec     { $$ = $1; }
 | udplite_packet_spec { $$ = $1; }
 | icmp_packet_spec    { $$ = $1; }
 ;
 
+sctp_packet_spec
+: packet_prefix opt_ip_info SCTP ':' sctp_chunk_list_spec {
+	char *error = NULL;
+	struct packet *outer = $1, *inner = NULL;
+	enum direction_t direction = outer->direction;
+
+	inner = new_sctp_packet(in_config->wire_protocol, direction, $2, $5,
+				&error);
+	if (inner == NULL) {
+		assert(error != NULL);
+		semantic_error(error);
+		free(error);
+	}
+
+	$$ = packet_encapsulate_and_free(outer, inner);
+}
+;
+
+sctp_chunk_list_spec
+:                                          { $$ = sctp_chunk_list_new(); }
+| sctp_chunk_spec                          { $$ = sctp_chunk_list_new();
+                                             sctp_chunk_list_append($$, $1); }
+| sctp_chunk_list_spec ';' sctp_chunk_spec { $$ = $1;
+                                             sctp_chunk_list_append($1, $3); }
+;
+
+sctp_chunk_spec
+: sctp_data_chunk_spec              { $$ = $1; }
+| sctp_init_chunk_spec              { $$ = $1; }
+| sctp_init_ack_chunk_spec          { $$ = $1; }
+| sctp_sack_chunk_spec              { $$ = $1; }
+| sctp_heartbeat_chunk_spec         { $$ = $1; }
+| sctp_heartbeat_ack_chunk_spec     { $$ = $1; }
+| sctp_abort_chunk_spec             { $$ = $1; }
+| sctp_shutdown_chunk_spec          { $$ = $1; }
+| sctp_shutdown_ack_chunk_spec      { $$ = $1; }
+| sctp_error_chunk_spec             { $$ = $1; }
+| sctp_cookie_echo_chunk_spec       { $$ = $1; }
+| sctp_cookie_ack_chunk_spec        { $$ = $1; }
+| sctp_ecne_chunk_spec              { $$ = $1; }
+| sctp_cwr_chunk_spec               { $$ = $1; }
+| sctp_shutdown_complete_chunk_spec { $$ = $1; }
+;
+
+opt_a_rwnd
+:		{ $$ = -1; }
+| A_RWND '=' INTEGER	{
+	if (!is_valid_u32($3)) {
+		semantic_error("a_rwnd value out of range");
+	}
+	$$ = $3;
+}
+
+opt_os
+:		{ $$ = -1; }
+| OS '=' INTEGER	{
+	if (!is_valid_u16($3)) {
+		semantic_error("os value out of range");
+	}
+	$$ = $3;
+}
+
+opt_is
+:		{ $$ = -1; }
+| IS '=' INTEGER	{
+	if (!is_valid_u16($3)) {
+		semantic_error("is value out of range");
+	}
+	$$ = $3;
+}
+
+opt_tsn
+:		{ $$ = -1; }
+| TSN '=' INTEGER	{
+	if (!is_valid_u32($3)) {
+		semantic_error("tsn value out of range");
+	}
+	$$ = $3;
+}
+
+opt_sid
+:		{ $$ = -1; }
+| SID '=' INTEGER	{
+	if (!is_valid_u16($3)) {
+		semantic_error("sid value out of range");
+	}
+	$$ = $3;
+}
+
+opt_ssn
+:		{ $$ = -1; }
+| SSN '=' INTEGER	{
+	if (!is_valid_u16($3)) {
+		semantic_error("ssn value out of range");
+	}
+	$$ = $3;
+}
+
+opt_ppid
+:		{ $$ = -1; }
+| PPID '=' INTEGER	{
+	if (!is_valid_u32($3)) {
+		semantic_error("ppid value out of range");
+	}
+	$$ = $3;
+}
+
+sctp_data_chunk_spec
+: DATA '[' opt_tsn opt_sid opt_ssn opt_ppid ']' {
+	$$ = sctp_data_chunk_new($3, $4, $5, $6);
+}
+
+sctp_init_chunk_spec
+: INIT '[' TAG '=' INTEGER opt_a_rwnd opt_os opt_is TSN '=' INTEGER ']' {
+	if (!is_valid_u32($5)) {
+		semantic_error("tag value out of range");
+	}
+	if (!is_valid_u32($11)) {
+		semantic_error("tsn value out of range");
+	}
+	$$ = sctp_init_chunk_new($5, $6, $7, $8, $11);
+}
+
+sctp_init_ack_chunk_spec
+: INIT_ACK '[' TAG '=' INTEGER opt_a_rwnd opt_os opt_is TSN '=' INTEGER ']' {
+	if (!is_valid_u32($5)) {
+		semantic_error("tag value out of range");
+	}
+	if (!is_valid_u32($11)) {
+		semantic_error("tsn value out of range");
+	}
+	$$ = sctp_init_ack_chunk_new($5, $6, $7, $8, $11);
+}
+
+sctp_sack_chunk_spec
+: SACK '[' opt_tsn opt_a_rwnd']' {
+	$$ = sctp_sack_chunk_new($3, $4);
+}
+
+sctp_heartbeat_chunk_spec
+: HEARTBEAT '[' ']' {
+	$$ = sctp_heartbeat_chunk_new(0);
+}
+
+sctp_heartbeat_ack_chunk_spec
+: HEARTBEAT_ACK '[' ']' {
+	$$ = sctp_heartbeat_ack_chunk_new(0);
+}
+
+sctp_abort_chunk_spec
+: ABORT '[' ']' {
+	$$ = sctp_abort_chunk_new(0);
+}
+
+sctp_shutdown_chunk_spec
+: SHUTDOWN '[' opt_tsn ']' {
+	$$ = sctp_shutdown_chunk_new($3);
+}
+
+sctp_shutdown_ack_chunk_spec
+: SHUTDOWN_ACK '[' ']' {
+	$$ = sctp_shutdown_ack_chunk_new(0);
+}
+
+sctp_error_chunk_spec
+: ERROR '[' ']' {
+	$$ = sctp_error_chunk_new(0);
+}
+
+sctp_cookie_echo_chunk_spec
+: COOKIE_ECHO '[' ']' {
+	$$ = sctp_cookie_echo_chunk_new(0);
+}
+
+sctp_cookie_ack_chunk_spec
+: COOKIE_ACK '[' ']' {
+	$$ = sctp_cookie_ack_chunk_new(0);
+}
+
+sctp_ecne_chunk_spec
+: ECNE '[' opt_tsn ']' {
+	$$ = sctp_ecne_chunk_new($3);
+}
+
+sctp_cwr_chunk_spec
+: CWR '[' opt_tsn ']' {
+	$$ = sctp_cwr_chunk_new($3);
+}
+
+sctp_shutdown_complete_chunk_spec
+: SHUTDOWN_COMPLETE '[' ']' {
+	$$ = sctp_shutdown_complete_chunk_new(0);
+}
+
 tcp_packet_spec
 : packet_prefix opt_ip_info flags seq opt_ack opt_window opt_tcp_options {
 	char *error = NULL;
diff --git a/gtests/net/packetdrill/run.c b/gtests/net/packetdrill/run.c
index a3260197881ee8e4134ca43a8d61b5b2fb156bd5..3edfb49b859895946082a7582a4fb227652f9992 100644
--- a/gtests/net/packetdrill/run.c
+++ b/gtests/net/packetdrill/run.c
@@ -108,6 +108,11 @@ static void close_all_sockets(struct state *state)
 		    reset_connection(state, socket)) {
 			die("error reseting connection\n");
 		}
+		if (socket->protocol == IPPROTO_SCTP &&
+		    !state->config->is_wire_client &&
+		    abort_association(state, socket)) {
+			die("error aborting association\n");
+		}
 		struct socket *dead_socket = socket;
 		socket = socket->next;
 		socket_free(dead_socket);
diff --git a/gtests/net/packetdrill/run_packet.c b/gtests/net/packetdrill/run_packet.c
index 070a184bc831cb8e3084959d9085e83c9170cc4c..08e368296b09e4af0b4866938697b9c6969f7dec 100644
--- a/gtests/net/packetdrill/run_packet.c
+++ b/gtests/net/packetdrill/run_packet.c
@@ -39,6 +39,8 @@
 #include "packet_to_string.h"
 #include "run.h"
 #include "script.h"
+#include "sctp_iterator.h"
+#include "sctp_packet.h"
 #include "tcp_options_iterator.h"
 #include "tcp_options_to_string.h"
 #include "tcp_packet.h"
@@ -146,6 +148,7 @@ static struct socket *find_socket_for_live_packet(
 	enum direction_t *direction)
 {
 	struct socket *socket = state->socket_under_test;	/* shortcut */
+
 	if (socket == NULL)
 		return NULL;
 
@@ -188,6 +191,8 @@ static struct socket *handle_listen_for_script_packet(
 	 */
 	struct config *config = state->config;
 	struct socket *socket = state->socket_under_test;	/* shortcut */
+	struct sctp_init_chunk *init;
+	struct sctp_chunk_list_item *item;
 
 	bool match = (direction == DIRECTION_INBOUND);
 	if (!match)
@@ -206,6 +211,17 @@ static struct socket *handle_listen_for_script_packet(
 	if (!match)
 		return NULL;
 
+	if (packet->sctp != NULL) {
+		assert(packet->chunk_list != NULL);
+		item = packet->chunk_list->first;
+		if ((item != NULL) &&
+		    (item->chunk->type == SCTP_INIT_CHUNK_TYPE)) {
+			init = (struct sctp_init_chunk *)item->chunk;
+		} else {
+			return NULL;
+		}
+	}
+
 	/* Create a child passive socket for this incoming SYN packet.
 	 * Any further packets in the test script will be directed to
 	 * this child socket.
@@ -222,7 +238,12 @@ static struct socket *handle_listen_for_script_packet(
 	get_packet_tuple(packet, &tuple);
 	socket->script.remote		= tuple.src;
 	socket->script.local		= tuple.dst;
-	socket->script.remote_isn	= ntohl(packet->tcp->seq);
+	if (packet->tcp != NULL) {
+		socket->script.remote_isn = ntohl(packet->tcp->seq);
+	} else {
+		socket->script.remote_initiate_tag = ntohl(init->initiate_tag);
+		socket->script.remote_initial_tsn = ntohl(init->initial_tsn);
+	}
 	socket->script.fd		= -1;
 
 	/* Set up the live info for this socket based
@@ -232,7 +253,12 @@ static struct socket *handle_listen_for_script_packet(
 	socket->live.remote.port	= htons(next_ephemeral_port(state));
 	socket->live.local.ip		= config->live_local_ip;
 	socket->live.local.port		= htons(config->live_bind_port);
-	socket->live.remote_isn		= ntohl(packet->tcp->seq);
+	if (packet->tcp != NULL) {
+		socket->live.remote_isn = ntohl(packet->tcp->seq);
+	} else {
+		socket->live.remote_initiate_tag = ntohl(init->initiate_tag);
+		socket->live.remote_initial_tsn = ntohl(init->initial_tsn);
+	}
 	socket->live.fd			= -1;
 
 	if (DEBUG_LOGGING) {
@@ -244,7 +270,12 @@ static struct socket *handle_listen_for_script_packet(
 		DEBUGP("live: remote: %s.%d\n",
 		       ip_to_string(&socket->live.remote.ip, remote_string),
 		       ntohs(socket->live.remote.port));
-		DEBUGP("live: ISN: %u\n", socket->live.remote_isn);
+		if (packet->tcp != NULL) {
+			DEBUGP("live: ISN: %u\n", socket->live.remote_isn);
+		} else {
+			DEBUGP("live: initiate tag: %u\n", socket->live.remote_initiate_tag);
+			DEBUGP("live: initial tsn: %u\n", socket->live.remote_initial_tsn);
+		}
 	}
 
 	return socket;
@@ -266,9 +297,28 @@ static struct socket *handle_connect_for_script_packet(
 	 */
 	struct config *config = state->config;
 	struct socket *socket = state->socket_under_test;	/* shortcut */
+	struct sctp_init_chunk *init;
+	struct sctp_chunk_list_item *item;
+	bool match;
 
-	bool match = ((direction == DIRECTION_OUTBOUND) &&
-		      packet->tcp->syn && !packet->tcp->ack);
+	DEBUGP("handle_connect_for_script_packet\n");
+	assert(packet->tcp != NULL || packet->sctp != NULL);
+	if (direction != DIRECTION_OUTBOUND)
+		return NULL;
+	if (packet->tcp != NULL) {
+		match = (packet->tcp->syn && !packet->tcp->ack);
+	} else {
+		assert(packet->chunk_list != NULL);
+		item = packet->chunk_list->first;
+		if ((item != NULL) &&
+		    (item->chunk->type == SCTP_INIT_CHUNK_TYPE)) {
+			init = (struct sctp_init_chunk *)item->chunk;
+			match = true;
+		} else {
+			init = NULL;
+			match = false;
+		}
+	}
 	if (!match)
 		return NULL;
 
@@ -306,11 +356,17 @@ static struct socket *handle_connect_for_script_packet(
 	/* Fill in the new info about this connection. */
 	struct tuple tuple;
 	get_packet_tuple(packet, &tuple);
-	socket->state			= SOCKET_ACTIVE_SYN_SENT;
 	socket->script.remote		= tuple.dst;
 	socket->script.local		= tuple.src;
-	socket->script.local_isn	= ntohl(packet->tcp->seq);
-
+	if (packet->tcp) {
+		socket->state = SOCKET_ACTIVE_SYN_SENT;
+		socket->script.local_isn = ntohl(packet->tcp->seq);
+	} else {
+		DEBUGP("Moving socket in SOCKET_ACTIVE_INIT_SENT\n");
+		socket->state = SOCKET_ACTIVE_INIT_SENT;
+		socket->script.local_initial_tsn = ntohl(init->initial_tsn);
+		socket->script.local_initiate_tag = ntohl(init->initiate_tag);
+	}
 	return socket;
 }
 
@@ -319,7 +375,13 @@ static struct socket *find_connect_for_live_packet(
 	struct state *state, struct packet *packet,
 	enum direction_t *direction)
 {
+	struct sctp_chunks_iterator iter;
+	struct sctp_chunk *chunk;
+	struct sctp_init_chunk *init;
 	struct tuple tuple;
+	char *error;
+
+	DEBUGP("find_connect_for_live_packet\n");
 	get_packet_tuple(packet, &tuple);
 
 	*direction = DIRECTION_INVALID;
@@ -327,6 +389,14 @@ static struct socket *find_connect_for_live_packet(
 	if (!socket)
 		return NULL;
 
+	bool is_sctp_match =
+		(packet->sctp &&
+		 (socket->protocol == IPPROTO_SCTP) &&
+		 (socket->state == SOCKET_ACTIVE_INIT_SENT));
+	bool is_tcp_match =
+		(packet->tcp && packet->tcp->syn && !packet->tcp->ack &&
+		 (socket->protocol == IPPROTO_TCP) &&
+		 (socket->state == SOCKET_ACTIVE_SYN_SENT));
 	bool is_udp_match =
 		(packet->udp &&
 		 (socket->protocol == IPPROTO_UDP) &&
@@ -335,11 +405,8 @@ static struct socket *find_connect_for_live_packet(
 		(packet->udplite &&
 		 (socket->protocol == IPPROTO_UDPLITE) &&
 		 (socket->state == SOCKET_ACTIVE_CONNECTING));
-	bool is_tcp_match =
-		(packet->tcp && packet->tcp->syn && !packet->tcp->ack &&
-		 (socket->protocol == IPPROTO_TCP) &&
-		 (socket->state == SOCKET_ACTIVE_SYN_SENT));
-	if (!is_udp_match && !is_tcp_match && !is_udplite_match)
+	if (!is_sctp_match && !is_tcp_match &&
+	    !is_udp_match && !is_udplite_match)
 		return NULL;
 
 	if (!is_equal_ip(&tuple.dst.ip, &socket->live.remote.ip) ||
@@ -356,6 +423,17 @@ static struct socket *find_connect_for_live_packet(
 
 	if (packet->tcp)
 		socket->live.local_isn	= ntohl(packet->tcp->seq);
+	if (packet->sctp) {
+		error = NULL;
+		chunk = sctp_chunks_begin(packet, &iter, &error);
+		if ((error == NULL) &&
+		    (chunk != NULL) &&
+		    (chunk->type == SCTP_INIT_CHUNK_TYPE)) {
+			init = (struct sctp_init_chunk *)chunk;
+			socket->live.local_initiate_tag = ntohl(init->initiate_tag);
+			socket->live.local_initial_tsn = ntohl(init->initial_tsn);
+		}
+	}
 
 	return socket;
 }
@@ -482,6 +560,85 @@ static int map_inbound_icmp_packet(
 	return STATUS_ERR;
 }
 
+static int map_inbound_sctp_packet(
+	struct socket *socket, struct packet *live_packet, char **error)
+{
+	struct sctp_chunks_iterator iter;
+	struct sctp_chunk *chunk;
+	struct sctp_data_chunk *data;
+	struct sctp_init_chunk *init;
+	struct sctp_init_ack_chunk *init_ack;
+	struct sctp_sack_chunk *sack;
+	struct sctp_shutdown_chunk *shutdown;
+	struct sctp_ecne_chunk *ecne;
+	struct sctp_cwr_chunk *cwr;
+	u32 local_diff, remote_diff;
+	u16 nr_gap_blocks, nr_dup_tsns, i;
+
+	live_packet->sctp->v_tag = htonl(socket->live.local_initiate_tag);
+	for (chunk = sctp_chunks_begin(live_packet, &iter, error);
+	     chunk != NULL;
+	     chunk = sctp_chunks_next(&iter, error)) {
+		if (*error != NULL) {
+			return STATUS_ERR;
+		}
+		DEBUGP("live remote tsn %d, scripte remote tsn %d\n",
+		       socket->live.remote_initial_tsn, socket->script.remote_initial_tsn);
+		DEBUGP("live local tsn %d, scripte local tsn %d\n",
+		       socket->live.local_initial_tsn, socket->script.local_initial_tsn);
+		remote_diff = socket->live.remote_initial_tsn - socket->script.remote_initial_tsn;
+		local_diff = socket->live.local_initial_tsn - socket->script.local_initial_tsn;
+		switch (chunk->type) {
+		case SCTP_DATA_CHUNK_TYPE:
+			data = (struct sctp_data_chunk *)chunk;
+			data->tsn = htonl(ntohl(data->tsn) + remote_diff);
+			break;
+		case SCTP_INIT_CHUNK_TYPE:
+			init = (struct sctp_init_chunk *)chunk;
+			init->initial_tsn = htonl(ntohl(init->initial_tsn) + remote_diff);
+			/* XXX: Does this work in all cases? */
+			if (ntohl(init->initiate_tag) == socket->script.local_initiate_tag) {
+				init->initiate_tag = htonl(socket->live.local_initiate_tag);
+			}
+			break;
+		case SCTP_INIT_ACK_CHUNK_TYPE:
+			init_ack = (struct sctp_init_ack_chunk *)chunk;
+			init_ack->initial_tsn = htonl(ntohl(init_ack->initial_tsn) + remote_diff);
+			/* XXX: Does this work in all cases? */
+			if (ntohl(init_ack->initiate_tag) == socket->script.local_initiate_tag) {
+				init_ack->initiate_tag = htonl(socket->live.local_initiate_tag);
+			}
+			break;
+		case SCTP_SACK_CHUNK_TYPE:
+			sack = (struct sctp_sack_chunk *)chunk;
+			DEBUGP("Old SACK cum TSN %d\n", ntohl(sack->cum_tsn));
+			sack->cum_tsn = htonl(ntohl(sack->cum_tsn) + local_diff);
+			DEBUGP("New SACK cum TSN %d\n", ntohl(sack->cum_tsn));
+			nr_gap_blocks = ntohs(sack->nr_gap_blocks);
+			nr_dup_tsns = ntohs(sack->nr_dup_tsns);
+			for (i = 0; i < nr_dup_tsns; i++) {
+				sack->block[i].tsn = htonl(ntohl(sack->block[i].tsn) + local_diff);
+			}
+			break;
+		case SCTP_SHUTDOWN_CHUNK_TYPE:
+			shutdown = (struct sctp_shutdown_chunk *)chunk;
+			shutdown->cum_tsn = htonl(ntohl(shutdown->cum_tsn) + local_diff);
+			break;
+		case SCTP_ECNE_CHUNK_TYPE:
+			ecne = (struct sctp_ecne_chunk *)chunk;
+			ecne->lowest_tsn = htonl(ntohl(ecne->lowest_tsn) + local_diff);
+			break;
+		case SCTP_CWR_CHUNK_TYPE:
+			cwr = (struct sctp_cwr_chunk *)chunk;
+			cwr->lowest_tsn = htonl(ntohl(cwr->lowest_tsn) + local_diff);
+			break;
+		default:
+			break;
+		}
+	}
+	return STATUS_OK;
+}
+
 /* Rewrite the IP and TCP, UDP, or ICMP fields in 'live_packet', mapping
  * inbound packet values (address 4-tuple and sequence numbers in seq,
  * ACK, SACK blocks) from script values to live values, so that we can
@@ -502,6 +659,10 @@ static int map_inbound_packet(
 	if ((live_packet->icmpv4 != NULL) || (live_packet->icmpv6 != NULL))
 		return map_inbound_icmp_packet(socket, live_packet, error);
 
+	if (live_packet->sctp) {
+		return map_inbound_sctp_packet(socket, live_packet, error);
+	}
+
 	/* If no TCP headers to rewrite, then we're done. */
 	if (live_packet->tcp == NULL)
 		return STATUS_OK;
@@ -547,6 +708,85 @@ static int map_inbound_packet(
 	return STATUS_OK;
 }
 
+static int map_outbound_live_sctp_packet(
+	struct socket *socket,
+	struct packet *live_packet,
+	struct packet *actual_packet,
+	struct packet *script_packet,
+	char **error)
+{
+	struct sctp_chunks_iterator iter;
+	struct sctp_chunk *chunk;
+	struct sctp_data_chunk *data;
+	struct sctp_init_chunk *init;
+	struct sctp_init_ack_chunk *init_ack;
+	struct sctp_sack_chunk *sack;
+	struct sctp_shutdown_chunk *shutdown;
+	struct sctp_ecne_chunk *ecne;
+	struct sctp_cwr_chunk *cwr;
+	u32 local_diff, remote_diff;
+	u16 nr_gap_blocks, nr_dup_tsns, i;
+
+	/* FIXME: transform v-tag in the common header*/
+	DEBUGP("map_outbound_live_sctp_packet\n");
+	for (chunk = sctp_chunks_begin(actual_packet, &iter, error);
+	     chunk != NULL;
+	     chunk = sctp_chunks_next(&iter, error)) {
+		if (*error != NULL) {
+			return STATUS_ERR;
+		}
+		local_diff = socket->script.local_initial_tsn - socket->live.local_initial_tsn;
+		remote_diff = socket->script.remote_initial_tsn - socket->live.remote_initial_tsn;
+		DEBUGP("Chunk type: 0x%02x\n", chunk->type);
+		switch (chunk->type) {
+		case SCTP_DATA_CHUNK_TYPE:
+			data = (struct sctp_data_chunk *)chunk;
+			data->tsn = htonl(ntohl(data->tsn) + local_diff);
+			break;
+		case SCTP_INIT_CHUNK_TYPE:
+			init = (struct sctp_init_chunk *)chunk;
+			init->initial_tsn = htonl(ntohl(init->initial_tsn) + local_diff);
+			/* XXX: Does this work in all cases? */
+			if (ntohl(init->initiate_tag) == socket->live.local_initiate_tag) {
+				init->initiate_tag = htonl(socket->script.local_initiate_tag);
+			}
+			break;
+		case SCTP_INIT_ACK_CHUNK_TYPE:
+			init_ack = (struct sctp_init_ack_chunk *)chunk;
+			init_ack->initial_tsn = htonl(ntohl(init_ack->initial_tsn) + local_diff);
+			/* XXX: Does this work in all cases? */
+			if (ntohl(init_ack->initiate_tag) == socket->live.local_initiate_tag) {
+				init_ack->initiate_tag = htonl(socket->script.local_initiate_tag);
+			}
+			break;
+		case SCTP_SACK_CHUNK_TYPE:
+			sack = (struct sctp_sack_chunk *)chunk;
+			sack->cum_tsn = htonl(ntohl(sack->cum_tsn) + remote_diff);
+			nr_gap_blocks = ntohs(sack->nr_gap_blocks);
+			nr_dup_tsns = ntohs(sack->nr_dup_tsns);
+			for (i = 0; i < nr_dup_tsns; i++) {
+				sack->block[i].tsn = htonl(ntohl(sack->block[i].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);
+			break;
+		case SCTP_ECNE_CHUNK_TYPE:
+			ecne = (struct sctp_ecne_chunk *)chunk;
+			ecne->lowest_tsn = htonl(ntohl(ecne->lowest_tsn) + remote_diff);
+			break;
+		case SCTP_CWR_CHUNK_TYPE:
+			cwr = (struct sctp_cwr_chunk *)chunk;
+			cwr->lowest_tsn = htonl(ntohl(cwr->lowest_tsn) + remote_diff);
+			break;
+		default:
+			break;
+		}
+	}
+	return STATUS_OK;
+}
+
 /* Transforms values in the 'actual_packet' by mapping outbound packet
  * values in the sniffed 'live_packet' (address 4-tuple, sequence
  * number in seq, timestamp value) from live values to script values
@@ -573,6 +813,10 @@ static int map_outbound_live_packet(
 	socket_get_outbound(&socket->script, &script_outbound);
 	set_packet_tuple(actual_packet, &script_outbound);
 
+	if (live_packet->sctp) {
+		return map_outbound_live_sctp_packet(socket, live_packet, actual_packet, script_packet, error);
+	}
+
 	/* If no TCP headers to rewrite, then we're done. */
 	if (live_packet->tcp == NULL)
 		return STATUS_OK;
@@ -718,13 +962,27 @@ static int verify_ipv4(
 			actual_ipv4->protocol, error) ||
 	    check_field("ipv4_header_length",
 			script_ipv4->ihl,
-			actual_ipv4->ihl, error) ||
-	    check_field("ipv4_total_length",
-			(ntohs(script_ipv4->tot_len) +
-			 tcp_options_allowance(actual_packet,
-					       script_packet)),
-			ntohs(actual_ipv4->tot_len), error))
+			actual_ipv4->ihl, error))
 		return STATUS_ERR;
+	switch (script_ipv4->protocol) {
+	case IPPROTO_SCTP:
+		/* FIXME */;
+		break;
+	case IPPROTO_TCP:
+		if (check_field("ipv4_total_length",
+				(ntohs(script_ipv4->tot_len) +
+				tcp_options_allowance(actual_packet,
+						      script_packet)),
+				ntohs(actual_ipv4->tot_len), error))
+			return STATUS_ERR;
+		break;
+	default:
+		if (check_field("ipv4_total_length",
+				ntohs(script_ipv4->tot_len),
+				ntohs(actual_ipv4->tot_len), error))
+			return STATUS_ERR;
+		break;
+	}
 
 	if (verify_outbound_live_ecn(script_packet->ecn,
 				     ipv4_ecn_bits(actual_ipv4),
@@ -766,6 +1024,465 @@ static int verify_ipv6(
 	return STATUS_OK;
 }
 
+static int verify_data_chunk(struct sctp_data_chunk *actual_chunk,
+                             struct sctp_data_chunk *script_chunk,
+                             u32 flags, char **error)
+{
+	if (check_field("sctp_data_chunk_tsn",
+		        ntohl(script_chunk->tsn),
+		        ntohl(actual_chunk->tsn),
+		        error) ||
+	    (flags & FLAG_DATA_CHUNK_SID_NOCHECK ? STATUS_OK :
+	        check_field("sctp_data_chunk_sid",
+		            ntohs(script_chunk->sid),
+		            ntohs(actual_chunk->sid),
+		            error)) ||
+	    (flags & FLAG_DATA_CHUNK_SSN_NOCHECK? STATUS_OK :
+		check_field("sctp_data_chunk_ssn",
+		            ntohs(script_chunk->ssn),
+		            ntohs(actual_chunk->ssn),
+		            error)) ||
+	    (flags & FLAG_DATA_CHUNK_PPID_NOCHECK? STATUS_OK :
+		check_field("sctp_data_chunk_ppid",
+		            ntohl(script_chunk->ppid),
+		            ntohl(actual_chunk->ppid),
+		            error))) {
+		return STATUS_ERR;
+	}
+	return STATUS_OK;
+}
+
+static int verify_init_chunk(struct sctp_init_chunk *actual_chunk,
+                             struct sctp_init_chunk *script_chunk,
+                             u32 flags, char **error)
+{
+	if (check_field("sctp_init_chunk_tag",
+		        ntohl(script_chunk->initiate_tag),
+		        ntohl(actual_chunk->initiate_tag),
+		        error) ||
+	    (flags & FLAG_INIT_CHUNK_A_RWND_NOCHECK ? STATUS_OK :
+	        check_field("sctp_init_chunk_a_rwnd",
+		            ntohl(script_chunk->a_rwnd),
+		            ntohl(actual_chunk->a_rwnd),
+		            error)) ||
+	    (flags & FLAG_INIT_CHUNK_OS_NOCHECK? STATUS_OK :
+		check_field("sctp_init_chunk_os",
+		            ntohs(script_chunk->os),
+		            ntohs(actual_chunk->os),
+		            error)) ||
+	    (flags & FLAG_INIT_CHUNK_IS_NOCHECK? STATUS_OK :
+		check_field("sctp_init_chunk_is",
+		            ntohs(script_chunk->is),
+		            ntohs(actual_chunk->is),
+		            error)) ||
+	    check_field("sctp_init_chunk_tsn",
+		        ntohl(script_chunk->initial_tsn),
+		        ntohl(actual_chunk->initial_tsn),
+		        error)) {
+		return STATUS_ERR;
+	}
+	/* FIXME: Validate parameters */
+	return STATUS_OK;
+}
+
+static int verify_init_ack_chunk(struct sctp_init_ack_chunk *actual_chunk,
+                                 struct sctp_init_ack_chunk *script_chunk,
+                                 u32 flags, char **error)
+{
+	if (check_field("sctp_init_ack_chunk_tag",
+		        ntohl(script_chunk->initiate_tag),
+		        ntohl(actual_chunk->initiate_tag),
+		        error) ||
+	    (flags & FLAG_INIT_ACK_CHUNK_A_RWND_NOCHECK ? STATUS_OK :
+	        check_field("sctp_init_ack_chunk_a_rwnd",
+		            ntohl(script_chunk->a_rwnd),
+		            ntohl(actual_chunk->a_rwnd),
+		            error)) ||
+	    (flags & FLAG_INIT_ACK_CHUNK_OS_NOCHECK? STATUS_OK :
+		check_field("sctp_init_ack_chunk_os",
+		            ntohs(script_chunk->os),
+		            ntohs(actual_chunk->os),
+		            error)) ||
+	    (flags & FLAG_INIT_ACK_CHUNK_IS_NOCHECK? STATUS_OK :
+		check_field("sctp_init_ack_chunk_is",
+		            ntohs(script_chunk->is),
+		            ntohs(actual_chunk->is),
+		            error)) ||
+	    check_field("sctp_init_ack_chunk_tsn",
+		        ntohl(script_chunk->initial_tsn),
+		        ntohl(actual_chunk->initial_tsn),
+		        error)) {
+		return STATUS_ERR;
+	}
+	/* FIXME: Validate parameters */
+	return STATUS_OK;
+}
+
+static int verify_sack_chunk(struct sctp_sack_chunk *actual_chunk,
+                             struct sctp_sack_chunk *script_chunk,
+                             u32 flags, char **error)
+{
+	u16 actual_nr_gap_blocks, actual_nr_dup_tsns;
+	u16 script_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_dup_tsns = ntohs(actual_chunk->nr_dup_tsns);
+	script_nr_gap_blocks = ntohs(script_chunk->nr_gap_blocks);
+	script_nr_dup_tsns = ntohs(script_chunk->nr_dup_tsns);
+
+	if ((flags & FLAG_SACK_CHUNK_CUM_TSN_NOCHECK ? STATUS_OK :
+	        check_field("sctp_sack_chunk_cum_tsn",
+		            ntohl(script_chunk->cum_tsn),
+		            ntohl(actual_chunk->cum_tsn),
+		            error)) ||
+	    (flags & FLAG_SACK_CHUNK_A_RWND_NOCHECK ? STATUS_OK :
+	        check_field("sctp_sack_chunk_a_rwnd",
+		            ntohl(script_chunk->a_rwnd),
+		            ntohl(actual_chunk->a_rwnd),
+		            error)) ||
+	    (flags & FLAG_SACK_CHUNK_GAP_BLOCKS_NOCHECK? STATUS_OK :
+		check_field("sctp_sack_chunk_nr_gap_blocks",
+		            script_nr_gap_blocks,
+		            actual_nr_gap_blocks,
+		            error)) ||
+	    (flags & FLAG_SACK_CHUNK_DUP_TSNS_NOCHECK? STATUS_OK :
+		check_field("sctp_sack_chunk_nr_dup_tsns",
+		            script_nr_dup_tsns,
+		            actual_nr_dup_tsns,
+		            error))) {
+		return STATUS_ERR;
+	}
+
+	if ((flags & FLAG_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;
+			}
+		}
+	}
+	if ((flags & FLAG_SACK_CHUNK_DUP_TSNS_NOCHECK) == 0) {
+		for (i = 0; i < script_nr_dup_tsns; i++) {
+			actual_base = actual_nr_gap_blocks;
+			if ((flags & FLAG_SACK_CHUNK_GAP_BLOCKS_NOCHECK) == 0) {
+				script_base = actual_nr_gap_blocks;
+			} else {
+				script_base = 0;
+			}
+			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)
+{
+	u16 length;
+
+	if (flags & FLAG_CHUNK_VALUE_NOCHECK) {
+		return STATUS_OK;
+	} else {
+		assert((flags & FLAG_CHUNK_LENGTH_NOCHECK) == 0);
+		length = ntohs(actual_chunk->length);
+		assert(length >= sizeof(struct sctp_heartbeat_chunk));
+		if (memcmp(actual_chunk->value,
+		           script_chunk->value,
+		           length - sizeof(struct sctp_heartbeat_chunk)) == 0) {
+		        return STATUS_OK;
+		} else {
+			return STATUS_ERR;
+		}
+	}
+}
+
+static int verify_heartbeat_ack_chunk(struct sctp_heartbeat_ack_chunk *actual_chunk,
+                                      struct sctp_heartbeat_ack_chunk *script_chunk,
+                                      u32 flags, char **error)
+{
+	u16 length;
+
+	if (flags & FLAG_CHUNK_VALUE_NOCHECK) {
+		return STATUS_OK;
+	} else {
+		assert((flags & FLAG_CHUNK_LENGTH_NOCHECK) == 0);
+		length = ntohs(actual_chunk->length);
+		assert(length >= sizeof(struct sctp_heartbeat_ack_chunk));
+		if (memcmp(actual_chunk->value,
+		           script_chunk->value,
+		           length - sizeof(struct sctp_heartbeat_ack_chunk)) == 0) {
+		        return STATUS_OK;
+		} else {
+			return STATUS_ERR;
+		}
+	}
+}
+
+static int verify_abort_chunk(struct sctp_abort_chunk *actual_chunk,
+                              struct sctp_abort_chunk *script_chunk,
+                              u32 flags, char **error)
+{
+	/* FIXME: Validate causes */
+	return STATUS_OK;
+}
+
+static int verify_shutdown_chunk(struct sctp_shutdown_chunk *actual_chunk,
+                                 struct sctp_shutdown_chunk *script_chunk,
+                                 u32 flags, char **error)
+{
+	return (flags & FLAG_SHUTDOWN_CHUNK_CUM_TSN_NOCHECK) ? STATUS_OK :
+	        check_field("sctp_shutdown_chunk_cum_tsn",
+		            ntohl(script_chunk->cum_tsn),
+		            ntohl(actual_chunk->cum_tsn),
+		            error);
+}
+
+static int verify_shutdown_ack_chunk(struct sctp_shutdown_ack_chunk *actual_chunk,
+                                     struct sctp_shutdown_ack_chunk *script_chunk,
+                                     u32 flags, char **error)
+{
+	/* Nothing to check */
+	return STATUS_OK;
+}
+
+static int verify_error_chunk(struct sctp_error_chunk *actual_chunk,
+                              struct sctp_error_chunk *script_chunk,
+                              u32 flags, char **error)
+{
+	/* FIXME: Validate causes */
+	return STATUS_OK;
+}
+
+static int verify_cookie_echo_chunk(struct sctp_cookie_echo_chunk *actual_chunk,
+                                    struct sctp_cookie_echo_chunk *script_chunk,
+                                    u32 flags, char **error)
+{
+	u16 length;
+
+	if (flags & FLAG_CHUNK_VALUE_NOCHECK) {
+		return STATUS_OK;
+	} else {
+		assert((flags & FLAG_CHUNK_LENGTH_NOCHECK) == 0);
+		length = ntohs(actual_chunk->length);
+		assert(length >= sizeof(struct sctp_cookie_echo_chunk));
+		if (memcmp(actual_chunk->cookie,
+		           script_chunk->cookie,
+		           length - sizeof(struct sctp_cookie_echo_chunk)) == 0) {
+		        return STATUS_OK;
+		} else {
+			return STATUS_ERR;
+		}
+	}
+}
+
+static int verify_cookie_ack_chunk(struct sctp_cookie_ack_chunk *actual_chunk,
+                                   struct sctp_cookie_ack_chunk *script_chunk,
+                                   u32 flags, char **error)
+{
+	/* Nothing to check */
+	return STATUS_OK;
+}
+
+static int verify_ecne_chunk(struct sctp_ecne_chunk *actual_chunk,
+                             struct sctp_ecne_chunk *script_chunk,
+                             u32 flags, char **error)
+{
+	return (flags & FLAG_ECNE_CHUNK_LOWEST_TSN_NOCHECK ? STATUS_OK :
+	        check_field("sctp_ecne_chunk_lowest_tsn",
+		            ntohl(script_chunk->lowest_tsn),
+		            ntohl(actual_chunk->lowest_tsn),
+		            error));
+}
+
+static int verify_cwr_chunk(struct sctp_cwr_chunk *actual_chunk,
+                            struct sctp_cwr_chunk *script_chunk,
+                            u32 flags, char **error)
+{
+	return (flags & FLAG_CWR_CHUNK_LOWEST_TSN_NOCHECK ? STATUS_OK :
+	        check_field("sctp_cwr_chunk_lowest_tsn",
+		            ntohl(script_chunk->lowest_tsn),
+		            ntohl(actual_chunk->lowest_tsn),
+		            error));
+}
+
+static int verify_shutdown_complete_chunk(struct sctp_shutdown_complete_chunk *actual_chunk,
+                                          struct sctp_shutdown_complete_chunk *script_chunk,
+                                          u32 flags, char **error)
+{
+	/* Nothing to check */
+	return STATUS_OK;
+}
+
+/* Verify that required actual SCTP packet fields are as the script expected. */
+static int verify_sctp(
+	const struct packet *actual_packet,
+	const struct packet *script_packet,
+	int layer, char **error)
+{
+	struct sctp_chunks_iterator iter;
+	struct sctp_chunk *actual_chunk;
+	struct sctp_chunk *script_chunk;
+	struct sctp_chunk_list_item *script_chunk_item;
+	u32 flags;
+	int result;
+
+	DEBUGP("Verifying SCTP packet\n")
+	DEBUGP("script packet: src port %05u, dst port %05u, v-tag 0x%08x\n",
+	       ntohs(script_packet->sctp->src_port),
+	       ntohs(script_packet->sctp->dst_port),
+	       ntohl(script_packet->sctp->v_tag));
+	DEBUGP("actual packet: src port %05u, dst port %05u, v-tag 0x%08x\n",
+	       ntohs(actual_packet->sctp->src_port),
+	       ntohs(actual_packet->sctp->dst_port),
+	       ntohl(actual_packet->sctp->v_tag));
+	for (actual_chunk = sctp_chunks_begin((struct packet *)actual_packet, &iter, error),
+	     script_chunk_item = script_packet->chunk_list->first;
+	     actual_chunk != NULL && script_chunk_item != NULL;
+	     actual_chunk = sctp_chunks_next(&iter, error),
+	     script_chunk_item = script_chunk_item->next) {
+		if (*error != NULL) {
+			DEBUGP("Error during iteration\n");
+			return STATUS_ERR;
+		}
+		script_chunk = script_chunk_item->chunk;
+		flags = script_chunk_item->flags;
+		assert(script_chunk != NULL);
+		DEBUGP("script chunk: type %02d, flags 0x%02x, length %04d\n",
+		       script_chunk->type,
+		       script_chunk->flags,
+		       ntohs(script_chunk->length));
+		DEBUGP("actual chunk: type %02d, flags 0x%02x, length %04d\n",
+		       actual_chunk->type,
+		       actual_chunk->flags,
+		       ntohs(actual_chunk->length));
+		if (check_field("sctp_chunk_type",
+		                script_chunk->type,
+		                actual_chunk->type,
+		                error) ||
+		    (flags & FLAG_CHUNK_FLAGS_NOCHECK ? STATUS_OK :
+		        check_field("sctp_chunk_flags",
+		                    script_chunk->flags,
+		                    actual_chunk->flags,
+		                    error)) ||
+		    (flags & FLAG_CHUNK_LENGTH_NOCHECK ? STATUS_OK :
+		        check_field("sctp_chunk_length",
+		                    ntohs(script_chunk->length),
+		                    ntohs(actual_chunk->length),
+		                    error))) {
+			return STATUS_ERR;
+		}
+		switch (actual_chunk->type) {
+		case SCTP_DATA_CHUNK_TYPE:
+			result = verify_data_chunk((struct sctp_data_chunk *)actual_chunk,
+			                           (struct sctp_data_chunk *)script_chunk,
+			                           flags, error);
+			break;
+		case SCTP_INIT_CHUNK_TYPE:
+			result = verify_init_chunk((struct sctp_init_chunk *)actual_chunk,
+			                           (struct sctp_init_chunk *)script_chunk,
+			                           flags, error);
+			break;
+		case SCTP_INIT_ACK_CHUNK_TYPE:
+			result = verify_init_ack_chunk((struct sctp_init_ack_chunk *)actual_chunk,
+			                               (struct sctp_init_ack_chunk *)script_chunk,
+			                               flags, error);
+			break;
+		case SCTP_SACK_CHUNK_TYPE:
+			result = verify_sack_chunk((struct sctp_sack_chunk *)actual_chunk,
+			                           (struct sctp_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,
+			                                flags, error);
+			break;
+		case SCTP_HEARTBEAT_ACK_CHUNK_TYPE:
+			result = verify_heartbeat_ack_chunk((struct sctp_heartbeat_ack_chunk *)actual_chunk,
+			                                    (struct sctp_heartbeat_ack_chunk *)script_chunk,
+			                                    flags, error);
+			break;
+		case SCTP_ABORT_CHUNK_TYPE:
+			result = verify_abort_chunk((struct sctp_abort_chunk *)actual_chunk,
+			                            (struct sctp_abort_chunk *)script_chunk,
+			                            flags, error);
+			break;
+		case SCTP_SHUTDOWN_CHUNK_TYPE:
+			result = verify_shutdown_chunk((struct sctp_shutdown_chunk *)actual_chunk,
+			                               (struct sctp_shutdown_chunk *)script_chunk,
+			                               flags, error);
+			break;
+		case SCTP_SHUTDOWN_ACK_CHUNK_TYPE:
+			result = verify_shutdown_ack_chunk((struct sctp_shutdown_ack_chunk *)actual_chunk,
+			                                   (struct sctp_shutdown_ack_chunk *)script_chunk,
+			                                   flags, error);
+			break;
+		case SCTP_ERROR_CHUNK_TYPE:
+			result = verify_error_chunk((struct sctp_error_chunk *)actual_chunk,
+			                            (struct sctp_error_chunk *)script_chunk,
+			                            flags, error);
+			break;
+		case SCTP_COOKIE_ECHO_CHUNK_TYPE:
+			result = verify_cookie_echo_chunk((struct sctp_cookie_echo_chunk *)actual_chunk,
+			                                  (struct sctp_cookie_echo_chunk *)script_chunk,
+			                                  flags, error);
+			break;
+		case SCTP_COOKIE_ACK_CHUNK_TYPE:
+			result = verify_cookie_ack_chunk((struct sctp_cookie_ack_chunk *)actual_chunk,
+			                                 (struct sctp_cookie_ack_chunk *)script_chunk,
+			                                 flags, error);
+			break;
+		case SCTP_ECNE_CHUNK_TYPE:
+			result = verify_ecne_chunk((struct sctp_ecne_chunk *)actual_chunk,
+			                           (struct sctp_ecne_chunk *)script_chunk,
+			                           flags, error);
+			break;
+		case SCTP_CWR_CHUNK_TYPE:
+			result = verify_cwr_chunk((struct sctp_cwr_chunk *)actual_chunk,
+			                          (struct sctp_cwr_chunk *)script_chunk,
+			                          flags, error);
+			break;
+		case SCTP_SHUTDOWN_COMPLETE_CHUNK_TYPE:
+			result = verify_shutdown_complete_chunk((struct sctp_shutdown_complete_chunk *)actual_chunk,
+			                                        (struct sctp_shutdown_complete_chunk *)script_chunk,
+			                                        flags, error);
+			break;
+		default:
+			result = STATUS_ERR;
+			assert(!"unsupported SCTP chunk type");
+			break;
+		}
+		if (result == STATUS_ERR) {
+			return STATUS_ERR;
+		}
+	}
+	if (actual_chunk != NULL) {
+		DEBUGP("actual packet contains more chunks than script packet\n");
+	}
+	if (script_chunk_item != NULL) {
+		DEBUGP("script packet contains more chunks than actual packet\n");
+	}
+	if ((actual_chunk != NULL) || (script_chunk_item != NULL)) {
+		asprintf(error,
+		         "live packet and expected packet have not the same number of chunks");
+		return STATUS_ERR;
+	}
+	return STATUS_OK;
+}
+
 /* Verify that required actual TCP header fields are as the script expected. */
 static int verify_tcp(
 	const struct packet *actual_packet,
@@ -922,6 +1639,7 @@ static int verify_header(
 		[HEADER_IPV6]		= verify_ipv6,
 		[HEADER_GRE]		= verify_gre,
 		[HEADER_MPLS]		= verify_mpls,
+		[HEADER_SCTP]		= verify_sctp,
 		[HEADER_TCP]		= verify_tcp,
 		[HEADER_UDP]		= verify_udp,
 		[HEADER_UDPLITE]	= verify_udplite,
@@ -956,8 +1674,11 @@ static int verify_outbound_live_headers(
 	const int script_headers = packet_header_count(script_packet);
 	int i;
 
+	DEBUGP("verify_outbound_live_headers\n");
+
 	assert((actual_packet->ipv4 != NULL) || (actual_packet->ipv6 != NULL));
-	assert((actual_packet->tcp != NULL) ||
+	assert((actual_packet->sctp != NULL) ||
+	       (actual_packet->tcp != NULL) ||
 	       (actual_packet->udp != NULL) ||
 	       (actual_packet->udplite != NULL));
 
@@ -1044,6 +1765,8 @@ static int verify_outbound_live_payload(
 	struct packet *actual_packet,
 	struct packet *script_packet, char **error)
 {
+	if (actual_packet->sctp != NULL)
+		return STATUS_OK;
 	/* Diff the TCP/UDP data payloads. We've already implicitly
 	 * checked their length by checking the IP and TCP/UDP headers.
 	 */
@@ -1185,7 +1908,9 @@ static bool is_script_packet_match_for_socket(
 {
 	const bool is_packet_icmp = (packet->icmpv4 || packet->icmpv6);
 
-	if (socket->protocol == IPPROTO_TCP)
+	if (socket->protocol == IPPROTO_SCTP)
+		return packet->sctp || is_packet_icmp;
+	else if (socket->protocol == IPPROTO_TCP)
 		return packet->tcp || is_packet_icmp;
 	else if (socket->protocol == IPPROTO_UDP)
 		return packet->udp || is_packet_icmp;
@@ -1206,7 +1931,7 @@ static int find_or_create_socket_for_script_packet(
 
 	DEBUGP("find_or_create_socket_for_script_packet\n");
 
-	if (packet->tcp != NULL) {
+	if ((packet->tcp != NULL) || (packet->sctp != NULL)) {
 		/* Is this an inbound packet matching a listening
 		 * socket? If so, this call will create a new child
 		 * socket object.
@@ -1243,33 +1968,112 @@ static int do_outbound_script_packet(
 	struct state *state, struct packet *packet,
 	struct socket *socket,	char **error)
 {
-	DEBUGP("do_outbound_script_packet\n");
+	struct sctp_chunk_list_item *item;
+	struct sctp_chunks_iterator chunk_iter;
+	struct sctp_parameters_iterator param_iter;
+	struct sctp_chunk *chunk;
+	struct sctp_init_ack_chunk *init_ack;
+	struct sctp_cookie_echo_chunk *cookie_echo;
+	struct sctp_parameter *parameter;
+	struct sctp_state_cookie_parameter *state_cookie;
 	int result = STATUS_ERR;		/* return value */
 	struct packet *live_packet = NULL;
+	u16 cookie_length, chunk_length, parameter_length, parameters_length;
+	u16 padding_length;
 
+	DEBUGP("do_outbound_script_packet\n");
 	if ((packet->icmpv4 != NULL) || (packet->icmpv6 != NULL)) {
 		asprintf(error, "outbound ICMP packets are not supported");
 		goto out;
 	}
 
-	if ((socket->state == SOCKET_PASSIVE_PACKET_RECEIVED) &&
-	    packet->tcp && packet->tcp->syn && packet->tcp->ack) {
-		/* Script says we should see an outbound server SYNACK. */
-		socket->script.local_isn = ntohl(packet->tcp->seq);
-		DEBUGP("SYNACK script.local_isn: %u\n",
-		       socket->script.local_isn);
+	if (socket->state == SOCKET_PASSIVE_PACKET_RECEIVED) {
+		if (packet->tcp && packet->tcp->syn && packet->tcp->ack) {
+			/* Script says we should see an outbound server SYNACK. */
+			socket->script.local_isn = ntohl(packet->tcp->seq);
+			DEBUGP("SYNACK script.local_isn: %u\n",
+			       socket->script.local_isn);
+		}
+		if (packet->sctp) {
+			assert(packet->chunk_list != NULL);
+			item = packet->chunk_list->first;
+			if ((item != NULL) &&
+			    (item->chunk->type == SCTP_INIT_ACK_CHUNK_TYPE)) {
+				init_ack = (struct sctp_init_ack_chunk *)item->chunk;
+				socket->script.local_initiate_tag = ntohl(init_ack->initiate_tag);
+				socket->script.local_initial_tsn = ntohl(init_ack->initial_tsn);
+				DEBUGP("INIT_ACK: script.local_initiate_tag: %u\n",
+				       socket->script.local_initiate_tag);
+				DEBUGP("INIT_ACK: script.local_initial_tsn: %u\n",
+				       socket->script.local_initial_tsn);
+			}
+		}
 	}
 
 	/* Sniff outbound live packet and verify it's for the right socket. */
 	if (sniff_outbound_live_packet(state, socket, &live_packet, error))
 		goto out;
 
-	if ((socket->state == SOCKET_PASSIVE_PACKET_RECEIVED) &&
-	    packet->tcp && packet->tcp->syn && packet->tcp->ack) {
-		socket->state = SOCKET_PASSIVE_SYNACK_SENT;
-		socket->live.local_isn = ntohl(live_packet->tcp->seq);
-		DEBUGP("SYNACK live.local_isn: %u\n",
-		       socket->live.local_isn);
+	if (socket->state == SOCKET_PASSIVE_PACKET_RECEIVED) {
+		if (packet->tcp && packet->tcp->syn && packet->tcp->ack) {
+			socket->state = SOCKET_PASSIVE_SYNACK_SENT;
+			socket->live.local_isn = ntohl(live_packet->tcp->seq);
+			DEBUGP("SYNACK live.local_isn: %u\n",
+			       socket->live.local_isn);
+		}
+		if (live_packet->sctp) {
+			chunk = sctp_chunks_begin(live_packet, &chunk_iter, error);
+			if ((*error == NULL) &&
+			    (chunk != NULL) &&
+			    (chunk->type == SCTP_INIT_ACK_CHUNK_TYPE)) {
+				chunk_length = ntohs(chunk->length);
+				if (chunk_length < sizeof(struct sctp_init_ack_chunk)) {
+					asprintf(error, "INIT chunk too short (length=%u)", chunk_length);
+					goto out;
+				}
+				parameters_length = chunk_length - sizeof(struct sctp_init_chunk);
+				init_ack = (struct sctp_init_ack_chunk *)chunk;
+
+				for (parameter = sctp_parameters_begin(init_ack->parameter,
+				                                       parameters_length,
+				                                       &param_iter, error);
+				     parameter != NULL;
+				     parameter = sctp_parameters_next(&param_iter, error)) {
+					if (*error != NULL)
+						goto out;
+					if (ntohs(parameter->type) == SCTP_STATE_COOKIE_PARAMETER_TYPE) {
+						state_cookie = (struct sctp_state_cookie_parameter *)parameter;
+						parameter_length = ntohs(state_cookie->length);
+						if (parameter_length < sizeof(struct sctp_state_cookie_parameter)) {
+							asprintf(error, "State Cookie parameter too short (length=%u)", parameter_length);
+							goto out;
+						}
+						cookie_length = parameter_length - 4;
+						padding_length = cookie_length % 4;
+						if (padding_length > 0) {
+							padding_length = 4 - padding_length;
+						}
+						chunk_length = sizeof(struct sctp_cookie_echo_chunk) + cookie_length;
+						cookie_echo = (struct sctp_cookie_echo_chunk *)malloc(chunk_length + padding_length);
+						cookie_echo->type = SCTP_COOKIE_ECHO_CHUNK_TYPE;
+						cookie_echo->flags = 0;
+						cookie_echo->length = htons(chunk_length);
+						memcpy(cookie_echo->cookie, state_cookie->cookie, cookie_length);
+						memset(cookie_echo->cookie + cookie_length, 0, padding_length);
+						socket->prepared_state_cookie = cookie_echo;
+						socket->prepared_state_cookie_length = chunk_length + padding_length;
+						break;
+					}
+				}
+				socket->live.local_initiate_tag = ntohl(init_ack->initiate_tag);
+				socket->live.local_initial_tsn = ntohl(init_ack->initial_tsn);
+				socket->state = SOCKET_PASSIVE_INIT_ACK_SENT;
+				DEBUGP("INIT_ACK: live.local_initiate_tag: %u\n",
+				       socket->live.local_initiate_tag);
+				DEBUGP("INIT_ACK: live.local_initial_tsn: %u\n",
+				       socket->live.local_initial_tsn);
+			}
+		}
 	}
 
 	verbose_packet_dump(state, "outbound sniffed", live_packet,
@@ -1298,7 +2102,7 @@ static int send_live_ip_packet(struct netdev *netdev,
 	/* We do IPv4 and IPv6 */
 	assert(packet->ipv4 || packet->ipv6);
 	/* We only do TCP, UDP, UDPLite and ICMP */
-	assert(packet->tcp || packet->udp || packet->udplite ||
+	assert(packet->sctp || packet->tcp || packet->udp || packet->udplite ||
 	       packet->icmpv4 || packet->icmpv6);
 
 	/* Fill in layer 3 and layer 4 checksums */
@@ -1312,19 +2116,74 @@ static int do_inbound_script_packet(
 	struct state *state, struct packet *packet,
 	struct socket *socket,	char **error)
 {
-	DEBUGP("do_inbound_script_packet\n");
+	struct sctp_init_ack_chunk *init_ack;
+	struct sctp_chunk_list_item *item;
 	int result = STATUS_ERR;	/* return value */
+	u16 offset;
+	bool cooie_echo_replaced = false;
+	u16 i;
 
-	if ((socket->state == SOCKET_PASSIVE_SYNACK_SENT) &&
-	    packet->tcp && packet->tcp->ack) {
-		/* Received the ACK that completes the 3-way handshake. */
-		socket->state = SOCKET_PASSIVE_SYNACK_ACKED;
-	} else if ((socket->state == SOCKET_ACTIVE_SYN_SENT) &&
-		   packet->tcp && packet->tcp->syn && packet->tcp->ack) {
-		/* Received the server's SYNACK, which ACKs our SYN. */
-		socket->state = SOCKET_ACTIVE_SYN_ACKED;
-		socket->script.remote_isn	= ntohl(packet->tcp->seq);
-		socket->live.remote_isn		= ntohl(packet->tcp->seq);
+	DEBUGP("do_inbound_script_packet\n");
+	if (packet->tcp) {
+		if ((socket->state == SOCKET_PASSIVE_SYNACK_SENT) &&
+		    packet->tcp->ack) {
+			/* Received the ACK that completes the 3-way handshake. */
+			socket->state = SOCKET_PASSIVE_SYNACK_ACKED;
+		} else if ((socket->state == SOCKET_ACTIVE_SYN_SENT) &&
+		           packet->tcp->syn && packet->tcp->ack) {
+			/* Received the server's SYNACK, which ACKs our SYN. */
+			socket->state = SOCKET_ACTIVE_SYN_ACKED;
+			socket->script.remote_isn = ntohl(packet->tcp->seq);
+			socket->live.remote_isn = ntohl(packet->tcp->seq);
+		}
+	}
+	if (packet->sctp) {
+		item = packet->chunk_list->first;
+		if ((socket->state == SOCKET_ACTIVE_INIT_SENT) &&
+		    (item != NULL) &&
+		    (item->chunk->type == SCTP_INIT_ACK_CHUNK_TYPE)) {
+			init_ack = (struct sctp_init_ack_chunk *)item->chunk;
+			DEBUGP("Moving socket in SOCKET_ACTIVE_INIT_ACK_RECEIVED\n");
+			socket->state = SOCKET_ACTIVE_INIT_ACK_RECEIVED;
+			socket->script.remote_initiate_tag = ntohl(init_ack->initiate_tag);
+			socket->script.remote_initial_tsn = ntohl(init_ack->initial_tsn);
+			socket->live.remote_initiate_tag = ntohl(init_ack->initiate_tag);
+			socket->live.remote_initial_tsn = ntohl(init_ack->initial_tsn);
+			DEBUGP("remote_initiate_tag %d, remote_initial_tsn %d\n", ntohl(init_ack->initiate_tag), ntohl(init_ack->initial_tsn));
+		}
+		if (socket->state == SOCKET_PASSIVE_INIT_ACK_SENT) {
+			for (; item != NULL; item = item->next) {
+				if (item->chunk->type == SCTP_COOKIE_ECHO_CHUNK_TYPE) {
+					assert(item->next == NULL); /*FIXME: Handle chunks after the COOKIE_ECHO */
+					offset = socket->prepared_state_cookie_length - item->length;
+					memcpy(item->chunk, socket->prepared_state_cookie, socket->prepared_state_cookie_length);
+					item->length = socket->prepared_state_cookie_length;
+					packet->buffer_bytes += offset;
+					packet->ip_bytes += offset;
+					if (packet->ipv4) {
+						packet->ipv4->tot_len = htons(ntohs(packet->ipv4->tot_len) + offset);
+					}
+					if (packet->ipv6) {
+						packet->ipv6->payload_len = htons(ntohs(packet->ipv6->payload_len) + offset);
+					}
+					for (i = 0; i < PACKET_MAX_HEADERS; i++) {
+						if ((packet->ipv4 != NULL && packet->headers[i].h.ipv4 == packet->ipv4) ||
+						    (packet->ipv6 != NULL && packet->headers[i].h.ipv6 == packet->ipv6)) {
+							break;
+						}
+					}
+					assert(packet->headers[i + 1].type == HEADER_SCTP);
+					packet->headers[i].total_bytes += offset;
+					packet->headers[i + 1].total_bytes += offset;
+					cooie_echo_replaced = true;
+					socket->state = SOCKET_PASSIVE_COOKIE_ECHO_RECEIVED;
+				} else {
+					if (cooie_echo_replaced) {
+						item->chunk = (struct sctp_chunk *)((char *)item->chunk + offset);
+					}
+				}
+			}
+		}
 	}
 
 	/* Start with a bit-for-bit copy of the packet from the script. */
@@ -1462,6 +2321,52 @@ int reset_connection(struct state *state, struct socket *socket)
 	return result;
 }
 
+/* Inject an SCTP packet containing an ABORT chunk to clear the
+ * association state out of the kernel, so the association does not
+ * continue to retransmit packets that may be sniffed during later test
+ * executions and cause false negatives.
+ */
+int abort_association(struct state *state, struct socket *socket)
+{
+	char *error = NULL;
+	struct packet *packet;
+	struct sctp_chunk_list *chunk_list;
+	struct tuple live_inbound;
+	int result = 0;
+
+	if ((socket->live.local_initiate_tag == 0) &&
+	    (socket->live.remote_initiate_tag == 0)) {
+		return STATUS_OK;
+	}
+		chunk_list = sctp_chunk_list_new();
+	if (socket->live.local_initiate_tag != 0) {
+		sctp_chunk_list_append(chunk_list, sctp_abort_chunk_new(0));
+	} else {
+		sctp_chunk_list_append(chunk_list, sctp_abort_chunk_new(0x01));
+	}
+	packet = new_sctp_packet(socket->address_family,
+				 DIRECTION_INBOUND, ECN_NONE,
+				 chunk_list, &error);
+	if (packet == NULL)
+		die("%s", error);
+	/* Rewrite addresses and port to match inbound live traffic. */
+	socket_get_inbound(&socket->live, &live_inbound);
+	set_packet_tuple(packet, &live_inbound);
+	/* Rewrite the verification tag in the SCTP common header */
+	if (socket->live.local_initiate_tag != 0) {
+		packet->sctp->v_tag = htonl(socket->live.local_initiate_tag);
+	} else {
+		packet->sctp->v_tag = htonl(socket->live.remote_initiate_tag);
+	}
+
+	/* Inject live packet into kernel. */
+	result = send_live_ip_packet(state->netdev, packet);
+
+	packet_free(packet);
+
+	return result;
+}
+
 struct packets *packets_new(void)
 {
 	struct packets *packets = calloc(1, sizeof(struct packets));
diff --git a/gtests/net/packetdrill/run_packet.h b/gtests/net/packetdrill/run_packet.h
index 194676d9b044fdc01380f7857d6c551441a5aa53..daecd8dcec4e4b4bb6a8b31b377c3c18715d5b4a 100644
--- a/gtests/net/packetdrill/run_packet.h
+++ b/gtests/net/packetdrill/run_packet.h
@@ -58,4 +58,10 @@ extern int run_packet_event(struct state *state,
 extern int reset_connection(struct state *state,
 			    struct socket *socket);
 
+/* Inject an SCTP packet containing an ABORT chunk to clear the association
+ * state out of the kernel.
+ */
+extern int abort_association(struct state *state,
+			     struct socket *socket);
+
 #endif /* __RUN_PACKET_H__ */
diff --git a/gtests/net/packetdrill/run_system_call.c b/gtests/net/packetdrill/run_system_call.c
index aa9c3a90f9a8301e0a017a3763fc9e3cc3e55d0e..6181c7c9ab8c90113d5ae090cf84919db246e2fc 100644
--- a/gtests/net/packetdrill/run_system_call.c
+++ b/gtests/net/packetdrill/run_system_call.c
@@ -788,7 +788,8 @@ static int run_syscall_accept(struct state *state,
 		}
 
 		if ((socket->state == SOCKET_PASSIVE_SYNACK_SENT) ||  /* TFO */
-		    (socket->state == SOCKET_PASSIVE_SYNACK_ACKED)) {
+		    (socket->state == SOCKET_PASSIVE_SYNACK_ACKED) ||
+		    (socket->state == SOCKET_PASSIVE_COOKIE_ECHO_RECEIVED)) {
 			assert(is_equal_ip(&socket->live.remote.ip, &ip));
 			assert(is_equal_port(socket->live.remote.port,
 					     htons(port)));
diff --git a/gtests/net/packetdrill/sctp_packet.c b/gtests/net/packetdrill/sctp_packet.c
new file mode 100644
index 0000000000000000000000000000000000000000..9d1777e2f1c8c4032d5d39d9586a2406c372f45c
--- /dev/null
+++ b/gtests/net/packetdrill/sctp_packet.c
@@ -0,0 +1,692 @@
+/*
+ * Copyright 2015 Michael Tuexen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+/*
+ * Author: tuexen@fh-muenster.de (Michael Tuexen)
+ *
+ * Implementation of module for formatting SCTP packets.
+ */
+
+#include "logging.h"
+#include "sctp_packet.h"
+#include "ip_packet.h"
+#include "sctp.h"
+
+/*
+ * ToDo:
+ * - Add support for chunk flags (fix hard coded flags for DATA chunk)
+ * - Add support for user data length in DATA chunks (fix hard coded payload)
+ * - Add support for parameters (fix hard coded state cookie in INIT-ACK)
+ * - Add support for error causes
+ */
+ 
+struct sctp_chunk_list_item *
+sctp_chunk_list_item_new(struct sctp_chunk *chunk, u32 length, u32 flags)
+{
+	struct sctp_chunk_list_item *item;
+
+	item = malloc(sizeof(struct sctp_chunk_list_item));
+	assert(item != NULL);
+	item->next = NULL;
+	item->chunk = chunk;
+	item->length = length;
+	item->flags = flags;
+	return item;
+}
+
+struct sctp_chunk_list_item *
+sctp_data_chunk_new(s64 tsn, s64 sid, s64 ssn, s64 ppid)
+{
+	struct sctp_chunk_list_item *item;
+	struct sctp_data_chunk *chunk;
+	u32 flags;
+	u16 payload_len = 1000;
+
+	flags = 0;
+	chunk = malloc(sizeof(struct sctp_data_chunk) + payload_len);
+	assert(chunk != NULL);
+	chunk->type = SCTP_DATA_CHUNK_TYPE;
+	chunk->flags = 0x03; /* FIXME */
+	chunk->length = htons(sizeof(struct sctp_data_chunk) + payload_len);
+	if (tsn == -1) {
+		chunk->tsn = htonl(0);
+		flags |= FLAG_DATA_CHUNK_TSN_NOCHECK;
+	} else {
+		chunk->tsn = htonl((u32)tsn);
+	}
+	if (sid == -1) {
+		chunk->sid = htons(0);
+		flags |= FLAG_DATA_CHUNK_SID_NOCHECK;
+	} else {
+		chunk->sid = htons((u16)sid);
+	}
+	if (ssn == -1) {
+		chunk->ssn = htons(0);
+		flags |= FLAG_DATA_CHUNK_SSN_NOCHECK;
+	} else {
+		chunk->ssn = htons((u16)ssn);
+	}
+	if (ppid == -1) {
+		chunk->ppid = htons(0);
+		flags |= FLAG_DATA_CHUNK_PPID_NOCHECK;
+	} else {
+		chunk->ppid = htons((u32)ppid);
+	}
+	memset(chunk->data, 0, payload_len);
+	item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk,
+	                                (u32)sizeof(struct sctp_data_chunk) + payload_len,
+	                                flags);
+	return item;
+}
+
+struct sctp_chunk_list_item *
+sctp_init_chunk_new(s64 tag, s64 a_rwnd, s64 os, s64 is, s64 tsn)
+{
+	struct sctp_chunk_list_item *item;
+	struct sctp_init_chunk *chunk;
+	u32 flags;
+
+	flags = 0;
+	chunk = malloc(sizeof(struct sctp_init_chunk));
+	assert(chunk != NULL);
+	chunk->type = SCTP_INIT_CHUNK_TYPE;
+	chunk->flags = 0;
+	/* FIXME */
+	flags |= FLAG_CHUNK_LENGTH_NOCHECK;
+	chunk->length = htons(sizeof(struct sctp_init_chunk));
+	chunk->initiate_tag = htonl((u32)tag);
+	if (a_rwnd == -1) {
+		chunk->a_rwnd = htonl(0);
+		flags |= FLAG_INIT_CHUNK_A_RWND_NOCHECK;
+	} else {
+		chunk->a_rwnd = htonl((u32)a_rwnd);
+	}
+	if (os == -1) {
+		chunk->os = htons(0);
+		flags |= FLAG_INIT_CHUNK_OS_NOCHECK;
+	} else {
+		chunk->os = htons((u16)os);
+	}
+	if (is == -1) {
+		chunk->is = htons(0);
+		flags |= FLAG_INIT_CHUNK_IS_NOCHECK;
+	} else {
+		chunk->is = htons((u16)is);
+	}
+	chunk->initial_tsn = htonl((u32)tsn);
+	item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk,
+	                                (u32)sizeof(struct sctp_init_chunk),
+	                                flags);
+	return item;
+}
+
+/* FIXME: Don't fake the cookie that way... */
+struct sctp_chunk_list_item *
+sctp_init_ack_chunk_new(s64 tag, s64 a_rwnd, s64 os, s64 is, s64 tsn)
+{
+	struct sctp_chunk_list_item *item;
+	struct sctp_init_ack_chunk *chunk;
+	struct sctp_state_cookie_parameter state_cookie_parameter;
+	u32 flags;
+
+	flags = 0;
+	chunk = malloc(sizeof(struct sctp_init_ack_chunk) + sizeof(struct sctp_state_cookie_parameter));
+	assert(chunk != NULL);
+	chunk->type = SCTP_INIT_ACK_CHUNK_TYPE;
+	chunk->flags = 0;
+	/* FIXME */
+	flags |= FLAG_CHUNK_LENGTH_NOCHECK;
+	chunk->length = htons(sizeof(struct sctp_init_ack_chunk) + sizeof(struct sctp_state_cookie_parameter));
+	chunk->initiate_tag = htonl((u32)tag);
+	if (a_rwnd == -1) {
+		chunk->a_rwnd = htonl(0);
+		flags |= FLAG_INIT_CHUNK_A_RWND_NOCHECK;
+	} else {
+		chunk->a_rwnd = htonl((u32)a_rwnd);
+	}
+	if (os == -1) {
+		chunk->os = htons(0);
+		flags |= FLAG_INIT_CHUNK_OS_NOCHECK;
+	} else {
+		chunk->os = htons((u16)os);
+	}
+	if (is == -1) {
+		chunk->is = htons(0);
+		flags |= FLAG_INIT_CHUNK_IS_NOCHECK;
+	} else {
+		chunk->is = htons((u16)is);
+	}
+	chunk->initial_tsn = htonl((u32)tsn);
+	state_cookie_parameter.type = htons(SCTP_STATE_COOKIE_PARAMETER_TYPE);
+	state_cookie_parameter.length = htons(sizeof(struct sctp_state_cookie_parameter));
+	memcpy(chunk->parameter, &state_cookie_parameter, sizeof(struct sctp_state_cookie_parameter));
+	item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk,
+	                                (u32)sizeof(struct sctp_init_ack_chunk) + sizeof(struct sctp_state_cookie_parameter),
+	                                flags);
+	return item;
+}
+
+struct sctp_chunk_list_item *
+sctp_sack_chunk_new(s64 cum_tsn, s64 a_rwnd)
+{
+	struct sctp_chunk_list_item *item;
+	struct sctp_sack_chunk *chunk;
+	u32 flags;
+
+	flags = 0;
+	chunk = malloc(sizeof(struct sctp_sack_chunk));
+	assert(chunk != NULL);
+	chunk->type = SCTP_SACK_CHUNK_TYPE;
+	chunk->flags = 0;
+	chunk->length = htons(sizeof(struct sctp_sack_chunk));
+	if (cum_tsn == -1) {
+		chunk->cum_tsn = htonl(0);
+		flags |= FLAG_SACK_CHUNK_CUM_TSN_NOCHECK;
+	} else {
+		chunk->cum_tsn = htonl((u32)cum_tsn);
+	}
+	if (a_rwnd == -1) {
+		chunk->a_rwnd = htonl(0);
+		flags |= FLAG_SACK_CHUNK_A_RWND_NOCHECK;
+	} 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;
+}
+
+struct sctp_chunk_list_item *
+sctp_heartbeat_chunk_new(u8 flags)
+{
+	struct sctp_chunk_list_item *item;
+	struct sctp_heartbeat_chunk *chunk;
+
+	chunk = malloc(sizeof(struct sctp_heartbeat_chunk));
+	assert(chunk != NULL);
+	chunk->type = SCTP_HEARTBEAT_CHUNK_TYPE;
+	chunk->flags = flags;
+	chunk->length = htons(sizeof(struct sctp_heartbeat_chunk));
+	item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk,
+	                                (u32)sizeof(struct sctp_heartbeat_chunk), 
+	                                FLAG_CHUNK_LENGTH_NOCHECK | FLAG_CHUNK_VALUE_NOCHECK);
+	return item;
+}
+
+struct sctp_chunk_list_item *
+sctp_heartbeat_ack_chunk_new(u8 flags)
+{
+	struct sctp_chunk_list_item *item;
+	struct sctp_heartbeat_ack_chunk *chunk;
+
+	chunk = malloc(sizeof(struct sctp_heartbeat_ack_chunk));
+	assert(chunk != NULL);
+	chunk->type = SCTP_HEARTBEAT_ACK_CHUNK_TYPE;
+	chunk->flags = flags;
+	chunk->length = htons(sizeof(struct sctp_heartbeat_ack_chunk));
+	item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk,
+	                                (u32)sizeof(struct sctp_heartbeat_ack_chunk), 
+	                                FLAG_CHUNK_LENGTH_NOCHECK | FLAG_CHUNK_VALUE_NOCHECK);
+	return item;
+}
+
+struct sctp_chunk_list_item *
+sctp_abort_chunk_new(u8 flags)
+{
+	struct sctp_chunk_list_item *item;
+	struct sctp_abort_chunk *chunk;
+
+	chunk = malloc(sizeof(struct sctp_abort_chunk));
+	assert(chunk != NULL);
+	chunk->type = SCTP_ABORT_CHUNK_TYPE;
+	chunk->flags = flags;
+	chunk->length = htons(sizeof(struct sctp_abort_chunk));
+	item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk,
+	                                (u32)sizeof(struct sctp_abort_chunk), 
+	                                0);
+	return item;
+}
+
+struct sctp_chunk_list_item *
+sctp_shutdown_chunk_new(s64 cum_tsn)
+{
+	struct sctp_chunk_list_item *item;
+	struct sctp_shutdown_chunk *chunk;
+	u32 flags;
+
+	flags = 0;
+	chunk = malloc(sizeof(struct sctp_shutdown_chunk));
+	assert(chunk != NULL);
+	chunk->type = SCTP_SHUTDOWN_CHUNK_TYPE;
+	chunk->flags = 0;
+	chunk->length = htons(sizeof(struct sctp_shutdown_chunk));
+	if (cum_tsn == -1) {
+		chunk->cum_tsn = htonl(0);
+		flags |= FLAG_SHUTDOWN_CHUNK_CUM_TSN_NOCHECK;
+	} else {
+		chunk->cum_tsn = htonl((u32)cum_tsn);
+	}
+
+	item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk,
+	                                (u32)sizeof(struct sctp_shutdown_chunk), 
+	                                flags);
+	return item;
+}
+
+struct sctp_chunk_list_item *
+sctp_shutdown_ack_chunk_new(u8 flags)
+{
+	struct sctp_chunk_list_item *item;
+	struct sctp_shutdown_ack_chunk *chunk;
+
+	chunk = malloc(sizeof(struct sctp_shutdown_ack_chunk));
+	assert(chunk != NULL);
+	chunk->type = SCTP_SHUTDOWN_ACK_CHUNK_TYPE;
+	chunk->flags = flags;
+	chunk->length = htons(sizeof(struct sctp_shutdown_ack_chunk));
+	item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk,
+	                                (u32)sizeof(struct sctp_shutdown_ack_chunk), 
+	                                0);
+	return item;
+}
+
+struct sctp_chunk_list_item *
+sctp_error_chunk_new(u8 flags)
+{
+	struct sctp_chunk_list_item *item;
+	struct sctp_error_chunk *chunk;
+
+	chunk = malloc(sizeof(struct sctp_error_chunk));
+	assert(chunk != NULL);
+	chunk->type = SCTP_ERROR_CHUNK_TYPE;
+	chunk->flags = flags;
+	chunk->length = htons(sizeof(struct sctp_error_chunk));
+	item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk,
+	                                (u32)sizeof(struct sctp_error_chunk), 
+	                                FLAG_CHUNK_LENGTH_NOCHECK | FLAG_CHUNK_VALUE_NOCHECK);
+	return item;
+}
+
+struct sctp_chunk_list_item *
+sctp_cookie_echo_chunk_new(u8 flags)
+{
+	struct sctp_chunk_list_item *item;
+	struct sctp_cookie_echo_chunk *chunk;
+
+	chunk = malloc(sizeof(struct sctp_cookie_echo_chunk));
+	assert(chunk != NULL);
+	chunk->type = SCTP_COOKIE_ECHO_CHUNK_TYPE;
+	chunk->flags = flags;
+	chunk->length = htons(sizeof(struct sctp_cookie_echo_chunk));
+	item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk,
+	                                (u32)sizeof(struct sctp_cookie_echo_chunk), 
+	                                FLAG_CHUNK_LENGTH_NOCHECK | FLAG_CHUNK_VALUE_NOCHECK);
+	return item;
+}
+
+struct sctp_chunk_list_item *
+sctp_cookie_ack_chunk_new(u8 flags)
+{
+	struct sctp_chunk_list_item *item;
+	struct sctp_cookie_ack_chunk *chunk;
+
+	chunk = malloc(sizeof(struct sctp_cookie_ack_chunk));
+	assert(chunk != NULL);
+	chunk->type = SCTP_COOKIE_ACK_CHUNK_TYPE;
+	chunk->flags = flags;
+	chunk->length = htons(sizeof(struct sctp_cookie_ack_chunk));
+	item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk,
+	                                (u32)sizeof(struct sctp_cookie_ack_chunk), 
+	                                0);
+	return item;
+}
+
+struct sctp_chunk_list_item *
+sctp_ecne_chunk_new(s64 lowest_tsn)
+{
+	struct sctp_chunk_list_item *item;
+	struct sctp_ecne_chunk *chunk;
+	u32 flags;
+
+	flags = 0;
+	chunk = malloc(sizeof(struct sctp_ecne_chunk));
+	assert(chunk != NULL);
+	chunk->type = SCTP_ECNE_CHUNK_TYPE;
+	chunk->flags = 0;
+	chunk->length = htons(sizeof(struct sctp_ecne_chunk));
+	if (lowest_tsn == -1) {
+		chunk->lowest_tsn = htonl(0);
+		flags |= FLAG_ECNE_CHUNK_LOWEST_TSN_NOCHECK;
+	} else {
+		chunk->lowest_tsn = htonl((u32)lowest_tsn);
+	}
+
+	item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk,
+	                                (u32)sizeof(struct sctp_ecne_chunk), 
+	                                flags);
+	return item;
+}
+
+struct sctp_chunk_list_item *
+sctp_cwr_chunk_new(s64 lowest_tsn)
+{
+	struct sctp_chunk_list_item *item;
+	struct sctp_cwr_chunk *chunk;
+	u32 flags;
+
+	flags = 0;
+	chunk = malloc(sizeof(struct sctp_cwr_chunk));
+	assert(chunk != NULL);
+	chunk->type = SCTP_CWR_CHUNK_TYPE;
+	chunk->flags = 0;
+	chunk->length = htons(sizeof(struct sctp_cwr_chunk));
+	if (lowest_tsn == -1) {
+		chunk->lowest_tsn = htonl(0);
+		flags |= FLAG_CWR_CHUNK_LOWEST_TSN_NOCHECK;
+	} else {
+		chunk->lowest_tsn = htonl((u32)lowest_tsn);
+	}
+
+	item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk,
+	                                (u32)sizeof(struct sctp_cwr_chunk), 
+	                                flags);
+	return item;
+}
+
+struct sctp_chunk_list_item *
+sctp_shutdown_complete_chunk_new(u8 flags)
+{
+	struct sctp_chunk_list_item *item;
+	struct sctp_shutdown_complete_chunk *chunk;
+
+	chunk = malloc(sizeof(struct sctp_shutdown_complete_chunk));
+	assert(chunk != NULL);
+	chunk->type = SCTP_SHUTDOWN_COMPLETE_CHUNK_TYPE;
+	chunk->flags = flags;
+	chunk->length = htons(sizeof(struct sctp_shutdown_complete_chunk));
+	item = sctp_chunk_list_item_new((struct sctp_chunk *)chunk,
+	                                (u32)sizeof(struct sctp_shutdown_complete_chunk), 
+	                                0);
+	return item;
+}
+
+struct sctp_chunk_list *sctp_chunk_list_new(void)
+{
+	struct sctp_chunk_list *list;
+
+	list = malloc(sizeof(struct sctp_chunk_list));
+	assert(list != NULL);
+	list->first = NULL;
+	list->last = NULL;
+	list->length = 0;
+	return list;
+}
+
+void sctp_chunk_list_append(struct sctp_chunk_list *list,
+			    struct sctp_chunk_list_item *item)
+{
+	assert(item->next == NULL);
+	if (list->last == NULL) {
+		assert(list->first == NULL);
+		assert(list->length == 0);
+		list->first = item;
+	} else {
+		assert(list->first != NULL);
+		list->last->next = item;
+	}
+	list->last = item;
+	list->length += item->length;
+}
+
+void sctp_chunk_list_free(struct sctp_chunk_list *list)
+{
+	struct sctp_chunk_list_item *current_item, *next_item;
+
+	assert(list != NULL);
+	current_item = list->first;
+	while (current_item != NULL) {
+		next_item = current_item->next;
+		assert(next_item != NULL || current_item == list->last);
+		assert(current_item->chunk != NULL);
+		free(current_item);
+		current_item = next_item;
+	}
+	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 *packet;  /* the newly-allocated result packet */
+	struct header *sctp_header;  /* the SCTP header info */
+	struct sctp_chunk_list_item *item;
+	/* Calculate lengths in bytes of all sections of the packet */
+	const int ip_option_bytes = 0;
+	const int ip_header_bytes = (ip_header_min_len(address_family) +
+				     ip_option_bytes);
+	const int sctp_header_bytes = sizeof(struct sctp_common_header);
+	const int sctp_chunk_bytes = list->length;
+	const int ip_bytes =
+		 ip_header_bytes + sctp_header_bytes + sctp_chunk_bytes;
+	bool overbook = false;
+
+	/* Sanity-check all the various lengths */
+	if (ip_option_bytes & 0x3) {
+		asprintf(error, "IP options are not padded correctly "
+			 "to ensure IP header is a multiple of 4 bytes: "
+			 "%d excess bytes", ip_option_bytes & 0x3);
+		return NULL;
+	}
+	assert((ip_header_bytes & 0x3) == 0);
+
+	if (ip_bytes > MAX_SCTP_DATAGRAM_BYTES) {
+		asprintf(error, "SCTP packet too large");
+		return NULL;
+	}
+
+	if (direction == DIRECTION_INBOUND) {
+		for (item = list->first; item != NULL; item = item->next) {
+			switch (item->chunk->type) {
+			case SCTP_DATA_CHUNK_TYPE:
+				if (item->flags & FLAG_CHUNK_FLAGS_NOCHECK) {
+					asprintf(error,
+						 "chunk flags must be specified for inbound packets");
+					return NULL;
+				}
+				if (item->flags & FLAG_CHUNK_LENGTH_NOCHECK) {
+					asprintf(error,
+						 "chunk length must be specified for inbound packets");
+					return NULL;
+				}				
+				if (item->flags & FLAG_DATA_CHUNK_TSN_NOCHECK) {
+					asprintf(error,
+						 "TSN must be specified for inbound packets");
+					return NULL;
+				}
+				if (item->flags & FLAG_DATA_CHUNK_SID_NOCHECK) {
+					asprintf(error,
+						 "SID must be specified for inbound packets");
+					return NULL;
+				}
+				if (item->flags & FLAG_DATA_CHUNK_SSN_NOCHECK) {
+					asprintf(error,
+						 "SSN must be specified for inbound packets");
+					return NULL;
+				}
+				if (item->flags & FLAG_DATA_CHUNK_PPID_NOCHECK) {
+					asprintf(error,
+						 "PPID must be specified for inbound packets");
+					return NULL;
+				}	
+				break;
+			case SCTP_INIT_CHUNK_TYPE:
+				if (item->flags & FLAG_INIT_CHUNK_A_RWND_NOCHECK) {
+					asprintf(error,
+						 "A_RWND must be specified for inbound packets");
+					return NULL;
+				}
+				if (item->flags & FLAG_INIT_CHUNK_OS_NOCHECK) {
+					asprintf(error,
+						 "OS must be specified for inbound packets");
+					return NULL;
+				}
+				if (item->flags & FLAG_INIT_CHUNK_IS_NOCHECK) {
+					asprintf(error,
+						 "IS must be specified for inbound packets");
+					return NULL;
+				}
+				break;
+			case SCTP_INIT_ACK_CHUNK_TYPE:
+				if (item->flags & FLAG_INIT_ACK_CHUNK_A_RWND_NOCHECK) {
+					asprintf(error,
+						 "A_RWND must be specified for inbound packets");
+					return NULL;
+				}
+				if (item->flags & FLAG_INIT_ACK_CHUNK_OS_NOCHECK) {
+					asprintf(error,
+						 "OS must be specified for inbound packets");
+					return NULL;
+				}
+				if (item->flags & FLAG_INIT_ACK_CHUNK_IS_NOCHECK) {
+					asprintf(error,
+						 "IS must be specified for inbound packets");
+					return NULL;
+				}
+				break;
+			case SCTP_SACK_CHUNK_TYPE:
+				if (item->flags & FLAG_SACK_CHUNK_CUM_TSN_NOCHECK) {
+					asprintf(error,
+						 "CUM_TSN must be specified for inbound packets");
+					return NULL;
+				}
+				if (item->flags & FLAG_SACK_CHUNK_A_RWND_NOCHECK) {
+					asprintf(error,
+						 "A_RWND must be specified for inbound packets");
+					return NULL;
+				}				
+				if (item->flags & FLAG_SACK_CHUNK_GAP_BLOCKS_NOCHECK) {
+					asprintf(error,
+						 "GAP_BLOCKS must be specified for inbound packets");
+					return NULL;
+				}
+				if (item->flags & FLAG_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:
+				break;
+			case SCTP_ABORT_CHUNK_TYPE:
+				if (item->flags & FLAG_CHUNK_LENGTH_NOCHECK) {
+					asprintf(error,
+						 "error causes must be specified for inbound packets");
+					return NULL;
+				}				
+				break;
+			case SCTP_SHUTDOWN_CHUNK_TYPE:
+				if (item->flags & FLAG_SHUTDOWN_CHUNK_CUM_TSN_NOCHECK) {
+					asprintf(error,
+						 "TSN must be specified for inbound packets");
+					return NULL;
+				}
+				break;
+			case SCTP_SHUTDOWN_ACK_CHUNK_TYPE:
+				break;
+			case SCTP_ERROR_CHUNK_TYPE:
+				if (item->flags & FLAG_CHUNK_LENGTH_NOCHECK) {
+					asprintf(error,
+						 "error causes must be specified for inbound packets");
+					return NULL;
+				}				
+				break;
+			case SCTP_COOKIE_ECHO_CHUNK_TYPE:
+				overbook = true;
+				break;
+			case SCTP_COOKIE_ACK_CHUNK_TYPE:
+				break;
+			case SCTP_ECNE_CHUNK_TYPE:
+				if (item->flags & FLAG_ECNE_CHUNK_LOWEST_TSN_NOCHECK) {
+					asprintf(error,
+						 "LOWEST_TSN must be specified for inbound packets");
+					return NULL;
+				}
+				break;
+			case SCTP_CWR_CHUNK_TYPE:
+				if (item->flags & FLAG_CWR_CHUNK_LOWEST_TSN_NOCHECK) {
+					asprintf(error,
+						 "LOWEST_TSN must be specified for inbound packets");
+					return NULL;
+				}
+				break;
+			case SCTP_SHUTDOWN_COMPLETE_CHUNK_TYPE:
+				break;
+			default:
+				asprintf(error, "Unknown chunk type 0x%02x", item->chunk->type);
+				return NULL;
+			}
+		}
+	}
+	
+	/* Allocate and zero out a packet object of the desired size */
+	packet = packet_new(overbook ? MAX_SCTP_DATAGRAM_BYTES : ip_bytes);
+	memset(packet->buffer, 0, overbook ? MAX_SCTP_DATAGRAM_BYTES : ip_bytes);
+
+	packet->direction = direction;
+	packet->flags = 0;
+	packet->ecn = ecn;
+
+	/* Set IP header fields */
+	set_packet_ip_header(packet, address_family, ip_bytes, ecn,
+			     IPPROTO_SCTP);
+
+	sctp_header = packet_append_header(packet, HEADER_SCTP, sctp_header_bytes);
+	sctp_header->total_bytes = sctp_header_bytes + sctp_chunk_bytes;
+
+	/* Find the start of the SCTP common header of the packet */
+	packet->sctp = (struct sctp_common_header *) (ip_start(packet) + ip_header_bytes);
+	u8 *sctp_chunk_start = (u8 *) (packet->sctp + 1);
+
+	/* Set SCTP header fields */
+	packet->sctp->src_port = htons(0);
+	packet->sctp->dst_port = htons(0);
+	packet->sctp->v_tag = htonl(0);
+	packet->sctp->crc32c = htonl(0);
+
+	for (item = list->first; item != NULL; item = item->next) {
+		DEBUGP("Copy in a chunk of length %d\n", item->length);
+		memcpy(sctp_chunk_start, item->chunk, item->length);
+		DEBUGP("Old location: %p\n", (void *)item->chunk);
+		free(item->chunk);
+		item->chunk = (struct sctp_chunk *)sctp_chunk_start;
+		DEBUGP("New location: %p\n", (void *)item->chunk);
+		sctp_chunk_start += item->length;
+	}
+	free(packet->chunk_list);
+	packet->chunk_list = list;
+	packet->ip_bytes = ip_bytes;
+	return packet;
+}
diff --git a/gtests/net/packetdrill/sctp_packet.h b/gtests/net/packetdrill/sctp_packet.h
new file mode 100644
index 0000000000000000000000000000000000000000..b2e90e851e6cd111560980ca03d3330d9db0ec8f
--- /dev/null
+++ b/gtests/net/packetdrill/sctp_packet.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2015 Michael Tuexen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+/*
+ * Author: tuexen@fh-muenster.de (Michael Tuexen)
+ *
+ * Interface for module for formatting SCTP packets.
+ */
+
+#ifndef __SCTP_PACKET_H__
+#define __SCTP_PACKET_H__
+
+#include "types.h"
+#include "packet.h"
+#include "sctp.h"
+
+struct sctp_chunk_list_item {
+	struct sctp_chunk_list_item *next;
+	struct sctp_chunk *chunk;
+	/* total length in bytes */
+	u32 length;
+	/* metadata */
+	u32 flags;
+};
+
+struct sctp_chunk_list {
+	struct sctp_chunk_list_item *first;
+	struct sctp_chunk_list_item *last;
+	/* length in bytes */
+	u32 length;
+};
+
+#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);
+
+#define FLAG_DATA_CHUNK_TSN_NOCHECK             0x00000100
+#define FLAG_DATA_CHUNK_SID_NOCHECK             0x00000200
+#define FLAG_DATA_CHUNK_SSN_NOCHECK             0x00000400
+#define FLAG_DATA_CHUNK_PPID_NOCHECK            0x00000800
+
+struct sctp_chunk_list_item *
+sctp_data_chunk_new(s64 tsn, s64 sid, s64 ssn, s64 ppid);
+
+#define FLAG_INIT_CHUNK_A_RWND_NOCHECK          0x00000100
+#define FLAG_INIT_CHUNK_OS_NOCHECK              0x00000200
+#define FLAG_INIT_CHUNK_IS_NOCHECK              0x00000400
+
+struct sctp_chunk_list_item *
+sctp_init_chunk_new(s64 tag, s64 a_rwnd, s64 os, s64 is, s64 tsn);
+
+#define FLAG_INIT_ACK_CHUNK_A_RWND_NOCHECK      0x00000100
+#define FLAG_INIT_ACK_CHUNK_OS_NOCHECK          0x00000200
+#define FLAG_INIT_ACK_CHUNK_IS_NOCHECK          0x00000400
+
+struct sctp_chunk_list_item *
+sctp_init_ack_chunk_new(s64 tag, s64 a_rwnd, s64 os, s64 is, s64 tsn);
+
+#define FLAG_SACK_CHUNK_CUM_TSN_NOCHECK         0x00000100
+#define FLAG_SACK_CHUNK_A_RWND_NOCHECK          0x00000200
+#define FLAG_SACK_CHUNK_GAP_BLOCKS_NOCHECK      0x00000400
+#define FLAG_SACK_CHUNK_DUP_TSNS_NOCHECK        0x00000800
+
+struct sctp_chunk_list_item *
+sctp_sack_chunk_new(s64 cum_tsn, s64 a_rwnd);
+
+struct sctp_chunk_list_item *
+sctp_heartbeat_chunk_new(u8 flags);
+
+struct sctp_chunk_list_item *
+sctp_heartbeat_ack_chunk_new(u8 flags);
+
+struct sctp_chunk_list_item *
+sctp_abort_chunk_new(u8 flags);
+
+#define FLAG_SHUTDOWN_CHUNK_CUM_TSN_NOCHECK     0x00000100
+
+struct sctp_chunk_list_item *
+sctp_shutdown_chunk_new(s64 cum_tsn);
+
+struct sctp_chunk_list_item *
+sctp_shutdown_ack_chunk_new(u8 flags);
+
+struct sctp_chunk_list_item *
+sctp_error_chunk_new(u8 flags);
+
+struct sctp_chunk_list_item *
+sctp_cookie_echo_chunk_new(u8 flags);
+
+struct sctp_chunk_list_item *
+sctp_cookie_ack_chunk_new(u8 flags);
+
+#define FLAG_ECNE_CHUNK_LOWEST_TSN_NOCHECK      0x00000100
+
+struct sctp_chunk_list_item *
+sctp_ecne_chunk_new(s64 lowest_tsn);
+
+#define FLAG_CWR_CHUNK_LOWEST_TSN_NOCHECK       0x00000100
+
+struct sctp_chunk_list_item *
+sctp_cwr_chunk_new(s64 lowest_tsn);
+
+struct sctp_chunk_list_item *
+sctp_shutdown_complete_chunk_new(u8 flags);
+
+struct sctp_chunk_list *sctp_chunk_list_new(void);
+
+void sctp_chunk_list_append(struct sctp_chunk_list *list,
+			    struct sctp_chunk_list_item *item);
+
+void sctp_chunk_list_free(struct sctp_chunk_list *list);
+
+/* Create and initialize a new struct packet containing a SCTP packet.
+ * On success, returns a newly-allocated packet. On failure, returns NULL
+ * and fills in *error with an error message.
+ */
+extern struct packet *new_sctp_packet(int address_family,
+				      enum direction_t direction,
+				      enum ip_ecn_t ecn,
+				      struct sctp_chunk_list *chunk_list,
+				      char **error);
+#endif /* __SCTP_PACKET_H__ */
diff --git a/gtests/net/packetdrill/socket.h b/gtests/net/packetdrill/socket.h
index 41b23c88df9507a38d411e4b84460ae0e0bb0047..f38515f8b6dad74762d1fa1ff7e14a9b75a62c0c 100644
--- a/gtests/net/packetdrill/socket.h
+++ b/gtests/net/packetdrill/socket.h
@@ -43,9 +43,13 @@ enum socket_state_t {
 	SOCKET_PASSIVE_PACKET_RECEIVED,	/* after receiving first packet */
 	SOCKET_PASSIVE_SYNACK_SENT,	/* after sending SYNACK */
 	SOCKET_PASSIVE_SYNACK_ACKED,	/* after server's SYN is ACKed */
+	SOCKET_PASSIVE_INIT_ACK_SENT,
+	SOCKET_PASSIVE_COOKIE_ECHO_RECEIVED,
 	SOCKET_ACTIVE_CONNECTING,	/* after connect() call */
 	SOCKET_ACTIVE_SYN_SENT,		/* after sending client's SYN */
 	SOCKET_ACTIVE_SYN_ACKED,	/* after client's SYN is ACKed */
+	SOCKET_ACTIVE_INIT_SENT,	/* after sending client's INIT */
+	SOCKET_ACTIVE_INIT_ACK_RECEIVED	/* after receiving server's INIT-ACK */
 };
 
 /* A TCP/UDP/IP address for an endpoint. */
@@ -64,9 +68,17 @@ struct tuple {
 struct socket_state {
 	int fd;				/* file descriptor for this socket */
 	struct endpoint local;		/* local endpoint address */
+	/* TCP specific */
 	u32 local_isn;			/* initial TCP sequence (host order) */
+	/* SCTP specific (in host byte order ) */
+	u32 local_initial_tsn;		/* initial TSN */
+	u32 local_initiate_tag;		/* v-tag expected from the peer */
 	struct endpoint remote;		/* remote endpoint address */
+	/* TCP specific */
 	u32 remote_isn;			/* initial TCP sequence (host order) */
+	/* SCTP specific (in host byte order) */
+	u32 remote_initial_tsn;		/* initial TSN */
+	u32 remote_initiate_tag;	/* v-tag expected by the peer */
 };
 
 /* The runtime state for a socket */
@@ -104,6 +116,9 @@ struct socket {
 	struct tcp last_injected_tcp_header;
 	u32 last_injected_tcp_payload_len;
 
+	struct sctp_cookie_echo_chunk *prepared_state_cookie;
+	u16 prepared_state_cookie_length;
+
 	struct socket *next;	/* next in linked list of sockets */
 };
 
diff --git a/gtests/net/packetdrill/tests/bsd/sctp/sctp.pkt b/gtests/net/packetdrill/tests/bsd/sctp/sctp.pkt
new file mode 100644
index 0000000000000000000000000000000000000000..a4a590a3c238841b3fd5ee2f39d24e0feb3e0bae
--- /dev/null
+++ b/gtests/net/packetdrill/tests/bsd/sctp/sctp.pkt
@@ -0,0 +1,29 @@
+ 0.0 `sysctl -w net.inet.sctp.ecn_enable=0`
++0.0 `sysctl -w net.inet.sctp.pr_enable=0`
++0.0 `sysctl -w net.inet.sctp.asconf_enable=0`
++0.0 `sysctl -w net.inet.sctp.auth_enable=0`
++0.0 `sysctl -w net.inet.sctp.reconfig_enable=0`
++0.0 `sysctl -w net.inet.sctp.nrsack_enable=0`
++0.0 `sysctl -w net.inet.sctp.pktdrop_enable=0`
++0.0 socket(..., SOCK_STREAM, IPPROTO_SCTP) = 3
++0.0 fcntl(3, F_GETFL) = 0x2 (flags O_RDWR)
++0.0 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0
++0.1 connect(3, ..., ...) = -1 EINPROGRESS (Operation now in progress)
++0.0 > sctp: INIT[tag=1 tsn=0]
++0.0 < sctp: INIT_ACK[tag=2 a_rwnd=1500 os=1 is =1 tsn=3] // faked
++0.0 > sctp: COOKIE_ECHO[] // syntax not clear
++0.0 < sctp: COOKIE_ACK[]
++0.0 getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
++1.0 write(3, ..., 1000) = 1000
++0.0 > sctp: DATA[tsn=0 sid=0 ssn=0 ppid=0]
++0.0 < sctp: SACK[tsn=0 a_rwnd=1500]
++1.0 < sctp: DATA[tsn=3 sid=0 ssn=0 ppid=0] // How to handle
++0.0 > sctp: SACK[tsn=3]
++0.0 read(3, ..., 2000) = 1000
++1.0 < sctp: DATA[tsn=4 sid=0 ssn=1 ppid=0]
++0.0 read(3, ..., 2000) = 1000
++0.2 > sctp: SACK[tsn=4]
++0.0 close(3) = 0
++0.0 > sctp: SHUTDOWN[tsn=4]
++0.0 < sctp: SHUTDOWN_ACK[]
++0.0 > sctp: SHUTDOWN_COMPLETE[]
diff --git a/gtests/net/packetdrill/tests/bsd/sctp/sctp_active.pkt b/gtests/net/packetdrill/tests/bsd/sctp/sctp_active.pkt
new file mode 100644
index 0000000000000000000000000000000000000000..1cc9a61807ec65858904874ebf3e45036b4b5852
--- /dev/null
+++ b/gtests/net/packetdrill/tests/bsd/sctp/sctp_active.pkt
@@ -0,0 +1,27 @@
++0.0 socket(..., SOCK_STREAM, IPPROTO_SCTP) = 3
++0.0 fcntl(3, F_GETFL) = 0x2 (flags O_RDWR)
++0.0 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0
+// Check the handshake with en empty(!) cookie
++0.1 connect(3, ..., ...) = -1 EINPROGRESS (Operation now in progress)
++0.0 > sctp: INIT[tag=1 tsn=0]
++0.1 < sctp: INIT_ACK[tag=2 a_rwnd=1500  os=1 is =1 tsn=3] // faked cookie
++0.0 > sctp: COOKIE_ECHO[] // syntax not clear
++0.1 < sctp: COOKIE_ACK[]
++0.0 getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
+// Send some data.
++1.0 write(3, ..., 1000) = 1000
++0.0 > sctp: DATA[tsn=0 sid=0 ssn=0 ppid=0]
++0.1 < sctp: SACK[tsn=0 a_rwnd=1500]
+// Receive some data
++1.0 < sctp: DATA[tsn=3 sid=0 ssn=0 ppid=0] // How to handle
++0.0 read(3, ..., 2000) = 1000
++0.0 > sctp: SACK[tsn=3]
+// Receive more data, observe delayed SACK
++1.0 < sctp: DATA[tsn=4 sid=0 ssn=1 ppid=0]
++0.0 read(3, ..., 2000) = 1000
++0.2 > sctp: SACK[tsn=4]
+// Tear down the association
++0.0 close(3) = 0
++0.0 > sctp: SHUTDOWN[tsn=4]
++0.1 < sctp: SHUTDOWN_ACK[]
++0.0 > sctp: SHUTDOWN_COMPLETE[]
diff --git a/gtests/net/packetdrill/tests/bsd/sctp/sctp_handle_init.pkt b/gtests/net/packetdrill/tests/bsd/sctp/sctp_handle_init.pkt
new file mode 100644
index 0000000000000000000000000000000000000000..eafcaf511913f7ad930b38a0c0e6f22ff19d09d1
--- /dev/null
+++ b/gtests/net/packetdrill/tests/bsd/sctp/sctp_handle_init.pkt
@@ -0,0 +1,7 @@
+ 0.0  socket(..., SOCK_STREAM, IPPROTO_SCTP) = 3
++0.0  bind(3, ..., ...) = 0
++0.0  listen(3, 1) = 0
+// The initiate tag in the INIT chunk MUST NOT be zero.
++0.1 < sctp: INIT[tag=0, a_rwnd=1500, is=1, os=1, tsn=0]
++0.0 > sctp: ABORT[INVALID_MANDATORY_PARAMETER[]]
++0.0 close(3) = 0
diff --git a/gtests/net/packetdrill/tests/bsd/sctp/sctp_init_rtx.pkt b/gtests/net/packetdrill/tests/bsd/sctp/sctp_init_rtx.pkt
new file mode 100644
index 0000000000000000000000000000000000000000..2254235e731b1dfe92447ee4507bf53b99d4f7cf
--- /dev/null
+++ b/gtests/net/packetdrill/tests/bsd/sctp/sctp_init_rtx.pkt
@@ -0,0 +1,26 @@
+ 0.0 `sysctl -w net.inet.sctp.ecn_enable=0`
++0.0 `sysctl -w net.inet.sctp.pr_enable=0`
++0.0 `sysctl -w net.inet.sctp.asconf_enable=0`
++0.0 `sysctl -w net.inet.sctp.auth_enable=0`
++0.0 `sysctl -w net.inet.sctp.reconfig_enable=0`
++0.0 `sysctl -w net.inet.sctp.nrsack_enable=0`
++0.0 `sysctl -w net.inet.sctp.pktdrop_enable=0`
++0.0 socket(..., SOCK_STREAM, IPPROTO_SCTP) = 3
++0.0 fcntl(3, F_GETFL) = 0x2 (flags O_RDWR)
++0.0 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0
++0.0 setsockopt(3, IPPROTO_SCTP, SCTP_RTOINFO, {srto_initial=100, srto_max=800, srto_min=100}, 16) = 0
++0.0 connect(3, ..., ...) = -1 EINPROGRESS (Operation now in progress)
+// Check the number and the timing of the restransmissions
++0.0 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...]
++0.1 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...] 
++0.2 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...] 
++0.4 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...]
++0.8 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...]
++0.8 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...]
++0.8 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...]
++0.8 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...] 
++0.8 > sctp: INIT[tag=..., a_rwnd=..., is=10, os=2048, tsn=0, ...]
+// Look as if we don't send a final ABORT. Shouldn't we?
+// +1.0 > sctp: ABORT[]
++1.0 getsockopt(3, SOL_SOCKET, SO_ERROR, [ETIMEDOUT], [4]) = 0
++0.0 close(3) = 0
diff --git a/gtests/net/packetdrill/tests/bsd/sctp/sctp_passive.pkt b/gtests/net/packetdrill/tests/bsd/sctp/sctp_passive.pkt
new file mode 100644
index 0000000000000000000000000000000000000000..563e07562563ae8b72a5ef9349a9be425c71bd2a
--- /dev/null
+++ b/gtests/net/packetdrill/tests/bsd/sctp/sctp_passive.pkt
@@ -0,0 +1,27 @@
++0.0 socket(..., SOCK_STREAM, IPPROTO_SCTP) = 3
+// Check the handshake with en empty(!) cookie
++0.0 bind(3, ..., ...) = 0
++0.0 listen(3, 1) = 0
++0.0 < sctp: INIT[tag=1 a_rwnd=1500 os=1 is=1 tsn=0]
++0.0 > sctp: INIT_ACK[tag=2 tsn=10] // faked cookie
++0.1 < sctp: COOKIE_ECHO[] // syntax not clear
++0.0 > sctp: COOKIE_ACK[]
++0.0 accept(3, ..., ...) = 4
+// Send some data.
++1.0 write(4, ..., 1000) = 1000
++0.0 > sctp: DATA[tsn=10 sid=0 ssn=0 ppid=0]
++0.1 < sctp: SACK[tsn=10 a_rwnd=1500]
+// Receive some data
++1.0 < sctp: DATA[tsn=0 sid=0 ssn=0 ppid=0] // How to handle payload?
++0.0 read(4, ..., 2000) = 1000
++0.0 > sctp: SACK[tsn=0]
+// Receive more data, observe delayed SACKi
++1.0 < sctp: DATA[tsn=1 sid=0 ssn=1 ppid=0]
++0.0 read(4, ..., 2000) = 1000
++0.2 > sctp: SACK[tsn=1]
+// Tear down the association
++1.0 < sctp: SHUTDOWN[tsn=0]
++0.0 > sctp: SHUTDOWN_ACK[]
++0.0 < sctp: SHUTDOWN_COMPLETE[]
++0.0 close(4) = 0
++0.0 close(3) = 0