diff --git a/gtests/net/packetdrill/Makefile.common b/gtests/net/packetdrill/Makefile.common
index 9ff5027c931ae354cfd5fcdbbbf56634bfa4b312..e58ab8540bbe7e2b52732fb2d6ad1534c6fc8235 100644
--- a/gtests/net/packetdrill/Makefile.common
+++ b/gtests/net/packetdrill/Makefile.common
@@ -24,6 +24,7 @@ packetdrill-lib := \
          mpls_packet.o \
          run.o run_command.o run_packet.o run_system_call.o \
          script.o socket.o system.o \
+         sctp_chunk_to_string.o sctp_iterator.o \
          tcp_options.o tcp_options_iterator.o tcp_options_to_string.o \
          logging.o types.o lexer.o parser.o \
          fmemopen.o open_memstream.o \
diff --git a/gtests/net/packetdrill/header.h b/gtests/net/packetdrill/header.h
index 389b126dd3c2c31266899f5a3b258d819d62b2ca..e0a3e09aa824c8eb603c4a82a371850527cb96d8 100644
--- a/gtests/net/packetdrill/header.h
+++ b/gtests/net/packetdrill/header.h
@@ -39,6 +39,7 @@
 #include "ip.h"
 #include "ipv6.h"
 #include "mpls.h"
+#include "sctp.h"
 #include "tcp.h"
 #include "udp.h"
 #include "udplite.h"
