From: Dmitry Eremin-Solenikov <dmitry.ereminsoleni...@linaro.org>

Implement support for handling IPv6 packets and IPv6 tunnels.

Signed-off-by: Dmitry Eremin-Solenikov <dmitry.ereminsoleni...@linaro.org>
---
/** Email created from pull request 304 (lumag:ipsec-ipv6-2)
 ** https://github.com/Linaro/odp/pull/304
 ** Patch: https://github.com/Linaro/odp/pull/304.patch
 ** Base sha: 4cb02e1caccb9179575e95448fd46979e17d0905
 ** Merge commit sha: 38498451ec522810c4433d56c78af6c4be05a7c7
 **/
 .../linux-generic/include/odp_ipsec_internal.h     |  44 +-
 platform/linux-generic/odp_ipsec.c                 | 462 ++++++++++++++++-----
 platform/linux-generic/odp_ipsec_sad.c             |  67 ++-
 3 files changed, 437 insertions(+), 136 deletions(-)

diff --git a/platform/linux-generic/include/odp_ipsec_internal.h 
b/platform/linux-generic/include/odp_ipsec_internal.h
index 06447870b..b294e7c4a 100644
--- a/platform/linux-generic/include/odp_ipsec_internal.h
+++ b/platform/linux-generic/include/odp_ipsec_internal.h
@@ -24,6 +24,8 @@ extern "C" {
 #include <odp/api/ipsec.h>
 #include <odp/api/ticketlock.h>
 
+#include <protocols/ip.h>
+
 /** @ingroup odp_ipsec
  *  @{
  */
@@ -127,10 +129,12 @@ struct ipsec_sa_s {
                        unsigned        dec_ttl : 1;
                        unsigned        copy_dscp : 1;
                        unsigned        copy_df : 1;
+                       unsigned        copy_flabel : 1;
                        unsigned        aes_ctr_iv : 1;
 
                        /* Only for outbound */
                        unsigned        use_counter_iv : 1;
+                       unsigned        tun_ipv4 : 1;
 
                        /* Only for inbound */
                        unsigned        antireplay : 1;
@@ -140,23 +144,38 @@ struct ipsec_sa_s {
        union {
                struct {
                        odp_ipsec_lookup_mode_t lookup_mode;
-                       odp_u32be_t     lookup_dst_ip;
+                       odp_ipsec_ip_version_t lookup_ver;
+                       union {
+                               odp_u32be_t     lookup_dst_ipv4;
+                               uint8_t lookup_dst_ipv6[_ODP_IPV6ADDR_LEN];
+                       };
                        odp_atomic_u64_t antireplay;
                } in;
 
                struct {
-                       odp_u32be_t     tun_src_ip;
-                       odp_u32be_t     tun_dst_ip;
-
-                       /* 32-bit from which low 16 are used */
-                       odp_atomic_u32_t tun_hdr_id;
-                       odp_atomic_u32_t seq;
-
                        odp_atomic_u64_t counter; /* for CTR/GCM */
+                       odp_atomic_u32_t seq;
 
-                       uint8_t         tun_ttl;
-                       uint8_t         tun_dscp;
-                       uint8_t         tun_df;
+                       union {
+                       struct {
+                               odp_u32be_t     src_ip;
+                               odp_u32be_t     dst_ip;
+
+                               /* 32-bit from which low 16 are used */
+                               odp_atomic_u32_t hdr_id;
+
+                               uint8_t         ttl;
+                               uint8_t         dscp;
+                               uint8_t         df;
+                       } tun_ipv4;
+                       struct {
+                               uint8_t         src_ip[_ODP_IPV6ADDR_LEN];
+                               uint8_t         dst_ip[_ODP_IPV6ADDR_LEN];
+                               uint8_t         hlimit;
+                               uint8_t         dscp;
+                               uint32_t        flabel;
+                       } tun_ipv6;
+                       };
                } out;
        };
 };
@@ -171,7 +190,8 @@ typedef struct odp_ipsec_sa_lookup_s {
        /** SPI value */
        uint32_t spi;
 
-       /* FIXME: IPv4 vs IPv6 */
+       /** IP protocol version */
+       odp_ipsec_ip_version_t ver;
 
        /** IP destination address (NETWORK ENDIAN) */
        void    *dst_addr;
diff --git a/platform/linux-generic/odp_ipsec.c 
b/platform/linux-generic/odp_ipsec.c
index 9f066badc..df7ac5c72 100644
--- a/platform/linux-generic/odp_ipsec.c
+++ b/platform/linux-generic/odp_ipsec.c
@@ -125,6 +125,8 @@ static inline int _odp_ipv4_csum(odp_packet_t pkt,
 
 #define _ODP_IPV4HDR_CSUM_OFFSET ODP_OFFSETOF(_odp_ipv4hdr_t, chksum)
 #define _ODP_IPV4HDR_PROTO_OFFSET ODP_OFFSETOF(_odp_ipv4hdr_t, proto)
+#define _ODP_IPV6HDR_NHDR_OFFSET ODP_OFFSETOF(_odp_ipv6hdr_t, next_hdr)
+#define _ODP_IPV6HDREXT_NHDR_OFFSET ODP_OFFSETOF(_odp_ipv6hdr_ext_t, next_hdr)
 
 /**
  * Calculate and fill in IPv4 checksum
@@ -159,11 +161,6 @@ static inline int _odp_ipv4_csum_update(odp_packet_t pkt)
 }
 
 #define ipv4_hdr_len(ip) (_ODP_IPV4HDR_IHL((ip)->ver_ihl) * 4)
-static inline
-void ipv4_adjust_len(_odp_ipv4hdr_t *ip, int adj)
-{
-       ip->tot_len = odp_cpu_to_be_16(odp_be_to_cpu_16(ip->tot_len) + adj);
-}
 
 static const uint8_t ipsec_padding[255] = {
              0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
@@ -219,13 +216,17 @@ static inline odp_pktio_parser_layer_t 
parse_layer(odp_ipsec_proto_layer_t l)
 }
 
 typedef struct {
-       _odp_ipv4hdr_t *ip;
+       void *ip;
        unsigned stats_length;
        uint16_t ip_offset;
        uint16_t ip_hdr_len;
        uint16_t ip_tot_len;
+       uint16_t ip_next_hdr_offset;
+       uint8_t  ip_next_hdr;
+       unsigned is_ipv4 : 1;
        union {
                struct {
+                       uint32_t ip_flabel;
                        uint16_t ip_df;
                        uint8_t  ip_tos;
                } out_tunnel;
@@ -233,25 +234,76 @@ typedef struct {
                        uint16_t hdr_len;
                        uint16_t trl_len;
                } in;
+               odp_u32be_t ipv4_addr;
+               uint8_t ipv6_addr[_ODP_IPV6ADDR_LEN];
        };
        union {
                struct {
                        uint8_t  tos;
                        uint8_t  ttl;
-                       uint16_t frag_offset;
+                       odp_u16be_t frag_offset;
                } ah_ipv4;
+               struct {
+                       odp_u32be_t ver_tc_flow;
+                       uint8_t hop_limit;
+               } ah_ipv6;
        };
        ipsec_aad_t aad;
        uint8_t iv[IPSEC_MAX_IV_LEN];
 } ipsec_state_t;
 
-static int ipsec_parse_ipv4(ipsec_state_t *state)
+static int ipsec_parse_ipv4(ipsec_state_t *state, odp_packet_t pkt)
 {
-       if (_ODP_IPV4HDR_IS_FRAGMENT(odp_be_to_cpu_16(state->ip->frag_offset)))
+       _odp_ipv4hdr_t ipv4hdr;
+
+       odp_packet_copy_to_mem(pkt, state->ip_offset,
+                              _ODP_IPV4HDR_LEN, &ipv4hdr);
+
+       if (_ODP_IPV4HDR_IS_FRAGMENT(odp_be_to_cpu_16(ipv4hdr.frag_offset)))
                return -1;
 
-       state->ip_hdr_len = ipv4_hdr_len(state->ip);
-       state->ip_tot_len = odp_be_to_cpu_16(state->ip->tot_len);
+       state->ip_hdr_len = ipv4_hdr_len(&ipv4hdr);
+       state->ip_tot_len = odp_be_to_cpu_16(ipv4hdr.tot_len);
+       state->ip_next_hdr = ipv4hdr.proto;
+       state->ip_next_hdr_offset = state->ip_offset +
+               _ODP_IPV4HDR_PROTO_OFFSET;
+       state->ipv4_addr = ipv4hdr.dst_addr;
+
+       return 0;
+}
+
+static int ipsec_parse_ipv6(ipsec_state_t *state, odp_packet_t pkt)
+{
+       _odp_ipv6hdr_t ipv6hdr;
+       _odp_ipv6hdr_ext_t ipv6hdrext;
+
+       odp_packet_copy_to_mem(pkt, state->ip_offset,
+                              _ODP_IPV6HDR_LEN, &ipv6hdr);
+
+       state->ip_hdr_len = _ODP_IPV6HDR_LEN;
+       state->ip_next_hdr = ipv6hdr.next_hdr;
+       state->ip_next_hdr_offset = state->ip_offset + _ODP_IPV6HDR_NHDR_OFFSET;
+       /* FIXME: Jumbo frames */
+       state->ip_tot_len = odp_be_to_cpu_16(ipv6hdr.payload_len) +
+                           _ODP_IPV6HDR_LEN;
+       memcpy(state->ipv6_addr, &ipv6hdr.dst_addr, _ODP_IPV6ADDR_LEN);
+
+       while (state->ip_next_hdr == _ODP_IPPROTO_HOPOPTS ||
+              state->ip_next_hdr == _ODP_IPPROTO_DEST ||
+              state->ip_next_hdr == _ODP_IPPROTO_ROUTE) {
+               odp_packet_copy_to_mem(pkt,
+                                      state->ip_offset + state->ip_hdr_len,
+                                      sizeof(ipv6hdrext),
+                                      &ipv6hdrext);
+               state->ip_next_hdr = ipv6hdrext.next_hdr;
+               state->ip_next_hdr_offset = state->ip_offset +
+                       state->ip_hdr_len +
+                       _ODP_IPV6HDREXT_NHDR_OFFSET;
+               state->ip_hdr_len += (ipv6hdrext.ext_len + 1) * 8;
+       }
+
+       if (_ODP_IPPROTO_FRAG == state->ip_next_hdr)
+               return -1;
 
        return 0;
 }
@@ -259,6 +311,7 @@ static int ipsec_parse_ipv4(ipsec_state_t *state)
 static inline ipsec_sa_t *ipsec_get_sa(odp_ipsec_sa_t sa,
                                       odp_ipsec_protocol_t proto,
                                       uint32_t spi,
+                                      odp_ipsec_ip_version_t ver,
                                       void *dst_addr,
                                       odp_ipsec_op_status_t *status)
 {
@@ -269,6 +322,7 @@ static inline ipsec_sa_t *ipsec_get_sa(odp_ipsec_sa_t sa,
 
                lookup.proto = proto;
                lookup.spi = spi;
+               lookup.ver = ver;
                lookup.dst_addr = dst_addr;
 
                ipsec_sa = _odp_ipsec_sa_lookup(&lookup);
@@ -332,7 +386,9 @@ static int ipsec_in_esp(odp_packet_t *pkt,
 
        ipsec_sa = ipsec_get_sa(sa, ODP_IPSEC_ESP,
                                odp_be_to_cpu_32(esp.spi),
-                               &state->ip->dst_addr, status);
+                               state->is_ipv4 ? ODP_IPSEC_IPV4 :
+                                               ODP_IPSEC_IPV6,
+                               &state->ipv4_addr, status);
        *_ipsec_sa = ipsec_sa;
        if (status->error.all)
                return -1;
@@ -386,8 +442,10 @@ static int ipsec_in_esp_post(odp_packet_t pkt,
                                 ipsec_padding, esptrl.pad_len) != 0)
                return -1;
 
-       state->ip->proto = esptrl.next_header;
+       odp_packet_copy_from_mem(pkt, state->ip_next_hdr_offset,
+                                1, &esptrl.next_header);
        state->in.trl_len += esptrl.pad_len;
+       state->ip_next_hdr = esptrl.next_header;
 
        return 0;
 }
@@ -413,7 +471,9 @@ static int ipsec_in_ah(odp_packet_t *pkt,
 
        ipsec_sa = ipsec_get_sa(sa, ODP_IPSEC_AH,
                                odp_be_to_cpu_32(ah.spi),
-                               &state->ip->dst_addr, status);
+                               state->is_ipv4 ? ODP_IPSEC_IPV4 :
+                                               ODP_IPSEC_IPV6,
+                               &state->ipv4_addr, status);
        *_ipsec_sa = ipsec_sa;
        if (status->error.all)
                return -1;
@@ -429,19 +489,31 @@ static int ipsec_in_ah(odp_packet_t *pkt,
        state->in.hdr_len = (ah.ah_len + 2) * 4;
        state->in.trl_len = 0;
 
-       /* Save everything to context */
-       state->ah_ipv4.tos = state->ip->tos;
-       state->ah_ipv4.frag_offset = state->ip->frag_offset;
-       state->ah_ipv4.ttl = state->ip->ttl;
+       if (state->is_ipv4) {
+               _odp_ipv4hdr_t *ipv4hdr = state->ip;
+
+               /* Save everything to context */
+               state->ah_ipv4.tos = ipv4hdr->tos;
+               state->ah_ipv4.frag_offset = ipv4hdr->frag_offset;
+               state->ah_ipv4.ttl = ipv4hdr->ttl;
+
+               /* FIXME: zero copy of header, passing it to crypto! */
+               /*
+                * If authenticating, zero the mutable fields build the request
+                */
+               ipv4hdr->chksum = 0;
+               ipv4hdr->tos = 0;
+               ipv4hdr->frag_offset = 0;
+               ipv4hdr->ttl = 0;
+       } else {
+               _odp_ipv6hdr_t *ipv6hdr = state->ip;
 
-       /* FIXME: zero copy of header, passing it to crypto! */
-       /*
-        * If authenticating, zero the mutable fields build the request
-        */
-       state->ip->chksum = 0;
-       state->ip->tos = 0;
-       state->ip->frag_offset = 0;
-       state->ip->ttl = 0;
+               state->ah_ipv6.ver_tc_flow = ipv6hdr->ver_tc_flow;
+               state->ah_ipv6.hop_limit = ipv6hdr->hop_limit;
+               ipv6hdr->ver_tc_flow =
+                       odp_cpu_to_be_32(6 << _ODP_IPV6HDR_VERSION_SHIFT);
+               ipv6hdr->hop_limit = 0;
+       }
 
        state->aad.spi = ah.spi;
        state->aad.seq_no = ah.seq_no;
@@ -470,12 +542,23 @@ static int ipsec_in_ah_post(odp_packet_t pkt,
                                   sizeof(ah), &ah) < 0)
                return -1;
 
-       state->ip->proto = ah.next_header;
+       odp_packet_copy_from_mem(pkt, state->ip_next_hdr_offset,
+                                1, &ah.next_header);
 
        /* Restore mutable fields */
-       state->ip->ttl = state->ah_ipv4.ttl;
-       state->ip->tos = state->ah_ipv4.tos;
-       state->ip->frag_offset = state->ah_ipv4.frag_offset;
+       if (state->is_ipv4) {
+               _odp_ipv4hdr_t *ipv4hdr = state->ip;
+
+               ipv4hdr->ttl = state->ah_ipv4.ttl;
+               ipv4hdr->tos = state->ah_ipv4.tos;
+               ipv4hdr->frag_offset = state->ah_ipv4.frag_offset;
+       } else {
+               _odp_ipv6hdr_t *ipv6hdr = odp_packet_l3_ptr(pkt, NULL);
+
+               ipv6hdr->ver_tc_flow = state->ah_ipv6.ver_tc_flow;
+               ipv6hdr->hop_limit = state->ah_ipv6.hop_limit;
+       }
+       state->ip_next_hdr = ah.next_header;
 
        return 0;
 }
@@ -501,7 +584,17 @@ static ipsec_sa_t *ipsec_in_single(odp_packet_t pkt,
        /* Initialize parameters block */
        memset(&param, 0, sizeof(param));
 
-       rc = ipsec_parse_ipv4(&state);
+       /*
+        * FIXME: maybe use packet flag as below ???
+        * This adds requirement that input packets contain not only valid
+        * l3/l4 offsets, but also valid packet flags
+        * state.is_ipv4 = odp_packet_has_ipv4(pkt);
+        */
+       state.is_ipv4 = (((uint8_t *)state.ip)[0] >> 4) == 0x4;
+       if (state.is_ipv4)
+               rc = ipsec_parse_ipv4(&state, pkt);
+       else
+               rc = ipsec_parse_ipv6(&state, pkt);
        if (rc < 0 ||
            state.ip_tot_len + state.ip_offset > odp_packet_len(pkt)) {
                status->error.alg = 1;
@@ -509,9 +602,9 @@ static ipsec_sa_t *ipsec_in_single(odp_packet_t pkt,
        }
 
        /* Check IP header for IPSec protocols and look it up */
-       if (_ODP_IPPROTO_ESP == state.ip->proto) {
+       if (_ODP_IPPROTO_ESP == state.ip_next_hdr) {
                rc = ipsec_in_esp(&pkt, &state, &ipsec_sa, sa, &param, status);
-       } else if (_ODP_IPPROTO_AH == state.ip->proto) {
+       } else if (_ODP_IPPROTO_AH == state.ip_next_hdr) {
                rc = ipsec_in_ah(&pkt, &state, &ipsec_sa, sa, &param, status);
        } else {
                status->error.proto = 1;
@@ -587,6 +680,7 @@ static ipsec_sa_t *ipsec_in_single(odp_packet_t pkt,
                status->error.alg = 1;
                goto err;
        }
+       state.ip_tot_len -= state.in.trl_len;
 
        if (ODP_IPSEC_MODE_TUNNEL == ipsec_sa->mode) {
                /* We have a tunneled IPv4 packet, strip outer and IPsec
@@ -600,11 +694,14 @@ static ipsec_sa_t *ipsec_in_single(odp_packet_t pkt,
                        status->error.alg = 1;
                        goto err;
                }
-
-               if (odp_packet_len(pkt) > sizeof(*state.ip)) {
-                       state.ip = odp_packet_l3_ptr(pkt, NULL);
-                       state.ip->ttl -= ipsec_sa->dec_ttl;
-                       _odp_ipv4_csum_update(pkt);
+               state.ip_tot_len -= state.ip_hdr_len + state.in.hdr_len;
+               if (_ODP_IPPROTO_IPIP == state.ip_next_hdr) {
+                       state.is_ipv4 = 1;
+               } else if (_ODP_IPPROTO_IPV6 == state.ip_next_hdr) {
+                       state.is_ipv4 = 0;
+               } else {
+                       status->error.proto = 1;
+                       goto err;
                }
        } else {
                odp_packet_move_data(pkt, state.in.hdr_len, 0,
@@ -614,13 +711,30 @@ static ipsec_sa_t *ipsec_in_single(odp_packet_t pkt,
                        status->error.alg = 1;
                        goto err;
                }
+               state.ip_tot_len -= state.in.hdr_len;
+       }
 
-               if (odp_packet_len(pkt) > sizeof(*state.ip)) {
-                       state.ip = odp_packet_l3_ptr(pkt, NULL);
-                       ipv4_adjust_len(state.ip,
-                                       -(state.in.hdr_len + state.in.trl_len));
-                       _odp_ipv4_csum_update(pkt);
-               }
+       /* Finalize the IPv4 header */
+       if (state.is_ipv4 && odp_packet_len(pkt) > _ODP_IPV4HDR_LEN) {
+               _odp_ipv4hdr_t *ipv4hdr = odp_packet_l3_ptr(pkt, NULL);
+
+               if (ODP_IPSEC_MODE_TRANSPORT == ipsec_sa->mode)
+                       ipv4hdr->tot_len = odp_cpu_to_be_16(state.ip_tot_len);
+               else
+                       ipv4hdr->ttl -= ipsec_sa->dec_ttl;
+               _odp_ipv4_csum_update(pkt);
+       } else if (!state.is_ipv4 && odp_packet_len(pkt) > _ODP_IPV6HDR_LEN) {
+               _odp_ipv6hdr_t *ipv6hdr = odp_packet_l3_ptr(pkt, NULL);
+
+               if (ODP_IPSEC_MODE_TRANSPORT == ipsec_sa->mode)
+                       ipv6hdr->payload_len =
+                               odp_cpu_to_be_16(state.ip_tot_len -
+                                                _ODP_IPV6HDR_LEN);
+               else
+                       ipv6hdr->hop_limit -= ipsec_sa->dec_ttl;
+       } else {
+               status->error.proto = 1;
+               goto err;
        }
 
        pkt_hdr = odp_packet_hdr(pkt);
@@ -628,7 +742,10 @@ static ipsec_sa_t *ipsec_in_single(odp_packet_t pkt,
        packet_parse_reset(pkt_hdr);
 
        packet_parse_l3_l4(pkt_hdr, parse_layer(ipsec_config.inbound.parse),
-                          state.ip_offset, _ODP_ETHTYPE_IPV4);
+                          state.ip_offset,
+                          state.is_ipv4 ?
+                          _ODP_ETHTYPE_IPV4 :
+                          _ODP_ETHTYPE_IPV6);
 
        *pkt_out = pkt;
 
@@ -662,6 +779,24 @@ static int ipsec_out_tunnel_parse_ipv4(ipsec_state_t 
*state,
        ipv4hdr->ttl -= ipsec_sa->dec_ttl;
        state->out_tunnel.ip_tos = ipv4hdr->tos;
        state->out_tunnel.ip_df = _ODP_IPV4HDR_FLAGS_DONT_FRAG(flags);
+       state->out_tunnel.ip_flabel = 0;
+
+       return 0;
+}
+
+static int ipsec_out_tunnel_parse_ipv6(ipsec_state_t *state,
+                                      ipsec_sa_t *ipsec_sa)
+{
+       _odp_ipv6hdr_t *ipv6hdr = state->ip;
+
+       ipv6hdr->hop_limit -= ipsec_sa->dec_ttl;
+       state->out_tunnel.ip_tos = (ipv6hdr->ver_tc_flow &
+                                   _ODP_IPV6HDR_TC_MASK) >>
+               _ODP_IPV6HDR_TC_SHIFT;
+       state->out_tunnel.ip_df = 0;
+       state->out_tunnel.ip_flabel = (ipv6hdr->ver_tc_flow &
+                                      _ODP_IPV6HDR_FLOW_LABEL_MASK) >>
+               _ODP_IPV6HDR_FLOW_LABEL_SHIFT;
 
        return 0;
 }
@@ -679,26 +814,25 @@ static int ipsec_out_tunnel_ipv4(odp_packet_t *pkt,
        else
                out_ip.tos = (state->out_tunnel.ip_tos &
                              ~_ODP_IP_TOS_DSCP_MASK) |
-                            (ipsec_sa->out.tun_dscp <<
+                            (ipsec_sa->out.tun_ipv4.dscp <<
                              _ODP_IP_TOS_DSCP_SHIFT);
        state->ip_tot_len = odp_packet_len(*pkt) - state->ip_offset;
        state->ip_tot_len += _ODP_IPV4HDR_LEN;
 
        out_ip.tot_len = odp_cpu_to_be_16(state->ip_tot_len);
        /* No need to convert to BE: ID just should not be duplicated */
-       out_ip.id = odp_atomic_fetch_add_u32(&ipsec_sa->out.tun_hdr_id,
+       out_ip.id = odp_atomic_fetch_add_u32(&ipsec_sa->out.tun_ipv4.hdr_id,
                                             1);
        if (ipsec_sa->copy_df)
                flags = state->out_tunnel.ip_df;
        else
-               flags = ((uint16_t)ipsec_sa->out.tun_df) << 14;
+               flags = ((uint16_t)ipsec_sa->out.tun_ipv4.df) << 14;
        out_ip.frag_offset = odp_cpu_to_be_16(flags);
-       out_ip.ttl = ipsec_sa->out.tun_ttl;
-       out_ip.proto = _ODP_IPPROTO_IPIP;
+       out_ip.ttl = ipsec_sa->out.tun_ipv4.ttl;
        /* Will be filled later by packet checksum update */
        out_ip.chksum = 0;
-       out_ip.src_addr = ipsec_sa->out.tun_src_ip;
-       out_ip.dst_addr = ipsec_sa->out.tun_dst_ip;
+       out_ip.src_addr = ipsec_sa->out.tun_ipv4.src_ip;
+       out_ip.dst_addr = ipsec_sa->out.tun_ipv4.dst_ip;
 
        if (odp_packet_extend_head(pkt, _ODP_IPV4HDR_LEN,
                                   NULL, NULL) < 0)
@@ -713,6 +847,70 @@ static int ipsec_out_tunnel_ipv4(odp_packet_t *pkt,
 
        state->ip = odp_packet_l3_ptr(*pkt, NULL);
        state->ip_hdr_len = _ODP_IPV4HDR_LEN;
+       if (state->is_ipv4)
+               state->ip_next_hdr = _ODP_IPPROTO_IPIP;
+       else
+               state->ip_next_hdr = _ODP_IPPROTO_IPV6;
+       state->ip_next_hdr_offset = state->ip_offset +
+               _ODP_IPV4HDR_PROTO_OFFSET;
+
+       state->is_ipv4 = 1;
+
+       return 0;
+}
+
+static int ipsec_out_tunnel_ipv6(odp_packet_t *pkt,
+                                ipsec_state_t *state,
+                                ipsec_sa_t *ipsec_sa)
+{
+       _odp_ipv6hdr_t out_ip;
+       uint32_t ver;
+
+       ver = 6 << _ODP_IPV6HDR_VERSION_SHIFT;
+       if (ipsec_sa->copy_dscp)
+               ver |= state->out_tunnel.ip_tos << _ODP_IPV6HDR_TC_SHIFT;
+       else
+               ver |= ((state->out_tunnel.ip_tos &
+                        ~_ODP_IP_TOS_DSCP_MASK) |
+                       (ipsec_sa->out.tun_ipv6.dscp <<
+                        _ODP_IP_TOS_DSCP_SHIFT)) <<
+                       _ODP_IPV6HDR_TC_SHIFT;
+       if (ipsec_sa->copy_flabel)
+               ver |= state->out_tunnel.ip_flabel;
+       else
+               ver |= ipsec_sa->out.tun_ipv6.flabel;
+       out_ip.ver_tc_flow = odp_cpu_to_be_32(ver);
+
+       state->ip_tot_len = odp_packet_len(*pkt) - state->ip_offset;
+       out_ip.payload_len = odp_cpu_to_be_16(state->ip_tot_len);
+       state->ip_tot_len += _ODP_IPV6HDR_LEN;
+
+       out_ip.hop_limit = ipsec_sa->out.tun_ipv6.hlimit;
+       memcpy(&out_ip.src_addr, ipsec_sa->out.tun_ipv6.src_ip,
+              _ODP_IPV6ADDR_LEN);
+       memcpy(&out_ip.dst_addr, ipsec_sa->out.tun_ipv6.dst_ip,
+              _ODP_IPV6ADDR_LEN);
+
+       if (odp_packet_extend_head(pkt, _ODP_IPV6HDR_LEN,
+                                  NULL, NULL) < 0)
+               return -1;
+
+       odp_packet_move_data(*pkt, 0, _ODP_IPV6HDR_LEN, state->ip_offset);
+
+       odp_packet_copy_from_mem(*pkt, state->ip_offset,
+                                sizeof(out_ip), &out_ip);
+
+       odp_packet_l4_offset_set(*pkt, state->ip_offset + _ODP_IPV6HDR_LEN);
+
+       state->ip = odp_packet_l3_ptr(*pkt, NULL);
+       state->ip_hdr_len = _ODP_IPV6HDR_LEN;
+       if (state->is_ipv4)
+               state->ip_next_hdr = _ODP_IPPROTO_IPIP;
+       else
+               state->ip_next_hdr = _ODP_IPPROTO_IPV6;
+       state->ip_next_hdr_offset = state->ip_offset + _ODP_IPV6HDR_NHDR_OFFSET;
+
+       state->is_ipv4 = 0;
 
        return 0;
 }
@@ -769,6 +967,7 @@ static int ipsec_out_esp(odp_packet_t *pkt,
        uint16_t ipsec_offset = state->ip_offset + state->ip_hdr_len;
        unsigned hdr_len;
        unsigned trl_len;
+       uint8_t proto = _ODP_IPPROTO_ESP;
 
        /* ESP trailer should be 32-bit right aligned */
        if (pad_block < 4)
@@ -787,24 +986,6 @@ static int ipsec_out_esp(odp_packet_t *pkt,
 
        param->override_iv_ptr = state->iv;
 
-       if (odp_packet_extend_tail(pkt, trl_len, NULL, NULL) < 0 ||
-           odp_packet_extend_head(pkt, hdr_len, NULL, NULL) < 0)
-               return -1;
-
-       odp_packet_move_data(*pkt, 0, hdr_len, ipsec_offset);
-
-       state->ip = odp_packet_l3_ptr(*pkt, NULL);
-
-       /* Set IPv4 length before authentication */
-       ipv4_adjust_len(state->ip, hdr_len + trl_len);
-       state->ip_tot_len += hdr_len + trl_len;
-
-       uint32_t esptrl_offset = state->ip_offset +
-                                state->ip_hdr_len +
-                                hdr_len +
-                                encrypt_len -
-                                _ODP_ESPTRL_LEN;
-
        memset(&esp, 0, sizeof(esp));
        esp.spi = odp_cpu_to_be_32(ipsec_sa->spi);
        esp.seq_no = odp_cpu_to_be_32(ipsec_seq_no(ipsec_sa));
@@ -816,8 +997,32 @@ static int ipsec_out_esp(odp_packet_t *pkt,
 
        memset(&esptrl, 0, sizeof(esptrl));
        esptrl.pad_len = encrypt_len - ip_data_len - _ODP_ESPTRL_LEN;
-       esptrl.next_header = state->ip->proto;
-       state->ip->proto = _ODP_IPPROTO_ESP;
+       esptrl.next_header = state->ip_next_hdr;
+
+       odp_packet_copy_from_mem(*pkt, state->ip_next_hdr_offset, 1, &proto);
+       state->ip_tot_len += hdr_len + trl_len;
+       if (state->is_ipv4) {
+               _odp_ipv4hdr_t *ipv4hdr = state->ip;
+
+               ipv4hdr->tot_len = odp_cpu_to_be_16(state->ip_tot_len);
+       } else {
+               _odp_ipv6hdr_t *ipv6hdr = state->ip;
+
+               ipv6hdr->payload_len = odp_cpu_to_be_16(state->ip_tot_len -
+                                                       _ODP_IPV6HDR_LEN);
+       }
+
+       if ((odp_packet_extend_tail(pkt, trl_len, NULL, NULL) < 0) ||
+           (odp_packet_extend_head(pkt, hdr_len, NULL, NULL) < 0))
+               return -1;
+
+       odp_packet_move_data(*pkt, 0, hdr_len, ipsec_offset);
+
+       uint32_t esptrl_offset = state->ip_offset +
+                                state->ip_hdr_len +
+                                hdr_len +
+                                encrypt_len -
+                                _ODP_ESPTRL_LEN;
 
        odp_packet_copy_from_mem(*pkt,
                                 ipsec_offset, _ODP_ESPHDR_LEN,
@@ -852,6 +1057,12 @@ static int ipsec_out_esp(odp_packet_t *pkt,
        return 0;
 }
 
+static void ipsec_out_esp_post(ipsec_state_t *state, odp_packet_t pkt)
+{
+       if (state->is_ipv4)
+               _odp_ipv4_csum_update(pkt);
+}
+
 static int ipsec_out_ah(odp_packet_t *pkt,
                        ipsec_state_t *state,
                        ipsec_sa_t *ipsec_sa,
@@ -861,29 +1072,44 @@ static int ipsec_out_ah(odp_packet_t *pkt,
        unsigned hdr_len = _ODP_AHHDR_LEN + ipsec_sa->esp_iv_len +
                ipsec_sa->icv_len;
        uint16_t ipsec_offset = state->ip_offset + state->ip_hdr_len;
-
-       /* Save IPv4 stuff */
-       state->ah_ipv4.tos = state->ip->tos;
-       state->ah_ipv4.frag_offset = state->ip->frag_offset;
-       state->ah_ipv4.ttl = state->ip->ttl;
-
-       if (odp_packet_extend_head(pkt, hdr_len, NULL, NULL) < 0)
-               return -1;
-
-       odp_packet_move_data(*pkt, 0, hdr_len, ipsec_offset);
-
-       state->ip = odp_packet_l3_ptr(*pkt, NULL);
-
-       /* Set IPv4 length before authentication */
-       ipv4_adjust_len(state->ip, hdr_len);
-       state->ip_tot_len += hdr_len;
+       uint8_t proto = _ODP_IPPROTO_AH;
 
        memset(&ah, 0, sizeof(ah));
        ah.spi = odp_cpu_to_be_32(ipsec_sa->spi);
-       ah.ah_len = 1 + (ipsec_sa->esp_iv_len + ipsec_sa->icv_len) / 4;
        ah.seq_no = odp_cpu_to_be_32(ipsec_seq_no(ipsec_sa));
-       ah.next_header = state->ip->proto;
-       state->ip->proto = _ODP_IPPROTO_AH;
+       ah.next_header = state->ip_next_hdr;
+
+       odp_packet_copy_from_mem(*pkt, state->ip_next_hdr_offset, 1, &proto);
+       /* Save IP stuff */
+       if (state->is_ipv4) {
+               _odp_ipv4hdr_t *ipv4hdr = state->ip;
+
+               state->ah_ipv4.tos = ipv4hdr->tos;
+               state->ah_ipv4.frag_offset = ipv4hdr->frag_offset;
+               state->ah_ipv4.ttl = ipv4hdr->ttl;
+               ipv4hdr->chksum = 0;
+               ipv4hdr->tos = 0;
+               ipv4hdr->frag_offset = 0;
+               ipv4hdr->ttl = 0;
+               hdr_len = IPSEC_PAD_LEN(hdr_len, 4);
+               state->ip_tot_len += hdr_len;
+               ipv4hdr->tot_len = odp_cpu_to_be_16(state->ip_tot_len);
+       } else {
+               _odp_ipv6hdr_t *ipv6hdr = state->ip;
+
+               state->ah_ipv6.ver_tc_flow = ipv6hdr->ver_tc_flow;
+               state->ah_ipv6.hop_limit = ipv6hdr->hop_limit;
+               ipv6hdr->ver_tc_flow =
+                       odp_cpu_to_be_32(6 << _ODP_IPV6HDR_VERSION_SHIFT);
+               ipv6hdr->hop_limit = 0;
+
+               hdr_len = IPSEC_PAD_LEN(hdr_len, 8);
+               state->ip_tot_len += hdr_len;
+               ipv6hdr->payload_len = odp_cpu_to_be_16(state->ip_tot_len -
+                                                       _ODP_IPV6HDR_LEN);
+       }
+
+       ah.ah_len = hdr_len / 4 - 2;
 
        state->aad.spi = ah.spi;
        state->aad.seq_no = ah.seq_no;
@@ -896,6 +1122,11 @@ static int ipsec_out_ah(odp_packet_t *pkt,
 
        param->override_iv_ptr = state->iv;
 
+       if (odp_packet_extend_head(pkt, hdr_len, NULL, NULL) < 0)
+               return -1;
+
+       odp_packet_move_data(*pkt, 0, hdr_len, ipsec_offset);
+
        odp_packet_copy_from_mem(*pkt,
                                 ipsec_offset, _ODP_AHHDR_LEN,
                                 &ah);
@@ -906,12 +1137,8 @@ static int ipsec_out_ah(odp_packet_t *pkt,
        _odp_packet_set_data(*pkt,
                             ipsec_offset + _ODP_AHHDR_LEN +
                               ipsec_sa->esp_iv_len,
-                            0, ipsec_sa->icv_len);
-
-       state->ip->chksum = 0;
-       state->ip->tos = 0;
-       state->ip->frag_offset = 0;
-       state->ip->ttl = 0;
+                            0,
+                            hdr_len - _ODP_AHHDR_LEN - ipsec_sa->esp_iv_len);
 
        param->auth_range.offset = state->ip_offset;
        param->auth_range.length = state->ip_tot_len;
@@ -923,11 +1150,22 @@ static int ipsec_out_ah(odp_packet_t *pkt,
        return 0;
 }
 
-static void ipsec_out_ah_post(ipsec_state_t *state)
+static void ipsec_out_ah_post(ipsec_state_t *state, odp_packet_t pkt)
 {
-       state->ip->ttl = state->ah_ipv4.ttl;
-       state->ip->tos = state->ah_ipv4.tos;
-       state->ip->frag_offset = state->ah_ipv4.frag_offset;
+       if (state->is_ipv4) {
+               _odp_ipv4hdr_t *ipv4hdr = odp_packet_l3_ptr(pkt, NULL);
+
+               ipv4hdr->ttl = state->ah_ipv4.ttl;
+               ipv4hdr->tos = state->ah_ipv4.tos;
+               ipv4hdr->frag_offset = state->ah_ipv4.frag_offset;
+
+               _odp_ipv4_csum_update(pkt);
+       } else {
+               _odp_ipv6hdr_t *ipv6hdr = odp_packet_l3_ptr(pkt, NULL);
+
+               ipv6hdr->ver_tc_flow = state->ah_ipv6.ver_tc_flow;
+               ipv6hdr->hop_limit = state->ah_ipv6.hop_limit;
+       }
 }
 
 static ipsec_sa_t *ipsec_out_single(odp_packet_t pkt,
@@ -955,18 +1193,30 @@ static ipsec_sa_t *ipsec_out_single(odp_packet_t pkt,
        /* Initialize parameters block */
        memset(&param, 0, sizeof(param));
 
+       state.is_ipv4 = (((uint8_t *)state.ip)[0] >> 4) == 0x4;
+
        if (ODP_IPSEC_MODE_TRANSPORT == ipsec_sa->mode) {
-               rc = ipsec_parse_ipv4(&state);
+               if (state.is_ipv4)
+                       rc = ipsec_parse_ipv4(&state, pkt);
+               else
+                       rc = ipsec_parse_ipv6(&state, pkt);
+
                if (state.ip_tot_len + state.ip_offset != odp_packet_len(pkt))
                        rc = -1;
        } else {
-               rc = ipsec_out_tunnel_parse_ipv4(&state, ipsec_sa);
+               if (state.is_ipv4)
+                       rc = ipsec_out_tunnel_parse_ipv4(&state, ipsec_sa);
+               else
+                       rc = ipsec_out_tunnel_parse_ipv6(&state, ipsec_sa);
                if (rc < 0) {
                        status->error.alg = 1;
                        goto err;
                }
 
-               rc = ipsec_out_tunnel_ipv4(&pkt, &state, ipsec_sa);
+               if (ipsec_sa->tun_ipv4)
+                       rc = ipsec_out_tunnel_ipv4(&pkt, &state, ipsec_sa);
+               else
+                       rc = ipsec_out_tunnel_ipv6(&pkt, &state, ipsec_sa);
        }
        if (rc < 0) {
                status->error.alg = 1;
@@ -1023,7 +1273,9 @@ static ipsec_sa_t *ipsec_out_single(odp_packet_t pkt,
        }
 
        /* Finalize the IPv4 header */
-       if (ODP_IPSEC_AH == ipsec_sa->proto)
+       if (ODP_IPSEC_ESP == ipsec_sa->proto)
+               ipsec_out_esp_post(&state, pkt);
+       else if (ODP_IPSEC_AH == ipsec_sa->proto)
                ipsec_out_ah_post(&state, pkt);
 
        _odp_ipv4_csum_update(pkt);
diff --git a/platform/linux-generic/odp_ipsec_sad.c 
b/platform/linux-generic/odp_ipsec_sad.c
index 0287d6f73..812ad0c46 100644
--- a/platform/linux-generic/odp_ipsec_sad.c
+++ b/platform/linux-generic/odp_ipsec_sad.c
@@ -211,10 +211,18 @@ odp_ipsec_sa_t odp_ipsec_sa_create(const 
odp_ipsec_sa_param_t *param)
        ipsec_sa->flags = 0;
        if (ODP_IPSEC_DIR_INBOUND == param->dir) {
                ipsec_sa->in.lookup_mode = param->inbound.lookup_mode;
-               if (ODP_IPSEC_LOOKUP_DSTADDR_SPI == ipsec_sa->in.lookup_mode)
-                       memcpy(&ipsec_sa->in.lookup_dst_ip,
-                              param->inbound.lookup_param.dst_addr,
-                              sizeof(ipsec_sa->in.lookup_dst_ip));
+               if (ODP_IPSEC_LOOKUP_DSTADDR_SPI == ipsec_sa->in.lookup_mode) {
+                       ipsec_sa->in.lookup_ver =
+                               param->inbound.lookup_param.ip_version;
+                       if (ODP_IPSEC_IPV4 == ipsec_sa->in.lookup_ver)
+                               memcpy(&ipsec_sa->in.lookup_dst_ipv4,
+                                      param->inbound.lookup_param.dst_addr,
+                                      sizeof(ipsec_sa->in.lookup_dst_ipv4));
+                       else
+                               memcpy(&ipsec_sa->in.lookup_dst_ipv6,
+                                      param->inbound.lookup_param.dst_addr,
+                                      sizeof(ipsec_sa->in.lookup_dst_ipv6));
+               }
 
                if (param->inbound.antireplay_ws > IPSEC_ANTIREPLAY_WS)
                        return ODP_IPSEC_SA_INVALID;
@@ -226,6 +234,7 @@ odp_ipsec_sa_t odp_ipsec_sa_create(const 
odp_ipsec_sa_param_t *param)
        ipsec_sa->dec_ttl = param->opt.dec_ttl;
        ipsec_sa->copy_dscp = param->opt.copy_dscp;
        ipsec_sa->copy_df = param->opt.copy_df;
+       ipsec_sa->copy_flabel = param->opt.copy_flabel;
 
        odp_atomic_store_u64(&ipsec_sa->bytes, 0);
        odp_atomic_store_u64(&ipsec_sa->packets, 0);
@@ -236,19 +245,36 @@ odp_ipsec_sa_t odp_ipsec_sa_create(const 
odp_ipsec_sa_param_t *param)
 
        if (ODP_IPSEC_MODE_TUNNEL == ipsec_sa->mode &&
            ODP_IPSEC_DIR_OUTBOUND == param->dir) {
-               if (param->outbound.tunnel.type != ODP_IPSEC_TUNNEL_IPV4)
-                       goto error;
-
-               memcpy(&ipsec_sa->out.tun_src_ip,
-                      param->outbound.tunnel.ipv4.src_addr,
-                      sizeof(ipsec_sa->out.tun_src_ip));
-               memcpy(&ipsec_sa->out.tun_dst_ip,
-                      param->outbound.tunnel.ipv4.dst_addr,
-                      sizeof(ipsec_sa->out.tun_dst_ip));
-               odp_atomic_init_u32(&ipsec_sa->out.tun_hdr_id, 0);
-               ipsec_sa->out.tun_ttl = param->outbound.tunnel.ipv4.ttl;
-               ipsec_sa->out.tun_dscp = param->outbound.tunnel.ipv4.dscp;
-               ipsec_sa->out.tun_df = param->outbound.tunnel.ipv4.df;
+               if (ODP_IPSEC_TUNNEL_IPV4 == param->outbound.tunnel.type) {
+                       ipsec_sa->tun_ipv4 = 1;
+                       memcpy(&ipsec_sa->out.tun_ipv4.src_ip,
+                              param->outbound.tunnel.ipv4.src_addr,
+                              sizeof(ipsec_sa->out.tun_ipv4.src_ip));
+                       memcpy(&ipsec_sa->out.tun_ipv4.dst_ip,
+                              param->outbound.tunnel.ipv4.dst_addr,
+                              sizeof(ipsec_sa->out.tun_ipv4.dst_ip));
+                       odp_atomic_init_u32(&ipsec_sa->out.tun_ipv4.hdr_id, 0);
+                       ipsec_sa->out.tun_ipv4.ttl =
+                               param->outbound.tunnel.ipv4.ttl;
+                       ipsec_sa->out.tun_ipv4.dscp =
+                               param->outbound.tunnel.ipv4.dscp;
+                       ipsec_sa->out.tun_ipv4.df =
+                               param->outbound.tunnel.ipv4.df;
+               } else {
+                       ipsec_sa->tun_ipv4 = 0;
+                       memcpy(&ipsec_sa->out.tun_ipv6.src_ip,
+                              param->outbound.tunnel.ipv6.src_addr,
+                              sizeof(ipsec_sa->out.tun_ipv6.src_ip));
+                       memcpy(&ipsec_sa->out.tun_ipv6.dst_ip,
+                              param->outbound.tunnel.ipv6.dst_addr,
+                              sizeof(ipsec_sa->out.tun_ipv6.dst_ip));
+                       ipsec_sa->out.tun_ipv6.hlimit =
+                               param->outbound.tunnel.ipv6.hlimit;
+                       ipsec_sa->out.tun_ipv6.dscp =
+                               param->outbound.tunnel.ipv6.dscp;
+                       ipsec_sa->out.tun_ipv6.flabel =
+                               param->outbound.tunnel.ipv6.flabel;
+               }
        }
 
        odp_crypto_session_param_init(&crypto_param);
@@ -485,8 +511,11 @@ ipsec_sa_t *_odp_ipsec_sa_lookup(const ipsec_sa_lookup_t 
*lookup)
                if (ODP_IPSEC_LOOKUP_DSTADDR_SPI == ipsec_sa->in.lookup_mode &&
                    lookup->proto == ipsec_sa->proto &&
                    lookup->spi == ipsec_sa->spi &&
-                   !memcmp(lookup->dst_addr, &ipsec_sa->in.lookup_dst_ip,
-                           sizeof(ipsec_sa->in.lookup_dst_ip))) {
+                   lookup->ver == ipsec_sa->in.lookup_ver &&
+                   !memcmp(lookup->dst_addr, &ipsec_sa->in.lookup_dst_ipv4,
+                           lookup->ver == ODP_IPSEC_IPV4 ?
+                                   _ODP_IPV4ADDR_LEN :
+                                   _ODP_IPV6ADDR_LEN)) {
                        if (NULL != best)
                                _odp_ipsec_sa_unuse(best);
                        return ipsec_sa;

Reply via email to