diff --git a/examples/ipsec-secgw/ipsec-secgw.c b/examples/ipsec-secgw/ipsec-secgw.c
index 478dd80c2..1c49aa22c 100644
--- a/examples/ipsec-secgw/ipsec-secgw.c
+++ b/examples/ipsec-secgw/ipsec-secgw.c
@@ -41,6 +41,7 @@
 #include <rte_jhash.h>
 #include <rte_cryptodev.h>
 #include <rte_security.h>
+#include <rte_ip.h>
 
 #include "ipsec.h"
 #include "parser.h"
@@ -248,16 +249,38 @@ prepare_one_packet(struct rte_mbuf *pkt, struct ipsec_traffic *t)
 		pkt->l2_len = 0;
 		pkt->l3_len = sizeof(struct ip);
 	} else if (eth->ether_type == rte_cpu_to_be_16(ETHER_TYPE_IPv6)) {
-		nlp = (uint8_t *)rte_pktmbuf_adj(pkt, ETHER_HDR_LEN);
-		nlp = RTE_PTR_ADD(nlp, offsetof(struct ip6_hdr, ip6_nxt));
-		if (*nlp == IPPROTO_ESP)
+		int next_proto;
+		size_t l3len, ext_len;
+		struct ipv6_hdr *v6h;
+		uint8_t *p;
+
+		/* get protocol type */
+		v6h = (struct ipv6_hdr *)rte_pktmbuf_adj(pkt, ETHER_HDR_LEN);
+		next_proto = v6h->proto;
+
+		/* determine l3 header size up to ESP extension */
+		l3len = sizeof(struct ip6_hdr);
+		p = rte_pktmbuf_mtod(pkt, uint8_t *);
+		while (next_proto != IPPROTO_ESP && l3len < pkt->data_len &&
+			(next_proto = rte_ipv6_get_next_ext(p + l3len,
+						next_proto, &ext_len)) >= 0)
+			l3len += ext_len;
+
+		/* drop packet when IPv6 header exceeds first segment length */
+		if (unlikely(l3len > pkt->data_len)) {
+			rte_pktmbuf_free(pkt);
+			return;
+		}
+
+		if (next_proto == IPPROTO_ESP)
 			t->ipsec.pkts[(t->ipsec.num)++] = pkt;
 		else {
-			t->ip6.data[t->ip6.num] = nlp;
+			t->ip6.data[t->ip6.num] = rte_pktmbuf_mtod_offset(pkt,
+				uint8_t *, offsetof(struct ipv6_hdr, proto));
 			t->ip6.pkts[(t->ip6.num)++] = pkt;
 		}
 		pkt->l2_len = 0;
-		pkt->l3_len = sizeof(struct ip6_hdr);
+		pkt->l3_len = l3len;
 	} else {
 		/* Unknown/Unsupported type, drop the packet */
 		RTE_LOG(ERR, IPSEC, "Unsupported packet type 0x%x\n",
diff --git a/examples/ipsec-secgw/sa.c b/examples/ipsec-secgw/sa.c
index b850e9839..607527d08 100644
--- a/examples/ipsec-secgw/sa.c
+++ b/examples/ipsec-secgw/sa.c
@@ -1228,10 +1228,7 @@ single_inbound_lookup(struct ipsec_sa *sadb, struct rte_mbuf *pkt,
 	*sa_ret = NULL;
 
 	ip = rte_pktmbuf_mtod(pkt, struct ip *);
-	if (ip->ip_v == IPVERSION)
-		esp = (struct esp_hdr *)(ip + 1);
-	else
-		esp = (struct esp_hdr *)(((struct ip6_hdr *)ip) + 1);
+	esp = rte_pktmbuf_mtod_offset(pkt, struct esp_hdr *, pkt->l3_len);
 
 	if (esp->spi == INVALID_SPI)
 		return;
diff --git a/examples/ipsec-secgw/test/test-scapy.py b/examples/ipsec-secgw/test/test-scapy.py
new file mode 100755
index 000000000..d7f66b734
--- /dev/null
+++ b/examples/ipsec-secgw/test/test-scapy.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python3
+
+# Run DPDK IPsec example with following arguments:
+# ./dpdk-ipsec-secgw --log-level=31 -l 0 --vdev=crypto_openssl --vdev=net_tap0 --vdev=net_tap1 -- -P -p 0x3 -u 0x1 --config "(0,0,0),(1,0,0)" -f test-transport.cfg -l
+# Two tap ports are expected: 0: unprotected (remote), 1: protected (local)
+
+# sample configuration:
+#	sp ipv6 out esp protect 5 pri 1 \
+#	src 1111:0000:0000:0000:0000:0000:0000:0000/64 \
+#	dst 2222:0000:0000:0000:0000:0000:0000:0000/64 \
+#	sport 0:65535 dport 0:65535
+#
+#	sp ipv6 in esp protect 6 pri 1 \
+#	src 2222:0000:0000:0000:0000:0000:0000:0000/64 \
+#	dst 1111:0000:0000:0000:0000:0000:0000:0000/64 \
+#	sport 0:65535 dport 0:65535
+#
+#	sa out 5 cipher_algo aes-128-cbc cipher_key 0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0 \
+#	auth_algo sha1-hmac auth_key 0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0 \
+#	mode transport
+#
+#	sa in 6 cipher_algo aes-128-cbc cipher_key 0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0 \
+#	auth_algo sha1-hmac auth_key 0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0 \
+#	mode transport
+#
+#	rt ipv6 dst 1111:0000:0000:0000:0000:0000:0000:0000/64 port 1
+#	rt ipv6 dst 2222:0000:0000:0000:0000:0000:0000:0000/64 port 0
+#
+# run tests with:
+# python3 -m unittest test-scapy
+
+
+import socket
+import sys
+import unittest
+from scapy.all import *
+
+
+SRC_ETHER = "52:54:00:00:00:01"
+DST_ETHER = "52:54:00:00:00:02"
+SRC_ADDR = "1111::1"
+DST_ADDR = "2222::1"
+LOCAL_IFACE = "dtap1"
+REMOTE_IFACE = "dtap0"
+
+
+class Interface(object):
+    ETH_P_ALL = 3
+    MAX_PACKET_SIZE = 1280
+    def __init__(self, ifname):
+        self.name = ifname
+        self.s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL))
+        self.s.bind((self.name, 0, socket.PACKET_OTHERHOST))
+
+    def __del__(self):
+        self.s.close()
+
+    def send_packet(self, pkt):
+        self.send_bytes(bytes(pkt))
+
+    def send_bytes(self, bytedata):
+        self.s.send(bytedata)
+
+    def recv_packet(self):
+        return Ether(self.recv_bytes())
+
+    def recv_bytes(self):
+        return self.s.recv(Interface.MAX_PACKET_SIZE)
+
+
+class TestTransportMode(unittest.TestCase):
+    # There is a bug in the IPsec Scapy implementation
+    # which causes invalid packet reconstruction after
+    # successful decryption. This method is a workaround.
+    @staticmethod
+    def decrypt(pkt, sa):
+        esp = pkt[ESP]
+
+        # decrypt dummy packet with no extensions
+        d = sa.decrypt(IPv6()/esp)
+
+        # fix 'next header' in the preceding header of the original
+        # packet and remove ESP
+        pkt[ESP].underlayer.nh = d[IPv6].nh
+        pkt[ESP].underlayer.remove_payload()
+
+        # combine L3 header with decrypted payload
+        npkt = pkt/d[IPv6].payload
+
+        # fix length
+        npkt[IPv6].plen = d[IPv6].plen + len(pkt[IPv6].payload)
+
+        return npkt
+
+    def setUp(self):
+        self.ilocal = Interface(LOCAL_IFACE)
+        self.iremote = Interface(REMOTE_IFACE)
+        self.outb_sa = SecurityAssociation(ESP, spi=5, crypt_algo='AES-CBC', crypt_key='\x00'*16, auth_algo='HMAC-SHA1-96', auth_key='\x00'*20)
+        self.inb_sa = SecurityAssociation(ESP, spi=6, crypt_algo='AES-CBC', crypt_key='\x00'*16, auth_algo='HMAC-SHA1-96', auth_key='\x00'*20)
+
+    def test_outb_ipv6_noopt(self):
+        pkt = Ether(src=SRC_ETHER, dst=DST_ETHER)
+        pkt /= IPv6(src=SRC_ADDR, dst=DST_ADDR)
+        pkt /= UDP(sport=123,dport=456)/Raw(load="abc")
+        self.ilocal.send_packet(pkt)
+
+        # check response
+        resp = self.iremote.recv_packet()
+        self.assertEqual(resp[IPv6].nh, socket.IPPROTO_ESP)
+        self.assertEqual(resp[ESP].spi, 5)
+
+        # decrypt response, check packet after decryption
+        d = TestTransportMode.decrypt(resp[IPv6], self.outb_sa)
+        self.assertEqual(d[IPv6].nh, socket.IPPROTO_UDP)
+        self.assertEqual(d[UDP].sport, 123)
+        self.assertEqual(d[UDP].dport, 456)
+        self.assertEqual(bytes(d[UDP].payload), b'abc')
+
+    def test_outb_ipv6_opt(self):
+        hoptions = []
+        hoptions.append(RouterAlert(value=2))
+        hoptions.append(Jumbo(jumboplen=5000))
+        hoptions.append(Pad1())
+
+        doptions = []
+        doptions.append(HAO(hoa="1234::4321"))
+
+        pkt = Ether(src=SRC_ETHER, dst=DST_ETHER)
+        pkt /= IPv6(src=SRC_ADDR, dst=DST_ADDR)
+        pkt /= IPv6ExtHdrHopByHop(options=hoptions)
+        pkt /= IPv6ExtHdrRouting(addresses=["3333::3","4444::4"])
+        pkt /= IPv6ExtHdrDestOpt(options=doptions)
+        pkt /= UDP(sport=123,dport=456)/Raw(load="abc")
+        self.ilocal.send_packet(pkt)
+
+        # check response
+        resp = self.iremote.recv_packet()
+        self.assertEqual(resp[IPv6].nh, socket.IPPROTO_HOPOPTS)
+
+        # check extensions
+        self.assertEqual(resp[IPv6ExtHdrHopByHop].nh, socket.IPPROTO_ROUTING)
+        self.assertEqual(resp[IPv6ExtHdrRouting].nh, socket.IPPROTO_DSTOPTS)
+        self.assertEqual(resp[IPv6ExtHdrDestOpt].nh, socket.IPPROTO_ESP)
+
+        # check ESP
+        self.assertEqual(resp[ESP].spi, 5)
+
+        # decrypt response, check packet after decryption
+        d = TestTransportMode.decrypt(resp[IPv6], self.outb_sa)
+        self.assertEqual(d[IPv6].nh, socket.IPPROTO_HOPOPTS)
+        self.assertEqual(d[IPv6ExtHdrHopByHop].nh, socket.IPPROTO_ROUTING)
+        self.assertEqual(d[IPv6ExtHdrRouting].nh, socket.IPPROTO_DSTOPTS)
+        self.assertEqual(d[IPv6ExtHdrDestOpt].nh, socket.IPPROTO_UDP)
+
+        # check UDP
+        self.assertEqual(d[UDP].sport, 123)
+        self.assertEqual(d[UDP].dport, 456)
+        self.assertEqual(bytes(d[UDP].payload), b'abc')
+
+    def test_inb_ipv6_noopt(self):
+        # encrypt and send raw UDP packet
+        pkt = IPv6(src=DST_ADDR, dst=SRC_ADDR)
+        pkt /= UDP(sport=123,dport=456)/Raw(load="abc")
+        e = self.inb_sa.encrypt(pkt)
+        e = Ether(src=DST_ETHER, dst=SRC_ETHER)/e
+        self.iremote.send_packet(e)
+
+        # check response
+        resp = self.ilocal.recv_packet()
+        self.assertEqual(resp[IPv6].nh, socket.IPPROTO_UDP)
+
+        # check UDP packet
+        self.assertEqual(resp[UDP].sport, 123)
+        self.assertEqual(resp[UDP].dport, 456)
+        self.assertEqual(bytes(resp[UDP].payload), b'abc')
+
+    def test_inb_ipv6_opt(self):
+        hoptions = []
+        hoptions.append(RouterAlert(value=2))
+        hoptions.append(Jumbo(jumboplen=5000))
+        hoptions.append(Pad1())
+
+        doptions = []
+        doptions.append(HAO(hoa="1234::4321"))
+
+        # prepare packet with options
+        pkt = IPv6(src=DST_ADDR, dst=SRC_ADDR)
+        pkt /= IPv6ExtHdrHopByHop(options=hoptions)
+        pkt /= IPv6ExtHdrRouting(addresses=["3333::3","4444::4"])
+        pkt /= IPv6ExtHdrDestOpt(options=doptions)
+        pkt /= UDP(sport=123,dport=456)/Raw(load="abc")
+
+        # encrypt and send packet
+        e = self.inb_sa.encrypt(pkt)
+        e = Ether(src=DST_ETHER, dst=SRC_ETHER)/e
+        self.iremote.send_packet(e)
+
+        # check response
+        resp = self.ilocal.recv_packet()
+        self.assertEqual(resp[IPv6].nh, socket.IPPROTO_HOPOPTS)
+        self.assertEqual(resp[IPv6ExtHdrHopByHop].nh, socket.IPPROTO_ROUTING)
+        self.assertEqual(resp[IPv6ExtHdrRouting].nh, socket.IPPROTO_DSTOPTS)
+        self.assertEqual(resp[IPv6ExtHdrDestOpt].nh, socket.IPPROTO_UDP)
+
+        # check UDP
+        self.assertEqual(resp[UDP].sport, 123)
+        self.assertEqual(resp[UDP].dport, 456)
+        self.assertEqual(bytes(resp[UDP].payload), b'abc')
+
+    def test_inb_ipv6_frag(self):
+        # prepare ESP payload
+        pkt = UDP(sport=123,dport=456)/Raw(load="abc")
+        e = self.inb_sa.encrypt(IPv6()/pkt)
+
+        # craft and send inbound packet
+        e = Ether(src=DST_ETHER, dst=SRC_ETHER)/IPv6(src=DST_ADDR, dst=SRC_ADDR)/IPv6ExtHdrFragment()/e[IPv6].payload
+        self.iremote.send_packet(e)
+
+        # check response
+        resp = self.ilocal.recv_packet()
+        self.assertEqual(resp[IPv6].nh, socket.IPPROTO_FRAGMENT)
+        self.assertEqual(resp[IPv6ExtHdrFragment].nh, socket.IPPROTO_UDP)
+
+        # check UDP
+        self.assertEqual(resp[UDP].sport, 123)
+        self.assertEqual(resp[UDP].dport, 456)
+        self.assertEqual(bytes(resp[UDP].payload), b'abc')
+
+
+if __name__ == "__main__":
+    unittest.main()
