diff --git a/examples/ipsec-secgw/ipsec-secgw.c b/examples/ipsec-secgw/ipsec-secgw.c
index 0d1fd6af6..2b0b8226b 100644
--- a/examples/ipsec-secgw/ipsec-secgw.c
+++ b/examples/ipsec-secgw/ipsec-secgw.c
@@ -562,11 +562,10 @@ send_single_packet(struct rte_mbuf *m, uint16_t port, uint8_t proto)
 }
 
 static inline void
-inbound_sp_sa(struct sp_ctx *sp, struct sa_ctx *sa, struct traffic_type *ip,
-		uint16_t lim)
+inbound_sp_sa(struct sp_ctx *sp, struct traffic_type *ip, uint16_t lim)
 {
 	struct rte_mbuf *m;
-	uint32_t i, j, res, sa_idx;
+	uint32_t i, j, res;
 
 	if (ip->num == 0 || sp == NULL)
 		return;
@@ -593,8 +592,7 @@ inbound_sp_sa(struct sp_ctx *sp, struct sa_ctx *sa, struct traffic_type *ip,
 			continue;
 		}
 
-		sa_idx = SPI2IDX(res);
-		if (!inbound_sa_check(sa, m, sa_idx)) {
+		if (!inbound_sa_check(m, res)) {
 			rte_pktmbuf_free(m);
 			continue;
 		}
@@ -652,15 +650,16 @@ process_pkts_inbound(struct ipsec_ctx *ipsec_ctx,
 				traffic->ipsec.num, MAX_PKT_BURST);
 		split46_traffic(traffic, traffic->ipsec.pkts, nb_pkts_in);
 	} else {
-		inbound_sa_lookup(ipsec_ctx->sa_ctx, traffic->ipsec.pkts,
-			traffic->ipsec.saptr, traffic->ipsec.num);
+		inbound_sa_lookup(ipsec_ctx->sa4_ctx, ipsec_ctx->sa6_ctx,
+			traffic->ipsec.pkts, traffic->ipsec.saptr,
+			traffic->ipsec.num);
 		ipsec_process(ipsec_ctx, traffic);
 	}
 
-	inbound_sp_sa(ipsec_ctx->sp4_ctx, ipsec_ctx->sa_ctx, &traffic->ip4,
+	inbound_sp_sa(ipsec_ctx->sp4_ctx, &traffic->ip4,
 			n_ip4);
 
-	inbound_sp_sa(ipsec_ctx->sp6_ctx, ipsec_ctx->sa_ctx, &traffic->ip6,
+	inbound_sp_sa(ipsec_ctx->sp6_ctx, &traffic->ip6,
 			n_ip6);
 }
 
@@ -728,8 +727,9 @@ process_pkts_outbound(struct ipsec_ctx *ipsec_ctx,
 			}
 		}
 	} else {
-		outbound_sa_lookup(ipsec_ctx->sa_ctx, traffic->ipsec.res,
-			traffic->ipsec.saptr, traffic->ipsec.num);
+		outbound_sa_lookup(ipsec_ctx->sa4_ctx, ipsec_ctx->sa6_ctx,
+			traffic->ipsec.res, traffic->ipsec.saptr,
+			traffic->ipsec.pkts, traffic->ipsec.num);
 		ipsec_process(ipsec_ctx, traffic);
 	}
 }
@@ -770,8 +770,9 @@ process_pkts_inbound_nosp(struct ipsec_ctx *ipsec_ctx,
 			}
 		}
 	} else {
-		inbound_sa_lookup(ipsec_ctx->sa_ctx, traffic->ipsec.pkts,
-			traffic->ipsec.saptr, traffic->ipsec.num);
+		inbound_sa_lookup(ipsec_ctx->sa4_ctx, ipsec_ctx->sa6_ctx,
+			traffic->ipsec.pkts, traffic->ipsec.saptr,
+			traffic->ipsec.num);
 		ipsec_process(ipsec_ctx, traffic);
 	}
 }
@@ -823,8 +824,9 @@ process_pkts_outbound_nosp(struct ipsec_ctx *ipsec_ctx,
 				traffic->ip6.pkts[i] = traffic->ipsec.pkts[i];
 		}
 	} else {
-		outbound_sa_lookup(ipsec_ctx->sa_ctx, traffic->ipsec.res,
-			traffic->ipsec.saptr, traffic->ipsec.num);
+		outbound_sa_lookup(ipsec_ctx->sa4_ctx, ipsec_ctx->sa6_ctx,
+			traffic->ipsec.res, traffic->ipsec.saptr,
+			traffic->ipsec.pkts, traffic->ipsec.num);
 		ipsec_process(ipsec_ctx, traffic);
 	}
 }
@@ -1042,13 +1044,13 @@ drain_inbound_crypto_queues(const struct lcore_conf *qconf,
 
 	/* process ipv4 packets */
 	if (trf.ip4.num != 0) {
-		inbound_sp_sa(ctx->sp4_ctx, ctx->sa_ctx, &trf.ip4, 0);
+		inbound_sp_sa(ctx->sp4_ctx, &trf.ip4, 0);
 		route4_pkts(qconf->rt4_ctx, trf.ip4.pkts, trf.ip4.num);
 	}
 
 	/* process ipv6 packets */
 	if (trf.ip6.num != 0) {
-		inbound_sp_sa(ctx->sp6_ctx, ctx->sa_ctx, &trf.ip6, 0);
+		inbound_sp_sa(ctx->sp6_ctx, &trf.ip6, 0);
 		route6_pkts(qconf->rt6_ctx, trf.ip6.pkts, trf.ip6.num);
 	}
 }
