Fwd: [PATCH 1/2 v2] examples: ipsec: tunnel mode support

Message ID CA+gU81RfdbmcwukkHHJaYqTmSp7ej0D0Pr=8HTJb5tc-uAWjEw@mail.gmail.com
State New
Headers show

Commit Message

Alexandru Badicioiu June 10, 2015, 6:06 a.m.
Ping.

---------- Forwarded message ----------
From: <alexandru.badicioiu@linaro.org>
Date: 22 May 2015 at 14:44
Subject: [lng-odp][PATCH 1/2 v2] examples: ipsec: tunnel mode support
To: lng-odp@lists.linaro.org
Cc: Alexandru Badicioiu <alexandru.badicioiu@linaro.org>


From: Alexandru Badicioiu <alexandru.badicioiu@linaro.org>

v1 - added comment for tunnel DB entry use
v2 - fixed tun pointer initialization, new checkpatch compliance

Tunnel mode is enabled from the command line using -t argument with
the following format: SrcIP:DstIP:TunnelSrcIP:TunnelDstIP.
SrcIP - cleartext packet source IP
DstIP - cleartext packet destination IP
TunnelSrcIP - tunnel source IP
TunnelDstIP - tunnel destination IP

The outbound packets matching SrcIP:DstIP will be encapsulated
in a TunnelSrcIP:TunnelDstIP IPSec tunnel (AH/ESP/AH+ESP)
if a matching outbound SA is determined (as for transport mode).
For inbound packets each entry in the IPSec cache is matched
for the cleartext addresses, as in the transport mode (SrcIP:DstIP)
and then for the tunnel addresses (TunnelSrcIP:TunnelDstIP)
in case cleartext addresses didn't match. After authentication and
decryption tunneled packets are verified against the tunnel entry
(packets came in from the expected tunnel).

Signed-off-by: Alexandru Badicioiu <alexandru.badicioiu@linaro.org>
Reviewed-by: Steve Kordus <skordus@cisco.com>
---
 example/ipsec/odp_ipsec.c        |  107 +++++++++++++++++++++++++++---
 example/ipsec/odp_ipsec_cache.c  |   32 +++++++++-
 example/ipsec/odp_ipsec_cache.h  |    6 ++
 example/ipsec/odp_ipsec_sa_db.c  |  134
+++++++++++++++++++++++++++++++++++++-
 example/ipsec/odp_ipsec_sa_db.h  |   55 ++++++++++++++++
 example/ipsec/odp_ipsec_stream.c |  102 ++++++++++++++++++++++++----
 6 files changed, 406 insertions(+), 30 deletions(-)

        if (ODP_PACKET_INVALID == pkt)
