/*
 * Copyright 2013 Google Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */
/*
 * Author: ncardwell@google.com (Neal Cardwell)
 *
 * A module to execute a packet command from a test script.
 */

#include "run_packet.h"

#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include "checksum.h"
#include "gre.h"
#include "logging.h"
#include "netdev.h"
#include "packet.h"
#include "packet_checksum.h"
#include "packet_to_string.h"
#include "run.h"
#include "script.h"
#include "sctp_iterator.h"
#include "sctp_packet.h"
#include "tcp_options_iterator.h"
#include "tcp_options_to_string.h"
#include "tcp_packet.h"

/* To avoid issues with TIME_WAIT, FIN_WAIT1, and FIN_WAIT2 we use
 * dynamically-chosen, unique 4-tuples for each test. We implement the
 * picking of unique ports by binding a socket to port 0 and seeing
 * what port we are assigned. Note that we keep the socket fd open for
 * the lifetime of our process to ensure that the port is not
 * reused by a later test.
 */
static u16 ephemeral_port(void)
{
	int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (fd < 0)
		die_perror("socket");

	struct sockaddr_in addr;
	socklen_t addrlen = sizeof(addr);
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = 0;		/* let the OS pick the port */
	if (bind(fd, (struct sockaddr *)&addr, addrlen) < 0)
		die_perror("bind");

	memset(&addr, 0, sizeof(addr));
	if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) < 0)
		die_perror("getsockname");
	assert(addr.sin_family == AF_INET);

	if (listen(fd, 1) < 0)
		die_perror("listen");

	return ntohs(addr.sin_port);
}

/* Return the next ephemeral port to use. We want quick results for
 * the very common case where there is only one remote port to use
 * over the course of a test. So we avoid paying the overhead of the
 * several system calls in ephemeral_port() right before injecting an
 * incoming SYN by pre-allocating and caching a single port to use
 * before starting each test.
 */
static u16 next_ephemeral_port(struct state *state)
{
	if (state->packets->next_ephemeral_port >= 0) {
		int port = state->packets->next_ephemeral_port;
		assert(port <= 0xffff);
		state->packets->next_ephemeral_port = -1;
		return port;
	} else {
		return ephemeral_port();
	}
}

/* Add a dump of the given packet to the given error message.
 * Frees *error and replaces it with a version that has the original
 * *error followed by the given type and a hex dump of the given
 * packet.
 */
static void add_packet_dump(char **error, const char *type,
			    struct packet *packet, s64 time_usecs,
			    enum dump_format_t format)
{
	if (packet->ip_bytes != 0) {
		char *old_error = *error;
		char *dump = NULL, *dump_error = NULL;

		if (packet_to_string(packet, format, &dump, &dump_error) == STATUS_OK) {
			asprintf(error, "%s\n%s packet: %9.6f %s%s%s",
				 old_error, type, usecs_to_secs(time_usecs), dump,
				 dump_error ? "\n" : "",
				 dump_error ? dump_error : "");
		}
		free(dump);
		free(dump_error);
		free(old_error);
	}
}

/* For verbose runs, print a short packet dump of all live packets. */
static void verbose_packet_dump(struct state *state, const char *type,
				struct packet *live_packet, s64 time_usecs)
{
	if (state->config->verbose) {
		char *dump = NULL, *dump_error = NULL;

		if (packet_to_string(live_packet, DUMP_SHORT,  &dump, &dump_error) == STATUS_OK) {
			printf("%s packet: %9.6f %s%s%s\n",
			       type, usecs_to_secs(time_usecs), dump,
			       dump_error ? "\n" : "",
			       dump_error ? dump_error : "");
		}
		free(dump);
		free(dump_error);
	}
}

/* See if the live packet matches the live 4-tuple of the socket under test. */
static struct socket *find_socket_for_live_packet(
	struct state *state, const struct packet *packet,
	enum direction_t *direction)
{
	struct socket *socket = state->socket_under_test;	/* shortcut */

	DEBUGP("find_connect_for_live_packet\n");
	if (socket == NULL)
		return NULL;

	struct tuple packet_tuple, live_outbound, live_inbound;
	get_packet_tuple(packet, &packet_tuple);
	/* Is packet inbound to the socket under test? */
	socket_get_inbound(&socket->live, &live_inbound);
	if (is_equal_tuple(&packet_tuple, &live_inbound)) {
		*direction = DIRECTION_INBOUND;
		DEBUGP("inbound live packet, socket in state %d\n",
		       socket->state);
		return socket;
	}
	/* Is packet outbound from the socket under test? */
	socket_get_outbound(&socket->live, &live_outbound);
	if (is_equal_tuple(&packet_tuple, &live_outbound)) {
		*direction = DIRECTION_OUTBOUND;
		DEBUGP("outbound live packet, socket in state %d\n",
		       socket->state);
		return socket;
	}
	return NULL;
}

static struct socket *setup_new_child_socket(struct state *state, const struct packet *packet) {
	/* Create a child passive socket for this incoming SYN packet.
	 * Any further packets in the test script will be directed to
	 * this child socket.
	 */
	struct config *config = state->config;
	struct socket *socket;	/* shortcut */
	
	DEBUGP("creating new child_socket!\n");
	
	socket = socket_new(state);
	state->socket_under_test = socket;
	assert(socket->state == SOCKET_INIT);
	socket->state = SOCKET_PASSIVE_PACKET_RECEIVED;
	socket->address_family = packet_address_family(packet);
	socket->protocol = packet_ip_protocol(packet, config->udp_encaps);

	/* Set script info for this socket using script packet. */
	struct tuple tuple;
	get_packet_tuple(packet, &tuple);
	socket->script.remote		= tuple.src;
	socket->script.local		= tuple.dst;
	socket->script.fd		= -1;
	
	/* Set up the live info for this socket based
	 * on the script packet and our overall config.
	 */
	socket->live.remote.ip		= config->live_remote_ip;
	socket->live.remote.port	= htons(next_ephemeral_port(state));
	socket->live.local.ip		= config->live_local_ip;
	socket->live.local.port		= htons(config->live_bind_port);
	socket->live.fd			= -1;
	return socket;
}

static inline bool sctp_is_init_packet(const struct packet *packet) {
	struct sctp_chunk_list_item *item;
	
	if (packet->chunk_list != NULL) {
		item = packet->chunk_list->first;
		if ((item != NULL) && 
		    (item->chunk->type == SCTP_INIT_CHUNK_TYPE)) {
			return true;
		}
	} else {
		if (packet->flags & FLAGS_SCTP_GENERIC_PACKET) {
			u8 *sctp_chunk_start = (u8 *) (packet->sctp + 1);
			if ((sctp_chunk_start != NULL) && 
				(sctp_chunk_start[0] == SCTP_INIT_CHUNK_TYPE)) {
				return true;
			}
		}
	}
	
	return false;
}

static inline void sctp_socket_set_initiate_tag(struct socket *socket, u32 initiate_tag) {
	socket->script.remote_initiate_tag = initiate_tag;
	socket->live.remote_initiate_tag = initiate_tag;
}

static inline void sctp_socket_set_initial_tsn(struct socket *socket, u32 initial_tsn) {
	socket->script.remote_initial_tsn = initial_tsn;
	socket->live.remote_initial_tsn = initial_tsn;
}
	

/* See if the socket under test is listening and is willing to receive
 * this incoming SYN packet. If so, create a new child socket, anoint
 * it as the new socket under test, and return a pointer to
 * it. Otherwise, return NULL.
 */
static struct socket *handle_listen_for_script_packet(
	struct state *state, const struct packet *packet,
	enum direction_t direction)
{
	/* Does this packet match this socket? For now we only support
	 * testing one socket at a time, so we merely check whether
	 * the socket is listening. (If we were to support testing
	 * more than one socket at a time then we'd want to check to
	 * see if the address tuples in the packet and socket match.)
	 */
	struct config *config = state->config;
	struct socket *socket = state->socket_under_test;	/* shortcut */
	struct sctp_chunk_list_item *item;

	bool match = (direction == DIRECTION_INBOUND);
	if (!match)
		return NULL;

	if (config->is_wire_server) {
		/* On wire servers we don't see the system calls, so
		 * we won't have any socket_under_test yet.
		 */
		match = (socket == NULL);
	} else {
		/* In local mode we will certainly know about this socket. */
		match = ((socket != NULL) &&
			 (socket->state == SOCKET_PASSIVE_LISTENING));
	}
	if (!match)
		return NULL;
	
	if (socket == NULL)
		socket = setup_new_child_socket(state, packet);

	if (packet->sctp != NULL) {
		if (sctp_is_init_packet(packet)) {
			if (packet->chunk_list != NULL) {
				struct _sctp_init_chunk *init;
				item = packet->chunk_list->first;
				init = (struct _sctp_init_chunk *) item->chunk;

				sctp_socket_set_initiate_tag(socket, ntohl(init->initiate_tag));
				sctp_socket_set_initial_tsn(socket, ntohl(init->initial_tsn));
			} else {
				struct header sctp_header;
				unsigned int i;
				bool found = false;
				size_t chunk_length;

				for (i = 0; i < ARRAY_SIZE(packet->headers); ++i) {
					if (packet->headers[i].type == HEADER_SCTP) {
						sctp_header = packet->headers[i];
						found = true;
						break;
					}
				}
				
				assert(found != false);
				chunk_length = sctp_header.total_bytes - sizeof(struct sctp_common_header);

				if (chunk_length < sizeof(struct _sctp_init_chunk)) {
					fprintf(stderr, "length of init chunk too short. you must specify the whole init chunk.");
					return NULL;
				}
				
				u8 *sctp_chunk_start = (u8 *) (packet->sctp + 1);
				struct _sctp_init_chunk *init = (struct _sctp_init_chunk *) sctp_chunk_start;
				
				sctp_socket_set_initiate_tag(socket, ntohl(init->initiate_tag));
				sctp_socket_set_initial_tsn(socket, ntohl(init->initial_tsn));
			}
		} else {
			return socket;
		}
	}
	
	if (packet->tcp != NULL) {
		socket->script.remote_isn = ntohl(packet->tcp->seq);
		socket->live.remote_isn = ntohl(packet->tcp->seq);
	}

#if defined(DEBUG)
	if (debug_logging) {
#if 0
		char local_string[ADDR_STR_LEN];
		char remote_string[ADDR_STR_LEN];
		DEBUGP("live: local: %s.%d\n",
		       ip_to_string(&socket->live.local.ip, local_string),
		       ntohs(socket->live.local.port));
		DEBUGP("live: remote: %s.%d\n",
		       ip_to_string(&socket->live.remote.ip, remote_string),
		       ntohs(socket->live.remote.port));
#endif
		if (packet->tcp != NULL) {
			DEBUGP("live: ISN: %u\n", socket->live.remote_isn);
		} else {
			DEBUGP("live: initiate tag: %u\n", socket->live.remote_initiate_tag);
			DEBUGP("live: initial tsn: %u\n", socket->live.remote_initial_tsn);
		}
	}
#endif /* DEBUG */

	return socket;
}

/* See if the socket under test is a connecting socket that would emit
 * this outgoing script SYN. If so, return a pointer to the socket;
 * otherwise, return NULL.
 */
static struct socket *handle_connect_for_script_packet(
	struct state *state, const struct packet *packet,
	enum direction_t direction)
{
	/* Does this packet match this socket? For now we only support
	 * testing one socket at a time, so we merely check whether
	 * the socket is connecting. (If we were to support testing
	 * more than one socket at a time then we'd want to check to
	 * see if the address tuples in the packet and socket match.)
	 */
	struct config *config = state->config;
	struct socket *socket = state->socket_under_test;	/* shortcut */
	struct _sctp_init_chunk *init;
	struct sctp_chunk_list_item *item;
	bool match;

	DEBUGP("handle_connect_for_script_packet\n");
	assert(packet->tcp != NULL || packet->sctp != NULL);
	if (direction != DIRECTION_OUTBOUND)
		return NULL;
	if (packet->tcp != NULL) {
		match = (packet->tcp->syn && !packet->tcp->ack);
	} else {
		assert(packet->chunk_list != NULL);
		item = packet->chunk_list->first;
		if ((item != NULL) &&
		    (item->chunk->type == SCTP_INIT_CHUNK_TYPE)) {
			init = (struct _sctp_init_chunk *)item->chunk;
			match = true;
		} else {
			init = NULL;
			match = false;
		}
	}
	if (!match)
		return NULL;

	if (config->is_wire_server) {
		/* On wire servers we don't see the system calls, so
		 * we won't have any socket_under_test yet.
		 */
		match = (socket == NULL);
	} else {
		/* In local mode we will certainly know about this socket. */
		match = ((socket != NULL) &&
			 (socket->state == SOCKET_ACTIVE_CONNECTING));
	}
	if (!match)
		return NULL;

	if (socket == NULL) {
		/* Wire server. Create a socket for this outbound SYN
		 * packet. Any further packets in the test script are
		 * mapped here.
		 */
		socket = socket_new(state);
		state->socket_under_test = socket;
		assert(socket->state == SOCKET_INIT);
		socket->address_family = packet_address_family(packet);
		socket->protocol = packet_ip_protocol(packet, config->udp_encaps);

		socket->script.fd	 = -1;

		socket->live.remote.ip   = config->live_remote_ip;
		socket->live.remote.port = htons(config->live_connect_port);
		socket->live.fd		 = -1;
	}

	/* Fill in the new info about this connection. */
	struct tuple tuple;
	get_packet_tuple(packet, &tuple);
	socket->script.remote		= tuple.dst;
	socket->script.local		= tuple.src;
	if (packet->tcp) {
		socket->state = SOCKET_ACTIVE_SYN_SENT;
		socket->script.local_isn = ntohl(packet->tcp->seq);
	} else {
		DEBUGP("Moving socket in SOCKET_ACTIVE_INIT_SENT\n");
		socket->state = SOCKET_ACTIVE_INIT_SENT;
		socket->script.local_initial_tsn = ntohl(init->initial_tsn);
		socket->script.local_initiate_tag = ntohl(init->initiate_tag);
	}
	return socket;
}

/* Look for a connecting socket that would emit this outgoing live packet. */
static struct socket *find_connect_for_live_packet(
	struct state *state, struct packet *packet,
	enum direction_t *direction)
{
	struct sctp_chunks_iterator iter;
	struct sctp_chunk *chunk;
	struct _sctp_init_chunk *init;
	struct tuple tuple;
	char *error;

	DEBUGP("find_connect_for_live_packet\n");
	get_packet_tuple(packet, &tuple);

	*direction = DIRECTION_INVALID;
	struct socket *socket = state->socket_under_test;	/* shortcut */
	if (!socket)
		return NULL;

	bool is_sctp_match =
		(packet->sctp &&
		 (socket->protocol == IPPROTO_SCTP) &&
		 (socket->state == SOCKET_ACTIVE_INIT_SENT));
	bool is_tcp_match =
		(packet->tcp && packet->tcp->syn && !packet->tcp->ack &&
		 (socket->protocol == IPPROTO_TCP) &&
		 (socket->state == SOCKET_ACTIVE_SYN_SENT));
	bool is_udp_match =
		(packet->udp &&
		 (socket->protocol == IPPROTO_UDP) &&
		 (socket->state == SOCKET_ACTIVE_CONNECTING));
	bool is_udplite_match =
		(packet->udplite &&
		 (socket->protocol == IPPROTO_UDPLITE) &&
		 (socket->state == SOCKET_ACTIVE_CONNECTING));
	if (!is_sctp_match && !is_tcp_match &&
	    !is_udp_match && !is_udplite_match)
		return NULL;

	if (!is_equal_ip(&tuple.dst.ip, &socket->live.remote.ip) ||
	    !is_equal_port(tuple.dst.port, socket->live.remote.port))
		return NULL;

	*direction = DIRECTION_OUTBOUND;
	/* Using the details in this outgoing packet, fill in the
	 * new details we've learned about this actively initiated
	 * connection (for which we've seen a connect() call).
	 */
	socket->live.local.ip	= tuple.src.ip;
	socket->live.local.port	= tuple.src.port;

	if (packet->tcp)
		socket->live.local_isn	= ntohl(packet->tcp->seq);
	if (packet->sctp) {
		error = NULL;
		chunk = sctp_chunks_begin(packet, &iter, &error);
		if ((error == NULL) &&
		    (chunk != NULL) &&
		    (chunk->type == SCTP_INIT_CHUNK_TYPE)) {
			init = (struct _sctp_init_chunk *)chunk;
			socket->live.local_initiate_tag = ntohl(init->initiate_tag);
			socket->live.local_initial_tsn = ntohl(init->initial_tsn);
		}
	}

	return socket;
}

/* Convert outbound TCP timestamp value from scripted value to live value. */
static int get_outbound_ts_val_mapping(
	struct socket *socket, u32 script_timestamp, u32 *live_timestamp)
{
	DEBUGP("get_outbound_ts_val_mapping\n");
	DEBUGP("ts_val_mapping %u -> ?\n", ntohl(script_timestamp));
	if (hash_map_get(socket->ts_val_map,
				 script_timestamp, live_timestamp))
		return STATUS_OK;
	return STATUS_ERR;
}

/* Store script->live mapping for outbound TCP timestamp value. */
static void set_outbound_ts_val_mapping(
	struct socket *socket, u32 script_timestamp, u32 live_timestamp)
{
	DEBUGP("set_outbound_ts_val_mapping\n");
	DEBUGP("ts_val_mapping %u -> %u\n",
	       ntohl(script_timestamp), ntohl(live_timestamp));
	hash_map_set(socket->ts_val_map,
			     script_timestamp, live_timestamp);
}

