From 4be3d393bf3987470f60a962f5797c9e30f7b9d5 Mon Sep 17 00:00:00 2001
From: Michael Tuexen <tuexen@fh-muenster.de>
Date: Tue, 23 May 2017 19:49:14 +0200
Subject: [PATCH] Add support for UDP encapsulated transport protocols.

This adds support for SCTP/UDP and TCP/UDP including ICMP
support.

Sponsored by:	Netflix, Inc.
---
 gtests/net/packetdrill/config.c               |  14 +-
 gtests/net/packetdrill/config.h               |   5 +-
 gtests/net/packetdrill/icmp_packet.c          |  29 +-
 gtests/net/packetdrill/icmp_packet.h          |   2 +
 gtests/net/packetdrill/netdev.c               |  12 +-
 gtests/net/packetdrill/netdev.h               |   6 +-
 gtests/net/packetdrill/packet.c               |   1 +
 gtests/net/packetdrill/packet.h               |  58 ++-
 gtests/net/packetdrill/packet_checksum.c      |  93 +++-
 gtests/net/packetdrill/packet_checksum.h      |   2 +-
 gtests/net/packetdrill/packet_parser.c        | 120 ++---
 gtests/net/packetdrill/packet_parser.h        |   4 +-
 gtests/net/packetdrill/packet_parser_test.c   | 127 +++++-
 gtests/net/packetdrill/packet_to_string.c     |  34 +-
 gtests/net/packetdrill/packet_to_string.h     |   2 +-
 .../net/packetdrill/packet_to_string_test.c   | 425 ++++++++++++++++--
 gtests/net/packetdrill/parser.y               | 173 +++----
 gtests/net/packetdrill/run_packet.c           | 251 ++++++++---
 gtests/net/packetdrill/sctp_packet.c          |  85 +++-
 gtests/net/packetdrill/sctp_packet.h          |  16 +-
 gtests/net/packetdrill/socket.h               |  18 +-
 gtests/net/packetdrill/symbols_freebsd.c      |  10 +-
 gtests/net/packetdrill/tcp_packet.c           |  34 +-
 gtests/net/packetdrill/tcp_packet.h           |   2 +
 gtests/net/packetdrill/wire_client_netdev.c   |   2 +-
 gtests/net/packetdrill/wire_server_netdev.c   |   6 +-
 26 files changed, 1176 insertions(+), 355 deletions(-)

diff --git a/gtests/net/packetdrill/config.c b/gtests/net/packetdrill/config.c
index 4df80329..e79061c4 100644
--- a/gtests/net/packetdrill/config.c
+++ b/gtests/net/packetdrill/config.c
@@ -60,6 +60,7 @@ enum option_codes {
 	OPT_DRY_RUN,
 	OPT_VERBOSE = 'v',	/* our only single-letter option */
 	OPT_DEBUG,
+	OPT_UDP_ENCAPS,
 };
 
 /* Specification of command line options for getopt_long(). */
@@ -89,6 +90,7 @@ struct option options[] = {
 	{ "dry_run",		.has_arg = false, NULL, OPT_DRY_RUN },
 	{ "verbose",		.has_arg = false, NULL, OPT_VERBOSE },
 	{ "debug",		.has_arg = false, NULL, OPT_DEBUG },
+	{ "udp_encapsulation",	.has_arg = true,  NULL, OPT_UDP_ENCAPS },
 	{ NULL },
 };
 
@@ -120,6 +122,7 @@ void show_usage(void)
 		"\t[--dry_run]\n"
 		"\t[--verbose|-v]\n"
 		"\t[--debug] * requires compilation with DEBUG *\n"
+		"\t[--udp_encapsulation=[sctp,tcp]]\n"
 		"\tscript_path ...\n");
 }
 