@@ -205,13 +211,22 @@ odp_packet_t create_ipv4_packet(stream_db_entry_t
*stream,
        /* Wait until almost finished to fill in mutable fields */
        memset((char *)ip, 0, sizeof(*ip));
        ip->ver_ihl = 0x45;
-       ip->proto = ODPH_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);
+       /* Outer IP header in tunnel mode */
+       if (entry && entry->mode == IPSEC_SA_MODE_TUNNEL &&
+           (entry == stream->input.entry)) {
+               ip->proto = ODPH_IPV4;
+               ip->src_addr = odp_cpu_to_be_32(entry->tun_src_ip);
+               ip->dst_addr = odp_cpu_to_be_32(entry->tun_dst_ip);
+       } else {
+               ip->proto = ODPH_IPPROTO_ICMP;
+               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 (entry && (entry == stream->input.entry) &&
+           (ODP_AUTH_ALG_NULL != entry->ah.alg)) {
                if (ODP_AUTH_ALG_MD5_96 != entry->ah.alg)
                        abort();

@@ -226,7 +241,8 @@ odp_packet_t create_ipv4_packet(stream_db_entry_t
*stream,
        }

        /* ESP (if specified) */
-       if (entry && (ODP_CIPHER_ALG_NULL != entry->esp.alg)) {
+       if (entry && (entry == stream->input.entry) &&
+           (ODP_CIPHER_ALG_NULL != entry->esp.alg)) {
                if (ODP_CIPHER_ALG_3DES_CBC != entry->esp.alg)
                        abort();

@@ -239,6 +255,23 @@ odp_packet_t create_ipv4_packet(stream_db_entry_t
*stream,
                RAND_bytes(esp->iv, 8);
        }

+       /* Inner IP header in tunnel mode */
+       if (entry && (entry == stream->input.entry) &&
+           (entry->mode == IPSEC_SA_MODE_TUNNEL)) {
+               inner_ip = (odph_ipv4hdr_t *)data;
+               memset((char *)inner_ip, 0, sizeof(*inner_ip));
+               inner_ip->ver_ihl = 0x45;
+               inner_ip->proto = ODPH_IPPROTO_ICMP;
+               inner_ip->id = odp_cpu_to_be_16(stream->id);
+               inner_ip->ttl = 64;
+               inner_ip->tos = 0;
+               inner_ip->frag_offset = 0;
+               inner_ip->src_addr = odp_cpu_to_be_32(stream->src_ip);
+               inner_ip->dst_addr = odp_cpu_to_be_32(stream->dst_ip);
+               inner_ip->chksum = odp_chksum(inner_ip, sizeof(inner_ip));
+               data += sizeof(*inner_ip);
+       }
+
        /* ICMP header so we can see it on wireshark */
        icmp = (odph_icmphdr_t *)data;
        data += sizeof(*icmp);
@@ -261,6 +294,13 @@ odp_packet_t create_ipv4_packet(stream_db_entry_t
*stream,
        /* Close ESP if specified */
        if (esp) {
                int payload_len = data - (uint8_t *)icmp;
+               uint8_t *encrypt_start = (uint8_t *)icmp;
+
+               if (entry->mode == IPSEC_SA_MODE_TUNNEL) {
+                       payload_len = data - (uint8_t *)inner_ip;
+                       encrypt_start = (uint8_t *)inner_ip;
+               }
+
                int encrypt_len;
                odph_esptrl_t *esp_t;
                DES_key_schedule ks1, ks2, ks3;
@@ -282,8 +322,8 @@ odp_packet_t create_ipv4_packet(stream_db_entry_t
*stream,
                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,
+               DES_ede3_cbc_encrypt(encrypt_start,
+                                    encrypt_start,
                                     encrypt_len,
                                     &ks1,
                                     &ks2,
@@ -332,7 +372,7 @@ odp_packet_t create_ipv4_packet(stream_db_entry_t
*stream,
 odp_bool_t verify_ipv4_packet(stream_db_entry_t *stream,
                              odp_packet_t pkt)
 {
-       ipsec_cache_entry_t *entry = stream->output.entry;
+       ipsec_cache_entry_t *entry = NULL;
        uint8_t *data;
        odph_ipv4hdr_t *ip;
        odph_ahhdr_t *ah = NULL;
@@ -340,6 +380,12 @@ odp_bool_t verify_ipv4_packet(stream_db_entry_t
*stream,
        int hdr_len;
        odph_icmphdr_t *icmp;
        stream_pkt_hdr_t *test;
+       uint32_t src_ip, dst_ip;
+
+       if (stream->input.entry)
+               entry = stream->input.entry;
+       else if (stream->output.entry)
+               entry = stream->output.entry;

        /* Basic IPv4 verify (add checksum verification) */
        data = odp_packet_l3_ptr(pkt, NULL);
@@ -347,13 +393,29 @@ odp_bool_t verify_ipv4_packet(stream_db_entry_t
*stream,
        data += sizeof(*ip);
        if (0x45 != ip->ver_ihl)
                return FALSE;
-       if (stream->src_ip != odp_be_to_cpu_32(ip->src_addr))
+
+       src_ip = odp_be_to_cpu_32(ip->src_addr);
+       dst_ip = odp_be_to_cpu_32(ip->dst_addr);
+       if ((stream->src_ip != src_ip) && stream->output.entry &&
+           (stream->output.entry->tun_src_ip != src_ip))
                return FALSE;
-       if (stream->dst_ip != odp_be_to_cpu_32(ip->dst_addr))
+       if ((stream->dst_ip != dst_ip) && stream->output.entry &&
+           (stream->output.entry->tun_dst_ip != dst_ip))
+               return FALSE;
+
+       if ((stream->src_ip != src_ip) && stream->input.entry &&
+           (stream->input.entry->tun_src_ip != src_ip))
+               return FALSE;
+       if ((stream->dst_ip != dst_ip) && stream->input.entry &&
+           (stream->input.entry->tun_dst_ip != dst_ip))
                return FALSE;

        /* Find IPsec headers if any and compare against entry */
        hdr_len = locate_ipsec_headers(ip, &ah, &esp);
+
+       /* Cleartext packet */
+       if (!ah && !esp)
+               goto clear_packet;
        if (ah) {
                if (!entry)
                        return FALSE;
@@ -446,12 +508,22 @@ odp_bool_t verify_ipv4_packet(stream_db_entry_t
*stream,
                ip->proto = esp_t->next_header;
        }

-       /* Verify ICMP packet */
-       if (ODPH_IPPROTO_ICMP != ip->proto)
-               return FALSE;
+clear_packet:
+       /* Verify IP/ICMP packet */
+       if (entry && (entry->mode == IPSEC_SA_MODE_TUNNEL) && (ah || esp)) {
+               if (ODPH_IPV4 != ip->proto)
+                       return FALSE;
+               odph_ipv4hdr_t *inner_ip = (odph_ipv4hdr_t *)data;
+
+               icmp = (odph_icmphdr_t *)(inner_ip + 1);
+               data = (uint8_t *)icmp;
+       } else {
+               if (ODPH_IPPROTO_ICMP != ip->proto)
+                       return FALSE;
+               icmp = (odph_icmphdr_t *)data;
+       }

        /* Verify ICMP header */
-       icmp = (odph_icmphdr_t *)data;
        data += sizeof(*icmp);
        if (ICMP_ECHO != icmp->type)
                return FALSE;
--
1.7.3.4

Patch

diff --git a/example/ipsec/odp_ipsec.c b/example/ipsec/odp_ipsec.c
index 99ccd6b..ed56e14 100644
--- a/example/ipsec/odp_ipsec.c
+++ b/example/ipsec/odp_ipsec.c
@@ -135,13 +135,20 @@  typedef struct {
        uint8_t  ip_ttl;         /**< Saved IP TTL value */
        int      hdr_len;        /**< Length of IPsec headers */
        int      trl_len;        /**< Length of IPsec trailers */
+       uint16_t tun_hdr_offset; /**< Offset of tunnel header from
+                                     buffer start */
        uint16_t ah_offset;      /**< Offset of AH header from buffer start
*/
        uint16_t esp_offset;     /**< Offset of ESP header from buffer
start */

+       /* Input only */
+       uint32_t src_ip;         /**< SA source IP address */
+       uint32_t dst_ip;         /**< SA dest IP address */
+
        /* Output only */
        odp_crypto_op_params_t params;  /**< Parameters for crypto call */
        uint32_t *ah_seq;               /**< AH sequence number location */
        uint32_t *esp_seq;              /**< ESP sequence number location */
+       uint16_t *tun_hdr_id;           /**< Tunnel header ID > */
 } ipsec_ctx_t;

 /**
@@ -357,6 +364,7 @@  void ipsec_init_pre(void)
        /* Initialize our data bases */
        init_sp_db();
        init_sa_db();
+       init_tun_db();
        init_ipsec_cache();
 }

@@ -376,19 +384,27 @@  void ipsec_init_post(crypto_api_mode_e api_mode)
        for (entry = sp_db->list; NULL != entry; entry = entry->next) {
                sa_db_entry_t *cipher_sa = NULL;
                sa_db_entry_t *auth_sa = NULL;
+               tun_db_entry_t *tun = NULL;

-               if (entry->esp)
+               if (entry->esp) {
                        cipher_sa = find_sa_db_entry(&entry->src_subnet,
                                                     &entry->dst_subnet,
                                                     1);
-               if (entry->ah)
+                       tun = find_tun_db_entry(cipher_sa->src_ip,
+                                               cipher_sa->dst_ip);
+               }
+               if (entry->ah) {
                        auth_sa = find_sa_db_entry(&entry->src_subnet,
                                                   &entry->dst_subnet,
                                                   0);
+                       tun = find_tun_db_entry(auth_sa->src_ip,
+                                               auth_sa->dst_ip);
+               }

                if (cipher_sa || auth_sa) {
                        if (create_ipsec_cache_entry(cipher_sa,
                                                     auth_sa,
+                                                    tun,
                                                     api_mode,
                                                     entry->input,
                                                     completionq,
@@ -670,6 +686,8 @@  pkt_disposition_e do_ipsec_in_classify(odp_packet_t pkt,
        ctx->ipsec.esp_offset = esp ? ((uint8_t *)esp) - buf : 0;
        ctx->ipsec.hdr_len = hdr_len;
        ctx->ipsec.trl_len = 0;
+       ctx->ipsec.src_ip = entry->src_ip;
+       ctx->ipsec.dst_ip = entry->dst_ip;

        /*If authenticating, zero the mutable fields build the request */
        if (ah) {
@@ -750,6 +768,24 @@  pkt_disposition_e do_ipsec_in_finish(odp_packet_t pkt,
                trl_len += esp_t->pad_len + sizeof(*esp_t);
        }

+       /* We have a tunneled IPv4 packet */
+       if (ip->proto == ODPH_IPV4) {
+               odp_packet_pull_head(pkt, sizeof(*ip) + hdr_len);
+               odp_packet_pull_tail(pkt, trl_len);
+               odph_ethhdr_t *eth;
+
+               eth = (odph_ethhdr_t *)odp_packet_l2_ptr(pkt, NULL);
+               eth->type = ODPH_ETHTYPE_IPV4;
+               ip = (odph_ipv4hdr_t *)odp_packet_l3_ptr(pkt, NULL);
+
+               /* Check inbound policy */
+               if ((ip->src_addr != ctx->ipsec.src_ip ||
+                    ip->dst_addr != ctx->ipsec.dst_ip))
+                       return PKT_DROP;
+
+               return PKT_CONTINUE;
+       }
+
        /* Finalize the IPv4 header */
        ipv4_adjust_len(ip, -(hdr_len + trl_len));
        ip->ttl = ctx->ipsec.ip_ttl;
@@ -821,9 +857,13 @@  pkt_disposition_e do_ipsec_out_classify(odp_packet_t
pkt,
        params.pkt = pkt;
        params.out_pkt = entry->in_place ? pkt : ODP_PACKET_INVALID;

+       if (entry->mode == IPSEC_SA_MODE_TUNNEL) {
+               hdr_len += sizeof(odph_ipv4hdr_t);
+               ip_data = (uint8_t *)ip;
+       }
        /* Compute ah and esp, determine length of headers, move the data */
        if (entry->ah.alg) {
-               ah = (odph_ahhdr_t *)(ip_data);
+               ah = (odph_ahhdr_t *)(ip_data + hdr_len);
                hdr_len += sizeof(odph_ahhdr_t);
                hdr_len += entry->ah.icv_len;
        }
@@ -835,21 +875,39 @@  pkt_disposition_e do_ipsec_out_classify(odp_packet_t
pkt,
        memmove(ip_data + hdr_len, ip_data, ip_data_len);
        ip_data += hdr_len;

+       /* update outer header in tunnel mode */
+       if (entry->mode == IPSEC_SA_MODE_TUNNEL) {
+               /* tunnel addresses */
+               ip->src_addr = odp_cpu_to_be_32(entry->tun_src_ip);
+               ip->dst_addr = odp_cpu_to_be_32(entry->tun_dst_ip);
+       }
+
        /* For cipher, compute encrypt length, build headers and request */
        if (esp) {
                uint32_t encrypt_len;
                odph_esptrl_t *esp_t;

-               encrypt_len = ESP_ENCODE_LEN(ip_data_len + sizeof(*esp_t),
-                                            entry->esp.block_len);
-               trl_len = encrypt_len - ip_data_len;
+               if (entry->mode == IPSEC_SA_MODE_TUNNEL) {
+                       encrypt_len = ESP_ENCODE_LEN(ip->tot_len +
+                                                    sizeof(*esp_t),
+                                                    entry->esp.block_len);
+                       trl_len = encrypt_len - ip->tot_len;
+               } else {
+                       encrypt_len = ESP_ENCODE_LEN(ip_data_len +
+                                                    sizeof(*esp_t),
+                                                    entry->esp.block_len);
+                       trl_len = encrypt_len - ip_data_len;
+               }

                esp->spi = odp_cpu_to_be_32(entry->esp.spi);
                memcpy(esp + 1, entry->state.iv, entry->esp.iv_len);

                esp_t = (odph_esptrl_t *)(ip_data + encrypt_len) - 1;
                esp_t->pad_len     = trl_len - sizeof(*esp_t);
-               esp_t->next_header = ip->proto;
+               if (entry->mode == IPSEC_SA_MODE_TUNNEL)
+                       esp_t->next_header = ODPH_IPV4;
+               else
+                       esp_t->next_header = ip->proto;
                ip->proto = ODPH_IPPROTO_ESP;

                params.cipher_range.offset = ip_data - buf;
@@ -861,7 +919,10 @@  pkt_disposition_e do_ipsec_out_classify(odp_packet_t
pkt,
                memset(ah, 0, sizeof(*ah) + entry->ah.icv_len);
                ah->spi = odp_cpu_to_be_32(entry->ah.spi);
                ah->ah_len = 1 + (entry->ah.icv_len / 4);
-               ah->next_header = ip->proto;
+               if (entry->mode == IPSEC_SA_MODE_TUNNEL && !esp)
+                       ah->next_header = ODPH_IPV4;
+               else
+                       ah->next_header = ip->proto;
                ip->proto = ODPH_IPPROTO_AH;

                ip->chksum = 0;
@@ -884,8 +945,11 @@  pkt_disposition_e do_ipsec_out_classify(odp_packet_t
pkt,
        ctx->ipsec.trl_len = trl_len;
        ctx->ipsec.ah_offset = ah ? ((uint8_t *)ah) - buf : 0;
        ctx->ipsec.esp_offset = esp ? ((uint8_t *)esp) - buf : 0;
+       ctx->ipsec.tun_hdr_offset = (entry->mode == IPSEC_SA_MODE_TUNNEL) ?
+                                      ((uint8_t *)ip - buf) : 0;
        ctx->ipsec.ah_seq = &entry->state.ah_seq;
        ctx->ipsec.esp_seq = &entry->state.esp_seq;
+       ctx->ipsec.tun_hdr_id = &entry->state.tun_hdr_id;
        memcpy(&ctx->ipsec.params, &params, sizeof(params));

        *skip = FALSE;
@@ -924,6 +988,21 @@  pkt_disposition_e do_ipsec_out_seq(odp_packet_t pkt,
                esp = (odph_esphdr_t *)(ctx->ipsec.esp_offset + buf);
                esp->seq_no = odp_cpu_to_be_32((*ctx->ipsec.esp_seq)++);
        }
+       if (ctx->ipsec.tun_hdr_offset) {
+               odph_ipv4hdr_t *ip;
+               int ret;
+
+               ip = (odph_ipv4hdr_t *)(ctx->ipsec.tun_hdr_offset + buf);
+               ip->id = odp_cpu_to_be_16((*ctx->ipsec.tun_hdr_id)++);
+               if (!ip->id) {
+                       /* re-init tunnel hdr id */
+                       ret = odp_random_data((uint8_t
*)ctx->ipsec.tun_hdr_id,
+
 sizeof(*ctx->ipsec.tun_hdr_id),
+                                             1);
+                       if (ret != sizeof(*ctx->ipsec.tun_hdr_id))
+                               abort();
+               }
+       }

        /* Issue crypto request */
        if (odp_crypto_operation(&ctx->ipsec.params,
@@ -1315,8 +1394,9 @@  static void parse_args(int argc, char *argv[],
appl_args_t *appl_args)
                {"mode", required_argument, NULL, 'm'},         /* return
'm' */
                {"route", required_argument, NULL, 'r'},        /* return
'r' */
                {"policy", required_argument, NULL, 'p'},       /* return
'p' */
-               {"ah", required_argument, NULL, 'a'},           /* return
'a' */
-               {"esp", required_argument, NULL, 'e'},          /* return
'e' */
+               {"ah", required_argument, NULL, 'a'},           /* return
'a' */
+               {"esp", required_argument, NULL, 'e'},          /* return
'e' */
+               {"tunnel", required_argument, NULL, 't'},       /* return
't' */
                {"stream", required_argument, NULL, 's'},       /* return
's' */
                {"help", no_argument, NULL, 'h'},               /* return
'h' */
                {NULL, 0, NULL, 0}
@@ -1327,7 +1407,7 @@  static void parse_args(int argc, char *argv[],
appl_args_t *appl_args)
        appl_args->mode = 0;  /* turn off async crypto API by default */

        while (!rc) {
-               opt = getopt_long(argc, argv, "+c:i:m:h:r:p:a:e:s:",
+               opt = getopt_long(argc, argv, "+c:i:m:h:r:p:a:e:t:s:",
                                  longopts, &long_index);

                if (-1 == opt)
@@ -1398,6 +1478,10 @@  static void parse_args(int argc, char *argv[],
appl_args_t *appl_args)
                        rc = create_sa_db_entry(optarg, TRUE);
                        break;

+               case 't':
+                       rc = create_tun_db_entry(optarg);
+                       break;
+
                case 's':
                        rc = create_stream_db_entry(optarg);
                        break;
@@ -1458,6 +1542,7 @@  static void print_info(char *progname, appl_args_t
*appl_args)
        dump_fwd_db();
        dump_sp_db();
        dump_sa_db();
+       dump_tun_db();
        printf("\n\n");
        fflush(NULL);
 }
diff --git a/example/ipsec/odp_ipsec_cache.c
b/example/ipsec/odp_ipsec_cache.c
index 12b960d..6a8f3c9 100644
--- a/example/ipsec/odp_ipsec_cache.c
+++ b/example/ipsec/odp_ipsec_cache.c
@@ -38,6 +38,7 @@  void init_ipsec_cache(void)

 int create_ipsec_cache_entry(sa_db_entry_t *cipher_sa,
                             sa_db_entry_t *auth_sa,
+                            tun_db_entry_t *tun,
                             crypto_api_mode_e api_mode,
                             odp_bool_t in,
                             odp_queue_t completionq,
@@ -47,12 +48,18 @@  int create_ipsec_cache_entry(sa_db_entry_t *cipher_sa,
        ipsec_cache_entry_t *entry;
        enum odp_crypto_ses_create_err ses_create_rc;
        odp_crypto_session_t session;
+       sa_mode_t mode = IPSEC_SA_MODE_TRANSPORT;

        /* Verify we have a good entry */
        entry = &ipsec_cache->array[ipsec_cache->index];
        if (MAX_DB <= ipsec_cache->index)
                return -1;

+       /* Verify SA mode match in case of cipher&auth */
+       if (cipher_sa && auth_sa &&
+           (cipher_sa->mode != auth_sa->mode))
+               return -1;
+
        /* Setup parameters and call crypto library to create session */
        params.op = (in) ? ODP_CRYPTO_OP_DECODE : ODP_CRYPTO_OP_ENCODE;
        params.auth_cipher_text = TRUE;
@@ -79,6 +86,7 @@  int create_ipsec_cache_entry(sa_db_entry_t *cipher_sa,
                params.cipher_key.length  = cipher_sa->key.length;
                params.iv.data = entry->state.iv;
                params.iv.length = cipher_sa->iv_len;
+               mode = cipher_sa->mode;
        } else {
                params.cipher_alg = ODP_CIPHER_ALG_NULL;
                params.iv.data = NULL;
@@ -90,6 +98,7 @@  int create_ipsec_cache_entry(sa_db_entry_t *cipher_sa,
                params.auth_alg = auth_sa->alg.u.auth;
                params.auth_key.data = auth_sa->key.data;
                params.auth_key.length = auth_sa->key.length;
+               mode = auth_sa->mode;
        } else {
                params.auth_alg = ODP_AUTH_ALG_NULL;
        }
@@ -128,6 +137,25 @@  int create_ipsec_cache_entry(sa_db_entry_t *cipher_sa,
                memcpy(&entry->ah.key, &auth_sa->key, sizeof(ipsec_key_t));
        }

+       if (tun) {
+               entry->tun_src_ip = tun->tun_src_ip;
+               entry->tun_dst_ip = tun->tun_dst_ip;
+               mode = IPSEC_SA_MODE_TUNNEL;
+
+               int ret;
+
+               if (!in) {
+                       /* init tun hdr id */
+                       ret = odp_random_data((uint8_t *)
+                                             &entry->state.tun_hdr_id,
+
 sizeof(entry->state.tun_hdr_id),
+                                             1);
+                       if (ret != sizeof(entry->state.tun_hdr_id))
+                               return -1;
+               }
+       }
+       entry->mode = mode;
+
        /* Initialize state */
        entry->state.esp_seq = 0;
        entry->state.ah_seq = 0;
@@ -156,7 +184,9 @@  ipsec_cache_entry_t *find_ipsec_cache_entry_in(uint32_t
src_ip,
        /* Look for a hit */
        for (; NULL != entry; entry = entry->next) {
                if ((entry->src_ip != src_ip) || (entry->dst_ip != dst_ip))
-                       continue;
+                       if ((entry->tun_src_ip != src_ip) ||
+                           (entry->tun_dst_ip != dst_ip))
+                               continue;
                if (ah &&
                    ((!entry->ah.alg) ||
                     (entry->ah.spi != odp_be_to_cpu_32(ah->spi))))
diff --git a/example/ipsec/odp_ipsec_cache.h
b/example/ipsec/odp_ipsec_cache.h
index 714cae8..5706007 100644
--- a/example/ipsec/odp_ipsec_cache.h
+++ b/example/ipsec/odp_ipsec_cache.h
@@ -34,6 +34,9 @@  typedef struct ipsec_cache_entry_s {
        odp_bool_t                   in_place;    /**< Crypto API mode */
        uint32_t                     src_ip;      /**< Source v4 address */
        uint32_t                     dst_ip;      /**< Destination v4
address */
+       sa_mode_t                    mode;        /**< SA mode -
transport/tun */
+       uint32_t                     tun_src_ip;  /**< Tunnel src IPv4 addr
*/
+       uint32_t                     tun_dst_ip;  /**< Tunnel dst IPv4 addr
*/
        struct {
                enum  odp_cipher_alg alg;         /**< Cipher algorithm */
                uint32_t             spi;         /**< Cipher SPI */
@@ -54,6 +57,7 @@  typedef struct ipsec_cache_entry_s {
                uint32_t      esp_seq;         /**< ESP TX sequence number
*/
                uint32_t      ah_seq;          /**< AH TX sequence number */
                uint8_t       iv[MAX_IV_LEN];  /**< ESP IV storage */
+               uint16be_t    tun_hdr_id;      /**< Tunnel header IP ID */
        } state;
 } ipsec_cache_entry_t;

@@ -78,6 +82,7 @@  void init_ipsec_cache(void);
  *
  * @param cipher_sa   Cipher SA DB entry pointer
  * @param auth_sa     Auth SA DB entry pointer
+ * @param tun         Tunnel DB entry pointer
  * @param api_mode    Crypto API mode for testing
  * @param in          Direction (input versus output)
  * @param completionq Completion queue
@@ -87,6 +92,7 @@  void init_ipsec_cache(void);
  */
 int create_ipsec_cache_entry(sa_db_entry_t *cipher_sa,
                             sa_db_entry_t *auth_sa,
+                            tun_db_entry_t *tun,
                             crypto_api_mode_e api_mode,
                             odp_bool_t in,
                             odp_queue_t completionq,
diff --git a/example/ipsec/odp_ipsec_sa_db.c
b/example/ipsec/odp_ipsec_sa_db.c
index 5837cb6..7967614 100644
--- a/example/ipsec/odp_ipsec_sa_db.c
+++ b/example/ipsec/odp_ipsec_sa_db.c
@@ -1,7 +1,7 @@ 
 /* Copyright (c) 2014, Linaro Limited
  * All rights reserved.
  *
- * SPDX-License-Identifier:     BSD-3-Clause
+ * SPDX-License-Identifier:    BSD-3-Clause
  */

 /* enable strtok */
@@ -19,6 +19,9 @@ 
 /** Global pointer to sa db */
 static sa_db_t *sa_db;

+/** Global pointer to tun db */
+static tun_db_t *tun_db;
+
 void init_sa_db(void)
 {
        odp_shm_t shm;
@@ -37,6 +40,23 @@  void init_sa_db(void)
        memset(sa_db, 0, sizeof(*sa_db));
 }

+void init_tun_db(void)
+{
+       odp_shm_t shm;
+
+       shm = odp_shm_reserve("shm_tun_db",
+                             sizeof(tun_db_t),
+                             ODP_CACHE_LINE_SIZE,
+                             0);
+       tun_db = odp_shm_addr(shm);
+
+       if (!tun_db) {
+               EXAMPLE_ERR("Error: shared mem alloc failed.\n");
+               exit(EXIT_FAILURE);
+       }
+       memset(tun_db, 0, sizeof(*tun_db));
+}
+
 int create_sa_db_entry(char *input, odp_bool_t cipher)
 {
        int pos = 0;
@@ -81,7 +101,7 @@  int create_sa_db_entry(char *input, odp_bool_t cipher)
                                        entry->alg.u.cipher =
                                                ODP_CIPHER_ALG_3DES_CBC;
                                        entry->block_len  = 8;
-                                       entry->iv_len     = 8;
+                                       entry->iv_len     = 8;
                                } else {
                                        entry->alg.u.cipher =
                                                ODP_CIPHER_ALG_NULL;
@@ -90,7 +110,7 @@  int create_sa_db_entry(char *input, odp_bool_t cipher)
                                if (0 == strcmp(token, "md5")) {
                                        entry->alg.u.auth =
                                                ODP_AUTH_ALG_MD5_96;
-                                       entry->icv_len    = 12;
+                                       entry->icv_len    = 12;
                                } else {
                                        entry->alg.u.auth =
ODP_AUTH_ALG_NULL;
                                }
@@ -132,6 +152,89 @@  int create_sa_db_entry(char *input, odp_bool_t cipher)
        return 0;
 }

+int create_tun_db_entry(char *input)
+{
+       int pos = 0;
+       char *local;
+       char *str;
+       char *save;
+       char *token;
+       tun_db_entry_t *entry = &tun_db->array[tun_db->index];
+
+       /* Verify we have a good entry */
+       if (MAX_DB <= tun_db->index)
+               return -1;
+
+       /* Make a local copy */
+       local = malloc(strlen(input) + 1);
+       if (NULL == local)
+               return -1;
+       strcpy(local, input);
+
+       /* Setup for using "strtok_r" to search input string */
+       str = local;
+       save = NULL;
+
+       /* Parse tokens separated by ':' */
+       while (NULL != (token = strtok_r(str, ":", &save))) {
+               str = NULL;  /* reset str for subsequent strtok_r calls */
+
+               /* Parse token based on its position */
+               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:
+                       parse_ipv4_string(token, &entry->tun_src_ip, NULL);
+                       break;
+               case 3:
+                       parse_ipv4_string(token, &entry->tun_dst_ip, NULL);
+                       break;
+               default:
+                       printf("ERROR: extra token \"%s\" at position %d\n",
+                              token, pos);
+                       break;
+               }
+               pos++;
+       }
+
+       /* Verify we parsed exactly the number of tokens we expected */
+       if (4 != pos) {
+               printf("ERROR: \"%s\" contains %d tokens, expected 4\n",
+                      input,
+                      pos);
+               free(local);
+               return -1;
+       }
+
+       /* Add route to the list */
+       tun_db->index++;
+       entry->next = tun_db->list;
+       tun_db->list = entry;
+
+       free(local);
+       return 0;
+}
+
+tun_db_entry_t *find_tun_db_entry(uint32_t ip_src,
+                                 uint32_t ip_dst)
+{
+       tun_db_entry_t *entry = NULL;
+
+       /* Scan all entries and return first match */
+       for (entry = tun_db->list; NULL != entry; entry = entry->next) {
+               if (entry->src_ip != ip_src)
+                       continue;
+               if (entry->dst_ip != ip_dst)
+                       continue;
+               break;
+       }
+       return entry;
+}
+
 void dump_sa_db(void)
 {
        sa_db_entry_t *entry;
@@ -182,3 +285,28 @@  sa_db_entry_t *find_sa_db_entry(ip_addr_range_t *src,
        }
        return entry;
 }
+
+void dump_tun_db(void)
+{
+       tun_db_entry_t *entry;
+
+       printf("\n"
+              "Tunnel table\n"
+              "--------------------------\n");
+
+       for (entry = tun_db->list; NULL != entry; entry = entry->next) {
+               char src_ip_str[MAX_STRING];
+               char dst_ip_str[MAX_STRING];
+               char tun_src_ip_str[MAX_STRING];
+               char tun_dst_ip_str[MAX_STRING];
+
+               printf(" %s:%s %s:%s ",
+                      ipv4_addr_str(src_ip_str, entry->src_ip),
+                      ipv4_addr_str(dst_ip_str, entry->dst_ip),
+                      ipv4_addr_str(tun_src_ip_str, entry->tun_src_ip),
+                      ipv4_addr_str(tun_dst_ip_str, entry->tun_dst_ip)
+                     );
+
+               printf("\n");
+       }
+}
diff --git a/example/ipsec/odp_ipsec_sa_db.h
b/example/ipsec/odp_ipsec_sa_db.h
index c30cbdb..409e82f 100644
--- a/example/ipsec/odp_ipsec_sa_db.h
+++ b/example/ipsec/odp_ipsec_sa_db.h
@@ -13,6 +13,10 @@  extern "C" {

 #include <odp_ipsec_misc.h>

+typedef enum sa_mode_s {
+       IPSEC_SA_MODE_TRANSPORT,
+       IPSEC_SA_MODE_TUNNEL
+} sa_mode_t;
 /**
  * Security Assocation (SA) data base entry
  */
@@ -26,6 +30,7 @@  typedef struct sa_db_entry_s {
        uint32_t              block_len; /**< Cipher block length */
        uint32_t              iv_len;    /**< Initialization Vector length
*/
        uint32_t              icv_len;   /**< Integrity Check Value length
*/
+       sa_mode_t             mode;      /**< SA mode - transport/tun */
 } sa_db_entry_t;

 /**
@@ -69,6 +74,56 @@  sa_db_entry_t *find_sa_db_entry(ip_addr_range_t *src,
                                ip_addr_range_t *dst,
                                odp_bool_t cipher);

+/**
+ * Tunnel entry
+ */
+typedef struct tun_db_entry_s {
+       struct tun_db_entry_s *next;
+       uint32_t        src_ip;        /**< Inner Source IPv4 address */
+       uint32_t        dst_ip;        /**< Inner Destination IPv4 address
*/
+       uint32_t        tun_src_ip; /**< Tunnel Source IPv4 address */
+       uint32_t        tun_dst_ip; /**< Tunnel Source IPv4 address */
+} tun_db_entry_t;
+
+/**
+ * Tunnel database
+ */
+typedef struct tun_db_s {
+       uint32_t         index;          /**< Index of next available entry
*/
+       tun_db_entry_t *list;    /**< List of active entries */
+       tun_db_entry_t array[MAX_DB]; /**< Entry storage */
+} tun_db_t;
+
+/** Initialize tun database global control structure */
+void init_tun_db(void);
+
+/**
+ * Create an tunnel DB entry
+ *
+ * String is of the format "SrcIP:DstIP:TunSrcIp:TunDstIp"
+ *
+ * @param input  Pointer to string describing tun
+ *
+ * @return 0 if successful else -1
+ */
+int create_tun_db_entry(char *input);
+
+/**
+ * Display the tun DB
+ */
+void dump_tun_db(void);
+
+/**
+ * Find a matching tun DB entry
+ *
+ * @param ip_src    Inner source IP address
+ * @param ip_dst    Inner destination IP address
+ *
+ * @return pointer to tun DB entry else NULL
+ */
+tun_db_entry_t *find_tun_db_entry(uint32_t ip_src,
+                                 uint32_t ip_dst);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/example/ipsec/odp_ipsec_stream.c
b/example/ipsec/odp_ipsec_stream.c
index 35042f5..a140d36 100644
--- a/example/ipsec/odp_ipsec_stream.c
+++ b/example/ipsec/odp_ipsec_stream.c
@@ -169,18 +169,24 @@  odp_packet_t create_ipv4_packet(stream_db_entry_t
*stream,
                                uint8_t *dmac,
                                odp_pool_t pkt_pool)
 {
-       ipsec_cache_entry_t *entry = stream->input.entry;
+       ipsec_cache_entry_t *entry = NULL;
        odp_packet_t pkt;
        uint8_t *base;
        uint8_t *data;
        odph_ethhdr_t *eth;
        odph_ipv4hdr_t *ip;
+       odph_ipv4hdr_t *inner_ip = NULL;
        odph_ahhdr_t *ah = NULL;
        odph_esphdr_t *esp = NULL;
        odph_icmphdr_t *icmp;
        stream_pkt_hdr_t *test;
        unsigned i;

+       if (stream->input.entry)
+               entry = stream->input.entry;
+       else if (stream->output.entry)
+               entry = stream->output.entry;
+
        /* Get packet */
        pkt = odp_packet_alloc(pkt_pool, 0);