/* A helper to find the TCP timestamp option in a packet. Parse the
 * TCP options and fill in packet->tcp_ts_val with the location of the
 * TCP timestamp value field (or NULL if there isn't one), and
 * likewise fill in packet->tcp_ts_ecr with the location of the TCP
 * timestamp echo reply field (or NULL if there isn't one). Returns
 * STATUS_OK on success; on failure returns STATUS_ERR and sets
 * error message.
 */
static int find_tcp_timestamp(struct packet *packet, char **error)
{
	struct tcp_options_iterator iter;
	struct tcp_option *option = NULL;

	packet->tcp_ts_val = NULL;
	packet->tcp_ts_ecr = NULL;
	for (option = tcp_options_begin(packet, &iter); option != NULL;
	     option = tcp_options_next(&iter, error))
		if (option->kind == TCPOPT_TIMESTAMP) {
			packet->tcp_ts_val = &(option->data.time_stamp.val);
			packet->tcp_ts_ecr = &(option->data.time_stamp.ecr);
		}
	return *error ? STATUS_ERR : STATUS_OK;
}

/* A helper to help translate SACK sequence numbers between live and
 * script space. Specifically, it offsets SACK block sequence numbers
 * by the given 'ack_offset'. Returns STATUS_OK on success; on
 * failure returns STATUS_ERR and sets error message.
 */
static int offset_sack_blocks(struct packet *packet,
			      u32 ack_offset, char **error)
{
	struct tcp_options_iterator iter;
	struct tcp_option *option = NULL;
	for (option = tcp_options_begin(packet, &iter); option != NULL;
	     option = tcp_options_next(&iter, error)) {
		if (option->kind == TCPOPT_SACK) {
			int num_blocks = 0;
			if (num_sack_blocks(option->length,
						    &num_blocks, error))
				return STATUS_ERR;
			int i = 0;
			for (i = 0; i < num_blocks; ++i) {
				u32 val;
				val = ntohl(option->data.sack.block[i].left);
				val += ack_offset;
				option->data.sack.block[i].left = htonl(val);
				val = ntohl(option->data.sack.block[i].right);
				val += ack_offset;
				option->data.sack.block[i].right = htonl(val);
			}
		}
	}
	return *error ? STATUS_ERR : STATUS_OK;
}

static int map_inbound_icmp_sctp_packet(
	struct socket *socket,
	struct packet *live_packet,
	bool encapsulated,
	char **error)
{
	u32 *v_tag = packet_echoed_sctp_v_tag(live_packet, encapsulated);
	*v_tag = htonl(socket->live.remote_initiate_tag);
	return STATUS_OK;
}

/* Rewrite the TCP sequence number echoed by the ICMP 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,
	bool encapsulated,
	char **error)
{
	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);
	*seq = htonl(ntohl(*seq) + seq_offset);
	return STATUS_OK;
}

/* UDP headers echoed by ICMP messages need no special rewriting. */
static int map_inbound_icmp_udp_packet(
	struct socket *socket, struct packet *live_packet, char **error)
{
	return STATUS_OK;
}

/* UDPLite headers echoed by ICMP messages need no special rewriting. */
static int map_inbound_icmp_udplite_packet(
	struct socket *socket, struct packet *live_packet, char **error)
{
	return STATUS_OK;
}

static int map_inbound_icmp_packet(
	struct socket *socket,
	struct packet *live_packet,
	u8 udp_encaps,
	char **error)
{
	int protocol;

	protocol = packet_echoed_ip_protocol(live_packet);
	if ((protocol == IPPROTO_UDP) && (udp_encaps != 0)) {
		protocol = udp_encaps;
	}
	if (protocol == IPPROTO_SCTP)
		return map_inbound_icmp_sctp_packet(socket, live_packet, udp_encaps == IPPROTO_SCTP, error);
	else if (protocol == IPPROTO_TCP)
		return map_inbound_icmp_tcp_packet(socket, live_packet, udp_encaps == IPPROTO_TCP, error);
	else if (protocol == IPPROTO_UDP)
		return map_inbound_icmp_udp_packet(socket, live_packet, error);
	else if (protocol == IPPROTO_UDPLITE)
		return map_inbound_icmp_udplite_packet(socket, live_packet,
						       error);
	else
		assert(!"unsupported layer 4 protocol echoed in ICMP packet");
	return STATUS_ERR;
}

static int map_inbound_sctp_packet(
	struct socket *socket, struct packet *live_packet, char **error)
{
	struct sctp_chunks_iterator iter;
	struct sctp_chunk *chunk;
	struct _sctp_data_chunk *data;
	struct _sctp_init_chunk *init;
	struct _sctp_init_ack_chunk *init_ack;
	struct _sctp_sack_chunk *sack;
	struct _sctp_nr_sack_chunk *nr_sack;
	struct _sctp_abort_chunk *abort;
	struct _sctp_shutdown_chunk *shutdown;
	struct _sctp_ecne_chunk *ecne;
	struct _sctp_cwr_chunk *cwr;
	struct _sctp_shutdown_complete_chunk *shutdown_complete;
	struct _sctp_i_data_chunk *i_data;
	struct _sctp_reconfig_chunk *reconfig;
	struct _sctp_forward_tsn_chunk *forward_tsn;
	struct _sctp_i_forward_tsn_chunk *i_forward_tsn;
	
	u32 local_diff, remote_diff;
	u32 v_tag;
	u16 nr_gap_blocks, number_of_nr_gap_blocks, nr_dup_tsns, i;
	bool reflect_v_tag;
	bool contains_init_chunk;

	assert(*error == NULL);
	reflect_v_tag = false;
	contains_init_chunk = false;
	/* Map the TSNs and the initiate tags in the INIT and INIT-ACK chunk */
	if ((live_packet->flags & FLAGS_SCTP_GENERIC_PACKET) == 0) {
		for (chunk = sctp_chunks_begin(live_packet, &iter, error);
		     chunk != NULL;
		     chunk = sctp_chunks_next(&iter, error)) {
			if (*error != NULL) {
				DEBUGP("Partial chunk detected\n");
				free(*error);
				*error = NULL;
				break;
			}
			DEBUGP("live remote tsn 0x%08x, script remote tsn 0x%08x\n",
			       socket->live.remote_initial_tsn, socket->script.remote_initial_tsn);
			DEBUGP("live local tsn 0x%08x, script local tsn 0x%08x\n",
			       socket->live.local_initial_tsn, socket->script.local_initial_tsn);
			remote_diff = socket->live.remote_initial_tsn - socket->script.remote_initial_tsn;
			local_diff = socket->live.local_initial_tsn - socket->script.local_initial_tsn;
			switch (chunk->type) {
			case SCTP_DATA_CHUNK_TYPE:
				data = (struct _sctp_data_chunk *)chunk;
				data->tsn = htonl(ntohl(data->tsn) + remote_diff);
				break;
			case SCTP_INIT_CHUNK_TYPE:
				init = (struct _sctp_init_chunk *)chunk;
				init->initial_tsn = htonl(ntohl(init->initial_tsn) + remote_diff);
				/* XXX: Does this work in all cases? */
				if (ntohl(init->initiate_tag) == socket->script.local_initiate_tag) {
					init->initiate_tag = htonl(socket->live.local_initiate_tag);
				}
				contains_init_chunk = true;
				break;
			case SCTP_INIT_ACK_CHUNK_TYPE:
				init_ack = (struct _sctp_init_ack_chunk *)chunk;
				init_ack->initial_tsn = htonl(ntohl(init_ack->initial_tsn) + remote_diff);
				/* XXX: Does this work in all cases? */
				if (ntohl(init_ack->initiate_tag) == socket->script.local_initiate_tag) {
					init_ack->initiate_tag = htonl(socket->live.local_initiate_tag);
				}
				break;
			case SCTP_SACK_CHUNK_TYPE:
				sack = (struct _sctp_sack_chunk *)chunk;
				DEBUGP("Old SACK cum TSN %d\n", ntohl(sack->cum_tsn));
				sack->cum_tsn = htonl(ntohl(sack->cum_tsn) + local_diff);
				DEBUGP("New SACK cum TSN %d\n", ntohl(sack->cum_tsn));
				nr_gap_blocks = ntohs(sack->nr_gap_blocks);
				nr_dup_tsns = ntohs(sack->nr_dup_tsns);

				if (ntohs(sack->length) == sizeof(struct _sctp_sack_chunk) + sizeof(union sctp_sack_block) * (nr_dup_tsns+nr_gap_blocks)) {
					for (i = 0; i < nr_dup_tsns; i++) {
						sack->block[i + nr_gap_blocks].tsn = htonl(ntohl(sack->block[i + nr_gap_blocks].tsn) + local_diff);
					}
				}
				break;
			case SCTP_NR_SACK_CHUNK_TYPE:
				nr_sack = (struct _sctp_nr_sack_chunk *)chunk;
				DEBUGP("Old SACK cum TSN %d\n", ntohl(nr_sack->cum_tsn));
				nr_sack->cum_tsn = htonl(ntohl(nr_sack->cum_tsn) + local_diff);
				DEBUGP("New SACK cum TSN %d\n", ntohl(nr_sack->cum_tsn));
				nr_gap_blocks = ntohs(nr_sack->nr_gap_blocks);
				number_of_nr_gap_blocks = ntohs(nr_sack->nr_of_nr_gap_blocks);
				nr_dup_tsns = ntohs(nr_sack->nr_dup_tsns);

				if (ntohs(nr_sack->length) == sizeof(struct _sctp_nr_sack_chunk) + sizeof(union sctp_nr_sack_block) * (nr_dup_tsns+nr_gap_blocks)) {
					for (i = 0; i < nr_dup_tsns; i++) {
						u16 offset = nr_gap_blocks + number_of_nr_gap_blocks;
						nr_sack->block[i + offset].tsn = htonl(ntohl(nr_sack->block[i + offset].tsn) + local_diff);
					}
				}
				break;
			case SCTP_ABORT_CHUNK_TYPE:
				abort = (struct _sctp_abort_chunk *)chunk;
				if (abort->flags & SCTP_ABORT_CHUNK_T_BIT) {
					reflect_v_tag = true;
				}
				break;
			case SCTP_SHUTDOWN_CHUNK_TYPE:
				shutdown = (struct _sctp_shutdown_chunk *)chunk;
				shutdown->cum_tsn = htonl(ntohl(shutdown->cum_tsn) + local_diff);
				break;
			case SCTP_ECNE_CHUNK_TYPE:
				ecne = (struct _sctp_ecne_chunk *)chunk;
				ecne->lowest_tsn = htonl(ntohl(ecne->lowest_tsn) + local_diff);
				break;
			case SCTP_CWR_CHUNK_TYPE:
				cwr = (struct _sctp_cwr_chunk *)chunk;
				cwr->lowest_tsn = htonl(ntohl(cwr->lowest_tsn) + local_diff);
				break;
			case SCTP_SHUTDOWN_COMPLETE_CHUNK_TYPE:
				shutdown_complete = (struct _sctp_shutdown_complete_chunk *)chunk;
				if (shutdown_complete->flags & SCTP_SHUTDOWN_COMPLETE_CHUNK_T_BIT) {
					reflect_v_tag = true;
				}
				break;
			case SCTP_I_DATA_CHUNK_TYPE:
				i_data = (struct _sctp_i_data_chunk *)chunk;
				i_data->tsn = htonl(ntohl(i_data->tsn) + remote_diff);
				break;
			case SCTP_FORWARD_TSN_CHUNK_TYPE: 
				forward_tsn = (struct _sctp_forward_tsn_chunk *) chunk;
				forward_tsn->cum_tsn = htonl(ntohl(forward_tsn->cum_tsn) + remote_diff);
				break;
			case SCTP_I_FORWARD_TSN_CHUNK_TYPE:
				i_forward_tsn = (struct _sctp_i_forward_tsn_chunk *) chunk;
				i_forward_tsn->cum_tsn = htonl(ntohl(i_forward_tsn->cum_tsn) + remote_diff);
				break;
			case SCTP_RECONFIG_CHUNK_TYPE:
				reconfig = (struct _sctp_reconfig_chunk *)chunk;
				if (htons(reconfig->length) >= sizeof(struct _sctp_reconfig_chunk) + 4) {
					struct sctp_parameter *parameter;
					struct sctp_parameters_iterator iter;
					int parameters_length = ntohs(reconfig->length) - sizeof(struct _sctp_reconfig_chunk);
					for (parameter = sctp_parameters_begin(reconfig->parameter, parameters_length,
									       &iter, error);
					     parameter != NULL;
					     parameter = sctp_parameters_next(&iter, error)) {
						if (*error != NULL) {
							DEBUGP("Partial parameter detected\n");
							free(*error);
							*error = NULL;
							break;
						}
						switch (htons(parameter->type)) {
						case SCTP_OUTGOING_SSN_RESET_REQUEST_PARAMETER_TYPE: {
							struct sctp_outgoing_ssn_reset_request_parameter *reset;
							reset = (struct sctp_outgoing_ssn_reset_request_parameter *)parameter;
							if (htons(reset->length) >= 8) {
								reset->reqsn = htonl(ntohl(reset->reqsn) + remote_diff);
							}
							if (htons(reset->length) >= 12) {
								reset->respsn = htonl(ntohl(reset->respsn) + local_diff);
							}
							if (htons(reset->length) >= 16) {
								reset->last_tsn = htonl(ntohl(reset->last_tsn) + remote_diff);
							}
							break;
						}
						case SCTP_INCOMING_SSN_RESET_REQUEST_PARAMETER_TYPE: {
							struct sctp_incoming_ssn_reset_request_parameter *reset;
							reset = (struct sctp_incoming_ssn_reset_request_parameter *)parameter;
							if (htons(reset->length) >= 8) {
								reset->reqsn = htonl(ntohl(reset->reqsn) + remote_diff);
							}
							break;
						}
						case SCTP_SSN_TSN_RESET_REQUEST_PARAMETER_TYPE: {
							struct sctp_ssn_tsn_reset_request_parameter *reset;
							reset = (struct sctp_ssn_tsn_reset_request_parameter *)parameter;
							if (htons(reset->length) >= 8) {
								reset->reqsn = htonl(ntohl(reset->reqsn) + remote_diff);
							}
							break;
						}
						case SCTP_RECONFIG_RESPONSE_PARAMETER_TYPE: {
							struct sctp_reconfig_response_parameter *response;
							response = (struct sctp_reconfig_response_parameter *)parameter;
							response->respsn = htonl(htonl(response->respsn) + local_diff);
							if (htons(response->length) >= 16) {
								response->receiver_next_tsn = htonl(htonl(response->receiver_next_tsn) + local_diff);
							}
							if (htons(response->length) >= 20) {
								response->sender_next_tsn = htonl(htonl(response->sender_next_tsn) + remote_diff);
							}
							break;
						}
						case SCTP_ADD_OUTGOING_STREAMS_REQUEST_PARAMETER_TYPE: {
							struct sctp_add_outgoing_streams_request_parameter *request;
							request = (struct sctp_add_outgoing_streams_request_parameter *)parameter;
							if (htons(request->length) >= 8) {
								request->reqsn = htonl(htonl(request->reqsn) + remote_diff);
							}
							break;
						}
						case SCTP_ADD_INCOMING_STREAMS_REQUEST_PARAMETER_TYPE: {
							struct sctp_add_incoming_streams_request_parameter *request;
							request = (struct sctp_add_incoming_streams_request_parameter *)parameter;
							if (htons(request->length) >= 8) {
								request->reqsn = htonl(htonl(request->reqsn) + remote_diff);
							}
							break;
						}
						default:
							//do nothing
							break;
						}
					}
				}
				break;
			default:
				break;
			}
		}
	}
	/* Map the verification tag in the common header */
	DEBUGP("live remote initiate tag 0x%08x, script remote initiate tag 0x%08x\n",
	       socket->live.remote_initiate_tag, socket->script.remote_initiate_tag);
	DEBUGP("live local initiate tag 0x%08x, script local initiate tag 0x%08x\n",
	       socket->live.local_initiate_tag, socket->script.local_initiate_tag);
	if (live_packet->flags & FLAGS_SCTP_EXPLICIT_TAG) {
		v_tag = ntohl(live_packet->sctp->v_tag);
		DEBUGP("verification tag specified in script: 0x%08x\n", v_tag);
		if (v_tag != 0) {
			if (reflect_v_tag) {
				u32 diff;

				diff = v_tag - socket->script.remote_initiate_tag;
				v_tag = socket->live.remote_initiate_tag + diff;
			} else {
				u32 diff;

				diff = v_tag - socket->script.local_initiate_tag;
				v_tag = socket->live.local_initiate_tag + diff;
			}
			if (v_tag == 0) {
				DEBUGP("Need to increment, since it would be zero.\n");
				v_tag = 1;
			}
		}
	} else {
		DEBUGP("verification tag not specified in script.\n")
		if (contains_init_chunk) {
			v_tag = 0;
		} else {
			if (reflect_v_tag) {
				v_tag = socket->live.remote_initiate_tag;
			} else {
				v_tag = socket->live.local_initiate_tag;
			}
		}
	}
	DEBUGP("verification tag of inbound packet: 0x%08x\n", v_tag);
	live_packet->sctp->v_tag = htonl(v_tag);

	return STATUS_OK;
}