@@ -1109,14 +1111,16 @@ main_loop(__attribute__((unused)) void *dummy)
 	qconf->rt6_ctx = socket_ctx[socket_id].rt_ip6;
 	qconf->inbound.sp4_ctx = socket_ctx[socket_id].sp_ip4_in;
 	qconf->inbound.sp6_ctx = socket_ctx[socket_id].sp_ip6_in;
-	qconf->inbound.sa_ctx = socket_ctx[socket_id].sa_in;
+	qconf->inbound.sa4_ctx = socket_ctx[socket_id].sa_ip4_in;
+	qconf->inbound.sa6_ctx = socket_ctx[socket_id].sa_ip6_in;
 	qconf->inbound.cdev_map = cdev_map_in;
 	qconf->inbound.session_pool = socket_ctx[socket_id].session_pool;
 	qconf->inbound.session_priv_pool =
 			socket_ctx[socket_id].session_priv_pool;
 	qconf->outbound.sp4_ctx = socket_ctx[socket_id].sp_ip4_out;
 	qconf->outbound.sp6_ctx = socket_ctx[socket_id].sp_ip6_out;
-	qconf->outbound.sa_ctx = socket_ctx[socket_id].sa_out;
+	qconf->outbound.sa4_ctx = socket_ctx[socket_id].sa_ip4_out;
+	qconf->outbound.sa6_ctx = socket_ctx[socket_id].sa_ip6_out;
 	qconf->outbound.cdev_map = cdev_map_out;
 	qconf->outbound.session_pool = socket_ctx[socket_id].session_pool;
 	qconf->outbound.session_priv_pool =
