TEST: IPSEC NAT-T with UDP header
[vpp.git] / test / template_ipsec.py
index 71485c5..68f1183 100644 (file)
@@ -1,67 +1,82 @@
 import unittest
 import socket
 
-from scapy.layers.inet import IP, ICMP, TCP
+from scapy.layers.inet import IP, ICMP, TCP, UDP
 from scapy.layers.ipsec import SecurityAssociation
 from scapy.layers.l2 import Ether, Raw
 from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest
 
 from framework import VppTestCase, VppTestRunner
 from util import ppp
+from vpp_papi import VppEnum
 
 
 class IPsecIPv4Params(object):
+
     addr_type = socket.AF_INET
     addr_any = "0.0.0.0"
     addr_bcast = "255.255.255.255"
     addr_len = 32
     is_ipv6 = 0
-    remote_tun_if_host = '1.1.1.1'
 
-    scapy_tun_sa_id = 10
-    scapy_tun_spi = 1001
-    vpp_tun_sa_id = 20
-    vpp_tun_spi = 1000
+    def __init__(self):
+        self.remote_tun_if_host = '1.1.1.1'
+
+        self.scapy_tun_sa_id = 10
+        self.scapy_tun_spi = 1001
+        self.vpp_tun_sa_id = 20
+        self.vpp_tun_spi = 1000
 
-    scapy_tra_sa_id = 30
-    scapy_tra_spi = 2001
-    vpp_tra_sa_id = 40
-    vpp_tra_spi = 2000
+        self.scapy_tra_sa_id = 30
+        self.scapy_tra_spi = 2001
+        self.vpp_tra_sa_id = 40
+        self.vpp_tra_spi = 2000
 
-    auth_algo_vpp_id = 2  # internal VPP enum value for SHA1_96
-    auth_algo = 'HMAC-SHA1-96'  # scapy name
-    auth_key = 'C91KUR9GYMm5GfkEvNjX'
+        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'
 
-    crypt_algo_vpp_id = 1  # internal VPP enum value for AES_CBC_128
-    crypt_algo = 'AES-CBC'  # scapy name
-    crypt_key = 'JPjyOWBeVEQiMe7h'
+        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.flags = 0
+        self.nat_header = None
 
 
 class IPsecIPv6Params(object):
+
     addr_type = socket.AF_INET6
     addr_any = "0::0"
     addr_bcast = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
     addr_len = 128
     is_ipv6 = 1
-    remote_tun_if_host = '1111:1111:1111:1111:1111:1111:1111:1111'
 
-    scapy_tun_sa_id = 50
-    scapy_tun_spi = 3001
-    vpp_tun_sa_id = 60
-    vpp_tun_spi = 3000
+    def __init__(self):
+        self.remote_tun_if_host = '1111:1111:1111:1111:1111:1111:1111:1111'
 
-    scapy_tra_sa_id = 70
-    scapy_tra_spi = 4001
-    vpp_tra_sa_id = 80
-    vpp_tra_spi = 4000
+        self.scapy_tun_sa_id = 50
+        self.scapy_tun_spi = 3001
+        self.vpp_tun_sa_id = 60
+        self.vpp_tun_spi = 3000
 
-    auth_algo_vpp_id = 4  # internal VPP enum value for SHA_256_128
-    auth_algo = 'SHA2-256-128'  # scapy name
-    auth_key = 'C91KUR9GYMm5GfkEvNjX'
+        self.scapy_tra_sa_id = 70
+        self.scapy_tra_spi = 4001
+        self.vpp_tra_sa_id = 80
+        self.vpp_tra_spi = 4000
 