/* Rewrite the IP and TCP, UDP, or ICMP fields in 'live_packet', mapping
 * inbound packet values (address 4-tuple and sequence numbers in seq,
 * ACK, SACK blocks) from script values to live values, so that we can
 * inject this packet into the kernel and have the kernel accept it
 * for the given socket and process it. Returns STATUS_OK on success;
 * on failure returns STATUS_ERR and sets error message.
 */
static int map_inbound_packet(
	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, udp_encaps != 0);

	if ((live_packet->icmpv4 != NULL) || (live_packet->icmpv6 != NULL))
		return map_inbound_icmp_packet(socket, live_packet, udp_encaps, error);

	if (live_packet->sctp) {
		return map_inbound_sctp_packet(socket, live_packet, error);
	}

	/* If no TCP headers to rewrite, then we're done. */
	if (live_packet->tcp == NULL)
		return STATUS_OK;

	/* Remap the sequence number from script sequence number to live. */
	const bool is_syn = live_packet->tcp->syn;
	const u32 seq_offset = remote_seq_script_to_live_offset(socket, is_syn);
	if ((live_packet->flags & FLAG_ABSOLUTE_SEQ) == 0) {
		live_packet->tcp->seq =
		    htonl(ntohl(live_packet->tcp->seq) + seq_offset);
	}

	/* Remap the ACK and SACKs from script sequence number to live. */
	const u32 ack_offset = local_seq_script_to_live_offset(socket, is_syn);
	if (live_packet->tcp->ack)
		live_packet->tcp->ack_seq =
			htonl(ntohl(live_packet->tcp->ack_seq) + ack_offset);
	if (offset_sack_blocks(live_packet, ack_offset, error))
		return STATUS_ERR;

	/* Find the timestamp echo reply is, so we can remap that below. */
	if (find_tcp_timestamp(live_packet, error))
		return STATUS_ERR;

	/* If we are using absolute ecr values, do not adjust the ecr. */
	if (live_packet->flags & FLAG_ABSOLUTE_TS_ECR) {
		return STATUS_OK;
	}
	/* Remap TCP timestamp echo reply from script value to a live
	 * value. We say "a" rather than "the" live value because
	 * there could be multiple live values corresponding to the
	 * same script value if a live test replay flips to a new
	 * jiffie in a spot where the script did not.
	 */
	if (live_packet->tcp->ack && (live_packet->tcp_ts_ecr != NULL)) {
		u32 live_ts_ecr = 0;

		if (get_outbound_ts_val_mapping(socket,
						packet_tcp_ts_ecr(live_packet),
						&live_ts_ecr)) {
			asprintf(error,
				 "unable to find mapping for timestamp ecr %u",
				 packet_tcp_ts_ecr(live_packet));
			return STATUS_ERR;
		}
		packet_set_tcp_ts_ecr(live_packet, live_ts_ecr);
	}

	return STATUS_OK;
}

static int map_outbound_live_sctp_packet(
	struct socket *socket,
	struct packet *live_packet,
	struct packet *actual_packet,
	struct packet *script_packet,
	char **error)
{
	struct sctp_chunks_iterator iter;
	struct sctp_chunk *chunk;
	struct _sctp_data_chunk *data;
	struct _sctp_init_chunk *init;
	struct _sctp_init_ack_chunk *init_ack;
	struct _sctp_sack_chunk *sack;
	struct _sctp_nr_sack_chunk *nr_sack;
	struct _sctp_shutdown_chunk *shutdown;
	struct _sctp_ecne_chunk *ecne;
	struct _sctp_cwr_chunk *cwr;
	struct _sctp_i_data_chunk *i_data;
	struct _sctp_reconfig_chunk *reconfig;
	struct _sctp_forward_tsn_chunk *forward_tsn;
	struct _sctp_i_forward_tsn_chunk *i_forward_tsn;
	u32 local_diff, remote_diff;
	u16 nr_gap_blocks, nr_dup_tsns, number_of_nr_gap_blocks, i;

	assert(*error == NULL);
	/* FIXME: transform v-tag in the common header*/
	DEBUGP("map_outbound_live_sctp_packet\n");
	for (chunk = sctp_chunks_begin(actual_packet, &iter, error);
	     chunk != NULL;
	     chunk = sctp_chunks_next(&iter, error)) {
		if (*error != NULL) {
			free(*error);
			asprintf(error, "Partial chunk for outbound packet");;
			return STATUS_ERR;
		}
		local_diff = socket->script.local_initial_tsn - socket->live.local_initial_tsn;
		remote_diff = socket->script.remote_initial_tsn - socket->live.remote_initial_tsn;
		DEBUGP("Chunk type: 0x%02x\n", chunk->type);
		switch (chunk->type) {
		case SCTP_DATA_CHUNK_TYPE:
			data = (struct _sctp_data_chunk *)chunk;
			data->tsn = htonl(ntohl(data->tsn) + local_diff);
			break;
		case SCTP_INIT_CHUNK_TYPE:
			init = (struct _sctp_init_chunk *)chunk;
			init->initial_tsn = htonl(ntohl(init->initial_tsn) + local_diff);
			/* XXX: Does this work in all cases? */
			if (ntohl(init->initiate_tag) == socket->live.local_initiate_tag) {
				init->initiate_tag = htonl(socket->script.local_initiate_tag);
			}
			break;
		case SCTP_INIT_ACK_CHUNK_TYPE:
			init_ack = (struct _sctp_init_ack_chunk *)chunk;
			init_ack->initial_tsn = htonl(ntohl(init_ack->initial_tsn) + local_diff);
			/* XXX: Does this work in all cases? */
			if (ntohl(init_ack->initiate_tag) == socket->live.local_initiate_tag) {
				init_ack->initiate_tag = htonl(socket->script.local_initiate_tag);
			}
			break;
		case SCTP_SACK_CHUNK_TYPE:
			sack = (struct _sctp_sack_chunk *)chunk;
			sack->cum_tsn = htonl(ntohl(sack->cum_tsn) + remote_diff);
			nr_gap_blocks = ntohs(sack->nr_gap_blocks);
			nr_dup_tsns = ntohs(sack->nr_dup_tsns);
			for (i = 0; i < nr_dup_tsns; i++) {
				sack->block[i + nr_gap_blocks].tsn = htonl(ntohl(sack->block[i + nr_gap_blocks].tsn) + remote_diff);
			}
			break;
		case SCTP_NR_SACK_CHUNK_TYPE:
			nr_sack = (struct _sctp_nr_sack_chunk *)chunk;
			nr_sack->cum_tsn = htonl(ntohl(nr_sack->cum_tsn) + remote_diff);
			nr_gap_blocks = ntohs(nr_sack->nr_gap_blocks);
			number_of_nr_gap_blocks = ntohs(nr_sack->nr_of_nr_gap_blocks);
			nr_dup_tsns = ntohs(nr_sack->nr_dup_tsns);
			for (i = 0; i < nr_dup_tsns; i++) {
				u16 offset = nr_gap_blocks + number_of_nr_gap_blocks;
				nr_sack->block[i + offset].tsn = htonl(ntohl(nr_sack->block[i + offset].tsn) + remote_diff);
			}
			break;
		case SCTP_SHUTDOWN_CHUNK_TYPE:
			shutdown = (struct _sctp_shutdown_chunk *)chunk;
			shutdown->cum_tsn = htonl(ntohl(shutdown->cum_tsn) + remote_diff);
			break;
		case SCTP_ECNE_CHUNK_TYPE:
			ecne = (struct _sctp_ecne_chunk *)chunk;
			ecne->lowest_tsn = htonl(ntohl(ecne->lowest_tsn) + remote_diff);
			break;
		case SCTP_CWR_CHUNK_TYPE:
			cwr = (struct _sctp_cwr_chunk *)chunk;
			cwr->lowest_tsn = htonl(ntohl(cwr->lowest_tsn) + remote_diff);
			break;
		case SCTP_I_DATA_CHUNK_TYPE:
			i_data = (struct _sctp_i_data_chunk *)chunk;
			i_data->tsn = htonl(ntohl(i_data->tsn) + local_diff);
			break;
		case SCTP_FORWARD_TSN_CHUNK_TYPE: 
			forward_tsn = (struct _sctp_forward_tsn_chunk *) chunk;
			forward_tsn->cum_tsn = htonl(ntohl(forward_tsn->cum_tsn) + local_diff);
			break;
		case SCTP_I_FORWARD_TSN_CHUNK_TYPE:
			i_forward_tsn = (struct _sctp_i_forward_tsn_chunk *) chunk;
			i_forward_tsn->cum_tsn = htonl(ntohl(i_forward_tsn->cum_tsn) + local_diff);
			break;
		case SCTP_RECONFIG_CHUNK_TYPE:
			reconfig = (struct _sctp_reconfig_chunk *)chunk;
			if (reconfig->length > sizeof(struct _sctp_reconfig_chunk)) {
				struct sctp_parameter *parameter;
				struct sctp_parameters_iterator iter;
				int parameters_length = ntohs(reconfig->length) - sizeof(struct _sctp_reconfig_chunk);
				for (parameter = sctp_parameters_begin(reconfig->parameter,
						parameters_length,
						&iter, error);
					parameter != NULL;
					parameter = sctp_parameters_next(&iter, error)) {
					if (*error != NULL) {
						free(*error);
						asprintf(error, "Partial parameter for outbound packet");;
						return STATUS_ERR;
					}
					switch (htons(parameter->type)) {
					case SCTP_OUTGOING_SSN_RESET_REQUEST_PARAMETER_TYPE: {
						struct sctp_outgoing_ssn_reset_request_parameter *reset;
						reset = (struct sctp_outgoing_ssn_reset_request_parameter *)parameter;
						reset->reqsn = htonl(ntohl(reset->reqsn) + local_diff);
						reset->respsn = htonl(ntohl(reset->respsn) + remote_diff);
						reset->last_tsn = htonl(ntohl(reset->last_tsn) + local_diff);
						break;
					}
					case SCTP_INCOMING_SSN_RESET_REQUEST_PARAMETER_TYPE: {
						struct sctp_incoming_ssn_reset_request_parameter *reset;
						reset = (struct sctp_incoming_ssn_reset_request_parameter *)parameter;
						reset->reqsn = htonl(ntohl(reset->reqsn) + local_diff);
						break;
					}
					case SCTP_SSN_TSN_RESET_REQUEST_PARAMETER_TYPE: {
						struct sctp_ssn_tsn_reset_request_parameter *reset;
						reset = (struct sctp_ssn_tsn_reset_request_parameter *)parameter;
						reset->reqsn = htonl(ntohl(reset->reqsn) + local_diff);
						break;
					}
					case SCTP_RECONFIG_RESPONSE_PARAMETER_TYPE: {
						struct sctp_reconfig_response_parameter *response;
						response = (struct sctp_reconfig_response_parameter *)parameter;
						response->respsn = htonl(htonl(response->respsn) + remote_diff);
						if (htons(response->length) == sizeof(struct sctp_reconfig_response_parameter)) {
							response->receiver_next_tsn = htonl(htonl(response->receiver_next_tsn) + remote_diff);
							response->sender_next_tsn = htonl(htonl(response->sender_next_tsn) + local_diff);
						}
						break;
					}
					case SCTP_ADD_OUTGOING_STREAMS_REQUEST_PARAMETER_TYPE: {
						struct sctp_add_outgoing_streams_request_parameter *request;
						request = (struct sctp_add_outgoing_streams_request_parameter *)parameter;
						request->reqsn = htonl(htonl(request->reqsn) + local_diff);
						break;
					}
					case SCTP_ADD_INCOMING_STREAMS_REQUEST_PARAMETER_TYPE: {
						struct sctp_add_incoming_streams_request_parameter *request;
						request = (struct sctp_add_incoming_streams_request_parameter *)parameter;
						request->reqsn = htonl(htonl(request->reqsn) + local_diff);
						break;
					}
					default:
						//do nothing
						break;
					}
				}
			}
			break;
		default:
			break;
		}
	}
	return STATUS_OK;
}

/* Transforms values in the 'actual_packet' by mapping outbound packet
 * values in the sniffed 'live_packet' (address 4-tuple, sequence
 * number in seq, timestamp value) from live values to script values
 * in the space of 'script_packet'. This will allow us to compare a
 * packet sent by the kernel to the packet expected by the script.
 */
static int map_outbound_live_packet(
	struct socket *socket,
	struct packet *live_packet,
	struct packet *actual_packet,
	struct packet *script_packet,
	u8 udp_encaps,
	char **error)
{
	DEBUGP("map_outbound_live_packet\n");

	struct tuple live_packet_tuple, live_outbound, script_outbound;

	/* Verify packet addresses are outbound and live for this socket. */
	get_packet_tuple(live_packet, &live_packet_tuple);
	socket_get_outbound(&socket->live, &live_outbound);
	assert(is_equal_tuple(&live_packet_tuple, &live_outbound));

	/* Rewrite 4-tuple to be outbound script values. */
	socket_get_outbound(&socket->script, &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);
	}

	/* If no TCP headers to rewrite, then we're done. */
	if (live_packet->tcp == NULL)
		return STATUS_OK;

	/* Rewrite TCP sequence number from live to script space. */
	const bool is_syn = live_packet->tcp->syn;
	const u32 seq_offset = local_seq_live_to_script_offset(socket, is_syn);
	if ((script_packet->flags & FLAG_ABSOLUTE_SEQ) == 0) {
		actual_packet->tcp->seq =
		    htonl(ntohl(live_packet->tcp->seq) + seq_offset);
	} else {
		actual_packet->tcp->seq = live_packet->tcp->seq;
	}
	/* Rewrite ACKs and SACKs from live to script space. */
	const u32 ack_offset = remote_seq_live_to_script_offset(socket, is_syn);
	if (actual_packet->tcp->ack)
		actual_packet->tcp->ack_seq =
		    htonl(ntohl(live_packet->tcp->ack_seq) + ack_offset);
	if (offset_sack_blocks(actual_packet, ack_offset, error))
		return STATUS_ERR;

	/* Extract location of script and actual TCP timestamp values. */
	if (find_tcp_timestamp(script_packet, error))
		return STATUS_ERR;
	if (find_tcp_timestamp(actual_packet, error))
		return STATUS_ERR;
	if ((script_packet->tcp_ts_val != NULL) &&
	    (actual_packet->tcp_ts_val != NULL)) {
		u32 script_ts_val = packet_tcp_ts_val(script_packet);
		u32 actual_ts_val = packet_tcp_ts_val(actual_packet);

		/* Remember script->actual TS val mapping for later. */
		if (!(script_packet->flags & FLAG_IGNORE_TS_VAL)) {
			set_outbound_ts_val_mapping(socket,
						    script_ts_val,
						    actual_ts_val);
		}

		/* Find baseline for socket's live->script TS val mapping. */
		if (!socket->found_first_tcp_ts) {
			socket->found_first_tcp_ts = true;
			socket->first_script_ts_val = script_ts_val;
			socket->first_actual_ts_val = actual_ts_val;
		}

		/* Rewrite TCP timestamp value to script space, so we
		 * can compare the script and actual outbound TCP
		 * timestamp val.
		 */
		packet_set_tcp_ts_val(actual_packet,
				      socket->first_script_ts_val +
				      (actual_ts_val -
				       socket->first_actual_ts_val));
	}

	return STATUS_OK;
}

/* Verify IP and TCP checksums on an outbound live packet. */
static int verify_outbound_live_checksums(struct packet *live_packet,
					  char **error)
{
	/* Verify IP header checksum. */
	if ((live_packet->ipv4 != NULL) &&
	    ipv4_checksum(live_packet->ipv4,
			  ipv4_header_len(live_packet->ipv4))) {
		asprintf(error, "bad outbound IP checksum");
		return STATUS_ERR;
	}

	/* TODO(ncardwell): Verify TCP and UDP checksum. This is a little
	 * subtle, due to TCP checksum offloading.
	 */

	return STATUS_OK;
}

/* Check whether the given field of a packet matches the expected
 * value, and emit a human-readable error message if not.
 */
static int check_field(
	const char *name,	/* human-readable name of the header field */
	u32 expected,		/* value script hopes to see */
	u32 actual,		/* actual value seen during test */
	char **error)		/* human-readable error string on failure */
{
	if (actual != expected) {
		asprintf(error, "live packet field %s: "
			 "expected: %u (0x%x) vs actual: %u (0x%x)",
			 name, expected, expected, actual, actual);
		return STATUS_ERR;
	}
	return STATUS_OK;
}

/* Check whether the given field of a packet matches the expected
 * value, and emit a human-readable error message if not.
 */
static int check_field_u16(
	const char *name,	/* human-readable name of the header field */
	u16 expected,		/* value script hopes to see */
	u16 actual,		/* actual value seen during test */
	char **error)		/* human-readable error string on failure */
{
	if (actual != expected) {
		asprintf(error, "live packet field %s: "
			 "expected: %hu (0x%x) vs actual: %hu (0x%x)",
			 name, expected, expected, actual, actual);
		return STATUS_ERR;
	}
	return STATUS_OK;
}

/* Verify that the actual ECN bits are as the script expected. */
static int verify_outbound_live_ecn(enum ip_ecn_t ecn,
				    u8 actual_ecn_bits,
				    u8 script_ecn_bits,
				    char **error)
{
	if (ecn == ECN_NOCHECK)
		return STATUS_OK;

	if (ecn == ECN_ECT01) {
		if ((actual_ecn_bits != IP_ECN_ECT0) &&
		    (actual_ecn_bits != IP_ECN_ECT1)) {
			asprintf(error, "live packet field ip_ecn: "
				 "expected: 0x1 or 0x2 vs actual: 0x%x",
				 actual_ecn_bits);
			return STATUS_ERR;
		}
	} else if (check_field("ip_ecn",
			       script_ecn_bits,
			       actual_ecn_bits, error)) {
		return STATUS_ERR;
	}

	return STATUS_OK;
}