@@ -52,6 +53,7 @@ enum header_t {
 	HEADER_IPV6,
 	HEADER_GRE,
 	HEADER_MPLS,
+	HEADER_SCTP,
 	HEADER_TCP,
 	HEADER_UDP,
 	HEADER_UDPLITE,
@@ -71,6 +73,7 @@ struct header {
 		struct ipv6 *ipv6;
 		struct gre *gre;
 		struct mpls *mpls;
+		struct sctp_common_header *sctp;
 		struct tcp *tcp;
 		struct udp *udp;
 		struct udplite *udplite;
diff --git a/gtests/net/packetdrill/packet.c b/gtests/net/packetdrill/packet.c
index bb62f3b69535cf41edf4db9f9d905aef2aaa9787..dbb33b177c743bdfcb7cc98b53893a34f4bd4398 100644
--- a/gtests/net/packetdrill/packet.c
+++ b/gtests/net/packetdrill/packet.c
@@ -41,6 +41,7 @@ struct header_type_info header_types[HEADER_NUM_TYPES] = {
 	{ "IPV6",    IPPROTO_IPV6,	ETHERTYPE_IPV6, ipv6_header_finish },
 	{ "GRE",     IPPROTO_GRE,	0,		gre_header_finish },
 	{ "MPLS",    0,			ETHERTYPE_MPLS_UC, mpls_header_finish },
+	{ "SCTP",    IPPROTO_SCTP,	0,		NULL },
 	{ "TCP",     IPPROTO_TCP,	0,		NULL },
 	{ "UDP",     IPPROTO_UDP,	0,		NULL },
 	{ "UDPLITE", IPPROTO_UDPLITE,	0,		NULL },
@@ -158,6 +159,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);
+	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);
 	packet->udplite	= offset_ptr(old_base, new_base, old_packet->udplite);
diff --git a/gtests/net/packetdrill/packet.h b/gtests/net/packetdrill/packet.h
index 57f8f73d01754c5ed475dbc3f121c295435eb729..06ddf02dcdd4f5f1140e757c8297e6d94967f1d9 100644
--- a/gtests/net/packetdrill/packet.h
+++ b/gtests/net/packetdrill/packet.h
@@ -36,6 +36,7 @@
 #include "icmpv6.h"
 #include "ip.h"
 #include "ipv6.h"
+#include "sctp.h"
 #include "tcp.h"
 #include "udp.h"
 #include "udplite.h"
@@ -47,6 +48,7 @@
 #define MAX_TCP_HEADER_BYTES (15*4)
 
 #define MAX_TCP_DATAGRAM_BYTES (64*1024)	/* for sanity-checking */
+#define MAX_SCTP_DATAGRAM_BYTES (64*1024)	/* for sanity-checking */
 #define MAX_UDP_DATAGRAM_BYTES (64*1024)	/* for sanity-checking */
 #define MAX_UDPLITE_DATAGRAM_BYTES (64*1024)	/* for sanity-checking */
 
@@ -92,6 +94,8 @@ struct packet {
 	struct ipv6 *ipv6;	/* start of IPv6 header, if present */
 
 	/* Layer 4 */
+	struct sctp_common_header *sctp;
+				/* start of SCTP common header, if present */
 	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 */
@@ -220,6 +224,8 @@ static inline int packet_ip_protocol(const struct packet *packet)
 /* Return the length of an optionless TCP or UDP header. */
 static inline int layer4_header_len(int protocol)
 {
+	if (protocol == IPPROTO_SCTP)
+		return sizeof(struct sctp_common_header);
 	if (protocol == IPPROTO_TCP)
 		return sizeof(struct tcp);
 	if (protocol == IPPROTO_UDP)
@@ -230,6 +236,13 @@ static inline int layer4_header_len(int protocol)
 	return 0;
 }
 
+/* Return the length of the SCTP common header. */
+static inline int packet_sctp_header_len(const struct packet *packet)
+{
+	assert(packet->sctp);
+	return sizeof(struct sctp_common_header);
+}
+
 /* Return the length of the TCP header, including options. */
 static inline int packet_tcp_header_len(const struct packet *packet)
 {
@@ -288,6 +301,8 @@ static inline void packet_set_tcp_ts_ecr(struct packet *packet, u32 ts_ecr)
 /* Return a pointer to the TCP/UDP data payload. */
 static inline u8 *packet_payload(struct packet *packet)
 {
+	if (packet->sctp)
+		return ((u8 *) packet->sctp) + packet_sctp_header_len(packet);
 	if (packet->tcp)
 		return ((u8 *) packet->tcp) + packet_tcp_header_len(packet);
 	if (packet->udp)
@@ -295,7 +310,7 @@ static inline u8 *packet_payload(struct packet *packet)
 	if (packet->udplite)
 		return ((u8 *) packet->udplite) +
 		       packet_udplite_header_len(packet);
-	assert(!"no valid payload; not TCP or UDP or UDPLite!?");
+	assert(!"no valid payload; not SCTP or TCP or UDP or UDPLite!?");
 	return NULL;
 }
 
@@ -360,7 +375,7 @@ static inline int packet_echoed_ip_protocol(struct packet *packet)
 	return 0;
 }
 
-/* Return the location of the TCP or UDP header echoed by an ICMP message. */
+/* Return the location of the transport header echoed by an ICMP message. */
 static inline u8 *packet_echoed_layer4_header(struct packet *packet)
 {
 	u8 *echoed_ip = packet_echoed_ip_header(packet);
@@ -368,6 +383,16 @@ static inline u8 *packet_echoed_layer4_header(struct packet *packet)
 	return echoed_ip + ip_header_len;
 }
 
+/* Return the location of the SCTP common header echoed by an ICMP message. */
+static inline struct sctp_common_header *
+packet_echoed_sctp_header(struct packet *packet)
+{
+	if (packet_echoed_ip_protocol(packet) == IPPROTO_SCTP)
+		return (struct sctp_common_header *)
+		       (packet_echoed_layer4_header(packet));
+	return NULL;
+}
+
 /* Return the location of the TCP header echoed by an ICMP message. */
 static inline struct tcp *packet_echoed_tcp_header(struct packet *packet)
 {
diff --git a/gtests/net/packetdrill/packet_parser.c b/gtests/net/packetdrill/packet_parser.c
index 1bd6d0d30365b91600a372139d2147fb0a1a3e37..6599777da5cd10475a61a8486ad165649697ef47 100644
--- a/gtests/net/packetdrill/packet_parser.c
+++ b/gtests/net/packetdrill/packet_parser.c
@@ -210,7 +210,14 @@ static int parse_ipv4(struct packet *packet, u8 *header_start, u8 *packet_end,
 	}
 	const u16 checksum = ipv4_checksum(ipv4, ip_header_bytes);
 	if (checksum != 0) {
-		asprintf(error, "Bad IP checksum");
+		u16 received_checksum, computed_checksum;
+
+		received_checksum = ntohs(ipv4->check);
+		ipv4->check = 0;
+		computed_checksum = ntohs(ipv4_checksum(ipv4, ip_header_bytes));
+		ipv4->check = htons(received_checksum);
+		asprintf(error, "Bad IP checksum 0x%04x (expected 0x%04x)",
+			 received_checksum, computed_checksum);
 		goto error_out;
 	}
 
@@ -326,6 +333,49 @@ error_out:
 	return PACKET_BAD;
 }
 
+/* Parse the SCTP header. Return a packet_parse_result_t. */
+static int parse_sctp(struct packet *packet, u8 *layer4_start, int layer4_bytes,
+		      u8 *packet_end, char **error)
+{
+	u32 received_crc32c, computed_crc32c;
+	struct header *sctp_header = NULL;
+	u8 *p = layer4_start;
+
+	assert(layer4_bytes >= 0);
+	if (layer4_bytes < sizeof(struct sctp_common_header)) {
+		asprintf(error, "Truncated SCTP common header");
+		goto error_out;
+	}
+	packet->sctp = (struct sctp_common_header *) p;
+
+	received_crc32c = ntohl(packet->sctp->crc32c);
+	packet->sctp->crc32c = htonl(0);
+	computed_crc32c = ntohl(sctp_crc32c(packet->sctp, layer4_bytes));
+	packet->sctp->crc32c = htonl(received_crc32c);
+	if (received_crc32c != computed_crc32c) {
+		asprintf(error, "Bad SCTP checksum 0x%08x (expected 0x%08x)",
+			 received_crc32c, computed_crc32c);
+		goto error_out;
+	}
+	const int sctp_header_len = sizeof(struct sctp_common_header);
+	sctp_header = packet_append_header(packet, HEADER_SCTP,
+					   sctp_header_len);
+	if (sctp_header == NULL) {
+		asprintf(error, "Too many nested headers at SCTP header");
+		goto error_out;
+	}
+	sctp_header->total_bytes = layer4_bytes;
+	p += layer4_bytes;
+	assert(p <= packet_end);
+
+	DEBUGP("SCTP src port: %d\n", ntohs(packet->sctp->src_port));
+	DEBUGP("SCTP dst port: %d\n", ntohs(packet->sctp->dst_port));
+	return PACKET_OK;
+
+error_out:
+	return PACKET_BAD;
+}
+
 /* Parse the TCP header. Return a packet_parse_result_t. */
 static int parse_tcp(struct packet *packet, u8 *layer4_start, int layer4_bytes,
 		     u8 *packet_end, char **error)
@@ -626,7 +676,11 @@ static int parse_layer4(struct packet *packet, u8 *layer4_start,
 			int layer4_protocol, int layer4_bytes,
 			u8 *packet_end, bool *is_inner, char **error)
 {
-	if (layer4_protocol == IPPROTO_TCP) {
+	if (layer4_protocol == IPPROTO_SCTP) {
+		*is_inner = true;	/* found inner-most layer 4 */
+		return parse_sctp(packet, layer4_start, layer4_bytes,
+				  packet_end, error);
+	} else if (layer4_protocol == IPPROTO_TCP) {
 		*is_inner = true;	/* found inner-most layer 4 */
 		return parse_tcp(packet, layer4_start, layer4_bytes, packet_end,
 				 error);
diff --git a/gtests/net/packetdrill/packet_parser_test.c b/gtests/net/packetdrill/packet_parser_test.c
index cbc9c3cea5b452ad17678c777bb2a04f7ee0fd4d..6eb6b0bd412183a65aa4ff828210f55011624c79 100644
--- a/gtests/net/packetdrill/packet_parser_test.c
+++ b/gtests/net/packetdrill/packet_parser_test.c
@@ -29,6 +29,96 @@
 #include <stdlib.h>
 #include <string.h>
 
+static void test_parse_sctp_ipv4_packet(void)
+{
+	/* A SCTP/IPv4 packet. */
+	u8 data[] = {
+		/* 1.1.1.1:1234 > 192.168.0.1:60213
+		 * sctp: ABORT[] */
+		0x45, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00,
+		0xff, 0x84, 0xf8, 0xaa, 0x01, 0x01, 0x01, 0x01,
+		0xc0, 0xa8, 0x00, 0x01, 0x04, 0xd2, 0xeb, 0x35,
+		0x01, 0x02, 0x03, 0x04, 0xab, 0x0c, 0xeb, 0x7a,
+		0x06, 0x01, 0x00, 0x04
+	};
+
+	struct packet *packet = packet_new(sizeof(data));
+
+	/* Populate and parse a packet */
+	memcpy(packet->buffer, data, sizeof(data));
+	char *error = NULL;
+	enum packet_parse_result_t result =
+		parse_packet(packet, sizeof(data), ETHERTYPE_IP, &error);
+	assert(result == PACKET_OK);
+	assert(error == NULL);
+
+	struct ipv4 *expected_ipv4 = (struct ipv4 *)(packet->buffer);
+	struct sctp_common_header *expected_sctp =
+		(struct sctp_common_header *)(expected_ipv4 + 1);
+
+	assert(packet->ip_bytes		== sizeof(data));
+	assert(packet->ipv4		== expected_ipv4);
+	assert(packet->ipv6		== NULL);
+	assert(packet->sctp		== expected_sctp);
+	assert(packet->tcp		== NULL);
+	assert(packet->udp		== NULL);
+	assert(packet->udplite		== NULL);
+	assert(packet->icmpv4		== NULL);
+	assert(packet->icmpv6		== NULL);
+
+	assert(packet->time_usecs	== 0);
+	assert(packet->flags		== 0);
+	assert(packet->ecn		== 0);
+
+	packet_free(packet);
+}
+
+static void test_parse_sctp_ipv6_packet(void)
+{
+	/* A SCTP/IPv6 packet. */
+	u8 data[] = {
+		/* 2001:db8::1:54242 > fd3d:fa7b:d17d::1:8080
+		 * sctp: ABORT[] */
+		0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0x84, 0xff,
+		0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+		0xfd, 0x3d, 0xfa, 0x7b, 0xd1, 0x7d, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+		0xd3, 0xe2, 0x1f, 0x90, 0x01, 0x02, 0x03, 0x04,
+		0xc4, 0xb0, 0x47, 0x64, 0x06, 0x01, 0x00, 0x04
+	};
+
+	struct packet *packet = packet_new(sizeof(data));
+
+	/* Populate and parse a packet */
+	memcpy(packet->buffer, data, sizeof(data));
+	char *error = NULL;
+	enum packet_parse_result_t result =
+		parse_packet(packet, sizeof(data), ETHERTYPE_IPV6, &error);
+	assert(result == PACKET_OK);
+	assert(error == NULL);
+
+	struct ipv6 *expected_ipv6 = (struct ipv6 *)(packet->buffer);
+	struct sctp_common_header *expected_sctp =
+		(struct sctp_common_header *)(expected_ipv6 + 1);
+
+	assert(packet->ip_bytes		== sizeof(data));
+	assert(packet->ipv4		== NULL);
+	assert(packet->ipv6		== expected_ipv6);
+	assert(packet->sctp		== expected_sctp);
+	assert(packet->tcp		== NULL);
+	assert(packet->udp		== NULL);
+	assert(packet->udplite		== NULL);
+	assert(packet->icmpv4		== NULL);
+	assert(packet->icmpv6		== NULL);
+
+	assert(packet->time_usecs	== 0);
+	assert(packet->flags		== 0);
+	assert(packet->ecn		== 0);
+
+	packet_free(packet);
+}
+
 static void test_parse_tcp_ipv4_packet(void)
 {
 	/* A TCP/IPv4 packet. */
@@ -53,8 +143,7 @@ static void test_parse_tcp_ipv4_packet(void)
 	memcpy(packet->buffer, data, sizeof(data));
 	char *error = NULL;
 	enum packet_parse_result_t result =
-		parse_packet(packet, sizeof(data), ETHERTYPE_IP,
-				     &error);
+		parse_packet(packet, sizeof(data), ETHERTYPE_IP, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -64,6 +153,7 @@ static void test_parse_tcp_ipv4_packet(void)
 	assert(packet->ip_bytes		== sizeof(data));
 	assert(packet->ipv4		== expected_ipv4);
 	assert(packet->ipv6		== NULL);
+	assert(packet->sctp		== NULL);
 	assert(packet->tcp		== expected_tcp);
 	assert(packet->udp		== NULL);
 	assert(packet->udplite		== NULL);
@@ -92,7 +182,7 @@ static void test_parse_tcp_ipv6_packet(void)
 		0xd3, 0xe2, 0x1f, 0x90, 0x00, 0x00, 0x00, 0x00,
 		0x00, 0x00, 0x00, 0x00, 0x80, 0x02, 0x80, 0x18,
 		0x06, 0x60, 0x00, 0x00, 0x02, 0x04, 0x03, 0xe8,
-		0x04, 0x02, 0x01, 0x01, 0x01, 0x03, 0x03, 0x07,
+		0x04, 0x02, 0x01, 0x01, 0x01, 0x03, 0x03, 0x07
 	};
 
 	struct packet *packet = packet_new(sizeof(data));
@@ -101,8 +191,7 @@ static void test_parse_tcp_ipv6_packet(void)
 	memcpy(packet->buffer, data, sizeof(data));
 	char *error = NULL;
 	enum packet_parse_result_t result =
-		parse_packet(packet, sizeof(data), ETHERTYPE_IPV6,
-				     &error);
+		parse_packet(packet, sizeof(data), ETHERTYPE_IPV6, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -112,6 +201,7 @@ static void test_parse_tcp_ipv6_packet(void)
 	assert(packet->ip_bytes		== sizeof(data));
 	assert(packet->ipv4		== NULL);
 	assert(packet->ipv6		== expected_ipv6);
+	assert(packet->sctp		== NULL);
 	assert(packet->tcp		== expected_tcp);
 	assert(packet->udp		== NULL);
 	assert(packet->udplite		== NULL);
@@ -133,7 +223,7 @@ static void test_parse_udp_ipv4_packet(void)
 		0x45, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
 		0xff, 0x11, 0x39, 0x22, 0xc0, 0x00, 0x02, 0x01,
 		0xc0, 0xa8, 0x00, 0x01, 0x1f, 0x90, 0xe1, 0xf5,
-		0x00, 0x0c, 0x7b, 0xa5, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x0c, 0x7b, 0xa5, 0x00, 0x00, 0x00, 0x00
 	};
 
 	struct packet *packet = packet_new(sizeof(data));
@@ -142,8 +232,7 @@ static void test_parse_udp_ipv4_packet(void)
 	memcpy(packet->buffer, data, sizeof(data));
 	char *error = NULL;
 	enum packet_parse_result_t result =
-		parse_packet(packet, sizeof(data), ETHERTYPE_IP,
-				     &error);
+		parse_packet(packet, sizeof(data), ETHERTYPE_IP, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -153,6 +242,7 @@ static void test_parse_udp_ipv4_packet(void)
 	assert(packet->ip_bytes		== sizeof(data));
 	assert(packet->ipv4		== expected_ipv4);
 	assert(packet->ipv6		== NULL);
+	assert(packet->sctp		== NULL);
 	assert(packet->tcp		== NULL);
 	assert(packet->udp		== expected_udp);
 	assert(packet->udplite		== NULL);
@@ -177,7 +267,7 @@ static void test_parse_udp_ipv6_packet(void)
 		0xfd, 0x3d, 0xfa, 0x7b, 0xd1, 0x7d, 0x00, 0x00,
 		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
 		0x1f, 0x90, 0xc9, 0x65, 0x00, 0x0c, 0x1f, 0xee,
-		0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00
 	};
 
 	struct packet *packet = packet_new(sizeof(data));
@@ -186,8 +276,7 @@ static void test_parse_udp_ipv6_packet(void)
 	memcpy(packet->buffer, data, sizeof(data));
 	char *error = NULL;
 	enum packet_parse_result_t result =
-		parse_packet(packet, sizeof(data), ETHERTYPE_IPV6,
-				     &error);
+		parse_packet(packet, sizeof(data), ETHERTYPE_IPV6, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -197,6 +286,7 @@ static void test_parse_udp_ipv6_packet(void)
 	assert(packet->ip_bytes		== sizeof(data));
 	assert(packet->ipv4		== NULL);
 	assert(packet->ipv6		== expected_ipv6);
+	assert(packet->sctp		== NULL);
 	assert(packet->tcp		== NULL);
 	assert(packet->udp		== expected_udp);
 	assert(packet->udplite		== NULL);
@@ -228,8 +318,7 @@ static void test_parse_udplite_ipv4_packet(void)
 	memcpy(packet->buffer, data, sizeof(data));
 	char *error = NULL;
 	enum packet_parse_result_t result =
-		parse_packet(packet, sizeof(data), ETHERTYPE_IP,
-				     &error);
+		parse_packet(packet, sizeof(data), ETHERTYPE_IP, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -240,6 +329,7 @@ static void test_parse_udplite_ipv4_packet(void)
 	assert(packet->ip_bytes		== sizeof(data));
 	assert(packet->ipv4		== expected_ipv4);
 	assert(packet->ipv6		== NULL);
+	assert(packet->sctp		== NULL);
 	assert(packet->tcp		== NULL);
 	assert(packet->udp		== NULL);
 	assert(packet->udplite		== expected_udplite);
@@ -274,8 +364,7 @@ static void test_parse_udplite_ipv6_packet(void)
 	memcpy(packet->buffer, data, sizeof(data));
 	char *error = NULL;
 	enum packet_parse_result_t result =
-		parse_packet(packet, sizeof(data), ETHERTYPE_IPV6,
-				     &error);
+		parse_packet(packet, sizeof(data), ETHERTYPE_IPV6, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -286,6 +375,7 @@ static void test_parse_udplite_ipv6_packet(void)
 	assert(packet->ip_bytes		== sizeof(data));
 	assert(packet->ipv4		== NULL);
 	assert(packet->ipv6		== expected_ipv6);
+	assert(packet->sctp		== NULL);
 	assert(packet->tcp		== NULL);
 	assert(packet->udp		== NULL);
 	assert(packet->udplite		== expected_udplite);
@@ -326,8 +416,7 @@ static void test_parse_ipv4_gre_ipv4_tcp_packet(void)
 	memcpy(packet->buffer, data, sizeof(data));
 	char *error = NULL;
 	enum packet_parse_result_t result =
-		parse_packet(packet, sizeof(data), ETHERTYPE_IP,
-				     &error);
+		parse_packet(packet, sizeof(data), ETHERTYPE_IP, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -365,6 +454,7 @@ static void test_parse_ipv4_gre_ipv4_tcp_packet(void)
 	assert(packet->ip_bytes		== sizeof(data));
 	assert(packet->ipv4		== expected_inner_ipv4);
 	assert(packet->ipv6		== NULL);
+	assert(packet->sctp		== NULL);
 	assert(packet->tcp		== expected_tcp);
 	assert(packet->udp		== NULL);
 	assert(packet->udplite		== NULL);
@@ -418,8 +508,7 @@ static void test_parse_ipv4_gre_mpls_ipv4_tcp_packet(void)
 	memcpy(packet->buffer, data, sizeof(data));
 	char *error = NULL;
 	enum packet_parse_result_t result =
-		parse_packet(packet, sizeof(data), ETHERTYPE_IP,
-				     &error);
+		parse_packet(packet, sizeof(data), ETHERTYPE_IP, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -464,6 +553,7 @@ static void test_parse_ipv4_gre_mpls_ipv4_tcp_packet(void)
 	assert(packet->ip_bytes		== sizeof(data));
 	assert(packet->ipv4		== expected_inner_ipv4);
 	assert(packet->ipv6		== NULL);
+	assert(packet->sctp		== NULL);
 	assert(packet->tcp		== expected_tcp);
 	assert(packet->udp		== NULL);
 	assert(packet->udplite		== NULL);
@@ -487,7 +577,7 @@ static void test_parse_icmpv4_packet(void)
 		0x45, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x40, 0x00,
 		0x40, 0x01, 0xb6, 0xc4, 0xc0, 0xa8, 0x01, 0x65,
 		0xc0, 0xa8, 0x01, 0x67, 0x08, 0x00, 0xcd, 0x2e,
-		0x2a, 0xd0, 0x00, 0x01,
+		0x2a, 0xd0, 0x00, 0x01
 	};
 
 	struct packet *packet = packet_new(sizeof(data));
@@ -496,8 +586,7 @@ static void test_parse_icmpv4_packet(void)
 	memcpy(packet->buffer, data, sizeof(data));
 	char *error = NULL;
 	enum packet_parse_result_t result =
-		parse_packet(packet, sizeof(data), ETHERTYPE_IP,
-				     &error);
+		parse_packet(packet, sizeof(data), ETHERTYPE_IP, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -507,6 +596,7 @@ static void test_parse_icmpv4_packet(void)
 	assert(packet->ip_bytes		== sizeof(data));
 	assert(packet->ipv4		== expected_ipv4);
 	assert(packet->ipv6		== NULL);
+	assert(packet->sctp		== NULL);
 	assert(packet->tcp		== NULL);
 	assert(packet->udp		== NULL);
 	assert(packet->udplite		== NULL);
@@ -550,8 +640,7 @@ static void test_parse_icmpv6_packet(void)
 	memcpy(packet->buffer, data, sizeof(data));
 	char *error = NULL;
 	enum packet_parse_result_t result =
-		parse_packet(packet, sizeof(data), ETHERTYPE_IPV6,
-				     &error);
+		parse_packet(packet, sizeof(data), ETHERTYPE_IPV6, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -561,6 +650,7 @@ static void test_parse_icmpv6_packet(void)
 	assert(packet->ip_bytes		== sizeof(data));
 	assert(packet->ipv4		== NULL);
 	assert(packet->ipv6		== expected_ipv6);
+	assert(packet->sctp		== NULL);
 	assert(packet->tcp		== NULL);
 	assert(packet->udp		== NULL);
 	assert(packet->udplite		== NULL);
@@ -576,6 +666,8 @@ static void test_parse_icmpv6_packet(void)
 
 int main(void)
 {
+	test_parse_sctp_ipv4_packet();
+	test_parse_sctp_ipv6_packet();
 	test_parse_tcp_ipv4_packet();
 	test_parse_tcp_ipv6_packet();
 	test_parse_udp_ipv4_packet();
diff --git a/gtests/net/packetdrill/packet_to_string.c b/gtests/net/packetdrill/packet_to_string.c
index 9fc5e5fb7cd69ded9ebe6d4664484291d8e5ad13..08573d39da96c9f2ea18160c4c92d23ffd147009 100644
--- a/gtests/net/packetdrill/packet_to_string.c
+++ b/gtests/net/packetdrill/packet_to_string.c
@@ -27,6 +27,8 @@
 
 #include <stdlib.h>
 #include "socket.h"
+#include "sctp_iterator.h"
+#include "sctp_chunk_to_string.h"
 #include "tcp_options_to_string.h"
 
 static void endpoints_to_string(FILE *s, const struct packet *packet)
@@ -90,7 +92,7 @@ static int ipv6_header_to_string(FILE *s, struct packet *packet, int layer,
 static int gre_header_to_string(FILE *s, struct packet *packet, int layer,
 				enum dump_format_t format, char **error)
 {
-	fprintf(s, "gre: ");
+	fputs("gre: ", s);
 
 	return STATUS_OK;
 }
@@ -102,7 +104,7 @@ static int mpls_header_to_string(FILE *s, struct packet *packet, int layer,
 	int num_entries = header->header_bytes / sizeof(struct mpls);
 	int i = 0;
 
-	fprintf(s, "mpls");
+	fputs("mpls", s);
 
 	for (i = 0; i < num_entries; ++i) {
 		const struct mpls *mpls = header->h.mpls + i;
@@ -118,6 +120,46 @@ static int mpls_header_to_string(FILE *s, struct packet *packet, int layer,
 	return STATUS_OK;
 }
 
+static int sctp_packet_to_string(FILE *s, struct packet *packet,
+				 enum dump_format_t format, char **error)
+{
+	struct sctp_chunks_iterator iter;
+	struct sctp_chunk *chunk;
+	u16 index;
+	int result = STATUS_OK;       /* return value */
+
+	assert(*error == NULL);
+	if ((format == DUMP_FULL) || (format == DUMP_VERBOSE)) {
+		endpoints_to_string(s, packet);
+		fputc(' ', s);
+	}
+
+	fputs("sctp:", s);
+
+	index = 0;
+	for (chunk = sctp_chunks_begin(packet, &iter, error);
+	     chunk != NULL;
+	     chunk = sctp_chunks_next(&iter, error)) {
+		if (index == 0)
+			fputc(' ', s);
+		else
+			fputs("; ", s);
+		if (*error != NULL)
+			break;
+		result = sctp_chunk_to_string(s, chunk, error);
+		if (result != STATUS_OK)
+			break;
+		index++;
+	}
+	if (*error != NULL)
+		result = STATUS_ERR;
+
+	if (format == DUMP_VERBOSE)
+		packet_buffer_to_string(s, packet);
+
+	return result;
+}
+
 /* Print a string representation of the TCP packet:
  *  direction opt_ip_info flags seq ack window tcp_options
  */
@@ -216,7 +258,7 @@ static int udplite_packet_to_string(FILE *s, struct packet *packet,
 static int icmpv4_packet_to_string(FILE *s, struct packet *packet,
 				   enum dump_format_t format, char **error)
 {
-	fprintf(s, "icmpv4");
+	fputs("icmpv4", s);
 	/* TODO(ncardwell): print type, code; use tables from icmp_packet.c */
 	return STATUS_OK;
 }
@@ -224,7 +266,7 @@ static int icmpv4_packet_to_string(FILE *s, struct packet *packet,
 static int icmpv6_packet_to_string(FILE *s, struct packet *packet,
 				   enum dump_format_t format, char **error)
 {
-	fprintf(s, "icmpv6");
+	fputs("icmpv6", s);
 	/* TODO(ncardwell): print type, code; use tables from icmp_packet.c */
 	return STATUS_OK;
 }
@@ -251,7 +293,6 @@ static int encap_header_to_string(FILE *s, struct packet *packet, int layer,
 	return printer(s, packet, layer, format, error);
 }
 
-
 int packet_to_string(struct packet *packet,
 		     enum dump_format_t format,
 		     char **ascii_string, char **error)
@@ -272,9 +313,12 @@ int packet_to_string(struct packet *packet,
 	}
 
 	if ((packet->ipv4 == NULL) && (packet->ipv6 == NULL)) {
-		fprintf(s, "[NO IP HEADER]");
+		fputs("[NO IP HEADER]", s);
 	} else {
-		if (packet->tcp != NULL) {
+		if (packet->sctp != NULL) {
+			if (sctp_packet_to_string(s, packet, format, error))
+				goto out;
+		} else if (packet->tcp != NULL) {
 			if (tcp_packet_to_string(s, packet, format, error))
 				goto out;
 		} else if (packet->udp != NULL) {
@@ -290,7 +334,7 @@ int packet_to_string(struct packet *packet,
 			if (icmpv6_packet_to_string(s, packet, format, error))
 				goto out;
 		} else {
-			fprintf(s, "[No TCP or UDP or UDPLite or ICMP header]");
+			fputs("[No SCTP, TCP, UDP, UDPLite or ICMP header]", s);
 		}
 	}
 
diff --git a/gtests/net/packetdrill/packet_to_string_test.c b/gtests/net/packetdrill/packet_to_string_test.c
index 8851437a4ab38295b1b9198cc5dbb32e8112abda..0e0f0788a7a13b56d0ccc5426086fa9af0e828d1 100644
--- a/gtests/net/packetdrill/packet_to_string_test.c
+++ b/gtests/net/packetdrill/packet_to_string_test.c
@@ -30,6 +30,399 @@
 #include "ethernet.h"
 #include "packet_parser.h"
 
+static void test_sctp_ipv4_packet_to_string(void)
+{
+	/* An IPv4/SCTP packet. */
+	u8 data[] = {
+		/* IPv4: */
+		0x45, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00,
+		0xff, 0x84, 0xb5, 0x50, 0x02, 0x02, 0x02, 0x02,
+		0x01, 0x01, 0x01, 0x01,
+		/* SCTP Common Header: */
+		0x04, 0xd2, 0x1f, 0x90, 0x01, 0x02, 0x03, 0x04,
+		0x3d, 0x99, 0xbf, 0xe3,
+		/* SCTP ABORT Chunk: */
+		0x06, 0x01, 0x00, 0x04
+	};
+
+	struct packet *packet = packet_new(sizeof(data));
+
+	/* Populate and parse a packet */
+	memcpy(packet->buffer, data, sizeof(data));
+	char *error = NULL;
+	enum packet_parse_result_t result =
+		parse_packet(packet, sizeof(data), ETHERTYPE_IP, &error);
+	assert(result == PACKET_OK);
+	assert(error == NULL);
+
+	int status = 0;
+	char *dump = NULL, *expected = NULL;
+
+	/* Test a DUMP_SHORT dump */
+	status = packet_to_string(packet, DUMP_SHORT, &dump, &error);
+	assert(status == STATUS_OK);
+	assert(error == NULL);
+	printf("dump = '%s'\n", dump);
+	expected =
+		"sctp: ABORT[flags=T]";
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+
+	/* Test a DUMP_FULL dump */
+	status = packet_to_string(packet, DUMP_FULL, &dump, &error);
+	assert(status == STATUS_OK);
+	assert(error == NULL);
+	printf("dump = '%s'\n", dump);
+	expected =
+		"2.2.2.2:1234 > 1.1.1.1:8080 "
+		"sctp: ABORT[flags=T]";
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+
+	/* Test a DUMP_VERBOSE dump */
+	status = packet_to_string(packet, DUMP_VERBOSE, &dump, &error);
+	assert(status == STATUS_OK);
+	assert(error == NULL);
+	printf("dump = '%s'\n", dump);
+	expected =
+		"2.2.2.2:1234 > 1.1.1.1:8080 "
+		"sctp: ABORT[flags=T]"
+		"\n"
+		"0x0000: 45 00 00 24 00 00 00 00 ff 84 b5 50 02 02 02 02 " "\n"
+		"0x0010: 01 01 01 01 04 d2 1f 90 01 02 03 04 3d 99 bf e3 " "\n"
+		"0x0020: 06 01 00 04 " "\n";
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+
+	packet_free(packet);
+}
+
+static void test_sctp_ipv6_packet_to_string(void)
+{
+	/* An IPv6/SCTP packet. */
+	u8 data[] = {
+		/* IPv6 Base Header: */
+		0x60, 0x00, 0x00, 0x00, 0x01, 0x78, 0x84, 0xff,
+		0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22,
+		0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11,
+		/* SCTP Common Header: */
+		0x04, 0xd2, 0x1f, 0x90,
+		0x01, 0x02, 0x03, 0x04,
+		0x1b, 0x68, 0x75, 0x8e,
+		/* SCTP DATA Chunk */
+		0x00, 0x0f, 0x00, 0x13,
+		0x01, 0x02, 0x03, 0x04,
+		0x00, 0xff, 0x01, 0x00,
+		0x00, 0x00, 0x00, 0x00,
+		0x00, 0x01, 0x02, 0x00,
+		/* SCTP INIT Chunk */
+		0x01, 0x00, 0x00, 0x50,
+		0x00, 0x00, 0x00, 0x01,
+		0x00, 0x01, 0x00, 0x00,
+		0x00, 0x0f, 0x00, 0x0f,
+		0x01, 0x02, 0x03, 0x04,
+		0x00, 0x05, 0x00, 0x08,
+		0x01, 0x02, 0x03, 0x04,
+		0x00, 0x06, 0x00, 0x14,
+		0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x01,
+		0x00, 0x09, 0x00, 0x08,
+		0x00, 0x01, 0x00, 0x00,
+		0x00, 0x0b, 0x00, 0x06,
+		0x40, 0x41, 0x00, 0x00,
+		0x00, 0x0c, 0x00, 0x0a,
+		0x00, 0x05, 0x00, 0x06,
+		0x00, 0x0b, 0x00, 0x00,
+		0x80, 0x00, 0x00, 0x04,
+		/* SCTP INIT_ACK Chunk */
+		0x02, 0x00, 0x00, 0x24,
+		0x00, 0x00, 0x00, 0x01,
+		0x00, 0x01, 0x00, 0x00,
+		0x00, 0x0f, 0x00, 0x0f,
+		0x01, 0x02, 0x03, 0x04,
+		0x00, 0x07, 0x00, 0x07,
+		0x01, 0x02, 0x03, 0x00,
+		0x00, 0x08, 0x00, 0x08,
+		0x80, 0x01, 0x00, 0x04,
+		/* SCTP SACK Chunk */
+		0x03, 0x00, 0x00, 0x20,
+		0x01, 0x02, 0x03, 0x04,
+		0x00, 0x01, 0x00, 0x00,
+		0x00, 0x03, 0x00, 0x01,
+		0x00, 0x01, 0x00, 0x03,
+		0x00, 0x05, 0x00, 0x0f,
+		0x10, 0x00, 0x10, 0x14,
+		0x01, 0x02, 0x03, 0x04,
+		/* SCTP HEARTBEAT Chunk */
+		0x04, 0x00, 0x00, 0x06,
+		0x01, 0x02, 0x00, 0x00,
+		/* SCTP HEARTBEAT-ACK Chunk */
+		0x05, 0x00, 0x00, 0x06,
+		0x01, 0x02, 0x00, 0x00,
+		/* SCTP ABORT Chunk: */
+		0x06, 0x01, 0x00, 0x04,
+		/* SCTP ABORT Chunk: */
+		0x06, 0x00, 0x00, 0x80,
+		0x00, 0x01, 0x00, 0x08,
+		0x00, 0xff, 0x00, 0x00,
+		0x00, 0x02, 0x00, 0x0a,
+		0x00, 0x00, 0x00, 0x01,
+		0x00, 0x07, 0x00, 0x00,
+		0x00, 0x03, 0x00, 0x08,
+		0x00, 0x01, 0x00, 0x00,
+		0x00, 0x04, 0x00, 0x04,
+		0x00, 0x05, 0x00, 0x0c,
+		0x00, 0x0b, 0x00, 0x06,
+		0x40, 0x41, 0x00, 0x00,
+		0x00, 0x06, 0x00, 0x0c,
+		0xfe, 0x05, 0x00, 0x05,
+		0x01, 0x00, 0x00, 0x00,
+		0x00, 0x07, 0x00, 0x04,
+		0x00, 0x08, 0x00, 0x10,
+		0x80, 0x0a, 0x00, 0x04,
+		0x80, 0x0b, 0x00, 0x05,
+		0x01, 0x00, 0x00, 0x00,
+		0x00, 0x09, 0x00, 0x08,
+		0x01, 0x02, 0x03, 0x04,
+		0x00, 0x0a, 0x00, 0x04,
+		0x00, 0x0b, 0x00, 0x14,
+		0x00, 0x05, 0x00, 0x08,
+		0x01, 0x02, 0x03, 0x04,
+		0x00, 0x05, 0x00, 0x08,
+		0x02, 0x03, 0x04, 0x05,
+		0x00, 0x0c, 0x00, 0x07,
+		0x42, 0x59, 0x45, 0x00,
+		0x00, 0x0d, 0x00, 0x06,
+		0x40, 0x40, 0x00, 0x00,
+		/* SCTP SHUTDOWN Chunk */
+		0x07, 0x00, 0x00, 0x08,
+		0x01, 0x02, 0x03, 0x04,
+		/* SCTP SHUTDOWN_ACK Chunk */
+		0x08, 0x00, 0x00, 0x04,
+		/* SCTP ERROR Chunk */
+		0x09, 0x00, 0x00, 0x04,
+		/* SCTP COOKIE_ECHO Chunk */
+		0x0a, 0x00, 0x00, 0x05,
+		0x45, 0x00, 0x00, 0x00,
+		/* SCTP COOKIE_ACK Chunk */
+		0x0b, 0x00, 0x00, 0x04,
+		/* SCTP ECNE Chunk */
+		0x0c, 0x00, 0x00, 0x08,
+		0x01, 0x02, 0x03, 0x04,
+		/* SCTP CWR Chunk */
+		0x0d, 0x00, 0x00, 0x08,
+		0x01, 0x02, 0x03, 0x04,
+		/* SCTP SHUTDOWN_COMPLETE Chunk */
+		0x0e, 0x01, 0x00, 0x04
+	};
+
+	struct packet *packet = packet_new(sizeof(data));
+
+	/* Populate and parse a packet */
+	memcpy(packet->buffer, data, sizeof(data));
+	char *error = NULL;
+	enum packet_parse_result_t result =
+		parse_packet(packet, sizeof(data), ETHERTYPE_IPV6, &error);
+	assert(result == PACKET_OK);
+	assert(error == NULL);
+
+	int status = 0;
+	char *dump = NULL, *expected = NULL;
+
+	/* Test a DUMP_SHORT dump */
+	status = packet_to_string(packet, DUMP_SHORT, &dump, &error);
+	assert(status == STATUS_OK);
+	assert(error == NULL);
+	printf("dump = '%s'\n", dump);
+	expected =
+		"sctp: "
+		"DATA[flags=IUBE, tsn=16909060, sid=255, ssn=256, ppid=0, "
+		     "payload_len=3]; "
+		"INIT[tag=1, a_rwnd=65536, os=15, is=15, tsn=16909060, "
+		     "IPV4_ADDRESS[1.2.3.4], "
+		     "IPV6_ADDRESS[::1], "
+		     "COOKIE_PRESERVATIVE[65536], "
+		     "HOSTNAME[@A], "
+		     "SUPPORTED_ADDRESS_TYPES[IPV4, IPV6, HOSTNAME], "
+		     "ECN_CAPABLE[]]; "
+		"INIT_ACK[tag=1, a_rwnd=65536, os=15, is=15, tsn=16909060, "
+			 "STATE_COOKIE[cookie_len=3], "
+			 "UNRECOGNIZED_PARAMETER["
+			   "PARAMETER[type=0x8001, value=[]]]]; "
+		"SACK[cum_tsn=16909060, a_rwnd=65536, "
+		     "gaps=[1-3, 5-15, 4096-4116], dups=[16909060]]; "
+		"HEARTBEAT[info_len=2]; "
+		"HEARTBEAT_ACK[info_len=2]; "
+		"ABORT[flags=T]; "
+		"ABORT[INVALID_STREAM_IDENTIFIER[sid=255], "
+		      "MISSING_MANDATORY_PARAMETER[STATE_COOKIE], "
+		      "STALE_COOKIE_ERROR[staleness=65536], "
+		      "OUT_OF_RESOURCES[], "
+		      "UNRESOLVABLE_ADDRESS[HOSTNAME[@A]], "
+		      "UNRECOGNIZED_CHUNK["
+			"CHUNK[type=0xfe, flags=0x05, value=[0x01]]], "
+		      "INVALID_MANDATORY_PARAMETER[], "
+		      "UNRECOGNIZED_PARAMETERS["
+			"PARAMETER[type=0x800a, value=[]], "
+			"PARAMETER[type=0x800b, value=[0x01]]], "
+		      "NO_USER_DATA[tsn=16909060], "
+		      "COOKIE_RECEIVED_WHILE_SHUTDOWN[], "
+		      "RESTART_WITH_NEW_ADDRESSES[IPV4_ADDRESS[1.2.3.4], "
+						 "IPV4_ADDRESS[2.3.4.5]], "
+		      "USER_INITIATED_ABORT[BYE], "
+		      "PROTOCOL_VIOLATION[@@]]; "
+		"SHUTDOWN[tsn=16909060]; "
+		"SHUTDOWN_ACK[]; "
+		"ERROR[]; "
+		"COOKIE_ECHO[cookie_len=1]; "
+		"COOKIE_ACK[]; "
+		"ECNE[tsn=16909060]; "
+		"CWR[tsn=16909060]; "
+		"SHUTDOWN_COMPLETE[flags=T]";
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+
+	/* Test a DUMP_FULL dump */
+	status = packet_to_string(packet, DUMP_FULL, &dump, &error);
+	assert(status == STATUS_OK);
+	assert(error == NULL);
+	printf("dump = '%s'\n", dump);
+	expected =
+		"2::2222:1234 > 1::1111:8080 "
+		"sctp: "
+		"DATA[flags=IUBE, tsn=16909060, sid=255, ssn=256, ppid=0, "
+		     "payload_len=3]; "
+		"INIT[tag=1, a_rwnd=65536, os=15, is=15, tsn=16909060, "
+		     "IPV4_ADDRESS[1.2.3.4], "
+		     "IPV6_ADDRESS[::1], "
+		     "COOKIE_PRESERVATIVE[65536], "
+		     "HOSTNAME[@A], "
+		     "SUPPORTED_ADDRESS_TYPES[IPV4, IPV6, HOSTNAME], "
+		     "ECN_CAPABLE[]]; "
+		"INIT_ACK[tag=1, a_rwnd=65536, os=15, is=15, tsn=16909060, "
+			 "STATE_COOKIE[cookie_len=3], "
+			 "UNRECOGNIZED_PARAMETER["
+			   "PARAMETER[type=0x8001, value=[]]]]; "
+		"SACK[cum_tsn=16909060, a_rwnd=65536, "
+		     "gaps=[1-3, 5-15, 4096-4116], dups=[16909060]]; "
+		"HEARTBEAT[info_len=2]; "
+		"HEARTBEAT_ACK[info_len=2]; "
+		"ABORT[flags=T]; "
+		"ABORT[INVALID_STREAM_IDENTIFIER[sid=255], "
+		      "MISSING_MANDATORY_PARAMETER[STATE_COOKIE], "
+		      "STALE_COOKIE_ERROR[staleness=65536], "
+		      "OUT_OF_RESOURCES[], "
+		      "UNRESOLVABLE_ADDRESS[HOSTNAME[@A]], "
+		      "UNRECOGNIZED_CHUNK["
+			"CHUNK[type=0xfe, flags=0x05, value=[0x01]]], "
+		      "INVALID_MANDATORY_PARAMETER[], "
+		      "UNRECOGNIZED_PARAMETERS["
+			"PARAMETER[type=0x800a, value=[]], "
+			"PARAMETER[type=0x800b, value=[0x01]]], "
+		      "NO_USER_DATA[tsn=16909060], "
+		      "COOKIE_RECEIVED_WHILE_SHUTDOWN[], "
+		      "RESTART_WITH_NEW_ADDRESSES[IPV4_ADDRESS[1.2.3.4], "
+						 "IPV4_ADDRESS[2.3.4.5]], "
+		      "USER_INITIATED_ABORT[BYE], "
+		      "PROTOCOL_VIOLATION[@@]]; "
+		"SHUTDOWN[tsn=16909060]; "
+		"SHUTDOWN_ACK[]; "
+		"ERROR[]; "
+		"COOKIE_ECHO[cookie_len=1]; "
+		"COOKIE_ACK[]; "
+		"ECNE[tsn=16909060]; "
+		"CWR[tsn=16909060]; "
+		"SHUTDOWN_COMPLETE[flags=T]";
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+
+	/* Test a DUMP_VERBOSE dump */
+	status = packet_to_string(packet, DUMP_VERBOSE, &dump, &error);
+	assert(status == STATUS_OK);
+	assert(error == NULL);
+	printf("dump = '%s'\n", dump);
+	expected =
+		"2::2222:1234 > 1::1111:8080 "
+		"sctp: "
+		"DATA[flags=IUBE, tsn=16909060, sid=255, ssn=256, ppid=0, "
+		     "payload_len=3]; "
+		"INIT[tag=1, a_rwnd=65536, os=15, is=15, tsn=16909060, "
+		     "IPV4_ADDRESS[1.2.3.4], "
+		     "IPV6_ADDRESS[::1], "
+		     "COOKIE_PRESERVATIVE[65536], "
+		     "HOSTNAME[@A], "
+		     "SUPPORTED_ADDRESS_TYPES[IPV4, IPV6, HOSTNAME], "
+		     "ECN_CAPABLE[]]; "
+		"INIT_ACK[tag=1, a_rwnd=65536, os=15, is=15, tsn=16909060, "
+			 "STATE_COOKIE[cookie_len=3], "
+			 "UNRECOGNIZED_PARAMETER["
+			   "PARAMETER[type=0x8001, value=[]]]]; "
+		"SACK[cum_tsn=16909060, a_rwnd=65536, "
+		     "gaps=[1-3, 5-15, 4096-4116], dups=[16909060]]; "
+		"HEARTBEAT[info_len=2]; "
+		"HEARTBEAT_ACK[info_len=2]; "
+		"ABORT[flags=T]; "
+		"ABORT[INVALID_STREAM_IDENTIFIER[sid=255], "
+		      "MISSING_MANDATORY_PARAMETER[STATE_COOKIE], "
+		      "STALE_COOKIE_ERROR[staleness=65536], "
+		      "OUT_OF_RESOURCES[], "
+		      "UNRESOLVABLE_ADDRESS[HOSTNAME[@A]], "
+		      "UNRECOGNIZED_CHUNK["
+			"CHUNK[type=0xfe, flags=0x05, value=[0x01]]], "
+		      "INVALID_MANDATORY_PARAMETER[], "
+		      "UNRECOGNIZED_PARAMETERS["
+			"PARAMETER[type=0x800a, value=[]], "
+			"PARAMETER[type=0x800b, value=[0x01]]], "
+		      "NO_USER_DATA[tsn=16909060], "
+		      "COOKIE_RECEIVED_WHILE_SHUTDOWN[], "
+		      "RESTART_WITH_NEW_ADDRESSES[IPV4_ADDRESS[1.2.3.4], "
+						 "IPV4_ADDRESS[2.3.4.5]], "
+		      "USER_INITIATED_ABORT[BYE], "
+		      "PROTOCOL_VIOLATION[@@]]; "
+		"SHUTDOWN[tsn=16909060]; "
+		"SHUTDOWN_ACK[]; "
+		"ERROR[]; "
+		"COOKIE_ECHO[cookie_len=1]; "
+		"COOKIE_ACK[]; "
+		"ECNE[tsn=16909060]; "
+		"CWR[tsn=16909060]; "
+		"SHUTDOWN_COMPLETE[flags=T]"
+		"\n"
+		"0x0000: 60 00 00 00 01 78 84 ff 00 02 00 00 00 00 00 00 " "\n"
+		"0x0010: 00 00 00 00 00 00 22 22 00 01 00 00 00 00 00 00 " "\n"
+		"0x0020: 00 00 00 00 00 00 11 11 04 d2 1f 90 01 02 03 04 " "\n"
+		"0x0030: 1b 68 75 8e 00 0f 00 13 01 02 03 04 00 ff 01 00 " "\n"
+		"0x0040: 00 00 00 00 00 01 02 00 01 00 00 50 00 00 00 01 " "\n"
+		"0x0050: 00 01 00 00 00 0f 00 0f 01 02 03 04 00 05 00 08 " "\n"
+		"0x0060: 01 02 03 04 00 06 00 14 00 00 00 00 00 00 00 00 " "\n"
+		"0x0070: 00 00 00 00 00 00 00 01 00 09 00 08 00 01 00 00 " "\n"
+		"0x0080: 00 0b 00 06 40 41 00 00 00 0c 00 0a 00 05 00 06 " "\n"
+		"0x0090: 00 0b 00 00 80 00 00 04 02 00 00 24 00 00 00 01 " "\n"
+		"0x00a0: 00 01 00 00 00 0f 00 0f 01 02 03 04 00 07 00 07 " "\n"
+		"0x00b0: 01 02 03 00 00 08 00 08 80 01 00 04 03 00 00 20 " "\n"
+		"0x00c0: 01 02 03 04 00 01 00 00 00 03 00 01 00 01 00 03 " "\n"
+		"0x00d0: 00 05 00 0f 10 00 10 14 01 02 03 04 04 00 00 06 " "\n"
+		"0x00e0: 01 02 00 00 05 00 00 06 01 02 00 00 06 01 00 04 " "\n"
+		"0x00f0: 06 00 00 80 00 01 00 08 00 ff 00 00 00 02 00 0a " "\n"
+		"0x0100: 00 00 00 01 00 07 00 00 00 03 00 08 00 01 00 00 " "\n"
+		"0x0110: 00 04 00 04 00 05 00 0c 00 0b 00 06 40 41 00 00 " "\n"
+		"0x0120: 00 06 00 0c fe 05 00 05 01 00 00 00 00 07 00 04 " "\n"
+		"0x0130: 00 08 00 10 80 0a 00 04 80 0b 00 05 01 00 00 00 " "\n"
+		"0x0140: 00 09 00 08 01 02 03 04 00 0a 00 04 00 0b 00 14 " "\n"
+		"0x0150: 00 05 00 08 01 02 03 04 00 05 00 08 02 03 04 05 " "\n"
+		"0x0160: 00 0c 00 07 42 59 45 00 00 0d 00 06 40 40 00 00 " "\n"
+		"0x0170: 07 00 00 08 01 02 03 04 08 00 00 04 09 00 00 04 " "\n"
+		"0x0180: 0a 00 00 05 45 00 00 00 0b 00 00 04 0c 00 00 08 " "\n"
+		"0x0190: 01 02 03 04 0d 00 00 08 01 02 03 04 0e 01 00 04 " "\n";
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+	packet_free(packet);
+}
+
 static void test_tcp_ipv4_packet_to_string(void)
 {
 	/* An IPv4/GRE/IPv4/TCP packet. */
@@ -133,7 +526,7 @@ static void test_tcp_ipv6_packet_to_string(void)
 		0xd3, 0xe2, 0x1f, 0x90, 0x00, 0x00, 0x00, 0x00,
 		0x00, 0x00, 0x00, 0x00, 0x80, 0x02, 0x80, 0x18,
 		0x06, 0x60, 0x00, 0x00, 0x02, 0x04, 0x03, 0xe8,
-		0x04, 0x02, 0x01, 0x01, 0x01, 0x03, 0x03, 0x07,
+		0x04, 0x02, 0x01, 0x01, 0x01, 0x03, 0x03, 0x07
 	};
 
 	struct packet *packet = packet_new(sizeof(data));
@@ -244,6 +637,8 @@ static void test_gre_mpls_tcp_ipv4_packet_to_string(void)
 		"<nop,nop,TS val 117573699 ecr 5>";
 	assert(strcmp(dump, expected) == 0);
 	free(dump);
+
+	packet_free(packet);
 }
 
 static void test_udplite_ipv4_packet_to_string(void)
@@ -269,8 +664,7 @@ static void test_udplite_ipv4_packet_to_string(void)
 	memcpy(packet->buffer, data, sizeof(data));
 	char *error = NULL;
 	enum packet_parse_result_t result =
-		parse_packet(packet, sizeof(data), ETHERTYPE_IP,
-				     &error);
+		parse_packet(packet, sizeof(data), ETHERTYPE_IP, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -348,8 +742,7 @@ static void test_udplite_ipv6_packet_to_string(void)
 	memcpy(packet->buffer, data, sizeof(data));
 	char *error = NULL;
 	enum packet_parse_result_t result =
-		parse_packet(packet, sizeof(data), ETHERTYPE_IPV6,
-				     &error);
+		parse_packet(packet, sizeof(data), ETHERTYPE_IPV6, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -402,6 +795,8 @@ static void test_udplite_ipv6_packet_to_string(void)
 
 int main(void)
 {
+	test_sctp_ipv4_packet_to_string();
+	test_sctp_ipv6_packet_to_string();
 	test_tcp_ipv4_packet_to_string();
 	test_tcp_ipv6_packet_to_string();
 	test_gre_mpls_tcp_ipv4_packet_to_string();
diff --git a/gtests/net/packetdrill/platforms.h b/gtests/net/packetdrill/platforms.h
index 11df81187dfd62645d6c7e6b31e6414b4afd0d84..207d8ee0d7069904e1b1f06eef23eecd40392ed3 100644
--- a/gtests/net/packetdrill/platforms.h
+++ b/gtests/net/packetdrill/platforms.h
@@ -30,7 +30,7 @@
 
 #ifdef linux
 
-+/* It seems that Linux does not provide netinet/udplite.h */
+/* It seems that Linux does not provide netinet/udplite.h */
 #define SOL_UDPLITE            IPPROTO_UDPLITE
 #define UDPLITE_SEND_CSCOV     10
 #define UDPLITE_RECV_CSCOV     11
diff --git a/gtests/net/packetdrill/script.c b/gtests/net/packetdrill/script.c
index 9877c4eb263e91262014f072dc805f02c049bd4e..d0e9dcd14d9eb287a5f109ba94e474cea8d2b798 100644
--- a/gtests/net/packetdrill/script.c
+++ b/gtests/net/packetdrill/script.c
@@ -104,9 +104,7 @@ struct int_symbol cross_platform_symbols[] = {
 	{ IPPROTO_IP,                       "IPPROTO_IP"                      },
 	{ IPPROTO_IPV6,                     "IPPROTO_IPV6"                    },
 	{ IPPROTO_ICMP,                     "IPPROTO_ICMP"                    },
-#ifdef IPPROTO_SCTP
 	{ IPPROTO_SCTP,                     "IPPROTO_SCTP"                    },
-#endif
 	{ IPPROTO_TCP,                      "IPPROTO_TCP"                     },
 	{ IPPROTO_UDP,                      "IPPROTO_UDP"                     },
 	{ IPPROTO_UDPLITE,                  "IPPROTO_UDPLITE"                 },
diff --git a/gtests/net/packetdrill/sctp.h b/gtests/net/packetdrill/sctp.h
index b831dd9c22613496f87025e709c905cc57f7a84a..ded5509ae450cdda30cbc83044fc0dc3c2fd02cf 100644
--- a/gtests/net/packetdrill/sctp.h
+++ b/gtests/net/packetdrill/sctp.h
@@ -35,6 +35,330 @@ struct sctp_common_header {
 	__be16	dst_port;
 	__be32	v_tag;
 	__be32  crc32c;
-};
+} __packed;
+
+#define SCTP_DATA_CHUNK_TYPE				0x00
+#define SCTP_INIT_CHUNK_TYPE				0x01
+#define SCTP_INIT_ACK_CHUNK_TYPE			0x02
+#define SCTP_SACK_CHUNK_TYPE				0x03
+#define SCTP_HEARTBEAT_CHUNK_TYPE			0x04
+#define SCTP_HEARTBEAT_ACK_CHUNK_TYPE			0x05
+#define SCTP_ABORT_CHUNK_TYPE				0x06
+#define SCTP_SHUTDOWN_CHUNK_TYPE			0x07
+#define SCTP_SHUTDOWN_ACK_CHUNK_TYPE			0x08
+#define SCTP_ERROR_CHUNK_TYPE				0x09
+#define SCTP_COOKIE_ECHO_CHUNK_TYPE			0x0a
+#define SCTP_COOKIE_ACK_CHUNK_TYPE			0x0b
+#define SCTP_ECNE_CHUNK_TYPE				0x0c
+#define SCTP_CWR_CHUNK_TYPE				0x0d
+#define SCTP_SHUTDOWN_COMPLETE_CHUNK_TYPE		0x0e
+
+struct sctp_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+	__u8 value[];
+} __packed;
+
+#define SCTP_DATA_CHUNK_I_BIT				0x08
+#define SCTP_DATA_CHUNK_U_BIT				0x04
+#define SCTP_DATA_CHUNK_B_BIT				0x02
+#define SCTP_DATA_CHUNK_E_BIT				0x01
+
+struct sctp_data_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+	__be32 tsn;
+	__be16 sid;
+	__be16 ssn;
+	__be32 ppid;
+	__u8 data[];
+} __packed;
+
+#define SCTP_INIT_CHUNK_PARAMETER_OFFSET		20
+
+struct sctp_init_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+	__be32 initiate_tag;
+	__be32 a_rwnd;
+	__be16 os;
+	__be16 is;
+	__be32 initial_tsn;
+	__u8 parameter[];
+} __packed;
+
+struct sctp_init_ack_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+	__be32 initiate_tag;
+	__be32 a_rwnd;
+	__be16 os;
+	__be16 is;
+	__be32 initial_tsn;
+	__u8 parameter[];
+} __packed;
+
+union sctp_sack_block {
+	struct {
+		__be16 start;
+		__be16 end;
+	} gap;
+	u32 tsn;
+} __packed;
+
+struct sctp_sack_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+	__be32 cum_tsn;
+	__be32 a_rwnd;
+	__be16 nr_gap_blocks;
+	__be16 nr_dup_tsns;
+	union sctp_sack_block block[];
+} __packed;
+
+struct sctp_heartbeat_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+	__u8 value[];
+} __packed;
+
+struct sctp_heartbeat_ack_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+	__u8 value[];
+} __packed;
+
+#define SCTP_ABORT_CHUNK_T_BIT				0x01
+#define SCTP_ABORT_CHUNK_CAUSE_OFFSET			4
+
+struct sctp_abort_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+	__u8 cause[];
+} __packed;
+
+struct sctp_shutdown_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+	__be32 cum_tsn;
+} __packed;
+
+struct sctp_shutdown_ack_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+} __packed;
+
+#define SCTP_ERROR_CHUNK_CAUSE_OFFSET			4
+
+struct sctp_error_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+	__u8 cause[];
+} __packed;
+
+struct sctp_cookie_echo_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+	__u8 cookie[];
+} __packed;
+
+struct sctp_cookie_ack_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+} __packed;
+
+struct sctp_ecne_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+	__be32 lowest_tsn;
+} __packed;
+
+struct sctp_cwr_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+	__be32 lowest_tsn;
+} __packed;
+
+#define SCTP_SHUTDOWN_COMPLETE_CHUNK_T_BIT		0x01
+
+struct sctp_shutdown_complete_chunk {
+	__u8 type;
+	__u8 flags;
+	__be16 length;
+} __packed;
+
+#define SCTP_IPV4_ADDRESS_PARAMETER_TYPE		0x0005
+#define SCTP_IPV6_ADDRESS_PARAMETER_TYPE		0x0006
+#define SCTP_STATE_COOKIE_PARAMETER_TYPE		0x0007
+#define SCTP_UNRECOGNIZED_PARAMETER_PARAMETER_TYPE	0x0008
+#define SCTP_COOKIE_PRESERVATIVE_PARAMETER_TYPE		0x0009
+#define SCTP_HOSTNAME_ADDRESS_PARAMETER_TYPE		0x000b
+#define SCTP_SUPPORTED_ADDRESS_TYPES_PARAMETER_TYPE	0x000c
+#define SCTP_ECN_CAPABLE_PARAMETER_TYPE			0x8000
+
+struct sctp_parameter {
+	__be16 type;
+	__be16 length;
+	__u8 value[];
+} __packed;
+
+struct sctp_ipv4_address_parameter {
+	__be16 type;
+	__be16 length;
+	struct in_addr addr;
+} __packed;
+
+struct sctp_ipv6_address_parameter {
+	__be16 type;
+	__be16 length;
+	struct in6_addr addr;
+} __packed;
+
+struct sctp_state_cookie_parameter {
+	__be16 type;
+	__be16 length;
+	__u8 cookie[];
+} __packed;
+
+struct sctp_unrecognized_parameter_parameter {
+	__be16 type;
+	__be16 length;
+	__u8 value[];
+} __packed;
+
+struct sctp_cookie_preservative_parameter {
+	__be16 type;
+	__be16 length;
+	__be32 increment;
+} __packed;
+
+struct sctp_hostname_address_parameter {
+	__be16 type;
+	__be16 length;
+	__u8 hostname[];
+} __packed;
+
+struct sctp_supported_address_types_parameter {
+	__be16 type;
+	__be16 length;
+	__be16 address_type[];
+} __packed;
+
+struct sctp_ecn_capable_parameter {
+	__be16 type;
+	__be16 length;
+} __packed;
+
+#define SCTP_INVALID_STREAM_IDENTIFIER_CAUSE_CODE	0x0001
+#define SCTP_MISSING_MADATORY_PARAMETER_CAUSE_CODE	0x0002
+#define SCTP_STALE_COOKIE_ERROR_CAUSE_CODE		0x0003
+#define SCTP_OUT_OF_RESOURCES_CAUSE_CODE		0x0004
+#define SCTP_UNRESOLVABLE_ADDRESS_CAUSE_CODE		0x0005
+#define SCTP_UNRECOGNIZED_CHUNK_TYPE_CAUSE_CODE		0x0006
+#define SCTP_INVALID_MADATORY_PARAMETER_CAUSE_CODE	0x0007
+#define SCTP_UNRECOGNIZED_PARAMETERS_CAUSE_CODE		0x0008
+#define SCTP_NO_USER_DATA_CAUSE_CODE			0x0009
+#define SCTP_COOKIE_RECEIVED_WHILE_SHUTDOWN_CAUSE_CODE	0x000a
+#define SCTP_RESTART_WITH_NEW_ADDRESSES_CAUSE_CODE	0x000b
+#define SCTP_USER_INITIATED_ABORT_CAUSE_CODE		0x000c
+#define SCTP_PROTOCOL_VIOLATION_CAUSE_CODE		0x000d
+
+struct sctp_cause {
+	__be16 code;
+	__be16 length;
+	__u8 information[];
+} __packed;
+
+struct sctp_invalid_stream_identifier_cause {
+	__be16 code;
+	__be16 length;
+	__be16 sid;
+	__be16 reserved;
+} __packed;
+
+struct sctp_missing_mandatory_parameter_cause {
+	__be16 code;
+	__be16 length;
+	__be32 nr_parameters;
+	__be16 parameter_type[];
+} __packed;
+
+struct sctp_stale_cookie_error_cause {
+	__be16 code;
+	__be16 length;
+	__be32 staleness;
+} __packed;
+
+struct sctp_out_of_resources_cause {
+	__be16 code;
+	__be16 length;
+} __packed;
+
+struct sctp_unresolvable_address_cause {
+	__be16 code;
+	__be16 length;
+	__u8 parameter[];
+} __packed;
+
+struct sctp_unrecognized_chunk_type_cause {
+	__be16 code;
+	__be16 length;
+	__u8 chunk[];
+} __packed;
+
+struct sctp_invalid_mandatory_parameter_cause {
+	__be16 code;
+	__be16 length;
+} __packed;
+
+struct sctp_unrecognized_parameters_cause {
+	__be16 code;
+	__be16 length;
+	__u8 parameters[];
+} __packed;
+
+struct sctp_no_user_data_cause {
+	__be16 code;
+	__be16 length;
+	__be32 tsn;
+} __packed;
+
+struct sctp_cookie_received_while_shutdown_cause {
+	__be16 code;
+	__be16 length;
+} __packed;
+
+struct sctp_restart_with_new_addresses_cause {
+	__be16 code;
+	__be16 length;
+	__u8 addresses[];
+} __packed;
+
+struct sctp_user_initiated_abort_cause {
+	__be16 code;
+	__be16 length;
+	__u8 information[];
+} __packed;
+
+struct sctp_protocol_violation_cause {
+	__be16 code;
+	__be16 length;
+	__u8 information[];
+} __packed;
 
 #endif /* __SCTP_HEADERS_H__ */
diff --git a/gtests/net/packetdrill/sctp_chunk_to_string.c b/gtests/net/packetdrill/sctp_chunk_to_string.c
new file mode 100644
index 0000000000000000000000000000000000000000..48b05824e3c2498a72a731f37c1ec9157a79c52b
--- /dev/null
+++ b/gtests/net/packetdrill/sctp_chunk_to_string.c
@@ -0,0 +1,1266 @@
+/*
+ * 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 for generating human-readable representations of SCTP chunks.
+ */
+
+#include "sctp_chunk_to_string.h"
+#include "sctp_iterator.h"
+
+static int sctp_parameter_to_string(FILE *, struct sctp_parameter *, char **);
+
+static int sctp_ipv4_address_parameter_to_string(
+	FILE *s,
+	struct sctp_ipv4_address_parameter *parameter,
+	char **error)
+{
+	u16 length;
+	char buffer[INET_ADDRSTRLEN];
+
+	length = ntohs(parameter->length);
+	if (length != sizeof(struct sctp_ipv4_address_parameter)) {
+		asprintf(error, "IPV4_ADDRESS parameter illegal (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	inet_ntop(AF_INET, &parameter->addr, buffer, INET_ADDRSTRLEN);
+	fprintf(s, "IPV4_ADDRESS[%s]", buffer);
+	return STATUS_OK;
+}
+
+static int sctp_ipv6_address_parameter_to_string(
+	FILE *s,
+	struct sctp_ipv6_address_parameter *parameter,
+	char **error)
+{
+	u16 length;
+	char buffer[INET6_ADDRSTRLEN];
+
+	length = ntohs(parameter->length);
+	if (length != sizeof(struct sctp_ipv6_address_parameter)) {
+		asprintf(error, "IPV6_ADDRESS parameter illegal (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	inet_ntop(AF_INET6, &parameter->addr, buffer, INET6_ADDRSTRLEN);
+	fprintf(s, "IPV6_ADDRESS[%s]", buffer);
+	return STATUS_OK;
+}
+
+static int sctp_state_cookie_parameter_to_string(
+	FILE *s,
+	struct sctp_state_cookie_parameter *parameter,
+	char **error)
+{
+	u16 length, cookie_length;
+
+	length = ntohs(parameter->length);
+	if (length < sizeof(struct sctp_state_cookie_parameter)) {
+		asprintf(error, "STATE_COOKIE parameter illegal (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	cookie_length = length - sizeof(struct sctp_state_cookie_parameter);
+	fprintf(s, "STATE_COOKIE[cookie_len=%d]", cookie_length);
+	return STATUS_OK;
+}
+
+static int sctp_unrecognized_parameter_parameter_to_string(
+	FILE *s,
+	struct sctp_unrecognized_parameter_parameter *parameter,
+	char **error)
+{
+	u16 length;
+	int result = STATUS_OK;
+
+	length = ntohs(parameter->length);
+	if (length < (sizeof(struct sctp_unrecognized_parameter_parameter) +
+		      sizeof(struct sctp_parameter))) {
+		asprintf(error,
+			 "UNRECOGNIZED_PARAMETER parameter illegal (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	fputs("UNRECOGNIZED_PARAMETER[", s);
+	result = sctp_parameter_to_string(s,
+		(struct sctp_parameter *)parameter->value, error);
+	fputc(']', s);
+	return result;
+}
+
+static int sctp_cookie_preservative_parameter_to_string(
+	FILE *s,
+	struct sctp_cookie_preservative_parameter *parameter,
+	char **error)
+{
+	u16 length;
+
+	length = ntohs(parameter->length);
+	if (length != sizeof(struct sctp_cookie_preservative_parameter)) {
+		asprintf(error,
+			 "COOKIE_PRESERVATIVE parameter illegal (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	fputs("COOKIE_PRESERVATIVE[", s);
+	fprintf(s, "%u", ntohl(parameter->increment));
+	fputc(']', s);
+	return STATUS_OK;
+}
+
+static int sctp_hostname_parameter_to_string(
+	FILE *s,
+	struct sctp_hostname_address_parameter *parameter,
+	char **error)
+{
+	u16 length;
+
+	length = ntohs(parameter->length);
+	if (length < sizeof(struct sctp_hostname_address_parameter)) {
+		asprintf(error, "HOSTNAME parameter illegal (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	fprintf(s, "HOSTNAME[%.*s]",
+		(int)(length - sizeof(struct sctp_hostname_address_parameter)),
+		(char *)parameter->hostname);
+	return STATUS_OK;
+}
+
+static int sctp_supported_address_types_parameter_to_string(
+	FILE *s,
+	struct sctp_supported_address_types_parameter *parameter,
+	char **error)
+{
+	u16 i, length, nr_address_types;
+
+	length = ntohs(parameter->length);
+	if ((length < sizeof(struct sctp_supported_address_types_parameter)) ||
+	    ((length & 0x0001) != 0)) {
+		asprintf(error,
+			 "SUPPORTED_ADDRESS_TYPES parameter illegal (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	nr_address_types =
+		(length - sizeof(struct sctp_supported_address_types_parameter))
+		/ sizeof(u16);
+	fputs("SUPPORTED_ADDRESS_TYPES[", s);
+	for (i = 0; i < nr_address_types; i++) {
+		if (i > 0)
+			fputs(", ", s);
+		switch (ntohs(parameter->address_type[i])) {
+		case SCTP_IPV4_ADDRESS_PARAMETER_TYPE:
+			fputs("IPV4", s);
+			break;
+		case SCTP_IPV6_ADDRESS_PARAMETER_TYPE:
+			fputs("IPV6", s);
+			break;
+		case SCTP_HOSTNAME_ADDRESS_PARAMETER_TYPE:
+			fputs("HOSTNAME", s);
+			break;
+		default:
+			fprintf(s, "0x%04x", ntohs(parameter->address_type[i]));
+			break;
+		}
+	}
+	fputs("]", s);
+	return STATUS_OK;
+}
+
+static int sctp_ecn_capable_parameter_to_string(
+	FILE *s,
+	struct sctp_ecn_capable_parameter *parameter,
+	char **error)
+{
+	u16 length;
+
+	length = ntohs(parameter->length);
+	if (length != sizeof(struct sctp_ecn_capable_parameter)) {
+		asprintf(error, "ECN_CAPABLE parameter illegal (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	fputs("ECN_CAPABLE[]", s);
+	return STATUS_OK;
+}
+
+static int sctp_unknown_parameter_to_string(
+	FILE *s,
+	struct sctp_parameter *parameter,
+	char **error)
+{
+	u16 i, length;
+
+	length = ntohs(parameter->length);
+	if (length < sizeof(struct sctp_parameter)) {
+		asprintf(error, "PARAMETER too short (type=0x%04x, length=%u)",
+			 ntohs(parameter->type), length);
+		return STATUS_ERR;
+	}
+	fputs("PARAMETER[", s);
+	fprintf(s, "type=0x%04x, ", ntohs(parameter->type));
+	fputs("value=[", s);
+	for (i = 0; i < length - sizeof(struct sctp_parameter); i++) {
+		fprintf(s, "%s0x%02x",
+			   i > 0 ? ", " : "",
+			   parameter->value[i]);
+	}
+	fputs("]]", s);
+	return STATUS_OK;
+}
+
+static int sctp_parameter_to_string(FILE *s,
+				    struct sctp_parameter *parameter,
+				    char **error)
+{
+	int result;
+
+	switch (ntohs(parameter->type)) {
+	case SCTP_IPV4_ADDRESS_PARAMETER_TYPE:
+		result = sctp_ipv4_address_parameter_to_string(s,
+			(struct sctp_ipv4_address_parameter *)parameter, error);
+		break;
+	case SCTP_IPV6_ADDRESS_PARAMETER_TYPE:
+		result = sctp_ipv6_address_parameter_to_string(s,
+			(struct sctp_ipv6_address_parameter *)parameter, error);
+		break;
+	case SCTP_STATE_COOKIE_PARAMETER_TYPE:
+		result = sctp_state_cookie_parameter_to_string(s,
+			(struct sctp_state_cookie_parameter *)parameter, error);
+		break;
+	case SCTP_UNRECOGNIZED_PARAMETER_PARAMETER_TYPE:
+		result = sctp_unrecognized_parameter_parameter_to_string(s,
+			(struct sctp_unrecognized_parameter_parameter *)parameter,
+			error);
+		break;
+	case SCTP_COOKIE_PRESERVATIVE_PARAMETER_TYPE:
+		result = sctp_cookie_preservative_parameter_to_string(s,
+			(struct sctp_cookie_preservative_parameter *)parameter,
+			error);
+		break;
+	case SCTP_HOSTNAME_ADDRESS_PARAMETER_TYPE:
+		result = sctp_hostname_parameter_to_string(s,
+			(struct sctp_hostname_address_parameter *)parameter,
+			error);
+		break;
+	case SCTP_SUPPORTED_ADDRESS_TYPES_PARAMETER_TYPE:
+		result = sctp_supported_address_types_parameter_to_string(s,
+			(struct sctp_supported_address_types_parameter *)parameter,
+			error);
+		break;
+	case SCTP_ECN_CAPABLE_PARAMETER_TYPE:
+		result = sctp_ecn_capable_parameter_to_string(s,
+			(struct sctp_ecn_capable_parameter *)parameter, error);
+		break;
+	default:
+		result = sctp_unknown_parameter_to_string(s, parameter, error);
+		break;
+	}
+	return result;
+}
+
+static int sctp_invalid_stream_identifier_cause_to_string(
+	FILE *s,
+	struct sctp_invalid_stream_identifier_cause *cause,
+	char **error)
+{
+	u16 length;
+
+	length = ntohs(cause->length);
+	if (length != sizeof(struct sctp_invalid_stream_identifier_cause)) {
+		asprintf(error,
+			 "INVALID_STREAM_IDENTIFIER cause invalid (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	fprintf(s, "INVALID_STREAM_IDENTIFIER[sid=%u]", ntohs(cause->sid));
+	return STATUS_OK;
+}
+
+static int sctp_missing_mandatory_parameter_cause_to_string(
+	FILE *s,
+	struct sctp_missing_mandatory_parameter_cause *cause,
+	char **error)
+{
+	u16 length;
+	u32 i, nr_parameters;
+
+	length = ntohs(cause->length);
+	if (length < sizeof(struct sctp_missing_mandatory_parameter_cause)) {
+		asprintf(error,
+			 "MISSING_MANDATORY_PARAMETER cause too short (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	nr_parameters = ntohl(cause->nr_parameters);
+	if (length != sizeof(struct sctp_missing_mandatory_parameter_cause) +
+		      nr_parameters * sizeof(u16)) {
+		asprintf(error, "MISSING_MANDATORY_PARAMETER inconsistent");
+		return STATUS_ERR;
+	}
+	fputs("MISSING_MANDATORY_PARAMETER[", s);
+	for (i = 0; i < nr_parameters; i++) {
+		if (i > 0)
+			fputs(", ", s);
+		switch (ntohs(cause->parameter_type[i])) {
+		case SCTP_IPV4_ADDRESS_PARAMETER_TYPE:
+			fputs("IPV4_ADDRESS", s);
+			break;
+		case SCTP_IPV6_ADDRESS_PARAMETER_TYPE:
+			fputs("IPV6_ADDRESS", s);
+			break;
+		case SCTP_STATE_COOKIE_PARAMETER_TYPE:
+			fputs("STATE_COOKIE", s);
+			break;
+		case SCTP_UNRECOGNIZED_PARAMETER_PARAMETER_TYPE:
+			fputs("UNRECOGNIZED_PARAMETER", s);
+			break;
+		case SCTP_COOKIE_PRESERVATIVE_PARAMETER_TYPE:
+			fputs("COOKIE_PRESERVATIVE", s);
+			break;
+		case SCTP_HOSTNAME_ADDRESS_PARAMETER_TYPE:
+			fputs("HOSTNAME_ADDRESS", s);
+			break;
+		case SCTP_SUPPORTED_ADDRESS_TYPES_PARAMETER_TYPE:
+			fputs("SUPPORTED_ADDRESS_TYPES", s);
+			break;
+		case SCTP_ECN_CAPABLE_PARAMETER_TYPE:
+			fputs("ECN_CAPABLE", s);
+			break;
+		default:
+			fprintf(s, "0x%04x", ntohs(cause->parameter_type[i]));
+			break;
+		}
+	}
+	fputc(']', s);
+	return STATUS_OK;
+}
+
+static int sctp_stale_cookie_error_cause_to_string(
+	FILE *s,
+	struct sctp_stale_cookie_error_cause *cause,
+	char **error)
+{
+	u16 length;
+
+	length = ntohs(cause->length);
+	if (length != sizeof(struct sctp_stale_cookie_error_cause)) {
+		asprintf(error, "STALE_COOKIE_ERROR cause invalid (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	fprintf(s, "STALE_COOKIE_ERROR[staleness=%u]", ntohl(cause->staleness));
+	return STATUS_OK;
+}
+
+static int sctp_out_of_resources_cause_to_string(
+	FILE *s,
+	struct sctp_out_of_resources_cause *cause,
+	char **error)
+{
+	u16 length;
+
+	length = ntohs(cause->length);
+	if (length != sizeof(struct sctp_out_of_resources_cause)) {
+		asprintf(error, "OUT_OF_RESOURCES cause invalid (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	fputs("OUT_OF_RESOURCES[]", s);
+	return STATUS_OK;
+}
+
+static int sctp_unresolvable_address_cause_to_string(
+	FILE *s,
+	struct sctp_unresolvable_address_cause *cause,
+	char **error)
+{
+	u16 cause_length, parameter_length, cause_padding, parameter_padding;
+	struct sctp_parameter *parameter;
+	int result;
+
+	cause_length = ntohs(cause->length);
+	if (cause_length < sizeof(struct sctp_unresolvable_address_cause) +
+			   sizeof(struct sctp_parameter)) {
+		asprintf(error, "UNRESOLVABLE_ADDRESS cause too short");
+		return STATUS_ERR;
+	}
+	cause_padding = cause_length & 0x0003;
+	if (cause_padding != 0)
+		cause_padding = 4 - cause_padding;
+	parameter = (struct sctp_parameter *)cause->parameter;
+	parameter_length = ntohs(parameter->length);
+	parameter_padding = parameter_length & 0x0003;
+	if (parameter_padding != 0)
+		parameter_padding = 4 - parameter_padding;
+	if (cause_length + cause_padding !=
+	    sizeof(struct sctp_unresolvable_address_cause) +
+	    parameter_length + parameter_padding) {
+		asprintf(error, "UNRESOLVABLE_ADDRESS cause inconsistent");
+		return STATUS_ERR;
+	}
+	fputs("UNRESOLVABLE_ADDRESS[", s);
+	result = sctp_parameter_to_string(s, parameter, error);
+	fputc(']', s);
+	return result;
+}
+
+static int sctp_unrecognized_chunk_type_cause_to_string(
+	FILE *s,
+	struct sctp_unrecognized_chunk_type_cause *cause,
+	char **error)
+{
+	u16 cause_length, chunk_length, cause_padding, chunk_padding;
+	struct sctp_chunk *chunk;
+	int result;
+
+	cause_length = ntohs(cause->length);
+	if (cause_length < sizeof(struct sctp_unrecognized_chunk_type_cause) +
+			   sizeof(struct sctp_chunk)) {
+		asprintf(error, "UNRECOGNIZED_CHUNK cause too short");
+		return STATUS_ERR;
+	}
+	cause_padding = cause_length & 0x0003;
+	if (cause_padding != 0)
+		cause_padding = 4 - cause_padding;
+	chunk = (struct sctp_chunk *)cause->chunk;
+	chunk_length = ntohs(chunk->length);
+	chunk_padding = chunk_length & 0x0003;
+	if (chunk_padding != 0)
+		chunk_padding = 4 - chunk_padding;
+	/* XXX: Do we need to deal with padding here? */
+	if (cause_length + cause_padding !=
+	    sizeof(struct sctp_unrecognized_chunk_type_cause) +
+	    chunk_length + chunk_padding) {
+		asprintf(error, "UNRECOGNIZED_CHUNK cause inconsistent");
+		return STATUS_ERR;
+	}
+	fputs("UNRECOGNIZED_CHUNK[", s);
+	result = sctp_chunk_to_string(s, chunk, error);
+	fputc(']', s);
+	return result;
+}
+
+static int sctp_invalid_mandatory_parameter_cause_to_string(
+	FILE *s,
+	struct sctp_invalid_mandatory_parameter_cause *cause,
+	char **error)
+{
+	u16 length;
+
+	length = ntohs(cause->length);
+	if (length != sizeof(struct sctp_invalid_mandatory_parameter_cause)) {
+		asprintf(error,
+			 "INVALID_MANDATORY_PARAMETER cause invalid (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	fputs("INVALID_MANDATORY_PARAMETER[]", s);
+	return STATUS_OK;
+}
+
+static int sctp_unrecognized_parameters_cause_to_string(
+	FILE *s,
+	struct sctp_unrecognized_parameters_cause *cause,
+	char **error)
+{
+	u16 length, parameters_length, index;
+	struct sctp_parameters_iterator iter;
+	struct sctp_parameter *parameter;
+	int result = STATUS_OK;
+
+	length = ntohs(cause->length);
+	if (length < sizeof(struct sctp_unrecognized_parameters_cause)) {
+		asprintf(error,
+			 "UNRECOGNIZED_PARAMETERS cause too short (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	parameters_length = length -
+			    sizeof(struct sctp_unrecognized_parameters_cause);
+	fputs("UNRECOGNIZED_PARAMETERS[", s);
+	index = 0;
+	for (parameter = sctp_parameters_begin(cause->parameters,
+					       parameters_length,
+					       &iter, error);
+	     parameter != NULL;
+	     parameter = sctp_parameters_next(&iter, error)) {
+		if (index > 0)
+			fputs(", ", s);
+		if (*error != NULL)
+			break;
+		result = sctp_parameter_to_string(s, parameter, error);
+		if (result != STATUS_OK)
+			break;
+		index++;
+	}
+	fputc(']', s);
+	return STATUS_OK;
+}
+
+static int sctp_no_user_data_cause_to_string(
+	FILE *s,
+	struct sctp_no_user_data_cause *cause,
+	char **error)
+{
+	u16 length;
+
+	length = ntohs(cause->length);
+	if (length != sizeof(struct sctp_no_user_data_cause)) {
+		asprintf(error, "NO_USER_DATA cause invalid (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	fprintf(s, "NO_USER_DATA[tsn=%u]", ntohl(cause->tsn));
+	return STATUS_OK;
+}
+
+static int sctp_cookie_received_while_shutdown_cause_to_string(
+	FILE *s,
+	struct sctp_cookie_received_while_shutdown_cause *cause,
+	char **error)
+{
+	u16 length;
+
+	length = ntohs(cause->length);
+	if (length !=
+	    sizeof(struct sctp_cookie_received_while_shutdown_cause)) {
+		asprintf(error,
+			 "COOKIE_RECEIVED_WHILE_SHUTDOWN cause invalid (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	fputs("COOKIE_RECEIVED_WHILE_SHUTDOWN[]", s);
+	return STATUS_OK;
+}
+
+static int sctp_restart_with_new_addresses_cause_to_string(
+	FILE *s,
+	struct sctp_restart_with_new_addresses_cause *cause,
+	char **error)
+{
+	u16 length, addressess_length, index;
+	struct sctp_parameters_iterator iter;
+	struct sctp_parameter *parameter;
+	int result = STATUS_OK;
+
+	length = ntohs(cause->length);
+	if (length < sizeof(struct sctp_restart_with_new_addresses_cause)) {
+		asprintf(error,
+			 "RESTART_WITH_NEW_ADDRESSES cause too short (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	addressess_length =
+		length - sizeof(struct sctp_restart_with_new_addresses_cause);
+	fputs("RESTART_WITH_NEW_ADDRESSES[", s);
+	index = 0;
+	for (parameter = sctp_parameters_begin(cause->addresses,
+					       addressess_length,
+					       &iter, error);
+	     parameter != NULL;
+	     parameter = sctp_parameters_next(&iter, error)) {
+		if (index > 0)
+			fputs(", ", s);
+		if (*error != NULL)
+			break;
+		result = sctp_parameter_to_string(s, parameter, error);
+		if (result != STATUS_OK)
+			break;
+		index++;
+	}
+	fputc(']', s);
+	return STATUS_OK;
+}
+
+static int sctp_user_initiated_abort_cause_to_string(
+	FILE *s,
+	struct sctp_user_initiated_abort_cause *cause,
+	char **error)
+{
+	u16 length;
+
+	length = ntohs(cause->length);
+	if (length < sizeof(struct sctp_user_initiated_abort_cause)) {
+		asprintf(error,
+			 "USER_INITIATED_ABORT cause illegal (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	fprintf(s, "USER_INITIATED_ABORT[%.*s]",
+		(int)(length - sizeof(struct sctp_user_initiated_abort_cause)),
+		(char *)cause->information);
+	return STATUS_OK;
+}
+
+static int sctp_protocol_violation_cause_to_string(
+	FILE *s,
+	struct sctp_protocol_violation_cause *cause,
+	char **error)
+{
+	u16 length;
+
+	length = ntohs(cause->length);
+	if (length < sizeof(struct sctp_protocol_violation_cause)) {
+		asprintf(error, "PROTOCOL_VIOLOATION cause illegal (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	fprintf(s, "PROTOCOL_VIOLATION[%.*s]",
+		(int)(length - sizeof(struct sctp_protocol_violation_cause)),
+		(char *)cause->information);
+	return STATUS_OK;
+}
+
+static int sctp_unknown_cause_to_string(FILE *s,
+					struct sctp_cause *cause,
+					char **error)
+{
+	u16 i, length;
+
+	length = ntohs(cause->length);
+	if (length < sizeof(struct sctp_parameter)) {
+		asprintf(error, "CAUSE too short (code=0x%04x, length=%u)",
+			 ntohs(cause->code), length);
+		return STATUS_ERR;
+	}
+	fputs("CAUSE[", s);
+	fprintf(s, "code=0x%04x, ", ntohs(cause->code));
+	fputs("value=[", s);
+	for (i = 0; i < length - sizeof(struct sctp_cause); i++) {
+		fprintf(s, "%s0x%02x",
+			   i > 0 ? ", " : "",
+			   cause->information[i]);
+	}
+	fputs("]]", s);
+	return STATUS_OK;
+}
+
+static int sctp_cause_to_string(FILE *s, struct sctp_cause *cause, char **error)
+{
+	int result;
+
+	switch (ntohs(cause->code)) {
+	case SCTP_INVALID_STREAM_IDENTIFIER_CAUSE_CODE:
+		result = sctp_invalid_stream_identifier_cause_to_string(s,
+			(struct sctp_invalid_stream_identifier_cause *)cause,
+			error);
+		break;
+	case SCTP_MISSING_MADATORY_PARAMETER_CAUSE_CODE:
+		result = sctp_missing_mandatory_parameter_cause_to_string(s,
+			(struct sctp_missing_mandatory_parameter_cause *)cause,
+			error);
+		break;
+	case SCTP_STALE_COOKIE_ERROR_CAUSE_CODE:
+		result = sctp_stale_cookie_error_cause_to_string(s,
+			(struct sctp_stale_cookie_error_cause *)cause, error);
+		break;
+	case SCTP_OUT_OF_RESOURCES_CAUSE_CODE:
+		result = sctp_out_of_resources_cause_to_string(s,
+			(struct sctp_out_of_resources_cause *)cause, error);
+		break;
+	case SCTP_UNRESOLVABLE_ADDRESS_CAUSE_CODE:
+		result = sctp_unresolvable_address_cause_to_string(s,
+			(struct sctp_unresolvable_address_cause *)cause, error);
+		break;
+	case SCTP_UNRECOGNIZED_CHUNK_TYPE_CAUSE_CODE:
+		result = sctp_unrecognized_chunk_type_cause_to_string(s,
+			(struct sctp_unrecognized_chunk_type_cause *)cause,
+			error);
+		break;
+	case SCTP_INVALID_MADATORY_PARAMETER_CAUSE_CODE:
+		result = sctp_invalid_mandatory_parameter_cause_to_string(s,
+			(struct sctp_invalid_mandatory_parameter_cause *)cause,
+			error);
+		break;
+	case SCTP_UNRECOGNIZED_PARAMETERS_CAUSE_CODE:
+		result = sctp_unrecognized_parameters_cause_to_string(s,
+			(struct sctp_unrecognized_parameters_cause *)cause,
+			error);
+		break;
+	case SCTP_NO_USER_DATA_CAUSE_CODE:
+		result = sctp_no_user_data_cause_to_string(s,
+			(struct sctp_no_user_data_cause *)cause, error);
+		break;
+	case SCTP_COOKIE_RECEIVED_WHILE_SHUTDOWN_CAUSE_CODE:
+		result = sctp_cookie_received_while_shutdown_cause_to_string(s,
+			(struct sctp_cookie_received_while_shutdown_cause *)cause,
+			error);
+		break;
+	case SCTP_RESTART_WITH_NEW_ADDRESSES_CAUSE_CODE:
+		result = sctp_restart_with_new_addresses_cause_to_string(s,
+			(struct sctp_restart_with_new_addresses_cause *)cause,
+			error);
+		break;
+	case SCTP_USER_INITIATED_ABORT_CAUSE_CODE:
+		result = sctp_user_initiated_abort_cause_to_string(s,
+			(struct sctp_user_initiated_abort_cause *)cause, error);
+		break;
+	case SCTP_PROTOCOL_VIOLATION_CAUSE_CODE:
+		result = sctp_protocol_violation_cause_to_string(s,
+			(struct sctp_protocol_violation_cause *)cause, error);
+		break;
+	default:
+		result = sctp_unknown_cause_to_string(s, cause, error);
+		break;
+	}
+	return result;
+}
+
+static int sctp_data_chunk_to_string(FILE *s,
+				     struct sctp_data_chunk *chunk,
+				     char **error)
+{
+	u16 length;
+	u8 flags;
+
+	flags = chunk->flags;
+	length = ntohs(chunk->length);
+	if (length < sizeof(struct sctp_data_chunk)) {
+		asprintf(error, "DATA chunk too short (length=%u)", length);
+		return STATUS_ERR;
+	}
+	fputs("DATA[", s);
+	if (flags != 0x00) {
+		fputs("flags=", s);
+		if (flags & ~(SCTP_DATA_CHUNK_I_BIT |
+			      SCTP_DATA_CHUNK_U_BIT |
+			      SCTP_DATA_CHUNK_B_BIT |
+			      SCTP_DATA_CHUNK_E_BIT))
+			fprintf(s, "0x%02x", chunk->flags);
+		else {
+			if (flags & SCTP_DATA_CHUNK_I_BIT)
+				fputc('I', s);
+			if (flags & SCTP_DATA_CHUNK_U_BIT)
+				fputc('U', s);
+			if (flags & SCTP_DATA_CHUNK_B_BIT)
+				fputc('B', s);
+			if (flags & SCTP_DATA_CHUNK_E_BIT)
+				fputc('E', s);
+		}
+		fputs(", ", s);
+	}
+	fprintf(s, "tsn=%u, ", ntohl(chunk->tsn));
+	fprintf(s, "sid=%d, ", ntohs(chunk->sid));
+	fprintf(s, "ssn=%u, ", ntohs(chunk->ssn));
+	fprintf(s, "ppid=%u, ", ntohl(chunk->ppid));
+	fprintf(s, "payload_len=%u]",
+		length - (u16)sizeof(struct sctp_data_chunk));
+	return STATUS_OK;
+}
+
+static int sctp_init_chunk_to_string(FILE *s,
+				     struct sctp_init_chunk *chunk,
+				     char **error)
+{
+	struct sctp_parameters_iterator iter;
+	struct sctp_parameter *parameter;
+	u16 length, parameters_length;
+	u8 flags;
+	int result = STATUS_OK;
+
+	assert(*error == NULL);
+	flags = chunk->flags;
+	length = ntohs(chunk->length);
+	if (length < sizeof(struct sctp_init_chunk)) {
+		asprintf(error, "INIT chunk too short (length=%u)", length);
+		return STATUS_ERR;
+	}
+	parameters_length = length - sizeof(struct sctp_init_chunk);
+	fputs("INIT[", s);
+	if (flags != 0x00)
+		fprintf(s, "flags=0x%02x, ", chunk->flags);
+	fprintf(s, "tag=%u, ", ntohl(chunk->initiate_tag));
+	fprintf(s, "a_rwnd=%d, ", ntohl(chunk->a_rwnd));
+	fprintf(s, "os=%u, ", ntohs(chunk->os));
+	fprintf(s, "is=%u, ", ntohs(chunk->is));
+	fprintf(s, "tsn=%u", ntohl(chunk->initial_tsn));
+	for (parameter = sctp_parameters_begin(chunk->parameter,
+					       parameters_length,
+					       &iter, error);
+	     parameter != NULL;
+	     parameter = sctp_parameters_next(&iter, error)) {
+		fputs(", ", s);
+		if (*error != NULL)
+			break;
+		result = sctp_parameter_to_string(s, parameter, error);
+		if (result != STATUS_OK)
+			break;
+	}
+	fputc(']', s);
+	if (*error != NULL)
+		result = STATUS_ERR;
+	return result;
+}
+
+static int sctp_init_ack_chunk_to_string(FILE *s,
+					 struct sctp_init_ack_chunk *chunk,
+					 char **error)
+{
+	struct sctp_parameters_iterator iter;
+	struct sctp_parameter *parameter;
+	u16 length, parameters_length;
+	u8 flags;
+	int result = STATUS_OK;
+
+	assert(*error == NULL);
+	flags = chunk->flags;
+	length = ntohs(chunk->length);
+	if (length < sizeof(struct sctp_init_ack_chunk)) {
+		asprintf(error, "INIT_ACK chunk too short (length=%u)", length);
+		return STATUS_ERR;
+	}
+	parameters_length = length - sizeof(struct sctp_init_ack_chunk);
+	fputs("INIT_ACK[", s);
+	if (flags != 0x00)
+		fprintf(s, "flags=0x%02x, ", chunk->flags);
+	fprintf(s, "tag=%u, ", ntohl(chunk->initiate_tag));
+	fprintf(s, "a_rwnd=%d, ", ntohl(chunk->a_rwnd));
+	fprintf(s, "os=%u, ", ntohs(chunk->os));
+	fprintf(s, "is=%u, ", ntohs(chunk->is));
+	fprintf(s, "tsn=%u", ntohl(chunk->initial_tsn));
+	for (parameter = sctp_parameters_begin(chunk->parameter,
+					       parameters_length,
+					       &iter, error);
+	     parameter != NULL;
+	     parameter = sctp_parameters_next(&iter, error)) {
+		fputs(", ", s);
+		if (*error != NULL)
+			break;
+		result = sctp_parameter_to_string(s, parameter, error);
+		if (result != STATUS_OK)
+			break;
+	}
+	fputc(']', s);
+	if (*error != NULL)
+		result = STATUS_ERR;
+	return result;
+}
+
+static int sctp_sack_chunk_to_string(FILE *s,
+				     struct sctp_sack_chunk *chunk,
+				     char **error)
+{
+	u16 length;
+	u16 nr_gaps, nr_dups;
+	u16 i;
+	u8 flags;
+
+	flags = chunk->flags;
+	length = ntohs(chunk->length);
+	if (length < sizeof(struct sctp_sack_chunk)) {
+		asprintf(error, "SACK chunk too short (length=%u)", length);
+		return STATUS_ERR;
+	}
+	nr_gaps = ntohs(chunk->nr_gap_blocks);
+	nr_dups = ntohs(chunk->nr_dup_tsns);
+	if (length != sizeof(struct sctp_sack_chunk) +
+		      (nr_gaps + nr_dups) * sizeof(u32)) {
+		asprintf(error, "SACK chunk length inconsistent");
+		return STATUS_ERR;
+	}
+	fputs("SACK[", s);
+	if (flags != 0)
+		fprintf(s, "flags=0x%02x, ", flags);
+	fprintf(s, "cum_tsn=%u, ", ntohl(chunk->cum_tsn));
+	fprintf(s, "a_rwnd=%u, ", ntohl(chunk->a_rwnd));
+	fputs("gaps=[", s);
+	for (i = 0; i < nr_gaps; i++)
+		fprintf(s, "%s%u-%u",
+			   i > 0 ? ", " : "",
+			   ntohs(chunk->block[i].gap.start),
+			   ntohs(chunk->block[i].gap.end));
+	fputs("], dups=[", s);
+	for (i = 0; i < nr_dups; i++)
+		fprintf(s, "%s%u",
+			   i > 0 ? ", " : "",
+			   ntohl(chunk->block[i + nr_gaps].tsn));
+	fputs("]]", s);
+	return STATUS_OK;
+}
+
+static int sctp_heartbeat_chunk_to_string(FILE *s,
+					  struct sctp_heartbeat_chunk *chunk,
+					  char **error)
+{
+	u16 length;
+	u8 flags;
+
+	flags = chunk->flags;
+	length = ntohs(chunk->length);
+	fputs("HEARTBEAT[", s);
+	if (flags != 0x00)
+		fprintf(s, "flags=0x%02x, ", flags);
+	fprintf(s, "info_len=%u",
+		length - (u16)sizeof(struct sctp_heartbeat_chunk));
+	fputc(']', s);
+	return STATUS_OK;
+}
+
+static int sctp_heartbeat_ack_chunk_to_string(
+	FILE *s,
+	struct sctp_heartbeat_ack_chunk *chunk,
+	char **error)
+{
+	u16 length;
+	u8 flags;
+
+	flags = chunk->flags;
+	length = ntohs(chunk->length);
+	fputs("HEARTBEAT_ACK[", s);
+	if (flags != 0x00)
+		fprintf(s, "flags=0x%02x, ", flags);
+	fprintf(s, "info_len=%u",
+		length - (u16)sizeof(struct sctp_heartbeat_chunk));
+	fputc(']', s);
+	return STATUS_OK;
+}
+
+static int sctp_abort_chunk_to_string(FILE *s,
+				      struct sctp_abort_chunk *chunk,
+				      char **error)
+{
+	struct sctp_causes_iterator iter;
+	struct sctp_cause *cause;
+	u16 length, index;
+	u8 flags;
+	int result = STATUS_OK;
+
+	flags = chunk->flags;
+	length = ntohs(chunk->length);
+	if (length < sizeof(struct sctp_abort_chunk)) {
+		asprintf(error, "ABORT chunk too short (length=%u)", length);
+		return STATUS_ERR;
+	}
+	fputs("ABORT[", s);
+	if (flags != 0x00) {
+		fputs("flags=", s);
+		if (flags & ~SCTP_ABORT_CHUNK_T_BIT)
+			fprintf(s, "0x%02x", chunk->flags);
+		else
+			if (flags & SCTP_ABORT_CHUNK_T_BIT)
+				fputc('T', s);
+	}
+	index = 0;
+	for (cause = sctp_causes_begin((struct sctp_chunk *)chunk,
+				       SCTP_ABORT_CHUNK_CAUSE_OFFSET,
+				       &iter, error);
+	     cause != NULL;
+	     cause = sctp_causes_next(&iter, error)) {
+		if (index > 0)
+			fputs(", ", s);
+		if (*error != NULL)
+			break;
+		result = sctp_cause_to_string(s, cause, error);
+		if (result != STATUS_OK)
+			break;
+		index++;
+	}
+	fputc(']', s);
+	if (*error != NULL)
+		result = STATUS_ERR;
+	return result;
+}
+
+static int sctp_shutdown_chunk_to_string(FILE *s,
+					 struct sctp_shutdown_chunk *chunk,
+					 char **error)
+{
+	u8 flags;
+	u16 length;
+
+	flags = chunk->flags;
+	length = ntohs(chunk->length);
+	if (length != sizeof(struct sctp_shutdown_chunk)) {
+		asprintf(error, "SHUTDOWN chunk illegal (length=%u)", length);
+		return STATUS_ERR;
+	}
+	fputs("SHUTDOWN[", s);
+	if (flags != 0x00)
+		fprintf(s, "flags=0x%02x, ", chunk->flags);
+	fprintf(s, "tsn=%u", ntohl(chunk->cum_tsn));
+	fputc(']', s);
+	return STATUS_OK;
+}
+
+static int sctp_shutdown_ack_chunk_to_string(
+	FILE *s,
+	struct sctp_shutdown_ack_chunk *chunk,
+	char **error)
+{
+	u8 flags;
+	u16 length;
+
+	flags = chunk->flags;
+	length = ntohs(chunk->length);
+	if (length != sizeof(struct sctp_shutdown_ack_chunk)) {
+		asprintf(error, "SHUTDOWN_ACK chunk too long (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	fputs("SHUTDOWN_ACK[", s);
+	if (flags != 0x00)
+		fprintf(s, "flags=0x%02x, ", chunk->flags);
+	fputc(']', s);
+	return STATUS_OK;
+}
+
+static int sctp_error_chunk_to_string(FILE *s,
+				      struct sctp_error_chunk *chunk,
+				      char **error)
+{
+	struct sctp_causes_iterator iter;
+	struct sctp_cause *cause;
+	u16 length, index;
+	u8 flags;
+	int result = STATUS_OK;
+
+	flags = chunk->flags;
+	length = ntohs(chunk->length);
+	if (length < sizeof(struct sctp_abort_chunk)) {
+		asprintf(error, "ERROR chunk too short (length=%u)", length);
+		return STATUS_ERR;
+	}
+	fputs("ERROR[", s);
+	if (flags != 0x00)
+		fprintf(s, "flags=0x%02x", chunk->flags);
+	index = 0;
+	for (cause = sctp_causes_begin((struct sctp_chunk *)chunk,
+				       SCTP_ERROR_CHUNK_CAUSE_OFFSET,
+				       &iter, error);
+	     cause != NULL;
+	     cause = sctp_causes_next(&iter, error)) {
+		if (index > 0)
+			fputs(", ", s);
+		if (*error != NULL)
+			break;
+		result = sctp_cause_to_string(s, cause, error);
+		if (result != STATUS_OK)
+			break;
+		index++;
+	}
+	fputc(']', s);
+	if (*error != NULL)
+		result = STATUS_ERR;
+	return STATUS_OK;
+}
+
+static int sctp_cookie_echo_chunk_to_string(
+	FILE *s,
+	struct sctp_cookie_echo_chunk *chunk,
+	char **error)
+{
+	u16 length;
+	u8 flags;
+
+	flags = chunk->flags;
+	length = ntohs(chunk->length);
+	fputs("COOKIE_ECHO[", s);
+	if (flags != 0x00)
+		fprintf(s, "flags=0x%02x, ", flags);
+	fprintf(s, "cookie_len=%u",
+		length - (u16)sizeof(struct sctp_cookie_echo_chunk));
+	fputc(']', s);
+	return STATUS_OK;
+}
+
+static int sctp_cookie_ack_chunk_to_string(FILE *s,
+					   struct sctp_cookie_ack_chunk *chunk,
+					   char **error)
+{
+	u8 flags;
+	u16 length;
+
+	flags = chunk->flags;
+	length = ntohs(chunk->length);
+	if (length != sizeof(struct sctp_cookie_ack_chunk)) {
+		asprintf(error, "COOKIE_ACK chunk too long (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	fputs("COOKIE_ACK[", s);
+	if (flags != 0x00)
+		fprintf(s, "flags=0x%02x", chunk->flags);
+	fputc(']', s);
+	return STATUS_OK;
+}
+
+static int sctp_ecne_chunk_to_string(FILE *s,
+				     struct sctp_ecne_chunk *chunk,
+				     char **error)
+{
+	u8 flags;
+	u16 length;
+
+	flags = chunk->flags;
+	length = ntohs(chunk->length);
+	if (length != sizeof(struct sctp_ecne_chunk)) {
+		asprintf(error, "ECNE chunk illegal (length=%u)", length);
+		return STATUS_ERR;
+	}
+	fputs("ECNE[", s);
+	if (flags != 0x00)
+		fprintf(s, "flags=0x%02x, ", chunk->flags);
+	fprintf(s, "tsn=%u", ntohl(chunk->lowest_tsn));
+	fputc(']', s);
+	return STATUS_OK;
+}
+
+static int sctp_cwr_chunk_to_string(FILE *s,
+				    struct sctp_cwr_chunk *chunk,
+				    char **error)
+{
+	u8 flags;
+	u16 length;
+
+	flags = chunk->flags;
+	length = ntohs(chunk->length);
+	if (length != sizeof(struct sctp_cwr_chunk)) {
+		asprintf(error, "CWR chunk illegal (length=%u)", length);
+		return STATUS_ERR;
+	}
+	fputs("CWR[", s);
+	if (flags != 0x00)
+		fprintf(s, "flags=0x%02x, ", chunk->flags);
+	fprintf(s, "tsn=%u", ntohl(chunk->lowest_tsn));
+	fputc(']', s);
+	return STATUS_OK;
+}
+
+static int sctp_shutdown_complete_chunk_to_string(
+	FILE *s,
+	struct sctp_shutdown_complete_chunk *chunk,
+	char **error)
+{
+	u8 flags;
+	u16 length;
+
+	flags = chunk->flags;
+	length = ntohs(chunk->length);
+	if (length != sizeof(struct sctp_shutdown_complete_chunk)) {
+		asprintf(error, "SHUTDOWN_COMPLETE chunk too long (length=%u)",
+			 length);
+		return STATUS_ERR;
+	}
+	fputs("SHUTDOWN_COMPLETE[", s);
+	if (flags != 0x00) {
+		fputs("flags=", s);
+		if (flags & ~SCTP_SHUTDOWN_COMPLETE_CHUNK_T_BIT)
+			fprintf(s, "0x%02x", chunk->flags);
+		else
+			if (flags & SCTP_SHUTDOWN_COMPLETE_CHUNK_T_BIT)
+				fputc('T', s);
+	}
+	fputc(']', s);
+	return STATUS_OK;
+}
+
+static int sctp_unknown_chunk_to_string(FILE *s,
+					struct sctp_chunk *chunk,
+					char **error)
+{
+	u16 i, length;
+
+	length = ntohs(chunk->length);
+	fputs("CHUNK[", s);
+	fprintf(s, "type=0x%02x, ", chunk->type);
+	fprintf(s, "flags=0x%02x, ", chunk->flags);
+	fputs("value=[", s);
+	for (i = 0; i < length - sizeof(struct sctp_chunk); i++)
+		fprintf(s, "%s0x%02x",
+			   i > 0 ? ", " : "",
+			   chunk->value[i]);
+	fputs("]]", s);
+	return STATUS_OK;
+}
+
+int sctp_chunk_to_string(FILE *s, struct sctp_chunk *chunk, char **error)
+{
+	int result;
+
+	switch (chunk->type) {
+	case SCTP_DATA_CHUNK_TYPE:
+		result = sctp_data_chunk_to_string(s,
+			(struct sctp_data_chunk *)chunk, error);
+		break;
+	case SCTP_INIT_CHUNK_TYPE:
+		result = sctp_init_chunk_to_string(s,
+			(struct sctp_init_chunk *)chunk, error);
+		break;
+	case SCTP_INIT_ACK_CHUNK_TYPE:
+		result = sctp_init_ack_chunk_to_string(s,
+			(struct sctp_init_ack_chunk *)chunk, error);
+		break;
+	case SCTP_SACK_CHUNK_TYPE:
+		result = sctp_sack_chunk_to_string(s,
+			(struct sctp_sack_chunk *)chunk, error);
+		break;
+	case SCTP_HEARTBEAT_CHUNK_TYPE:
+		result = sctp_heartbeat_chunk_to_string(s,
+			(struct sctp_heartbeat_chunk *)chunk, error);
+		break;
+	case SCTP_HEARTBEAT_ACK_CHUNK_TYPE:
+		result = sctp_heartbeat_ack_chunk_to_string(s,
+			(struct sctp_heartbeat_ack_chunk *)chunk, error);
+		break;
+	case SCTP_ABORT_CHUNK_TYPE:
+		result = sctp_abort_chunk_to_string(s,
+			(struct sctp_abort_chunk *)chunk, error);
+		break;
+	case SCTP_SHUTDOWN_CHUNK_TYPE:
+		result = sctp_shutdown_chunk_to_string(s,
+			(struct sctp_shutdown_chunk *)chunk, error);
+		break;
+	case SCTP_SHUTDOWN_ACK_CHUNK_TYPE:
+		result = sctp_shutdown_ack_chunk_to_string(s,
+			(struct sctp_shutdown_ack_chunk *)chunk, error);
+		break;
+	case SCTP_ERROR_CHUNK_TYPE:
+		result = sctp_error_chunk_to_string(s,
+			(struct sctp_error_chunk *)chunk, error);
+		break;
+	case SCTP_COOKIE_ECHO_CHUNK_TYPE:
+		result = sctp_cookie_echo_chunk_to_string(s,
+			(struct sctp_cookie_echo_chunk *)chunk, error);
+		break;
+	case SCTP_COOKIE_ACK_CHUNK_TYPE:
+		result = sctp_cookie_ack_chunk_to_string(s,
+			(struct sctp_cookie_ack_chunk *)chunk, error);
+		break;
+	case SCTP_ECNE_CHUNK_TYPE:
+		result = sctp_ecne_chunk_to_string(s,
+			(struct sctp_ecne_chunk *)chunk, error);
+		break;
+	case SCTP_CWR_CHUNK_TYPE:
+		result = sctp_cwr_chunk_to_string(s,
+			(struct sctp_cwr_chunk *)chunk, error);
+		break;
+	case SCTP_SHUTDOWN_COMPLETE_CHUNK_TYPE:
+		result = sctp_shutdown_complete_chunk_to_string(s,
+			(struct sctp_shutdown_complete_chunk *)chunk, error);
+		break;
+	default:
+		result = sctp_unknown_chunk_to_string(s, chunk, error);
+		break;
+	}
+	return result;
+}
diff --git a/gtests/net/packetdrill/sctp_chunk_to_string.h b/gtests/net/packetdrill/sctp_chunk_to_string.h
new file mode 100644
index 0000000000000000000000000000000000000000..1503bbdf79c92d3ddbabf5d0fabcab1d8719df06
--- /dev/null
+++ b/gtests/net/packetdrill/sctp_chunk_to_string.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * 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: ncardwell@google.com (Neal Cardwell)
+ *
+ * Interface for generating human-readable representations of SCTP chunks.
+ */
+
+#ifndef __SCTP_CHUNK_TO_STRING_H__
+#define __SCTP_CHUNK_TO_STRING_H__
+
+#include "types.h"
+#include "packet.h"
+#include "sctp.h"
+
+/* Write to s a human-readable representation of the SCTP
+ * chunk. Returns STATUS_OK on success; on failure
+ * returns STATUS_ERR and sets error message.
+ */
+extern int sctp_chunk_to_string(FILE *s,
+				struct sctp_chunk *chunk,
+				char **error);
+
+#endif /* __SCTP_CHUNK_TO_STRING_H__ */
diff --git a/gtests/net/packetdrill/sctp_iterator.c b/gtests/net/packetdrill/sctp_iterator.c
new file mode 100644
index 0000000000000000000000000000000000000000..fc37d1622ad4b0f9ddab04e13b5bfa7479b379fc
--- /dev/null
+++ b/gtests/net/packetdrill/sctp_iterator.c
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * 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: ncardwell@google.com (Neal Cardwell)
+ *
+ * Implementation for module to allow iteration over SCTP chunks and
+ * parameters in wire format.
+ */
+
+#include "sctp_iterator.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include "packet.h"
+
+static struct sctp_chunk *get_current_chunk(struct sctp_chunks_iterator *iter,
+					    char **error)
+{
+	struct sctp_chunk *chunk;
+	u16 chunk_length;
+
+	if (iter->current_chunk == iter->packet_end)
+		iter->current_chunk = NULL;
+	else if (iter->current_chunk + sizeof(struct sctp_chunk) >
+		 iter->packet_end) {
+		asprintf(error, "Partial SCTP chunk not allowed");
+		iter->current_chunk = NULL;
+	} else {
+		chunk = (struct sctp_chunk *)iter->current_chunk;
+		chunk_length = ntohs(chunk->length);
+		if (iter->current_chunk + chunk_length > iter->packet_end) {
+			asprintf(error,
+				 "Partial SCTP chunk (type 0x%02x, length %u) not allowed",
+				 chunk->type, chunk_length);
+			iter->current_chunk = NULL;
+		}
+	}
+	return (struct sctp_chunk *)iter->current_chunk;
+}
+
+struct sctp_chunk *sctp_chunks_begin(struct packet *packet,
+				     struct sctp_chunks_iterator *iter,
+				     char **error)
+{
+	assert(*error == NULL);
+	memset(iter, 0, sizeof(*iter));
+	iter->current_chunk = packet_payload(packet);
+	iter->packet_end = packet_end(packet);
+	return get_current_chunk(iter, error);
+}
+
+struct sctp_chunk *sctp_chunks_next(struct sctp_chunks_iterator *iter,
+				    char **error)
+{
+	u16 chunk_length, padding_length;
+	struct sctp_chunk *current_chunk;
+
+	assert(*error == NULL);
+	current_chunk = (struct sctp_chunk *)iter->current_chunk;
+	chunk_length = ntohs(current_chunk->length);
+	padding_length = chunk_length & 0x0003;
+	if (padding_length > 0)
+		padding_length = 4 - padding_length;
+	assert(chunk_length >= sizeof(struct sctp_chunk));
+	assert(padding_length < 4);
+	iter->current_chunk += chunk_length;
+	iter->current_chunk += padding_length;
+	return get_current_chunk(iter, error);
+}
+
+static struct sctp_parameter *
+get_current_parameter(struct sctp_parameters_iterator *iter,
+		      char **error)
+{
+	struct sctp_parameter *parameter;
+	u16 parameter_length;
+
+	if (iter->current_parameter == iter->end)
+		iter->current_parameter = NULL;
+	else if (iter->current_parameter + sizeof(struct sctp_parameter) >
+		 iter->end) {
+		asprintf(error, "Partial SCTP parameter not allowed");
+		iter->current_parameter = NULL;
+	} else {
+		parameter = (struct sctp_parameter *)iter->current_parameter;
+		parameter_length = ntohs(parameter->length);
+		if (iter->current_parameter + parameter_length > iter->end) {
+			asprintf(error,
+				 "Partial SCTP parameter (type 0x%04x, length %u) not allowed",
+				 ntohs(parameter->type), parameter_length);
+			iter->current_parameter = NULL;
+		}
+	}
+	return (struct sctp_parameter *)iter->current_parameter;
+}
+
+struct sctp_parameter *
+sctp_parameters_begin(u8 *begin,
+		      u16 length,
+		      struct sctp_parameters_iterator *iter,
+		      char **error)
+{
+	assert(*error == NULL);
+	memset(iter, 0, sizeof(*iter));
+	iter->current_parameter = begin;
+	iter->end = begin + length;
+	return get_current_parameter(iter, error);
+}
+
+struct sctp_parameter *
+sctp_parameters_next(struct sctp_parameters_iterator *iter,
+		     char **error)
+{
+	u16 parameter_length, padding_length;
+	struct sctp_parameter *current_parameter;
+
+	assert(*error == NULL);
+	current_parameter = (struct sctp_parameter *)iter->current_parameter;
+	parameter_length = ntohs(current_parameter->length);
+	padding_length = parameter_length & 0x0003;
+	if (padding_length > 0)
+		padding_length = 4 - padding_length;
+	assert(parameter_length >= sizeof(struct sctp_parameter));
+	assert(padding_length < 4);
+	iter->current_parameter += parameter_length;
+	iter->current_parameter += padding_length;
+	return get_current_parameter(iter, error);
+}
+
+static struct sctp_cause *get_current_cause(struct sctp_causes_iterator *iter,
+					    char **error)
+{
+	struct sctp_cause *cause;
+	u16 cause_length;
+
+	if (iter->current_cause == iter->chunk_end)
+		iter->current_cause = NULL;
+	else if (iter->current_cause + sizeof(struct sctp_cause) >
+		 iter->chunk_end) {
+		asprintf(error, "Partial SCTP cause not allowed");
+		iter->current_cause = NULL;
+	} else {
+		cause = (struct sctp_cause *)iter->current_cause;
+		cause_length = ntohs(cause->length);
+		if (iter->current_cause + cause_length > iter->chunk_end) {
+			asprintf(error,
+				 "Partial SCTP cause (code 0x%04x, length %u) not allowed",
+				 ntohs(cause->code), cause_length);
+			iter->current_cause = NULL;
+		}
+	}
+	return (struct sctp_cause *)iter->current_cause;
+}
+
+struct sctp_cause *sctp_causes_begin(struct sctp_chunk *chunk,
+				     u16 offset,
+				     struct sctp_causes_iterator *iter,
+				     char **error)
+{
+	assert(*error == NULL);
+	memset(iter, 0, sizeof(*iter));
+	iter->current_cause = (u8 *)chunk + offset;
+	iter->chunk_end = (u8 *)chunk + ntohs(chunk->length);
+	return get_current_cause(iter, error);
+}
+
+struct sctp_cause *sctp_causes_next(struct sctp_causes_iterator *iter,
+				    char **error)
+{
+	u16 cause_length, padding_length;
+	struct sctp_cause *current_cause;
+
+	assert(*error == NULL);
+	current_cause = (struct sctp_cause *)iter->current_cause;
+	cause_length = ntohs(current_cause->length);
+	padding_length = cause_length & 0x0003;
+	if (padding_length > 0)
+		padding_length = 4 - padding_length;
+	assert(cause_length >= sizeof(struct sctp_cause));
+	assert(padding_length < 4);
+	iter->current_cause += cause_length;
+	iter->current_cause += padding_length;
+	return get_current_cause(iter, error);
+}
diff --git a/gtests/net/packetdrill/sctp_iterator.h b/gtests/net/packetdrill/sctp_iterator.h
new file mode 100644
index 0000000000000000000000000000000000000000..36d596b34629290687a0c95b565d17ef7fc3c24f
--- /dev/null
+++ b/gtests/net/packetdrill/sctp_iterator.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * 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: ncardwell@google.com (Neal Cardwell)
+ *
+ * Interface for a module to allow iteration over TCP options in wire format.
+ */
+
+#ifndef __SCTP_ITERATOR_H__
+#define __SCTP_ITERATOR_H__
+
+#include "types.h"
+
+#include "packet.h"
+
+/* Internal state for an iterator for SCTP chunks in wire format. */
+struct sctp_chunks_iterator {
+	u8 *current_chunk;
+	u8 *packet_end;
+};
+
+/* Internal state for an iterator for SCTP parameters in wire format. */
+struct sctp_parameters_iterator {
+	u8 *current_parameter;
+	u8 *end;
+};
+
+/* Internal state for an iterator for SCTP causes in wire format. */
+struct sctp_causes_iterator {
+	u8 *current_cause;
+	u8 *chunk_end;
+};
+
+/* Initialize the iterator to iterate over the SCTP chunks in the
+ * given packet. Return a pointer to the first chunk in the packet,
+ * or NULL if there are none.
+ */
+extern struct sctp_chunk *sctp_chunks_begin(
+	struct packet *packet,
+	struct sctp_chunks_iterator *iter,
+	char **error);
+
+/* Return a pointer to the next chunk in the packet, or NULL if there
+ * are no more. On failure returns NULL and sets error message.
+ */
+extern struct sctp_chunk *sctp_chunks_next(
+	struct sctp_chunks_iterator *iter,
+	char **error);
+
+/* Initialize the iterator to iterate over the SCTP parameters in the
+ * given chunk. Return a pointer to the first parameter in the chunk,
+ * or NULL if there are none or an error is present. This the error case
+ * *error is not NULL.
+ */
+extern struct sctp_parameter *sctp_parameters_begin(
+	u8 *begin,
+	u16 length,
+	struct sctp_parameters_iterator *iter,
+	char **error);
+
+/* Return a pointer to the next parameter in the chunk, or NULL if there
+ * are no more. On failure returns NULL and sets error message.
+ */
+extern struct sctp_parameter *sctp_parameters_next(
+	struct sctp_parameters_iterator *iter,
+	char **error);
+
+/* Initialize the iterator to iterate over the SCTP causes in the
+ * given chunk. Return a pointer to the first cause in the chunk,
+ * or NULL if there are none or an error is present. This the error case
+ * *error is not NULL.
+ */
+extern struct sctp_cause *sctp_causes_begin(
+	struct sctp_chunk *chunk,
+	u16 offset,
+	struct sctp_causes_iterator *iter,
+	char **error);
+
+/* Return a pointer to the next cause in the chunk, or NULL if there
+ * are no more. On failure returns NULL and sets error message.
+ */
+extern struct sctp_cause *sctp_causes_next(
+	struct sctp_causes_iterator *iter,
+	char **error);
+
+#endif /* __SCTP_ITERATOR_H__ */
diff --git a/gtests/net/packetdrill/socket.h b/gtests/net/packetdrill/socket.h
index 0b14a7e0fe52c2776d2ecc6f17111d0e49954d00..41b23c88df9507a38d411e4b84460ae0e0bb0047 100644
--- a/gtests/net/packetdrill/socket.h
+++ b/gtests/net/packetdrill/socket.h
@@ -166,7 +166,10 @@ static inline void get_packet_tuple(const struct packet *packet,
 	} else {
 		assert(!"bad IP version in packet");
 	}
-	if (packet->tcp != NULL) {
+	if (packet->sctp != NULL) {
+		tuple->src.port	= packet->sctp->src_port;
+		tuple->dst.port	= packet->sctp->dst_port;
+	} else if (packet->tcp != NULL) {
 		tuple->src.port	= packet->tcp->src_port;
 		tuple->dst.port	= packet->tcp->dst_port;
 	} else if (packet->udp != NULL) {
@@ -181,6 +184,7 @@ static inline void get_packet_tuple(const struct packet *packet,
 /* Set the tuple inside some TCP/IPv4 or TCP/IPv6 headers. */
 static inline void set_headers_tuple(struct ipv4 *ipv4,
 				     struct ipv6 *ipv6,
+				     struct sctp_common_header *sctp,
 				     struct tcp *tcp,
 				     struct udp *udp,
 				     struct udplite *udplite,
@@ -195,7 +199,10 @@ static inline void set_headers_tuple(struct ipv4 *ipv4,
 	} else {
 		assert(!"bad IP version in packet");
 	}
-	if (tcp != NULL) {
+	if (sctp != NULL) {
+		sctp->src_port = tuple->src.port;
+		sctp->dst_port = tuple->dst.port;
+	} else if (tcp != NULL) {
 		tcp->src_port = tuple->src.port;
 		tcp->dst_port = tuple->dst.port;
 	} else if (udp != NULL) {
@@ -226,6 +233,7 @@ static inline void set_icmp_echoed_tuple(struct packet *packet,
 	reverse_tuple(tuple, &echoed_tuple);
 	set_headers_tuple(packet_echoed_ipv4_header(packet),
 			  packet_echoed_ipv6_header(packet),
+			  packet_echoed_sctp_header(packet),
 			  packet_echoed_tcp_header(packet),
 			  packet_echoed_udp_header(packet),
 			  packet_echoed_udplite_header(packet),
@@ -236,7 +244,7 @@ static inline void set_icmp_echoed_tuple(struct packet *packet,
 static inline void set_packet_tuple(struct packet *packet,
 				    const struct tuple *tuple)
 {
-	set_headers_tuple(packet->ipv4, packet->ipv6, packet->tcp,
+	set_headers_tuple(packet->ipv4, packet->ipv6, packet->sctp, packet->tcp,
 			  packet->udp, packet->udplite, tuple);
 	if ((packet->icmpv4 != NULL) || (packet->icmpv6 != NULL))
 		set_icmp_echoed_tuple(packet, tuple);
diff --git a/gtests/net/packetdrill/types.h b/gtests/net/packetdrill/types.h
index c3b37cef43e6990e142a0ba09d53d1816227569a..6ccfbe4422bd9014a5b5c945b117ae7a3aa1a059 100644
--- a/gtests/net/packetdrill/types.h
+++ b/gtests/net/packetdrill/types.h
@@ -53,7 +53,10 @@
 #define __aligned(x) __attribute__ ((aligned(x)))
 #endif
 
-/* Make sure we have the following constant on all platforms */
+/* Make sure we have the following constants on all platforms */
+#ifndef IPPROTO_SCTP
+#define IPPROTO_SCTP 132
+#endif
 #ifndef IPPROTO_UDPLITE
 #define IPPROTO_UDPLITE 136
 #endif