@@ -365,8 +368,7 @@ static void process_option(int opt, char *optarg, struct config *config,
 	char *end = NULL;
 	unsigned long speed = 0;
 
-	DEBUGP("process_option %d ('%c') = %s\n",
-	       opt, (char)opt, optarg);
+	DEBUGP("process_option %d = %s\n", opt, optarg);
 
 	switch (opt) {
 	case OPT_IP_VERSION:
@@ -475,6 +477,14 @@ static void process_option(int opt, char *optarg, struct config *config,
 #endif
 		debug_logging = true;
 		break;
+	case OPT_UDP_ENCAPS:
+		if (strcmp(optarg, "sctp") == 0)
+			config->udp_encaps = IPPROTO_SCTP;
+		else if (strcmp(optarg, "tcp") == 0)
+			config->udp_encaps = IPPROTO_TCP;
+		else
+			die("%s: bad --udp_encapsulation: %s\n", where, optarg);
+		break;
 	default:
 		show_usage();
 		exit(EXIT_FAILURE);
diff --git a/gtests/net/packetdrill/config.h b/gtests/net/packetdrill/config.h
index 55190ce1..d7898261 100644
--- a/gtests/net/packetdrill/config.h
+++ b/gtests/net/packetdrill/config.h
@@ -36,7 +36,7 @@
 #include "script.h"
 
 #define TUN_DRIVER_SPEED_CUR	0	/* don't change current speed */
-#define TUN_DRIVER_DEFAULT_MTU 1500	/* default MTU for tun device */
+#define TUN_DRIVER_DEFAULT_MTU	1500	/* default MTU for tun device */
 
 extern struct option options[];
 
@@ -81,6 +81,9 @@ struct config {
 	bool dry_run;			/* parse script but don't execute? */
 
 	bool verbose;			/* print detailed debug info? */
+
+	u8 udp_encaps;			/* Protocol encapsulated in UDP */
+
 	char *script_path;		/* pathname of script file */
 
 	/* Shell command to invoke via system(3) to run post-processing code */
diff --git a/gtests/net/packetdrill/icmp_packet.c b/gtests/net/packetdrill/icmp_packet.c
index 82533e3c..89d60eb3 100644
--- a/gtests/net/packetdrill/icmp_packet.c
+++ b/gtests/net/packetdrill/icmp_packet.c
@@ -291,6 +291,8 @@ struct packet *new_icmp_packet(int address_family,
 				u16 udplite_checksum_coverage,
 				u32 sctp_verification_tag,
 				s64 mtu,
+				u16 udp_src_port,
+				u16 udp_dst_port,
 				char **error)
 {
 	s32 type = -1;	/* bad type; means "unknown so far" */
@@ -303,10 +305,13 @@ struct packet *new_icmp_packet(int address_family,
 	 * header and the first 8 bytes after that (which will
 	 * typically have the port info needed to demux the message).
 	 */
+	const bool encapsulated = ((protocol != IPPROTO_UDP) &&
+				   ((udp_src_port > 0) || (udp_dst_port > 0)));
 	const int ip_fixed_bytes = ip_header_min_len(address_family);
 	const int ip_option_bytes = 0;
 	const int ip_header_bytes = ip_fixed_bytes + ip_option_bytes;
-	const int echoed_bytes = ip_fixed_bytes + ICMP_ECHO_BYTES;
+	const int echoed_bytes = ip_fixed_bytes + ICMP_ECHO_BYTES +
+				 (encapsulated ? sizeof(struct udp) : 0);
 	const int icmp_bytes = icmp_header_len(address_family) + echoed_bytes;
 	const int ip_bytes = ip_header_bytes + icmp_bytes;
 
@@ -355,16 +360,30 @@ struct packet *new_icmp_packet(int address_family,
 	 */
 	u8 *echoed_ip = packet_echoed_ip_header(packet);
 	const int echoed_ip_bytes = (ip_fixed_bytes +
+				     (encapsulated ? sizeof(struct udp) : 0) +
 				     layer4_header_len(protocol) +
 				     payload_bytes);
-	set_ip_header(echoed_ip, address_family, echoed_ip_bytes,
-		      ecn, protocol);
+	if (encapsulated) {
+		struct udp *udp;
+
+		set_ip_header(echoed_ip, address_family, echoed_ip_bytes,
+			      ecn, IPPROTO_UDP);
+		udp = (struct udp *)(echoed_ip + ip_fixed_bytes);
+		udp->src_port = htons(udp_src_port);
+		udp->dst_port = htons(udp_dst_port);
+		udp->len = htons(sizeof(struct udp) +
+				 layer4_header_len(protocol) +
+				 payload_bytes);
+	} else {
+		set_ip_header(echoed_ip, address_family, echoed_ip_bytes,
+			      ecn, protocol);
+	}
 	if (protocol == IPPROTO_SCTP) {
-		u32 *v_tag = packet_echoed_sctp_v_tag(packet);
+		u32 *v_tag = packet_echoed_sctp_v_tag(packet, encapsulated);
 		*v_tag = htonl(sctp_verification_tag);
 	}
 	if (protocol == IPPROTO_TCP) {
-		u32 *seq = packet_echoed_tcp_seq(packet);
+		u32 *seq = packet_echoed_tcp_seq(packet, encapsulated);
 		*seq = htonl(tcp_start_sequence);
 	}
 	if (protocol == IPPROTO_UDP) {
diff --git a/gtests/net/packetdrill/icmp_packet.h b/gtests/net/packetdrill/icmp_packet.h
index 609d3a66..6f20fc90 100644
--- a/gtests/net/packetdrill/icmp_packet.h
+++ b/gtests/net/packetdrill/icmp_packet.h
@@ -50,6 +50,8 @@ extern struct packet *new_icmp_packet(int address_family,
 				      u16 udplite_checksum_coverage,
 				      u32 sctp_verification_tag,
 				      s64 mtu,
+				      u16 udp_src_port,
+				      u16 udp_dst_port,
 				      char **error);
 
 #endif /* __ICMP_PACKET_H__ */
diff --git a/gtests/net/packetdrill/netdev.c b/gtests/net/packetdrill/netdev.c
index d4ac7c80..18bdefb8 100644
--- a/gtests/net/packetdrill/netdev.c
+++ b/gtests/net/packetdrill/netdev.c
@@ -405,10 +405,10 @@ static void local_netdev_read_queue(struct local_netdev *netdev,
 			else
 				die_perror("tun read()");
 		}
-       }
+	}
 }
 
-static int local_netdev_receive(struct netdev *a_netdev,
+static int local_netdev_receive(struct netdev *a_netdev, u8 udp_encaps,
 				struct packet **packet, char **error)
 {
 	struct local_netdev *netdev = to_local_netdev(a_netdev);
@@ -417,14 +417,15 @@ static int local_netdev_receive(struct netdev *a_netdev,
 
 	DEBUGP("local_netdev_receive\n");
 
-	status = netdev_receive_loop(netdev->psock, DIRECTION_OUTBOUND, packet,
-				     &num_packets, error);
+	status = netdev_receive_loop(netdev->psock, DIRECTION_OUTBOUND,
+				     udp_encaps,packet, &num_packets, error);
 	local_netdev_read_queue(netdev, num_packets);
 	return status;
 }
 
 int netdev_receive_loop(struct packet_socket *psock,
 			enum direction_t direction,
+			u8 udp_encaps,
 			struct packet **packet,
 			int *num_packets,
 			char **error)
@@ -446,7 +447,8 @@ int netdev_receive_loop(struct packet_socket *psock,
 			continue;
 
 		++*num_packets;
-		result = parse_packet(*packet, in_bytes, ether_type, error);
+		result = parse_packet(*packet, in_bytes, ether_type, udp_encaps,
+				      error);
 
 		if (result == PACKET_OK)
 			return STATUS_OK;
diff --git a/gtests/net/packetdrill/netdev.h b/gtests/net/packetdrill/netdev.h
index 115c3453..3e479678 100644
--- a/gtests/net/packetdrill/netdev.h
+++ b/gtests/net/packetdrill/netdev.h
@@ -52,7 +52,7 @@ struct netdev_ops {
 	 * pointer to the newly-allocated packet. Caller must free the packet
 	 * with packet_free().
 	 */
-	int (*receive)(struct netdev *netdev,
+	int (*receive)(struct netdev *netdev, u8 udp_encaps,
 		       struct packet **packet, char **error);
 };
 
@@ -75,10 +75,11 @@ static inline int netdev_send(struct netdev *netdev,
  * with packet_free().
  */
 static inline int netdev_receive(struct netdev *netdev,
+				 u8 udp_encaps,
 				 struct packet **packet,
 				 char **error)
 {
-	return netdev->ops->receive(netdev, packet, error);
+	return netdev->ops->receive(netdev, udp_encaps, packet, error);
 }
 
 
@@ -88,6 +89,7 @@ static inline int netdev_receive(struct netdev *netdev,
  */
 extern int netdev_receive_loop(struct packet_socket *psock,
 			       enum direction_t direction,
+			       u8 udp_encaps,
 			       struct packet **packet,
 			       int *num_packets,
 			       char **error);
diff --git a/gtests/net/packetdrill/packet.c b/gtests/net/packetdrill/packet.c
index 406e3465..ebaebd5e 100644
--- a/gtests/net/packetdrill/packet.c
+++ b/gtests/net/packetdrill/packet.c
@@ -33,6 +33,7 @@
 #include "ip_packet.h"
 #include "mpls_packet.h"
 #include "sctp_packet.h"
+#include "udp_packet.h"
 
 
 /* Info for all types of header we support. */
diff --git a/gtests/net/packetdrill/packet.h b/gtests/net/packetdrill/packet.h
index a7510b34..db09a339 100644
--- a/gtests/net/packetdrill/packet.h
+++ b/gtests/net/packetdrill/packet.h
@@ -216,14 +216,18 @@ static inline int ip_header_min_len(int address_family)
 }
 
 /* Return the layer4 protocol of the packet. */
-static inline int packet_ip_protocol(const struct packet *packet)
+static inline int packet_ip_protocol(const struct packet *packet, u8 udp_encaps)
 {
+	int protocol = 0;
+
+	assert(packet->ipv4 != NULL || packet->ipv6 != NULL);
 	if (packet->ipv4 != NULL)
-		return packet->ipv4->protocol;
+		protocol = packet->ipv4->protocol;
 	if (packet->ipv6 != NULL)
-		return packet->ipv6->next_header;
-	assert(!"no valid IP header");
-	return 0;
+		protocol = packet->ipv6->next_header;
+	if (protocol == IPPROTO_UDP && udp_encaps != 0)
+		protocol = udp_encaps;
+	return protocol;
 }
 
 /* Return the length of an optionless TCP or UDP header. */
@@ -381,27 +385,37 @@ static inline int packet_echoed_ip_protocol(struct packet *packet)
 }
 
 /* Return the location of the transport header echoed by an ICMP message. */
-static inline u8 *packet_echoed_layer4_header(struct packet *packet)
+static inline u8 *packet_echoed_layer4_header(struct packet *packet, bool encapsulated)
 {
 	u8 *echoed_ip = packet_echoed_ip_header(packet);
 	int ip_header_len = packet_echoed_ip_header_len(packet);
-	return echoed_ip + ip_header_len;
+	if (packet_echoed_ip_protocol(packet) == IPPROTO_UDP && encapsulated == true) {
+		return echoed_ip + ip_header_len + sizeof(struct udp);
+	} else {
+		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)
+packet_echoed_sctp_header(struct packet *packet, bool encapsulated)
 {
-	if (packet_echoed_ip_protocol(packet) == IPPROTO_SCTP)
+	int protocol;
+
+	protocol = packet_echoed_ip_protocol(packet);
+	if (protocol == IPPROTO_UDP && encapsulated == true) {
+		protocol = IPPROTO_SCTP;
+	}
+	if (protocol == IPPROTO_SCTP)
 		return (struct sctp_common_header *)
-		       (packet_echoed_layer4_header(packet));
+		       (packet_echoed_layer4_header(packet, encapsulated));
 	return NULL;
 }
 
 /* Return the location of the SCTP verification tag echoed by an ICMP message. */
-static inline u32 *packet_echoed_sctp_v_tag(struct packet *packet)
+static inline u32 *packet_echoed_sctp_v_tag(struct packet *packet, bool encapsulated)
 {
-	struct sctp_common_header *echoed_sctp = packet_echoed_sctp_header(packet);
+	struct sctp_common_header *echoed_sctp = packet_echoed_sctp_header(packet, encapsulated);
 	assert(echoed_sctp);
 	u32 *v_tag = &(echoed_sctp->v_tag);
 	/* Check that the v_tag field is actually in the space we
@@ -412,10 +426,16 @@ static inline u32 *packet_echoed_sctp_v_tag(struct packet *packet)
 }
 
 /* Return the location of the TCP header echoed by an ICMP message. */
-static inline struct tcp *packet_echoed_tcp_header(struct packet *packet)
+static inline struct tcp *packet_echoed_tcp_header(struct packet *packet, bool encapsulated)
 {
-	if (packet_echoed_ip_protocol(packet) == IPPROTO_TCP)
-		return (struct tcp *)(packet_echoed_layer4_header(packet));
+	int protocol;
+
+	protocol = packet_echoed_ip_protocol(packet);
+	if (protocol == IPPROTO_UDP && encapsulated == true) {
+		protocol = IPPROTO_TCP;
+	}
+	if (protocol == IPPROTO_TCP)
+		return (struct tcp *)(packet_echoed_layer4_header(packet, encapsulated));
 	return NULL;
 }
 
@@ -423,7 +443,7 @@ static inline struct tcp *packet_echoed_tcp_header(struct packet *packet)
 static inline struct udp *packet_echoed_udp_header(struct packet *packet)
 {
 	if (packet_echoed_ip_protocol(packet) == IPPROTO_UDP)
-		return (struct udp *)(packet_echoed_layer4_header(packet));
+		return (struct udp *)(packet_echoed_layer4_header(packet, 0));
 	return NULL;
 }
 
@@ -432,14 +452,14 @@ static inline struct
 udplite *packet_echoed_udplite_header(struct packet *packet)
 {
 	if (packet_echoed_ip_protocol(packet) == IPPROTO_UDPLITE)
-		return (struct udplite *)(packet_echoed_layer4_header(packet));
+		return (struct udplite *)(packet_echoed_layer4_header(packet, 0));
 	return NULL;
 }
 
 /* Return the location of the TCP sequence number echoed by an ICMP message. */
-static inline u32 *packet_echoed_tcp_seq(struct packet *packet)
+static inline u32 *packet_echoed_tcp_seq(struct packet *packet, bool encapsulated)
 {
-	struct tcp *echoed_tcp = packet_echoed_tcp_header(packet);
+	struct tcp *echoed_tcp = packet_echoed_tcp_header(packet, encapsulated);
 	assert(echoed_tcp);
 	u32 *seq = &(echoed_tcp->seq);
 	/* Check that the seq field is actually in the space we
diff --git a/gtests/net/packetdrill/packet_checksum.c b/gtests/net/packetdrill/packet_checksum.c
index a81a3d05..a5853e28 100644
--- a/gtests/net/packetdrill/packet_checksum.c
+++ b/gtests/net/packetdrill/packet_checksum.c
@@ -31,7 +31,7 @@
 #include "ipv6.h"
 #include "tcp.h"
 
-static void checksum_ipv4_packet(struct packet *packet)
+static void checksum_ipv4_packet(struct packet *packet, u8 udp_encaps)
 {
 	struct ipv4 *ipv4 = packet->ipv4;
 
@@ -47,19 +47,52 @@ static void checksum_ipv4_packet(struct packet *packet)
 	/* Fill in IPv4-based layer 4 checksum. */
 	if (packet->sctp != NULL) {
 		struct sctp_common_header *sctp = packet->sctp;
+		struct udp *udp;
+
 		sctp->crc32c = htonl(0);
-		sctp->crc32c = sctp_crc32c(sctp, l4_bytes);
+		if (udp_encaps == IPPROTO_SCTP) {
+			sctp->crc32c = sctp_crc32c(sctp, l4_bytes - sizeof(struct udp));
+		} else {
+			sctp->crc32c = sctp_crc32c(sctp, l4_bytes);
+		}
 		if (packet->flags & FLAGS_SCTP_BAD_CRC32C) {
 			sctp->crc32c = htonl(ntohl(sctp->crc32c) + 1);
 		}
+		if (udp_encaps == IPPROTO_SCTP) {
+			udp = ((struct udp *)sctp) - 1;
+			udp->check = 0;
+			udp->check = tcp_udp_v4_checksum(ipv4->src_ip,
+							 ipv4->dst_ip,
+							 IPPROTO_UDP, udp,
+							 l4_bytes);
+		}
 	} else if (packet->tcp != NULL) {
 		struct tcp *tcp = packet->tcp;
+		struct udp *udp;
+
 		tcp->check = 0;
-		tcp->check = tcp_udp_v4_checksum(ipv4->src_ip,
-						 ipv4->dst_ip,
-						 IPPROTO_TCP, tcp, l4_bytes);
+		if (udp_encaps == IPPROTO_TCP) {
+			tcp->check = tcp_udp_v4_checksum(ipv4->src_ip,
+							 ipv4->dst_ip,
+							 IPPROTO_TCP, tcp,
+							 l4_bytes - sizeof(struct udp));
+		} else {
+			tcp->check = tcp_udp_v4_checksum(ipv4->src_ip,
+							 ipv4->dst_ip,
+							 IPPROTO_TCP, tcp,
+							 l4_bytes);
+		}
+		if (udp_encaps == IPPROTO_TCP) {
+			udp = ((struct udp *)tcp) - 1;
+			udp->check = 0;
+			udp->check = tcp_udp_v4_checksum(ipv4->src_ip,
+							 ipv4->dst_ip,
+							 IPPROTO_UDP, udp,
+							 l4_bytes);
+		}
 	} else if (packet->udp != NULL) {
 		struct udp *udp = packet->udp;
+
 		udp->check = 0;
 		udp->check = tcp_udp_v4_checksum(ipv4->src_ip,
 						 ipv4->dst_ip,
@@ -79,6 +112,7 @@ static void checksum_ipv4_packet(struct packet *packet)
 						     l4_bytes, coverage);
 	} else if (packet->icmpv4 != NULL) {
 		struct icmpv4 *icmpv4 = packet->icmpv4;
+
 		icmpv4->checksum = 0;
 		icmpv4->checksum = ipv4_checksum(icmpv4, l4_bytes);
 	} else {
@@ -86,7 +120,7 @@ static void checksum_ipv4_packet(struct packet *packet)
 	}
 }
 
-static void checksum_ipv6_packet(struct packet *packet)
+static void checksum_ipv6_packet(struct packet *packet, u8 udp_encaps)
 {
 	struct ipv6 *ipv6 = packet->ipv6;
 
@@ -101,19 +135,52 @@ static void checksum_ipv6_packet(struct packet *packet)
 	/* Fill in IPv6-based layer 4 checksum. */
 	if (packet->sctp != NULL) {
 		struct sctp_common_header *sctp = packet->sctp;
+		struct udp *udp;
+
 		sctp->crc32c = htonl(0);
-		sctp->crc32c = sctp_crc32c(sctp, l4_bytes);
+		if (udp_encaps == IPPROTO_SCTP) {
+			sctp->crc32c = sctp_crc32c(sctp, l4_bytes - sizeof(struct udp));
+		} else {
+			sctp->crc32c = sctp_crc32c(sctp, l4_bytes);
+		}
 		if (packet->flags & FLAGS_SCTP_BAD_CRC32C) {
 			sctp->crc32c = htonl(ntohl(sctp->crc32c) + 1);
 		}
+		if (udp_encaps == IPPROTO_SCTP) {
+			udp = ((struct udp *)sctp) - 1;
+			udp->check = 0;
+			udp->check = tcp_udp_v6_checksum(&ipv6->src_ip,
+							 &ipv6->dst_ip,
+							 IPPROTO_UDP, udp,
+							 l4_bytes);
+		}
 	} else if (packet->tcp != NULL) {
 		struct tcp *tcp = packet->tcp;
+		struct udp *udp;
+
 		tcp->check = 0;
-		tcp->check = tcp_udp_v6_checksum(&ipv6->src_ip,
-						 &ipv6->dst_ip,
-						 IPPROTO_TCP, tcp, l4_bytes);
+		if (udp_encaps == IPPROTO_TCP) {
+			tcp->check = tcp_udp_v6_checksum(&ipv6->src_ip,
+							 &ipv6->dst_ip,
+							 IPPROTO_TCP, tcp,
+							 l4_bytes - sizeof(struct udp));
+		} else {
+			tcp->check = tcp_udp_v6_checksum(&ipv6->src_ip,
+							 &ipv6->dst_ip,
+							 IPPROTO_TCP, tcp,
+							 l4_bytes);
+		}
+		if (udp_encaps == IPPROTO_TCP) {
+			udp = ((struct udp *)tcp) - 1;
+			udp->check = 0;
+			udp->check = tcp_udp_v6_checksum(&ipv6->src_ip,
+							 &ipv6->dst_ip,
+							 IPPROTO_UDP, udp,
+							 l4_bytes);
+		}
 	} else if (packet->udp != NULL) {
 		struct udp *udp = packet->udp;
+
 		udp->check = 0;
 		udp->check = tcp_udp_v6_checksum(&ipv6->src_ip,
 						 &ipv6->dst_ip,
@@ -144,13 +211,13 @@ static void checksum_ipv6_packet(struct packet *packet)
 	}
 }
 
-void checksum_packet(struct packet *packet)
+void checksum_packet(struct packet *packet, u8 udp_encaps)
 {
 	int address_family = packet_address_family(packet);
 	if (address_family == AF_INET)
-		return checksum_ipv4_packet(packet);
+		return checksum_ipv4_packet(packet, udp_encaps);
 	else if (address_family == AF_INET6)
-		return checksum_ipv6_packet(packet);
+		return checksum_ipv6_packet(packet, udp_encaps);
 	else
 		assert(!"bad ip version");
 }
diff --git a/gtests/net/packetdrill/packet_checksum.h b/gtests/net/packetdrill/packet_checksum.h
index 2c87df3e..2eee17c3 100644
--- a/gtests/net/packetdrill/packet_checksum.h
+++ b/gtests/net/packetdrill/packet_checksum.h
@@ -28,6 +28,6 @@
 #include "packet.h"
 
 /* Fill in layer 3 and layer 4 checksums for the given input 'packet'. */
-extern void checksum_packet(struct packet *packet);
+extern void checksum_packet(struct packet *packet, u8 udp_encaps);
 
 #endif /* __PACKET_CHECKSUM_H__ */
diff --git a/gtests/net/packetdrill/packet_parser.c b/gtests/net/packetdrill/packet_parser.c
index 03209545..008b01d7 100644
--- a/gtests/net/packetdrill/packet_parser.c
+++ b/gtests/net/packetdrill/packet_parser.c
@@ -44,22 +44,24 @@
 #include "packet.h"
 #include "tcp.h"
 
-static int parse_ipv4(struct packet *packet, u8 *header_start, u8 *packet_end,
-		      char **error);
-static int parse_ipv6(struct packet *packet, u8 *header_start, u8 *packet_end,
-		      char **error);
-static int parse_mpls(struct packet *packet, u8 *header_start, u8 *packet_end,
-		      char **error);
+static int parse_ipv4(struct packet *packet, u8 udp_encaps,
+		      u8 *header_start, u8 *packet_end, char **error);
+static int parse_ipv6(struct packet *packet, u8 udp_encaps,
+		      u8 *header_start, u8 *packet_end, char **error);
+static int parse_mpls(struct packet *packet, u8 udp_encaps,
+		      u8 *header_start, u8 *packet_end, char **error);
 static int parse_layer3_packet_by_proto(struct packet *packet,
-					u16 proto, u8 *header_start,
-					u8 *packet_end, char **error);
-static int parse_layer4(struct packet *packet, u8 *header_start,
+					u16 proto, u8 udp_encaps,
+					u8 *header_start, u8 *packet_end,
+					char **error);
+static int parse_layer4(struct packet *packet, u8 udp_encaps, u8 *header_start,
 			int layer4_protocol, int layer4_bytes,
 			u8 *packet_end, bool *is_inner, char **error);
 
 static int parse_layer3_packet_by_proto(struct packet *packet,
-					u16 proto, u8 *header_start,
-					u8 *packet_end, char **error)
+					u16 proto, u8 udp_encaps,
+					u8 *header_start, u8 *packet_end,
+					char **error)
 {
 	u8 *p = header_start;
 
@@ -77,7 +79,8 @@ static int parse_layer3_packet_by_proto(struct packet *packet,
 		 */
 		ip = (struct ipv4 *)p;
 		if (ip->version == 4) {
-			return parse_ipv4(packet, p, packet_end, error);
+			return parse_ipv4(packet, udp_encaps, p, packet_end,
+					  error);
 		} else {
 			asprintf(error, "Bad IP version for ETHERTYPE_IP");
 			goto error_out;
@@ -96,14 +99,15 @@ static int parse_layer3_packet_by_proto(struct packet *packet,
 		 */
 		ip = (struct ipv6 *)p;
 		if (ip->version == 6) {
-			return parse_ipv6(packet, p, packet_end, error);
+			return parse_ipv6(packet, udp_encaps, p, packet_end,
+					  error);
 		} else {
 			asprintf(error, "Bad IP version for ETHERTYPE_IPV6");
 			goto error_out;
 		}
 	} else if ((proto == ETHERTYPE_MPLS_UC) ||
 		   (proto == ETHERTYPE_MPLS_MC)) {
-		return parse_mpls(packet, p, packet_end, error);
+		return parse_mpls(packet, udp_encaps, p, packet_end, error);
 	} else {
 		return PACKET_UNKNOWN_L4;
 	}
@@ -112,7 +116,7 @@ error_out:
 	return PACKET_BAD;
 }
 
-static int parse_layer3_packet(struct packet *packet,
+static int parse_layer3_packet(struct packet *packet, u8 udp_encaps,
 			       u8 *header_start, u8 *packet_end,
 			       char **error)
 {
@@ -131,16 +135,16 @@ static int parse_layer3_packet(struct packet *packet,
 	 */
 	ip = (struct ipv4 *) (p);
 	if (ip->version == 4)
-		return parse_ipv4(packet, p, packet_end, error);
+		return parse_ipv4(packet, udp_encaps, p, packet_end, error);
 	else if (ip->version == 6)
-		return parse_ipv6(packet, p, packet_end, error);
+		return parse_ipv6(packet, udp_encaps, p, packet_end, error);
 
 	asprintf(error, "Unsupported IP version");
 	return PACKET_BAD;
 }
 
 int parse_packet(struct packet *packet, int in_bytes,
-		 u16 ether_type, char **error)
+		 u16 ether_type, u8 udp_encaps, char **error)
 {
 	assert(in_bytes <= packet->buffer_bytes);
 	char *message = NULL;		/* human-readable error summary */
@@ -150,7 +154,7 @@ int parse_packet(struct packet *packet, int in_bytes,
 	/* packet_end points to the byte beyond the end of packet. */
 	u8 *packet_end = packet->buffer + in_bytes;
 
-	result = parse_layer3_packet_by_proto(packet, ether_type,
+	result = parse_layer3_packet_by_proto(packet, ether_type, udp_encaps,
 					      header_start, packet_end, error);
 
 	if (result != PACKET_BAD)
@@ -170,8 +174,8 @@ int parse_packet(struct packet *packet, int in_bytes,
  * packet_parse_result_t.
  * Note that packet_end points to the byte beyond the end of packet.
  */
-static int parse_ipv4(struct packet *packet, u8 *header_start, u8 *packet_end,
-		      char **error)
+static int parse_ipv4(struct packet *packet, u8 udp_encaps,
+		      u8 *header_start, u8 *packet_end, char **error)
 {
 	struct header *ip_header = NULL;
 	u8 *p = header_start;
@@ -247,8 +251,8 @@ static int parse_ipv4(struct packet *packet, u8 *header_start, u8 *packet_end,
 	/* Examine the L4 header. */
 	const int layer4_bytes = ip_total_bytes - ip_header_bytes;
 	const int layer4_protocol = ipv4->protocol;
-	result = parse_layer4(packet, p, layer4_protocol, layer4_bytes,
-			      packet_end, &is_inner, error);
+	result = parse_layer4(packet, udp_encaps, p, layer4_protocol,
+			      layer4_bytes, packet_end, &is_inner, error);
 
 	/* If this is the innermost IP header then this is the primary. */
 	if (is_inner)
@@ -268,8 +272,8 @@ error_out:
  * protocol other than TCP. Return a packet_parse_result_t.
  * Note that packet_end points to the byte beyond the end of packet.
  */
-static int parse_ipv6(struct packet *packet, u8 *header_start, u8 *packet_end,
-		      char **error)
+static int parse_ipv6(struct packet *packet, u8 udp_encaps,
+		      u8 *header_start, u8 *packet_end, char **error)
 {
 	struct header *ip_header = NULL;
 	u8 *p = header_start;
@@ -321,8 +325,8 @@ static int parse_ipv6(struct packet *packet, u8 *header_start, u8 *packet_end,
 	/* Examine the L4 header. */
 	const int layer4_bytes = ip_total_bytes - ip_header_bytes;
 	const int layer4_protocol = ipv6->next_header;
-	result = parse_layer4(packet, p, layer4_protocol, layer4_bytes,
-			      packet_end, &is_inner, error);
+	result = parse_layer4(packet, udp_encaps, p, layer4_protocol,
+			      layer4_bytes, packet_end, &is_inner, error);
 
 	/* If this is the innermost IP header then this is the primary. */
 	if (is_inner)
@@ -422,19 +426,21 @@ error_out:
 }
 
 /* Parse the UDP header. Return a packet_parse_result_t. */
-static int parse_udp(struct packet *packet, u8 *layer4_start, int layer4_bytes,
-		     u8 *packet_end, char **error)
+static int parse_udp(struct packet *packet, u8 udp_encaps,
+		     u8 *layer4_start, int layer4_bytes, u8 *packet_end,
+		     char **error)
 {
 	struct header *udp_header = NULL;
 	u8 *p = layer4_start;
+	struct udp *udp;
 
 	assert(layer4_bytes >= 0);
 	if (layer4_bytes < sizeof(struct udp)) {
 		asprintf(error, "Truncated UDP header");
 		goto error_out;
 	}
-	packet->udp = (struct udp *) p;
-	const int udp_len = ntohs(packet->udp->len);
+	udp = (struct udp *) p;
+	const int udp_len = ntohs(udp->len);
 	const int udp_header_len = sizeof(struct udp);
 	if (udp_len < udp_header_len) {
 		asprintf(error, "UDP datagram length too small for UDP header");
@@ -456,12 +462,23 @@ static int parse_udp(struct packet *packet, u8 *layer4_start, int layer4_bytes,
 	}
 	udp_header->total_bytes = layer4_bytes;
 
-	p += layer4_bytes;
-	assert(p <= packet_end);
-
-	DEBUGP("UDP src port: %d\n", ntohs(packet->udp->src_port));
-	DEBUGP("UDP dst port: %d\n", ntohs(packet->udp->dst_port));
-	return PACKET_OK;
+	DEBUGP("UDP src port: %d\n", ntohs(udp->src_port));
+	DEBUGP("UDP dst port: %d\n", ntohs(udp->dst_port));
+	if (udp_encaps == IPPROTO_SCTP)
+		return parse_sctp(packet, p + udp_header_len,
+				  layer4_bytes - udp_header_len,
+				  packet_end, error);
+	else if (udp_encaps == IPPROTO_TCP)
+		return parse_tcp(packet, p + udp_header_len,
+				 layer4_bytes - udp_header_len,
+				  packet_end, error);
+	else {
+		assert(udp_encaps == 0);
+		packet->udp = udp;
+		p += layer4_bytes;
+		assert(p <= packet_end);
+		return PACKET_OK;
+	}
 
 error_out:
 	return PACKET_BAD;
@@ -585,8 +602,9 @@ error_out:
 }
 
 /* Parse the GRE header. Return a packet_parse_result_t. */
-static int parse_gre(struct packet *packet, u8 *layer4_start, int layer4_bytes,
-		     u8 *packet_end, char **error)
+static int parse_gre(struct packet *packet, u8 udp_encaps,
+		     u8 *layer4_start, int layer4_bytes, u8 *packet_end,
+		     char **error)
 {
 	struct header *gre_header = NULL;
 	u8 *p = layer4_start;
@@ -629,14 +647,14 @@ static int parse_gre(struct packet *packet, u8 *layer4_start, int layer4_bytes,
 	p += gre_header_len;
 	assert(p <= packet_end);
 	return parse_layer3_packet_by_proto(packet, ntohs(gre->protocol),
-					    p, packet_end, error);
+					    udp_encaps, p, packet_end, error);
 
 error_out:
 	return PACKET_BAD;
 }
 
-static int parse_mpls(struct packet *packet, u8 *header_start, u8 *packet_end,
-		      char **error)
+static int parse_mpls(struct packet *packet, u8 udp_encaps,
+		      u8 *header_start, u8 *packet_end, char **error)
 {
 	struct header *mpls_header = NULL;
 	u8 *p = header_start;
@@ -670,13 +688,13 @@ static int parse_mpls(struct packet *packet, u8 *header_start, u8 *packet_end,
 
 	/* Move on to the header inside the MPLS label stack. */
 	assert(p <= packet_end);
-	return parse_layer3_packet(packet, p, packet_end, error);
+	return parse_layer3_packet(packet, udp_encaps, p, packet_end, error);
 
 error_out:
 	return PACKET_BAD;
 }
 
-static int parse_layer4(struct packet *packet, u8 *layer4_start,
+static int parse_layer4(struct packet *packet, u8 udp_encaps, u8 *layer4_start,
 			int layer4_protocol, int layer4_bytes,
 			u8 *packet_end, bool *is_inner, char **error)
 {
@@ -690,8 +708,8 @@ static int parse_layer4(struct packet *packet, u8 *layer4_start,
 				 error);
 	} else if (layer4_protocol == IPPROTO_UDP) {
 		*is_inner = true;	/* found inner-most layer 4 */
-		return parse_udp(packet, layer4_start, layer4_bytes, packet_end,
-				 error);
+		return parse_udp(packet, udp_encaps, layer4_start, layer4_bytes,
+				 packet_end, error);
 	} else if (layer4_protocol == IPPROTO_UDPLITE) {
 		*is_inner = true;	/* found inner-most layer 4 */
 		return parse_udplite(packet, layer4_start, layer4_bytes,
@@ -706,14 +724,16 @@ static int parse_layer4(struct packet *packet, u8 *layer4_start,
 				    packet_end, error);
 	} else if (layer4_protocol == IPPROTO_GRE) {
 		*is_inner = false;
-		return parse_gre(packet, layer4_start, layer4_bytes, packet_end,
-				 error);
+		return parse_gre(packet, udp_encaps, layer4_start, layer4_bytes,
+				 packet_end, error);
 	} else if (layer4_protocol == IPPROTO_IPIP) {
 		*is_inner = false;
-		return parse_ipv4(packet, layer4_start, packet_end, error);
+		return parse_ipv4(packet, udp_encaps, layer4_start, packet_end,
+				  error);
 	} else if (layer4_protocol == IPPROTO_IPV6) {
 		*is_inner = false;
-		return parse_ipv6(packet, layer4_start, packet_end, error);
+		return parse_ipv6(packet, udp_encaps, layer4_start, packet_end,
+				  error);
 	}
 	return PACKET_UNKNOWN_L4;
 }
diff --git a/gtests/net/packetdrill/packet_parser.h b/gtests/net/packetdrill/packet_parser.h
index 07805553..3051d224 100644
--- a/gtests/net/packetdrill/packet_parser.h
+++ b/gtests/net/packetdrill/packet_parser.h
@@ -30,7 +30,7 @@
 enum packet_parse_result_t {
 	PACKET_OK,		/* no errors detected */
 	PACKET_BAD,		/* illegal header */
-	PACKET_UNKNOWN_L4,	/* not TCP or UDP or UDPLite */
+	PACKET_UNKNOWN_L4,	/* not SCTP or TCP or UDP or UDPLite */
 };
 
 /* Given an input packet of length 'in_bytes' stored in the buffer
@@ -42,6 +42,6 @@ enum packet_parse_result_t {
  * error message.
  */
 int parse_packet(struct packet *packet, int in_bytes,
-		 u16 ether_type, char **error);
+		 u16 ether_type, u8 udp_encaps, char **error);
 
 #endif /* __PACKET_PARSER_H__ */
diff --git a/gtests/net/packetdrill/packet_parser_test.c b/gtests/net/packetdrill/packet_parser_test.c
index 07ef9592..5b216f07 100644
--- a/gtests/net/packetdrill/packet_parser_test.c
+++ b/gtests/net/packetdrill/packet_parser_test.c
@@ -29,7 +29,7 @@
 #include <stdlib.h>
 #include <string.h>
 
-int debug_logging=0;
+int debug_logging = 0;
 
 static void test_parse_sctp_ipv4_packet(void)
 {
@@ -50,7 +50,7 @@ static void test_parse_sctp_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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -96,7 +96,7 @@ static void test_parse_sctp_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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -121,6 +121,105 @@ static void test_parse_sctp_ipv6_packet(void)
 	packet_free(packet);
 }
 
+static void test_parse_sctp_udp_ipv4_packet(void)
+{
+	/* A SCTP/UDP/IPv4 packet. */
+	u8 data[] = {
+		/* 1.1.1.1:1234 > 192.168.0.1:60213
+		 * udp(9899 > 9899): sctp: ABORT[] */
+		0x45, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00,
+		0xff, 0x11, 0xf9, 0x15, 0x01, 0x01, 0x01, 0x01,
+		0xc0, 0xa8, 0x00, 0x01, 0x26, 0xab, 0x26, 0xab,
+		0x00, 0x18, 0x00, 0x00, 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, IPPROTO_SCTP, &error);
+	assert(result == PACKET_OK);
+	assert(error == NULL);
+
+	struct ipv4 *expected_ipv4 = (struct ipv4 *)(packet->buffer);
+	struct udp *expected_udp =
+		(struct udp *)(expected_ipv4 + 1);
+	struct sctp_common_header *expected_sctp =
+		(struct sctp_common_header *)(expected_udp + 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_udp_ipv6_packet(void)
+{
+	/* A SCTP/UDP/IPv6 packet. */
+	u8 data[] = {
+		/* 2001:db8::1:54242 > fd3d:fa7b:d17d::1:8080
+		 * udp(9899->9899): sctp: ABORT[] */
+		0x60, 0x00, 0x00, 0x00, 0x00, 0x18, 0x11, 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,
+		0x26, 0xab, 0x26, 0xab, 0x00, 0x18, 0x00, 0x00,
+		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, IPPROTO_SCTP, &error);
+	assert(result == PACKET_OK);
+	assert(error == NULL);
+
+	struct ipv6 *expected_ipv6 = (struct ipv6 *)(packet->buffer);
+	struct udp *expected_udp =
+		(struct udp *)(expected_ipv6 + 1);
+	struct sctp_common_header *expected_sctp =
+		(struct sctp_common_header *)(expected_udp + 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. */
@@ -145,7 +244,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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -193,7 +292,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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -234,7 +333,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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -278,7 +377,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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -320,7 +419,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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -366,7 +465,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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -418,7 +517,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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -510,7 +609,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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -588,7 +687,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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -642,7 +741,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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -670,6 +769,8 @@ int main(void)
 {
 	test_parse_sctp_ipv4_packet();
 	test_parse_sctp_ipv6_packet();
+	test_parse_sctp_udp_ipv4_packet();
+	test_parse_sctp_udp_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 9b1dd53e..c74fb542 100644
--- a/gtests/net/packetdrill/packet_to_string.c
+++ b/gtests/net/packetdrill/packet_to_string.c
@@ -120,7 +120,7 @@ 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,
+static int sctp_packet_to_string(FILE *s, struct packet *packet, int i,
 				 enum dump_format_t format, char **error)
 {
 	struct sctp_chunks_iterator iter;
@@ -138,6 +138,13 @@ static int sctp_packet_to_string(FILE *s, struct packet *packet,
 	if (packet->flags & FLAGS_SCTP_BAD_CRC32C) {
 		fputs("(bad_crc32c)", s);
 	}
+
+	if (packet->headers[i + 1].type == HEADER_UDP) {
+		struct udp *udp = packet->headers[i + 1].h.udp;
+
+		fprintf(s, "/udp(%u > %u)",
+			ntohs(udp->src_port), ntohs(udp->dst_port));
+	}
 	fputc(':', s);
 
 	index = 0;
@@ -167,7 +174,7 @@ static int sctp_packet_to_string(FILE *s, struct packet *packet,
 /* Print a string representation of the TCP packet:
  *  direction opt_ip_info flags seq ack window tcp_options
  */
-static int tcp_packet_to_string(FILE *s, struct packet *packet,
+static int tcp_packet_to_string(FILE *s, struct packet *packet, int i,
 				enum dump_format_t format, char **error)
 {
 	int result = STATUS_OK;       /* return value */
@@ -177,7 +184,6 @@ static int tcp_packet_to_string(FILE *s, struct packet *packet,
 		fputc(' ', s);
 	}
 
-
 	/* We print flags in the same order as tcpdump 4.1.1. */
 	if (packet->tcp->fin)
 		fputc('F', s);
@@ -216,6 +222,13 @@ static int tcp_packet_to_string(FILE *s, struct packet *packet,
 		free(tcp_options);
 	}
 
+	if (packet->headers[i + 1].type == HEADER_UDP) {
+		struct udp *udp = packet->headers[i + 1].h.udp;
+
+		fprintf(s, "/udp(%u > %u)",
+			ntohs(udp->src_port), ntohs(udp->dst_port));
+	}
+
 	if (format == DUMP_VERBOSE)
 		packet_buffer_to_string(s, packet);
 
@@ -297,7 +310,7 @@ 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,
+int packet_to_string(struct packet *packet, u8 udp_encaps,
 		     enum dump_format_t format,
 		     char **ascii_string, char **error)
 {
@@ -307,9 +320,14 @@ int packet_to_string(struct packet *packet,
 	FILE *s = open_memstream(ascii_string, &size);  /* output string */
 	int i;
 	int header_count = packet_header_count(packet);
+	int limit;
 
 	/* Print any encapsulation headers preceding layer 3 and 4 headers. */
-	for (i = 0; i < header_count - 2; ++i) {
+	if (udp_encaps == 0)
+		limit = header_count - 2;
+	else
+		limit = header_count - 3;
+	for (i = 0; i < limit; ++i) {
 		if (packet->headers[i].type == HEADER_NONE)
 			break;
 		if (encap_header_to_string(s, packet, i, format, error))
@@ -320,10 +338,12 @@ int packet_to_string(struct packet *packet,
 		fputs("[NO IP HEADER]", s);
 	} else {
 		if (packet->sctp != NULL) {
-			if (sctp_packet_to_string(s, packet, format, error))
+			if (sctp_packet_to_string(s, packet, limit, format,
+						  error))
 				goto out;
 		} else if (packet->tcp != NULL) {
-			if (tcp_packet_to_string(s, packet, format, error))
+			if (tcp_packet_to_string(s, packet, limit, format,
+						 error))
 				goto out;
 		} else if (packet->udp != NULL) {
 			if (udp_packet_to_string(s, packet, format, error))
diff --git a/gtests/net/packetdrill/packet_to_string.h b/gtests/net/packetdrill/packet_to_string.h
index 462a4f9e..3a4fb40d 100644
--- a/gtests/net/packetdrill/packet_to_string.h
+++ b/gtests/net/packetdrill/packet_to_string.h
@@ -37,7 +37,7 @@ enum dump_format_t {
  * packet 'packet'. Returns STATUS_OK on success; on failure returns
  * STATUS_ERR and sets error message.
  */
-extern int packet_to_string(struct packet *packet,
+extern int packet_to_string(struct packet *packet, u8 udp_encaps,
 			    enum dump_format_t format,
 			    char **ascii_string, char **error);
 
diff --git a/gtests/net/packetdrill/packet_to_string_test.c b/gtests/net/packetdrill/packet_to_string_test.c
index 560aa24d..2ad71b44 100644
--- a/gtests/net/packetdrill/packet_to_string_test.c
+++ b/gtests/net/packetdrill/packet_to_string_test.c
@@ -31,7 +31,8 @@
 #include "packet_parser.h"
 #include "logging.h"
 
-int debug_logging=0;
+int debug_logging = 0;
+#define DEBUG_LOGGING 1
 
 static void test_sctp_ipv4_packet_to_string(void)
 {
@@ -54,7 +55,12 @@ static void test_sctp_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, 0, &error);
+#if DEBUG_LOGGING == 1
+	if (result != PACKET_OK) {
+		printf("error was: %s\n", error);
+	}
+#endif
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -62,7 +68,7 @@ static void test_sctp_ipv4_packet_to_string(void)
 	char *dump = NULL, *expected = NULL;
 
 	/* Test a DUMP_SHORT dump */
-	status = packet_to_string(packet, DUMP_SHORT, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_SHORT, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -72,7 +78,7 @@ static void test_sctp_ipv4_packet_to_string(void)
 	free(dump);
 
 	/* Test a DUMP_FULL dump */
-	status = packet_to_string(packet, DUMP_FULL, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_FULL, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -83,7 +89,7 @@ static void test_sctp_ipv4_packet_to_string(void)
 	free(dump);
 
 	/* Test a DUMP_VERBOSE dump */
-	status = packet_to_string(packet, DUMP_VERBOSE, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_VERBOSE, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -276,7 +282,7 @@ static void test_sctp_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, 0, &error);
 #if DEBUG_LOGGING == 1
 	if (result != PACKET_OK) {
 		printf("error was: %s\n", error);
@@ -289,7 +295,7 @@ static void test_sctp_ipv6_packet_to_string(void)
 	char *dump = NULL, *expected = NULL;
 
 	/* Test a DUMP_SHORT dump */
-	status = packet_to_string(packet, DUMP_SHORT, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_SHORT, &dump, &error);
 #if DEBUG_LOGGING == 1
 	if (status != STATUS_OK) {
 		printf("error was: %s\n", error);
@@ -356,7 +362,7 @@ static void test_sctp_ipv6_packet_to_string(void)
 	free(dump);
 
 	/* Test a DUMP_FULL dump */
-	status = packet_to_string(packet, DUMP_FULL, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_FULL, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -419,7 +425,7 @@ static void test_sctp_ipv6_packet_to_string(void)
 	free(dump);
 
 	/* Test a DUMP_VERBOSE dump */
-	status = packet_to_string(packet, DUMP_VERBOSE, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_VERBOSE, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -523,6 +529,366 @@ static void test_sctp_ipv6_packet_to_string(void)
 	packet_free(packet);
 }
 
+static void test_sctp_udp_ipv4_packet_to_string(void)
+{
+	/* An IPv4/UDP/SCTP packet. */
+	u8 data[] = {
+		/* IPv4: */
+		0x45, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00,
+		0xff, 0x11, 0xb5, 0xbb, 0x02, 0x02, 0x02, 0x02,
+		0x01, 0x01, 0x01, 0x01,
+		/* UDP Header */
+		0x26, 0xab, 0x26, 0xab, 0x00, 0x18, 0x00, 0x00,
+		/* 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, IPPROTO_SCTP,
+			     &error);
+#if DEBUG_LOGGING == 1
+	if (result != PACKET_OK) {
+		printf("error was: %s\n", error);
+	}
+#endif
+	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, IPPROTO_SCTP, DUMP_SHORT, &dump,
+				  &error);
+	assert(status == STATUS_OK);
+	assert(error == NULL);
+	printf("dump = '%s'\n", dump);
+	expected =
+		"sctp/udp(9899 > 9899): ABORT[flgs=T]";
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+
+	/* Test a DUMP_FULL dump */
+	status = packet_to_string(packet, IPPROTO_SCTP, 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/udp(9899 > 9899): ABORT[flgs=T]";
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+
+	/* Test a DUMP_VERBOSE dump */
+	status = packet_to_string(packet, IPPROTO_SCTP, 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/udp(9899 > 9899): ABORT[flgs=T]"
+		"\n"
+		"0x0000: 45 00 00 2c 00 00 00 00 ff 11 b5 bb 02 02 02 02 " "\n"
+		"0x0010: 01 01 01 01 26 ab 26 ab 00 18 00 00 04 d2 1f 90 " "\n"
+		"0x0020: 01 02 03 04 3d 99 bf e3 06 01 00 04 " "\n";
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+
+	packet_free(packet);
+}
+
+static void test_sctp_udp_ipv6_packet_to_string(void)
+{
+	/* An IPv6/UDP/SCTP packet. */
+	u8 data[] = {
+		/* IPv6 Base Header: */
+		0x60, 0x00, 0x00, 0x00, 0x00, 0x18, 0x11, 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,
+		/* UDP Header */
+		0x26, 0xab, 0x26, 0xab, 0x00, 0x18, 0x00, 0x00,
+		/* 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_IPV6, IPPROTO_SCTP,
+			     &error);
+#if DEBUG_LOGGING == 1
+	if (result != PACKET_OK) {
+		printf("error was: %s\n", error);
+	}
+#endif
+	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, IPPROTO_SCTP, DUMP_SHORT, &dump,
+				  &error);
+#if DEBUG_LOGGING == 1
+	if (status != STATUS_OK) {
+		printf("error was: %s\n", error);
+	}
+#endif
+	assert(status == STATUS_OK);
+	assert(error == NULL);
+	printf("dump = '%s'\n", dump);
+	expected =
+		"sctp/udp(9899 > 9899): ABORT[flgs=T]";
+
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+
+	/* Test a DUMP_FULL dump */
+	status = packet_to_string(packet, IPPROTO_SCTP, DUMP_FULL, &dump,
+				  &error);
+	assert(status == STATUS_OK);
+	assert(error == NULL);
+	printf("dump = '%s'\n", dump);
+	expected =
+		"2::2222:1234 > 1::1111:8080 "
+		"sctp/udp(9899 > 9899): ABORT[flgs=T]";
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+
+	/* Test a DUMP_VERBOSE dump */
+	status = packet_to_string(packet, IPPROTO_SCTP, DUMP_VERBOSE, &dump,
+				  &error);
+	assert(status == STATUS_OK);
+	assert(error == NULL);
+	printf("dump = '%s'\n", dump);
+	expected =
+		"2::2222:1234 > 1::1111:8080 "
+		"sctp/udp(9899 > 9899): ABORT[flgs=T]"
+		"\n"
+		"0x0000: 60 00 00 00 00 18 11 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 26 ab 26 ab 00 18 00 00 " "\n"
+		"0x0030: 04 d2 1f 90 01 02 03 04 3d 99 bf e3 06 01 00 04 " "\n";
+
+	printf("expected = '%s'\n", expected);
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+	packet_free(packet);
+}
+
+static void test_tcp_udp_ipv4_packet_to_string(void)
+{
+	/* An IPv4/GRE/IPv4/UDP/TCP packet. */
+	u8 data[] = {
+		/* IPv4: */
+		0x45, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x00,
+		0xff, 0x2f, 0xb5, 0x6d, 0x02, 0x02, 0x02, 0x02,
+		0x01, 0x01, 0x01, 0x01,
+		/* GRE: */
+		0x00, 0x00, 0x08, 0x00,
+		/* IPv4, UDP, TCP: */
+		0x45, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00,
+		0xff, 0x11, 0x38, 0xfe, 0xc0, 0x00, 0x02, 0x01,
+		0xc0, 0xa8, 0x00, 0x01, 0x26, 0xab, 0x26, 0xab,
+		0x00, 0x30, 0x00, 0x00, 0xcf, 0x3f, 0x1f, 0x90,
+		0x00, 0x00, 0x00, 0x01, 0x83, 0x4d, 0xa5, 0x5b,
+		0xa0, 0x10, 0x01, 0x01, 0xdb, 0x2d, 0x00, 0x00,
+		0x05, 0x0a, 0x83, 0x4d, 0xab, 0x03, 0x83, 0x4d,
+		0xb0, 0xab, 0x08, 0x0a, 0x00, 0x00, 0x01, 0x2c,
+		0x60, 0xc2, 0x18, 0x20
+	};
+
+	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, IPPROTO_TCP,
+			     &error);
+#if DEBUG_LOGGING == 1
+	if (result != STATUS_OK) {
+		printf("error was: %s\n", error);
+	}
+#endif
+	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, IPPROTO_TCP, DUMP_SHORT, &dump,
+				  &error);
+	assert(status == STATUS_OK);
+	assert(error == NULL);
+	printf("dump = '%s'\n", dump);
+	expected =
+		"ipv4 2.2.2.2 > 1.1.1.1: gre: "
+		". 1:1(0) ack 2202903899 win 257 "
+		"<sack 2202905347:2202906795,TS val 300 ecr 1623332896>"
+		"/udp(9899 > 9899)";
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+
+	/* Test a DUMP_FULL dump */
+	status = packet_to_string(packet, IPPROTO_TCP, DUMP_FULL, &dump,
+				  &error);
+	assert(status == STATUS_OK);
+	assert(error == NULL);
+	printf("dump = '%s'\n", dump);
+	expected =
+		"ipv4 2.2.2.2 > 1.1.1.1: gre: "
+		"192.0.2.1:53055 > 192.168.0.1:8080 "
+		". 1:1(0) ack 2202903899 win 257 "
+		"<sack 2202905347:2202906795,TS val 300 ecr 1623332896>"
+		"/udp(9899 > 9899)";
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+
+	/* Test a DUMP_VERBOSE dump */
+	status = packet_to_string(packet, IPPROTO_TCP, DUMP_VERBOSE, &dump,
+				  &error);
+	assert(status == STATUS_OK);
+	assert(error == NULL);
+	printf("dump = '%s'\n", dump);
+	expected =
+		"ipv4 2.2.2.2 > 1.1.1.1: gre: "
+		"192.0.2.1:53055 > 192.168.0.1:8080 "
+		". 1:1(0) ack 2202903899 win 257 "
+		"<sack 2202905347:2202906795,TS val 300 ecr 1623332896>"
+		"/udp(9899 > 9899)"
+		"\n"
+		"0x0000: 45 00 00 5c 00 00 00 00 ff 2f b5 6d 02 02 02 02 " "\n"
+		"0x0010: 01 01 01 01 00 00 08 00 45 00 00 44 00 00 00 00 " "\n"
+		"0x0020: ff 11 38 fe c0 00 02 01 c0 a8 00 01 26 ab 26 ab " "\n"
+		"0x0030: 00 30 00 00 cf 3f 1f 90 00 00 00 01 83 4d a5 5b " "\n"
+		"0x0040: a0 10 01 01 db 2d 00 00 05 0a 83 4d ab 03 83 4d " "\n"
+		"0x0050: b0 ab 08 0a 00 00 01 2c 60 c2 18 20 " "\n";
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+
+	packet_free(packet);
+}
+
+static void test_tcp_udp_ipv6_packet_to_string(void)
+{
+	/* An IPv6/GRE/IPv6/UDP/TCP packet. */
+	u8 data[] = {
+		/* IPv6: */
+		0x60, 0x00, 0x00, 0x00, 0x00, 0x54, 0x2f, 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,
+		/* GRE: */
+		0x00, 0x00, 0x86, 0xdd,
+		/* IPv6, UDP, TCP: */
+		0x60, 0x00, 0x00, 0x00, 0x00, 0x28, 0x11, 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,
+		0x26, 0xab, 0x26, 0xab, 0x00, 0x28, 0x00, 0x00,
+		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
+	};
+
+	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, IPPROTO_TCP,
+			     &error);
+#if DEBUG_LOGGING == 1
+	if (result != STATUS_OK) {
+		printf("error was: %s\n", error);
+	}
+#endif
+	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, IPPROTO_TCP, DUMP_SHORT, &dump,
+				  &error);
+	assert(status == STATUS_OK);
+	assert(error == NULL);
+	printf("dump = '%s'\n", dump);
+	expected =
+		"ipv6 2::2222 > 1::1111: gre: "
+		"S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>"
+		"/udp(9899 > 9899)";
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+
+	/* Test a DUMP_FULL dump */
+	status = packet_to_string(packet, IPPROTO_TCP, DUMP_FULL, &dump,
+				  &error);
+	assert(status == STATUS_OK);
+	assert(error == NULL);
+	printf("dump = '%s'\n", dump);
+	expected =
+		"ipv6 2::2222 > 1::1111: gre: "
+		"2001:db8::1:54242 > fd3d:fa7b:d17d::1:8080 "
+		"S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>"
+		"/udp(9899 > 9899)";
+	assert(strcmp(dump, expected) == 0);
+	free(dump);
+
+	/* Test a DUMP_VERBOSE dump */
+	status = packet_to_string(packet, IPPROTO_TCP, DUMP_VERBOSE, &dump,
+				  &error);
+	assert(status == STATUS_OK);
+	assert(error == NULL);
+	printf("dump = '%s'\n", dump);
+	expected =
+		"ipv6 2::2222 > 1::1111: gre: "
+		"2001:db8::1:54242 > fd3d:fa7b:d17d::1:8080 "
+		"S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>"
+		"/udp(9899 > 9899)"
+		"\n"
+		"0x0000: 60 00 00 00 00 54 2f 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 00 00 86 dd 60 00 00 00 " "\n"
+		"0x0030: 00 28 11 ff 20 01 0d b8 00 00 00 00 00 00 00 00 " "\n"
+		"0x0040: 00 00 00 01 fd 3d fa 7b d1 7d 00 00 00 00 00 00 " "\n"
+		"0x0050: 00 00 00 01 26 ab 26 ab 00 28 00 00 d3 e2 1f 90 " "\n"
+		"0x0060: 00 00 00 00 00 00 00 00 80 02 80 18 06 60 00 00 " "\n"
+		"0x0070: 02 04 03 e8 04 02 01 01 01 03 03 07 " "\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. */
@@ -550,7 +916,7 @@ static void test_tcp_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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -558,7 +924,7 @@ static void test_tcp_ipv4_packet_to_string(void)
 	char *dump = NULL, *expected = NULL;
 
 	/* Test a DUMP_SHORT dump */
-	status = packet_to_string(packet, DUMP_SHORT, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_SHORT, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -570,7 +936,7 @@ static void test_tcp_ipv4_packet_to_string(void)
 	free(dump);
 
 	/* Test a DUMP_FULL dump */
-	status = packet_to_string(packet, DUMP_FULL, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_FULL, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -583,7 +949,7 @@ static void test_tcp_ipv4_packet_to_string(void)
 	free(dump);
 
 	/* Test a DUMP_VERBOSE dump */
-	status = packet_to_string(packet, DUMP_VERBOSE, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_VERBOSE, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -635,7 +1001,7 @@ static void test_tcp_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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -643,7 +1009,7 @@ static void test_tcp_ipv6_packet_to_string(void)
 	char *dump = NULL, *expected = NULL;
 
 	/* Test a DUMP_SHORT dump */
-	status = packet_to_string(packet, DUMP_SHORT, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_SHORT, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -654,7 +1020,7 @@ static void test_tcp_ipv6_packet_to_string(void)
 	free(dump);
 
 	/* Test a DUMP_FULL dump */
-	status = packet_to_string(packet, DUMP_FULL, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_FULL, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -666,7 +1032,7 @@ static void test_tcp_ipv6_packet_to_string(void)
 	free(dump);
 
 	/* Test a DUMP_VERBOSE dump */
-	status = packet_to_string(packet, DUMP_VERBOSE, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_VERBOSE, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -716,7 +1082,7 @@ static void test_gre_mpls_tcp_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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -724,7 +1090,7 @@ static void test_gre_mpls_tcp_ipv4_packet_to_string(void)
 	char *dump = NULL, *expected = NULL;
 
 	/* Test a DUMP_FULL dump */
-	status = packet_to_string(packet, DUMP_FULL, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_FULL, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -764,7 +1130,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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -772,7 +1138,7 @@ static void test_udplite_ipv4_packet_to_string(void)
 	char *dump = NULL, *expected = NULL;
 
 	/* Test a DUMP_SHORT dump */
-	status = packet_to_string(packet, DUMP_SHORT, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_SHORT, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -783,7 +1149,7 @@ static void test_udplite_ipv4_packet_to_string(void)
 	free(dump);
 
 	/* Test a DUMP_FULL dump */
-	status = packet_to_string(packet, DUMP_FULL, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_FULL, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -795,7 +1161,7 @@ static void test_udplite_ipv4_packet_to_string(void)
 	free(dump);
 
 	/* Test a DUMP_VERBOSE dump */
-	status = packet_to_string(packet, DUMP_VERBOSE, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_VERBOSE, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -842,7 +1208,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, 0, &error);
 	assert(result == PACKET_OK);
 	assert(error == NULL);
 
@@ -850,7 +1216,7 @@ static void test_udplite_ipv6_packet_to_string(void)
 	char *dump = NULL, *expected = NULL;
 
 	/* Test a DUMP_SHORT dump */
-	status = packet_to_string(packet, DUMP_SHORT, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_SHORT, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -861,7 +1227,7 @@ static void test_udplite_ipv6_packet_to_string(void)
 	free(dump);
 
 	/* Test a DUMP_FULL dump */
-	status = packet_to_string(packet, DUMP_FULL, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_FULL, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -873,7 +1239,7 @@ static void test_udplite_ipv6_packet_to_string(void)
 	free(dump);
 
 	/* Test a DUMP_VERBOSE dump */
-	status = packet_to_string(packet, DUMP_VERBOSE, &dump, &error);
+	status = packet_to_string(packet, 0, DUMP_VERBOSE, &dump, &error);
 	assert(status == STATUS_OK);
 	assert(error == NULL);
 	printf("dump = '%s'\n", dump);
@@ -895,10 +1261,15 @@ static void test_udplite_ipv6_packet_to_string(void)
 
 int main(void)
 {
+	test_tcp_udp_ipv4_packet_to_string();
 	test_sctp_ipv4_packet_to_string();
 	test_sctp_ipv6_packet_to_string();
+	test_sctp_udp_ipv4_packet_to_string();
+	test_sctp_udp_ipv6_packet_to_string();
 	test_tcp_ipv4_packet_to_string();
 	test_tcp_ipv6_packet_to_string();
+	test_tcp_udp_ipv4_packet_to_string();
+	test_tcp_udp_ipv6_packet_to_string();
 	test_gre_mpls_tcp_ipv4_packet_to_string();
 	test_udplite_ipv4_packet_to_string();
 	test_udplite_ipv6_packet_to_string();
diff --git a/gtests/net/packetdrill/parser.y b/gtests/net/packetdrill/parser.y
index ffa9d175..399656cd 100644
--- a/gtests/net/packetdrill/parser.y
+++ b/gtests/net/packetdrill/parser.y
@@ -467,7 +467,17 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 		u32 verification_tag;	/* used for SCTP */
 		u32 start_sequence;	/* used for TCP */
 		u16 checksum_coverage;	/* used for UDPLite */
+		u16 udp_src_port;
+		u16 udp_dst_port;
 	} transport_info;
+	struct {
+		u16 udp_src_port;
+		u16 udp_dst_port;
+	} udp_encaps_info;
+	struct {
+		bool bad_crc32c;
+		s64 tag;
+	} sctp_header_spec;
 	struct option_list *option;
 	struct event *event;
 	struct packet *packet;
@@ -629,7 +639,10 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %type <expression> decimal_integer hex_integer data
 %type <expression> inaddr sockaddr msghdr cmsghdr cmsg_level cmsg_type cmsg_data
 %type <expression> sf_hdtr iovec pollfd opt_revents
-%type <expression> linger l_onoff l_linger sctp_assoc_id
+%type <expression> linger l_onoff l_linger
+%type <udp_encaps_info> opt_udp_encaps_info
+%type <sctp_header_spec> sctp_header_spec
+%type <expression> sctp_assoc_id
 %type <expression> sctp_status sstat_state sstat_rwnd sstat_unackdata sstat_penddata
 %type <expression> sstat_instrms sstat_outstrms sstat_fragmentation_point sstat_primary
 %type <expression> sctp_initmsg sinit_num_ostreams sinit_max_instreams sinit_max_attempts
@@ -910,113 +923,58 @@ packet_spec
 | icmp_packet_spec    { $$ = $1; }
 ;
 
-sctp_packet_spec
-: packet_prefix opt_ip_info SCTP ':' sctp_chunk_list_spec {
-	char *error = NULL;
-	struct packet *outer = $1, *inner = NULL;
-	enum direction_t direction = outer->direction;
-
-	inner = new_sctp_packet(in_config->wire_protocol, direction, $2,
-	                        -1, false, $5, &error);
-	if (inner == NULL) {
-		assert(error != NULL);
-		semantic_error(error);
-		free(error);
-	}
-
-	$$ = packet_encapsulate_and_free(outer, inner);
-}
-| packet_prefix opt_ip_info SCTP '(' BAD_CRC32C ')' ':' sctp_chunk_list_spec {
-	char *error = NULL;
-	struct packet *outer = $1, *inner = NULL;
-	enum direction_t direction = outer->direction;
-
-	inner = new_sctp_packet(in_config->wire_protocol, direction, $2,
-	                        -1, true, $8, &error);
-	if (inner == NULL) {
-		assert(error != NULL);
-		semantic_error(error);
-		free(error);
-	}
-
-	$$ = packet_encapsulate_and_free(outer, inner);
+opt_udp_encaps_info
+:	{
+	$$.udp_src_port = 0;
+	$$.udp_dst_port = 0;
 }
-| packet_prefix opt_ip_info SCTP '(' TAG '=' INTEGER ')' ':' sctp_chunk_list_spec {
-	char *error = NULL;
-	struct packet *outer = $1, *inner = NULL;
-	enum direction_t direction = outer->direction;
-
-	if (!is_valid_u32($7)) {
-		semantic_error("tag value out of range");
+| '/' UDP '(' INTEGER '>' INTEGER ')' {
+	if (!is_valid_u16($4)) {
+		semantic_error("UDP source port out of range");
 	}
-	inner = new_sctp_packet(in_config->wire_protocol, direction, $2,
-	                        $7, false, $10, &error);
-	if (inner == NULL) {
-		assert(error != NULL);
-		semantic_error(error);
-		free(error);
+	if (!is_valid_u16($6)) {
+		semantic_error("UDP destination port out of range");
 	}
-
-	$$ = packet_encapsulate_and_free(outer, inner);
+	$$.udp_src_port = $4;
+	$$.udp_dst_port = $6;
 }
-| packet_prefix opt_ip_info SCTP '(' BAD_CRC32C ',' TAG '=' INTEGER ')' ':' sctp_chunk_list_spec {
-	char *error = NULL;
-	struct packet *outer = $1, *inner = NULL;
-	enum direction_t direction = outer->direction;
+;
 
-	if (!is_valid_u32($9)) {
+sctp_header_spec
+: SCTP {
+	$$.bad_crc32c = false;
+	$$.tag = -1;
+}
+| SCTP '(' BAD_CRC32C ')' {
+	$$.bad_crc32c = true;
+	$$.tag = -1;
+}
+| SCTP '(' TAG '=' INTEGER ')' {
+	if (!is_valid_u32($5)) {
 		semantic_error("tag value out of range");
 	}
-	inner = new_sctp_packet(in_config->wire_protocol, direction, $2,
-	                        $9, true, $12, &error);
-	if (inner == NULL) {
-		assert(error != NULL);
-		semantic_error(error);
-		free(error);
-	}
-
-	$$ = packet_encapsulate_and_free(outer, inner);
+	$$.bad_crc32c = false;
+	$$.tag = $5;
 }
-| packet_prefix opt_ip_info SCTP ':' '[' byte_list ']' {
-	char *error = NULL;
-	struct packet *outer = $1, *inner = NULL;
-	enum direction_t direction = outer->direction;
-
-	inner = new_sctp_generic_packet(in_config->wire_protocol, direction, $2,
-	                                -1, false, $6, &error);
-	if (inner == NULL) {
-		assert(error != NULL);
-		semantic_error(error);
-		free(error);
+| SCTP '(' BAD_CRC32C ',' TAG '=' INTEGER ')' {
+	if (!is_valid_u32($7)) {
+		semantic_error("tag value out of range");
 	}
-
-	$$ = packet_encapsulate_and_free(outer, inner);
+	$$.bad_crc32c = true;
+	$$.tag = $7;
 }
-| packet_prefix opt_ip_info SCTP '(' BAD_CRC32C ')' ':'  '[' byte_list ']' {
-	char *error = NULL;
-	struct packet *outer = $1, *inner = NULL;
-	enum direction_t direction = outer->direction;
-
-	inner = new_sctp_generic_packet(in_config->wire_protocol, direction, $2,
-	                                -1, true, $9, &error);
-	if (inner == NULL) {
-		assert(error != NULL);
-		semantic_error(error);
-		free(error);
-	}
+;
 
-	$$ = packet_encapsulate_and_free(outer, inner);
-}
-| packet_prefix opt_ip_info SCTP '(' TAG '=' INTEGER ')' ':' '[' byte_list ']' {
+sctp_packet_spec
+: packet_prefix opt_ip_info sctp_header_spec opt_udp_encaps_info ':' sctp_chunk_list_spec {
 	char *error = NULL;
 	struct packet *outer = $1, *inner = NULL;
 	enum direction_t direction = outer->direction;
 
-	if (!is_valid_u32($7)) {
-		semantic_error("tag value out of range");
-	}
-	inner = new_sctp_generic_packet(in_config->wire_protocol, direction, $2,
-	                                $7, false, $11, &error);
+	inner = new_sctp_packet(in_config->wire_protocol, direction, $2,
+	                        $3.tag, $3.bad_crc32c, $6,
+	                        $4.udp_src_port, $4.udp_dst_port,
+	                        &error);
 	if (inner == NULL) {
 		assert(error != NULL);
 		semantic_error(error);
@@ -1025,16 +983,15 @@ sctp_packet_spec
 
 	$$ = packet_encapsulate_and_free(outer, inner);
 }
-| packet_prefix opt_ip_info SCTP '(' BAD_CRC32C ',' TAG '=' INTEGER ')' ':' '[' byte_list ']' {
+| packet_prefix opt_ip_info sctp_header_spec opt_udp_encaps_info ':' '[' byte_list ']' {
 	char *error = NULL;
 	struct packet *outer = $1, *inner = NULL;
 	enum direction_t direction = outer->direction;
 
-	if (!is_valid_u32($9)) {
-		semantic_error("tag value out of range");
-	}
 	inner = new_sctp_generic_packet(in_config->wire_protocol, direction, $2,
-	                                $9, true, $13, &error);
+	                                $3.tag, $3.bad_crc32c, $7,
+	                                $4.udp_src_port, $4.udp_dst_port,
+	                                &error);
 	if (inner == NULL) {
 		assert(error != NULL);
 		semantic_error(error);
@@ -2362,7 +2319,7 @@ sctp_protocol_violation_cause_spec
 }
 
 tcp_packet_spec
-: packet_prefix opt_ip_info flags seq opt_ack opt_window opt_tcp_options {
+: packet_prefix opt_ip_info flags seq opt_ack opt_window opt_tcp_options opt_udp_encaps_info{
 	char *error = NULL;
 	struct packet *outer = $1, *inner = NULL;
 	enum direction_t direction = outer->direction;
@@ -2378,6 +2335,7 @@ tcp_packet_spec
 			       $4.start_sequence, $4.payload_bytes,
 			       $5, $6, $7,
 			       absolute_ts_ecr,
+			       $8.udp_src_port, $8.udp_dst_port,
 			       &error);
 	absolute_ts_ecr = false;
 	free($3);
@@ -2447,7 +2405,8 @@ icmp_packet_spec
 	inner = new_icmp_packet(in_config->wire_protocol, direction, $4, $5,
 				$2.protocol, $2.payload_bytes,
 				$2.start_sequence, $2.checksum_coverage,
-				$2.verification_tag, $6, &error);
+				$2.verification_tag, $6,
+				$2.udp_src_port, $2.udp_dst_port, &error);
 	free($4);
 	free($5);
 	if (inner == NULL) {
@@ -2559,26 +2518,36 @@ opt_icmp_echoed
 	$$.protocol		= IPPROTO_TCP;
 	$$.payload_bytes	= 0;
 	$$.start_sequence	= 0;
+	$$.udp_src_port		= 0;
+	$$.udp_dst_port		= 0;
 }
-| '[' SCTP '(' INTEGER ')' ']'	{
+| '[' SCTP '(' INTEGER ')' opt_udp_encaps_info ']'	{
 	$$.protocol		= IPPROTO_SCTP;
 	$$.payload_bytes	= 0;
 	$$.verification_tag	= $4;
+	$$.udp_src_port		= $6.udp_src_port;
+	$$.udp_dst_port		= $6.udp_dst_port;
 }
 | '[' UDP '(' INTEGER ')' ']'	{
 	$$.protocol		= IPPROTO_UDP;
 	$$.payload_bytes	= $4;
 	$$.start_sequence	= 0;
+	$$.udp_src_port		= 0;
+	$$.udp_dst_port		= 0;
 }
 | '[' UDPLITE '(' INTEGER ',' INTEGER ')' ']'	{
 	$$.protocol		= IPPROTO_UDPLITE;
 	$$.payload_bytes	= $4;
 	$$.checksum_coverage	= $6;
+	$$.udp_src_port		= 0;
+	$$.udp_dst_port		= 0;
 }
-| '[' seq ']'		{
+| '[' seq opt_udp_encaps_info ']'	{
 	$$.protocol		= IPPROTO_TCP;
 	$$.payload_bytes	= $2.payload_bytes;
 	$$.start_sequence	= $2.start_sequence;
+	$$.udp_src_port		= $3.udp_src_port;
+	$$.udp_dst_port		= $3.udp_dst_port;
 }
 ;
 
diff --git a/gtests/net/packetdrill/run_packet.c b/gtests/net/packetdrill/run_packet.c
index 07ebe30a..a984ce09 100644
--- a/gtests/net/packetdrill/run_packet.c
+++ b/gtests/net/packetdrill/run_packet.c
@@ -101,7 +101,7 @@ static u16 next_ephemeral_port(struct state *state)
  * *error followed by the given type and a hex dump of the given
  * packet.
  */
-static void add_packet_dump(char **error, const char *type,
+static void add_packet_dump(struct state *state, char **error, const char *type,
 			    struct packet *packet, s64 time_usecs,
 			    enum dump_format_t format)
 {
@@ -109,7 +109,7 @@ static void add_packet_dump(char **error, const char *type,
 		char *old_error = *error;
 		char *dump = NULL, *dump_error = NULL;
 
-		packet_to_string(packet, format,
+		packet_to_string(packet, state->config->udp_encaps, format,
 				 &dump, &dump_error);
 		asprintf(error, "%s\n%s packet: %9.6f %s%s%s",
 			 old_error, type, usecs_to_secs(time_usecs), dump,
@@ -129,8 +129,8 @@ static void verbose_packet_dump(struct state *state, const char *type,
 	if (state->config->verbose) {
 		char *dump = NULL, *dump_error = NULL;
 
-		packet_to_string(live_packet, DUMP_SHORT,
-				 &dump, &dump_error);
+		packet_to_string(live_packet, state->config->udp_encaps,
+				 DUMP_SHORT, &dump, &dump_error);
 
 		printf("%s packet: %9.6f %s%s%s\n",
 		       type, usecs_to_secs(time_usecs), dump,
@@ -189,7 +189,7 @@ static struct socket *setup_new_child_socket(struct state *state, const struct p
 	assert(socket->state == SOCKET_INIT);
 	socket->state = SOCKET_PASSIVE_PACKET_RECEIVED;
 	socket->address_family = packet_address_family(packet);
-	socket->protocol = packet_ip_protocol(packet);
+	socket->protocol = packet_ip_protocol(packet, config->udp_encaps);
 
 	/* Set script info for this socket using script packet. */
 	struct tuple tuple;
@@ -413,7 +413,7 @@ static struct socket *handle_connect_for_script_packet(
 		state->socket_under_test = socket;
 		assert(socket->state == SOCKET_INIT);
 		socket->address_family = packet_address_family(packet);
-		socket->protocol = packet_ip_protocol(packet);
+		socket->protocol = packet_ip_protocol(packet, config->udp_encaps);
 
 		socket->script.fd	 = -1;
 
@@ -587,9 +587,12 @@ static int offset_sack_blocks(struct packet *packet,
 }
 
 static int map_inbound_icmp_sctp_packet(
-	struct socket *socket, struct packet *live_packet, char **error)
+	struct socket *socket,
+	struct packet *live_packet,
+	bool encapsulated,
+	char **error)
 {
-	u32 *v_tag = packet_echoed_sctp_v_tag(live_packet);
+	u32 *v_tag = packet_echoed_sctp_v_tag(live_packet, encapsulated);
 	*v_tag = htonl(socket->live.remote_initiate_tag);
 	return STATUS_OK;
 }
@@ -598,9 +601,12 @@ static int map_inbound_icmp_sctp_packet(
  * The Linux TCP layer ignores ICMP messages with bogus sequence numbers.
  */
 static int map_inbound_icmp_tcp_packet(
-	struct socket *socket, struct packet *live_packet, char **error)
+	struct socket *socket,
+	struct packet *live_packet,
+	bool encapsulated,
+	char **error)
 {
-	u32 *seq = packet_echoed_tcp_seq(live_packet);
+	u32 *seq = packet_echoed_tcp_seq(live_packet, encapsulated);
 	/* FIXME: There is currently no way to access the TCP flags */
 	bool is_syn = false;
 	u32 seq_offset = local_seq_script_to_live_offset(socket, is_syn);
@@ -623,12 +629,15 @@ static int map_inbound_icmp_udplite_packet(
 }
 
 static int map_inbound_icmp_packet(
-	struct socket *socket, struct packet *live_packet, char **error)
+	struct socket *socket,
+	struct packet *live_packet,
+	bool encapsulated,
+	char **error)
 {
 	if (packet_echoed_ip_protocol(live_packet) == IPPROTO_SCTP)
-		return map_inbound_icmp_sctp_packet(socket, live_packet, error);
+		return map_inbound_icmp_sctp_packet(socket, live_packet, encapsulated, error);
 	else if (packet_echoed_ip_protocol(live_packet) == IPPROTO_TCP)
-		return map_inbound_icmp_tcp_packet(socket, live_packet, error);
+		return map_inbound_icmp_tcp_packet(socket, live_packet, encapsulated, error);
 	else if (packet_echoed_ip_protocol(live_packet) == IPPROTO_UDP)
 		return map_inbound_icmp_udp_packet(socket, live_packet, error);
 	else if (packet_echoed_ip_protocol(live_packet) == IPPROTO_UDPLITE)
@@ -899,17 +908,18 @@ static int map_inbound_sctp_packet(
  * on failure returns STATUS_ERR and sets error message.
  */
 static int map_inbound_packet(
-	struct socket *socket, struct packet *live_packet, char **error)
+	struct socket *socket, struct packet *live_packet, u8 udp_encaps,
+	char **error)
 {
 	DEBUGP("map_inbound_packet\n");
 
 	/* Remap packet to live values. */
 	struct tuple live_inbound;
 	socket_get_inbound(&socket->live, &live_inbound);
-	set_packet_tuple(live_packet, &live_inbound);
+	set_packet_tuple(live_packet, &live_inbound, udp_encaps != 0);
 
 	if ((live_packet->icmpv4 != NULL) || (live_packet->icmpv6 != NULL))
-		return map_inbound_icmp_packet(socket, live_packet, error);
+		return map_inbound_icmp_packet(socket, live_packet, udp_encaps != 0, error);
 
 	if (live_packet->sctp) {
 		return map_inbound_sctp_packet(socket, live_packet, error);
@@ -1143,6 +1153,7 @@ static int map_outbound_live_packet(
 	struct packet *live_packet,
 	struct packet *actual_packet,
 	struct packet *script_packet,
+	u8 udp_encaps,
 	char **error)
 {
 	DEBUGP("map_outbound_live_packet\n");
@@ -1156,7 +1167,7 @@ static int map_outbound_live_packet(
 
 	/* Rewrite 4-tuple to be outbound script values. */
 	socket_get_outbound(&socket->script, &script_outbound);
-	set_packet_tuple(actual_packet, &script_outbound);
+	set_packet_tuple(actual_packet, &script_outbound, udp_encaps != 0);
 
 	if (live_packet->sctp) {
 		return map_outbound_live_sctp_packet(socket, live_packet, actual_packet, script_packet, error);
@@ -1312,7 +1323,7 @@ static int tcp_options_allowance(const struct packet *actual_packet,
 static int verify_ipv4(
 	const struct packet *actual_packet,
 	const struct packet *script_packet,
-	int layer, char **error)
+	int layer, u8 udp_encaps, char **error)
 {
 	const struct ipv4 *actual_ipv4 = actual_packet->headers[layer].h.ipv4;
 	const struct ipv4 *script_ipv4 = script_packet->headers[layer].h.ipv4;
@@ -1339,6 +1350,18 @@ static int verify_ipv4(
 				ntohs(actual_ipv4->tot_len), error))
 			return STATUS_ERR;
 		break;
+	case IPPROTO_UDP:
+		if (udp_encaps == IPPROTO_TCP) {
+			if (check_field("ipv4_total_length",
+					(ntohs(script_ipv4->tot_len) +
+					 tcp_options_allowance(actual_packet,
+							       script_packet)),
+					ntohs(actual_ipv4->tot_len), error))
+				return STATUS_ERR;
+			break;
+		} else if (udp_encaps == IPPROTO_SCTP) {
+			break;
+		}
 	default:
 		if (check_field("ipv4_total_length",
 				ntohs(script_ipv4->tot_len),
@@ -1360,7 +1383,7 @@ static int verify_ipv4(
 static int verify_ipv6(
 	const struct packet *actual_packet,
 	const struct packet *script_packet,
-	int layer, char **error)
+	int layer, u8 udp_encaps, char **error)
 {
 	const struct ipv6 *actual_ipv6 = actual_packet->headers[layer].h.ipv6;
 	const struct ipv6 *script_ipv6 = script_packet->headers[layer].h.ipv6;
@@ -1384,6 +1407,18 @@ static int verify_ipv6(
 				ntohs(actual_ipv6->payload_len), error))
 			return STATUS_ERR;
 		break;
+	case IPPROTO_UDP:
+		if (udp_encaps != 0) {
+			if (check_field("ipv6_payload_len",
+					(ntohs(script_ipv6->payload_len) +
+					 tcp_options_allowance(actual_packet,
+							       script_packet)),
+					ntohs(actual_ipv6->payload_len), error))
+				return STATUS_ERR;
+			break;
+		} else if (udp_encaps == IPPROTO_SCTP) {
+			break;
+		}
 	default:
 		if (check_field("ipv6_payload_len",
 				ntohs(script_ipv6->payload_len),
@@ -1456,7 +1491,7 @@ static int verify_sctp_parameters(u8 *begin, u16 length,
 			    (flags & FLAG_RECONFIG_RESP_SN_NOCHECK ? STATUS_OK :
 			    check_field("outgoing_ssn_reset_request_parameter.resp_sn",
 			                ntohl(script_reset->respsn),
-		                        ntohl(live_reset->respsn),
+			                ntohl(live_reset->respsn),
 			                error)) ||
 			    (flags & FLAG_RECONFIG_LAST_TSN_NOCHECK ? STATUS_OK :
 			    check_field("outgoing_ssn_reset_request_parameter.last_tsn",
@@ -1472,7 +1507,7 @@ static int verify_sctp_parameters(u8 *begin, u16 length,
 						    ntohs(live_reset->sids[i]),
 						    error)) {
 					return STATUS_ERR;
-                        	}
+				}
 			}
 			break;
 		}
@@ -1496,7 +1531,7 @@ static int verify_sctp_parameters(u8 *begin, u16 length,
 						    ntohs(live_reset->sids[i]),
 						    error)) {
 					return STATUS_ERR;
-                        	}
+				}
 			}
 			break;
 		}
@@ -2286,7 +2321,7 @@ static int verify_i_forward_tsn_chunk(struct sctp_i_forward_tsn_chunk *actual_ch
 static int verify_sctp(
 	const struct packet *actual_packet,
 	const struct packet *script_packet,
-	int layer, char **error)
+	int layer, u8 udp_encaps, char **error)
 {
 	struct sctp_chunks_iterator iter;
 	struct sctp_chunk *actual_chunk;
@@ -2469,11 +2504,23 @@ static int verify_sctp(
 static int verify_tcp(
 	const struct packet *actual_packet,
 	const struct packet *script_packet,
-	int layer, char **error)
+	int layer, u8 udp_encaps, char **error)
 {
 	const struct tcp *actual_tcp = actual_packet->headers[layer].h.tcp;
 	const struct tcp *script_tcp = script_packet->headers[layer].h.tcp;
-
+	const struct udp *actual_udp =  (struct udp *)actual_tcp - 1;
+	const struct udp *script_udp =  (struct udp *)script_tcp - 1;
+
+	if (udp_encaps != 0) {
+		if (check_field("udp_src_port",
+				ntohs(script_udp->src_port),
+				ntohs(actual_udp->src_port), error) ||
+		    check_field("udp_dst_port",
+				ntohs(script_udp->dst_port),
+				ntohs(actual_udp->dst_port), error)) {
+			return STATUS_ERR;
+		}
+	}
 	if (check_field("tcp_data_offset",
 			(script_tcp->doff +
 			 tcp_options_allowance(actual_packet,
@@ -2528,11 +2575,14 @@ static int verify_tcp(
 static int verify_udp(
 	const struct packet *actual_packet,
 	const struct packet *script_packet,
-	int layer, char **error)
+	int layer, u8 udp_encaps, char **error)
 {
 	const struct udp *actual_udp = actual_packet->headers[layer].h.udp;
 	const struct udp *script_udp = script_packet->headers[layer].h.udp;
 
+	if (udp_encaps != 0) {
+		return STATUS_OK;
+	}
 	if (check_field("udp_len",
 			ntohs(script_udp->len),
 			ntohs(actual_udp->len), error))
@@ -2545,7 +2595,7 @@ static int verify_udp(
 static int verify_udplite(
 	const struct packet *actual_packet,
 	const struct packet *script_packet,
-	int layer, char **error)
+	int layer, u8 udp_encaps, char **error)
 {
 	const struct udplite *actual_udplite =
 	    actual_packet->headers[layer].h.udplite;
@@ -2562,7 +2612,7 @@ static int verify_udplite(
 static int verify_gre(
 	const struct packet *actual_packet,
 	const struct packet *script_packet,
-	int layer, char **error)
+	int layer, u8 udp_encaps, char **error)
 {
 	const struct gre *actual_gre = actual_packet->headers[layer].h.gre;
 	const struct gre *script_gre = script_packet->headers[layer].h.gre;
@@ -2579,7 +2629,7 @@ static int verify_gre(
 static int verify_mpls(
 	const struct packet *actual_packet,
 	const struct packet *script_packet,
-	int layer, char **error)
+	int layer, u8 udp_encaps, char **error)
 {
 	const struct header *actual_header = &actual_packet->headers[layer];
 	const struct header *script_header = &script_packet->headers[layer];
@@ -2608,13 +2658,13 @@ static int verify_mpls(
 typedef int (*verifier_func)(
 	const struct packet *actual_packet,
 	const struct packet *script_packet,
-	int layer, char **error);
+	int layer, u8 udp_encaps, char **error);
 
 /* Verify that required actual header fields are as the script expected. */
 static int verify_header(
 	const struct packet *actual_packet,
 	const struct packet *script_packet,
-	int layer, char **error)
+	int layer, u8 udp_encaps, char **error)
 {
 	verifier_func verifiers[HEADER_NUM_TYPES] = {
 		[HEADER_IPV4]		= verify_ipv4,
@@ -2644,13 +2694,13 @@ static int verify_header(
 	assert(type < HEADER_NUM_TYPES);
 	verifier = verifiers[type];
 	assert(verifier != NULL);
-	return verifier(actual_packet, script_packet, layer, error);
+	return verifier(actual_packet, script_packet, layer, udp_encaps, error);
 }
 
 /* Verify that required actual header fields are as the script expected. */
 static int verify_outbound_live_headers(
 	const struct packet *actual_packet,
-	const struct packet *script_packet, char **error)
+	const struct packet *script_packet, u8 udp_encaps, char **error)
 {
 	const int actual_headers = packet_header_count(actual_packet);
 	const int script_headers = packet_header_count(script_packet);
@@ -2676,7 +2726,8 @@ static int verify_outbound_live_headers(
 		if (script_packet->headers[i].type == HEADER_NONE)
 			break;
 
-		if (verify_header(actual_packet, script_packet, i, error))
+		if (verify_header(actual_packet, script_packet, i,
+				  udp_encaps, error))
 			return STATUS_ERR;
 	}
 
@@ -2795,11 +2846,13 @@ static int verify_outbound_live_packet(
 
 	/* Map live packet values into script space for easy comparison. */
 	if (map_outbound_live_packet(
-		    socket, live_packet, actual_packet, script_packet, error))
+		    socket, live_packet, actual_packet, script_packet,
+		    state->config->udp_encaps, error))
 		goto out;
 
 	/* Verify actual IP, TCP/UDP header values matched expected ones. */
-	if (verify_outbound_live_headers(actual_packet, script_packet, error)) {
+	if (verify_outbound_live_headers(actual_packet, script_packet,
+					 state->config->udp_encaps, error)) {
 		non_fatal = true;
 		goto out;
 	}
@@ -2832,11 +2885,11 @@ static int verify_outbound_live_packet(
 	result = STATUS_OK;
 
 out:
-	add_packet_dump(error, "script", script_packet, script_usecs,
+	add_packet_dump(state, error, "script", script_packet, script_usecs,
 			DUMP_SHORT);
 	if (actual_packet != NULL) {
-		add_packet_dump(error, "actual", actual_packet, actual_usecs,
-				DUMP_SHORT);
+		add_packet_dump(state, error, "actual", actual_packet,
+				actual_usecs, DUMP_SHORT);
 		packet_free(actual_packet);
 	}
 	if (result == STATUS_ERR &&
@@ -2857,7 +2910,8 @@ static int sniff_outbound_live_packet(
 	enum direction_t direction = DIRECTION_INVALID;
 	assert(*packet == NULL);
 	while (1) {
-		if (netdev_receive(state->netdev, packet, error))
+		if (netdev_receive(state->netdev, state->config->udp_encaps,
+				   packet, error))
 			return STATUS_ERR;
 		/* See if the packet matches an existing, known socket. */
 		socket = find_socket_for_live_packet(state, *packet,
@@ -3113,8 +3167,24 @@ static int do_outbound_script_packet(
 				    state, live_packet->time_usecs));
 
 	/* Save the TCP header so we can reset the connection at the end. */
-	if (live_packet->tcp)
+	if (live_packet->tcp) {
 		socket->last_outbound_tcp_header = *(live_packet->tcp);
+		if (state->config->udp_encaps == IPPROTO_TCP) {
+			struct udp *udp = (struct udp *)(live_packet->tcp) - 1;
+
+			socket->last_outbound_udp_encaps_src_port = ntohs(udp->src_port);
+			socket->last_outbound_udp_encaps_dst_port = ntohs(udp->dst_port);
+		}
+	}
+
+	if (live_packet->sctp) {
+		if (state->config->udp_encaps == IPPROTO_SCTP) {
+			struct udp *udp = (struct udp *)(live_packet->sctp) - 1;
+
+			socket->last_outbound_udp_encaps_src_port = ntohs(udp->src_port);
+			socket->last_outbound_udp_encaps_dst_port = ntohs(udp->dst_port);
+		}
+	}
 
 	/* Verify the bits the kernel sent were what the script expected. */
 	result = verify_outbound_live_packet(
@@ -3128,7 +3198,8 @@ out:
 
 /* Checksum the packet and inject it into the kernel under test. */
 static int send_live_ip_packet(struct netdev *netdev,
-			       struct packet *packet)
+			       struct packet *packet,
+			       u8 udp_encaps)
 {
 	assert(packet->ip_bytes > 0);
 	/* We do IPv4 and IPv6 */
@@ -3138,7 +3209,7 @@ static int send_live_ip_packet(struct netdev *netdev,
 	       packet->icmpv4 || packet->icmpv6);
 
 	/* Fill in layer 3 and layer 4 checksums */
-	checksum_packet(packet);
+	checksum_packet(packet, udp_encaps);
 
 	return netdev_send(netdev, packet);
 }
@@ -3211,9 +3282,21 @@ static int do_inbound_script_packet(
 								break;
 							}
 						}
-						assert(packet->headers[i + 1].type == HEADER_SCTP);
-						packet->headers[i].total_bytes += temp_offset;
-						packet->headers[i + 1].total_bytes += temp_offset;
+						if (state->config->udp_encaps == IPPROTO_SCTP) {
+							struct udp *udp;
+
+							assert(packet->headers[i + 1].type == HEADER_UDP);
+							assert(packet->headers[i + 2].type == HEADER_SCTP);
+							packet->headers[i].total_bytes += temp_offset;
+							packet->headers[i + 1].total_bytes += temp_offset;
+							packet->headers[i + 2].total_bytes += temp_offset;
+							udp = ((struct udp *)packet->sctp) - 1;
+							udp->len = htons(ntohs(udp->len) + temp_offset);
+						} else {
+							assert(packet->headers[i + 1].type == HEADER_SCTP);
+							packet->headers[i].total_bytes += temp_offset;
+							packet->headers[i + 1].total_bytes += temp_offset;
+						}
 						offset += temp_offset;
 					}
 					if (((packet->flags & FLAGS_SCTP_BAD_CRC32C) == 0) &&
@@ -3248,9 +3331,21 @@ static int do_inbound_script_packet(
 								break;
 							}
 						}
-						assert(packet->headers[i + 1].type == HEADER_SCTP);
-						packet->headers[i].total_bytes += temp_offset;
-						packet->headers[i + 1].total_bytes += temp_offset;
+						if (state->config->udp_encaps == IPPROTO_SCTP) {
+							struct udp *udp;
+
+							assert(packet->headers[i + 1].type == HEADER_UDP);
+							assert(packet->headers[i + 2].type == HEADER_SCTP);
+							packet->headers[i].total_bytes += temp_offset;
+							packet->headers[i + 1].total_bytes += temp_offset;
+							packet->headers[i + 2].total_bytes += temp_offset;
+							udp = ((struct udp *)packet->sctp) - 1;
+							udp->len = htons(ntohs(udp->len) + temp_offset);
+						} else {
+							assert(packet->headers[i + 1].type == HEADER_SCTP);
+							packet->headers[i].total_bytes += temp_offset;
+							packet->headers[i + 1].total_bytes += temp_offset;
+						}
 						offset += temp_offset;
 					}
 					break;
@@ -3265,7 +3360,8 @@ static int do_inbound_script_packet(
 	/* Start with a bit-for-bit copy of the packet from the script. */
 	struct packet *live_packet = packet_copy(packet);
 	/* Map packet fields from script values to live values. */
-	if (map_inbound_packet(socket, live_packet, error))
+	if (map_inbound_packet(socket, live_packet, state->config->udp_encaps,
+			       error))
 		goto out;
 
 	verbose_packet_dump(state, "inbound injected", live_packet,
@@ -3277,7 +3373,22 @@ static int do_inbound_script_packet(
 		socket->last_injected_tcp_header = *(live_packet->tcp);
 		socket->last_injected_tcp_payload_len =
 			packet_payload_len(live_packet);
+		if (state->config->udp_encaps == IPPROTO_TCP) {
+			struct udp *udp = (struct udp *)(live_packet->tcp) - 1;
+
+			socket->last_injected_udp_encaps_src_port = ntohs(udp->src_port);
+			socket->last_injected_udp_encaps_dst_port = ntohs(udp->dst_port);
+		}
 	}
+	if (live_packet->sctp) {
+		if (state->config->udp_encaps == IPPROTO_SCTP) {
+			struct udp *udp = (struct udp *)(live_packet->sctp) - 1;
+
+			socket->last_injected_udp_encaps_src_port = ntohs(udp->src_port);
+			socket->last_injected_udp_encaps_dst_port = ntohs(udp->dst_port);
+		}
+	}
+
 	if (((live_packet->ipv4 != NULL) &&
 	     (live_packet->ipv4->src_ip.s_addr == htonl(INADDR_ANY))) ||
 	    ((live_packet->ipv6 != NULL) &&
@@ -3288,11 +3399,11 @@ static int do_inbound_script_packet(
 			DEBUGP("socket_under_test = %p\n", state->socket_under_test);
 			state->socket_under_test = setup_new_child_socket(state, packet);
 			socket_get_inbound(&state->socket_under_test->live, &live_inbound);
-			set_packet_tuple(live_packet, &live_inbound);
+			set_packet_tuple(live_packet, &live_inbound, state->config->udp_encaps != 0);
 	}
 
 	/* Inject live packet into kernel. */
-	result = send_live_ip_packet(state->netdev, live_packet);
+	result = send_live_ip_packet(state->netdev, live_packet, state->config->udp_encaps);
 
 out:
 	packet_free(live_packet);
@@ -3360,7 +3471,17 @@ int reset_connection(struct state *state, struct socket *socket)
 	struct packet *packet = NULL;
 	struct tuple live_inbound;
 	int result = STATUS_OK;
+	u16 udp_src_port;
+	u16 udp_dst_port;
 
+	if (socket->last_outbound_udp_encaps_src_port != 0 ||
+	    socket->last_outbound_udp_encaps_dst_port != 0) {
+		udp_src_port = socket->last_outbound_udp_encaps_dst_port;
+		udp_dst_port = socket->last_outbound_udp_encaps_src_port;
+	} else {
+		udp_src_port = socket->last_injected_udp_encaps_src_port;
+		udp_dst_port = socket->last_injected_udp_encaps_dst_port;
+	}
 	/* Pick TCP header fields to be something the kernel will accept. */
 	if (socket->last_injected_tcp_header.ack) {
 		/* If we've already injected something, then use a sequence
@@ -3393,16 +3514,17 @@ int reset_connection(struct state *state, struct socket *socket)
 
 	packet = new_tcp_packet(socket->address_family,
 				DIRECTION_INBOUND, ECN_NONE,
-				"R.", seq, 0, ack_seq, window, NULL, false, &error);
+				"R.", seq, 0, ack_seq, window, NULL, false,
+				udp_src_port, udp_dst_port, &error);
 	if (packet == NULL)
 		die("%s", error);
 
 	/* Rewrite addresses and port to match inbound live traffic. */
 	socket_get_inbound(&socket->live, &live_inbound);
-	set_packet_tuple(packet, &live_inbound);
+	set_packet_tuple(packet, &live_inbound, state->config->udp_encaps != 0);
 
 	/* Inject live packet into kernel. */
-	result = send_live_ip_packet(state->netdev, packet);
+	result = send_live_ip_packet(state->netdev, packet, state->config->udp_encaps);
 
 	packet_free(packet);
 
@@ -3421,8 +3543,10 @@ int abort_association(struct state *state, struct socket *socket)
 	struct sctp_chunk_list *chunk_list;
 	struct sctp_cause_list *cause_list;
 	struct tuple live_inbound;
-	int result = STATUS_OK;
+	int result;
 	s64 flgs;
+	u16 udp_src_port;
+	u16 udp_dst_port;
 
 	if ((socket->live.local_initiate_tag == 0) &&
 	    (socket->live.remote_initiate_tag == 0)) {
@@ -3433,6 +3557,14 @@ int abort_association(struct state *state, struct socket *socket)
 	} else {
 		flgs = SCTP_ABORT_CHUNK_T_BIT;
 	}
+	if (socket->last_outbound_udp_encaps_src_port != 0 ||
+	    socket->last_outbound_udp_encaps_dst_port != 0) {
+		udp_src_port = socket->last_outbound_udp_encaps_dst_port;
+		udp_dst_port = socket->last_outbound_udp_encaps_src_port;
+	} else {
+		udp_src_port = socket->last_injected_udp_encaps_src_port;
+		udp_dst_port = socket->last_injected_udp_encaps_dst_port;
+	}
 	cause_list = sctp_cause_list_new();
 	sctp_cause_list_append(cause_list,
 	                       sctp_user_initiated_abort_cause_new("packetdrill cleaning up"));
@@ -3440,12 +3572,13 @@ int abort_association(struct state *state, struct socket *socket)
 	sctp_chunk_list_append(chunk_list, sctp_abort_chunk_new(flgs, cause_list));
 	packet = new_sctp_packet(socket->address_family,
 				 DIRECTION_INBOUND, ECN_NONE, -1, false,
-				 chunk_list, &error);
+				 chunk_list, udp_src_port, udp_dst_port,
+				 &error);
 	if (packet == NULL)
 		die("%s", error);
 	/* Rewrite addresses and port to match inbound live traffic. */
 	socket_get_inbound(&socket->live, &live_inbound);
-	set_packet_tuple(packet, &live_inbound);
+	set_packet_tuple(packet, &live_inbound, state->config->udp_encaps != 0);
 	/* Rewrite the verification tag in the SCTP common header */
 	if (socket->live.local_initiate_tag != 0) {
 		packet->sctp->v_tag = htonl(socket->live.local_initiate_tag);
@@ -3454,7 +3587,7 @@ int abort_association(struct state *state, struct socket *socket)
 	}
 
 	/* Inject live packet into kernel. */
-	result = send_live_ip_packet(state->netdev, packet);
+	result = send_live_ip_packet(state->netdev, packet, state->config->udp_encaps);
 
 	packet_free(packet);
 
diff --git a/gtests/net/packetdrill/sctp_packet.c b/gtests/net/packetdrill/sctp_packet.c
index 8be43c6e..4237d9a9 100644
--- a/gtests/net/packetdrill/sctp_packet.c
+++ b/gtests/net/packetdrill/sctp_packet.c
@@ -3013,10 +3013,12 @@ new_sctp_packet(int address_family,
                 s64 tag,
                 bool bad_crc32c,
                 struct sctp_chunk_list *list,
+                u16 udp_src_port,
+                u16 udp_dst_port,
                 char **error)
 {
 	struct packet *packet;  /* the newly-allocated result packet */
-	struct header *sctp_header;  /* the SCTP header info */
+	struct header *sctp_header, *udp_header;
 	struct sctp_chunk_list_item *chunk_item;
 	struct sctp_parameter_list_item *parameter_item;
 	struct sctp_cause_list_item *cause_item;
@@ -3024,11 +3026,12 @@ new_sctp_packet(int address_family,
 	const int ip_option_bytes = 0;
 	const int ip_header_bytes = (ip_header_min_len(address_family) +
 				     ip_option_bytes);
+	const int udp_header_bytes = sizeof(struct udp);
 	const int sctp_header_bytes = sizeof(struct sctp_common_header);
 	const int sctp_chunk_bytes = list->length;
-	const int ip_bytes =
-		 ip_header_bytes + sctp_header_bytes + sctp_chunk_bytes;
+	int ip_bytes;
 	bool overbook = false;
+	bool encapsulate = (udp_src_port > 0) || (udp_dst_port > 0);
 
 	/* Sanity-check all the various lengths */
 	if (ip_option_bytes & 0x3) {
@@ -3039,6 +3042,11 @@ new_sctp_packet(int address_family,
 	}
 	assert((ip_header_bytes & 0x3) == 0);
 
+	ip_bytes = ip_header_bytes + sctp_header_bytes + sctp_chunk_bytes;
+	if (encapsulate) {
+		ip_bytes += udp_header_bytes;
+	}
+
 	if (ip_bytes > MAX_SCTP_DATAGRAM_BYTES) {
 		asprintf(error, "SCTP packet too large");
 		return NULL;
@@ -3383,7 +3391,6 @@ new_sctp_packet(int address_family,
 	/* Allocate and zero out a packet object of the desired size */
 	packet = packet_new(overbook ? MAX_SCTP_DATAGRAM_BYTES : ip_bytes);
 	memset(packet->buffer, 0, overbook ? MAX_SCTP_DATAGRAM_BYTES : ip_bytes);
-
 	packet->direction = direction;
 	packet->flags = 0;
 	if (bad_crc32c) {
@@ -3395,14 +3402,29 @@ new_sctp_packet(int address_family,
 	packet->ecn = ecn;
 
 	/* Set IP header fields */
-	set_packet_ip_header(packet, address_family, ip_bytes, ecn,
-			     IPPROTO_SCTP);
+	if (encapsulate) {
+		set_packet_ip_header(packet, address_family, ip_bytes, ecn,
+				     IPPROTO_UDP);
+		udp_header = packet_append_header(packet, HEADER_UDP, udp_header_bytes);
+		udp_header->total_bytes = udp_header_bytes + sctp_header_bytes + sctp_chunk_bytes;
+		udp_header->h.udp->src_port = htons(udp_src_port);
+		udp_header->h.udp->dst_port = htons(udp_dst_port);
+		udp_header->h.udp->len = htons(udp_header_bytes + sctp_header_bytes + sctp_chunk_bytes);
+		udp_header->h.udp->check = htons(0);
+	} else {
+		set_packet_ip_header(packet, address_family, ip_bytes, ecn,
+				     IPPROTO_SCTP);
+	}
 
 	sctp_header = packet_append_header(packet, HEADER_SCTP, sctp_header_bytes);
 	sctp_header->total_bytes = sctp_header_bytes + sctp_chunk_bytes;
 
 	/* Find the start of the SCTP common header of the packet */
-	packet->sctp = (struct sctp_common_header *) (ip_start(packet) + ip_header_bytes);
+	if (encapsulate) {
+		packet->sctp = (struct sctp_common_header *) (ip_start(packet) + ip_header_bytes + udp_header_bytes);
+	} else {
+		packet->sctp = (struct sctp_common_header *) (ip_start(packet) + ip_header_bytes);
+	}
 	u8 *sctp_chunk_start = (u8 *) (packet->sctp + 1);
 
 	/* Set SCTP header fields */
@@ -3436,8 +3458,8 @@ new_sctp_packet(int address_family,
 		sctp_chunk_start += chunk_item->length;
 	}
 	free(packet->chunk_list);
+	packet->ip_bytes += sctp_chunk_bytes;
 	packet->chunk_list = list;
-	packet->ip_bytes = ip_bytes;
 	return packet;
 }
 
@@ -3453,24 +3475,27 @@ static void print_sctp_byte_list(struct sctp_byte_list *list) {
 
 struct packet *
 new_sctp_generic_packet(int address_family,
-                enum direction_t direction,
-                enum ip_ecn_t ecn,
-                s64 tag,
-                bool bad_crc32c,
-                struct sctp_byte_list *bytes,
-                char **error) {
+			enum direction_t direction,
+			enum ip_ecn_t ecn,
+			s64 tag,
+			bool bad_crc32c,
+			struct sctp_byte_list *bytes,
+			u16 udp_src_port,
+			u16 udp_dst_port,
+			char **error) {
 	struct packet *packet;  /* the newly-allocated result packet */
-	struct header *sctp_header;  /* the SCTP header info */
+	struct header *sctp_header, *udp_header;
 	struct sctp_byte_list_item *item = NULL;
 	/* Calculate lengths in bytes of all sections of the packet */
 	const int ip_option_bytes = 0;
 	const int ip_header_bytes = (ip_header_min_len(address_family) +
 				     ip_option_bytes);
+	const int udp_header_bytes = sizeof(struct udp);
 	const int sctp_header_bytes = sizeof(struct sctp_common_header);
 	const int sctp_chunk_bytes = bytes->nr_entries;
-	const int ip_bytes =
-		 ip_header_bytes + sctp_header_bytes + sctp_chunk_bytes;
+	int ip_bytes;
 	bool overbook = false;
+	bool encapsulate = (udp_src_port > 0) || (udp_dst_port > 0);
 	u16 i;
 
 #ifdef DEBUG_LOGGING
@@ -3492,6 +3517,11 @@ new_sctp_generic_packet(int address_family,
 	}
 	assert((ip_header_bytes & 0x3) == 0);
 
+	ip_bytes = ip_header_bytes + sctp_header_bytes + sctp_chunk_bytes;
+	if (encapsulate) {
+		ip_bytes += udp_header_bytes;
+	}
+
 	if (ip_bytes > MAX_SCTP_DATAGRAM_BYTES) {
 		asprintf(error, "SCTP packet too large");
 		return NULL;
@@ -3512,14 +3542,29 @@ new_sctp_generic_packet(int address_family,
 	packet->ecn = ecn;
 
 	/* Set IP header fields */
-	set_packet_ip_header(packet, address_family, ip_bytes, ecn,
-			     IPPROTO_SCTP);
+	if (encapsulate) {
+		set_packet_ip_header(packet, address_family, ip_bytes, ecn,
+				     IPPROTO_UDP);
+		udp_header = packet_append_header(packet, HEADER_UDP, udp_header_bytes);
+		udp_header->total_bytes = udp_header_bytes + sctp_header_bytes + sctp_chunk_bytes;
+		udp_header->h.udp->src_port = htons(udp_src_port);
+		udp_header->h.udp->dst_port = htons(udp_dst_port);
+		udp_header->h.udp->len = htons(udp_header_bytes + sctp_header_bytes + sctp_chunk_bytes);
+		udp_header->h.udp->check = htons(0);
+	} else {
+		set_packet_ip_header(packet, address_family, ip_bytes, ecn,
+				     IPPROTO_SCTP);
+	}
 
 	sctp_header = packet_append_header(packet, HEADER_SCTP, sctp_header_bytes);
 	sctp_header->total_bytes = sctp_header_bytes + sctp_chunk_bytes;
 
 	/* Find the start of the SCTP common header of the packet */
-	packet->sctp = (struct sctp_common_header *) (ip_start(packet) + ip_header_bytes);
+	if (encapsulate) {
+		packet->sctp = (struct sctp_common_header *) (ip_start(packet) + ip_header_bytes + udp_header_bytes);
+	} else {
+		packet->sctp = (struct sctp_common_header *) (ip_start(packet) + udp_header_bytes);
+	}
 	u8 *sctp_chunk_start = (u8 *) (packet->sctp + 1);
 
 	/* Set SCTP header fields */
diff --git a/gtests/net/packetdrill/sctp_packet.h b/gtests/net/packetdrill/sctp_packet.h
index 6fece3a3..33f420e5 100644
--- a/gtests/net/packetdrill/sctp_packet.h
+++ b/gtests/net/packetdrill/sctp_packet.h
@@ -564,14 +564,18 @@ extern struct packet *new_sctp_packet(int address_family,
 				      s64 tag,
 				      bool bad_crc32c,
 				      struct sctp_chunk_list *chunk_list,
+				      u16 udp_src_port,
+				      u16 udp_dst_port,
 				      char **error);
 
 struct packet *
 new_sctp_generic_packet(int address_family,
-                enum direction_t direction,
-                enum ip_ecn_t ecn,
-                s64 tag,
-                bool bad_crc32c,
-                struct sctp_byte_list *bytes,
-                char **error);
+			enum direction_t direction,
+			enum ip_ecn_t ecn,
+			s64 tag,
+			bool bad_crc32c,
+			struct sctp_byte_list *bytes,
+			u16 udp_src_port,
+			u16 udp_dst_port,
+			char **error);
 #endif /* __SCTP_PACKET_H__ */
diff --git a/gtests/net/packetdrill/socket.h b/gtests/net/packetdrill/socket.h
index 96c6f6bf..07fc1336 100644
--- a/gtests/net/packetdrill/socket.h
+++ b/gtests/net/packetdrill/socket.h
@@ -116,6 +116,11 @@ struct socket {
 	struct tcp last_injected_tcp_header;
 	u32 last_injected_tcp_payload_len;
 
+	u16 last_outbound_udp_encaps_dst_port;
+	u16 last_outbound_udp_encaps_src_port;
+	u16 last_injected_udp_encaps_src_port;
+	u16 last_injected_udp_encaps_dst_port;
+
 	struct sctp_cookie_echo_chunk *prepared_cookie_echo;
 	u16 prepared_cookie_echo_length;
 	struct sctp_heartbeat_ack_chunk *prepared_heartbeat_ack;
@@ -233,14 +238,15 @@ static inline void set_headers_tuple(struct ipv4 *ipv4,
 
 /* Set the tuple for a packet header echoed inside an ICMPv4/ICMPv6 message. */
 static inline void set_icmp_echoed_tuple(struct packet *packet,
-					 const struct tuple *tuple)
+					 const struct tuple *tuple,
+					 bool encapsulated)
 {
 	/* All currently supported ICMP message types include a copy
 	 * of the outbound IP header and the first few bytes inside,
 	 * which so far always means the first ICMP_ECHO_BYTES of
 	 * TCP header.
 	 */
-	DEBUGP("set_icmp_echoed_tuple");
+	DEBUGP("set_icmp_echoed_tuple encapsulated: %d\n", encapsulated);
 
 	/* Flip the direction of the tuple, since the ICMP message is
 	 * flowing in the direction opposite that of the echoed TCP/IP
@@ -250,8 +256,8 @@ 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_sctp_header(packet, encapsulated),
+			  packet_echoed_tcp_header(packet, encapsulated),
 			  packet_echoed_udp_header(packet),
 			  packet_echoed_udplite_header(packet),
 			  &echoed_tuple);
@@ -259,12 +265,12 @@ static inline void set_icmp_echoed_tuple(struct packet *packet,
 
 /* Set the tuple for a packet. */
 static inline void set_packet_tuple(struct packet *packet,
-				    const struct tuple *tuple)
+				    const struct tuple *tuple, bool encapsulated)
 {
 	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);
+		set_icmp_echoed_tuple(packet, tuple, encapsulated);
 }
 
 
diff --git a/gtests/net/packetdrill/symbols_freebsd.c b/gtests/net/packetdrill/symbols_freebsd.c
index d8b0f52f..9e21d375 100644
--- a/gtests/net/packetdrill/symbols_freebsd.c
+++ b/gtests/net/packetdrill/symbols_freebsd.c
@@ -366,18 +366,20 @@ struct int_symbol platform_symbols_table[] = {
 	{ TCP_MD5SIG,                       "TCP_MD5SIG"                      },
 	{ TCP_INFO,                         "TCP_INFO"                        },
 	{ TCP_CONGESTION,                   "TCP_CONGESTION"                  },
-#if __FreeBSD_version >=1100000
+#if defined(TCP_CCALGOOPT)
 	{ TCP_CCALGOOPT,                    "TCP_CCALGOOPT"                   },
 #endif
 	{ TCP_KEEPINIT,                     "TCP_KEEPINIT"                    },
 	{ TCP_KEEPIDLE,                     "TCP_KEEPIDLE"                    },
 	{ TCP_KEEPINTVL,                    "TCP_KEEPINTVL"                   },
 	{ TCP_KEEPCNT,                      "TCP_KEEPCNT"                     },
-#if __FreeBSD_version >= 1003000
+#if defined(TCP_FASTOPEN)
 	{ TCP_FASTOPEN,                     "TCP_FASTOPEN"                    },
 #endif
-
-#if __FreeBSD_version >= 1001000
+#if defined(TCP_REMOTE_UDP_ENCAPS_PORT)
+	{ TCP_REMOTE_UDP_ENCAPS_PORT,       "TCP_REMOTE_UDP_ENCAPS_PORT"      },
+#endif
+#if defined(UDPLITE_RECV_CSCOV) && defined(UDPLITE_SEND_CSCOV)
 	/* /usr/include/netinet/udplite.h */
 	{ UDPLITE_RECV_CSCOV,               "UDPLITE_RECV_CSCOV"              },
 	{ UDPLITE_SEND_CSCOV,               "UDPLITE_SEND_CSCOV"              },
diff --git a/gtests/net/packetdrill/tcp_packet.c b/gtests/net/packetdrill/tcp_packet.c
index de415f98..fd536547 100644
--- a/gtests/net/packetdrill/tcp_packet.c
+++ b/gtests/net/packetdrill/tcp_packet.c
@@ -60,18 +60,21 @@ struct packet *new_tcp_packet(int address_family,
 			       s32 window,
 			       const struct tcp_options *tcp_options,
 			       bool abs_ts_ecr,
+			       u16 udp_src_port,
+			       u16 udp_dst_port,
 			       char **error)
 {
 	struct packet *packet = NULL;  /* the newly-allocated result packet */
-	struct header *tcp_header = NULL;  /* the TCP header info */
+	struct header *tcp_header, *udp_header;
 	/* Calculate lengths in bytes of all sections of the packet */
 	const int ip_option_bytes = 0;
 	const int tcp_option_bytes = tcp_options ? tcp_options->length : 0;
 	const int ip_header_bytes = (ip_header_min_len(address_family) +
 				     ip_option_bytes);
+	const int udp_header_bytes = sizeof(struct udp);
 	const int tcp_header_bytes = sizeof(struct tcp) + tcp_option_bytes;
-	const int ip_bytes =
-		 ip_header_bytes + tcp_header_bytes + tcp_payload_bytes;
+	int ip_bytes;
+	bool encapsulate = (udp_src_port > 0) || (udp_dst_port > 0);
 
 	/* Sanity-check all the various lengths */
 	if (ip_option_bytes & 0x3) {
@@ -95,6 +98,10 @@ struct packet *new_tcp_packet(int address_family,
 		return NULL;
 	}
 
+	ip_bytes = ip_header_bytes + tcp_header_bytes + tcp_payload_bytes;
+	if (encapsulate) {
+		ip_bytes += udp_header_bytes;
+	}
 	if (ip_bytes > MAX_TCP_DATAGRAM_BYTES) {
 		asprintf(error, "TCP segment too large");
 		return NULL;
@@ -112,14 +119,29 @@ struct packet *new_tcp_packet(int address_family,
 	packet->ecn = ecn;
 
 	/* Set IP header fields */
-	set_packet_ip_header(packet, address_family, ip_bytes, ecn,
-			     IPPROTO_TCP);
+	if (encapsulate) {
+		set_packet_ip_header(packet, address_family, ip_bytes, ecn,
+				     IPPROTO_UDP);
+		udp_header = packet_append_header(packet, HEADER_UDP, udp_header_bytes);
+		udp_header->total_bytes = udp_header_bytes + tcp_header_bytes + tcp_payload_bytes;
+		udp_header->h.udp->src_port = htons(udp_src_port);
+		udp_header->h.udp->dst_port = htons(udp_dst_port);
+		udp_header->h.udp->len = htons(udp_header_bytes + tcp_header_bytes + tcp_payload_bytes);
+		udp_header->h.udp->check = htons(0);
+	} else {
+		set_packet_ip_header(packet, address_family, ip_bytes, ecn,
+				     IPPROTO_TCP);
+	}
 
 	tcp_header = packet_append_header(packet, HEADER_TCP, tcp_header_bytes);
 	tcp_header->total_bytes = tcp_header_bytes + tcp_payload_bytes;
 
 	/* Find the start of TCP sections of the packet */
-	packet->tcp = (struct tcp *) (ip_start(packet) + ip_header_bytes);
+	if (encapsulate) {
+		packet->tcp = (struct tcp *) (ip_start(packet) + ip_header_bytes + udp_header_bytes);
+	} else {
+		packet->tcp = (struct tcp *) (ip_start(packet) + ip_header_bytes);
+	}
 	u8 *tcp_option_start = (u8 *) (packet->tcp + 1);
 
 	/* Set TCP header fields */
diff --git a/gtests/net/packetdrill/tcp_packet.h b/gtests/net/packetdrill/tcp_packet.h
index 0c065c0e..415445e9 100644
--- a/gtests/net/packetdrill/tcp_packet.h
+++ b/gtests/net/packetdrill/tcp_packet.h
@@ -45,5 +45,7 @@ extern struct packet *new_tcp_packet(int address_family,
 				     s32 window,
 				     const struct tcp_options *tcp_options,
 				     bool abs_ts_ecr,
+				     u16 udp_src_port,
+				     u16 udp_dst_port,
 				     char **error);
 #endif /* __TCP_PACKET_H__ */
diff --git a/gtests/net/packetdrill/wire_client_netdev.c b/gtests/net/packetdrill/wire_client_netdev.c
index ce4ebefc..049f9821 100644
--- a/gtests/net/packetdrill/wire_client_netdev.c
+++ b/gtests/net/packetdrill/wire_client_netdev.c
@@ -150,7 +150,7 @@ static int wire_client_netdev_send(struct netdev *a_netdev,
 	return STATUS_ERR;
 }
 
-static int wire_client_netdev_receive(struct netdev *a_netdev,
+static int wire_client_netdev_receive(struct netdev *a_netdev, u8 udp_encaps,
 				      struct packet **packet, char **error)
 {
 	DEBUGP("wire_client_netdev_receive\n");
diff --git a/gtests/net/packetdrill/wire_server_netdev.c b/gtests/net/packetdrill/wire_server_netdev.c
index 8ef26f3a..02905614 100644
--- a/gtests/net/packetdrill/wire_server_netdev.c
+++ b/gtests/net/packetdrill/wire_server_netdev.c
@@ -184,7 +184,7 @@ static int wire_server_netdev_send(struct netdev *a_netdev,
 	return result;
 }
 
-static int wire_server_netdev_receive(struct netdev *a_netdev,
+static int wire_server_netdev_receive(struct netdev *a_netdev, u8 udp_encaps,
 				      struct packet **packet, char **error)
 {
 	struct wire_server_netdev *netdev = to_server_netdev(a_netdev);
@@ -192,8 +192,8 @@ static int wire_server_netdev_receive(struct netdev *a_netdev,
 
 	DEBUGP("wire_server_netdev_receive\n");
 
-	return netdev_receive_loop(netdev->psock, DIRECTION_INBOUND, packet,
-				   &num_packets, error);
+	return netdev_receive_loop(netdev->psock, DIRECTION_INBOUND, udp_encaps,
+				   packet, &num_packets, error);
 }
 
 struct netdev_ops wire_server_netdev_ops = {
-- 
GitLab