/* How many bytes should we tack onto the script packet to account for
 * the actual TCP options we did see?
 */
static int tcp_options_allowance(const struct packet *actual_packet,
				 const struct packet *script_packet)
{
	if (script_packet->flags & FLAG_OPTIONS_NOCHECK)
		return packet_tcp_options_len(actual_packet);
	else
		return 0;
}

/* Verify that required actual IPv4 header fields are as the script expected. */
static int verify_ipv4(
	const struct packet *actual_packet,
	const struct packet *script_packet,
	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;

	if (check_field("ipv4_version",
			script_ipv4->version,
			actual_ipv4->version, error) ||
	    check_field("ipv4_protocol",
			script_ipv4->protocol,
			actual_ipv4->protocol, error) ||
	    check_field("ipv4_header_length",
			script_ipv4->ihl,
			actual_ipv4->ihl, error))
		return STATUS_ERR;
	switch (script_ipv4->protocol) {
	case IPPROTO_SCTP:
		/* FIXME */;
		break;
	case IPPROTO_TCP:
		if (check_field("ipv4_total_length",
				(ntohs(script_ipv4->tot_len) +
				 tcp_options_allowance(actual_packet,
						       script_packet)),
				ntohs(actual_ipv4->tot_len), error))
			return STATUS_ERR;
		break;
	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),
				ntohs(actual_ipv4->tot_len), error))
			return STATUS_ERR;
		break;
	}

	if (verify_outbound_live_ecn(script_packet->ecn,
				     ipv4_ecn_bits(actual_ipv4),
				     ipv4_ecn_bits(script_ipv4),
				     error))
		return STATUS_ERR;

	return STATUS_OK;
}

/* Verify that required actual IPv6 header fields are as the script expected. */
static int verify_ipv6(
	const struct packet *actual_packet,
	const struct packet *script_packet,
	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;

	if (check_field("ipv6_version",
			script_ipv6->version,
			actual_ipv6->version, error) ||
	    check_field("ipv6_next_header",
			script_ipv6->next_header,
			actual_ipv6->next_header, error))
		return STATUS_ERR;
	switch (script_ipv6->next_header) {
	case IPPROTO_SCTP:
		/* FIXME */
		break;
	case IPPROTO_TCP:
		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;
	case IPPROTO_UDP:
		if (udp_encaps == IPPROTO_TCP) {
			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;
		} else
			break;
	default:
		if (check_field("ipv6_payload_len",
				ntohs(script_ipv6->payload_len),
				ntohs(actual_ipv6->payload_len), error))
			return STATUS_ERR;
		break;
	}

	if (verify_outbound_live_ecn(script_packet->ecn,
				     ipv6_ecn_bits(actual_ipv6),
				     ipv6_ecn_bits(script_ipv6),
				     error))
		return STATUS_ERR;

	return STATUS_OK;
}

static int verify_sctp_parameters(u8 *begin, u16 length,
                                  struct sctp_chunk_list_item *script_chunk_item,
                                  char **error)
{
	struct sctp_parameters_iterator iter;
	struct sctp_parameter *actual_parameter;
	struct sctp_parameter *script_parameter;
	struct sctp_parameter_list_item *script_parameter_item;
	u32 flags;
	for (actual_parameter = sctp_parameters_begin(begin, length, &iter, error),
	     script_parameter_item = script_chunk_item->parameter_list->first;
	     actual_parameter != NULL && script_parameter_item != NULL;
	     actual_parameter = sctp_parameters_next(&iter, error),
	     script_parameter_item = script_parameter_item->next) {
		if (*error != NULL) {
			free(*error);
			asprintf(error, "Partial parameter for outbound packet");;
			return STATUS_ERR;
		}
		script_parameter = script_parameter_item->parameter;
		flags = script_parameter_item->flags;
		assert(script_parameter != NULL);
		DEBUGP("script parameter: type 0x%04x, length %05d\n",
		       ntohs(script_parameter->type),
		       ntohs(script_parameter->length));
		DEBUGP("actual parameter: type 0x%04x, length %05d\n",
		       ntohs(actual_parameter->type),
		       ntohs(actual_parameter->length));
		DEBUGP("flags: %08x\n", flags);
		if ((flags & FLAG_PARAMETER_TYPE_NOCHECK ? STATUS_OK :
		        check_field("sctp_parameter_type",
		                    ntohs(script_parameter->type),
		                    ntohs(actual_parameter->type),
		                    error)) ||
		    (flags & FLAG_PARAMETER_LENGTH_NOCHECK ? STATUS_OK :
		        check_field("sctp_parameter_length",
		                    ntohs(script_parameter->length),
		                    ntohs(actual_parameter->length),
		                    error))) {
			return STATUS_ERR;
		}
		switch (ntohs(actual_parameter->type)) {
		case SCTP_OUTGOING_SSN_RESET_REQUEST_PARAMETER_TYPE: {
			struct sctp_outgoing_ssn_reset_request_parameter *live_reset, *script_reset;
			int sids_len = 0, i = 0;

			live_reset = (struct sctp_outgoing_ssn_reset_request_parameter *)actual_parameter;
			script_reset = (struct sctp_outgoing_ssn_reset_request_parameter *)script_parameter;
			if ((flags & FLAG_RECONFIG_REQ_SN_NOCHECK ? STATUS_OK :
			    check_field("outgoing_ssn_reset_request_parameter.req_sn",
			                ntohl(script_reset->reqsn),
			                ntohl(live_reset->reqsn),
			                error)) ||
			    (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),
			                error)) ||
			    (flags & FLAG_RECONFIG_LAST_TSN_NOCHECK ? STATUS_OK :
			    check_field("outgoing_ssn_reset_request_parameter.last_tsn",
			                ntohl(script_reset->last_tsn),
			 	        ntohl(live_reset->last_tsn),
				        error))) {
				return STATUS_ERR;
			}
			sids_len = ntohs(script_reset->length) - sizeof(struct sctp_outgoing_ssn_reset_request_parameter);
			for (i = 0; i<(sids_len / sizeof(u16)); i++) {
				if (check_field_u16("outgoing_ssn_reset_request_parameter.sids",
						    ntohs(script_reset->sids[i]),
						    ntohs(live_reset->sids[i]),
						    error)) {
					return STATUS_ERR;
				}
			}
			break;
		}
		case SCTP_INCOMING_SSN_RESET_REQUEST_PARAMETER_TYPE: {
			struct sctp_incoming_ssn_reset_request_parameter *live_reset, *script_reset;
			int sids_len = 0, i = 0;

			live_reset = (struct sctp_incoming_ssn_reset_request_parameter *)actual_parameter;
			script_reset = (struct sctp_incoming_ssn_reset_request_parameter *)script_parameter;
			if ((flags & FLAG_RECONFIG_REQ_SN_NOCHECK ? STATUS_OK :
			    check_field("incoming_ssn_reset_request_parameter.req_sn",
			                ntohl(script_reset->reqsn),
			                ntohl(live_reset->reqsn),
			                error))) {
				return STATUS_ERR;
			}
			sids_len = ntohs(script_reset->length) - sizeof(struct sctp_incoming_ssn_reset_request_parameter);
			for (i = 0; i<(sids_len / sizeof(u16)); i++) {
				if (check_field_u16("incoming_ssn_reset_request_parameter.sids",
						    ntohs(script_reset->sids[i]),
						    ntohs(live_reset->sids[i]),
						    error)) {
					return STATUS_ERR;
				}
			}
			break;
		}
		case SCTP_SSN_TSN_RESET_REQUEST_PARAMETER_TYPE: {
			struct sctp_ssn_tsn_reset_request_parameter *live_reset, *script_reset;

			live_reset = (struct sctp_ssn_tsn_reset_request_parameter *)actual_parameter;
			script_reset = (struct sctp_ssn_tsn_reset_request_parameter *)script_parameter;
			if ((flags & FLAG_RECONFIG_REQ_SN_NOCHECK ? STATUS_OK :
			    check_field("ssn_tsn_reset_request_parameter.req_sn",
			                ntohl(script_reset->reqsn),
			                ntohl(live_reset->reqsn),
			                error))) {
				return STATUS_ERR;
			}
			break;
		}
		case SCTP_RECONFIG_RESPONSE_PARAMETER_TYPE: {
			struct sctp_reconfig_response_parameter *live_resp, *script_resp;

			live_resp = (struct sctp_reconfig_response_parameter *)actual_parameter;
			script_resp = (struct sctp_reconfig_response_parameter *)script_parameter;
			if ((flags & FLAG_RECONFIG_RESP_SN_NOCHECK ? STATUS_OK :
			    check_field("reconfig_response_parameter.resp_sn",
			                ntohl(script_resp->respsn),
			                ntohl(live_resp->respsn),
			                error)) ||
			    (flags & FLAG_RECONFIG_RESULT_NOCHECK ? STATUS_OK :
			    check_field("reconfig_response_parameter.result",
			                ntohl(script_resp->result),
			 	        ntohl(live_resp->result),
				        error))) {
				return STATUS_ERR;
			}
			if (ntohs(live_resp->length) == sizeof(struct sctp_reconfig_response_parameter)) {
				if ((flags & FLAG_RECONFIG_SENDER_NEXT_TSN_NOCHECK ? STATUS_OK :
				    check_field("ssn_tsn_reset_request_parameter.sender_next_tsn",
				                ntohl(script_resp->sender_next_tsn),
				                ntohl(live_resp->sender_next_tsn),
				                error)) ||
				    (flags & FLAG_RECONFIG_RECEIVER_NEXT_TSN_NOCHECK ? STATUS_OK :
				    check_field("sctp_reconfig_response_parameter.receiver_next_tsn",
				                ntohl(script_resp->receiver_next_tsn),
				 	        ntohl(live_resp->receiver_next_tsn),
					        error))) {
					return STATUS_ERR;
				}
			}
			break;
		}
		case SCTP_ADD_OUTGOING_STREAMS_REQUEST_PARAMETER_TYPE: {
			struct sctp_add_outgoing_streams_request_parameter *live_add, *script_add;

			live_add = (struct sctp_add_outgoing_streams_request_parameter *)actual_parameter;
			script_add = (struct sctp_add_outgoing_streams_request_parameter *)script_parameter;
			if ((flags & FLAG_RECONFIG_REQ_SN_NOCHECK ? STATUS_OK :
			    check_field("add_outgoing_streams_request_parameter_parameter.req_sn",
			                ntohl(script_add->reqsn),
			                ntohl(live_add->reqsn),
			                error)) ||
			    (flags & FLAG_RECONFIG_NUMBER_OF_NEW_STREAMS_NOCHECK ? STATUS_OK :
			    check_field_u16("add_outgoing_streams_request_parameter.number_of_new_streams",
			                ntohs(script_add->number_of_new_streams),
			 	        ntohs(live_add->number_of_new_streams),
				        error))) {
				return STATUS_ERR;
			}
			break;
		}
		case SCTP_ADD_INCOMING_STREAMS_REQUEST_PARAMETER_TYPE: {
			struct sctp_add_incoming_streams_request_parameter *live_add, *script_add;

			live_add = (struct sctp_add_incoming_streams_request_parameter *)actual_parameter;
			script_add = (struct sctp_add_incoming_streams_request_parameter *)script_parameter;
			if ((flags & FLAG_RECONFIG_REQ_SN_NOCHECK ? STATUS_OK :
			    check_field("add_incoming_streams_request_parameter.req_sn",
			                ntohl(script_add->reqsn),
			                ntohl(live_add->reqsn),
			                error)) ||
			    (flags & FLAG_RECONFIG_NUMBER_OF_NEW_STREAMS_NOCHECK ? STATUS_OK :
			    check_field_u16("add_incoming_streams_request_parameter.number_of_new_streams",
			                ntohs(script_add->number_of_new_streams),
			 	        ntohs(live_add->number_of_new_streams),
				        error))) {
				return STATUS_ERR;
			}
			break;
		}
		default:
			if ((flags & FLAG_PARAMETER_VALUE_NOCHECK) == 0) {
				assert((flags & FLAG_PARAMETER_LENGTH_NOCHECK) == 0);
				if (memcmp(script_parameter->value,
				           actual_parameter->value,
				           ntohs(actual_parameter->length) - sizeof(struct sctp_parameter))) {
					asprintf(error, "live packet parameter value not as expected");
					return STATUS_ERR;
				}
			}
		}
	}
	if (actual_parameter != NULL) {
		DEBUGP("actual chunk contains more parameters than script chunk\n");
	}
	if (script_parameter_item != NULL) {
		DEBUGP("script chunk contains more parameters than actual chunk\n");
	}

	if ((actual_parameter != NULL) || (script_parameter_item != NULL)) {
		asprintf(error,
		         "live chunk and expected chunk have not the same number of parameters");
		return STATUS_ERR;
	}
	return STATUS_OK;
}

static int verify_sctp_causes(struct sctp_chunk *chunk, u16 offset,
                              struct sctp_chunk_list_item *script_chunk_item,
                              char **error)
{
	struct sctp_causes_iterator iter;
	struct sctp_cause *actual_cause;
	struct sctp_cause *script_cause;
	struct sctp_cause_list_item *script_cause_item;
	u32 flags;

	for (actual_cause = sctp_causes_begin(chunk, offset, &iter, error),
	     script_cause_item = script_chunk_item->cause_list->first;
	     actual_cause != NULL && script_cause_item != NULL;
	     actual_cause = sctp_causes_next(&iter, error),
	     script_cause_item = script_cause_item->next) {
		if (*error != NULL) {
			free(*error);
			asprintf(error, "Partial cause for outbound packet");;
			return STATUS_ERR;
		}
		script_cause = script_cause_item->cause;
		flags = script_cause_item->flags;
		assert(script_cause != NULL);
		DEBUGP("script cause: code 0x%04x, length %05d\n",
		       ntohs(script_cause->code),
		       ntohs(script_cause->length));
		DEBUGP("actual cause: code 0x%04x, length %05d\n",
		       ntohs(actual_cause->code),
		       ntohs(actual_cause->length));
		DEBUGP("flags: %08x\n", flags);
		if ((flags & FLAG_CAUSE_CODE_NOCHECK ? STATUS_OK :
		        check_field("sctp_cause_code",
		                    ntohs(script_cause->code),
		                    ntohs(actual_cause->code),
		                    error)) ||
		    (flags & FLAG_CAUSE_LENGTH_NOCHECK ? STATUS_OK :
		        check_field("sctp_cause_length",
		                    ntohs(script_cause->length),
		                    ntohs(actual_cause->length),
		                    error))) {
			return STATUS_ERR;
		}
		if ((flags & FLAG_CAUSE_INFORMATION_NOCHECK) == 0) {
			assert((flags & FLAG_CAUSE_LENGTH_NOCHECK) == 0);
			if (memcmp(script_cause->information,
			           actual_cause->information,
			           ntohs(actual_cause->length) - sizeof(struct sctp_cause))) {
				asprintf(error, "live packet cause information not as expected");
				return STATUS_ERR;
			}
		}
	}
	if (actual_cause != NULL) {
		DEBUGP("actual chunk contains more causes than script chunk\n");
	}
	if (script_cause_item != NULL) {
		DEBUGP("script chunk contains more causes than actual chunk\n");
	}
	if ((actual_cause != NULL) || (script_cause_item != NULL)) {
		asprintf(error,
		         "live chunk and expected chunk have not the same number of causes");
		return STATUS_ERR;
	}
	return STATUS_OK;
}

static int verify_data_chunk(struct _sctp_data_chunk *actual_chunk,
                             struct _sctp_data_chunk *script_chunk,
                             u32 flags, char **error)
{
	if (check_field("sctp_data_chunk_tsn",
		        ntohl(script_chunk->tsn),
		        ntohl(actual_chunk->tsn),
		        error) ||
	    (flags & FLAG_DATA_CHUNK_SID_NOCHECK ? STATUS_OK :
	        check_field("sctp_data_chunk_sid",
		            ntohs(script_chunk->sid),
		            ntohs(actual_chunk->sid),
		            error)) ||
	    (flags & FLAG_DATA_CHUNK_SSN_NOCHECK? STATUS_OK :
		check_field("sctp_data_chunk_ssn",
		            ntohs(script_chunk->ssn),
		            ntohs(actual_chunk->ssn),
		            error)) ||
	    (flags & FLAG_DATA_CHUNK_PPID_NOCHECK? STATUS_OK :
		check_field("sctp_data_chunk_ppid",
		            ntohl(script_chunk->ppid),
		            ntohl(actual_chunk->ppid),
		            error))) {
		return STATUS_ERR;
	}
	return STATUS_OK;
}

