ipsec: remove dedicated IPSec tunnels
[vpp.git] / test / template_ipsec.py
index e6f1d24..a59a213 100644 (file)
@@ -1,13 +1,14 @@
 import unittest
 import socket
+import struct
 
 from scapy.layers.inet import IP, ICMP, TCP, UDP
-from scapy.layers.ipsec import SecurityAssociation
+from scapy.layers.ipsec import SecurityAssociation, ESP
 from scapy.layers.l2 import Ether, Raw
 from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest
 
 from framework import VppTestCase, VppTestRunner
-from util import ppp, reassemble4
+from util import ppp, reassemble4, fragment_rfc791, fragment_rfc8200
 from vpp_papi import VppEnum
 
 
@@ -36,12 +37,13 @@ class IPsecIPv4Params(object):
         self.auth_algo_vpp_id = (VppEnum.vl_api_ipsec_integ_alg_t.
                                  IPSEC_API_INTEG_ALG_SHA1_96)
         self.auth_algo = 'HMAC-SHA1-96'  # scapy name
-        self.auth_key = 'C91KUR9GYMm5GfkEvNjX'
+        self.auth_key = b'C91KUR9GYMm5GfkEvNjX'
 
         self.crypt_algo_vpp_id = (VppEnum.vl_api_ipsec_crypto_alg_t.
                                   IPSEC_API_CRYPTO_ALG_AES_CBC_128)
         self.crypt_algo = 'AES-CBC'  # scapy name
-        self.crypt_key = 'JPjyOWBeVEQiMe7h'
+        self.crypt_key = b'JPjyOWBeVEQiMe7h'
+        self.salt = 0
         self.flags = 0
         self.nat_header = None
 
@@ -71,23 +73,33 @@ class IPsecIPv6Params(object):
         self.auth_algo_vpp_id = (VppEnum.vl_api_ipsec_integ_alg_t.
                                  IPSEC_API_INTEG_ALG_SHA1_96)
         self.auth_algo = 'HMAC-SHA1-96'  # scapy name
-        self.auth_key = 'C91KUR9GYMm5GfkEvNjX'
+        self.auth_key = b'C91KUR9GYMm5GfkEvNjX'
 
         self.crypt_algo_vpp_id = (VppEnum.vl_api_ipsec_crypto_alg_t.
-                                  IPSEC_API_CRYPTO_ALG_AES_CBC_256)
+                                  IPSEC_API_CRYPTO_ALG_AES_CBC_128)
         self.crypt_algo = 'AES-CBC'  # scapy name
-        self.crypt_key = 'JPjyOWBeVEQiMe7hJPjyOWBeVEQiMe7h'
+        self.crypt_key = b'JPjyOWBeVEQiMe7h'
+        self.salt = 0
         self.flags = 0
         self.nat_header = None
 
 
+def mk_scapy_crypt_key(p):
+    if p.crypt_algo == "AES-GCM":
+        return p.crypt_key + struct.pack("!I", p.salt)
+    else:
+        return p.crypt_key
+
+
 def config_tun_params(p, encryption_type, tun_if):
     ip_class_by_addr_type = {socket.AF_INET: IP, socket.AF_INET6: IPv6}
     use_esn = bool(p.flags & (VppEnum.vl_api_ipsec_sad_flags_t.
                               IPSEC_API_SAD_FLAG_USE_ESN))