-    crypt_algo_vpp_id = 3  # internal VPP enum value for AES_CBC_256
-    crypt_algo = 'AES-CBC'  # scapy name
-    crypt_key = 'JPjyOWBeVEQiMe7hJPjyOWBeVEQiMe7h'
+        self.auth_algo_vpp_id = (VppEnum.vl_api_ipsec_integ_alg_t.
+                                 IPSEC_API_INTEG_ALG_SHA_256_128)
+        self.auth_algo = 'SHA2-256-128'  # scapy name
+        self.auth_key = 'C91KUR9GYMm5GfkEvNjX'
+
+        self.crypt_algo_vpp_id = (VppEnum.vl_api_ipsec_crypto_alg_t.
+                                  IPSEC_API_CRYPTO_ALG_AES_CBC_256)
+        self.crypt_algo = 'AES-CBC'  # scapy name
+        self.crypt_key = 'JPjyOWBeVEQiMe7hJPjyOWBeVEQiMe7h'
+        self.flags = 0
+        self.nat_header = None
 
 
 class TemplateIpsec(VppTestCase):
@@ -82,39 +97,48 @@ class TemplateIpsec(VppTestCase):
     |tun_if| ------->  |VPP| ------> |pg1|
      ------             ---           ---
     """
-    ipv4_params = IPsecIPv4Params()
-    ipv6_params = IPsecIPv6Params()
-    params = {ipv4_params.addr_type: ipv4_params,
-              ipv6_params.addr_type: ipv6_params}
 
-    payload = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+    def ipsec_select_backend(self):
+        """ empty method to be overloaded when necessary """
+        pass
 
-    tun_spd_id = 1
-    tra_spd_id = 2
+    def setUp(self):
+        super(TemplateIpsec, self).setUp()
 
-    vpp_esp_protocol = 1
-    vpp_ah_protocol = 0
+        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}
 
-    @classmethod
-    def ipsec_select_backend(cls):
-        """ empty method to be overloaded when necessary """
-        pass
+        self.payload = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"\
+                       "XXXXXXXXXXXXXXXXXXXXX"
+
+        self.tun_spd_id = 1
+        self.tra_spd_id = 2
 
-    @classmethod
-    def setUpClass(cls):
-        super(TemplateIpsec, cls).setUpClass()
-        cls.create_pg_interfaces(range(3))
-        cls.interfaces = list(cls.pg_interfaces)
-        for i in cls.interfaces:
+        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()
-        cls.ipsec_select_backend()
+        self.ipsec_select_backend()
 
     def tearDown(self):
         super(TemplateIpsec, self).tearDown()
+
+        for i in self.interfaces:
+            i.admin_down()
+            i.unconfig_ip4()
+            i.unconfig_ip6()
+
         if not self.vpp_dead:
             self.vapi.cli("show hardware")
 
@@ -148,30 +172,35 @@ class TemplateIpsec(VppTestCase):
             auth_algo=params.auth_algo, auth_key=params.auth_key,
             tunnel_header=ip_class_by_addr_type[params.addr_type](
                 src=self.tun_if.remote_addr[params.addr_type],
-                dst=self.tun_if.local_addr[params.addr_type]))
+                dst=self.tun_if.local_addr[params.addr_type]),
+            nat_t_header=params.nat_header)
         vpp_tun_sa = SecurityAssociation(
             self.encryption_type, spi=params.scapy_tun_spi,
             crypt_algo=params.crypt_algo, crypt_key=params.crypt_key,
             auth_algo=params.auth_algo, auth_key=params.auth_key,
             tunnel_header=ip_class_by_addr_type[params.addr_type](
                 dst=self.tun_if.remote_addr[params.addr_type],
-                src=self.tun_if.local_addr[params.addr_type]))
+                src=self.tun_if.local_addr[params.addr_type]),
+            nat_t_header=params.nat_header)
         return vpp_tun_sa, scapy_tun_sa
 
     def configure_sa_tra(self, params):
-        scapy_tra_sa = SecurityAssociation(self.encryption_type,
-                                           spi=params.vpp_tra_spi,
-                                           crypt_algo=params.crypt_algo,
-                                           crypt_key=params.crypt_key,
-                                           auth_algo=params.auth_algo,
-                                           auth_key=params.auth_key)
-        vpp_tra_sa = SecurityAssociation(self.encryption_type,
-                                         spi=params.scapy_tra_spi,
-                                         crypt_algo=params.crypt_algo,
-                                         crypt_key=params.crypt_key,
-                                         auth_algo=params.auth_algo,
-                                         auth_key=params.auth_key)
-        return vpp_tra_sa, scapy_tra_sa
+        params.scapy_tra_sa = SecurityAssociation(
+            self.encryption_type,
+            spi=params.vpp_tra_spi,
+            crypt_algo=params.crypt_algo,
+            crypt_key=params.crypt_key,
+            auth_algo=params.auth_algo,
+            auth_key=params.auth_key,
+            nat_t_header=params.nat_header)
+        params.vpp_tra_sa = SecurityAssociation(
+            self.encryption_type,
+            spi=params.scapy_tra_spi,
+            crypt_algo=params.crypt_algo,
+            crypt_key=params.crypt_key,
+            auth_algo=params.auth_algo,
+            auth_key=params.auth_key,
+            nat_t_header=params.nat_header)
 
 
 class IpsecTcpTests(object):
@@ -191,30 +220,109 @@ class IpsecTcpTests(object):
         self.assert_packet_checksums_valid(decrypted)
 
 
-class IpsecTraTests(object):
+class IpsecTra4Tests(object):
+    def test_tra_anti_replay(self, count=1):
+        """ ipsec v4 transport anti-reply test """
+        p = self.params[socket.AF_INET]
+
+        # 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)
+
+        # now move the window over to 235
+        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))
+        recv_pkts = self.send_and_expect(self.tra_if, [pkt], self.tra_if)
+
+        # the window size is 64 packets
+        # in window are still accepted
+        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=172))
+        recv_pkts = self.send_and_expect(self.tra_if, [pkt], self.tra_if)
+
+        # out of window are dropped
+        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=17))
+        self.send_and_assert_no_replies(self.tra_if, pkt * 17)
+
+        self.assert_packet_counter_equal(
+            '/err/%s/SA replayed packet' % self.tra4_decrypt_node_name, 17)
+
+        # a packet that does not decrypt does not move the window forward
+        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)
+
+        self.assert_packet_counter_equal(
+            '/err/%s/Integrity check failed' % self.tra4_decrypt_node_name, 17)
+
+        # which we can determine since this packet is still in the window
+        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=234))
+        self.send_and_expect(self.tra_if, [pkt], self.tra_if)
+
+        # move the security-associations seq number on to the last we used
+        p.scapy_tra_sa.seq_num = 351
+        p.vpp_tra_sa.seq_num = 351
+
     def test_tra_basic(self, count=1):
         """ ipsec v4 transport basic test """
         self.vapi.cli("clear errors")
         try:
             p = self.params[socket.AF_INET]
-            vpp_tra_sa, scapy_tra_sa = self.configure_sa_tra(p)
-            send_pkts = self.gen_encrypt_pkts(scapy_tra_sa, self.tra_if,
+            send_pkts = self.gen_encrypt_pkts(p.scapy_tra_sa, self.tra_if,
                                               src=self.tra_if.remote_ip4,
                                               dst=self.tra_if.local_ip4,
                                               count=count)
             recv_pkts = self.send_and_expect(self.tra_if, send_pkts,
                                              self.tra_if)
-            for p in recv_pkts:
+            for rx in recv_pkts:
                 try:
-                    decrypted = vpp_tra_sa.decrypt(p[IP])
+                    decrypted = p.vpp_tra_sa.decrypt(rx[IP])
                     self.assert_packet_checksums_valid(decrypted)
                 except:
-                    self.logger.debug(ppp("Unexpected packet:", p))
+                    self.logger.debug(ppp("Unexpected packet:", rx))
                     raise
         finally:
             self.logger.info(self.vapi.ppcli("show error"))
             self.logger.info(self.vapi.ppcli("show ipsec"))
 
+        pkts = p.tra_sa_in.get_stats()['packets']
+        self.assertEqual(pkts, count,
+                         "incorrect SA in counts: expected %d != %d" %
+                         (count, pkts))
+        pkts = p.tra_sa_out.get_stats()['packets']
+        self.assertEqual(pkts, count,
+                         "incorrect SA out counts: expected %d != %d" %
+                         (count, pkts))
+
         self.assert_packet_counter_equal(self.tra4_encrypt_node_name, count)
         self.assert_packet_counter_equal(self.tra4_decrypt_node_name, count)
 
@@ -222,29 +330,38 @@ class IpsecTraTests(object):
         """ ipsec v4 transport burst test """
         self.test_tra_basic(count=257)
 
+
+class IpsecTra6Tests(object):
     def test_tra_basic6(self, count=1):
         """ ipsec v6 transport basic test """
         self.vapi.cli("clear errors")
         try:
             p = self.params[socket.AF_INET6]
-            vpp_tra_sa, scapy_tra_sa = self.configure_sa_tra(p)
-            send_pkts = self.gen_encrypt_pkts6(scapy_tra_sa, self.tra_if,
+            send_pkts = self.gen_encrypt_pkts6(p.scapy_tra_sa, self.tra_if,
                                                src=self.tra_if.remote_ip6,
                                                dst=self.tra_if.local_ip6,
                                                count=count)
             recv_pkts = self.send_and_expect(self.tra_if, send_pkts,
                                              self.tra_if)
-            for p in recv_pkts:
+            for rx in recv_pkts:
                 try:
-                    decrypted = vpp_tra_sa.decrypt(p[IPv6])
+                    decrypted = p.vpp_tra_sa.decrypt(rx[IPv6])
                     self.assert_packet_checksums_valid(decrypted)
                 except:
-                    self.logger.debug(ppp("Unexpected packet:", p))
+                    self.logger.debug(ppp("Unexpected packet:", rx))
                     raise
         finally:
             self.logger.info(self.vapi.ppcli("show error"))
             self.logger.info(self.vapi.ppcli("show ipsec"))
 
+        pkts = p.tra_sa_in.get_stats()['packets']
+        self.assertEqual(pkts, count,
+                         "incorrect SA in counts: expected %d != %d" %
+                         (count, pkts))
+        pkts = p.tra_sa_out.get_stats()['packets']
+        self.assertEqual(pkts, count,
+                         "incorrect SA out counts: expected %d != %d" %
+                         (count, pkts))
         self.assert_packet_counter_equal(self.tra6_encrypt_node_name, count)
         self.assert_packet_counter_equal(self.tra6_decrypt_node_name, count)
 
@@ -253,6 +370,10 @@ class IpsecTraTests(object):
         self.test_tra_basic6(count=257)
 
 
+class IpsecTra46Tests(IpsecTra4Tests, IpsecTra6Tests):
+    pass
+
+
 class IpsecTun4Tests(object):
     def test_tun_basic44(self, count=1):
         """ ipsec 4o4 tunnel basic test """
@@ -292,6 +413,22 @@ class IpsecTun4Tests(object):
             self.logger.info(self.vapi.ppcli("show error"))
             self.logger.info(self.vapi.ppcli("show ipsec"))
 
+        if (hasattr(p, "spd_policy_in_any")):
+            pkts = p.spd_policy_in_any.get_stats()['packets']
+            self.assertEqual(pkts, count,
+                             "incorrect SPD any policy: expected %d != %d" %
+                             (count, pkts))
+
+        if (hasattr(p, "tun_sa_in")):
+            pkts = p.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']
+            self.assertEqual(pkts, count,
+                             "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_decrypt_node_name, count)
 
@@ -340,6 +477,14 @@ class IpsecTun6Tests(object):
             self.logger.info(self.vapi.ppcli("show error"))
             self.logger.info(self.vapi.ppcli("show ipsec"))
 
+        pkts = p.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']
+        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)
 
@@ -348,7 +493,7 @@ class IpsecTun6Tests(object):
         self.test_tun_basic66(count=257)
 
 
-class IpsecTunTests(IpsecTun4Tests, IpsecTun6Tests):
+class IpsecTun46Tests(IpsecTun4Tests, IpsecTun6Tests):
     pass