static int verify_init_chunk(struct _sctp_init_chunk *actual_chunk,
                             struct sctp_chunk_list_item *script_chunk_item,
                             char **error)
{
	struct _sctp_init_chunk *script_chunk;
	u32 flags;
	u16 parameters_length;

	script_chunk = (struct _sctp_init_chunk *)script_chunk_item->chunk;
	flags = script_chunk_item->flags;
	assert(ntohs(actual_chunk->length) >= sizeof(struct _sctp_init_chunk));
	parameters_length = ntohs(actual_chunk->length) - sizeof(struct _sctp_init_chunk);
	if ((flags & FLAG_INIT_CHUNK_TAG_NOCHECK ? STATUS_OK :
	        check_field("sctp_init_chunk_tag",
		            ntohl(script_chunk->initiate_tag),
		            ntohl(actual_chunk->initiate_tag),
		            error)) ||
	    (flags & FLAG_INIT_CHUNK_A_RWND_NOCHECK ? STATUS_OK :
	        check_field("sctp_init_chunk_a_rwnd",
		            ntohl(script_chunk->a_rwnd),
		            ntohl(actual_chunk->a_rwnd),
		            error)) ||
	    (flags & FLAG_INIT_CHUNK_OS_NOCHECK? STATUS_OK :
		check_field("sctp_init_chunk_os",
		            ntohs(script_chunk->os),
		            ntohs(actual_chunk->os),
		            error)) ||
	    (flags & FLAG_INIT_CHUNK_IS_NOCHECK? STATUS_OK :
		check_field("sctp_init_chunk_is",
		            ntohs(script_chunk->is),
		            ntohs(actual_chunk->is),
		            error)) ||
	    (flags & FLAG_INIT_CHUNK_TSN_NOCHECK? STATUS_OK :
		check_field("sctp_init_chunk_tsn",
		            ntohl(script_chunk->initial_tsn),
		            ntohl(actual_chunk->initial_tsn),
		            error)) ||
	    (flags & FLAG_INIT_CHUNK_OPT_PARAM_NOCHECK? STATUS_OK :
	        verify_sctp_parameters(actual_chunk->parameter,
	                               parameters_length,
	                               script_chunk_item,
	                               error))) {
		return STATUS_ERR;
	}
	return STATUS_OK;
}

static int verify_init_ack_chunk(struct _sctp_init_ack_chunk *actual_chunk,
                                 struct sctp_chunk_list_item *script_chunk_item,
                                 char **error)
{
	struct _sctp_init_ack_chunk *script_chunk;
	u32 flags;
	u16 parameters_length;

	script_chunk = (struct _sctp_init_ack_chunk *)script_chunk_item->chunk;
	flags = script_chunk_item->flags;
	assert(ntohs(actual_chunk->length) >= sizeof(struct _sctp_init_ack_chunk));
	parameters_length = ntohs(actual_chunk->length) - sizeof(struct _sctp_init_ack_chunk);
	if ((flags & FLAG_INIT_ACK_CHUNK_TAG_NOCHECK ? STATUS_OK :
	        check_field("sctp_init_ack_chunk_tag",
		            ntohl(script_chunk->initiate_tag),
		            ntohl(actual_chunk->initiate_tag),
		            error)) ||
	    (flags & FLAG_INIT_ACK_CHUNK_A_RWND_NOCHECK ? STATUS_OK :
	        check_field("sctp_init_ack_chunk_a_rwnd",
		            ntohl(script_chunk->a_rwnd),
		            ntohl(actual_chunk->a_rwnd),
		            error)) ||
	    (flags & FLAG_INIT_ACK_CHUNK_OS_NOCHECK? STATUS_OK :
		check_field("sctp_init_ack_chunk_os",
		            ntohs(script_chunk->os),
		            ntohs(actual_chunk->os),
		            error)) ||
	    (flags & FLAG_INIT_ACK_CHUNK_IS_NOCHECK? STATUS_OK :
		check_field("sctp_init_ack_chunk_is",
		            ntohs(script_chunk->is),
		            ntohs(actual_chunk->is),
		            error)) ||
	    (flags & FLAG_INIT_ACK_CHUNK_TSN_NOCHECK? STATUS_OK :
		check_field("sctp_init_ack_chunk_tsn",
		            ntohl(script_chunk->initial_tsn),
		            ntohl(actual_chunk->initial_tsn),
		            error)) ||
	    (flags & FLAG_INIT_ACK_CHUNK_OPT_PARAM_NOCHECK? STATUS_OK :
		verify_sctp_parameters(actual_chunk->parameter,
		                       parameters_length,
		                       script_chunk_item,
		                       error))) {
		return STATUS_ERR;
	}
	return STATUS_OK;
}

static int verify_sack_chunk(struct _sctp_sack_chunk *actual_chunk,
                             struct _sctp_sack_chunk *script_chunk,
                             u32 flags, char **error)
{
	u16 actual_nr_gap_blocks, actual_nr_dup_tsns;
	u16 script_nr_gap_blocks, script_nr_dup_tsns;
	u16 i, actual_base, script_base;

	actual_nr_gap_blocks = ntohs(actual_chunk->nr_gap_blocks);
	actual_nr_dup_tsns = ntohs(actual_chunk->nr_dup_tsns);
	script_nr_gap_blocks = ntohs(script_chunk->nr_gap_blocks);
	script_nr_dup_tsns = ntohs(script_chunk->nr_dup_tsns);

	if ((flags & FLAG_SACK_CHUNK_CUM_TSN_NOCHECK ? STATUS_OK :
	        check_field("sctp_sack_chunk_cum_tsn",
		            ntohl(script_chunk->cum_tsn),
		            ntohl(actual_chunk->cum_tsn),
		            error)) ||
	    (flags & FLAG_SACK_CHUNK_A_RWND_NOCHECK ? STATUS_OK :
	        check_field("sctp_sack_chunk_a_rwnd",
		            ntohl(script_chunk->a_rwnd),
		            ntohl(actual_chunk->a_rwnd),
		            error)) ||
	    (flags & FLAG_SACK_CHUNK_GAP_BLOCKS_NOCHECK? STATUS_OK :
		check_field("sctp_sack_chunk_nr_gap_blocks",
		            script_nr_gap_blocks,
		            actual_nr_gap_blocks,
		            error)) ||
	    (flags & FLAG_SACK_CHUNK_DUP_TSNS_NOCHECK? STATUS_OK :
		check_field("sctp_sack_chunk_nr_dup_tsns",
		            script_nr_dup_tsns,
		            actual_nr_dup_tsns,
		            error))) {
		return STATUS_ERR;
	}

	if ((flags & FLAG_SACK_CHUNK_GAP_BLOCKS_NOCHECK) == 0) {
		for (i = 0; i < script_nr_gap_blocks; i++) {
			if (check_field("sctp_sack_chunk_gap_block_start",
		                        ntohs(script_chunk->block[i].gap.start),
		                        ntohs(actual_chunk->block[i].gap.start),
		                        error) ||
		            check_field("sctp_sack_chunk_gap_block_end",
		                        ntohs(script_chunk->block[i].gap.end),
		                        ntohs(actual_chunk->block[i].gap.end),
		                        error)) {
				return STATUS_ERR;
			}
		}
	}
	if ((flags & FLAG_SACK_CHUNK_DUP_TSNS_NOCHECK) == 0) {
		actual_base = actual_nr_gap_blocks;
		if ((flags & FLAG_SACK_CHUNK_GAP_BLOCKS_NOCHECK) == 0) {
			script_base = actual_nr_gap_blocks;
		} else {
			script_base = 0;
		}
		for (i = 0; i < script_nr_dup_tsns; i++) {
			if (check_field("sctp_sack_chunk_dup_tsn",
		                        ntohl(script_chunk->block[script_base + i].tsn),
		                        ntohl(actual_chunk->block[actual_base + i].tsn),
		                        error)) {
				return STATUS_ERR;
			}
		}
	}
	return STATUS_OK;
}

static int verify_nr_sack_chunk(struct _sctp_nr_sack_chunk *actual_chunk,
                                struct _sctp_nr_sack_chunk *script_chunk,
                                u32 flags, char **error)
{
	u16 actual_nr_gap_blocks, actual_nr_of_nr_gap_blocks, actual_nr_dup_tsns;
	u16 script_nr_gap_blocks, script_nr_of_nr_gap_blocks, script_nr_dup_tsns;
	u16 i, actual_base, script_base;

	actual_nr_gap_blocks = ntohs(actual_chunk->nr_gap_blocks);
	actual_nr_of_nr_gap_blocks = ntohs(actual_chunk->nr_of_nr_gap_blocks);
	actual_nr_dup_tsns = ntohs(actual_chunk->nr_dup_tsns);
	script_nr_gap_blocks = ntohs(script_chunk->nr_gap_blocks);
	script_nr_of_nr_gap_blocks = ntohs(script_chunk->nr_of_nr_gap_blocks);
	script_nr_dup_tsns = ntohs(script_chunk->nr_dup_tsns);
	
	script_base = 0;

	if ((flags & FLAG_NR_SACK_CHUNK_CUM_TSN_NOCHECK ? STATUS_OK :
	        check_field("sctp_nr_sack_chunk_cum_tsn",
		            ntohl(script_chunk->cum_tsn),
		            ntohl(actual_chunk->cum_tsn),
		            error)) ||
	    (flags & FLAG_NR_SACK_CHUNK_A_RWND_NOCHECK ? STATUS_OK :
	        check_field("sctp_nr_sack_chunk_a_rwnd",
		            ntohl(script_chunk->a_rwnd),
		            ntohl(actual_chunk->a_rwnd),
		            error)) ||
	    (flags & FLAG_NR_SACK_CHUNK_GAP_BLOCKS_NOCHECK? STATUS_OK :
		check_field("sctp_nr_sack_chunk_nr_gap_blocks",
		            script_nr_gap_blocks,
		            actual_nr_gap_blocks,
		            error)) ||
	    (flags & FLAG_NR_SACK_CHUNK_NR_GAP_BLOCKS_NOCHECK? STATUS_OK :
		check_field("sctp_nr_sack_chunk_nr_of_nr_gap_blocks",
		            script_nr_of_nr_gap_blocks,
		            actual_nr_of_nr_gap_blocks,
		            error)) ||
	    (flags & FLAG_NR_SACK_CHUNK_DUP_TSNS_NOCHECK? STATUS_OK :
		check_field("sctp_nr_sack_chunk_nr_dup_tsns",
		            script_nr_dup_tsns,
		            actual_nr_dup_tsns,
		            error))) {
		return STATUS_ERR;
	}

	if ((flags & FLAG_NR_SACK_CHUNK_GAP_BLOCKS_NOCHECK) == 0) {
		for (i = 0; i < script_nr_gap_blocks; i++) {
			if (check_field("sctp_nr_sack_chunk_gap_block_start",
		                        ntohs(script_chunk->block[i].gap.start),
		                        ntohs(actual_chunk->block[i].gap.start),
		                        error) ||
		            check_field("sctp_nr_sack_chunk_gap_block_end",
		                        ntohs(script_chunk->block[i].gap.end),
		                        ntohs(actual_chunk->block[i].gap.end),
		                        error)) {
				return STATUS_ERR;
			}
		}
		script_base += actual_nr_gap_blocks;
	}
	
	if ((flags & FLAG_NR_SACK_CHUNK_NR_GAP_BLOCKS_NOCHECK) == 0) {
		actual_base = actual_nr_gap_blocks;
		for (i = 0; i < script_nr_of_nr_gap_blocks; i++) {
			if (check_field("sctp_nr_sack_chunk_nr_gap_block_start",
		                        ntohs(script_chunk->block[script_base + i].gap.start),
		                        ntohs(actual_chunk->block[actual_base + i].gap.start),
		                        error) ||
		            check_field("sctp_nr_sack_chunk_nr_gap_block_end",
		                        ntohs(script_chunk->block[script_base + i].gap.end),
		                        ntohs(actual_chunk->block[actual_base + i].gap.end),
		                        error)) {
				return STATUS_ERR;
			}
		}
		script_base += script_nr_of_nr_gap_blocks;
	}
	
	
	if ((flags & FLAG_NR_SACK_CHUNK_DUP_TSNS_NOCHECK) == 0) {
		actual_base = actual_nr_gap_blocks + actual_nr_of_nr_gap_blocks;
		for (i = 0; i < script_nr_dup_tsns; i++) {
			if (check_field("sctp_nr_sack_chunk_dup_tsn",
		                        ntohl(script_chunk->block[script_base + i].tsn),
		                        ntohl(actual_chunk->block[actual_base + i].tsn),
		                        error)) {
				return STATUS_ERR;
			}
		}
	}
	return STATUS_OK;
}

static int verify_heartbeat_chunk(struct _sctp_heartbeat_chunk *actual_chunk,
                                  struct _sctp_heartbeat_chunk *script_chunk,
                                  u32 flags, char **error)
{
	u16 length;

	if (flags & FLAG_CHUNK_VALUE_NOCHECK) {
		return STATUS_OK;
	} else {
		assert((flags & FLAG_CHUNK_LENGTH_NOCHECK) == 0);
		length = ntohs(actual_chunk->length);
		assert(length >= sizeof(struct _sctp_heartbeat_chunk));
		if (memcmp(actual_chunk->value,
		           script_chunk->value,
		           length - sizeof(struct _sctp_heartbeat_chunk)) == 0) {
		        return STATUS_OK;
		} else {
			asprintf(error, "live packet heartbeat info not as expected");
			return STATUS_ERR;
		}
	}
}

static int verify_heartbeat_ack_chunk(struct _sctp_heartbeat_ack_chunk *actual_chunk,
                                      struct _sctp_heartbeat_ack_chunk *script_chunk,
                                      u32 flags, char **error)
{
	u16 length;

	if (flags & FLAG_CHUNK_VALUE_NOCHECK) {
		return STATUS_OK;
	} else {
		assert((flags & FLAG_CHUNK_LENGTH_NOCHECK) == 0);
		length = ntohs(actual_chunk->length);
		assert(length >= sizeof(struct _sctp_heartbeat_ack_chunk));
		if (memcmp(actual_chunk->value,
		           script_chunk->value,
		           length - sizeof(struct _sctp_heartbeat_ack_chunk)) == 0) {
		        return STATUS_OK;
		} else {
			asprintf(error, "live packet heartbeat info not as expected");
			return STATUS_ERR;
		}
	}
}

static int verify_abort_chunk(struct _sctp_abort_chunk *actual_chunk,
                              struct sctp_chunk_list_item *script_chunk_item,
                              char **error)
{
	u32 flags;

	assert(ntohs(actual_chunk->length) >= sizeof(struct _sctp_abort_chunk));
	flags = script_chunk_item->flags;
	return (flags & FLAG_ABORT_CHUNK_OPT_CAUSES_NOCHECK ? STATUS_OK :
	    verify_sctp_causes((struct sctp_chunk *)actual_chunk,
	                       sizeof(struct _sctp_error_chunk),
		               script_chunk_item, error));
}

static int verify_shutdown_chunk(struct _sctp_shutdown_chunk *actual_chunk,
                                 struct _sctp_shutdown_chunk *script_chunk,
                                 u32 flags, char **error)
{
	return (flags & FLAG_SHUTDOWN_CHUNK_CUM_TSN_NOCHECK) ? STATUS_OK :
	        check_field("sctp_shutdown_chunk_cum_tsn",
		            ntohl(script_chunk->cum_tsn),
		            ntohl(actual_chunk->cum_tsn),
		            error);
}

static int verify_shutdown_ack_chunk(struct _sctp_shutdown_ack_chunk *actual_chunk,
                                     struct _sctp_shutdown_ack_chunk *script_chunk,
                                     u32 flags, char **error)
{
	/* Nothing to check */
	return STATUS_OK;
}

static int verify_error_chunk(struct _sctp_error_chunk *actual_chunk,
                              struct sctp_chunk_list_item *script_chunk_item,
                              char **error)
{
	u32 flags;

	assert(ntohs(actual_chunk->length) >= sizeof(struct _sctp_error_chunk));
	flags = script_chunk_item->flags;
	return (flags & FLAG_ERROR_CHUNK_OPT_CAUSES_NOCHECK ? STATUS_OK :
	    verify_sctp_causes((struct sctp_chunk *)actual_chunk,
	                       sizeof(struct _sctp_error_chunk),
		               script_chunk_item, error));
}

static int verify_cookie_echo_chunk(struct _sctp_cookie_echo_chunk *actual_chunk,
                                    struct _sctp_cookie_echo_chunk *script_chunk,
                                    u32 flags, char **error)
{
	u16 length;

	if (flags & FLAG_CHUNK_VALUE_NOCHECK) {
		return STATUS_OK;
	} else {
		assert((flags & FLAG_CHUNK_LENGTH_NOCHECK) == 0);
		length = ntohs(actual_chunk->length);
		assert(length >= sizeof(struct _sctp_cookie_echo_chunk));
		if (memcmp(actual_chunk->cookie,
		           script_chunk->cookie,
		           length - sizeof(struct _sctp_cookie_echo_chunk)) == 0) {
		        return STATUS_OK;
		} else {
			return STATUS_ERR;
		}
	}
}

static int verify_cookie_ack_chunk(struct _sctp_cookie_ack_chunk *actual_chunk,
                                   struct _sctp_cookie_ack_chunk *script_chunk,
                                   u32 flags, char **error)
{
	/* Nothing to check */
	return STATUS_OK;
}

static int verify_ecne_chunk(struct _sctp_ecne_chunk *actual_chunk,
                             struct _sctp_ecne_chunk *script_chunk,
                             u32 flags, char **error)
{
	return (flags & FLAG_ECNE_CHUNK_LOWEST_TSN_NOCHECK ? STATUS_OK :
	        check_field("sctp_ecne_chunk_lowest_tsn",
		            ntohl(script_chunk->lowest_tsn),
		            ntohl(actual_chunk->lowest_tsn),
		            error));
}