+    crypt_key = mk_scapy_crypt_key(p)
     p.scapy_tun_sa = SecurityAssociation(
         encryption_type, spi=p.vpp_tun_spi,
-        crypt_algo=p.crypt_algo, crypt_key=p.crypt_key,
+        crypt_algo=p.crypt_algo,
+        crypt_key=crypt_key,
         auth_algo=p.auth_algo, auth_key=p.auth_key,
         tunnel_header=ip_class_by_addr_type[p.addr_type](
             src=tun_if.remote_addr[p.addr_type],
@@ -96,7 +108,8 @@ def config_tun_params(p, encryption_type, tun_if):
         use_esn=use_esn)
     p.vpp_tun_sa = SecurityAssociation(
         encryption_type, spi=p.scapy_tun_spi,
-        crypt_algo=p.crypt_algo, crypt_key=p.crypt_key,
+        crypt_algo=p.crypt_algo,
+        crypt_key=crypt_key,
         auth_algo=p.auth_algo, auth_key=p.auth_key,
         tunnel_header=ip_class_by_addr_type[p.addr_type](
             dst=tun_if.remote_addr[p.addr_type],
@@ -106,13 +119,14 @@ def config_tun_params(p, encryption_type, tun_if):
 
 
 def config_tra_params(p, encryption_type):
-    use_esn = p.flags & (VppEnum.vl_api_ipsec_sad_flags_t.
-                         IPSEC_API_SAD_FLAG_USE_ESN)
+    use_esn = bool(p.flags & (VppEnum.vl_api_ipsec_sad_flags_t.
+                              IPSEC_API_SAD_FLAG_USE_ESN))
+    crypt_key = mk_scapy_crypt_key(p)
     p.scapy_tra_sa = SecurityAssociation(
         encryption_type,
         spi=p.vpp_tra_spi,
         crypt_algo=p.crypt_algo,
-        crypt_key=p.crypt_key,
+        crypt_key=crypt_key,
         auth_algo=p.auth_algo,
         auth_key=p.auth_key,
         nat_t_header=p.nat_header,
@@ -121,7 +135,7 @@ def config_tra_params(p, encryption_type):
         encryption_type,
         spi=p.scapy_tra_spi,
         crypt_algo=p.crypt_algo,
-        crypt_key=p.crypt_key,
+        crypt_key=crypt_key,
         auth_algo=p.auth_algo,
         auth_key=p.auth_key,
         nat_t_header=p.nat_header,
@@ -146,6 +160,8 @@ class TemplateIpsec(VppTestCase):
     |tun_if| ------->  |VPP| ------> |pg1|
      ------             ---           ---
     """
+    tun_spd_id = 1
+    tra_spd_id = 2
 
     def ipsec_select_backend(self):
         """ empty method to be overloaded when necessary """
@@ -159,54 +175,55 @@ class TemplateIpsec(VppTestCase):
     def tearDownClass(cls):
         super(TemplateIpsec, cls).tearDownClass()
 
-    def setUp(self):
-        super(TemplateIpsec, self).setUp()
-
     def setup_params(self):
         self.ipv4_params = IPsecIPv4Params()
         self.ipv6_params = IPsecIPv6Params()
         self.params = {self.ipv4_params.addr_type: self.ipv4_params,
                        self.ipv6_params.addr_type: self.ipv6_params}
 
+    def config_interfaces(self):
+        self.create_pg_interfaces(range(3))
+        self.interfaces = list(self.pg_interfaces)
+        for i in self.interfaces:
+            i.admin_up()
+            i.config_ip4()
+            i.resolve_arp()
+            i.config_ip6()
+            i.resolve_ndp()
+
     def setUp(self):
         super(TemplateIpsec, self).setUp()
 
         self.setup_params()
 
-        self.tun_spd_id = 1
-        self.tra_spd_id = 2
-
         self.vpp_esp_protocol = (VppEnum.vl_api_ipsec_proto_t.
                                  IPSEC_API_PROTO_ESP)
         self.vpp_ah_protocol = (VppEnum.vl_api_ipsec_proto_t.
                                 IPSEC_API_PROTO_AH)
 
-        self.create_pg_interfaces(range(3))
-        self.interfaces = list(self.pg_interfaces)
-        for i in self.interfaces:
-            i.admin_up()
-            i.config_ip4()
-            i.resolve_arp()
-            i.config_ip6()
-            i.resolve_ndp()
-        self.ipsec_select_backend()
+        self.config_interfaces()
 
-    def tearDown(self):
-        super(TemplateIpsec, self).tearDown()
+        self.ipsec_select_backend()
 
+    def unconfig_interfaces(self):
         for i in self.interfaces:
             i.admin_down()
             i.unconfig_ip4()
             i.unconfig_ip6()
 
-        if not self.vpp_dead:
-            self.vapi.cli("show hardware")
+    def tearDown(self):
+        super(TemplateIpsec, self).tearDown()
+
+        self.unconfig_interfaces()
+
+    def show_commands_at_teardown(self):
+        self.logger.info(self.vapi.cli("show hardware"))
 
     def gen_encrypt_pkts(self, sa, sw_intf, src, dst, count=1,
                          payload_size=54):
         return [Ether(src=sw_intf.remote_mac, dst=sw_intf.local_mac) /
                 sa.encrypt(IP(src=src, dst=dst) /
-                           ICMP() / Raw('X' * payload_size))
+                           ICMP() / Raw(b'X' * payload_size))
                 for i in range(count)]
 
     def gen_encrypt_pkts6(self, sa, sw_intf, src, dst, count=1,
@@ -219,7 +236,7 @@ class TemplateIpsec(VppTestCase):
 
     def gen_pkts(self, sw_intf, src, dst, count=1, payload_size=54):
         return [Ether(src=sw_intf.remote_mac, dst=sw_intf.local_mac) /
-                IP(src=src, dst=dst) / ICMP() / Raw('X' * payload_size)
+                IP(src=src, dst=dst) / ICMP() / Raw(b'X' * payload_size)
                 for i in range(count)]
 
     def gen_pkts6(self, sw_intf, src, dst, count=1, payload_size=54):
@@ -229,12 +246,10 @@ class TemplateIpsec(VppTestCase):
                 for i in range(count)]
 
 
-class IpsecTcpTests(object):
-    def test_tcp_checksum(self):
-        """ verify checksum correctness for vpp generated packets """
+class IpsecTcp(object):
+    def verify_tcp_checksum(self):
         self.vapi.cli("test http server")
         p = self.params[socket.AF_INET]
-        config_tun_params(p, self.encryption_type, self.tun_if)
         send = (Ether(src=self.tun_if.remote_mac, dst=self.tun_if.local_mac) /
                 p.scapy_tun_sa.encrypt(IP(src=p.remote_tun_if_host,
                                           dst=self.tun_if.local_ip4) /
@@ -246,34 +261,91 @@ class IpsecTcpTests(object):
         self.assert_packet_checksums_valid(decrypted)
 
 
-class IpsecTra4Tests(object):
-    def test_tra_anti_replay(self, count=1):
-        """ ipsec v4 transport anti-reply test """
+class IpsecTcpTests(IpsecTcp):
+    def test_tcp_checksum(self):
+        """ verify checksum correctness for vpp generated packets """
+        self.verify_tcp_checksum()
+
+
+class IpsecTra4(object):
+    """ verify methods for Transport v4 """
+    def verify_tra_anti_replay(self):
         p = self.params[socket.AF_INET]
         use_esn = p.vpp_tra_sa.use_esn
 
-        # fire in a packet with seq number 1
-        pkt = (Ether(src=self.tra_if.remote_mac,
-                     dst=self.tra_if.local_mac) /
-               p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
-                                         dst=self.tra_if.local_ip4) /
-                                      ICMP(),
-                                      seq_num=1))
-        recv_pkts = self.send_and_expect(self.tra_if, [pkt], self.tra_if)
+        seq_cycle_node_name = ('/err/%s/sequence number cycled' %
+                               self.tra4_encrypt_node_name)
+        replay_node_name = ('/err/%s/SA replayed packet' %
+                            self.tra4_decrypt_node_name)
+        if ESP == self.encryption_type and p.crypt_algo == "AES-GCM":
+            hash_failed_node_name = ('/err/%s/ESP decryption failed' %
+                                     self.tra4_decrypt_node_name)
+        else:
+            hash_failed_node_name = ('/err/%s/Integrity check failed' %
+                                     self.tra4_decrypt_node_name)
+        replay_count = self.statistics.get_err_counter(replay_node_name)
+        hash_failed_count = self.statistics.get_err_counter(
+            hash_failed_node_name)
+        seq_cycle_count = self.statistics.get_err_counter(seq_cycle_node_name)
+
+        if ESP == self.encryption_type:
+            undersize_node_name = ('/err/%s/undersized packet' %
+                                   self.tra4_decrypt_node_name)
+            undersize_count = self.statistics.get_err_counter(
+                undersize_node_name)
+
+        #
+        # send packets with seq numbers 1->34
+        # this means the window size is still in Case B (see RFC4303
+        # Appendix A)
+        #
+        # for reasons i haven't investigated Scapy won't create a packet with
+        # seq_num=0
+        #
+        pkts = [(Ether(src=self.tra_if.remote_mac,
+                       dst=self.tra_if.local_mac) /
+                 p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                           dst=self.tra_if.local_ip4) /
+                                        ICMP(),
+                                        seq_num=seq))
+                for seq in range(1, 34)]
+        recv_pkts = self.send_and_expect(self.tra_if, pkts, self.tra_if)
 
-        # now move the window over to 235
+        # replayed packets are dropped
+        self.send_and_assert_no_replies(self.tra_if, pkts)
+        replay_count += len(pkts)
+        self.assert_error_counter_equal(replay_node_name, replay_count)
+
+        #
+        # now send a batch of packets all with the same sequence number
+        # the first packet in the batch is legitimate, the rest bogus
+        #
+        pkts = (Ether(src=self.tra_if.remote_mac,
+                      dst=self.tra_if.local_mac) /
+                p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                          dst=self.tra_if.local_ip4) /
+                                       ICMP(),
+                                       seq_num=35))
+        recv_pkts = self.send_and_expect(self.tra_if, pkts * 8,
+                                         self.tra_if, n_rx=1)
+        replay_count += 7
+        self.assert_error_counter_equal(replay_node_name, replay_count)
+
+        #
+        # now move the window over to 257 (more than one byte) and into Case A
+        #
         pkt = (Ether(src=self.tra_if.remote_mac,
                      dst=self.tra_if.local_mac) /
                p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
                                          dst=self.tra_if.local_ip4) /
                                       ICMP(),
-                                      seq_num=235))
+                                      seq_num=257))
         recv_pkts = self.send_and_expect(self.tra_if, [pkt], self.tra_if)
 
         # replayed packets are dropped
         self.send_and_assert_no_replies(self.tra_if, pkt * 3)
-        self.assert_packet_counter_equal(
-            '/err/%s/SA replayed packet' % self.tra4_decrypt_node_name, 3)
+        replay_count += 3
+        self.assert_error_counter_equal(replay_node_name, replay_count)
 
         # the window size is 64 packets
         # in window are still accepted
@@ -282,12 +354,16 @@ class IpsecTra4Tests(object):
                p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
                                          dst=self.tra_if.local_ip4) /
                                       ICMP(),
-                                      seq_num=172))
+                                      seq_num=200))
         recv_pkts = self.send_and_expect(self.tra_if, [pkt], self.tra_if)
 
         # a packet that does not decrypt does not move the window forward
         bogus_sa = SecurityAssociation(self.encryption_type,
-                                       p.vpp_tra_spi)
+                                       p.vpp_tra_spi,
+                                       crypt_algo=p.crypt_algo,
+                                       crypt_key=mk_scapy_crypt_key(p)[::-1],
+                                       auth_algo=p.auth_algo,
+                                       auth_key=p.auth_key[::-1])
         pkt = (Ether(src=self.tra_if.remote_mac,
                      dst=self.tra_if.local_mac) /
                bogus_sa.encrypt(IP(src=self.tra_if.remote_ip4,
@@ -296,8 +372,26 @@ class IpsecTra4Tests(object):
                                 seq_num=350))
         self.send_and_assert_no_replies(self.tra_if, pkt * 17)
 
-        self.assert_packet_counter_equal(
-            '/err/%s/Integrity check failed' % self.tra4_decrypt_node_name, 17)
+        hash_failed_count += 17
+        self.assert_error_counter_equal(hash_failed_node_name,
+                                        hash_failed_count)
+
+        # a malformed 'runt' packet
+        #  created by a mis-constructed SA
+        if (ESP == self.encryption_type and p.crypt_algo != "NULL"):
+            bogus_sa = SecurityAssociation(self.encryption_type,
+                                           p.vpp_tra_spi)
+            pkt = (Ether(src=self.tra_if.remote_mac,
+                         dst=self.tra_if.local_mac) /
+                   bogus_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                       dst=self.tra_if.local_ip4) /
+                                    ICMP(),
+                                    seq_num=350))
+            self.send_and_assert_no_replies(self.tra_if, pkt * 17)
+
+            undersize_count += 17
+            self.assert_error_counter_equal(undersize_node_name,
+                                            undersize_count)
 
         # which we can determine since this packet is still in the window
         pkt = (Ether(src=self.tra_if.remote_mac,
@@ -308,7 +402,11 @@ class IpsecTra4Tests(object):
                                       seq_num=234))
         self.send_and_expect(self.tra_if, [pkt], self.tra_if)
 
+        #
         # out of window are dropped
+        #  this is Case B. So VPP will consider this to be a high seq num wrap
+        #  and so the decrypt attempt will fail
+        #
         pkt = (Ether(src=self.tra_if.remote_mac,
                      dst=self.tra_if.local_mac) /
                p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
@@ -320,44 +418,59 @@ class IpsecTra4Tests(object):
         if use_esn:
             # an out of window error with ESN looks like a high sequence
             # wrap. but since it isn't then the verify will fail.
-            self.assert_packet_counter_equal(
-                '/err/%s/Integrity check failed' %
-                self.tra4_decrypt_node_name, 34)
+            hash_failed_count += 17
+            self.assert_error_counter_equal(hash_failed_node_name,
+                                            hash_failed_count)
 
         else:
-            self.assert_packet_counter_equal(
-                '/err/%s/SA replayed packet' %
-                self.tra4_decrypt_node_name, 20)
+            replay_count += 17
+            self.assert_error_counter_equal(replay_node_name,
+                                            replay_count)
 
-        # valid packet moves the window over to 236
+        # valid packet moves the window over to 258
         pkt = (Ether(src=self.tra_if.remote_mac,
                      dst=self.tra_if.local_mac) /
                p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
                                          dst=self.tra_if.local_ip4) /
                                       ICMP(),
-                                      seq_num=236))
+                                      seq_num=258))
         rx = self.send_and_expect(self.tra_if, [pkt], self.tra_if)
         decrypted = p.vpp_tra_sa.decrypt(rx[0][IP])
 
-        # move VPP's SA to just before the seq-number wrap
+        #
+        # move VPP's SA TX seq-num to just before the seq-number wrap.
+        # then fire in a packet that VPP should drop on TX because it
+        # causes the TX seq number to wrap; unless we're using extened sequence
+        # numbers.
+        #
         self.vapi.cli("test ipsec sa %d seq 0xffffffff" % p.scapy_tra_sa_id)
+        self.logger.info(self.vapi.ppcli("show ipsec sa 0"))
+        self.logger.info(self.vapi.ppcli("show ipsec sa 1"))
 
-        # then fire in a packet that VPP should drop because it causes the
-        # seq number to wrap  unless we're using extended.
-        pkt = (Ether(src=self.tra_if.remote_mac,
-                     dst=self.tra_if.local_mac) /
-               p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
-                                         dst=self.tra_if.local_ip4) /
-                                      ICMP(),
-                                      seq_num=237))
+        pkts = [(Ether(src=self.tra_if.remote_mac,
+                       dst=self.tra_if.local_mac) /
+                 p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                           dst=self.tra_if.local_ip4) /
+                                        ICMP(),
+                                        seq_num=seq))
+                for seq in range(259, 280)]
 
         if use_esn:
-            rx = self.send_and_expect(self.tra_if, [pkt], self.tra_if)
-            # in order to decrpyt the high order number needs to wrap
-            p.vpp_tra_sa.seq_num = 0x100000000
-            decrypted = p.vpp_tra_sa.decrypt(rx[0][IP])
+            rxs = self.send_and_expect(self.tra_if, pkts, self.tra_if)
 
-            # send packets with high bits set
+            #
+            # in order for scapy to decrypt its SA's high order number needs
+            # to wrap
+            #
+            p.vpp_tra_sa.seq_num = 0x100000000
+            for rx in rxs:
+                decrypted = p.vpp_tra_sa.decrypt(rx[0][IP])
+
+            #
+            # wrap scapy's TX high sequence number. VPP is in case B, so it
+            # will consider this a high seq wrap also.
+            # The low seq num we set it to will place VPP's RX window in Case A
+            #
             p.scapy_tra_sa.seq_num = 0x100000005
             pkt = (Ether(src=self.tra_if.remote_mac,
                          dst=self.tra_if.local_mac) /
@@ -366,22 +479,82 @@ class IpsecTra4Tests(object):
                                           ICMP(),
                                           seq_num=0x100000005))
             rx = self.send_and_expect(self.tra_if, [pkt], self.tra_if)
-            # in order to decrpyt the high order number needs to wrap
             decrypted = p.vpp_tra_sa.decrypt(rx[0][IP])
+
+            #
+            # A packet that has seq num between (2^32-64) and 5 is within
+            # the window
+            #
+            p.scapy_tra_sa.seq_num = 0xfffffffd
+            pkt = (Ether(src=self.tra_if.remote_mac,
+                         dst=self.tra_if.local_mac) /
+                   p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                             dst=self.tra_if.local_ip4) /
+                                          ICMP(),
+                                          seq_num=0xfffffffd))
+            rx = self.send_and_expect(self.tra_if, [pkt], self.tra_if)
+            decrypted = p.vpp_tra_sa.decrypt(rx[0][IP])
+
+            #
+            # While in case A we cannot wrap the high sequence number again
+            # becuase VPP will consider this packet to be one that moves the
+            # window forward
+            #
+            pkt = (Ether(src=self.tra_if.remote_mac,
+                         dst=self.tra_if.local_mac) /
+                   p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                             dst=self.tra_if.local_ip4) /
+                                          ICMP(),
+                                          seq_num=0x200000999))
+            self.send_and_assert_no_replies(self.tra_if, [pkt], self.tra_if)
+
+            hash_failed_count += 1
+            self.assert_error_counter_equal(hash_failed_node_name,
+                                            hash_failed_count)
+
+            #
+            # but if we move the wondow forward to case B, then we can wrap
+            # again
+            #
+            p.scapy_tra_sa.seq_num = 0x100000555
+            pkt = (Ether(src=self.tra_if.remote_mac,
+                         dst=self.tra_if.local_mac) /
+                   p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                             dst=self.tra_if.local_ip4) /
+                                          ICMP(),
+                                          seq_num=0x100000555))
+            rx = self.send_and_expect(self.tra_if, [pkt], self.tra_if)
+            decrypted = p.vpp_tra_sa.decrypt(rx[0][IP])
+
+            p.scapy_tra_sa.seq_num = 0x200000444
+            pkt = (Ether(src=self.tra_if.remote_mac,
+                         dst=self.tra_if.local_mac) /
+                   p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                             dst=self.tra_if.local_ip4) /
+                                          ICMP(),
+                                          seq_num=0x200000444))
+            rx = self.send_and_expect(self.tra_if, [pkt], self.tra_if)
+            decrypted = p.vpp_tra_sa.decrypt(rx[0][IP])
+
         else:
-            self.send_and_assert_no_replies(self.tra_if, [pkt])
-            self.assert_packet_counter_equal(
-                '/err/%s/sequence number cycled' %
-                self.tra4_encrypt_node_name, 1)
+            #
+            # without ESN TX sequence numbers can't wrap and packets are
+            # dropped from here on out.
+            #
+            self.send_and_assert_no_replies(self.tra_if, pkts)
+            seq_cycle_count += len(pkts)
+            self.assert_error_counter_equal(seq_cycle_node_name,
+                                            seq_cycle_count)
 
         # move the security-associations seq number on to the last we used
         self.vapi.cli("test ipsec sa %d seq 0x15f" % p.scapy_tra_sa_id)
         p.scapy_tra_sa.seq_num = 351
         p.vpp_tra_sa.seq_num = 351
 
-    def test_tra_basic(self, count=1):
+    def verify_tra_basic4(self, count=1):
         """ ipsec v4 transport basic test """
         self.vapi.cli("clear errors")
+        self.vapi.cli("clear ipsec sa")
         try:
             p = self.params[socket.AF_INET]
             send_pkts = self.gen_encrypt_pkts(p.scapy_tra_sa, self.tra_if,
@@ -391,6 +564,8 @@ class IpsecTra4Tests(object):
             recv_pkts = self.send_and_expect(self.tra_if, send_pkts,
                                              self.tra_if)
             for rx in recv_pkts:
+                self.assertEqual(len(rx) - len(Ether()), rx[IP].len)
+                self.assert_packet_checksums_valid(rx)
                 try:
                     decrypted = p.vpp_tra_sa.decrypt(rx[IP])
                     self.assert_packet_checksums_valid(decrypted)
@@ -399,7 +574,7 @@ class IpsecTra4Tests(object):
                     raise
         finally:
             self.logger.info(self.vapi.ppcli("show error"))
-            self.logger.info(self.vapi.ppcli("show ipsec"))
+            self.logger.info(self.vapi.ppcli("show ipsec all"))
 
         pkts = p.tra_sa_in.get_stats()['packets']
         self.assertEqual(pkts, count,
@@ -413,14 +588,25 @@ class IpsecTra4Tests(object):
         self.assert_packet_counter_equal(self.tra4_encrypt_node_name, count)
         self.assert_packet_counter_equal(self.tra4_decrypt_node_name, count)
 
+
+class IpsecTra4Tests(IpsecTra4):
+    """ UT test methods for Transport v4 """
+    def test_tra_anti_replay(self):
+        """ ipsec v4 transport anti-reply test """
+        self.verify_tra_anti_replay()
+
+    def test_tra_basic(self, count=1):
+        """ ipsec v4 transport basic test """
+        self.verify_tra_basic4(count=1)
+
     def test_tra_burst(self):
         """ ipsec v4 transport burst test """
-        self.test_tra_basic(count=257)
+        self.verify_tra_basic4(count=257)
 
 
-class IpsecTra6Tests(object):
-    def test_tra_basic6(self, count=1):
-        """ ipsec v6 transport basic test """
+class IpsecTra6(object):
+    """ verify methods for Transport v6 """
+    def verify_tra_basic6(self, count=1):
         self.vapi.cli("clear errors")
         try:
             p = self.params[socket.AF_INET6]
@@ -431,6 +617,8 @@ class IpsecTra6Tests(object):
             recv_pkts = self.send_and_expect(self.tra_if, send_pkts,
                                              self.tra_if)
             for rx in recv_pkts:
+                self.assertEqual(len(rx) - len(Ether()) - len(IPv6()),
+                                 rx[IPv6].plen)
                 try:
                     decrypted = p.vpp_tra_sa.decrypt(rx[IPv6])
                     self.assert_packet_checksums_valid(decrypted)
@@ -439,7 +627,7 @@ class IpsecTra6Tests(object):
                     raise
         finally:
             self.logger.info(self.vapi.ppcli("show error"))
-            self.logger.info(self.vapi.ppcli("show ipsec"))
+            self.logger.info(self.vapi.ppcli("show ipsec all"))
 
         pkts = p.tra_sa_in.get_stats()['packets']
         self.assertEqual(pkts, count,
@@ -452,18 +640,28 @@ class IpsecTra6Tests(object):
         self.assert_packet_counter_equal(self.tra6_encrypt_node_name, count)
         self.assert_packet_counter_equal(self.tra6_decrypt_node_name, count)
 
+
+class IpsecTra6Tests(IpsecTra6):
+    """ UT test methods for Transport v6 """
+    def test_tra_basic6(self):
+        """ ipsec v6 transport basic test """
+        self.verify_tra_basic6(count=1)
+
     def test_tra_burst6(self):
         """ ipsec v6 transport burst test """
-        self.test_tra_basic6(count=257)
+        self.verify_tra_basic6(count=257)
 
 
 class IpsecTra46Tests(IpsecTra4Tests, IpsecTra6Tests):
+    """ UT test methods for Transport v6 and v4"""
     pass
 
 
 class IpsecTun4(object):
-
-    def verify_counters(self, p, count):
+    """ verify methods for Tunnel v4 """
+    def verify_counters4(self, p, count, n_frags=None):
+        if not n_frags:
+            n_frags = count
         if (hasattr(p, "spd_policy_in_any")):
             pkts = p.spd_policy_in_any.get_stats()['packets']
             self.assertEqual(pkts, count,
@@ -480,7 +678,7 @@ class IpsecTun4(object):
                              "incorrect SA out counts: expected %d != %d" %
                              (count, pkts))
 
-        self.assert_packet_counter_equal(self.tun4_encrypt_node_name, count)
+        self.assert_packet_counter_equal(self.tun4_encrypt_node_name, n_frags)
         self.assert_packet_counter_equal(self.tun4_decrypt_node_name, count)
 
     def verify_decrypted(self, p, rxs):
@@ -492,6 +690,10 @@ class IpsecTun4(object):
     def verify_encrypted(self, p, sa, rxs):
         decrypt_pkts = []
         for rx in rxs:
+            if p.nat_header:
+                self.assertEqual(rx[UDP].dport, 4500)
+            self.assert_packet_checksums_valid(rx)
+            self.assertEqual(len(rx) - len(Ether()), rx[IP].len)
             try:
                 decrypt_pkt = p.vpp_tun_sa.decrypt(rx[IP])
                 if not decrypt_pkt.haslayer(IP):
@@ -515,7 +717,6 @@ class IpsecTun4(object):
         if not n_rx:
             n_rx = count
         try:
-            config_tun_params(p, self.encryption_type, self.tun_if)
             send_pkts = self.gen_encrypt_pkts(p.scapy_tun_sa, self.tun_if,
                                               src=p.remote_tun_if_host,
                                               dst=self.pg1.remote_ip4,
@@ -532,14 +733,43 @@ class IpsecTun4(object):
 
         finally:
             self.logger.info(self.vapi.ppcli("show error"))
-            self.logger.info(self.vapi.ppcli("show ipsec"))
+            self.logger.info(self.vapi.ppcli("show ipsec all"))
+
+        self.verify_counters4(p, count, n_rx)
+
+    def verify_tun_reass_44(self, p):
+        self.vapi.cli("clear errors")
+        self.vapi.ip_reassembly_enable_disable(
+            sw_if_index=self.tun_if.sw_if_index, enable_ip4=True)
 
-        self.verify_counters(p, count)
+        try:
+            send_pkts = self.gen_encrypt_pkts(p.scapy_tun_sa, self.tun_if,
+                                              src=p.remote_tun_if_host,
+                                              dst=self.pg1.remote_ip4,
+                                              payload_size=1900,
+                                              count=1)
+            send_pkts = fragment_rfc791(send_pkts[0], 1400)
+            recv_pkts = self.send_and_expect(self.tun_if, send_pkts,
+                                             self.pg1, n_rx=1)
+            self.verify_decrypted(p, recv_pkts)
+
+            send_pkts = self.gen_pkts(self.pg1, src=self.pg1.remote_ip4,
+                                      dst=p.remote_tun_if_host, count=1)
+            recv_pkts = self.send_and_expect(self.pg1, send_pkts,
+                                             self.tun_if)
+            self.verify_encrypted(p, p.vpp_tun_sa, recv_pkts)
+
+        finally:
+            self.logger.info(self.vapi.ppcli("show error"))
+            self.logger.info(self.vapi.ppcli("show ipsec all"))
+
+        self.verify_counters4(p, 1, 1)
+        self.vapi.ip_reassembly_enable_disable(
+            sw_if_index=self.tun_if.sw_if_index, enable_ip4=False)
 
     def verify_tun_64(self, p, count=1):
         self.vapi.cli("clear errors")
         try:
-            config_tun_params(p, self.encryption_type, self.tun_if)
             send_pkts = self.gen_encrypt_pkts6(p.scapy_tun_sa, self.tun_if,
                                                src=p.remote_tun_if_host6,
                                                dst=self.pg1.remote_ip6,
@@ -570,81 +800,156 @@ class IpsecTun4(object):
                     raise
         finally:
             self.logger.info(self.vapi.ppcli("show error"))
-            self.logger.info(self.vapi.ppcli("show ipsec"))
+            self.logger.info(self.vapi.ppcli("show ipsec all"))
 
-        self.verify_counters(p, count)
+        self.verify_counters4(p, count)
 
+    def verify_keepalive(self, p):
+        pkt = (Ether(src=self.tun_if.remote_mac, dst=self.tun_if.local_mac) /
+               IP(src=p.remote_tun_if_host, dst=self.tun_if.local_ip4) /
+               UDP(sport=333, dport=4500) /
+               Raw(b'\xff'))
+        self.send_and_assert_no_replies(self.tun_if, pkt*31)
+        self.assert_error_counter_equal(
+            '/err/%s/NAT Keepalive' % self.tun4_input_node, 31)
 
-class IpsecTun4Tests(IpsecTun4):
+        pkt = (Ether(src=self.tun_if.remote_mac, dst=self.tun_if.local_mac) /
+               IP(src=p.remote_tun_if_host, dst=self.tun_if.local_ip4) /
+               UDP(sport=333, dport=4500) /
+               Raw(b'\xfe'))
+        self.send_and_assert_no_replies(self.tun_if, pkt*31)
+        self.assert_error_counter_equal(
+            '/err/%s/Too Short' % self.tun4_input_node, 31)
 
+
+class IpsecTun4Tests(IpsecTun4):
+    """ UT test methods for Tunnel v4 """
     def test_tun_basic44(self):
         """ ipsec 4o4 tunnel basic test """
         self.verify_tun_44(self.params[socket.AF_INET], count=1)
 
+    def test_tun_reass_basic44(self):
+        """ ipsec 4o4 tunnel basic reassembly test """
+        self.verify_tun_reass_44(self.params[socket.AF_INET])
+
     def test_tun_burst44(self):
         """ ipsec 4o4 tunnel burst test """
         self.verify_tun_44(self.params[socket.AF_INET], count=257)
 
 
 class IpsecTun6(object):
-
-    def verify_counters(self, p, count):
-        if (hasattr(p, "tun_sa_in")):
-            pkts = p.tun_sa_in.get_stats()['packets']
+    """ verify methods for Tunnel v6 """
+    def verify_counters6(self, p_in, p_out, count):
+        if (hasattr(p_in, "tun_sa_in")):
+            pkts = p_in.tun_sa_in.get_stats()['packets']
             self.assertEqual(pkts, count,
                              "incorrect SA in counts: expected %d != %d" %
                              (count, pkts))
-            pkts = p.tun_sa_out.get_stats()['packets']
+        if (hasattr(p_out, "tun_sa_out")):
+            pkts = p_out.tun_sa_out.get_stats()['packets']
             self.assertEqual(pkts, count,
                              "incorrect SA out counts: expected %d != %d" %
                              (count, pkts))
         self.assert_packet_counter_equal(self.tun6_encrypt_node_name, count)
         self.assert_packet_counter_equal(self.tun6_decrypt_node_name, count)
 
-    def verify_tun_66(self, p, count=1):
-        """ ipsec 6o6 tunnel basic test """
+    def verify_decrypted6(self, p, rxs):
+        for rx in rxs:
+            self.assert_equal(rx[IPv6].src, p.remote_tun_if_host)
+            self.assert_equal(rx[IPv6].dst, self.pg1.remote_ip6)
+            self.assert_packet_checksums_valid(rx)
+
+    def verify_encrypted6(self, p, sa, rxs):
+        for rx in rxs:
+            self.assert_packet_checksums_valid(rx)
+            self.assertEqual(len(rx) - len(Ether()) - len(IPv6()),
+                             rx[IPv6].plen)
+            try:
+                decrypt_pkt = p.vpp_tun_sa.decrypt(rx[IPv6])
+                if not decrypt_pkt.haslayer(IPv6):
+                    decrypt_pkt = IPv6(decrypt_pkt[Raw].load)
+                self.assert_packet_checksums_valid(decrypt_pkt)
+                self.assert_equal(decrypt_pkt.src, self.pg1.remote_ip6)
+                self.assert_equal(decrypt_pkt.dst, p.remote_tun_if_host)
+            except:
+                self.logger.debug(ppp("Unexpected packet:", rx))
+                try:
+                    self.logger.debug(ppp("Decrypted packet:", decrypt_pkt))
+                except:
+                    pass
+                raise
+
+    def verify_drop_tun_66(self, p_in, count=1, payload_size=64):
         self.vapi.cli("clear errors")
+        self.vapi.cli("clear ipsec sa")
+
+        send_pkts = self.gen_encrypt_pkts6(p_in.scapy_tun_sa, self.tun_if,
+                                           src=p_in.remote_tun_if_host,
+                                           dst=self.pg1.remote_ip6,
+                                           count=count)
+        self.send_and_assert_no_replies(self.tun_if, send_pkts)
+        self.logger.info(self.vapi.cli("sh punt stats"))
+
+    def verify_tun_66(self, p_in, p_out=None, count=1, payload_size=64):
+        self.vapi.cli("clear errors")
+        self.vapi.cli("clear ipsec sa")
+        if not p_out:
+            p_out = p_in
         try:
-            config_tun_params(p, self.encryption_type, self.tun_if)
-            send_pkts = self.gen_encrypt_pkts6(p.scapy_tun_sa, self.tun_if,
-                                               src=p.remote_tun_if_host,
+            send_pkts = self.gen_encrypt_pkts6(p_in.scapy_tun_sa, self.tun_if,
+                                               src=p_in.remote_tun_if_host,
                                                dst=self.pg1.remote_ip6,
                                                count=count)
             recv_pkts = self.send_and_expect(self.tun_if, send_pkts, self.pg1)
-            for recv_pkt in recv_pkts:
-                self.assert_equal(recv_pkt[IPv6].src, p.remote_tun_if_host)
-                self.assert_equal(recv_pkt[IPv6].dst, self.pg1.remote_ip6)
-                self.assert_packet_checksums_valid(recv_pkt)
+            self.verify_decrypted6(p_in, recv_pkts)
+
+            send_pkts = self.gen_pkts6(self.pg1, src=self.pg1.remote_ip6,
+                                       dst=p_out.remote_tun_if_host,
+                                       count=count,
+                                       payload_size=payload_size)
+            recv_pkts = self.send_and_expect(self.pg1, send_pkts,
+                                             self.tun_if)
+            self.verify_encrypted6(p_out, p_out.vpp_tun_sa, recv_pkts)
+
+        finally:
+            self.logger.info(self.vapi.ppcli("show error"))
+            self.logger.info(self.vapi.ppcli("show ipsec all"))
+        self.verify_counters6(p_in, p_out, count)
+
+    def verify_tun_reass_66(self, p):
+        self.vapi.cli("clear errors")
+        self.vapi.ip_reassembly_enable_disable(
+            sw_if_index=self.tun_if.sw_if_index, enable_ip6=True)
+
+        try:
+            send_pkts = self.gen_encrypt_pkts6(p.scapy_tun_sa, self.tun_if,
+                                               src=p.remote_tun_if_host,
+                                               dst=self.pg1.remote_ip6,
+                                               count=1,
+                                               payload_size=1900)
+            send_pkts = fragment_rfc8200(send_pkts[0], 1, 1400, self.logger)
+            recv_pkts = self.send_and_expect(self.tun_if, send_pkts,
+                                             self.pg1, n_rx=1)
+            self.verify_decrypted6(p, recv_pkts)
+
             send_pkts = self.gen_pkts6(self.pg1, src=self.pg1.remote_ip6,
                                        dst=p.remote_tun_if_host,
-                                       count=count)
-            recv_pkts = self.send_and_expect(self.pg1, send_pkts, self.tun_if)
-            for recv_pkt in recv_pkts:
-                try:
-                    decrypt_pkt = p.vpp_tun_sa.decrypt(recv_pkt[IPv6])
-                    if not decrypt_pkt.haslayer(IPv6):
-                        decrypt_pkt = IPv6(decrypt_pkt[Raw].load)
-                    self.assert_equal(decrypt_pkt.src, self.pg1.remote_ip6)
-                    self.assert_equal(decrypt_pkt.dst, p.remote_tun_if_host)
-                    self.assert_packet_checksums_valid(decrypt_pkt)
-                except:
-                    self.logger.debug(ppp("Unexpected packet:", recv_pkt))
-                    try:
-                        self.logger.debug(
-                            ppp("Decrypted packet:", decrypt_pkt))
-                    except:
-                        pass
-                    raise
+                                       count=1,
+                                       payload_size=64)
+            recv_pkts = self.send_and_expect(self.pg1, send_pkts,
+                                             self.tun_if)
+            self.verify_encrypted6(p, p.vpp_tun_sa, recv_pkts)
         finally:
             self.logger.info(self.vapi.ppcli("show error"))
-            self.logger.info(self.vapi.ppcli("show ipsec"))
-        self.verify_counters(p, count)
+            self.logger.info(self.vapi.ppcli("show ipsec all"))
+        self.verify_counters6(p, p, 1)
+        self.vapi.ip_reassembly_enable_disable(
+            sw_if_index=self.tun_if.sw_if_index, enable_ip6=False)
 
     def verify_tun_46(self, p, count=1):
         """ ipsec 4o6 tunnel basic test """
         self.vapi.cli("clear errors")
         try:
-            config_tun_params(p, self.encryption_type, self.tun_if)
             send_pkts = self.gen_encrypt_pkts(p.scapy_tun_sa, self.tun_if,
                                               src=p.remote_tun_if_host4,
                                               dst=self.pg1.remote_ip4,
@@ -676,22 +981,28 @@ class IpsecTun6(object):
                     raise
         finally:
             self.logger.info(self.vapi.ppcli("show error"))
-            self.logger.info(self.vapi.ppcli("show ipsec"))
-        self.verify_counters(p, count)
+            self.logger.info(self.vapi.ppcli("show ipsec all"))
+        self.verify_counters6(p, p, count)
 
 
 class IpsecTun6Tests(IpsecTun6):
+    """ UT test methods for Tunnel v6 """
 
     def test_tun_basic66(self):
         """ ipsec 6o6 tunnel basic test """
         self.verify_tun_66(self.params[socket.AF_INET6], count=1)
 
+    def test_tun_reass_basic66(self):
+        """ ipsec 6o6 tunnel basic reassembly test """
+        self.verify_tun_reass_66(self.params[socket.AF_INET6])
+
     def test_tun_burst66(self):
         """ ipsec 6o6 tunnel burst test """
         self.verify_tun_66(self.params[socket.AF_INET6], count=257)
 
 
 class IpsecTun46Tests(IpsecTun4Tests, IpsecTun6Tests):
+    """ UT test methods for Tunnel v6 & v4 """
     pass