import unittest import socket import struct from scapy.layers.inet import IP, ICMP, TCP, UDP from scapy.layers.ipsec import SecurityAssociation, ESP from scapy.layers.l2 import Ether from scapy.packet import raw, Raw from scapy.layers.inet6 import ( IPv6, ICMPv6EchoRequest, IPv6ExtHdrHopByHop, IPv6ExtHdrFragment, IPv6ExtHdrDestOpt, ) from framework import VppTestCase, VppTestRunner from util import ppp, reassemble4, fragment_rfc791, fragment_rfc8200 from vpp_papi import VppEnum from vpp_ipsec import VppIpsecSpd, VppIpsecSpdEntry, VppIpsecSpdItfBinding from ipaddress import ip_address from re import search from os import popen class IPsecIPv4Params: addr_type = socket.AF_INET addr_any = "0.0.0.0" addr_bcast = "255.255.255.255" addr_len = 32 is_ipv6 = 0 def __init__(self): self.remote_tun_if_host = "1.1.1.1" self.remote_tun_if_host6 = "1111::1" self.scapy_tun_sa_id = 100 self.scapy_tun_spi = 1000 self.vpp_tun_sa_id = 200 self.vpp_tun_spi = 2000 self.scapy_tra_sa_id = 300 self.scapy_tra_spi = 3000 self.vpp_tra_sa_id = 400 self.vpp_tra_spi = 4000 self.outer_hop_limit = 64 self.inner_hop_limit = 255 self.outer_flow_label = 0 self.inner_flow_label = 0x12345 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 = 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 = b"JPjyOWBeVEQiMe7h" self.salt = 0 self.flags = 0 self.nat_header = None self.tun_flags = ( VppEnum.vl_api_tunnel_encap_decap_flags_t.TUNNEL_API_ENCAP_DECAP_FLAG_NONE ) self.dscp = 0 self.async_mode = False class IPsecIPv6Params: 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 def __init__(self): self.remote_tun_if_host = "1111:1111:1111:1111:1111:1111:1111:1111" self.remote_tun_if_host4 = "1.1.1.1" self.scapy_tun_sa_id = 500 self.scapy_tun_spi = 3001 self.vpp_tun_sa_id = 600 self.vpp_tun_spi = 3000 self.scapy_tra_sa_id = 700 self.scapy_tra_spi = 4001 self.vpp_tra_sa_id = 800 self.vpp_tra_spi = 4000 self.outer_hop_limit = 64 self.inner_hop_limit = 255 self.outer_flow_label = 0 self.inner_flow_label = 0x12345 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 = 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 = b"JPjyOWBeVEQiMe7h" self.salt = 0 self.flags = 0 self.nat_header = None self.tun_flags = ( VppEnum.vl_api_tunnel_encap_decap_flags_t.TUNNEL_API_ENCAP_DECAP_FLAG_NONE ) self.dscp = 0 self.async_mode = False def mk_scapy_crypt_key(p): if p.crypt_algo in ("AES-GCM", "AES-CTR"): 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} esn_en = bool( p.flags & (VppEnum.vl_api_ipsec_sad_flags_t.IPSEC_API_SAD_FLAG_USE_ESN) ) p.tun_dst = tun_if.remote_addr[p.addr_type] p.tun_src = tun_if.local_addr[p.addr_type] 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=crypt_key, auth_algo=p.auth_algo, auth_key=p.auth_key, tunnel_header=ip_class_by_addr_type[p.addr_type](src=p.tun_dst, dst=p.tun_src), nat_t_header=p.nat_header, esn_en=esn_en, ) p.vpp_tun_sa = SecurityAssociation( encryption_type, spi=p.scapy_tun_spi, 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=p.tun_dst, src=p.tun_src), nat_t_header=p.nat_header, esn_en=esn_en, ) def config_tra_params(p, encryption_type): esn_en = 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=crypt_key, auth_algo=p.auth_algo, auth_key=p.auth_key, nat_t_header=p.nat_header, esn_en=esn_en, ) p.vpp_tra_sa = SecurityAssociation( encryption_type, spi=p.scapy_tra_spi, crypt_algo=p.crypt_algo, crypt_key=crypt_key, auth_algo=p.auth_algo, auth_key=p.auth_key, nat_t_header=p.nat_header, esn_en=esn_en, ) class TemplateIpsec(VppTestCase): """ TRANSPORT MODE:: ------ encrypt --- |tra_if| <-------> |VPP| ------ decrypt --- TUNNEL MODE:: ------ encrypt --- plain --- |tun_if| <------- |VPP| <------ |pg1| ------ --- --- ------ decrypt --- plain --- |tun_if| -------> |VPP| ------> |pg1| ------ --- --- """ tun_spd_id = 1 tra_spd_id = 2 def ipsec_select_backend(self): """empty method to be overloaded when necessary""" pass @classmethod def setUpClass(cls): super(TemplateIpsec, cls).setUpClass() @classmethod def tearDownClass(cls): super(TemplateIpsec, cls).tearDownClass() def setup_params(self): if not hasattr(self, "ipv4_params"): self.ipv4_params = IPsecIPv4Params() if not hasattr(self, "ipv6_params"): 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.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.config_interfaces() self.ipsec_select_backend() def unconfig_interfaces(self): for i in self.interfaces: i.admin_down() i.unconfig_ip4() i.unconfig_ip6() 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, p, 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(b"X" * payload_size)) for i in range(count) ] def gen_encrypt_pkts6(self, p, sa, sw_intf, src, dst, count=1, payload_size=54): return [ Ether(src=sw_intf.remote_mac, dst=sw_intf.local_mac) / sa.encrypt( IPv6(src=src, dst=dst, hlim=p.inner_hop_limit, fl=p.inner_flow_label) / ICMPv6EchoRequest(id=0, seq=1, data="X" * payload_size) ) for i in range(count) ] 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(b"X" * payload_size) for i in range(count) ] def gen_pkts6(self, p, sw_intf, src, dst, count=1, payload_size=54): return [ Ether(src=sw_intf.remote_mac, dst=sw_intf.local_mac) / IPv6(src=src, dst=dst, hlim=p.inner_hop_limit, fl=p.inner_flow_label) / ICMPv6EchoRequest(id=0, seq=1, data="X" * payload_size) for i in range(count) ] class IpsecTcp(object): def verify_tcp_checksum(self): # start http cli server listener on http://0.0.0.0:80 self.vapi.cli("http cli server") p = self.params[socket.AF_INET] 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) / TCP(flags="S", dport=80) ) self.logger.debug(ppp("Sending packet:", send)) recv = self.send_and_expect(self.tun_if, [send], self.tun_if) recv = recv[0] decrypted = p.vpp_tun_sa.decrypt(recv[IP]) self.assert_packet_checksums_valid(decrypted) 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 get_replay_counts(self, p): replay_node_name = "/err/%s/SA replayed packet" % self.tra4_decrypt_node_name[0] count = self.statistics.get_err_counter(replay_node_name) if p.async_mode: replay_post_node_name = ( "/err/%s/SA replayed packet" % self.tra4_decrypt_node_name[p.async_mode] ) count += self.statistics.get_err_counter(replay_post_node_name) return count def get_hash_failed_counts(self, p): 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[p.async_mode] ) else: hash_failed_node_name = ( "/err/%s/Integrity check failed" % self.tra4_decrypt_node_name[p.async_mode] ) count = self.statistics.get_err_counter(hash_failed_node_name) if p.async_mode: count += self.statistics.get_err_counter("/err/crypto-dispatch/bad-hmac") return count def verify_hi_seq_num(self): p = self.params[socket.AF_INET] saf = VppEnum.vl_api_ipsec_sad_flags_t esn_on = p.vpp_tra_sa.esn_en ar_on = p.flags & saf.IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY seq_cycle_node_name = ( "/err/%s/sequence number cycled (packet dropped)" % self.tra4_encrypt_node_name ) replay_count = self.get_replay_counts(p) hash_failed_count = self.get_hash_failed_counts(p) seq_cycle_count = self.statistics.get_err_counter(seq_cycle_node_name) # a few packets so we get the rx seq number above the window size and # thus can simulate a wrap with an out of window packet 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(63, 80) ] recv_pkts = self.send_and_expect(self.tra_if, pkts, self.tra_if) # these 4 packets will all choose seq-num 0 to decrpyt since none # are out of window when first checked. however, once #200 has # decrypted it will move the window to 200 and has #81 is out of # window. this packet should be dropped. 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=200, ) ), ( 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=81, ) ), ( 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=201, ) ), ( 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=202, ) ), ] # if anti-replay is off then we won't drop #81 n_rx = 3 if ar_on else 4 self.send_and_expect(self.tra_if, pkts, self.tra_if, n_rx=n_rx) # this packet is one before the wrap 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=203, ) ) ] recv_pkts = self.send_and_expect(self.tra_if, pkts, self.tra_if) # move the window over half way to a wrap 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=0x80000001, ) ) ] recv_pkts = self.send_and_expect(self.tra_if, pkts, self.tra_if) # anti-replay will drop old packets, no anti-replay will not 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=0x44000001, ) ) ] if ar_on: self.send_and_assert_no_replies(self.tra_if, pkts) else: recv_pkts = self.send_and_expect(self.tra_if, pkts, self.tra_if) if esn_on: # # validate wrapping the ESN # # wrap scapy's TX SA SN p.scapy_tra_sa.seq_num = 0x100000005 # send a packet that wraps the window for both AR and no AR 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=0x100000005, ) ) ] rxs = self.send_and_expect(self.tra_if, pkts, self.tra_if) for rx in rxs: decrypted = p.vpp_tra_sa.decrypt(rx[0][IP]) # move the window forward to half way to the next wrap 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=0x180000005, ) ) ] rxs = self.send_and_expect(self.tra_if, pkts, self.tra_if) # a packet less than 2^30 from the current position is: # - AR: out of window and dropped # - non-AR: accepted 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=0x170000005, ) ) ] if ar_on: self.send_and_assert_no_replies(self.tra_if, pkts) else: self.send_and_expect(self.tra_if, pkts, self.tra_if) # a packet more than 2^30 from the current position is: # - AR: out of window and dropped # - non-AR: considered a wrap, but since it's not a wrap # it won't decrpyt and so will be dropped 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=0x130000005, ) ) ] self.send_and_assert_no_replies(self.tra_if, pkts) # a packet less than 2^30 from the current position and is a # wrap; (the seq is currently at 0x180000005). # - AR: out of window so considered a wrap, so accepted # - non-AR: not considered a wrap, so won't decrypt p.scapy_tra_sa.seq_num = 0x260000005 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=0x260000005, ) ) ] if ar_on: self.send_and_expect(self.tra_if, pkts, self.tra_if) else: self.send_and_assert_no_replies(self.tra_if, pkts) # # window positions are different now for AR/non-AR # move non-AR forward # if not ar_on: # a packet more than 2^30 from the current position and is a # wrap; (the seq is currently at 0x180000005). # - AR: accepted # - non-AR: not considered a wrap, so won't decrypt 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=0x200000005, ) ), ( 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=0x200000006, ) ), ] self.send_and_expect(self.tra_if, pkts, self.tra_if) 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=0x260000005, ) ) ] self.send_and_expect(self.tra_if, pkts, self.tra_if) def verify_tra_anti_replay(self): p = self.params[socket.AF_INET] esn_en = p.vpp_tra_sa.esn_en seq_cycle_node_name = ( "/err/%s/sequence number cycled (packet dropped)" % self.tra4_encrypt_node_name ) replay_count = self.get_replay_counts(p) hash_failed_count = self.get_hash_failed_counts(p) 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[0] ) 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) # replayed packets are dropped self.send_and_assert_no_replies(self.tra_if, pkts, timeout=0.2) replay_count += len(pkts) self.assertEqual(self.get_replay_counts(p), 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 # self.vapi.cli("clear error") self.vapi.cli("clear node counters") 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.assertEqual(self.get_replay_counts(p), replay_count) # # now move the window over to 257 (more than one byte) and into Case A # self.vapi.cli("clear error") 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=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, timeout=0.2) replay_count += 3 self.assertEqual(self.get_replay_counts(p), replay_count) # 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=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, 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, dst=self.tra_if.local_ip4) / ICMP(), seq_num=350, ) self.send_and_assert_no_replies(self.tra_if, pkt * 17, timeout=0.2) hash_failed_count += 17 self.assertEqual(self.get_hash_failed_counts(p), 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, timeout=0.2) 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, 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) # # 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, dst=self.tra_if.local_ip4) / ICMP(), seq_num=17, ) self.send_and_assert_no_replies(self.tra_if, pkt * 17, timeout=0.2) if esn_en: # an out of window error with ESN looks like a high sequence # wrap. but since it isn't then the verify will fail. hash_failed_count += 17 self.assertEqual(self.get_hash_failed_counts(p), hash_failed_count) else: replay_count += 17 self.assertEqual(self.get_replay_counts(p), replay_count) # 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=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 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")) 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 esn_en: rxs = self.send_and_expect(self.tra_if, pkts, self.tra_if) # # 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 ) / p.scapy_tra_sa.encrypt( IP(src=self.tra_if.remote_ip4, dst=self.tra_if.local_ip4) / ICMP(), seq_num=0x100000005, ) rx = self.send_and_expect(self.tra_if, [pkt], self.tra_if) 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 # because 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, timeout=0.2 ) hash_failed_count += 1 self.assertEqual(self.get_hash_failed_counts(p), hash_failed_count) # # but if we move the window 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: # # 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, timeout=0.2) 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 verify_tra_lost(self): p = self.params[socket.AF_INET] esn_en = p.vpp_tra_sa.esn_en # # 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, 3) ] self.send_and_expect(self.tra_if, pkts, self.tra_if) self.assertEqual(p.tra_sa_out.get_lost(), 0) # skip a sequence number 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(4, 6) ] self.send_and_expect(self.tra_if, pkts, self.tra_if) self.assertEqual(p.tra_sa_out.get_lost(), 0) # the lost packet are counted untill we get up past the first # sizeof(replay_window) packets 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(6, 100) ] self.send_and_expect(self.tra_if, pkts, self.tra_if) self.assertEqual(p.tra_sa_out.get_lost(), 1) # lost of holes in the sequence 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(100, 200, 2) ] self.send_and_expect(self.tra_if, pkts, self.tra_if, n_rx=50) 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(200, 300) ] self.send_and_expect(self.tra_if, pkts, self.tra_if) self.assertEqual(p.tra_sa_out.get_lost(), 51) # a big hole in the seq number space 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(400, 500) ] self.send_and_expect(self.tra_if, pkts, self.tra_if) self.assertEqual(p.tra_sa_out.get_lost(), 151) def verify_tra_basic4(self, count=1, payload_size=54): """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, p.scapy_tra_sa, self.tra_if, src=self.tra_if.remote_ip4, dst=self.tra_if.local_ip4, count=count, payload_size=payload_size, ) 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) except: 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 all")) 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.assertEqual(p.tra_sa_out.get_lost(), 0) self.assertEqual(p.tra_sa_in.get_lost(), 0) self.assert_packet_counter_equal(self.tra4_encrypt_node_name, count) self.assert_packet_counter_equal(self.tra4_decrypt_node_name[0], count) class IpsecTra4Tests(IpsecTra4): """UT test methods for Transport v4""" def test_tra_anti_replay(self): """ipsec v4 transport anti-replay test""" self.verify_tra_anti_replay() def test_tra_lost(self): """ipsec v4 transport lost packet test""" self.verify_tra_lost() 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.verify_tra_basic4(count=257) class IpsecTra6(object): """verify methods for Transport v6""" def verify_tra_basic6(self, count=1, payload_size=54): self.vapi.cli("clear errors") self.vapi.cli("clear ipsec sa") try: p = self.params[socket.AF_INET6] send_pkts = self.gen_encrypt_pkts6( p, p.scapy_tra_sa, self.tra_if, src=self.tra_if.remote_ip6, dst=self.tra_if.local_ip6, count=count, payload_size=payload_size, ) 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) except: 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 all")) 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[0], count) def gen_encrypt_pkts_ext_hdrs6( 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( IPv6(src=src, dst=dst) / ICMPv6EchoRequest(id=0, seq=1, data="X" * payload_size) ) for i in range(count) ] def gen_pkts_ext_hdrs6(self, sw_intf, src, dst, count=1, payload_size=54): return [ Ether(src=sw_intf.remote_mac, dst=sw_intf.local_mac) / IPv6(src=src, dst=dst) / IPv6ExtHdrHopByHop() / IPv6ExtHdrFragment(id=2, offset=200) / Raw(b"\xff" * 200) for i in range(count) ] def verify_tra_encrypted6(self, p, sa, rxs): decrypted = [] for rx in rxs: self.assert_packet_checksums_valid(rx) try: decrypt_pkt = p.vpp_tra_sa.decrypt(rx[IPv6]) decrypted.append(decrypt_pkt) self.assert_equal(decrypt_pkt.src, self.tra_if.local_ip6) self.assert_equal(decrypt_pkt.dst, self.tra_if.remote_ip6) except: self.logger.debug(ppp("Unexpected packet:", rx)) try: self.logger.debug(ppp("Decrypted packet:", decrypt_pkt)) except: pass raise return decrypted def verify_tra_66_ext_hdrs(self, p): count = 63 # # check we can decrypt with options # tx = self.gen_encrypt_pkts_ext_hdrs6( p.scapy_tra_sa, self.tra_if, src=self.tra_if.remote_ip6, dst=self.tra_if.local_ip6, count=count, ) self.send_and_expect(self.tra_if, tx, self.tra_if) # # injecting a packet from ourselves to be routed of box is a hack # but it matches an outbout policy, alors je ne regrette rien # # one extension before ESP tx = ( Ether(src=self.pg2.remote_mac, dst=self.pg2.local_mac) / IPv6(src=self.tra_if.local_ip6, dst=self.tra_if.remote_ip6) / IPv6ExtHdrFragment(id=2, offset=200) / Raw(b"\xff" * 200) ) rxs = self.send_and_expect(self.pg2, [tx], self.tra_if) dcs = self.verify_tra_encrypted6(p, p.vpp_tra_sa, rxs) for dc in dcs: # for reasons i'm not going to investigate scapy does not # created the correct headers after decrypt. but reparsing # the ipv6 packet fixes it dc = IPv6(raw(dc[IPv6])) self.assert_equal(dc[IPv6ExtHdrFragment].id, 2) # two extensions before ESP tx = ( Ether(src=self.pg2.remote_mac, dst=self.pg2.local_mac) / IPv6(src=self.tra_if.local_ip6, dst=self.tra_if.remote_ip6) / IPv6ExtHdrHopByHop() / IPv6ExtHdrFragment(id=2, offset=200) / Raw(b"\xff" * 200) ) rxs = self.send_and_expect(self.pg2, [tx], self.tra_if) dcs = self.verify_tra_encrypted6(p, p.vpp_tra_sa, rxs) for dc in dcs: dc = IPv6(raw(dc[IPv6])) self.assertTrue(dc[IPv6ExtHdrHopByHop]) self.assert_equal(dc[IPv6ExtHdrFragment].id, 2) # two extensions before ESP, one after tx = ( Ether(src=self.pg2.remote_mac, dst=self.pg2.local_mac) / IPv6(src=self.tra_if.local_ip6, dst=self.tra_if.remote_ip6) / IPv6ExtHdrHopByHop() / IPv6ExtHdrFragment(id=2, offset=200) / IPv6ExtHdrDestOpt() / Raw(b"\xff" * 200) ) rxs = self.send_and_expect(self.pg2, [tx], self.tra_if) dcs = self.verify_tra_encrypted6(p, p.vpp_tra_sa, rxs) for dc in dcs: dc = IPv6(raw(dc[IPv6])) self.assertTrue(dc[IPv6ExtHdrDestOpt]) self.assertTrue(dc[IPv6ExtHdrHopByHop]) self.assert_equal(dc[IPv6ExtHdrFragment].id, 2) 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.verify_tra_basic6(count=257) class IpsecTra6ExtTests(IpsecTra6): def test_tra_ext_hdrs_66(self): """ipsec 6o6 tra extension headers test""" self.verify_tra_66_ext_hdrs(self.params[socket.AF_INET6]) class IpsecTra46Tests(IpsecTra4Tests, IpsecTra6Tests): """UT test methods for Transport v6 and v4""" pass class IpsecTun4(object): """verify methods for Tunnel v4""" def verify_counters4(self, p, count, n_frags=None, worker=None): if not n_frags: n_frags = count if hasattr(p, "spd_policy_in_any"): pkts = p.spd_policy_in_any.get_stats(worker)["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(worker)["packets"] self.assertEqual( pkts, count, "incorrect SA in counts: expected %d != %d" % (count, pkts) ) pkts = p.tun_sa_out.get_stats(worker)["packets"] self.assertEqual( pkts, n_frags, "incorrect SA out counts: expected %d != %d" % (count, pkts), ) self.assert_packet_counter_equal(self.tun4_encrypt_node_name, n_frags) self.assert_packet_counter_equal(self.tun4_decrypt_node_name[0], count) def verify_decrypted(self, p, rxs): for rx in rxs: self.assert_equal(rx[IP].src, p.remote_tun_if_host) self.assert_equal(rx[IP].dst, self.pg1.remote_ip4) self.assert_packet_checksums_valid(rx) def verify_esp_padding(self, sa, esp_payload, decrypt_pkt): align = sa.crypt_algo.block_size if align < 4: align = 4 exp_len = (len(decrypt_pkt) + 2 + (align - 1)) & ~(align - 1) exp_len += sa.crypt_algo.iv_size exp_len += sa.crypt_algo.icv_size or sa.auth_algo.icv_size self.assertEqual(exp_len, len(esp_payload)) 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: rx_ip = rx[IP] decrypt_pkt = p.vpp_tun_sa.decrypt(rx_ip) if not decrypt_pkt.haslayer(IP): decrypt_pkt = IP(decrypt_pkt[Raw].load) if rx_ip.proto == socket.IPPROTO_ESP: self.verify_esp_padding(sa, rx_ip[ESP].data, decrypt_pkt) decrypt_pkts.append(decrypt_pkt) self.assert_equal(decrypt_pkt.src, self.pg1.remote_ip4) 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 pkts = reassemble4(decrypt_pkts) for pkt in pkts: self.assert_packet_checksums_valid(pkt) def verify_tun_44(self, p, count=1, payload_size=64, n_rx=None): self.vapi.cli("clear errors") self.vapi.cli("clear ipsec counters") self.vapi.cli("clear ipsec sa") if not n_rx: n_rx = count try: send_pkts = self.gen_encrypt_pkts( p, p.scapy_tun_sa, self.tun_if, src=p.remote_tun_if_host, dst=self.pg1.remote_ip4, count=count, payload_size=payload_size, ) recv_pkts = self.send_and_expect(self.tun_if, send_pkts, self.pg1) 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=count, payload_size=payload_size, ) recv_pkts = self.send_and_expect(self.pg1, send_pkts, self.tun_if, n_rx) self.verify_encrypted(p, p.vpp_tun_sa, recv_pkts) for rx in recv_pkts: self.assertEqual(rx[IP].src, p.tun_src) self.assertEqual(rx[IP].dst, p.tun_dst) finally: self.logger.info(self.vapi.ppcli("show error")) self.logger.info(self.vapi.ppcli("show ipsec all")) self.logger.info(self.vapi.ppcli("show ipsec sa 0")) self.logger.info(self.vapi.ppcli("show ipsec sa 4")) self.verify_counters4(p, count, n_rx) def verify_tun_dropped_44(self, p, count=1, payload_size=64, n_rx=None): self.vapi.cli("clear errors") if not n_rx: n_rx = count try: send_pkts = self.gen_encrypt_pkts( p, p.scapy_tun_sa, self.tun_if, src=p.remote_tun_if_host, dst=self.pg1.remote_ip4, count=count, ) self.send_and_assert_no_replies(self.tun_if, send_pkts) send_pkts = self.gen_pkts( self.pg1, src=self.pg1.remote_ip4, dst=p.remote_tun_if_host, count=count, payload_size=payload_size, ) self.send_and_assert_no_replies(self.pg1, send_pkts) finally: self.logger.info(self.vapi.ppcli("show error")) self.logger.info(self.vapi.ppcli("show ipsec all")) 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 ) try: send_pkts = self.gen_encrypt_pkts( p, 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") self.vapi.cli("clear ipsec sa") try: send_pkts = self.gen_encrypt_pkts6( p, p.scapy_tun_sa, self.tun_if, src=p.remote_tun_if_host6, 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_host6) self.assert_equal(recv_pkt[IPv6].dst, self.pg1.remote_ip6) self.assert_packet_checksums_valid(recv_pkt) send_pkts = self.gen_pkts6( p, self.pg1, src=self.pg1.remote_ip6, dst=p.remote_tun_if_host6, 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[IP]) 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_host6) self.assert_packet_checksums_valid(decrypt_pkt) except: self.logger.error(ppp("Unexpected packet:", recv_pkt)) try: self.logger.debug(ppp("Decrypted packet:", decrypt_pkt)) except: pass raise finally: self.logger.info(self.vapi.ppcli("show error")) self.logger.info(self.vapi.ppcli("show ipsec all")) self.verify_counters4(p, count) def verify_keepalive(self, p): # the sizeof Raw is calculated to pad to the minimum ehternet # frame size of 64 btyes 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") / Padding(0 * 21) ) 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 ) 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) 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") / Padding(0 * 21) ) self.send_and_assert_no_replies(self.tun_if, pkt * 31) self.assert_error_counter_equal("/err/%s/Too Short" % self.tun4_input_node, 62) 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) self.tun_if.admin_down() self.tun_if.resolve_arp() self.tun_if.admin_up() 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=127) class IpsecTun6(object): """verify methods for Tunnel v6""" def verify_counters6(self, p_in, p_out, count, worker=None): if hasattr(p_in, "tun_sa_in"): pkts = p_in.tun_sa_in.get_stats(worker)["packets"] self.assertEqual( pkts, count, "incorrect SA in counts: expected %d != %d" % (count, pkts) ) if hasattr(p_out, "tun_sa_out"): pkts = p_out.tun_sa_out.get_stats(worker)["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[0], count) 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) self.assert_equal(rx[IPv6].hlim, p.outer_hop_limit) if p.outer_flow_label: self.assert_equal(rx[IPv6].fl, p.outer_flow_label) 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) self.assert_equal(decrypt_pkt.hlim, p.inner_hop_limit - 1) self.assert_equal(decrypt_pkt.fl, p.inner_flow_label) except: self.logger.debug(ppp("Unexpected packet:", rx)) try: self.logger.debug(ppp("Decrypted packet:", decrypt_pkt)) except: pass raise def verify_drop_tun_tx_66(self, p_in, count=1, payload_size=64): self.vapi.cli("clear errors") self.vapi.cli("clear ipsec sa") send_pkts = self.gen_pkts6( p_in, self.pg1, src=self.pg1.remote_ip6, dst=p_in.remote_tun_if_host, count=count, payload_size=payload_size, ) self.send_and_assert_no_replies(self.tun_if, send_pkts) self.logger.info(self.vapi.cli("sh punt stats")) def verify_drop_tun_rx_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, 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) def verify_drop_tun_66(self, p_in, count=1, payload_size=64): self.verify_drop_tun_tx_66(p_in, count=count, payload_size=payload_size) self.verify_drop_tun_rx_66(p_in, count=count, payload_size=payload_size) 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: send_pkts = self.gen_encrypt_pkts6( p_in, p_in.scapy_tun_sa, self.tun_if, src=p_in.remote_tun_if_host, dst=self.pg1.remote_ip6, count=count, payload_size=payload_size, ) recv_pkts = self.send_and_expect(self.tun_if, send_pkts, self.pg1) self.verify_decrypted6(p_in, recv_pkts) send_pkts = self.gen_pkts6( p_in, 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) for rx in recv_pkts: self.assertEqual(rx[IPv6].src, p_out.tun_src) self.assertEqual(rx[IPv6].dst, p_out.tun_dst) 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, p.scapy_tun_sa, self.tun_if, src=p.remote_tun_if_host, dst=self.pg1.remote_ip6, count=1, payload_size=1850, ) 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( p, self.pg1, src=self.pg1.remote_ip6, dst=p.remote_tun_if_host, 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 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") self.vapi.cli("clear ipsec sa") try: send_pkts = self.gen_encrypt_pkts( p, p.scapy_tun_sa, self.tun_if, src=p.remote_tun_if_host4, dst=self.pg1.remote_ip4, 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[IP].src, p.remote_tun_if_host4) self.assert_equal(recv_pkt[IP].dst, self.pg1.remote_ip4) self.assert_packet_checksums_valid(recv_pkt) send_pkts = self.gen_pkts( self.pg1, src=self.pg1.remote_ip4, dst=p.remote_tun_if_host4, 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(IP): decrypt_pkt = IP(decrypt_pkt[Raw].load) self.assert_equal(decrypt_pkt.src, self.pg1.remote_ip4) self.assert_equal(decrypt_pkt.dst, p.remote_tun_if_host4) 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 finally: self.logger.info(self.vapi.ppcli("show error")) 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 IpsecTun6HandoffTests(IpsecTun6): """UT test methods for Tunnel v6 with multiple workers""" vpp_worker_count = 2 def test_tun_handoff_66(self): """ipsec 6o6 tunnel worker hand-off test""" self.vapi.cli("clear errors") self.vapi.cli("clear ipsec sa") N_PKTS = 15 p = self.params[socket.AF_INET6] # inject alternately on worker 0 and 1. all counts on the SA # should be against worker 0 for worker in [0, 1, 0, 1]: send_pkts = self.gen_encrypt_pkts6( p, p.scapy_tun_sa, self.tun_if, src=p.remote_tun_if_host, dst=self.pg1.remote_ip6, count=N_PKTS, ) recv_pkts = self.send_and_expect( self.tun_if, send_pkts, self.pg1, worker=worker ) self.verify_decrypted6(p, recv_pkts) send_pkts = self.gen_pkts6( p, self.pg1, src=self.pg1.remote_ip6, dst=p.remote_tun_if_host, count=N_PKTS, ) recv_pkts = self.send_and_expect( self.pg1, send_pkts, self.tun_if, worker=worker ) self.verify_encrypted6(p, p.vpp_tun_sa, recv_pkts) # all counts against the first worker that was used self.verify_counters6(p, p, 4 * N_PKTS, worker=0) class IpsecTun4HandoffTests(IpsecTun4): """UT test methods for Tunnel v4 with multiple workers""" vpp_worker_count = 2 def test_tun_handooff_44(self): """ipsec 4o4 tunnel worker hand-off test""" self.vapi.cli("clear errors") self.vapi.cli("clear ipsec sa") N_PKTS = 15 p = self.params[socket.AF_INET] # inject alternately on worker 0 and 1. all counts on the SA # should be against worker 0 for worker in [0, 1, 0, 1]: send_pkts = self.gen_encrypt_pkts( p, p.scapy_tun_sa, self.tun_if, src=p.remote_tun_if_host, dst=self.pg1.remote_ip4, count=N_PKTS, ) recv_pkts = self.send_and_expect( self.tun_if, send_pkts, self.pg1, worker=worker ) 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=N_PKTS, ) recv_pkts = self.send_and_expect( self.pg1, send_pkts, self.tun_if, worker=worker ) self.verify_encrypted(p, p.vpp_tun_sa, recv_pkts) # all counts against the first worker that was used self.verify_counters4(p, 4 * N_PKTS, worker=0) class IpsecTun46Tests(IpsecTun4Tests, IpsecTun6Tests): """UT test methods for Tunnel v6 & v4""" pass class IPSecIPv4Fwd(VppTestCase): """Test IPSec by capturing and verifying IPv4 forwarded pkts""" @classmethod def setUpConstants(cls): super(IPSecIPv4Fwd, cls).setUpConstants() def setUp(self): super(IPSecIPv4Fwd, self).setUp() # store SPD objects so we can remove configs on tear down self.spd_objs = [] self.spd_policies = [] def tearDown(self): # remove SPD policies for obj in self.spd_policies: obj.remove_vpp_config() self.spd_policies = [] # remove SPD items (interface bindings first, then SPD) for obj in reversed(self.spd_objs): obj.remove_vpp_config() self.spd_objs = [] # close down pg intfs for pg in self.pg_interfaces: pg.unconfig_ip4() pg.admin_down() super(IPSecIPv4Fwd, self).tearDown() def create_interfaces(self, num_ifs=2): # create interfaces pg0 ... pg self.create_pg_interfaces(range(num_ifs)) for pg in self.pg_interfaces: # put the interface up pg.admin_up() # configure IPv4 address on the interface pg.config_ip4() # resolve ARP, so that we know VPP MAC pg.resolve_arp() self.logger.info(self.vapi.ppcli("show int addr")) def spd_create_and_intf_add(self, spd_id, pg_list): spd = VppIpsecSpd(self, spd_id) spd.add_vpp_config() self.spd_objs.append(spd) for pg in pg_list: spdItf = VppIpsecSpdItfBinding(self, spd, pg) spdItf.add_vpp_config() self.spd_objs.append(spdItf) def get_policy(self, policy_type): e = VppEnum.vl_api_ipsec_spd_action_t if policy_type == "protect": return e.IPSEC_API_SPD_ACTION_PROTECT elif policy_type == "bypass": return e.IPSEC_API_SPD_ACTION_BYPASS elif policy_type == "discard": return e.IPSEC_API_SPD_ACTION_DISCARD else: raise Exception("Invalid policy type: %s", policy_type) def spd_add_rem_policy( self, spd_id, src_if, dst_if, proto, is_out, priority, policy_type, remove=False, all_ips=False, ip_range=False, local_ip_start=ip_address("0.0.0.0"), local_ip_stop=ip_address("255.255.255.255"), remote_ip_start=ip_address("0.0.0.0"), remote_ip_stop=ip_address("255.255.255.255"), remote_port_start=0, remote_port_stop=65535, local_port_start=0, local_port_stop=65535, ): spd = VppIpsecSpd(self, spd_id) if all_ips: src_range_low = ip_address("0.0.0.0") src_range_high = ip_address("255.255.255.255") dst_range_low = ip_address("0.0.0.0") dst_range_high = ip_address("255.255.255.255") elif ip_range: src_range_low = local_ip_start src_range_high = local_ip_stop dst_range_low = remote_ip_start dst_range_high = remote_ip_stop else: src_range_low = src_if.remote_ip4 src_range_high = src_if.remote_ip4 dst_range_low = dst_if.remote_ip4 dst_range_high = dst_if.remote_ip4 spdEntry = VppIpsecSpdEntry( self, spd, 0, src_range_low, src_range_high, dst_range_low, dst_range_high, proto, priority=priority, policy=self.get_policy(policy_type), is_outbound=is_out, remote_port_start=remote_port_start, remote_port_stop=remote_port_stop, local_port_start=local_port_start, local_port_stop=local_port_stop, ) if remove is False: spdEntry.add_vpp_config() self.spd_policies.append(spdEntry) else: spdEntry.remove_vpp_config() self.spd_policies.remove(spdEntry) self.logger.info(self.vapi.ppcli("show ipsec all")) return spdEntry def create_stream(self, src_if, dst_if, pkt_count, src_prt=1234, dst_prt=5678): packets = [] for i in range(pkt_count): # create packet info stored in the test case instance info = self.create_packet_info(src_if, dst_if) # convert the info into packet payload payload = self.info_to_payload(info) # create the packet itself p = ( Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) / UDP(sport=src_prt, dport=dst_prt) / Raw(payload) ) # store a copy of the packet in the packet info info.data = p.copy() # append the packet to the list packets.append(p) # return the created packet list return packets def verify_capture(self, src_if, dst_if, capture): packet_info = None for packet in capture: try: ip = packet[IP] udp = packet[UDP] # convert the payload to packet info object payload_info = self.payload_to_info(packet) # make sure the indexes match self.assert_equal( payload_info.src, src_if.sw_if_index, "source sw_if_index" ) self.assert_equal( payload_info.dst, dst_if.sw_if_index, "destination sw_if_index" ) packet_info = self.get_next_packet_info_for_interface2( src_if.sw_if_index, dst_if.sw_if_index, packet_info ) # make sure we didn't run out of saved packets self.assertIsNotNone(packet_info) self.assert_equal( payload_info.index, packet_info.index, "packet info index" ) saved_packet = packet_info.data # fetch the saved packet # assert the values match self.assert_equal(ip.src, saved_packet[IP].src, "IP source address") # ... more assertions here self.assert_equal(udp.sport, saved_packet[UDP].sport, "UDP source port") except Exception as e: self.logger.error(ppp("Unexpected or invalid packet:", packet)) raise remaining_packet = self.get_next_packet_info_for_interface2( src_if.sw_if_index, dst_if.sw_if_index, packet_info ) self.assertIsNone( remaining_packet, "Interface %s: Packet expected from interface " "%s didn't arrive" % (dst_if.name, src_if.name), ) def verify_policy_match(self, pkt_count, spdEntry): self.logger.info("XXXX %s %s", str(spdEntry), str(spdEntry.get_stats())) matched_pkts = spdEntry.get_stats().get("packets") self.logger.info("Policy %s matched: %d pkts", str(spdEntry), matched_pkts) self.assert_equal(pkt_count, matched_pkts) class SpdFlowCacheTemplate(IPSecIPv4Fwd): @classmethod def setUpConstants(cls): super(SpdFlowCacheTemplate, cls).setUpConstants() # Override this method with required cmdline parameters e.g. # cls.vpp_cmdline.extend(["ipsec", "{", # "ipv4-outbound-spd-flow-cache on", # "}"]) # cls.logger.info("VPP modified cmdline is %s" % " " # .join(cls.vpp_cmdline)) def setUp(self): super(SpdFlowCacheTemplate, self).setUp() def tearDown(self): super(SpdFlowCacheTemplate, self).tearDown() def get_spd_flow_cache_entries(self, outbound): """'show ipsec spd' output: ipv4-inbound-spd-flow-cache-entries: 0 ipv4-outbound-spd-flow-cache-entries: 0 """ show_ipsec_reply = self.vapi.cli("show ipsec spd") # match the relevant section of 'show ipsec spd' output if outbound: regex_match = re.search( "ipv4-outbound-spd-flow-cache-entries: (.*)", show_ipsec_reply, re.DOTALL, ) else: regex_match = re.search( "ipv4-inbound-spd-flow-cache-entries: (.*)", show_ipsec_reply, re.DOTALL ) if regex_match is None: raise Exception( "Unable to find spd flow cache entries \ in 'show ipsec spd' CLI output - regex failed to match" ) else: try: num_entries = int(regex_match.group(1)) except ValueError: raise Exception( "Unable to get spd flow cache entries \ from 'show ipsec spd' string: %s", regex_match.group(0), ) self.logger.info("%s", regex_match.group(0)) return num_entries def verify_num_outbound_flow_cache_entries(self, expected_elements): self.assertEqual( self.get_spd_flow_cache_entries(outbound=True), expected_elements ) def verify_num_inbound_flow_cache_entries(self, expected_elements): self.assertEqual( self.get_spd_flow_cache_entries(outbound=False), expected_elements ) def crc32_supported(self): # lscpu is part of util-linux package, available on all Linux Distros stream = os.popen("lscpu") cpu_info = stream.read() # feature/flag "crc32" on Aarch64 and "sse4_2" on x86 # see vppinfra/crc32.h if "crc32" or "sse4_2" in cpu_info: self.logger.info("\ncrc32 supported:\n" + cpu_info) return True else: self.logger.info("\ncrc32 NOT supported:\n" + cpu_info) return False if __name__ == "__main__": unittest.main(testRunner=VppTestRunner)