static int verify_cwr_chunk(struct _sctp_cwr_chunk *actual_chunk,
                            struct _sctp_cwr_chunk *script_chunk,
                            u32 flags, char **error)
{
	return (flags & FLAG_CWR_CHUNK_LOWEST_TSN_NOCHECK ? STATUS_OK :
	        check_field("sctp_cwr_chunk_lowest_tsn",
		            ntohl(script_chunk->lowest_tsn),
		            ntohl(actual_chunk->lowest_tsn),
		            error));
}

static int verify_shutdown_complete_chunk(struct _sctp_shutdown_complete_chunk *actual_chunk,
                                          struct _sctp_shutdown_complete_chunk *script_chunk,
                                          u32 flags, char **error)
{
	/* Nothing to check */
	return STATUS_OK;
}

static int verify_i_data_chunk(struct _sctp_i_data_chunk *actual_chunk,
                               struct _sctp_i_data_chunk *script_chunk,
                               u32 flags, char **error)
{
	if (check_field("sctp_i_data_chunk_tsn",
		        ntohl(script_chunk->tsn),
		        ntohl(actual_chunk->tsn),
		        error) ||
	    (flags & FLAG_I_DATA_CHUNK_SID_NOCHECK ? STATUS_OK :
	        check_field("sctp_i_data_chunk_sid",
		            ntohs(script_chunk->sid),
		            ntohs(actual_chunk->sid),
		            error)) ||
	    (flags & FLAG_I_DATA_CHUNK_RES_NOCHECK ? STATUS_OK :
	        check_field("sctp_i_data_chunk_res",
		            ntohs(script_chunk->res),
		            ntohs(actual_chunk->res),
		            error)) ||
	    (flags & FLAG_I_DATA_CHUNK_MID_NOCHECK? STATUS_OK :
		check_field("sctp_i_data_chunk_mid",
		            ntohl(script_chunk->mid),
		            ntohl(actual_chunk->mid),
		            error)) ||
	    (flags & FLAG_I_DATA_CHUNK_PPID_NOCHECK? STATUS_OK :
		check_field("sctp_i_data_chunk_ppid",
		            ntohl(script_chunk->field.ppid),
		            ntohl(actual_chunk->field.ppid),
		            error)) ||
	    (flags & FLAG_I_DATA_CHUNK_FSN_NOCHECK? STATUS_OK :
		check_field("sctp_i_data_chunk_fsn",
		            ntohl(script_chunk->field.fsn),
		            ntohl(actual_chunk->field.fsn),
		            error))) {
		return STATUS_ERR;
	}
	return STATUS_OK;
}

static int verify_pad_chunk(struct _sctp_pad_chunk *actual_chunk,
                            struct _sctp_pad_chunk *script_chunk,
                            u32 flags, char **error)
{
	/* Nothing to check */
	return STATUS_OK;
}

static int verify_reconfig_chunk(struct _sctp_reconfig_chunk *actual_chunk,
				 struct sctp_chunk_list_item *script_chunk_item,
				 u32 flags, char **error)
{
	struct _sctp_init_chunk *script_chunk;
	int parameter_length;
	
	script_chunk = (struct _sctp_init_chunk *)script_chunk_item->chunk;
	parameter_length = ntohs(actual_chunk->length) - sizeof(struct _sctp_reconfig_chunk);
	if ((flags & FLAG_CHUNK_FLAGS_NOCHECK ? STATUS_OK :
			check_field("sctp_reconfig_flags",
		        ntohl(script_chunk->flags),
		        ntohl(actual_chunk->flags),
		        error))) {
		return STATUS_ERR;
	}
	//TODO: check Parameter
	return verify_sctp_parameters(actual_chunk->parameter,
				      parameter_length,
				      script_chunk_item,
				      error);
}

static u16 get_num_id_blocks (u16 packet_length) {
	return (packet_length - sizeof(struct _sctp_forward_tsn_chunk)) / sizeof(struct sctp_stream_identifier_block);
}

static int verify_forward_tsn_chunk(struct _sctp_forward_tsn_chunk *actual_chunk,
				    struct _sctp_forward_tsn_chunk *script_chunk,
				    u32 flags, char **error) {
	u16 actual_packet_length = ntohs(script_chunk->length);
	u16 script_packet_length = ntohs(script_chunk->length);
	u16 actual_nr_id_blocks = get_num_id_blocks(actual_packet_length);
	u16 script_nr_id_blocks = get_num_id_blocks(script_packet_length);
	u16 i;
	
	if ((flags & FLAG_FORWARD_TSN_CHUNK_CUM_TSN_NOCHECK) == 0) {
		if (check_field("sctp_forward_tsn_cum_tsn",
				 ntohl(script_chunk->cum_tsn),
				 ntohl(actual_chunk->cum_tsn),
				 error) == STATUS_ERR) {
			return STATUS_ERR;
		}
	}
	
	if ((flags & FLAG_FORWARD_TSN_CHUNK_IDS_NOCHECK) == 0) {
		if (check_field("nr_sid_blocks",
				 actual_nr_id_blocks,
				 script_nr_id_blocks,
				 error) == STATUS_ERR) {
			return STATUS_ERR;
		}
		
		for (i = 0; i < script_nr_id_blocks; i++) {
			if (check_field("sctp_forward_tsn_stream_identifier",
		                        ntohs(script_chunk->stream_identifier_blocks[i].stream),
		                        ntohs(actual_chunk->stream_identifier_blocks[i].stream),
		                        error) == STATUS_ERR ||
		            check_field("sctp_forward_tsn_stream_sequence_number",
		                        ntohs(script_chunk->stream_identifier_blocks[i].stream_sequence),
		                        ntohs(actual_chunk->stream_identifier_blocks[i].stream_sequence),
		                        error) == STATUS_ERR) {
				return STATUS_ERR;
			}
		}
	}
	
	return STATUS_OK;
}

static u16 get_num_id_blocks_for_i_forward_tsn (u16 packet_length) {
	return (packet_length - sizeof(struct _sctp_i_forward_tsn_chunk)) / sizeof(struct sctp_i_forward_tsn_identifier_block);
}

static int verify_i_forward_tsn_chunk(struct _sctp_i_forward_tsn_chunk *actual_chunk,
				      struct _sctp_i_forward_tsn_chunk *script_chunk,
				      u32 flags, char **error) {
	u16 actual_packet_length = ntohs(script_chunk->length);
	u16 script_packet_length = ntohs(script_chunk->length);
	u16 actual_nr_id_blocks = get_num_id_blocks_for_i_forward_tsn(actual_packet_length);
	u16 script_nr_id_blocks = get_num_id_blocks_for_i_forward_tsn(script_packet_length);
	u16 i;
	
	if ((flags & FLAG_I_FORWARD_TSN_CHUNK_CUM_TSN_NOCHECK) == 0) {
		if (check_field("sctp_i_forward_tsn_cum_tsn",
				 ntohl(script_chunk->cum_tsn),
				 ntohl(actual_chunk->cum_tsn),
				 error) == STATUS_ERR) {
			return STATUS_ERR;
		}
	}
	
	if ((flags & FLAG_I_FORWARD_TSN_CHUNK_IDS_NOCHECK) == 0) {
		if (check_field("nr_id_blocks",
				 actual_nr_id_blocks,
				 script_nr_id_blocks,
				 error) == STATUS_ERR) {
			return STATUS_ERR;
		}
		
		for (i = 0; i < script_nr_id_blocks; i++) {
			if (check_field("sctp_i_forward_tsn_stream_identifier",
		                        ntohs(script_chunk->stream_identifier_blocks[i].stream_identifier),
		                        ntohs(actual_chunk->stream_identifier_blocks[i].stream_identifier),
		                        error) == STATUS_ERR ||
		            check_field("sctp_i_forward_tsn_u_bit",
		                        ntohs(script_chunk->stream_identifier_blocks[i].reserved),
		                        ntohs(actual_chunk->stream_identifier_blocks[i].reserved),
		                        error) == STATUS_ERR ||
		           check_field("sctp_i_forward_tsn_message_identifier",
		                        ntohl(script_chunk->stream_identifier_blocks[i].message_identifier),
		                        ntohl(actual_chunk->stream_identifier_blocks[i].message_identifier),
		                        error) == STATUS_ERR) {
				return STATUS_ERR;
			}
		}
	}
	
	return STATUS_OK;
}

/* Verify that required actual SCTP packet fields are as the script expected. */
static int verify_sctp(
	const struct packet *actual_packet,
	const struct packet *script_packet,
	int layer, u8 udp_encaps, char **error)
{
	struct sctp_chunks_iterator iter;
	struct sctp_chunk *actual_chunk;
	struct sctp_chunk *script_chunk;
	struct sctp_chunk_list_item *script_chunk_item;
	u32 flags;
	int result;

	DEBUGP("Verifying SCTP packet\n")
	DEBUGP("script packet: src port %05u, dst port %05u, v-tag 0x%08x\n",
	       ntohs(script_packet->sctp->src_port),
	       ntohs(script_packet->sctp->dst_port),
	       ntohl(script_packet->sctp->v_tag));
	DEBUGP("actual packet: src port %05u, dst port %05u, v-tag 0x%08x\n",
	       ntohs(actual_packet->sctp->src_port),
	       ntohs(actual_packet->sctp->dst_port),
	       ntohl(actual_packet->sctp->v_tag));
	for (actual_chunk = sctp_chunks_begin((struct packet *)actual_packet, &iter, error),
	     script_chunk_item = script_packet->chunk_list->first;
	     actual_chunk != NULL && script_chunk_item != NULL;
	     actual_chunk = sctp_chunks_next(&iter, error),
	     script_chunk_item = script_chunk_item->next) {
		if (*error != NULL) {
			free(*error);
			asprintf(error, "Partial chunk for outbound packet");;
			return STATUS_ERR;
		}
		script_chunk = script_chunk_item->chunk;
		flags = script_chunk_item->flags;
		assert(script_chunk != NULL);
		DEBUGP("script chunk: type %02d, flags 0x%02x, length %04d\n",
		       script_chunk->type,
		       script_chunk->flags,
		       ntohs(script_chunk->length));
		DEBUGP("actual chunk: type %02d, flags 0x%02x, length %04d\n",
		       actual_chunk->type,
		       actual_chunk->flags,
		       ntohs(actual_chunk->length));
		if (check_field("sctp_chunk_type",
		                script_chunk->type,
		                actual_chunk->type,
		                error) ||
		    (flags & FLAG_CHUNK_FLAGS_NOCHECK ? STATUS_OK :
		        check_field("sctp_chunk_flags",
		                    script_chunk->flags,
		                    actual_chunk->flags,
		                    error)) ||
		    (flags & FLAG_CHUNK_LENGTH_NOCHECK ? STATUS_OK :
		        check_field("sctp_chunk_length",
		                    ntohs(script_chunk->length),
		                    ntohs(actual_chunk->length),
		                    error))) {
			return STATUS_ERR;
		}
		switch (actual_chunk->type) {
		case SCTP_DATA_CHUNK_TYPE:
			result = verify_data_chunk((struct _sctp_data_chunk *)actual_chunk,
			                           (struct _sctp_data_chunk *)script_chunk,
			                           flags, error);
			break;
		case SCTP_INIT_CHUNK_TYPE:
			result = verify_init_chunk((struct _sctp_init_chunk *)actual_chunk,
			                           script_chunk_item, error);
			break;
		case SCTP_INIT_ACK_CHUNK_TYPE:
			result = verify_init_ack_chunk((struct _sctp_init_ack_chunk *)actual_chunk,
			                               script_chunk_item, error);
			break;
		case SCTP_SACK_CHUNK_TYPE:
			result = verify_sack_chunk((struct _sctp_sack_chunk *)actual_chunk,
			                           (struct _sctp_sack_chunk *)script_chunk,
			                           flags, error);
			break;
		case SCTP_NR_SACK_CHUNK_TYPE:
			result = verify_nr_sack_chunk((struct _sctp_nr_sack_chunk *)actual_chunk,
			                              (struct _sctp_nr_sack_chunk *)script_chunk,
			                              flags, error);
			break;
		case SCTP_HEARTBEAT_CHUNK_TYPE:
			result = verify_heartbeat_chunk((struct _sctp_heartbeat_chunk *)actual_chunk,
			                                (struct _sctp_heartbeat_chunk *)script_chunk,
			                                flags, error);
			break;
		case SCTP_HEARTBEAT_ACK_CHUNK_TYPE:
			result = verify_heartbeat_ack_chunk((struct _sctp_heartbeat_ack_chunk *)actual_chunk,
			                                    (struct _sctp_heartbeat_ack_chunk *)script_chunk,
			                                    flags, error);
			break;
		case SCTP_ABORT_CHUNK_TYPE:
			result = verify_abort_chunk((struct _sctp_abort_chunk *)actual_chunk,
			                            script_chunk_item, error);
			break;
		case SCTP_SHUTDOWN_CHUNK_TYPE:
			result = verify_shutdown_chunk((struct _sctp_shutdown_chunk *)actual_chunk,
			                               (struct _sctp_shutdown_chunk *)script_chunk,
			                               flags, error);
			break;
		case SCTP_SHUTDOWN_ACK_CHUNK_TYPE:
			result = verify_shutdown_ack_chunk((struct _sctp_shutdown_ack_chunk *)actual_chunk,
			                                   (struct _sctp_shutdown_ack_chunk *)script_chunk,
			                                   flags, error);
			break;
		case SCTP_ERROR_CHUNK_TYPE:
			result = verify_error_chunk((struct _sctp_error_chunk *)actual_chunk,
			                            script_chunk_item, error);
			break;
		case SCTP_COOKIE_ECHO_CHUNK_TYPE:
			result = verify_cookie_echo_chunk((struct _sctp_cookie_echo_chunk *)actual_chunk,
			                                  (struct _sctp_cookie_echo_chunk *)script_chunk,
			                                  flags, error);
			break;
		case SCTP_COOKIE_ACK_CHUNK_TYPE:
			result = verify_cookie_ack_chunk((struct _sctp_cookie_ack_chunk *)actual_chunk,
			                                 (struct _sctp_cookie_ack_chunk *)script_chunk,
			                                 flags, error);
			break;
		case SCTP_ECNE_CHUNK_TYPE:
			result = verify_ecne_chunk((struct _sctp_ecne_chunk *)actual_chunk,
			                           (struct _sctp_ecne_chunk *)script_chunk,
			                           flags, error);
			break;
		case SCTP_CWR_CHUNK_TYPE:
			result = verify_cwr_chunk((struct _sctp_cwr_chunk *)actual_chunk,
			                          (struct _sctp_cwr_chunk *)script_chunk,
			                          flags, error);
			break;
		case SCTP_SHUTDOWN_COMPLETE_CHUNK_TYPE:
			result = verify_shutdown_complete_chunk((struct _sctp_shutdown_complete_chunk *)actual_chunk,
			                                        (struct _sctp_shutdown_complete_chunk *)script_chunk,
			                                        flags, error);
			break;
		case SCTP_I_DATA_CHUNK_TYPE:
			result = verify_i_data_chunk((struct _sctp_i_data_chunk *)actual_chunk,
			                             (struct _sctp_i_data_chunk *)script_chunk,
			                             flags, error);
			break;
		case SCTP_PAD_CHUNK_TYPE:
			result = verify_pad_chunk((struct _sctp_pad_chunk *)actual_chunk,
			                          (struct _sctp_pad_chunk *)script_chunk,
			                          flags, error);
			break;
		case SCTP_RECONFIG_CHUNK_TYPE:
			result = verify_reconfig_chunk((struct _sctp_reconfig_chunk *)actual_chunk,
			                               script_chunk_item,
			                               flags, error);
			break;
		case SCTP_FORWARD_TSN_CHUNK_TYPE:
			result = verify_forward_tsn_chunk((struct _sctp_forward_tsn_chunk *)actual_chunk,
			                                  (struct _sctp_forward_tsn_chunk *)script_chunk,
			                                  flags, error);
			break;
		case SCTP_I_FORWARD_TSN_CHUNK_TYPE:
			result = verify_i_forward_tsn_chunk((struct _sctp_i_forward_tsn_chunk *)actual_chunk,
			                                    (struct _sctp_i_forward_tsn_chunk *)script_chunk,
			                                    flags, error);
			break;
		default:
			result = STATUS_ERR;
			assert(!"unsupported SCTP chunk type");
			break;
		}
		if (result == STATUS_ERR) {
			return STATUS_ERR;
		}
	}
	if (actual_chunk != NULL) {
		DEBUGP("actual packet contains more chunks than script packet\n");
	}
	if (script_chunk_item != NULL) {
		DEBUGP("script packet contains more chunks than actual packet\n");
	}
	if ((actual_chunk != NULL) || (script_chunk_item != NULL)) {
		asprintf(error,
		         "live packet and expected packet have not the same number of chunks");
		return STATUS_ERR;
	}
	return STATUS_OK;
}

