diff --git a/example/ipsec/odp_ipsec_stream.c b/example/ipsec/odp_ipsec_stream.c
new file mode 100644
index 0000000..02de790
--- /dev/null
+++ b/example/ipsec/odp_ipsec_stream.c
@@ -0,0 +1,534 @@
+/* Copyright (c) 2014, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <openssl/des.h>
+#include <openssl/rand.h>
+#include <openssl/hmac.h>
+#include <openssl/evp.h>
+
+#include <odp.h>
+#include <odp_align.h>
+#include <odp_crypto.h>
+#include <odp_packet.h>
+#include <helper/odp_packet_helper.h>
+#include <helper/odp_eth.h>
+#include <helper/odp_ip.h>
+#include <helper/odp_icmp.h>
+
+#include <odp_ipsec_stream.h>
+#include <odp_ipsec_loop_db.h>
+
+#define STREAM_MAGIC 0xBABE01234567CAFE
+
+#define LOOP_DEQ_MULTIPLE     0     /**< enable multi packet dequeue */
+
+/**
+ * Stream packet header
+ */
+typedef struct ODP_PACKED stream_pkt_hdr_s {
+	uint64be_t magic;    /**< Stream magic value for verification */
+	uint8_t    data[0];  /**< Incrementing data stream */
+} stream_pkt_hdr_t;
+
+stream_db_t *stream_db;
+
+void init_stream_db(void)
+{
+	stream_db = odp_shm_reserve("stream_db",
+				    sizeof(stream_db_t),
+				    ODP_CACHE_LINE_SIZE);
+	if (stream_db == NULL) {
+		ODP_ERR("Error: shared mem alloc failed.\n");
+		exit(EXIT_FAILURE);
+	}
+	memset(stream_db, 0, sizeof(*stream_db));
+}
+
+int create_stream_db_entry(char *input)
+{
+	int pos;
+	char *local, *str, *save;
+	stream_db_entry_t *entry = &stream_db->array[stream_db->index];
+
+	/* Verify we have a good entry */
+	if (MAX_DB <= stream_db->index)
+		return -1;
+
+	/* Make a local copy */
+	local = malloc(strlen(input) + 1);
+	if (local == NULL)
+		return -1;
+	strcpy(local, input);
+
+	/* count the number of tokens separated by ',' */
+	for (str = local, save = NULL, pos = 0;; str = NULL, pos++) {
+		char *token = strtok_r(str, ":", &save);
+
+		/* Check for no more tokens */
+		if (token == NULL)
+			break;
+
+		/* Parse based on postion */
+		switch (pos) {
+		case 0:
+			parse_ipv4_string(token, &entry->src_ip, NULL);
+			break;
+		case 1:
+			parse_ipv4_string(token, &entry->dst_ip, NULL);
+			break;
+		case 2:
+			entry->input.loop = loop_if_index(token);
+			if (entry->input.loop < 0) {
+				ODP_ERR("Error: stream must have input loop\n");
+				exit(EXIT_FAILURE);
+			}
+			break;
+		case 3:
+			entry->output.loop = loop_if_index(token);
+			break;
+		case 4:
+			entry->count = atoi(token);
+			break;
+		case 5:
+			entry->length = atoi(token);
+			if (entry->length < sizeof(stream_pkt_hdr_t))
+				entry->length = 0;
+			else
+				entry->length -= sizeof(stream_pkt_hdr_t);
+			break;
+		default:
+			return -1;
+		}
+	}
+
+	/* Verify all positions filled */
+	if (6 != pos)
+		return -1;
+
+	/* Add stream to the list */
+	entry->id = stream_db->index++;
+	entry->next = stream_db->list;
+	stream_db->list = entry;
+
+	return 0;
+}
+
+void resolve_stream_db(void)
+{
+	stream_db_entry_t *stream = NULL;
+
+	/* For each stream look for input and output IPsec entries */
+	for (stream = stream_db->list; NULL != stream; stream = stream->next) {
+		ipsec_cache_entry_t *entry;
+
+		/* Lookup input entry */
+		entry = find_ipsec_cache_entry_in(stream->src_ip,
+						  stream->dst_ip,
+						  NULL,
+						  NULL);
+		stream->input.entry = entry;
+
+		/* Lookup output entry */
+		entry = find_ipsec_cache_entry_out(stream->src_ip,
+						   stream->dst_ip,
+						   0);
+		stream->output.entry = entry;
+	}
+}
+
+odp_packet_t create_ipv4_packet(stream_db_entry_t *stream,
+				uint8_t *dmac,
+				odp_buffer_pool_t pkt_pool)
+{
+	ipsec_cache_entry_t *entry = stream->input.entry;
+	odp_buffer_t         bfr;
+	odp_packet_t         pkt;
+	uint8_t             *base;
+	uint8_t             *data;
+	odp_ethhdr_t        *eth;
+	odp_ipv4hdr_t       *ip;
+	odp_ahhdr_t         *ah = NULL;
+	odp_esphdr_t        *esp = NULL;
+	odp_icmphdr_t       *icmp;
+	stream_pkt_hdr_t    *test;
+	uint                 i;
+
+	/* Get buffer */
+	bfr = odp_buffer_alloc(pkt_pool);
+	if (ODP_BUFFER_INVALID == bfr)
+		return ODP_PACKET_INVALID;
+	pkt = odp_packet_from_buffer(bfr);
+	odp_packet_init(pkt);
+	base = odp_packet_start(pkt);
+	data = odp_packet_start(pkt);
+
+	/* Ethernet */
+	odp_packet_set_inflag_eth(pkt, 1);
+	odp_packet_set_l2_offset(pkt, data - base);
+	eth = (odp_ethhdr_t *)data;
+	data += sizeof(*eth);
+
+	memset((char *)eth->src.addr, (0x80 | stream->id), ODP_ETHADDR_LEN);
+	memcpy((char *)eth->dst.addr, dmac, ODP_ETHADDR_LEN);
+	eth->type = odp_cpu_to_be_16(ODP_ETHTYPE_IPV4);
+
+	/* IPv4 */
+	odp_packet_set_inflag_ipv4(pkt, 1);
+	odp_packet_set_l3_offset(pkt, data - base);
+	ip = (odp_ipv4hdr_t *)data;
+	data += sizeof(*ip);
+	odp_packet_set_l4_offset(pkt, data - base);
+
+	/* Wait until almost finished to fill in mutable fields */
+	memset((char *)ip, 0, sizeof(*ip));
+	ip->ver_ihl = 0x45;
+	ip->proto = ODP_IPPROTO_ICMP;
+	ip->id = odp_cpu_to_be_16(stream->id);
+	ip->src_addr = odp_cpu_to_be_32(stream->src_ip);
+	ip->dst_addr = odp_cpu_to_be_32(stream->dst_ip);
+
+	/* AH (if specified) */
+	if (entry && (ODP_AUTH_ALG_NULL != entry->ah.alg)) {
+		if (ODP_AUTH_ALG_MD5_96 != entry->ah.alg)
+			abort();
+
+		ah = (odp_ahhdr_t *)data;
+		data += sizeof(*ah);
+		data += entry->ah.icv_len;
+
+		memset((char *)ah, 0, sizeof(*ah) + entry->ah.icv_len);
+		ah->ah_len = 1 + (entry->ah.icv_len / 4);
+		ah->spi = odp_cpu_to_be_32(entry->ah.spi);
+		ah->seq_no = odp_cpu_to_be_32(stream->input.ah_seq++);
+	}
+
+	/* ESP (if specified) */
+	if (entry && (ODP_CIPHER_ALG_NULL != entry->esp.alg)) {
+		if (ODP_CIPHER_ALG_3DES_CBC != entry->esp.alg)
+			abort();
+
+		esp = (odp_esphdr_t *)data;
+		data += sizeof(*esp);
+		data += entry->esp.iv_len;
+
+		esp->spi = odp_cpu_to_be_32(entry->esp.spi);
+		esp->seq_no = odp_cpu_to_be_32(stream->input.esp_seq++);
+		RAND_bytes(esp->iv, 8);
+	}
+
+	/* ICMP header so we can see it on wireshark */
+	icmp = (odp_icmphdr_t *)data;
+	data += sizeof(*icmp);
+	icmp->type = ICMP_ECHO;
+	icmp->code = 0;
+	icmp->un.echo.id = odp_cpu_to_be_16(0x1234);
+	icmp->un.echo.sequence = odp_cpu_to_be_16(stream->created);
+
+	/* Packet payload of incrementing bytes */
+	test = (stream_pkt_hdr_t *)data;
+	data += sizeof(*test);
+	test->magic = odp_cpu_to_be_64(STREAM_MAGIC);
+	for (i = 0; i < stream->length; i++)
+		*data++ = (uint8_t)i;
+
+	/* Close ICMP */
+	icmp->chksum = 0;
+	icmp->chksum = odp_chksum(icmp, data - (uint8_t *)icmp);
+
+	/* Close ESP if specified */
+	if (esp) {
+		int payload_len = data - (uint8_t *)icmp;
+		int encrypt_len;
+		odp_esptrl_t *esp_t;
+		DES_key_schedule ks1, ks2, ks3;
+		uint8_t iv[8];
+
+		memcpy(iv, esp->iv, sizeof(iv));
+
+		encrypt_len = ESP_ENCODE_LEN(payload_len + sizeof(*esp_t),
+					     entry->esp.block_len);
+		memset(data, 0, encrypt_len - payload_len);
+		data += encrypt_len - payload_len;
+
+		esp_t = (odp_esptrl_t *)(data) - 1;
+		esp_t->pad_len = encrypt_len - payload_len - sizeof(*esp_t);
+		esp_t->next_header = ip->proto;
+		ip->proto = ODP_IPPROTO_ESP;
+
+		DES_set_key((DES_cblock *)&entry->esp.key.data[0], &ks1);
+		DES_set_key((DES_cblock *)&entry->esp.key.data[8], &ks2);
+		DES_set_key((DES_cblock *)&entry->esp.key.data[16], &ks3);
+
+		DES_ede3_cbc_encrypt((uint8_t *)icmp,
+				     (uint8_t *)icmp,
+				     encrypt_len,
+				     &ks1,
+				     &ks2,
+				     &ks3,
+				     (DES_cblock *)iv,
+				     1);
+	}
+
+	/* Since ESP can pad we can now fix IP length */
+	ip->tot_len = odp_cpu_to_be_16(data - (uint8_t *)ip);
+	odp_packet_set_len(pkt, data - base);
+
+	/* Close AH if specified */
+	if (ah) {
+		uint8_t hash[EVP_MAX_MD_SIZE];
+		uint32_t hash_len = 12;
+		int auth_len = data - (uint8_t *)ip;
+
+		ah->next_header = ip->proto;
+		ip->proto = ODP_IPPROTO_AH;
+
+		HMAC(EVP_md5(),
+		     entry->ah.key.data,
+		     16,
+		     (uint8_t *)ip,
+		     auth_len,
+		     hash,
+		     &hash_len);
+
+		memcpy(ah->icv, hash, 12);
+	}
+
+	/* Now fill in final IP header fields */
+	ip->ttl = 64;
+	ip->tos = 0;
+	ip->frag_offset = 0;
+	ip->chksum = 0;
+	odp_ipv4_csum_update(pkt);
+	return pkt;
+}
+
+bool verify_ipv4_packet(stream_db_entry_t *stream,
+			odp_packet_t pkt)
+{
+	ipsec_cache_entry_t *entry = stream->output.entry;
+	uint8_t             *data;
+	odp_ipv4hdr_t       *ip;
+	odp_ahhdr_t         *ah = NULL;
+	odp_esphdr_t        *esp = NULL;
+	int                  hdr_len;
+	odp_icmphdr_t       *icmp;
+	stream_pkt_hdr_t    *test;
+
+	/* Basic IPv4 verify (add checksum verification) */
+	data = odp_packet_l3(pkt);
+	ip = (odp_ipv4hdr_t *)data;
+	data += sizeof(*ip);
+	if (0x45 != ip->ver_ihl)
+		return FALSE;
+	if (stream->src_ip != odp_be_to_cpu_32(ip->src_addr))
+		return FALSE;
+	if (stream->dst_ip != odp_be_to_cpu_32(ip->dst_addr))
+		return FALSE;
+
+	/* Find IPsec headers if any and compare against entry */
+	hdr_len = locate_ipsec_headers(ip, &ah, &esp);
+	if (ah) {
+		if (!entry)
+			return FALSE;
+		if (ODP_AUTH_ALG_NULL == entry->ah.alg)
+			return FALSE;
+		if (odp_be_to_cpu_32(ah->spi) != entry->ah.spi)
+			return FALSE;
+		if (ODP_AUTH_ALG_MD5_96 != entry->ah.alg)
+			abort();
+	} else {
+		if (entry && (ODP_AUTH_ALG_NULL != entry->ah.alg))
+			return FALSE;
+	}
+	if (esp) {
+		if (!entry)
+			return FALSE;
+		if (ODP_CIPHER_ALG_NULL == entry->esp.alg)
+			return FALSE;
+		if (odp_be_to_cpu_32(esp->spi) != entry->esp.spi)
+			return FALSE;
+		if (ODP_CIPHER_ALG_3DES_CBC != entry->esp.alg)
+			abort();
+		hdr_len += entry->esp.iv_len;
+	} else {
+		if (entry && (ODP_CIPHER_ALG_NULL != entry->esp.alg))
+			return FALSE;
+	}
+	data += hdr_len;
+
+	/* Verify authentication (if present) */
+	if (ah) {
+		uint8_t  ip_tos;
+		uint8_t  ip_ttl;
+		uint16_t ip_frag_offset;
+		uint8_t  icv[12];
+		uint8_t  hash[EVP_MAX_MD_SIZE];
+		uint32_t hash_len = 12;
+
+		/* Save/clear mutable fields */
+		ip_tos = ip->tos;
+		ip_ttl = ip->ttl;
+		ip_frag_offset = odp_be_to_cpu_16(ip->frag_offset);
+		ip->tos = 0;
+		ip->ttl = 0;
+		ip->frag_offset = 0;
+		ip->chksum = 0;
+		memcpy(icv, ah->icv, 12);
+		memset(ah->icv, 0, 12);
+
+		/* Calculate HMAC and compare */
+		HMAC(EVP_md5(),
+		     entry->ah.key.data,
+		     entry->ah.key.length,
+		     (uint8_t *)ip,
+		     odp_be_to_cpu_16(ip->tot_len),
+		     hash,
+		     &hash_len);
+
+		if (0 != memcmp(icv, hash, sizeof(icv)))
+			return FALSE;
+
+		ip->proto = ah->next_header;
+		ip->tos = ip_tos;
+		ip->ttl = ip_ttl;
+		ip->frag_offset = odp_cpu_to_be_16(ip_frag_offset);
+	}
+
+	/* Decipher if present */
+	if (esp) {
+		odp_esptrl_t *esp_t;
+		DES_key_schedule ks1, ks2, ks3;
+		uint8_t iv[8];
+		int encrypt_len = ipv4_data_len(ip) - hdr_len;
+
+		memcpy(iv, esp->iv, sizeof(iv));
+
+		DES_set_key((DES_cblock *)&entry->esp.key.data[0], &ks1);
+		DES_set_key((DES_cblock *)&entry->esp.key.data[8], &ks2);
+		DES_set_key((DES_cblock *)&entry->esp.key.data[16], &ks3);
+
+		DES_ede3_cbc_encrypt((uint8_t *)data,
+				     (uint8_t *)data,
+				     encrypt_len,
+				     &ks1,
+				     &ks2,
+				     &ks3,
+				     (DES_cblock *)iv,
+				     0);
+
+		esp_t = (odp_esptrl_t *)(data + encrypt_len) - 1;
+		ip->proto = esp_t->next_header;
+	}
+
+	/* Verify ICMP packet */
+	if (ODP_IPPROTO_ICMP != ip->proto)
+		return FALSE;
+
+	/* Verify ICMP header */
+	icmp = (odp_icmphdr_t *)data;
+	data += sizeof(*icmp);
+	if (ICMP_ECHO != icmp->type)
+		return FALSE;
+	if (0x1234 != odp_be_to_cpu_16(icmp->un.echo.id))
+		return FALSE;
+
+	/* Now check our packet */
+	test = (stream_pkt_hdr_t *)data;
+	if (STREAM_MAGIC != odp_be_to_cpu_64(test->magic))
+		return FALSE;
+
+	return TRUE;
+}
+
+int create_stream_db_inputs(void)
+{
+	int created = 0;
+	odp_buffer_pool_t pkt_pool;
+	stream_db_entry_t *stream = NULL;
+
+	/* Lookup the packet pool */
+	pkt_pool = odp_buffer_pool_lookup("packet_pool");
+	if (pkt_pool == ODP_BUFFER_POOL_INVALID) {
+		ODP_ERR("Error: pkt_pool not found\n");
+		exit(EXIT_FAILURE);
+	}
+
+	/* For each stream create corresponding input packets */
+	for (stream = stream_db->list; NULL != stream; stream = stream->next) {
+		int count;
+		uint8_t *dmac = query_loopback_db_mac(stream->input.loop);
+		odp_queue_t queue = query_loopback_db_inq(stream->input.loop);
+
+		for (count = stream->count; count > 0; count--) {
+			odp_packet_t pkt;
+
+			pkt = create_ipv4_packet(stream, dmac, pkt_pool);
+			if (ODP_PACKET_INVALID == pkt) {
+				printf("Packet buffers exhausted\n");
+				break;
+			}
+			stream->created++;
+			odp_queue_enq(queue, pkt);
+
+			/* Count this stream when we create first packet */
+			if (1 == stream->created)
+				created++;
+		}
+	}
+
+	return created;
+}
+
+bool verify_stream_db_outputs(void)
+{
+	bool done = TRUE;
+	stream_db_entry_t *stream = NULL;
+
+	/* For each stream look for output packets */
+	for (stream = stream_db->list; NULL != stream; stream = stream->next) {
+		int idx;
+		int count;
+		odp_queue_t queue;
+		odp_buffer_t buf_tbl[32];
+
+		queue = query_loopback_db_outq(stream->output.loop);
+
+		if (ODP_QUEUE_INVALID == queue)
+			continue;
+
+		for (;;) {
+#if LOOP_DEQ_MULTIPLE
+			count = odp_queue_deq_multi(queue, buf_tbl, 32);
+#else
+			buf_tbl[0] = odp_queue_deq(queue);
+			count = (buf_tbl[0] != ODP_BUFFER_INVALID) ? 1 : 0;
+#endif
+			if (!count)
+				break;
+			for (idx = 0; idx < count; idx++) {
+				bool good;
+				odp_packet_t pkt;
+
+				pkt = odp_packet_from_buffer(buf_tbl[idx]);
+
+				good = verify_ipv4_packet(stream, pkt);
+				if (good)
+					stream->verified++;
+				odp_packet_free(pkt);
+			}
+		}
+
+		printf("Stream %d %d\n", stream->created, stream->verified);
+
+		if (stream->created != stream->verified)
+			done = FALSE;
+	}
+	return done;
+}
+
diff --git a/example/ipsec/odp_ipsec_stream.h b/example/ipsec/odp_ipsec_stream.h
new file mode 100644
index 0000000..d3a0dd4
--- /dev/null
+++ b/example/ipsec/odp_ipsec_stream.h
@@ -0,0 +1,133 @@
+/* Copyright (c) 2014, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+#ifndef ODP_IPSEC_STREAM_H_
+#define ODP_IPSEC_STREAM_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <odp.h>
+#include <odp_ipsec_misc.h>
+#include <odp_ipsec_cache.h>
+
+/**
+ * Stream database entry structure
+ */
+typedef struct stream_db_entry_s {
+	struct stream_db_entry_s *next; /**< Next entry on list */
+	int              id;            /**< Stream ID */
+	uint32_t         src_ip;        /**< Source IPv4 address */
+	uint32_t         dst_ip;        /**< Destination IPv4 address */
+	int              count;         /**< Packet count */
+	uint             length;        /**< Packet payload length */
+	uint32_t         created;       /**< Number successfully created */
+	uint32_t         verified;      /**< Number successfully verified */
+	struct {
+		int      loop;          /**< Input loop interface index */
+		uint32_t ah_seq;        /**< AH sequence number if present */
+		uint32_t esp_seq;       /**< ESP sequence number if present */
+		ipsec_cache_entry_t *entry;  /**< IPsec to apply on input */
+	} input;
+	struct {
+		int      loop;          /**< Output loop interface index */
+		ipsec_cache_entry_t *entry;  /**t IPsec to verify on output */
+	} output;
+} stream_db_entry_t;
+
+/**
+ * Stream database
+ */
+typedef struct stream_db_s {
+	uint32_t           index;          /**< Index of next available entry */
+	stream_db_entry_t *list;           /**< List of active entries */
+	stream_db_entry_t  array[MAX_DB];  /**< Entry storage */
+} stream_db_t;
+
+extern stream_db_t *stream_db;
+
+/** Initialize stream database global control structure */
+void init_stream_db(void);
+
+/**
+ * Create an stream DB entry
+ *
+ * String is of the format "SrcIP:DstIP:InInt:OutIntf:Count:Length"
+ *
+ * @param input  Pointer to string describing stream
+ *
+ * @return 0 if successful else -1
+ */
+int create_stream_db_entry(char *input);
+
+/**
+ * Resolve the stream DB against the IPsec input and output caches
+ *
+ * For each stream, look the source and destination IP address up in the
+ * input and output IPsec caches.  If a hit is found, store the hit in
+ * the stream DB to be used when creating packets.
+ */
+void resolve_stream_db(void);
+
+/**
+ * Create IPv4 packet for stream
+ *
+ * Create one ICMP test packet based on the stream structure.  If an input
+ * IPsec cache entry is associated with the stream, build a packet that should
+ * successfully match that entry and be correctly decoded by it.
+ *
+ * @param stream    Stream DB entry
+ * @param dmac      Destination MAC address to use
+ * @param pkt_pool  Packet buffer pool to allocate from
+ *
+ * @return packet else ODP_PACKET_INVALID
+ */
+odp_packet_t create_ipv4_packet(stream_db_entry_t *stream,
+				uint8_t *dmac,
+				odp_buffer_pool_t pkt_pool);
+
+/**
+ * Verify an IPv4 packet received on a loop output queue
+ *
+ * TODO: Better error checking, add counters, add tracing,
+ *       add order verification
+ *
+ * @param stream  Stream to verify the packet against
+ * @param pkt     Packet to verify
+ *
+ * @return TRUE if packet verifies else FALSE
+ */
+bool verify_ipv4_packet(stream_db_entry_t *stream,
+			odp_packet_t pkt);
+
+/**
+ * Create input packets based on the stream DB
+ *
+ * Create input packets based on the configured streams and enqueue them
+ * into loop interface input queues.  Once packet processing starts these
+ * packets will be remomved and processed as if they had come from a normal
+ * packet interface.
+ *
+ * @return number of streams successfully processed
+ */
+int create_stream_db_inputs(void);
+
+/**
+ * Verify stream DB outputs
+ *
+ * For each stream, poll the output loop interface queue and verify
+ * any packets found on it
+ *
+ * @return TRUE if all packets on all streams verified else FALSE
+ */
+bool verify_stream_db_outputs(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