@@ -2481,8 +2485,10 @@ main(int32_t argc, char **argv)
 	for (i = 0; i < NB_SOCKETS && i < rte_socket_count(); i++) {
 		socket_id = rte_socket_id_by_idx(i);
 		if ((socket_ctx[socket_id].mbuf_pool != NULL) &&
-			(socket_ctx[socket_id].sa_in == NULL) &&
-			(socket_ctx[socket_id].sa_out == NULL)) {
+			(socket_ctx[socket_id].sa_ip4_in == NULL) &&
+			(socket_ctx[socket_id].sa_ip6_in == NULL) &&
+			(socket_ctx[socket_id].sa_ip4_out == NULL) &&
+			(socket_ctx[socket_id].sa_ip6_out == NULL)) {
 			sa_init(&socket_ctx[socket_id], socket_id);
 			sp4_init(&socket_ctx[socket_id], socket_id);
 			sp6_init(&socket_ctx[socket_id], socket_id);
diff --git a/examples/ipsec-secgw/ipsec.c b/examples/ipsec-secgw/ipsec.c
index dc85adfe5..1d4cb3167 100644
--- a/examples/ipsec-secgw/ipsec.c
+++ b/examples/ipsec-secgw/ipsec.c
@@ -581,7 +581,7 @@ ipsec_inbound(struct ipsec_ctx *ctx, struct rte_mbuf *pkts[],
 {
 	struct ipsec_sa *sas[nb_pkts];
 
-	inbound_sa_lookup(ctx->sa_ctx, pkts, sas, nb_pkts);
+	inbound_sa_lookup(ctx->sa4_ctx, ctx->sa6_ctx, pkts, sas, nb_pkts);
 
 	ipsec_enqueue(esp_inbound, ctx, pkts, sas, nb_pkts);
 
@@ -601,7 +601,8 @@ ipsec_outbound(struct ipsec_ctx *ctx, struct rte_mbuf *pkts[],
 {
 	struct ipsec_sa *sas[nb_pkts];
 
-	outbound_sa_lookup(ctx->sa_ctx, sa_idx, sas, nb_pkts);
+	outbound_sa_lookup(ctx->sa4_ctx, ctx->sa6_ctx, sa_idx, sas, pkts,
+			nb_pkts);
 
 	ipsec_enqueue(esp_outbound, ctx, pkts, sas, nb_pkts);
 
diff --git a/examples/ipsec-secgw/ipsec.h b/examples/ipsec-secgw/ipsec.h
index 1efa6e488..736dec33b 100644
--- a/examples/ipsec-secgw/ipsec.h
+++ b/examples/ipsec-secgw/ipsec.h
@@ -174,7 +174,8 @@ struct ipsec_ctx {
 	struct rte_hash *cdev_map;
 	struct sp_ctx *sp4_ctx;
 	struct sp_ctx *sp6_ctx;
-	struct sa_ctx *sa_ctx;
+	struct sa_ctx *sa4_ctx;
+	struct sa_ctx *sa6_ctx;
 	uint16_t nb_qps;
 	uint16_t last_qp;
 	struct cdev_qp tbl[MAX_QP_PER_LCORE];
@@ -194,8 +195,10 @@ struct cdev_key {
 };
 
 struct socket_ctx {
-	struct sa_ctx *sa_in;
-	struct sa_ctx *sa_out;
+	struct sa_ctx *sa_ip4_in;
+	struct sa_ctx *sa_ip4_out;
+	struct sa_ctx *sa_ip6_in;
+	struct sa_ctx *sa_ip6_out;
 	struct sp_ctx *sp_ip4_in;
 	struct sp_ctx *sp_ip4_out;
 	struct sp_ctx *sp_ip6_in;
@@ -285,15 +288,17 @@ get_sym_cop(struct rte_crypto_op *cop)
 }
 
 int
-inbound_sa_check(struct sa_ctx *sa_ctx, struct rte_mbuf *m, uint32_t sa_idx);
+inbound_sa_check(struct rte_mbuf *m, uint32_t sa_idx);
 
 void
-inbound_sa_lookup(struct sa_ctx *sa_ctx, struct rte_mbuf *pkts[],
-		struct ipsec_sa *sa[], uint16_t nb_pkts);
+inbound_sa_lookup(struct sa_ctx *sa4_ctx, struct sa_ctx *sa6_ctx,
+		struct rte_mbuf *pkts[], struct ipsec_sa *sa[],
+		uint16_t nb_pkts);
 
 void
-outbound_sa_lookup(struct sa_ctx *sa_ctx, uint32_t sa_idx[],
-		struct ipsec_sa *sa[], uint16_t nb_pkts);
+outbound_sa_lookup(struct sa_ctx *sa4_ctx, struct sa_ctx *sa6_ctx,
+		uint32_t sa_idx[], struct ipsec_sa *sa[],
+		struct rte_mbuf *pkts[], uint16_t nb_pkts);
 
 void
 sp4_init(struct socket_ctx *ctx, int32_t socket_id);
diff --git a/examples/ipsec-secgw/sa.c b/examples/ipsec-secgw/sa.c
index c3cf3bd1f..015d5cf01 100644
--- a/examples/ipsec-secgw/sa.c
+++ b/examples/ipsec-secgw/sa.c
@@ -130,11 +130,18 @@ const struct supported_aead_algo aead_algos[] = {
 	}
 };
 
-static struct ipsec_sa sa_out[IPSEC_SA_MAX_ENTRIES];
-static uint32_t nb_sa_out;
+struct ipsec_sa_db {
+	struct ipsec_sa sadb[IPSEC_SA_MAX_ENTRIES];
+	uint32_t nb;
+};
+
+static struct ipsec_sa_db sa_out;
+static struct ipsec_sa_db sa_in;
 
-static struct ipsec_sa sa_in[IPSEC_SA_MAX_ENTRIES];
-static uint32_t nb_sa_in;
+static struct ipsec_sa_db sa_ip4_out;
+static struct ipsec_sa_db sa_ip4_in;
+static struct ipsec_sa_db sa_ip6_out;
+static struct ipsec_sa_db sa_ip6_in;
 
 static const struct supported_cipher_algo *
 find_match_cipher_algo(const char *cipher_keyword)
@@ -236,23 +243,23 @@ parse_sa_tokens(char **tokens, uint32_t n_tokens,
 	uint32_t portid_p = 0;
 
 	if (strcmp(tokens[0], "in") == 0) {
-		ri = &nb_sa_in;
+		ri = &sa_in.nb;
 
 		APP_CHECK(*ri <= IPSEC_SA_MAX_ENTRIES - 1, status,
 			"too many sa rules, abort insertion\n");
 		if (status->status < 0)
 			return;
 
-		rule = &sa_in[*ri];
+		rule = &sa_in.sadb[*ri];
 	} else {
-		ri = &nb_sa_out;
+		ri = &sa_out.nb;
 
 		APP_CHECK(*ri <= IPSEC_SA_MAX_ENTRIES - 1, status,
 			"too many sa rules, abort insertion\n");
 		if (status->status < 0)
 			return;
 
-		rule = &sa_out[*ri];
+		rule = &sa_out.sadb[*ri];
 	}
 
 	/* spi number */
@@ -778,39 +785,6 @@ check_eth_dev_caps(uint16_t portid, uint32_t inbound)
 	return 0;
 }
 
-/*
- * Helper function, tries to determine next_proto for SPI
- * by searching though SP rules.
- */
-static int
-get_spi_proto(uint32_t spi, enum rte_security_ipsec_sa_direction dir,
-		struct ip_addr ip_addr[2], uint32_t mask[2])
-{
-	int32_t rc4, rc6;
-
-	rc4 = sp4_spi_present(spi, dir == RTE_SECURITY_IPSEC_SA_DIR_INGRESS,
-				ip_addr, mask);
-	rc6 = sp6_spi_present(spi, dir == RTE_SECURITY_IPSEC_SA_DIR_INGRESS,
-				ip_addr, mask);
-
-	if (rc4 >= 0) {
-		if (rc6 >= 0) {
-			RTE_LOG(ERR, IPSEC,
-				"%s: SPI %u used simultaeously by "
-				"IPv4(%d) and IPv6 (%d) SP rules\n",
-				__func__, spi, rc4, rc6);
-			return -EINVAL;
-		} else
-			return IPPROTO_IPIP;
-	} else if (rc6 < 0) {
-		RTE_LOG(ERR, IPSEC,
-			"%s: SPI %u is not used by any SP rule\n",
-			__func__, spi);
-		return -EINVAL;
-	} else
-		return IPPROTO_IPV6;
-}
-
 /*
  * Helper function for getting source and destination IP addresses
  * from SP. Needed for inline crypto transport mode, as addresses are not
@@ -819,16 +793,21 @@ get_spi_proto(uint32_t spi, enum rte_security_ipsec_sa_direction dir,
  * addresses got from SP into SA.
  */
 static int
-sa_add_address_inline_crypto(struct ipsec_sa *sa)
+sa_add_address_inline_crypto(struct ipsec_sa *sa, int ip_v)
 {
-	int protocol;
+	int rc4, rc6;
 	struct ip_addr ip_addr[2];
 	uint32_t mask[2];
 
-	protocol = get_spi_proto(sa->spi, sa->direction, ip_addr, mask);
-	if (protocol < 0)
-		return protocol;
-	else if (protocol == IPPROTO_IPIP) {
+	if (ip_v == IPPROTO_IPIP) {
+		rc4 = sp4_spi_present(sa->spi, sa->direction, ip_addr, mask);
+		if (rc4 < 0) {
+			RTE_LOG(ERR, IPSEC,
+			"%s: SPI %u is not used by any SP rule\n",
+			__func__, sa->spi);
+			return rc4;
+		}
+
 		sa->flags |= IP4_TRANSPORT;
 		if (mask[0] == IP4_FULL_MASK &&
 				mask[1] == IP4_FULL_MASK &&
@@ -844,7 +823,15 @@ sa_add_address_inline_crypto(struct ipsec_sa *sa)
 			__func__, sa->spi);
 			return -EINVAL;
 		}
-	} else if (protocol == IPPROTO_IPV6) {
+	} else if (ip_v == IPPROTO_IPV6) {
+		rc6 = sp6_spi_present(sa->spi, sa->direction, ip_addr, mask);
+		if (rc6 < 0) {
+			RTE_LOG(ERR, IPSEC,
+			"%s: SPI %u is not used by any SP rule\n",
+			__func__, sa->spi);
+			return rc6;
+		}
+
 		sa->flags |= IP6_TRANSPORT;
 		if (mask[0] == IP6_FULL_MASK &&
 				mask[1] == IP6_FULL_MASK &&
@@ -866,9 +853,93 @@ sa_add_address_inline_crypto(struct ipsec_sa *sa)
 	return 0;
 }
 
+/*
+ * Determine to what SAD put new SA entry:
+ * 1) tunnel-inbound - check IP address of a tunnel (outer IP header)
+ *	if tunnel is IPv4, add SA entry to IPv4 SAD
+ *	if tunnel is IPv6, add SA entry to IPv6 SAD
+ * 2) transport-inbound - check inbound SPD
+ *	if there is matching entry in IPv4 SPD, add SA entry to IPv4 SAD
+ *	if there is matching entry in IPv6 SPD, add SA entry to IPv6 SAD
+ * 3) tunnel-outbound/transport-outbound - check outbound SPD
+ *	if there is matching entry in IPv4 SPD, add SA entry to IPv4 SAD
+ *	if there is matching entry in IPv6 SPD, add SA entry to IPv6 SAD
+ */
+static int
+sa_split(struct ipsec_sa_db *sa4, struct ipsec_sa_db *sa6,
+		const struct ipsec_sa_db *ent, int sa4_ip_v[],
+		int sa6_ip_v[], uint32_t inbound)
+{
+	uint32_t i, idx4, idx6;
+	int rc4, rc6;
+	idx4 = 0;
+	idx6 = 0;
+
+	for (i = 0; i < ent->nb; i++) {
+		rc4 = sp4_spi_present(ent->sadb[i].spi, inbound, NULL, NULL);
+		rc6 = sp6_spi_present(ent->sadb[i].spi, inbound, NULL, NULL);
+
+		if (rc4 < 0 && rc6 < 0) {
+			RTE_LOG(ERR, IPSEC,
+			"%s: SPI %u is not used by any SP rule\n",
+			__func__, ent->sadb[i].spi);
+			return -EINVAL;
+		}
+
+		if (inbound) {
+			if (rc4 >= 0) {
+				if (IS_IP6_TUNNEL(ent->sadb[i].flags)) {
+					sa6_ip_v[idx6] = IPPROTO_IPIP;
+					sa6->sadb[idx6++] = ent->sadb[i];
+				} else {
+					sa4_ip_v[idx4] = IPPROTO_IPIP;
+					sa4->sadb[idx4++] = ent->sadb[i];
+				}
+			}
+			if (rc6 >= 0) {
+				if (IS_IP4_TUNNEL(ent->sadb[i].flags)) {
+					sa4_ip_v[idx4] = IPPROTO_IPV6;
+					sa4->sadb[idx4++] = ent->sadb[i];
+				} else {
+					sa6_ip_v[idx6] = IPPROTO_IPV6;
+					sa6->sadb[idx6++] = ent->sadb[i];
+				}
+			}
+		} else {
+			if (rc4 >= 0) {
+				sa4_ip_v[idx4] = IPPROTO_IPIP;
+				sa4->sadb[idx4++] = ent->sadb[i];
+			}
+			if (rc6 >= 0) {
+				sa6_ip_v[idx6] = IPPROTO_IPV6;
+				sa6->sadb[idx6++] = ent->sadb[i];
+			}
+		}
+	}
+
+		sa4->nb = idx4;
+		sa6->nb = idx6;
+
+	return 0;
+}
+
+static int
+sa_in_split(struct ipsec_sa_db *sa4, struct ipsec_sa_db *sa6,
+		const struct ipsec_sa_db *ent, int sa4_ip_v[], int sa6_ip_v[])
+{
+	return sa_split(sa4, sa6, ent, sa4_ip_v, sa6_ip_v, 1);
+}
+
+static int
+sa_out_split(struct ipsec_sa_db *sa4, struct ipsec_sa_db *sa6,
+		const struct ipsec_sa_db *ent, int sa4_ip_v[], int sa6_ip_v[])
+{
+	return sa_split(sa4, sa6, ent, sa4_ip_v, sa6_ip_v, 0);
+}
+
 static int
-sa_add_rules(struct sa_ctx *sa_ctx, const struct ipsec_sa entries[],
-		uint32_t nb_entries, uint32_t inbound,
+sa_add_rules(struct sa_ctx *sa_ctx, const struct ipsec_sa_db *entries,
+		uint32_t inbound, int ip_v,
 		struct socket_ctx *skt_ctx)
 {
 	struct ipsec_sa *sa;
@@ -880,15 +951,15 @@ sa_add_rules(struct sa_ctx *sa_ctx, const struct ipsec_sa entries[],
 	/* for ESN upper 32 bits of SQN also need to be part of AAD */
 	aad_length = (app_sa_prm.enable_esn != 0) ? sizeof(uint32_t) : 0;
 
-	for (i = 0; i < nb_entries; i++) {
-		idx = SPI2IDX(entries[i].spi);
+	for (i = 0; i < entries->nb; i++) {
+		idx = SPI2IDX(entries->sadb[i].spi);
 		sa = &sa_ctx->sa[idx];
 		if (sa->spi != 0) {
 			printf("Index %u already in use by SPI %u\n",
 					idx, sa->spi);
 			return -EINVAL;
 		}
-		*sa = entries[i];
+		*sa = entries->sadb[i];
 		sa->seq = 0;
 
 		if (sa->type == RTE_SECURITY_ACTION_TYPE_INLINE_PROTOCOL ||
@@ -910,7 +981,7 @@ sa_add_rules(struct sa_ctx *sa_ctx, const struct ipsec_sa entries[],
 			if (sa->type ==
 				RTE_SECURITY_ACTION_TYPE_INLINE_CRYPTO) {
 				inline_status =
-					sa_add_address_inline_crypto(sa);
+					sa_add_address_inline_crypto(sa, ip_v);
 				if (inline_status < 0)
 					return inline_status;
 			}
@@ -1023,17 +1094,17 @@ sa_add_rules(struct sa_ctx *sa_ctx, const struct ipsec_sa entries[],
 }
 
 static inline int
-sa_out_add_rules(struct sa_ctx *sa_ctx, const struct ipsec_sa entries[],
-		uint32_t nb_entries, struct socket_ctx *skt_ctx)
+sa_out_add_rules(struct sa_ctx *sa_ctx, const struct ipsec_sa_db *entries,
+		int ip_v, struct socket_ctx *skt_ctx)
 {
-	return sa_add_rules(sa_ctx, entries, nb_entries, 0, skt_ctx);
+	return sa_add_rules(sa_ctx, entries, 0, ip_v, skt_ctx);
 }
 
 static inline int
-sa_in_add_rules(struct sa_ctx *sa_ctx, const struct ipsec_sa entries[],
-		uint32_t nb_entries, struct socket_ctx *skt_ctx)
+sa_in_add_rules(struct sa_ctx *sa_ctx, const struct ipsec_sa_db *entries,
+		int ip_v, struct socket_ctx *skt_ctx)
 {
-	return sa_add_rules(sa_ctx, entries, nb_entries, 1, skt_ctx);
+	return sa_add_rules(sa_ctx, entries, 1, ip_v, skt_ctx);
 }
 
 /*
@@ -1052,19 +1123,8 @@ fill_ipsec_app_sa_prm(struct rte_ipsec_sa_prm *prm,
 
 static int
 fill_ipsec_sa_prm(struct rte_ipsec_sa_prm *prm, const struct ipsec_sa *ss,
-	const struct rte_ipv4_hdr *v4, struct rte_ipv6_hdr *v6)
+	const struct rte_ipv4_hdr *v4, struct rte_ipv6_hdr *v6, int ip_v)
 {
-	int32_t rc;
-
-	/*
-	 * Try to get SPI next proto by searching that SPI in SPD.
-	 * probably not the optimal way, but there seems nothing
-	 * better right now.
-	 */
-	rc = get_spi_proto(ss->spi, ss->direction, NULL, NULL);
-	if (rc < 0)
-		return rc;
-
 	fill_ipsec_app_sa_prm(prm, &app_sa_prm);
 	prm->userdata = (uintptr_t)ss;
 
@@ -1082,16 +1142,16 @@ fill_ipsec_sa_prm(struct rte_ipsec_sa_prm *prm, const struct ipsec_sa *ss,
 	if (IS_IP4_TUNNEL(ss->flags)) {
 		prm->ipsec_xform.tunnel.type = RTE_SECURITY_IPSEC_TUNNEL_IPV4;
 		prm->tun.hdr_len = sizeof(*v4);
-		prm->tun.next_proto = rc;
+		prm->tun.next_proto = ip_v;
 		prm->tun.hdr = v4;
 	} else if (IS_IP6_TUNNEL(ss->flags)) {
 		prm->ipsec_xform.tunnel.type = RTE_SECURITY_IPSEC_TUNNEL_IPV6;
 		prm->tun.hdr_len = sizeof(*v6);
-		prm->tun.next_proto = rc;
+		prm->tun.next_proto = ip_v;
 		prm->tun.hdr = v6;
 	} else {
 		/* transport mode */
-		prm->trs.proto = rc;
+		prm->trs.proto = ip_v;
 	}
 
 	/* setup crypto section */
@@ -1134,7 +1194,8 @@ fill_ipsec_session(struct rte_ipsec_session *ss, struct rte_ipsec_sa *sa,
  * Initialise related rte_ipsec_sa object.
  */
 static int
-ipsec_sa_init(struct ipsec_sa *lsa, struct rte_ipsec_sa *sa, uint32_t sa_size)
+ipsec_sa_init(struct ipsec_sa *lsa, struct rte_ipsec_sa *sa, uint32_t sa_size,
+		int ip_v)
 {
 	int rc;
 	struct rte_ipsec_sa_prm prm;
@@ -1156,7 +1217,7 @@ ipsec_sa_init(struct ipsec_sa *lsa, struct rte_ipsec_sa *sa, uint32_t sa_size)
 		memcpy(v6.dst_addr, lsa->dst.ip.ip6.ip6_b, sizeof(v6.dst_addr));
 	}
 
-	rc = fill_ipsec_sa_prm(&prm, lsa, &v4, &v6);
+	rc = fill_ipsec_sa_prm(&prm, lsa, &v4, &v6, ip_v);
 	if (rc == 0)
 		rc = rte_ipsec_sa_init(sa, &prm, sa_size);
 	if (rc < 0)
@@ -1172,7 +1233,7 @@ ipsec_sa_init(struct ipsec_sa *lsa, struct rte_ipsec_sa *sa, uint32_t sa_size)
  */
 static int
 ipsec_satbl_init(struct sa_ctx *ctx, const struct ipsec_sa *ent,
-	uint32_t nb_ent, int32_t socket)
+	uint32_t nb_ent, int32_t socket, int ip_v[])
 {
 	int32_t rc, sz;
 	uint32_t i, idx;
@@ -1183,7 +1244,7 @@ ipsec_satbl_init(struct sa_ctx *ctx, const struct ipsec_sa *ent,
 
 	/* determine SA size */
 	idx = SPI2IDX(ent[0].spi);
-	fill_ipsec_sa_prm(&prm, ctx->sa + idx, NULL, NULL);
+	fill_ipsec_sa_prm(&prm, ctx->sa + idx, NULL, NULL, ip_v[0]);
 	sz = rte_ipsec_sa_size(&prm);
 	if (sz < 0) {
 		RTE_LOG(ERR, IPSEC, "%s(%p, %u, %d): "
@@ -1209,8 +1270,7 @@ ipsec_satbl_init(struct sa_ctx *ctx, const struct ipsec_sa *ent,
 
 		sa = (struct rte_ipsec_sa *)((uintptr_t)ctx->satbl + sz * i);
 		lsa = ctx->sa + idx;
-
-		rc = ipsec_sa_init(lsa, sa, sz);
+		rc = ipsec_sa_init(lsa, sa, sz, ip_v[i]);
 	}
 
 	return rc;
@@ -1226,11 +1286,11 @@ sa_spi_present(uint32_t spi, int inbound)
 	const struct ipsec_sa *sar;
 
 	if (inbound != 0) {
-		sar = sa_in;
-		num = nb_sa_in;
+		sar = sa_in.sadb;
+		num = sa_in.nb;
 	} else {
-		sar = sa_out;
-		num = nb_sa_out;
+		sar = sa_out.sadb;
+		num = sa_out.nb;
 	}
 
 	for (i = 0; i != num; i++) {
@@ -1246,54 +1306,122 @@ sa_init(struct socket_ctx *ctx, int32_t socket_id)
 {
 	int32_t rc;
 	const char *name;
+	int sa4_ip_v[IPSEC_SA_MAX_ENTRIES];
+	int sa6_ip_v[IPSEC_SA_MAX_ENTRIES];
 
 	if (ctx == NULL)
 		rte_exit(EXIT_FAILURE, "NULL context.\n");
 
-	if (ctx->sa_in != NULL)
-		rte_exit(EXIT_FAILURE, "Inbound SA DB for socket %u already "
-				"initialized\n", socket_id);
+	if (ctx->sa_ip4_in != NULL)
+		rte_exit(EXIT_FAILURE, "Inbound IPv4 SA DB for socket %u"
+				" already initialized\n", socket_id);
+
+	if (ctx->sa_ip4_out != NULL)
+		rte_exit(EXIT_FAILURE, "Outbound IPv4 SA DB for socket %u"
+				" already initialized\n", socket_id);
 
-	if (ctx->sa_out != NULL)
-		rte_exit(EXIT_FAILURE, "Outbound SA DB for socket %u already "
-				"initialized\n", socket_id);
+	if (ctx->sa_ip6_in != NULL)
+		rte_exit(EXIT_FAILURE, "Inbound IPv6 SA DB for socket %u"
+				" already initialized\n", socket_id);
 
-	if (nb_sa_in > 0) {
-		name = "sa_in";
-		ctx->sa_in = sa_create(name, socket_id);
-		if (ctx->sa_in == NULL)
+	if (ctx->sa_ip6_out != NULL)
+		rte_exit(EXIT_FAILURE, "Outbound IPv6 SA DB for socket %u"
+				" already initialized\n", socket_id);
+
+	if (sa_in.nb > 0) {
+		sa_in_split(&sa_ip4_in, &sa_ip6_in, &sa_in, sa4_ip_v,
+				sa6_ip_v);
+
+		name = "sa_ip4_in";
+		ctx->sa_ip4_in = sa_create(name, socket_id);
+		if (ctx->sa_ip4_in == NULL)
 			rte_exit(EXIT_FAILURE, "Error [%d] creating SA "
 				"context %s in socket %d\n", rte_errno,
 				name, socket_id);
 
-		sa_in_add_rules(ctx->sa_in, sa_in, nb_sa_in, ctx);
+		printf("IPv4 %s entries [%u]:\n", name, sa_ip4_in.nb);
+		sa_in_add_rules(ctx->sa_ip4_in, &sa_ip4_in, IPPROTO_IPIP, ctx);
+
+		name = "sa_ip6_in";
+		ctx->sa_ip6_in = sa_create(name, socket_id);
+		if (ctx->sa_ip6_in == NULL)
+			rte_exit(EXIT_FAILURE, "Error [%d] creating SA "
+				"context %s in socket %d\n", rte_errno,
+				name, socket_id);
+
+		printf("IPv6 %s entries [%u]:\n", name, sa_ip6_in.nb);
+		sa_in_add_rules(ctx->sa_ip6_in, &sa_ip6_in, IPPROTO_IPV6, ctx);
 
 		if (app_sa_prm.enable != 0) {
-			rc = ipsec_satbl_init(ctx->sa_in, sa_in, nb_sa_in,
-				socket_id);
-			if (rc != 0)
-				rte_exit(EXIT_FAILURE,
-					"failed to init inbound SAs\n");
+			if (sa_ip4_in.nb > 0) {
+				rc = ipsec_satbl_init(ctx->sa_ip4_in,
+					sa_ip4_in.sadb, sa_ip4_in.nb,
+					socket_id, sa4_ip_v);
+				if (rc != 0)
+					rte_exit(EXIT_FAILURE,
+						"failed to init inbound "
+						"IPv4 SAs\n");
+			}
+
+			if (sa_ip6_in.nb > 0) {
+				rc = ipsec_satbl_init(ctx->sa_ip6_in,
+					sa_ip6_in.sadb, sa_ip6_in.nb,
+					socket_id, sa6_ip_v);
+				if (rc != 0)
+					rte_exit(EXIT_FAILURE,
+						"failed to init inbound "
+						"IPv6 SAs\n");
+			}
 		}
 	} else
 		RTE_LOG(WARNING, IPSEC, "No SA Inbound rule specified\n");
 
-	if (nb_sa_out > 0) {
-		name = "sa_out";
-		ctx->sa_out = sa_create(name, socket_id);
-		if (ctx->sa_out == NULL)
+	if (sa_out.nb > 0) {
+		sa_out_split(&sa_ip4_out, &sa_ip6_out, &sa_out,
+				sa4_ip_v, sa6_ip_v);
+
+		name = "sa_ip4_out";
+		ctx->sa_ip4_out = sa_create(name, socket_id);
+		if (ctx->sa_ip4_out == NULL)
 			rte_exit(EXIT_FAILURE, "Error [%d] creating SA "
 				"context %s in socket %d\n", rte_errno,
 				name, socket_id);
 
-		sa_out_add_rules(ctx->sa_out, sa_out, nb_sa_out, ctx);
+		printf("IPv4 %s entries [%u]:\n", name, sa_ip4_out.nb);
+		sa_out_add_rules(ctx->sa_ip4_out, &sa_ip4_out, IPPROTO_IPIP,
+				ctx);
+
+		name = "sa_ip6_out";
+		ctx->sa_ip6_out = sa_create(name, socket_id);
+		if (ctx->sa_ip6_out == NULL)
+			rte_exit(EXIT_FAILURE, "Error [%d] creating SA "
+				"context %s in socket %d\n", rte_errno,
+				name, socket_id);
+
+		printf("IPv6 %s entries [%u]:\n", name, sa_ip6_out.nb);
+		sa_out_add_rules(ctx->sa_ip6_out, &sa_ip6_out, IPPROTO_IPV6,
+				ctx);
 
 		if (app_sa_prm.enable != 0) {
-			rc = ipsec_satbl_init(ctx->sa_out, sa_out, nb_sa_out,
-				socket_id);
-			if (rc != 0)
-				rte_exit(EXIT_FAILURE,
-					"failed to init outbound SAs\n");
+			if (sa_ip4_out.nb > 0) {
+				rc = ipsec_satbl_init(ctx->sa_ip4_out,
+					sa_ip4_out.sadb, sa_ip4_out.nb,
+					socket_id, sa4_ip_v);
+				if (rc != 0)
+					rte_exit(EXIT_FAILURE,
+						"failed to init outbound "
+						"IPv4 SAs\n");
+			}
+
+			if (sa_ip6_out.nb > 0) {
+				rc = ipsec_satbl_init(ctx->sa_ip6_out,
+					sa_ip6_out.sadb, sa_ip6_out.nb,
+					socket_id, sa6_ip_v);
+				if (rc != 0)
+					rte_exit(EXIT_FAILURE,
+						"failed to init outbound "
+						"IPv6 SAs\n");
+			}
 		}
 	} else
 		RTE_LOG(WARNING, IPSEC, "No SA Outbound rule "
@@ -1301,7 +1429,7 @@ sa_init(struct socket_ctx *ctx, int32_t socket_id)
 }
 
 int
-inbound_sa_check(struct sa_ctx *sa_ctx, struct rte_mbuf *m, uint32_t sa_idx)
+inbound_sa_check(struct rte_mbuf *m, uint32_t sp_spi)
 {
 	struct ipsec_mbuf_metadata *priv;
 	struct ipsec_sa *sa;
@@ -1309,7 +1437,7 @@ inbound_sa_check(struct sa_ctx *sa_ctx, struct rte_mbuf *m, uint32_t sa_idx)
 	priv = get_priv(m);
 	sa = priv->sa;
 	if (sa != NULL)
-		return (sa_ctx->sa[sa_idx].spi == sa->spi);
+		return (sp_spi == sa->spi);
 
 	RTE_LOG(ERR, IPSEC, "SA not saved in private data\n");
 	return 0;
@@ -1327,9 +1455,7 @@ single_inbound_lookup(struct ipsec_sa *sadb, struct rte_mbuf *pkt,
 
 	*sa_ret = NULL;
 
-	ip = rte_pktmbuf_mtod(pkt, struct ip *);
 	esp = rte_pktmbuf_mtod_offset(pkt, struct rte_esp_hdr *, pkt->l3_len);
-
 	if (esp->spi == INVALID_SPI)
 		return;
 
@@ -1337,6 +1463,8 @@ single_inbound_lookup(struct ipsec_sa *sadb, struct rte_mbuf *pkt,
 	if (rte_be_to_cpu_32(esp->spi) != sa->spi)
 		return;
 
+	ip = rte_pktmbuf_mtod(pkt, struct ip *);
+
 	switch (WITHOUT_TRANSPORT_VERSION(sa->flags)) {
 	case IP4_TUNNEL:
 		src4_addr = RTE_PTR_ADD(ip, offsetof(struct ip, ip_src));
@@ -1358,23 +1486,37 @@ single_inbound_lookup(struct ipsec_sa *sadb, struct rte_mbuf *pkt,
 }
 
 void
-inbound_sa_lookup(struct sa_ctx *sa_ctx, struct rte_mbuf *pkts[],
-		struct ipsec_sa *sa[], uint16_t nb_pkts)
+inbound_sa_lookup(struct sa_ctx *sa4_ctx, struct sa_ctx *sa6_ctx,
+		struct rte_mbuf *pkts[], struct ipsec_sa *sa[],
+		uint16_t nb_pkts)
 {
 	uint32_t i;
+	struct ip *ip;
 
-	for (i = 0; i < nb_pkts; i++)
-		single_inbound_lookup(sa_ctx->sa, pkts[i], &sa[i]);
+	for (i = 0; i < nb_pkts; i++) {
+		ip = rte_pktmbuf_mtod(pkts[i], struct ip *);
+		if (ip->ip_v == IPVERSION)
+			single_inbound_lookup(sa4_ctx->sa, pkts[i], &sa[i]);
+		else
+			single_inbound_lookup(sa6_ctx->sa, pkts[i], &sa[i]);
+	}
 }
 
 void
-outbound_sa_lookup(struct sa_ctx *sa_ctx, uint32_t sa_idx[],
-		struct ipsec_sa *sa[], uint16_t nb_pkts)
+outbound_sa_lookup(struct sa_ctx *sa4_ctx, struct sa_ctx *sa6_ctx,
+		uint32_t sa_idx[], struct ipsec_sa *sa[],
+		struct rte_mbuf *pkts[], uint16_t nb_pkts)
 {
 	uint32_t i;
+	struct ip *ip;
 
-	for (i = 0; i < nb_pkts; i++)
-		sa[i] = &sa_ctx->sa[sa_idx[i]];
+	for (i = 0; i < nb_pkts; i++) {
+		ip = rte_pktmbuf_mtod(pkts[i], struct ip *);
+		if (ip->ip_v == IPVERSION)
+			sa[i] = &sa4_ctx->sa[sa_idx[i]];
+		else
+			sa[i] = &sa6_ctx->sa[sa_idx[i]];
+	}
 }
 
 /*
@@ -1391,8 +1533,8 @@ sa_check_offloads(uint16_t port_id, uint64_t *rx_offloads,
 	*tx_offloads = 0;
 
 	/* Check for inbound rules that use offloads and use this port */
-	for (idx_sa = 0; idx_sa < nb_sa_in; idx_sa++) {
-		rule = &sa_in[idx_sa];
+	for (idx_sa = 0; idx_sa < sa_in.nb; idx_sa++) {
+		rule = &sa_in.sadb[idx_sa];
 		if ((rule->type == RTE_SECURITY_ACTION_TYPE_INLINE_CRYPTO ||
 				rule->type ==
 				RTE_SECURITY_ACTION_TYPE_INLINE_PROTOCOL)
@@ -1401,8 +1543,8 @@ sa_check_offloads(uint16_t port_id, uint64_t *rx_offloads,
 	}
 
 	/* Check for outbound rules that use offloads and use this port */
-	for (idx_sa = 0; idx_sa < nb_sa_out; idx_sa++) {
-		rule = &sa_out[idx_sa];
+	for (idx_sa = 0; idx_sa < sa_out.nb; idx_sa++) {
+		rule = &sa_out.sadb[idx_sa];
 		if ((rule->type == RTE_SECURITY_ACTION_TYPE_INLINE_CRYPTO ||
 				rule->type ==
 				RTE_SECURITY_ACTION_TYPE_INLINE_PROTOCOL)