/* Verify that required actual TCP header fields are as the script expected. */
static int verify_tcp(
	const struct packet *actual_packet,
	const struct packet *script_packet,
	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,
					       script_packet)/sizeof(u32)),
			actual_tcp->doff, error) ||
	    check_field("tcp_fin",
			script_tcp->fin,
			actual_tcp->fin, error) ||
	    check_field("tcp_syn",
			script_tcp->syn,
			actual_tcp->syn, error) ||
	    check_field("tcp_rst",
			script_tcp->rst,
			actual_tcp->rst, error) ||
	    check_field("tcp_psh",
			script_tcp->psh,
			actual_tcp->psh, error) ||
	    check_field("tcp_ack",
			script_tcp->ack,
			actual_tcp->ack, error) ||
	    check_field("tcp_urg",
			script_tcp->urg,
			actual_tcp->urg, error) ||
	    check_field("tcp_ece",
			script_tcp->ece,
			actual_tcp->ece, error) ||
	    check_field("tcp_cwr",
			script_tcp->cwr,
			actual_tcp->cwr, error) ||
	    check_field("tcp_reserved_bits",
			script_tcp->res1,
			actual_tcp->res1, error) ||
	    (script_packet->flags & FLAG_IGNORE_SEQ ? STATUS_OK :
		check_field("tcp_seq",
			    ntohl(script_tcp->seq),
			    ntohl(actual_tcp->seq), error)) ||
	    check_field("tcp_ack_seq",
			ntohl(script_tcp->ack_seq),
			ntohl(actual_tcp->ack_seq), error) ||
	    (script_packet->flags & FLAG_WIN_NOCHECK ? STATUS_OK :
		check_field("tcp_window",
			    ntohs(script_tcp->window),
			    ntohs(actual_tcp->window), error))  ||
	    check_field("tcp_urg_ptr",
			ntohs(script_tcp->urg_ptr),
			ntohs(actual_tcp->urg_ptr), error))
		return STATUS_ERR;

	return STATUS_OK;
}

/* Verify that required actual UDP header fields are as the script expected. */
static int verify_udp(
	const struct packet *actual_packet,
	const struct packet *script_packet,
	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))
		return STATUS_ERR;
	return STATUS_OK;
}

/* Verify that required actual UDPLite header fields are as the script
   expected. */
static int verify_udplite(
	const struct packet *actual_packet,
	const struct packet *script_packet,
	int layer, u8 udp_encaps, char **error)
{
	const struct udplite *actual_udplite =
	    actual_packet->headers[layer].h.udplite;
	const struct udplite *script_udplite =
	    script_packet->headers[layer].h.udplite;
	if (check_field("udplite_cov",
			ntohs(script_udplite->cov),
			ntohs(actual_udplite->cov), error))
		return STATUS_ERR;
	return STATUS_OK;
}

/* Verify that required actual GRE header fields are as the script expected. */
static int verify_gre(
	const struct packet *actual_packet,
	const struct packet *script_packet,
	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;

	/* TODO(ncardwell) check all fields of GRE header */
	if (check_field("gre_len",
			gre_len(script_gre),
			gre_len(actual_gre), error))
		return STATUS_ERR;
	return STATUS_OK;
}

/* Verify that required actual MPLS header fields are as the script expected. */
static int verify_mpls(
	const struct packet *actual_packet,
	const struct packet *script_packet,
	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];
	const struct mpls *actual_mpls = actual_packet->headers[layer].h.mpls;
	const struct mpls *script_mpls = script_packet->headers[layer].h.mpls;
	int num_entries = script_header->header_bytes / sizeof(struct mpls);
	int i = 0;

	if (script_header->header_bytes != actual_header->header_bytes) {
		asprintf(error, "mismatch in MPLS label stack depth");
		return STATUS_ERR;
	}

	for (i = 0; i < num_entries; ++i) {
		const struct mpls *actual_entry = actual_mpls + i;
		const struct mpls *script_entry = script_mpls + i;
		if (memcmp(actual_entry, script_entry, sizeof(*script_entry))) {
			asprintf(error, "mismatch in MPLS label %d", i);
			return STATUS_ERR;
		}
	}

	return STATUS_OK;
}

typedef int (*verifier_func)(
	const struct packet *actual_packet,
	const struct packet *script_packet,
	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, u8 udp_encaps, char **error)
{
	verifier_func verifiers[HEADER_NUM_TYPES] = {
		[HEADER_IPV4]		= verify_ipv4,
		[HEADER_IPV6]		= verify_ipv6,
		[HEADER_GRE]		= verify_gre,
		[HEADER_MPLS]		= verify_mpls,
		[HEADER_SCTP]		= verify_sctp,
		[HEADER_TCP]		= verify_tcp,
		[HEADER_UDP]		= verify_udp,
		[HEADER_UDPLITE]	= verify_udplite,
	};
	verifier_func verifier = NULL;
	const struct header *actual_header = &actual_packet->headers[layer];
	const struct header *script_header = &script_packet->headers[layer];
	enum header_t type = script_header->type;

	if (script_header->type != actual_header->type) {
		asprintf(error, "live packet header layer %d: "
			 "expected: %s header vs actual: %s header",
			 layer,
			 header_type_info(script_header->type)->name,
			 header_type_info(actual_header->type)->name);
		return STATUS_ERR;
	}

	assert(type > HEADER_NONE);
	assert(type < HEADER_NUM_TYPES);
	verifier = verifiers[type];
	assert(verifier != NULL);
	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, u8 udp_encaps, char **error)
{
	const int actual_headers = packet_header_count(actual_packet);
	const int script_headers = packet_header_count(script_packet);
	int i;

	DEBUGP("verify_outbound_live_headers\n");

	assert((actual_packet->ipv4 != NULL) || (actual_packet->ipv6 != NULL));
	assert((actual_packet->sctp != NULL) ||
	       (actual_packet->tcp != NULL) ||
	       (actual_packet->udp != NULL) ||
	       (actual_packet->udplite != NULL));

	if (actual_headers != script_headers) {
		asprintf(error, "live packet header layers: "
			 "expected: %d headers vs actual: %d headers",
			 script_headers, actual_headers);
		return STATUS_ERR;
	}

	/* Compare actual vs script headers, layer by layer. */
	for (i = 0; i < ARRAY_SIZE(script_packet->headers); ++i) {
		if (script_packet->headers[i].type == HEADER_NONE)
			break;

		if (verify_header(actual_packet, script_packet, i,
				  udp_encaps, error))
			return STATUS_ERR;
	}

	return STATUS_OK;
}

/* Return true iff the TCP options for the packets are bytewise identical. */
static bool same_tcp_options(struct packet *packet_a,
			     struct packet *packet_b)
{
	return ((packet_tcp_options_len(packet_a) ==
		 packet_tcp_options_len(packet_b)) &&
		(memcmp(packet_tcp_options(packet_a),
			packet_tcp_options(packet_b),
			packet_tcp_options_len(packet_a)) == 0));
}

/* Verify that the TCP option values matched expected values. */
static int verify_outbound_live_tcp_options(
	struct config *config,
	struct packet *actual_packet,
	struct packet *script_packet, char **error)
{
	/* See if we should validate TCP options at all. */
	if (script_packet->flags & FLAG_OPTIONS_NOCHECK)
		return STATUS_OK;

	/* Simplest case: see if full options are bytewise identical. */
	if (same_tcp_options(actual_packet, script_packet))
		return STATUS_OK;

	/* Otherwise, see if we just have a slight difference in TS val. */
	if (script_packet->tcp_ts_val != NULL &&
	    actual_packet->tcp_ts_val != NULL) {
		u32 script_ts_val = packet_tcp_ts_val(script_packet);
		u32 actual_ts_val = packet_tcp_ts_val(actual_packet);

		/* See if the deviation from the script TS val is
		 * within our configured tolerance.
		 */
		if (config->tcp_ts_tick_usecs &&
		    ((abs((s32)(actual_ts_val - script_ts_val)) *
		      config->tcp_ts_tick_usecs) >
		     config->tolerance_usecs)) {
			asprintf(error, "bad outbound TCP timestamp value");
			return STATUS_ERR;
		}

		/* Now see if the rest of the TCP options outside the
		 * TS val match: temporarily re-write the actual TS
		 * val to the script TS val and then see if the full
		 * options are now bytewise identical.
		 */
		packet_set_tcp_ts_val(actual_packet, script_ts_val);
		bool is_same = same_tcp_options(actual_packet, script_packet);
		packet_set_tcp_ts_val(actual_packet, actual_ts_val);
		if (is_same)
			return STATUS_OK;
	}

	asprintf(error, "bad outbound TCP options");
	return STATUS_ERR;	/* The TCP options did not match */
}


/* Verify TCP/UDP payload matches expected value. */
static int verify_outbound_live_payload(
	struct packet *actual_packet,
	struct packet *script_packet, char **error)
{
	if (actual_packet->sctp != NULL)
		return STATUS_OK;
	/* Diff the TCP/UDP data payloads. We've already implicitly
	 * checked their length by checking the IP and TCP/UDP headers.
	 */
	assert(packet_payload_len(actual_packet) ==
	       packet_payload_len(script_packet));
	if (memcmp(packet_payload(script_packet),
		   packet_payload(actual_packet),
		   packet_payload_len(script_packet)) != 0) {
		asprintf(error, "incorrect outbound data payload");
		return STATUS_ERR;
	}
	return STATUS_OK;
}

/* Verify that the outbound packet correctly matches the expected
 * outbound packet from the script.
 * Return STATUS_OK upon success.  If non_fatal_packet is unset in the
 * config, return STATUS_ERR upon all failures.  With non_fatal_packet,
 * return STATUS_WARN upon non-fatal failures.
 */
static int verify_outbound_live_packet(
	struct state *state, struct socket *socket,
	struct packet *script_packet, struct packet *live_packet,
	char **error)
{
	DEBUGP("verify_outbound_live_packet\n");

	int result = STATUS_ERR;	/* return value */
	bool non_fatal = false;		/* ok to continue on error? */
	enum event_time_t time_type = state->event->time_type;
	s64 script_usecs = state->event->time_usecs;
	s64 script_usecs_end = state->event->time_usecs_end;

	/* The "actual" packet will be the live packet with values
	 * mapped into script space.
	 */
	struct packet *actual_packet = packet_copy(live_packet);
	s64 actual_usecs = live_time_to_script_time_usecs(
		state, live_packet->time_usecs);

	/* Before mapping, see if the live outgoing checksums are correct. */
	if (verify_outbound_live_checksums(live_packet, error))
		goto out;

	/* Map live packet values into script space for easy comparison. */
	if (map_outbound_live_packet(
		    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,
					 state->config->udp_encaps, error)) {
		non_fatal = true;
		goto out;
	}

	if (script_packet->tcp) {
		/* Verify TCP options matched expected values. */
		if (verify_outbound_live_tcp_options(
			    state->config, actual_packet, script_packet,
			    error)) {
			non_fatal = true;
			goto out;
		}
	}

	/* Verify TCP/UDP payload matches expected value. */
	if (verify_outbound_live_payload(actual_packet, script_packet, error)) {
		non_fatal = true;
		goto out;
	}

	/* Verify that kernel sent packet at the time the script expected. */
	DEBUGP("packet time_usecs: %lld\n", live_packet->time_usecs);
	if (verify_time(state, time_type, script_usecs,
				script_usecs_end, live_packet->time_usecs,
				"outbound packet", error)) {
		non_fatal = true;
		goto out;
	}

	result = STATUS_OK;

out:
	add_packet_dump(error, "script", script_packet, script_usecs,
			DUMP_SHORT);
	if (actual_packet != NULL) {
		add_packet_dump(error, "actual", actual_packet,
				actual_usecs, DUMP_SHORT);
		packet_free(actual_packet);
	}
	if (result == STATUS_ERR &&
	    non_fatal &&
	    state->config->non_fatal_packet) {
		result = STATUS_WARN;
	}
	return result;
}

/* Sniff the next outbound live packet and return it. */
static int sniff_outbound_live_packet(
	struct state *state, struct socket *expected_socket,
	struct packet **packet, char **error)
{
	DEBUGP("sniff_outbound_live_packet\n");
	struct socket *socket = NULL;
	enum direction_t direction = DIRECTION_INVALID;
	assert(*packet == NULL);
	while (1) {
		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,
						     &direction);
		if ((socket != NULL) && (direction == DIRECTION_OUTBOUND))
			break;
		/* See if the packet matches a recent connect() call. */
		socket = find_connect_for_live_packet(state, *packet,
						      &direction);
		if ((socket != NULL) && (direction == DIRECTION_OUTBOUND))
			break;
		packet_free(*packet);
		*packet = NULL;
	}

	assert(*packet != NULL);
	assert(socket != NULL);
	assert(direction == DIRECTION_OUTBOUND);

	if (socket != expected_socket) {
		asprintf(error, "packet is not for expected socket");
		return STATUS_ERR;
	}
	return STATUS_OK;
}

/* Return true iff the given packet could be sent/received by the socket. */
static bool is_script_packet_match_for_socket(
	struct state *state, struct packet *packet, struct socket *socket)
{
	const bool is_packet_icmp = (packet->icmpv4 || packet->icmpv6);

	if (socket->protocol == IPPROTO_SCTP)
		return packet->sctp || is_packet_icmp;
	else if (socket->protocol == IPPROTO_TCP)
		return packet->tcp || is_packet_icmp;
	else if (socket->protocol == IPPROTO_UDP)
		return packet->udp || is_packet_icmp;
	else if (socket->protocol == IPPROTO_UDPLITE)
		return packet->udplite || is_packet_icmp;
	else
		assert(!"unsupported layer 4 protocol in socket");
	return false;
}

/* Find or create a socket object matching the given packet. */
static int find_or_create_socket_for_script_packet(
	struct state *state, struct packet *packet,
	enum direction_t direction, struct socket **socket,
	char **error)
{
	*socket = NULL;

	DEBUGP("find_or_create_socket_for_script_packet\n");

	if ((packet->tcp != NULL) || (packet->sctp != NULL)) {
		/* Is this an inbound packet matching a listening
		 * socket? If so, this call will create a new child
		 * socket object.
		 */
		*socket = handle_listen_for_script_packet(state,
							  packet, direction);
		if (*socket != NULL)
			return STATUS_OK;

		/* Is this an outbound packet matching a connecting socket? */
		*socket = handle_connect_for_script_packet(state,
							   packet, direction);
		if (*socket != NULL)
			return STATUS_OK;
	}
	/* See if there is an existing connection to handle this packet. */
	if (state->socket_under_test != NULL &&
	    is_script_packet_match_for_socket(state, packet,
					      state->socket_under_test)) {
		*socket = state->socket_under_test;
		return STATUS_OK;
	}

	asprintf(error, "no matching socket for script packet");
	return STATUS_ERR;
}

/* Perform the action implied by an outbound packet in a script
 * Return STATUS_OK upon success.  Without --use_expect, return STATUS_ERR
 * upon all failures.  With --use_expect, return STATUS_WARN upon non-fatal
 * failures.
 */
static int do_outbound_script_packet(
	struct state *state, struct packet *packet,
	struct socket *socket,	char **error)
{
	struct sctp_chunk_list_item *item;
	struct sctp_chunks_iterator chunk_iter;
	struct sctp_parameters_iterator param_iter;
	struct sctp_chunk *chunk;
	struct _sctp_init_ack_chunk *init_ack;
	struct _sctp_cookie_echo_chunk *cookie_echo;
	struct _sctp_heartbeat_chunk *heartbeat;
	struct _sctp_heartbeat_ack_chunk *heartbeat_ack;
	struct sctp_parameter *parameter;
	struct sctp_state_cookie_parameter *state_cookie;
	int result = STATUS_ERR;		/* return value */
	struct packet *live_packet = NULL;
	u16 cookie_length, chunk_length, parameter_length, parameters_length;
	u16 value_length, padding_length;

	DEBUGP("do_outbound_script_packet\n");
	if ((packet->icmpv4 != NULL) || (packet->icmpv6 != NULL)) {
		asprintf(error, "outbound ICMP packets are not supported");
		goto out;
	}

	if (socket->state == SOCKET_PASSIVE_PACKET_RECEIVED) {
		if (packet->tcp && packet->tcp->syn && packet->tcp->ack && !(packet->flags & FLAG_IGNORE_SEQ)) {
			/* Script says we should see an outbound server SYNACK. */
			socket->script.local_isn = ntohl(packet->tcp->seq);
			DEBUGP("SYNACK script.local_isn: %u\n",
			       socket->script.local_isn);
		}
		if (packet->sctp) {
			assert(packet->chunk_list != NULL);
			item = packet->chunk_list->first;
			if ((item != NULL) &&
			    (item->chunk->type == SCTP_INIT_ACK_CHUNK_TYPE)) {
				init_ack = (struct _sctp_init_ack_chunk *)item->chunk;
				socket->script.local_initiate_tag = ntohl(init_ack->initiate_tag);
				socket->script.local_initial_tsn = ntohl(init_ack->initial_tsn);
				DEBUGP("INIT_ACK: script.local_initiate_tag: %u\n",
				       socket->script.local_initiate_tag);
				DEBUGP("INIT_ACK: script.local_initial_tsn: %u\n",
				       socket->script.local_initial_tsn);
			}
		}
	}

	/* Sniff outbound live packet and verify it's for the right socket. */
	if (sniff_outbound_live_packet(state, socket, &live_packet, error))
		goto out;

