diff mbox series

[API-NEXT,v4,6/11] linux-gen: ipsec: implement IPv6 protocol support

Message ID 1511575212-10926-7-git-send-email-odpbot@yandex.ru
State Superseded
Headers show
Series [API-NEXT,v4,1/11] validation: ipsec: add ipv4 name parts | expand

Commit Message

Github ODP bot Nov. 25, 2017, 2 a.m. UTC
From: Dmitry Eremin-Solenikov <dmitry.ereminsolenikov@linaro.org>


Implement support for handling IPv6 packets and IPv6 tunnels.

Signed-off-by: Dmitry Eremin-Solenikov <dmitry.ereminsolenikov@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: b95e7d08043ba45d12635c5afaa3fbf3a07ecc90
 ** Merge commit sha: 9985269a954a3e183d4f19860e20e4f9b497cfdf
 **/
 .../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 mbox series

Patch

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;