	if (packet->tcp) {
		if ((socket->state == SOCKET_PASSIVE_PACKET_RECEIVED) &&
		    packet->tcp->syn && packet->tcp->ack) {
			socket->state = SOCKET_PASSIVE_SYNACK_SENT;
			socket->live.local_isn = ntohl(live_packet->tcp->seq);
			DEBUGP("SYNACK live.local_isn: %u\n",
			       socket->live.local_isn);
		}
	}
	if (live_packet->sctp) {
		for (chunk = sctp_chunks_begin(live_packet, &chunk_iter, error);
		     chunk != NULL;
		     chunk = sctp_chunks_next(&chunk_iter, error)) {
			if (*error != NULL) {
				free(*error);
				asprintf(error, "Partial chunk for outbound packet");;
				goto out;
			}
			if ((socket->state == SOCKET_PASSIVE_PACKET_RECEIVED) &&
			    (chunk->type == SCTP_INIT_ACK_CHUNK_TYPE)) {
				chunk_length = ntohs(chunk->length);
				if (chunk_length < sizeof(struct _sctp_init_ack_chunk)) {
					asprintf(error, "INIT chunk too short (length=%u)", chunk_length);
					goto out;
				}
				parameters_length = chunk_length - sizeof(struct _sctp_init_chunk);
				init_ack = (struct _sctp_init_ack_chunk *)chunk;

				for (parameter = sctp_parameters_begin(init_ack->parameter,
				                                       parameters_length,
				                                       &param_iter, error);
				     parameter != NULL;
				     parameter = sctp_parameters_next(&param_iter, error)) {
					if (*error != NULL) {
						free(*error);
						asprintf(error, "Partial parameter for outbound packet");;
						goto out;
					}
					if (ntohs(parameter->type) == SCTP_STATE_COOKIE_PARAMETER_TYPE) {
						state_cookie = (struct sctp_state_cookie_parameter *)parameter;
						parameter_length = ntohs(state_cookie->length);
						if (parameter_length < sizeof(struct sctp_state_cookie_parameter)) {
							asprintf(error, "State Cookie parameter too short (length=%u)", parameter_length);
							goto out;
						}
						cookie_length = parameter_length - 4;
						padding_length = cookie_length % 4;
						if (padding_length > 0) {
							padding_length = 4 - padding_length;
						}
						chunk_length = sizeof(struct _sctp_cookie_echo_chunk) + cookie_length;
						cookie_echo = (struct _sctp_cookie_echo_chunk *)malloc(chunk_length + padding_length);
						cookie_echo->type = SCTP_COOKIE_ECHO_CHUNK_TYPE;
						cookie_echo->flags = 0;
						cookie_echo->length = htons(chunk_length);
						memcpy(cookie_echo->cookie, state_cookie->cookie, cookie_length);
						memset(cookie_echo->cookie + cookie_length, 0, padding_length);
						if (socket->prepared_cookie_echo != NULL) {
							 /* paranoia to help catch bugs */
							memset(socket->prepared_cookie_echo,
							       0,
							       socket->prepared_cookie_echo_length);
							free(socket->prepared_cookie_echo);
							socket->prepared_cookie_echo = NULL;
							socket->prepared_cookie_echo_length = 0;
						}
						socket->prepared_cookie_echo = cookie_echo;
						socket->prepared_cookie_echo_length = chunk_length + padding_length;
						DEBUGP("COOKIE_ECHO of length %u prepared\n",
						       chunk_length);
						break;
					}
				}
				socket->live.local_initiate_tag = ntohl(init_ack->initiate_tag);
				socket->live.local_initial_tsn = ntohl(init_ack->initial_tsn);
				socket->state = SOCKET_PASSIVE_INIT_ACK_SENT;
				DEBUGP("INIT_ACK: live.local_initiate_tag: %u\n",
				       socket->live.local_initiate_tag);
				DEBUGP("INIT_ACK: live.local_initial_tsn: %u\n",
				       socket->live.local_initial_tsn);
			}
			if (chunk->type == SCTP_HEARTBEAT_CHUNK_TYPE) {
				heartbeat = (struct _sctp_heartbeat_chunk *)chunk;
				chunk_length = ntohs(heartbeat->length);
				if (chunk_length < sizeof(struct _sctp_heartbeat_chunk)) {
					asprintf(error, "HEARTBEAT chunk too short (length=%u)", chunk_length);
					goto out;
				}
				value_length = chunk_length - sizeof(struct _sctp_heartbeat_chunk);
				padding_length = chunk_length % 4;
				if (padding_length > 0) {
					padding_length = 4 - padding_length;
				}
				heartbeat_ack = (struct _sctp_heartbeat_ack_chunk *)malloc(chunk_length + padding_length);
				heartbeat_ack->type = SCTP_HEARTBEAT_ACK_CHUNK_TYPE;
				heartbeat_ack->flags = 0;
				heartbeat_ack->length = htons(chunk_length);
				memcpy(heartbeat_ack->value, heartbeat->value, value_length);
				memset(heartbeat_ack->value + value_length, 0, padding_length);
				if (socket->prepared_heartbeat_ack != NULL) {
					 /* paranoia to help catch bugs */
					memset(socket->prepared_heartbeat_ack,
					       0,
					       socket->prepared_heartbeat_ack_length);
					free(socket->prepared_heartbeat_ack);
					socket->prepared_heartbeat_ack = NULL;
					socket->prepared_heartbeat_ack_length = 0;
				}
				socket->prepared_heartbeat_ack = heartbeat_ack;
				socket->prepared_heartbeat_ack_length = chunk_length + padding_length;
				DEBUGP("HEARTBEAT-ACK of length %u prepeared\n",
				       chunk_length);
			}
		}
	}

	verbose_packet_dump(state, "outbound sniffed", live_packet,
			    live_time_to_script_time_usecs(
				    state, live_packet->time_usecs));

	/* Save the TCP header so we can reset the connection at the end. */
	if (live_packet->tcp) {
		socket->last_outbound_tcp_header = *(live_packet->tcp);
		if (live_packet->flags & FLAGS_UDP_ENCAPSULATED) {
			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 (live_packet->flags & FLAGS_UDP_ENCAPSULATED) {
			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(
			state, socket, packet, live_packet, error);

out:
	if (live_packet != NULL)
		packet_free(live_packet);
	return result;
}

/* Checksum the packet and inject it into the kernel under test. */
static int send_live_ip_packet(struct netdev *netdev,
			       struct packet *packet)
{
	assert(packet->ip_bytes > 0);
	/* We do IPv4 and IPv6 */
	assert(packet->ipv4 || packet->ipv6);
	/* We only do TCP, UDP, UDPLite and ICMP */
	assert(packet->sctp || packet->tcp || packet->udp || packet->udplite ||
	       packet->icmpv4 || packet->icmpv6);

	/* Fill in layer 3 and layer 4 checksums */
	checksum_packet(packet);

	return netdev_send(netdev, packet);
}

/* Perform the action implied by an inbound packet in a script */
static int do_inbound_script_packet(
	struct state *state, struct packet *packet,
	struct socket *socket,	char **error)
{
	struct _sctp_init_ack_chunk *init_ack;
	struct sctp_chunk_list_item *item;
	int result = STATUS_ERR;	/* return value */
	u16 offset = 0, temp_offset;
	u16 i;

	DEBUGP("do_inbound_script_packet\n");
	if (packet->tcp) {
		if ((socket->state == SOCKET_PASSIVE_SYNACK_SENT) &&
		    packet->tcp->ack) {
			/* Received the ACK that completes the 3-way handshake. */
			socket->state = SOCKET_PASSIVE_SYNACK_ACKED;
		} else if ((socket->state == SOCKET_ACTIVE_SYN_SENT) &&
		           packet->tcp->syn && packet->tcp->ack) {
			/* Received the server's SYNACK, which ACKs our SYN. */
			socket->state = SOCKET_ACTIVE_SYN_ACKED;
			socket->script.remote_isn = ntohl(packet->tcp->seq);
			socket->live.remote_isn = ntohl(packet->tcp->seq);
		}
	}
	if (packet->sctp) {
		if (packet->chunk_list != NULL) {
			for (item = packet->chunk_list->first;
			     item != NULL;
			     item = item->next) {
				switch (item->chunk->type) {
				case SCTP_INIT_ACK_CHUNK_TYPE:
					if (socket->state == SOCKET_ACTIVE_INIT_SENT) {
						init_ack = (struct _sctp_init_ack_chunk *)item->chunk;
						DEBUGP("Moving socket in SOCKET_ACTIVE_INIT_ACK_RECEIVED\n");
						socket->state = SOCKET_ACTIVE_INIT_ACK_RECEIVED;
						socket->script.remote_initiate_tag = ntohl(init_ack->initiate_tag);
						socket->script.remote_initial_tsn = ntohl(init_ack->initial_tsn);
						socket->live.remote_initiate_tag = ntohl(init_ack->initiate_tag);
						socket->live.remote_initial_tsn = ntohl(init_ack->initial_tsn);
						DEBUGP("remote_initiate_tag 0x%08x, remote_initial_tsn 0x%08x\n", ntohl(init_ack->initiate_tag), ntohl(init_ack->initial_tsn));
					}
					break;
				case SCTP_COOKIE_ECHO_CHUNK_TYPE:
					if (item->flags & FLAG_CHUNK_VALUE_NOCHECK) {
						temp_offset = socket->prepared_cookie_echo_length - item->length;
						assert(packet->ip_bytes + temp_offset <= packet->buffer_bytes);
						memmove((u8 *)item->chunk + item->length + temp_offset,
							(u8 *)item->chunk + item->length,
							packet_end(packet) - ((u8 *)item->chunk + item->length));
						memcpy(item->chunk,
						       socket->prepared_cookie_echo,
						       socket->prepared_cookie_echo_length);
						item->length = socket->prepared_cookie_echo_length;
						packet->buffer_bytes += temp_offset;
						packet->ip_bytes += temp_offset;
						if (packet->ipv4) {
							packet->ipv4->tot_len = htons(ntohs(packet->ipv4->tot_len) + temp_offset);
						}
						if (packet->ipv6) {
							packet->ipv6->payload_len = htons(ntohs(packet->ipv6->payload_len) + temp_offset);
						}
						for (i = 0; i < PACKET_MAX_HEADERS; i++) {
							if ((packet->ipv4 != NULL && packet->headers[i].h.ipv4 == packet->ipv4) ||
							    (packet->ipv6 != NULL && packet->headers[i].h.ipv6 == packet->ipv6)) {
								break;
							}
						}
						if (packet->flags & FLAGS_UDP_ENCAPSULATED) {
							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) &&
					    (((packet->flags & FLAGS_SCTP_EXPLICIT_TAG) == 0) ||
					     ((ntohl(packet->sctp->v_tag) == socket->script.local_initiate_tag) &&
					      (socket->script.local_initiate_tag != 0)))) {
						socket->state = SOCKET_PASSIVE_COOKIE_ECHO_RECEIVED;
					}
					break;
				case SCTP_HEARTBEAT_ACK_CHUNK_TYPE:
					if (item->flags & FLAG_CHUNK_VALUE_NOCHECK) {
						temp_offset = socket->prepared_heartbeat_ack_length - item->length;
						assert(packet->ip_bytes + temp_offset <= packet->buffer_bytes);
						memmove((u8 *)item->chunk + item->length + temp_offset,
							(u8 *)item->chunk + item->length,
							packet_end(packet) - ((u8 *)item->chunk + item->length));
						memcpy(item->chunk,
						       socket->prepared_heartbeat_ack,
						       socket->prepared_heartbeat_ack_length);
						item->length = socket->prepared_heartbeat_ack_length;
						packet->buffer_bytes += temp_offset;
						packet->ip_bytes += temp_offset;
						if (packet->ipv4) {
							packet->ipv4->tot_len = htons(ntohs(packet->ipv4->tot_len) + temp_offset);
						}
						if (packet->ipv6) {
							packet->ipv6->payload_len = htons(ntohs(packet->ipv6->payload_len) + temp_offset);
						}
						for (i = 0; i < PACKET_MAX_HEADERS; i++) {
							if ((packet->ipv4 != NULL && packet->headers[i].h.ipv4 == packet->ipv4) ||
							    (packet->ipv6 != NULL && packet->headers[i].h.ipv6 == packet->ipv6)) {
								break;
							}
						}
						if (packet->flags & FLAGS_UDP_ENCAPSULATED) {
							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;
				default:
					item->chunk = (struct sctp_chunk *)((char *)item->chunk + offset);
					break;
				}
			}
		}
	}

	/* 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, state->config->udp_encaps,
			       error))
		goto out;

	verbose_packet_dump(state, "inbound injected", live_packet,
			    live_time_to_script_time_usecs(
				    state, now_usecs()));

	if (live_packet->tcp) {
		/* Save the TCP header so we can reset the connection later. */
		socket->last_injected_tcp_header = *(live_packet->tcp);
		socket->last_injected_tcp_payload_len =
			packet_payload_len(live_packet);
		if (live_packet->flags & FLAGS_UDP_ENCAPSULATED) {
			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 (live_packet->flags & FLAGS_UDP_ENCAPSULATED) {
			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) &&
	     (IN6_IS_ADDR_UNSPECIFIED(&live_packet->ipv6->src_ip)))) {
			struct tuple live_inbound;

			DEBUGP("live_packet has wildcard source address.\n");
			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, state->config->udp_encaps != 0);
	}

	/* Inject live packet into kernel. */
	result = send_live_ip_packet(state->netdev, live_packet);

out:
	packet_free(live_packet);
	return result;
}

int run_packet_event(
	struct state *state, struct event *event, struct packet *packet,
	char **error)
{
	DEBUGP("%d: packet\n", event->line_number);

	char *err = NULL;
	struct socket *socket = NULL;
	int result = STATUS_ERR;

	enum direction_t direction = packet_direction(packet);
	assert(direction != DIRECTION_INVALID);

	if (find_or_create_socket_for_script_packet(
		    state, packet, direction, &socket, &err))
		goto out;

	assert(socket != NULL);

	if (direction == DIRECTION_OUTBOUND) {
		/* We don't wait for outbound event packets because we
		 * want to start sniffing ASAP in order to see if
		 * packets go out earlier than the script specifies.
		 */
		result = do_outbound_script_packet(state, packet, socket, &err);
		if (result == STATUS_WARN)
			goto out;
		else if (result == STATUS_ERR)
			goto out;
	} else if (direction == DIRECTION_INBOUND) {
		wait_for_event(state);
		if (do_inbound_script_packet(state, packet, socket, &err))
			goto out;
	} else {
		assert(!"bad direction");  /* internal bug */
	}

	return STATUS_OK;	 /* everything went fine */

out:
	/* Format a more complete error message and return that. */
	asprintf(error, "%s:%d: %s handling packet: %s\n",
		 state->config->script_path, event->line_number,
		 result == STATUS_ERR ? "error" : "warning", err);
	free(err);
	return result;
}

/* Inject a TCP RST packet to clear the connection state out of the
 * kernel, so the connection does not continue to retransmit packets
 * that may be sniffed during later test executions and cause false
 * negatives.
 */
int reset_connection(struct state *state, struct socket *socket)
{
	char *error = NULL;
	u32 seq = 0, ack_seq = 0;
	u16 window = 0;
	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
		 * number right after the last one we injected, and ACK
		 * the last thing we ACKed, and offer the same receive
		 * window we last offered.
		 */
		seq	= (ntohl(socket->last_injected_tcp_header.seq) +
			   (socket->last_injected_tcp_header.syn ? 1 : 0) +
			   (socket->last_injected_tcp_header.fin ? 1 : 0) +
			   socket->last_injected_tcp_payload_len);
		ack_seq	= ntohl(socket->last_injected_tcp_header.ack_seq);
		window	= ntohs(socket->last_injected_tcp_header.window);
	} else if (socket->last_outbound_tcp_header.ack) {
		/* If the kernel ACKed something, then just make sure
		 * we use the sequence number it ACKed, which will be
		 * something it expects.
		 */
		seq = ntohl(socket->last_outbound_tcp_header.ack_seq);
		ack_seq = ntohl(socket->last_outbound_tcp_header.seq);
	} else {
		/* If the kernel didn't ACK anything, then it probably
		 * sent only an initial SYN. So we get to send any
		 * sequence number we want, but should send an ACK
		 * suggesting we've seen the kernel's SYN.
		 */
		seq = 0;
		ack_seq = ntohl(socket->last_outbound_tcp_header.seq) + 1;
	}

	packet = new_tcp_packet(socket->address_family,
				DIRECTION_INBOUND, ECN_NONE,
				"R.", seq, 0, ack_seq, window, NULL, false, false,
				false, 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, state->config->udp_encaps != 0);

	/* Inject live packet into kernel. */
	result = send_live_ip_packet(state->netdev, packet);

	packet_free(packet);

	return result;
}

/* Inject an SCTP packet containing an ABORT chunk to clear the
 * association state out of the kernel, so the association does not
 * continue to retransmit packets that may be sniffed during later test
 * executions and cause false negatives.
 */
int abort_association(struct state *state, struct socket *socket)
{
	char *error = NULL;
	struct packet *packet;
	struct sctp_chunk_list *chunk_list;
	struct sctp_cause_list *cause_list;
	struct tuple live_inbound;
	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)) {
		return STATUS_OK;
	}
	if (socket->live.local_initiate_tag != 0) {
		flgs = 0;
	} 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"));
	chunk_list = sctp_chunk_list_new();
	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, 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, 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);
	} else {
		packet->sctp->v_tag = htonl(socket->live.remote_initiate_tag);
	}

	/* Inject live packet into kernel. */
	result = send_live_ip_packet(state->netdev, packet);

	packet_free(packet);

	return result;
}

struct packets *packets_new(void)
{
	struct packets *packets = calloc(1, sizeof(struct packets));

	packets->next_ephemeral_port = ephemeral_port();  /* cache a port */

	return packets;
}

void packets_free(struct packets *packets)
{
	memset(packets, 0, sizeof(*packets));  /* to help catch bugs */
	free(packets);
}