X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=blobdiff_plain;f=resources%2Flibraries%2Fpython%2FIPsecUtil.py;h=c99105abebe103c492b7e5292f6d105c3c37d166;hp=e8fdad3901fabff3abc907d751c80e8acf4c96f7;hb=333bab321770cbf99f8219e1dd6073193815b804;hpb=8bf02005fc2fc4f7a594a1887d1547aa8f417cb5 diff --git a/resources/libraries/python/IPsecUtil.py b/resources/libraries/python/IPsecUtil.py index e8fdad3901..c99105abeb 100644 --- a/resources/libraries/python/IPsecUtil.py +++ b/resources/libraries/python/IPsecUtil.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Cisco and/or its affiliates. +# Copyright (c) 2021 Cisco and/or its affiliates. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: @@ -22,15 +22,20 @@ from string import ascii_letters from ipaddress import ip_network, ip_address -from resources.libraries.python.IPUtil import IPUtil +from resources.libraries.python.Constants import Constants from resources.libraries.python.InterfaceUtil import InterfaceUtil, \ InterfaceStatusFlags +from resources.libraries.python.IPAddress import IPAddress +from resources.libraries.python.IPUtil import IPUtil, IpDscp, MPLS_LABEL_INVALID from resources.libraries.python.PapiExecutor import PapiSocketExecutor from resources.libraries.python.ssh import scp_node from resources.libraries.python.topology import Topology from resources.libraries.python.VatExecutor import VatExecutor +IPSEC_UDP_PORT_NONE = 0xffff + + def gen_key(length): """Generate random string as a key. @@ -83,15 +88,49 @@ class IntegAlg(Enum): class IPsecProto(IntEnum): """IPsec protocol.""" - ESP = 1 - SEC_AH = 0 + IPSEC_API_PROTO_ESP = 50 + IPSEC_API_PROTO_AH = 51 class IPsecSadFlags(IntEnum): """IPsec Security Association Database flags.""" - IPSEC_API_SAD_FLAG_NONE = 0 - IPSEC_API_SAD_FLAG_IS_TUNNEL = 4 - IPSEC_API_SAD_FLAG_IS_TUNNEL_V6 = 8 + IPSEC_API_SAD_FLAG_NONE = 0, + # Enable extended sequence numbers + IPSEC_API_SAD_FLAG_USE_ESN = 0x01, + # Enable Anti - replay + IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY = 0x02, + # IPsec tunnel mode if non-zero, else transport mode + IPSEC_API_SAD_FLAG_IS_TUNNEL = 0x04, + # IPsec tunnel mode is IPv6 if non-zero, else IPv4 tunnel + # only valid if is_tunnel is non-zero + IPSEC_API_SAD_FLAG_IS_TUNNEL_V6 = 0x08, + # Enable UDP encapsulation for NAT traversal + IPSEC_API_SAD_FLAG_UDP_ENCAP = 0x10, + # IPsec SA is or inbound traffic + IPSEC_API_SAD_FLAG_IS_INBOUND = 0x40 + + +class TunnelEncpaDecapFlags(IntEnum): + """Flags controlling tunnel behaviour.""" + TUNNEL_API_ENCAP_DECAP_FLAG_NONE = 0 + # at encap, copy the DF bit of the payload into the tunnel header + TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_DF = 1 + # at encap, set the DF bit in the tunnel header + TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_SET_DF = 2 + # at encap, copy the DSCP bits of the payload into the tunnel header + TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_DSCP = 4 + # at encap, copy the ECN bit of the payload into the tunnel header + TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_ECN = 8 + # at decap, copy the ECN bit of the tunnel header into the payload + TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_SET_ECN = 16 + + +class TunnelMode(IntEnum): + """Tunnel modes.""" + # point-to-point + TUNNEL_API_MODE_P2P = 0 + # multi-point + TUNNEL_API_MODE_MP = 1 class IPsecUtil: @@ -204,12 +243,14 @@ class IPsecUtil: def get_integ_alg_key_len(integ_alg): """Return integrity algorithm key length. + None argument is accepted, returning zero. + :param integ_alg: Integrity algorithm. - :type integ_alg: IntegAlg + :type integ_alg: Optional[IntegAlg] :returns: Key length. :rtype: int """ - return integ_alg.key_len + return 0 if integ_alg is None else integ_alg.key_len @staticmethod def get_integ_alg_scapy_name(integ_alg): @@ -229,7 +270,7 @@ class IPsecUtil: :returns: IPsecProto enum ESP object. :rtype: IPsecProto """ - return int(IPsecProto.ESP) + return int(IPsecProto.IPSEC_API_PROTO_ESP) @staticmethod def ipsec_proto_ah(): @@ -238,7 +279,7 @@ class IPsecUtil: :returns: IPsecProto enum AH object. :rtype: IPsecProto """ - return int(IPsecProto.SEC_AH) + return int(IPsecProto.IPSEC_API_PROTO_AH) @staticmethod def vpp_ipsec_select_backend(node, protocol, index=1): @@ -262,6 +303,49 @@ class IPsecUtil: with PapiSocketExecutor(node) as papi_exec: papi_exec.add(cmd, **args).get_reply(err_msg) + @staticmethod + def vpp_ipsec_set_async_mode(node, async_enable=1): + """Set IPsec async mode on|off. + + :param node: VPP node to set IPsec async mode. + :param async_enable: Async mode on or off. + :type node: dict + :type async_enable: int + :raises RuntimeError: If failed to set IPsec async mode or if no API + reply received. + """ + cmd = u"ipsec_set_async_mode" + err_msg = f"Failed to set IPsec async mode on host {node[u'host']}" + args = dict( + async_enable=async_enable + ) + with PapiSocketExecutor(node) as papi_exec: + papi_exec.add(cmd, **args).get_reply(err_msg) + + @staticmethod + def vpp_ipsec_crypto_sw_scheduler_set_worker( + node, worker_index, crypto_enable=False): + """Enable or disable crypto on specific vpp worker threads. + + :param node: VPP node to enable or disable crypto for worker threads. + :param worker_index: VPP worker thread index. + :param crypto_enable: Disable or enable crypto work. + :type node: dict + :type worker_index: int + :type crypto_enable: bool + :raises RuntimeError: If failed to enable or disable crypto for worker + thread or if no API reply received. + """ + cmd = u"crypto_sw_scheduler_set_worker" + err_msg = f"Failed to disable/enable crypto for worker thread " \ + f"on host {node[u'host']}" + args = dict( + worker_index=worker_index, + crypto_enable=crypto_enable + ) + with PapiSocketExecutor(node) as papi_exec: + papi_exec.add(cmd, **args).get_reply(err_msg) + @staticmethod def vpp_ipsec_add_sad_entry( node, sad_id, spi, crypto_alg, crypto_key, integ_alg=None, @@ -284,7 +368,7 @@ class IPsecUtil: :type spi: int :type crypto_alg: CryptoAlg :type crypto_key: str - :type integ_alg: IntegAlg + :type integ_alg: Optional[IntegAlg] :type integ_key: str :type tunnel_src: str :type tunnel_dst: str @@ -314,7 +398,7 @@ class IPsecUtil: src_addr = u"" dst_addr = u"" - cmd = u"ipsec_sad_entry_add_del" + cmd = u"ipsec_sad_entry_add_del_v2" err_msg = f"Failed to add Security Association Database entry " \ f"on host {node[u'host']}" sad_entry = dict( @@ -327,10 +411,16 @@ class IPsecUtil: flags=flags, tunnel_src=str(src_addr), tunnel_dst=str(dst_addr), - protocol=int(IPsecProto.ESP) + tunnel_flags=int( + TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE + ), + dscp=int(IpDscp.IP_API_DSCP_CS0), + protocol=int(IPsecProto.IPSEC_API_PROTO_ESP), + udp_src_port=4500, # default value in api + udp_dst_port=4500 # default value in api ) args = dict( - is_add=1, + is_add=True, entry=sad_entry ) with PapiSocketExecutor(node) as papi_exec: @@ -362,7 +452,7 @@ class IPsecUtil: :type spi: int :type crypto_alg: CryptoAlg :type crypto_key: str - :type integ_alg: IntegAlg + :type integ_alg: Optional[IntegAlg] :type integ_key: str :type tunnel_src: str :type tunnel_dst: str @@ -389,8 +479,8 @@ class IPsecUtil: integ = f"integ-alg {integ_alg.alg_name} " \ f"integ-key {integ_key.hex()}" \ if integ_alg else u"" - tunnel = f"tunnel-src {src_addr + i * addr_incr} " \ - f"tunnel-dst {dst_addr + i * addr_incr}" \ + tunnel = f"tunnel src {src_addr + i * addr_incr} " \ + f"tunnel dst {dst_addr + i * addr_incr}" \ if tunnel_src and tunnel_dst else u"" conf = f"exec ipsec sa add {sad_id + i} esp spi {spi + i} "\ f"crypto-alg {crypto_alg.alg_name} " \ @@ -422,7 +512,7 @@ class IPsecUtil: IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6 ) - cmd = u"ipsec_sad_entry_add_del" + cmd = u"ipsec_sad_entry_add_del_v2" err_msg = f"Failed to add Security Association Database entry " \ f"on host {node[u'host']}" @@ -436,10 +526,16 @@ class IPsecUtil: flags=flags, tunnel_src=str(src_addr), tunnel_dst=str(dst_addr), - protocol=int(IPsecProto.ESP) + tunnel_flags=int( + TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE + ), + dscp=int(IpDscp.IP_API_DSCP_CS0), + protocol=int(IPsecProto.IPSEC_API_PROTO_ESP), + udp_src_port=4500, # default value in api + udp_dst_port=4500 # default value in api ) args = dict( - is_add=1, + is_add=True, entry=sad_entry ) with PapiSocketExecutor(node) as papi_exec: @@ -477,10 +573,10 @@ class IPsecUtil: :type interface: str :type raddr_range: int """ - laddr = ip_address(tunnel_src) - raddr = ip_address(tunnel_dst) - taddr = ip_address(traffic_addr) - addr_incr = 1 << (128 - raddr_range) if laddr.version == 6 \ + tunnel_src = ip_address(tunnel_src) + tunnel_dst = ip_address(tunnel_dst) + traffic_addr = ip_address(traffic_addr) + addr_incr = 1 << (128 - raddr_range) if tunnel_src.version == 6 \ else 1 << (32 - raddr_range) if int(n_tunnels) > 10: @@ -490,13 +586,12 @@ class IPsecUtil: if_name = Topology.get_interface_name(node, interface) for i in range(n_tunnels): conf = f"exec set interface ip address {if_name} " \ - f"{laddr + i * addr_incr}/{raddr_range}\n" \ - f"exec ip route add {taddr + i}/" \ - f"{128 if taddr.version == 6 else 32} " \ - f"via {raddr + i * addr_incr} {if_name}\n" + f"{tunnel_src + i * addr_incr}/{raddr_range}\n" \ + f"exec ip route add {traffic_addr + i}/" \ + f"{128 if traffic_addr.version == 6 else 32} " \ + f"via {tunnel_dst + i * addr_incr} {if_name}\n" tmp_file.write(conf) - vat = VatExecutor() - vat.execute_script( + VatExecutor().execute_script( tmp_filename, node, timeout=300, json_out=False, copy_on_execute=True ) @@ -522,12 +617,12 @@ class IPsecUtil: with PapiSocketExecutor(node) as papi_exec: for i in range(n_tunnels): args1[u"prefix"] = IPUtil.create_prefix_object( - laddr + i * addr_incr, raddr_range + tunnel_src + i * addr_incr, raddr_range ) args2[u"route"] = IPUtil.compose_vpp_route_structure( - node, taddr + i, - prefix_len=128 if taddr.version == 6 else 32, - interface=interface, gateway=raddr + i * addr_incr + node, traffic_addr + i, + prefix_len=128 if traffic_addr.version == 6 else 32, + interface=interface, gateway=tunnel_dst + i * addr_incr ) history = bool(not 1 < i < n_tunnels - 2) papi_exec.add(cmd1, history=history, **args1).\ @@ -547,7 +642,7 @@ class IPsecUtil: err_msg = f"Failed to add Security Policy Database " \ f"on host {node[u'host']}" args = dict( - is_add=1, + is_add=True, spd_id=int(spd_id) ) with PapiSocketExecutor(node) as papi_exec: @@ -568,7 +663,7 @@ class IPsecUtil: err_msg = f"Failed to add interface {interface} to Security Policy " \ f"Database {spd_id} on host {node[u'host']}" args = dict( - is_add=1, + is_add=True, sw_if_index=InterfaceUtil.get_interface_index(node, interface), spd_id=int(spd_id) ) @@ -628,20 +723,20 @@ class IPsecUtil: spd_entry = dict( spd_id=int(spd_id), priority=int(priority), - is_outbound=0 if inbound else 1, + is_outbound=not inbound, sa_id=int(sa_id) if sa_id else 0, policy=action.policy_int_repr, protocol=int(proto) if proto else 0, - remote_address_start=IPUtil.create_ip_address_object( + remote_address_start=IPAddress.create_ip_address_object( ip_network(raddr_range, strict=False).network_address ), - remote_address_stop=IPUtil.create_ip_address_object( + remote_address_stop=IPAddress.create_ip_address_object( ip_network(raddr_range, strict=False).broadcast_address ), - local_address_start=IPUtil.create_ip_address_object( + local_address_start=IPAddress.create_ip_address_object( ip_network(laddr_range, strict=False).network_address ), - local_address_stop=IPUtil.create_ip_address_object( + local_address_stop=IPAddress.create_ip_address_object( ip_network(laddr_range, strict=False).broadcast_address ), remote_port_start=int(rport_range.split(u"-")[0]) if rport_range @@ -654,7 +749,7 @@ class IPsecUtil: else 65535 ) args = dict( - is_add=1, + is_add=True, entry=spd_entry ) with PapiSocketExecutor(node) as papi_exec: @@ -718,16 +813,16 @@ class IPsecUtil: spd_entry = dict( spd_id=int(spd_id), priority=int(priority), - is_outbound=0 if inbound else 1, + is_outbound=not inbound, sa_id=int(sa_id) if sa_id else 0, - policy=IPsecUtil.policy_action_protect().policy_int_repr, + policy=getattr(PolicyAction.PROTECT, u"policy_int_repr"), protocol=0, - remote_address_start=IPUtil.create_ip_address_object(raddr_ip), - remote_address_stop=IPUtil.create_ip_address_object(raddr_ip), - local_address_start=IPUtil.create_ip_address_object( + remote_address_start=IPAddress.create_ip_address_object(raddr_ip), + remote_address_stop=IPAddress.create_ip_address_object(raddr_ip), + local_address_start=IPAddress.create_ip_address_object( ip_network(laddr_range, strict=False).network_address ), - local_address_stop=IPUtil.create_ip_address_object( + local_address_stop=IPAddress.create_ip_address_object( ip_network(laddr_range, strict=False).broadcast_address ), remote_port_start=0, @@ -736,438 +831,914 @@ class IPsecUtil: local_port_stop=65535 ) args = dict( - is_add=1, + is_add=True, entry=spd_entry ) with PapiSocketExecutor(node) as papi_exec: for i in range(n_entries): args[u"entry"][u"remote_address_start"][u"un"] = \ - IPUtil.union_addr(raddr_ip + i) + IPAddress.union_addr(raddr_ip + i) args[u"entry"][u"remote_address_stop"][u"un"] = \ - IPUtil.union_addr(raddr_ip + i) + IPAddress.union_addr(raddr_ip + i) history = bool(not 1 < i < n_entries - 2) papi_exec.add(cmd, history=history, **args) papi_exec.get_replies(err_msg) @staticmethod - def vpp_ipsec_create_tunnel_interfaces( - nodes, if1_ip_addr, if2_ip_addr, if1_key, if2_key, n_tunnels, - crypto_alg, integ_alg, raddr_ip1, raddr_ip2, raddr_range, - existing_tunnels=0): - """Create multiple IPsec tunnel interfaces between two VPP nodes. + def _ipsec_create_tunnel_interfaces_dut1_vat( + nodes, tun_ips, if1_key, if2_key, n_tunnels, crypto_alg, integ_alg, + raddr_ip2, addr_incr, spi_d, existing_tunnels=0): + """Create multiple IPsec tunnel interfaces on DUT1 node using VAT. + + Generate random keys and return them (so DUT2 or TG can decrypt). :param nodes: VPP nodes to create tunnel interfaces. - :param if1_ip_addr: VPP node 1 interface IPv4/IPv6 address. - :param if2_ip_addr: VPP node 2 interface IPv4/IPv6 address. + :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface + IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface + IPv4/IPv6 address (ip2). :param if1_key: VPP node 1 interface key from topology file. - :param if2_key: VPP node 2 interface key from topology file. + :param if2_key: VPP node 2 / TG node (in case of 2-node topology) + interface key from topology file. :param n_tunnels: Number of tunnel interfaces to be there at the end. :param crypto_alg: The encryption algorithm name. :param integ_alg: The integrity algorithm name. - :param raddr_ip1: Policy selector remote IPv4/IPv6 start address for the - first tunnel in direction node1->node2. :param raddr_ip2: Policy selector remote IPv4/IPv6 start address for the first tunnel in direction node2->node1. - :param raddr_range: Mask specifying range of Policy selector Remote - IPv4/IPv6 addresses. Valid values are from 1 to 32 in case of IPv4 - and to 128 in case of IPv6. + :param spi_d: Dictionary with SPIs for VPP node 1 and VPP node 2. + :param addr_incr: IP / IPv6 address incremental step. :param existing_tunnels: Number of tunnel interfaces before creation. Useful mainly for reconf tests. Default 0. :type nodes: dict - :type if1_ip_addr: str - :type if2_ip_addr: str + :type tun_ips: dict :type if1_key: str :type if2_key: str :type n_tunnels: int :type crypto_alg: CryptoAlg - :type integ_alg: IntegAlg - :type raddr_ip1: string - :type raddr_ip2: string - :type raddr_range: int + :type integ_alg: Optional[IntegAlg] + :type raddr_ip2: IPv4Address or IPv6Address + :type addr_incr: int + :type spi_d: dict :type existing_tunnels: int + :returns: Generated ckeys and ikeys. + :rtype: List[bytes], List[bytes] """ - n_tunnels = int(n_tunnels) - existing_tunnels = int(existing_tunnels) - spi_1 = 100000 - spi_2 = 200000 - if1_ip = ip_address(if1_ip_addr) - if2_ip = ip_address(if2_ip_addr) - raddr_ip1 = ip_address(raddr_ip1) - raddr_ip2 = ip_address(raddr_ip2) - addr_incr = 1 << (128 - raddr_range) if if1_ip.version == 6 \ - else 1 << (32 - raddr_range) + tmp_fn1 = u"/tmp/ipsec_create_tunnel_dut1.config" + if1_n = Topology.get_interface_name(nodes[u"DUT1"], if1_key) - if n_tunnels - existing_tunnels > 10: - tmp_fn1 = u"/tmp/ipsec_create_tunnel_dut1.config" - tmp_fn2 = u"/tmp/ipsec_create_tunnel_dut2.config" - if1_n = Topology.get_interface_name(nodes[u"DUT1"], if1_key) - if2_n = Topology.get_interface_name(nodes[u"DUT2"], if2_key) - mask = 96 if if2_ip.version == 6 else 24 - mask2 = 128 if if2_ip.version == 6 else 32 - vat = VatExecutor() - with open(tmp_fn1, 'w') as tmp_f1, open(tmp_fn2, 'w') as tmp_f2: - rmac = Topology.get_interface_mac(nodes[u"DUT2"], if2_key) - if not existing_tunnels: - tmp_f1.write( - f"exec create loopback interface\n" - f"exec set interface state loop0 up\n" - f"exec set interface ip address " - f"{if1_n} {if2_ip - 1}/{mask}\n" - f"exec set ip neighbor {if1_n} {if2_ip}/{mask2} {rmac}" - f" static\n" - ) - tmp_f2.write( - f"exec set interface ip address {if2_n}" - f" {if2_ip}/{mask}\n" - ) - for i in range(existing_tunnels, n_tunnels): - ckey = gen_key( - IPsecUtil.get_crypto_alg_key_len(crypto_alg) - ).hex() - if integ_alg: - ikey = gen_key( - IPsecUtil.get_integ_alg_key_len(integ_alg) - ).hex() - integ = f"integ_alg {integ_alg.alg_name} " \ - f"local_integ_key {ikey} remote_integ_key {ikey} " - else: - integ = u"" - tmp_f1.write( - f"exec set interface ip address loop0 " - f"{if1_ip + i * addr_incr}/32\n" - f"ipsec_tunnel_if_add_del " - f"local_spi {spi_1 + i} remote_spi {spi_2 + i} " - f"crypto_alg {crypto_alg.alg_name} " - f"local_crypto_key {ckey} remote_crypto_key {ckey} " - f"{integ} " - f"local_ip {if1_ip + i * addr_incr} " - f"remote_ip {if2_ip} " - f"instance {i}\n" - ) - tmp_f2.write( - f"ipsec_tunnel_if_add_del " - f"local_spi {spi_2 + i} remote_spi {spi_1 + i} " - f"crypto_alg {crypto_alg.alg_name} " - f"local_crypto_key {ckey} remote_crypto_key {ckey} " - f"{integ} " - f"local_ip {if2_ip} " - f"remote_ip {if1_ip + i * addr_incr} " - f"instance {i}\n" - ) - vat.execute_script( - tmp_fn1, nodes[u"DUT1"], timeout=1800, json_out=False, - copy_on_execute=True, - history=bool(n_tunnels < 100) - ) - vat.execute_script( - tmp_fn2, nodes[u"DUT2"], timeout=1800, json_out=False, - copy_on_execute=True, - history=bool(n_tunnels < 100) - ) - os.remove(tmp_fn1) - os.remove(tmp_fn2) - - with open(tmp_fn1, 'w') as tmp_f1, open(tmp_fn2, 'w') as tmp_f2: - if not existing_tunnels: - tmp_f2.write( - f"exec ip route add {if1_ip}/8 via {if2_ip - 1}" - f" {if2_n}\n" - ) - for i in range(existing_tunnels, n_tunnels): - tmp_f1.write( - f"exec set interface unnumbered ipip{i} use {if1_n}\n" - f"exec set interface state ipip{i} up\n" - f"exec ip route add {raddr_ip2 + i}/{mask2} " - f"via ipip{i}\n" - ) - tmp_f2.write( - f"exec set interface unnumbered ipip{i} use {if2_n}\n" - f"exec set interface state ipip{i} up\n" - f"exec ip route add {raddr_ip1 + i}/{mask2} " - f"via ipip{i}\n" - ) - vat.execute_script( - tmp_fn1, nodes[u"DUT1"], timeout=1800, json_out=False, - copy_on_execute=True, - history=bool(n_tunnels < 100) - ) - vat.execute_script( - tmp_fn2, nodes[u"DUT2"], timeout=1800, json_out=False, - copy_on_execute=True, - history=bool(n_tunnels < 100) - ) - os.remove(tmp_fn1) - os.remove(tmp_fn2) - return + ckeys = [bytes()] * existing_tunnels + ikeys = [bytes()] * existing_tunnels - if not existing_tunnels: - with PapiSocketExecutor(nodes[u"DUT1"]) as papi_exec: - # Create loopback interface on DUT1, set it to up state - cmd1 = u"create_loopback" - args1 = dict( - mac_address=0 + vat = VatExecutor() + with open(tmp_fn1, u"w") as tmp_f1: + rmac = Topology.get_interface_mac(nodes[u"DUT2"], if2_key) \ + if u"DUT2" in nodes.keys() \ + else Topology.get_interface_mac(nodes[u"TG"], if2_key) + if not existing_tunnels: + tmp_f1.write( + f"exec create loopback interface\n" + f"exec set interface state loop0 up\n" + f"exec set interface ip address {if1_n} " + f"{tun_ips[u'ip2'] - 1}/" + f"{len(tun_ips[u'ip2'].packed)*8*3//4}\n" + f"exec set ip neighbor {if1_n} {tun_ips[u'ip2']} {rmac} " + f"static\n" ) - err_msg = f"Failed to create loopback interface " \ - f"on host {nodes[u'DUT1'][u'host']}" - loop_sw_if_idx = papi_exec.add(cmd1, **args1).\ - get_sw_if_index(err_msg) - cmd1 = u"sw_interface_set_flags" - args1 = dict( - sw_if_index=loop_sw_if_idx, - flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value + for i in range(existing_tunnels, n_tunnels): + ckeys.append( + gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg)) ) - err_msg = f"Failed to set loopback interface state up " \ - f"on host {nodes[u'DUT1'][u'host']}" - papi_exec.add(cmd1, **args1).get_reply(err_msg) - # Set IP address on VPP node 1 interface - cmd1 = u"sw_interface_add_del_address" - args1 = dict( - sw_if_index=InterfaceUtil.get_interface_index( + ikeys.append( + gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)) + ) + if integ_alg: + integ = f"integ-alg {integ_alg.alg_name} " \ + f"integ-key {ikeys[i].hex()} " + else: + integ = u"" + tmp_f1.write( + f"exec set interface ip address loop0 " + f"{tun_ips[u'ip1'] + i * addr_incr}/32\n" + f"exec create ipip tunnel " + f"src {tun_ips[u'ip1'] + i * addr_incr} " + f"dst {tun_ips[u'ip2']} " + f"p2p\n" + f"exec ipsec sa add {i} " + f"spi {spi_d[u'spi_1'] + i} " + f"crypto-alg {crypto_alg.alg_name} " + f"crypto-key {ckeys[i].hex()} " + f"{integ}" + f"esp\n" + f"exec ipsec sa add {100000 + i} " + f"spi {spi_d[u'spi_2'] + i} " + f"crypto-alg {crypto_alg.alg_name} " + f"crypto-key {ckeys[i].hex()} " + f"{integ}" + f"esp\n" + f"exec ipsec tunnel protect ipip{i} " + f"sa-out {i} " + f"sa-in {100000 + i} " + f"add\n" + ) + vat.execute_script( + tmp_fn1, nodes[u"DUT1"], timeout=1800, json_out=False, + copy_on_execute=True, + history=bool(n_tunnels < 100) + ) + os.remove(tmp_fn1) + + with open(tmp_fn1, 'w') as tmp_f1: + for i in range(existing_tunnels, n_tunnels): + tmp_f1.write( + f"exec set interface unnumbered ipip{i} use {if1_n}\n" + f"exec set interface state ipip{i} up\n" + f"exec ip route add " + f"{raddr_ip2 + i}/{len(raddr_ip2.packed)*8} " + f"via ipip{i}\n" + ) + vat.execute_script( + tmp_fn1, nodes[u"DUT1"], timeout=1800, json_out=False, + copy_on_execute=True, + history=bool(n_tunnels < 100) + ) + os.remove(tmp_fn1) + + return ckeys, ikeys + + @staticmethod + def _ipsec_create_tunnel_interfaces_dut2_vat( + nodes, tun_ips, if2_key, n_tunnels, crypto_alg, ckeys, integ_alg, + ikeys, raddr_ip1, addr_incr, spi_d, existing_tunnels=0): + """Create multiple IPsec tunnel interfaces on DUT2 node using VAT. + + This method accesses keys generated by DUT1 method + and does not return anything. + + :param nodes: VPP nodes to create tunnel interfaces. + :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface + IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface + IPv4/IPv6 address (ip2). + :param if2_key: VPP node 2 / TG node (in case of 2-node topology) + interface key from topology file. + :param n_tunnels: Number of tunnel interfaces to be there at the end. + :param crypto_alg: The encryption algorithm name. + :param ckeys: List of encryption keys. + :param integ_alg: The integrity algorithm name. + :param ikeys: List of integrity keys. + :param spi_d: Dictionary with SPIs for VPP node 1 and VPP node 2. + :param addr_incr: IP / IPv6 address incremental step. + :param existing_tunnels: Number of tunnel interfaces before creation. + Useful mainly for reconf tests. Default 0. + :type nodes: dict + :type tun_ips: dict + :type if2_key: str + :type n_tunnels: int + :type crypto_alg: CryptoAlg + :type ckeys: Sequence[bytes] + :type integ_alg: Optional[IntegAlg] + :type ikeys: Sequence[bytes] + :type addr_incr: int + :type spi_d: dict + :type existing_tunnels: int + """ + tmp_fn2 = u"/tmp/ipsec_create_tunnel_dut2.config" + if2_n = Topology.get_interface_name(nodes[u"DUT2"], if2_key) + + vat = VatExecutor() + with open(tmp_fn2, 'w') as tmp_f2: + if not existing_tunnels: + tmp_f2.write( + f"exec set interface ip address {if2_n}" + f" {tun_ips[u'ip2']}/{len(tun_ips[u'ip2'].packed)*8*3/4}\n" + ) + for i in range(existing_tunnels, n_tunnels): + if integ_alg: + integ = f"integ-alg {integ_alg.alg_name} " \ + f"integ-key {ikeys[i].hex()} " + else: + integ = u"" + tmp_f2.write( + f"exec create ipip tunnel " + f"src {tun_ips[u'ip2']} " + f"dst {tun_ips[u'ip1'] + i * addr_incr} " + f"p2p\n" + f"exec ipsec sa add {100000 + i} " + f"spi {spi_d[u'spi_2'] + i} " + f"crypto-alg {crypto_alg.alg_name} " + f"crypto-key {ckeys[i].hex()} " + f"{integ}" + f"esp\n" + f"exec ipsec sa add {i} " + f"spi {spi_d[u'spi_1'] + i} " + f"crypto-alg {crypto_alg.alg_name} " + f"crypto-key {ckeys[i].hex()} " + f"{integ}" + f"esp\n" + f"exec ipsec tunnel protect ipip{i} " + f"sa-out {100000 + i} " + f"sa-in {i} " + f"add\n" + ) + vat.execute_script( + tmp_fn2, nodes[u"DUT2"], timeout=1800, json_out=False, + copy_on_execute=True, + history=bool(n_tunnels < 100) + ) + os.remove(tmp_fn2) + + with open(tmp_fn2, 'w') as tmp_f2: + if not existing_tunnels: + tmp_f2.write( + f"exec ip route add {tun_ips[u'ip1']}/8 " + f"via {tun_ips[u'ip2'] - 1} {if2_n}\n" + ) + for i in range(existing_tunnels, n_tunnels): + tmp_f2.write( + f"exec set interface unnumbered ipip{i} use {if2_n}\n" + f"exec set interface state ipip{i} up\n" + f"exec ip route add " + f"{raddr_ip1 + i}/{len(raddr_ip1.packed)*8} " + f"via ipip{i}\n" + ) + vat.execute_script( + tmp_fn2, nodes[u"DUT2"], timeout=1800, json_out=False, + copy_on_execute=True, + history=bool(n_tunnels < 100) + ) + os.remove(tmp_fn2) + + @staticmethod + def _ipsec_create_loopback_dut1_papi(nodes, tun_ips, if1_key, if2_key): + """Create loopback interface and set IP address on VPP node 1 interface + using PAPI. + + :param nodes: VPP nodes to create tunnel interfaces. + :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface + IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface + IPv4/IPv6 address (ip2). + :param if1_key: VPP node 1 interface key from topology file. + :param if2_key: VPP node 2 / TG node (in case of 2-node topology) + interface key from topology file. + :type nodes: dict + :type tun_ips: dict + :type if1_key: str + :type if2_key: str + """ + with PapiSocketExecutor(nodes[u"DUT1"]) as papi_exec: + # Create loopback interface on DUT1, set it to up state + cmd = u"create_loopback_instance" + args = dict( + mac_address=0, + is_specified=False, + user_instance=0, + ) + err_msg = f"Failed to create loopback interface " \ + f"on host {nodes[u'DUT1'][u'host']}" + loop_sw_if_idx = papi_exec.add(cmd, **args). \ + get_sw_if_index(err_msg) + cmd = u"sw_interface_set_flags" + args = dict( + sw_if_index=loop_sw_if_idx, + flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value + ) + err_msg = f"Failed to set loopback interface state up " \ + f"on host {nodes[u'DUT1'][u'host']}" + papi_exec.add(cmd, **args).get_reply(err_msg) + # Set IP address on VPP node 1 interface + cmd = u"sw_interface_add_del_address" + args = dict( + sw_if_index=InterfaceUtil.get_interface_index( + nodes[u"DUT1"], if1_key + ), + is_add=True, + del_all=False, + prefix=IPUtil.create_prefix_object( + tun_ips[u"ip2"] - 1, 96 if tun_ips[u"ip2"].version == 6 + else 24 + ) + ) + err_msg = f"Failed to set IP address on interface {if1_key} " \ + f"on host {nodes[u'DUT1'][u'host']}" + papi_exec.add(cmd, **args).get_reply(err_msg) + cmd2 = u"ip_neighbor_add_del" + args2 = dict( + is_add=1, + neighbor=dict( + sw_if_index=Topology.get_interface_sw_index( nodes[u"DUT1"], if1_key ), - is_add=True, - del_all=False, - prefix=IPUtil.create_prefix_object( - if2_ip - 1, 96 if if2_ip.version == 6 else 24 - ) - ) - err_msg = f"Failed to set IP address on interface {if1_key} " \ - f"on host {nodes[u'DUT1'][u'host']}" - papi_exec.add(cmd1, **args1).get_reply(err_msg) - cmd4 = u"ip_neighbor_add_del" - args4 = dict( - is_add=1, - neighbor=dict( - sw_if_index=Topology.get_interface_sw_index( - nodes[u"DUT1"], if1_key - ), - flags=1, - mac_address=str( - Topology.get_interface_mac(nodes[u"DUT2"], if2_key) - ), - ip_address=str(ip_address(if2_ip_addr)) - ) + flags=1, + mac_address=str( + Topology.get_interface_mac(nodes[u"DUT2"], if2_key) + if u"DUT2" in nodes.keys() + else Topology.get_interface_mac( + nodes[u"TG"], if2_key + ) + ), + ip_address=tun_ips[u"ip2"].compressed ) - err_msg = f"Failed to add IP neighbor on interface {if1_key}" - papi_exec.add(cmd4, **args4).get_reply(err_msg) + ) + err_msg = f"Failed to add IP neighbor on interface {if1_key}" + papi_exec.add(cmd2, **args2).get_reply(err_msg) + + return loop_sw_if_idx + + @staticmethod + def _ipsec_create_tunnel_interfaces_dut1_papi( + nodes, tun_ips, if1_key, if2_key, n_tunnels, crypto_alg, integ_alg, + raddr_ip2, addr_incr, spi_d, existing_tunnels=0): + """Create multiple IPsec tunnel interfaces on DUT1 node using PAPI. + + Generate random keys and return them (so DUT2 or TG can decrypt). + + :param nodes: VPP nodes to create tunnel interfaces. + :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface + IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface + IPv4/IPv6 address (ip2). + :param if1_key: VPP node 1 interface key from topology file. + :param if2_key: VPP node 2 / TG node (in case of 2-node topology) + interface key from topology file. + :param n_tunnels: Number of tunnel interfaces to be there at the end. + :param crypto_alg: The encryption algorithm name. + :param integ_alg: The integrity algorithm name. + :param raddr_ip2: Policy selector remote IPv4/IPv6 start address for the + first tunnel in direction node2->node1. + :param spi_d: Dictionary with SPIs for VPP node 1 and VPP node 2. + :param addr_incr: IP / IPv6 address incremental step. + :param existing_tunnels: Number of tunnel interfaces before creation. + Useful mainly for reconf tests. Default 0. + :type nodes: dict + :type tun_ips: dict + :type if1_key: str + :type if2_key: str + :type n_tunnels: int + :type crypto_alg: CryptoAlg + :type integ_alg: Optional[IntegAlg] + :type raddr_ip2: IPv4Address or IPv6Address + :type addr_incr: int + :type spi_d: dict + :type existing_tunnels: int + :returns: Generated ckeys and ikeys. + :rtype: List[bytes], List[bytes] + """ + if not existing_tunnels: + loop_sw_if_idx = IPsecUtil._ipsec_create_loopback_dut1_papi( + nodes, tun_ips, if1_key, if2_key + ) else: - # Executor not open, as InterfaceUtil will open its own. loop_sw_if_idx = InterfaceUtil.vpp_get_interface_sw_index( - nodes[u"DUT1"], u"loop0") - cmd1 = u"sw_interface_add_del_address" + nodes[u"DUT1"], u"loop0" + ) with PapiSocketExecutor(nodes[u"DUT1"]) as papi_exec: - # Configure IPsec tunnel interfaces - args1 = dict( + # Configure IP addresses on loop0 interface + cmd = u"sw_interface_add_del_address" + args = dict( sw_if_index=loop_sw_if_idx, is_add=True, del_all=False, prefix=None ) - cmd2 = u"ipsec_tunnel_if_add_del" - args2 = dict( - is_add=1, - local_ip=None, - remote_ip=None, - local_spi=0, - remote_spi=0, - crypto_alg=crypto_alg.alg_int_repr, - local_crypto_key_len=0, - local_crypto_key=None, - remote_crypto_key_len=0, - remote_crypto_key=None, - integ_alg=integ_alg.alg_int_repr if integ_alg else 0, - local_integ_key_len=0, - local_integ_key=None, - remote_integ_key_len=0, - remote_integ_key=None, - tx_table_id=0 + for i in range(existing_tunnels, n_tunnels): + args[u"prefix"] = IPUtil.create_prefix_object( + tun_ips[u"ip1"] + i * addr_incr, + 128 if tun_ips[u"ip1"].version == 6 else 32 + ) + papi_exec.add( + cmd, history=bool(not 1 < i < n_tunnels - 2), **args + ) + # Configure IPIP tunnel interfaces + cmd = u"ipip_add_tunnel" + ipip_tunnel = dict( + instance=Constants.BITWISE_NON_ZERO, + src=None, + dst=None, + table_id=0, + flags=int( + TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE + ), + mode=int(TunnelMode.TUNNEL_API_MODE_P2P), + dscp=int(IpDscp.IP_API_DSCP_CS0) + ) + args = dict( + tunnel=ipip_tunnel + ) + ipip_tunnels = [None] * existing_tunnels + for i in range(existing_tunnels, n_tunnels): + args[u"tunnel"][u"src"] = IPAddress.create_ip_address_object( + tun_ips[u"ip1"] + i * addr_incr + ) + args[u"tunnel"][u"dst"] = IPAddress.create_ip_address_object( + tun_ips[u"ip2"] + ) + papi_exec.add( + cmd, history=bool(not 1 < i < n_tunnels - 2), **args + ) + err_msg = f"Failed to add IPIP tunnel interfaces on host" \ + f" {nodes[u'DUT1'][u'host']}" + ipip_tunnels.extend( + [ + reply[u"sw_if_index"] + for reply in papi_exec.get_replies(err_msg) + if u"sw_if_index" in reply + ] + ) + # Configure IPSec SAD entries + ckeys = [bytes()] * existing_tunnels + ikeys = [bytes()] * existing_tunnels + cmd = u"ipsec_sad_entry_add_del_v2" + c_key = dict( + length=0, + data=None + ) + i_key = dict( + length=0, + data=None + ) + sad_entry = dict( + sad_id=None, + spi=None, + protocol=int(IPsecProto.IPSEC_API_PROTO_ESP), + crypto_algorithm=crypto_alg.alg_int_repr, + crypto_key=c_key, + integrity_algorithm=integ_alg.alg_int_repr if integ_alg else 0, + integrity_key=i_key, + flags=None, + tunnel_src=0, + tunnel_dst=0, + tunnel_flags=int( + TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE + ), + dscp=int(IpDscp.IP_API_DSCP_CS0), + table_id=0, + salt=0, + udp_src_port=IPSEC_UDP_PORT_NONE, + udp_dst_port=IPSEC_UDP_PORT_NONE + ) + args = dict( + is_add=True, + entry=sad_entry ) - err_msg = f"Failed to add IPsec tunnel interfaces " \ - f"on host {nodes[u'DUT1'][u'host']}" - ipsec_tunnels = [None] * existing_tunnels - ckeys = [None] * existing_tunnels - ikeys = [None] * existing_tunnels for i in range(existing_tunnels, n_tunnels): ckeys.append( gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg)) ) + ikeys.append( + gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)) + ) + # SAD entry for outband / tx path + args[u"entry"][u"sad_id"] = i + args[u"entry"][u"spi"] = spi_d[u"spi_1"] + i + + args[u"entry"][u"crypto_key"][u"length"] = len(ckeys[i]) + args[u"entry"][u"crypto_key"][u"data"] = ckeys[i] if integ_alg: - ikeys.append( - gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)) - ) - args1[u"prefix"] = IPUtil.create_prefix_object( - if1_ip + i * addr_incr, 128 if if1_ip.version == 6 else 32 + args[u"entry"][u"integrity_key"][u"length"] = len(ikeys[i]) + args[u"entry"][u"integrity_key"][u"data"] = ikeys[i] + args[u"entry"][u"flags"] = int( + IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE ) - args2[u"local_spi"] = spi_1 + i - args2[u"remote_spi"] = spi_2 + i - args2[u"local_ip"] = IPUtil.create_ip_address_object( - if1_ip + i * addr_incr + papi_exec.add( + cmd, history=bool(not 1 < i < n_tunnels - 2), **args ) - args2[u"remote_ip"] = IPUtil.create_ip_address_object(if2_ip) - args2[u"local_crypto_key_len"] = len(ckeys[i]) - args2[u"local_crypto_key"] = ckeys[i] - args2[u"remote_crypto_key_len"] = len(ckeys[i]) - args2[u"remote_crypto_key"] = ckeys[i] + # SAD entry for inband / rx path + args[u"entry"][u"sad_id"] = 100000 + i + args[u"entry"][u"spi"] = spi_d[u"spi_2"] + i + + args[u"entry"][u"crypto_key"][u"length"] = len(ckeys[i]) + args[u"entry"][u"crypto_key"][u"data"] = ckeys[i] if integ_alg: - args2[u"local_integ_key_len"] = len(ikeys[i]) - args2[u"local_integ_key"] = ikeys[i] - args2[u"remote_integ_key_len"] = len(ikeys[i]) - args2[u"remote_integ_key"] = ikeys[i] - history = bool(not 1 < i < n_tunnels - 2) - papi_exec.add(cmd1, history=history, **args1).\ - add(cmd2, history=history, **args2) - replies = papi_exec.get_replies(err_msg) - for reply in replies: - if u"sw_if_index" in reply: - ipsec_tunnels.append(reply[u"sw_if_index"]) - # Configure IP routes - cmd1 = u"sw_interface_set_unnumbered" - args1 = dict( + args[u"entry"][u"integrity_key"][u"length"] = len(ikeys[i]) + args[u"entry"][u"integrity_key"][u"data"] = ikeys[i] + args[u"entry"][u"flags"] = int( + IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE | + IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_INBOUND + ) + papi_exec.add( + cmd, history=bool(not 1 < i < n_tunnels - 2), **args + ) + err_msg = f"Failed to add IPsec SAD entries on host" \ + f" {nodes[u'DUT1'][u'host']}" + papi_exec.get_replies(err_msg) + # Add protection for tunnels with IPSEC + cmd = u"ipsec_tunnel_protect_update" + n_hop = dict( + address=0, + via_label=MPLS_LABEL_INVALID, + obj_id=Constants.BITWISE_NON_ZERO + ) + ipsec_tunnel_protect = dict( + sw_if_index=None, + nh=n_hop, + sa_out=None, + n_sa_in=1, + sa_in=None + ) + args = dict( + tunnel=ipsec_tunnel_protect + ) + for i in range(existing_tunnels, n_tunnels): + args[u"tunnel"][u"sw_if_index"] = ipip_tunnels[i] + args[u"tunnel"][u"sa_out"] = i + args[u"tunnel"][u"sa_in"] = [100000 + i] + papi_exec.add( + cmd, history=bool(not 1 < i < n_tunnels - 2), **args + ) + err_msg = f"Failed to add protection for tunnels with IPSEC " \ + f"on host {nodes[u'DUT1'][u'host']}" + papi_exec.get_replies(err_msg) + + # Configure unnumbered interfaces + cmd = u"sw_interface_set_unnumbered" + args = dict( is_add=True, sw_if_index=InterfaceUtil.get_interface_index( nodes[u"DUT1"], if1_key ), unnumbered_sw_if_index=0 ) - cmd2 = u"sw_interface_set_flags" - args2 = dict( + for i in range(existing_tunnels, n_tunnels): + args[u"unnumbered_sw_if_index"] = ipip_tunnels[i] + papi_exec.add( + cmd, history=bool(not 1 < i < n_tunnels - 2), **args + ) + # Set interfaces up + cmd = u"sw_interface_set_flags" + args = dict( sw_if_index=0, flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value ) - cmd3 = u"ip_route_add_del" - args3 = dict( + for i in range(existing_tunnels, n_tunnels): + args[u"sw_if_index"] = ipip_tunnels[i] + papi_exec.add( + cmd, history=bool(not 1 < i < n_tunnels - 2), **args + ) + # Configure IP routes + cmd = u"ip_route_add_del" + args = dict( is_add=1, is_multipath=0, route=None ) - err_msg = f"Failed to add IP routes " \ - f"on host {nodes[u'DUT1'][u'host']}" for i in range(existing_tunnels, n_tunnels): - args1[u"unnumbered_sw_if_index"] = ipsec_tunnels[i] - args2[u"sw_if_index"] = ipsec_tunnels[i] - args3[u"route"] = IPUtil.compose_vpp_route_structure( + args[u"route"] = IPUtil.compose_vpp_route_structure( nodes[u"DUT1"], (raddr_ip2 + i).compressed, prefix_len=128 if raddr_ip2.version == 6 else 32, - interface=ipsec_tunnels[i] + interface=ipip_tunnels[i] ) - history = bool(not 1 < i < n_tunnels - 2) - papi_exec.add(cmd1, history=history, **args1).\ - add(cmd2, history=history, **args2).\ - add(cmd3, history=history, **args3) + papi_exec.add( + cmd, history=bool(not 1 < i < n_tunnels - 2), **args + ) + err_msg = f"Failed to add IP routes on host " \ + f"{nodes[u'DUT1'][u'host']}" papi_exec.get_replies(err_msg) + return ckeys, ikeys + + @staticmethod + def _ipsec_create_tunnel_interfaces_dut2_papi( + nodes, tun_ips, if2_key, n_tunnels, crypto_alg, ckeys, integ_alg, + ikeys, raddr_ip1, addr_incr, spi_d, existing_tunnels=0): + """Create multiple IPsec tunnel interfaces on DUT2 node using PAPI. + + This method accesses keys generated by DUT1 method + and does not return anything. + + :param nodes: VPP nodes to create tunnel interfaces. + :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface + IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface + IPv4/IPv6 address (ip2). + :param if2_key: VPP node 2 / TG node (in case of 2-node topology) + interface key from topology file. + :param n_tunnels: Number of tunnel interfaces to be there at the end. + :param crypto_alg: The encryption algorithm name. + :param ckeys: List of encryption keys. + :param integ_alg: The integrity algorithm name. + :param ikeys: List of integrity keys. + :param spi_d: Dictionary with SPIs for VPP node 1 and VPP node 2. + :param addr_incr: IP / IPv6 address incremental step. + :param existing_tunnels: Number of tunnel interfaces before creation. + Useful mainly for reconf tests. Default 0. + :type nodes: dict + :type tun_ips: dict + :type if2_key: str + :type n_tunnels: int + :type crypto_alg: CryptoAlg + :type ckeys: Sequence[bytes] + :type integ_alg: Optional[IntegAlg] + :type ikeys: Sequence[bytes] + :type addr_incr: int + :type spi_d: dict + :type existing_tunnels: int + """ with PapiSocketExecutor(nodes[u"DUT2"]) as papi_exec: if not existing_tunnels: # Set IP address on VPP node 2 interface - cmd1 = u"sw_interface_add_del_address" - args1 = dict( + cmd = u"sw_interface_add_del_address" + args = dict( sw_if_index=InterfaceUtil.get_interface_index( nodes[u"DUT2"], if2_key ), is_add=True, del_all=False, prefix=IPUtil.create_prefix_object( - if2_ip, 96 if if2_ip.version == 6 else 24 + tun_ips[u"ip2"], 96 if tun_ips[u"ip2"].version == 6 + else 24 ) ) err_msg = f"Failed to set IP address on interface {if2_key} " \ f"on host {nodes[u'DUT2'][u'host']}" - papi_exec.add(cmd1, **args1).get_reply(err_msg) - # Configure IPsec tunnel interfaces - cmd2 = u"ipsec_tunnel_if_add_del" - args2 = dict( - is_add=1, - local_ip=IPUtil.create_ip_address_object(if2_ip), - remote_ip=None, - local_spi=0, - remote_spi=0, - crypto_alg=crypto_alg.alg_int_repr, - local_crypto_key_len=0, - local_crypto_key=None, - remote_crypto_key_len=0, - remote_crypto_key=None, - integ_alg=integ_alg.alg_int_repr if integ_alg else 0, - local_integ_key_len=0, - local_integ_key=None, - remote_integ_key_len=0, - remote_integ_key=None, - tx_table_id=0 + papi_exec.add(cmd, **args).get_reply(err_msg) + # Configure IPIP tunnel interfaces + cmd = u"ipip_add_tunnel" + ipip_tunnel = dict( + instance=Constants.BITWISE_NON_ZERO, + src=None, + dst=None, + table_id=0, + flags=int( + TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE + ), + mode=int(TunnelMode.TUNNEL_API_MODE_P2P), + dscp=int(IpDscp.IP_API_DSCP_CS0) + ) + args = dict( + tunnel=ipip_tunnel + ) + ipip_tunnels = [None] * existing_tunnels + for i in range(existing_tunnels, n_tunnels): + args[u"tunnel"][u"src"] = IPAddress.create_ip_address_object( + tun_ips[u"ip2"] + ) + args[u"tunnel"][u"dst"] = IPAddress.create_ip_address_object( + tun_ips[u"ip1"] + i * addr_incr + ) + papi_exec.add( + cmd, history=bool(not 1 < i < n_tunnels - 2), **args + ) + err_msg = f"Failed to add IPIP tunnel interfaces on host" \ + f" {nodes[u'DUT2'][u'host']}" + ipip_tunnels.extend( + [ + reply[u"sw_if_index"] + for reply in papi_exec.get_replies(err_msg) + if u"sw_if_index" in reply + ] + ) + # Configure IPSec SAD entries + cmd = u"ipsec_sad_entry_add_del_v2" + c_key = dict( + length=0, + data=None + ) + i_key = dict( + length=0, + data=None + ) + sad_entry = dict( + sad_id=None, + spi=None, + protocol=int(IPsecProto.IPSEC_API_PROTO_ESP), + + crypto_algorithm=crypto_alg.alg_int_repr, + crypto_key=c_key, + integrity_algorithm=integ_alg.alg_int_repr if integ_alg else 0, + integrity_key=i_key, + + flags=None, + tunnel_src=0, + tunnel_dst=0, + tunnel_flags=int( + TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE + ), + dscp=int(IpDscp.IP_API_DSCP_CS0), + table_id=0, + salt=0, + udp_src_port=IPSEC_UDP_PORT_NONE, + udp_dst_port=IPSEC_UDP_PORT_NONE + ) + args = dict( + is_add=True, + entry=sad_entry ) - err_msg = f"Failed to add IPsec tunnel interfaces " \ - f"on host {nodes[u'DUT2'][u'host']}" - ipsec_tunnels = [None] * existing_tunnels for i in range(existing_tunnels, n_tunnels): - args2[u"local_spi"] = spi_2 + i - args2[u"remote_spi"] = spi_1 + i - args2[u"local_ip"] = IPUtil.create_ip_address_object(if2_ip) - args2[u"remote_ip"] = IPUtil.create_ip_address_object( - if1_ip + i * addr_incr) - args2[u"local_crypto_key_len"] = len(ckeys[i]) - args2[u"local_crypto_key"] = ckeys[i] - args2[u"remote_crypto_key_len"] = len(ckeys[i]) - args2[u"remote_crypto_key"] = ckeys[i] + ckeys.append( + gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg)) + ) + ikeys.append( + gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)) + ) + # SAD entry for outband / tx path + args[u"entry"][u"sad_id"] = 100000 + i + args[u"entry"][u"spi"] = spi_d[u"spi_2"] + i + + args[u"entry"][u"crypto_key"][u"length"] = len(ckeys[i]) + args[u"entry"][u"crypto_key"][u"data"] = ckeys[i] if integ_alg: - args2[u"local_integ_key_len"] = len(ikeys[i]) - args2[u"local_integ_key"] = ikeys[i] - args2[u"remote_integ_key_len"] = len(ikeys[i]) - args2[u"remote_integ_key"] = ikeys[i] - history = bool(not 1 < i < n_tunnels - 2) - papi_exec.add(cmd2, history=history, **args2) - replies = papi_exec.get_replies(err_msg) - for reply in replies: - if u"sw_if_index" in reply: - ipsec_tunnels.append(reply[u"sw_if_index"]) + args[u"entry"][u"integrity_key"][u"length"] = len(ikeys[i]) + args[u"entry"][u"integrity_key"][u"data"] = ikeys[i] + args[u"entry"][u"flags"] = int( + IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE + ) + papi_exec.add( + cmd, history=bool(not 1 < i < n_tunnels - 2), **args + ) + # SAD entry for inband / rx path + args[u"entry"][u"sad_id"] = i + args[u"entry"][u"spi"] = spi_d[u"spi_1"] + i + + args[u"entry"][u"crypto_key"][u"length"] = len(ckeys[i]) + args[u"entry"][u"crypto_key"][u"data"] = ckeys[i] + if integ_alg: + args[u"entry"][u"integrity_key"][u"length"] = len(ikeys[i]) + args[u"entry"][u"integrity_key"][u"data"] = ikeys[i] + args[u"entry"][u"flags"] = int( + IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE | + IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_INBOUND + ) + papi_exec.add( + cmd, history=bool(not 1 < i < n_tunnels - 2), **args + ) + err_msg = f"Failed to add IPsec SAD entries on host" \ + f" {nodes[u'DUT2'][u'host']}" + papi_exec.get_replies(err_msg) + # Add protection for tunnels with IPSEC + cmd = u"ipsec_tunnel_protect_update" + n_hop = dict( + address=0, + via_label=MPLS_LABEL_INVALID, + obj_id=Constants.BITWISE_NON_ZERO + ) + ipsec_tunnel_protect = dict( + sw_if_index=None, + nh=n_hop, + sa_out=None, + n_sa_in=1, + sa_in=None + ) + args = dict( + tunnel=ipsec_tunnel_protect + ) + for i in range(existing_tunnels, n_tunnels): + args[u"tunnel"][u"sw_if_index"] = ipip_tunnels[i] + args[u"tunnel"][u"sa_out"] = 100000 + i + args[u"tunnel"][u"sa_in"] = [i] + papi_exec.add( + cmd, history=bool(not 1 < i < n_tunnels - 2), **args + ) + err_msg = f"Failed to add protection for tunnels with IPSEC " \ + f"on host {nodes[u'DUT2'][u'host']}" + papi_exec.get_replies(err_msg) + if not existing_tunnels: - # Configure IP routes - cmd1 = u"ip_route_add_del" + # Configure IP route + cmd = u"ip_route_add_del" route = IPUtil.compose_vpp_route_structure( - nodes[u"DUT2"], if1_ip.compressed, - prefix_len=32 if if1_ip.version == 6 else 8, + nodes[u"DUT2"], tun_ips[u"ip1"].compressed, + prefix_len=32 if tun_ips[u"ip1"].version == 6 else 8, interface=if2_key, - gateway=(if2_ip - 1).compressed + gateway=(tun_ips[u"ip2"] - 1).compressed ) - args1 = dict( + args = dict( is_add=1, is_multipath=0, route=route ) - papi_exec.add(cmd1, **args1) - cmd1 = u"sw_interface_set_unnumbered" - args1 = dict( + papi_exec.add(cmd, **args) + # Configure unnumbered interfaces + cmd = u"sw_interface_set_unnumbered" + args = dict( is_add=True, sw_if_index=InterfaceUtil.get_interface_index( nodes[u"DUT2"], if2_key ), unnumbered_sw_if_index=0 ) - cmd2 = u"sw_interface_set_flags" - args2 = dict( + for i in range(existing_tunnels, n_tunnels): + args[u"unnumbered_sw_if_index"] = ipip_tunnels[i] + papi_exec.add( + cmd, history=bool(not 1 < i < n_tunnels - 2), **args + ) + # Set interfaces up + cmd = u"sw_interface_set_flags" + args = dict( sw_if_index=0, flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value ) - cmd3 = u"ip_route_add_del" - args3 = dict( + for i in range(existing_tunnels, n_tunnels): + args[u"sw_if_index"] = ipip_tunnels[i] + papi_exec.add( + cmd, history=bool(not 1 < i < n_tunnels - 2), **args + ) + # Configure IP routes + cmd = u"ip_route_add_del" + args = dict( is_add=1, is_multipath=0, route=None ) - err_msg = f"Failed to add IP routes " \ - f"on host {nodes[u'DUT2'][u'host']}" for i in range(existing_tunnels, n_tunnels): - args1[u"unnumbered_sw_if_index"] = ipsec_tunnels[i] - args2[u"sw_if_index"] = ipsec_tunnels[i] - args3[u"route"] = IPUtil.compose_vpp_route_structure( + args[u"route"] = IPUtil.compose_vpp_route_structure( nodes[u"DUT1"], (raddr_ip1 + i).compressed, prefix_len=128 if raddr_ip1.version == 6 else 32, - interface=ipsec_tunnels[i] + interface=ipip_tunnels[i] ) - history = bool(not 1 < i < n_tunnels - 2) - papi_exec.add(cmd1, history=history, **args1). \ - add(cmd2, history=history, **args2). \ - add(cmd3, history=history, **args3) + papi_exec.add( + cmd, history=bool(not 1 < i < n_tunnels - 2), **args + ) + err_msg = f"Failed to add IP routes " \ + f"on host {nodes[u'DUT2'][u'host']}" papi_exec.get_replies(err_msg) + @staticmethod + def vpp_ipsec_create_tunnel_interfaces( + nodes, tun_if1_ip_addr, tun_if2_ip_addr, if1_key, if2_key, + n_tunnels, crypto_alg, integ_alg, raddr_ip1, raddr_ip2, raddr_range, + existing_tunnels=0, return_keys=False): + """Create multiple IPsec tunnel interfaces between two VPP nodes. + + Some deployments (e.g. devicetest) need to know the generated keys. + But other deployments (e.g. scale perf test) would get spammed + if we returned keys every time. + + :param nodes: VPP nodes to create tunnel interfaces. + :param tun_if1_ip_addr: VPP node 1 ipsec tunnel interface IPv4/IPv6 + address. + :param tun_if2_ip_addr: VPP node 2 ipsec tunnel interface IPv4/IPv6 + address. + :param if1_key: VPP node 1 interface key from topology file. + :param if2_key: VPP node 2 / TG node (in case of 2-node topology) + interface key from topology file. + :param n_tunnels: Number of tunnel interfaces to be there at the end. + :param crypto_alg: The encryption algorithm name. + :param integ_alg: The integrity algorithm name. + :param raddr_ip1: Policy selector remote IPv4/IPv6 start address for the + first tunnel in direction node1->node2. + :param raddr_ip2: Policy selector remote IPv4/IPv6 start address for the + first tunnel in direction node2->node1. + :param raddr_range: Mask specifying range of Policy selector Remote + IPv4/IPv6 addresses. Valid values are from 1 to 32 in case of IPv4 + and to 128 in case of IPv6. + :param existing_tunnels: Number of tunnel interfaces before creation. + Useful mainly for reconf tests. Default 0. + :param return_keys: Whether generated keys should be returned. + :type nodes: dict + :type tun_if1_ip_addr: str + :type tun_if2_ip_addr: str + :type if1_key: str + :type if2_key: str + :type n_tunnels: int + :type crypto_alg: CryptoAlg + :type integ_alg: Optonal[IntegAlg] + :type raddr_ip1: string + :type raddr_ip2: string + :type raddr_range: int + :type existing_tunnels: int + :type return_keys: bool + :returns: Ckeys, ikeys, spi_1, spi_2. + :rtype: Optional[List[bytes], List[bytes], int, int] + """ + n_tunnels = int(n_tunnels) + existing_tunnels = int(existing_tunnels) + spi_d = dict( + spi_1=100000, + spi_2=200000 + ) + tun_ips = dict( + ip1=ip_address(tun_if1_ip_addr), + ip2=ip_address(tun_if2_ip_addr) + ) + raddr_ip1 = ip_address(raddr_ip1) + raddr_ip2 = ip_address(raddr_ip2) + addr_incr = 1 << (128 - raddr_range) if tun_ips[u"ip1"].version == 6 \ + else 1 << (32 - raddr_range) + + if n_tunnels - existing_tunnels > 10: + ckeys, ikeys = IPsecUtil._ipsec_create_tunnel_interfaces_dut1_vat( + nodes, tun_ips, if1_key, if2_key, n_tunnels, crypto_alg, + integ_alg, raddr_ip2, addr_incr, spi_d, existing_tunnels + ) + if u"DUT2" in nodes.keys(): + IPsecUtil._ipsec_create_tunnel_interfaces_dut2_vat( + nodes, tun_ips, if2_key, n_tunnels, crypto_alg, ckeys, + integ_alg, ikeys, raddr_ip1, addr_incr, spi_d, + existing_tunnels + ) + else: + ckeys, ikeys = IPsecUtil._ipsec_create_tunnel_interfaces_dut1_papi( + nodes, tun_ips, if1_key, if2_key, n_tunnels, crypto_alg, + integ_alg, raddr_ip2, addr_incr, spi_d, existing_tunnels + ) + if u"DUT2" in nodes.keys(): + IPsecUtil._ipsec_create_tunnel_interfaces_dut2_papi( + nodes, tun_ips, if2_key, n_tunnels, crypto_alg, ckeys, + integ_alg, ikeys, raddr_ip1, addr_incr, spi_d, + existing_tunnels + ) + + if return_keys: + return ckeys, ikeys, spi_d[u"spi_1"], spi_d[u"spi_2"] + return None + @staticmethod def _create_ipsec_script_files(dut, instances): """Create script files for configuring IPsec in containers @@ -1209,16 +1780,13 @@ class IPsecUtil: @staticmethod def vpp_ipsec_create_tunnel_interfaces_in_containers( - nodes, if1_ip_addr, if2_ip_addr, if1_key, if2_key, n_tunnels, - crypto_alg, integ_alg, raddr_ip1, raddr_ip2, raddr_range, - n_instances): + nodes, if1_ip_addr, if2_ip_addr, n_tunnels, crypto_alg, integ_alg, + raddr_ip1, raddr_ip2, raddr_range, n_instances): """Create multiple IPsec tunnel interfaces between two VPP nodes. :param nodes: VPP nodes to create tunnel interfaces. :param if1_ip_addr: VPP node 1 interface IP4 address. :param if2_ip_addr: VPP node 2 interface IP4 address. - :param if1_key: VPP node 1 interface key from topology file. - :param if2_key: VPP node 2 interface key from topology file. :param n_tunnels: Number of tunnell interfaces to create. :param crypto_alg: The encryption algorithm name. :param integ_alg: The integrity algorithm name. @@ -1232,11 +1800,9 @@ class IPsecUtil: :type nodes: dict :type if1_ip_addr: str :type if2_ip_addr: str - :type if1_key: str - :type if2_key: str :type n_tunnels: int :type crypto_alg: CryptoAlg - :type integ_alg: IntegAlg + :type integ_alg: Optional[IntegAlg] :type raddr_ip1: string :type raddr_ip2: string :type raddr_range: int @@ -1247,9 +1813,11 @@ class IPsecUtil: addr_incr = 1 << (32 - raddr_range) dut1_scripts = IPsecUtil._create_ipsec_script_files( - u"DUT1", n_instances) + u"DUT1", n_instances + ) dut2_scripts = IPsecUtil._create_ipsec_script_files( - u"DUT2", n_instances) + u"DUT2", n_instances + ) for cnf in range(0, n_instances): dut1_scripts[cnf].write( @@ -1262,57 +1830,60 @@ class IPsecUtil: ) for tnl in range(0, n_tunnels): - tnl_incr = tnl * addr_incr cnf = tnl % n_instances - i = tnl // n_instances - ckey = gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg)).hex() + ckey = getattr( + gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg)), u"hex" + ) integ = u"" + ikey = getattr( + gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)), u"hex" + ) if integ_alg: - ikey = gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)).hex() integ = ( f"integ-alg {integ_alg.alg_name} " f"local-integ-key {ikey} " f"remote-integ-key {ikey} " ) - # Configure tunnel end point(s) on left side dut1_scripts[cnf].write( u"set interface ip address loop0 " - f"{ip_address(if1_ip_addr) + tnl_incr}/32\n" + f"{ip_address(if1_ip_addr) + tnl * addr_incr}/32\n" f"create ipsec tunnel " - f"local-ip {ip_address(if1_ip_addr) + tnl_incr} " + f"local-ip {ip_address(if1_ip_addr) + tnl * addr_incr} " f"local-spi {spi_1 + tnl} " f"remote-ip {ip_address(if2_ip_addr) + cnf} " f"remote-spi {spi_2 + tnl} " f"crypto-alg {crypto_alg.alg_name} " f"local-crypto-key {ckey} " f"remote-crypto-key {ckey} " - f"instance {i} " + f"instance {tnl // n_instances} " f"salt 0x0 " f"{integ} \n" - f"set interface unnumbered ipip{i} use loop0\n" - f"set interface state ipip{i} up\n" - f"ip route add {ip_address(raddr_ip2)+tnl}/32 via ipip{i}\n\n" + f"set interface unnumbered ipip{tnl // n_instances} use loop0\n" + f"set interface state ipip{tnl // n_instances} up\n" + f"ip route add {ip_address(raddr_ip2)+tnl}/32 " + f"via ipip{tnl // n_instances}\n\n" ) - # Configure tunnel end point(s) on right side dut2_scripts[cnf].write( f"set ip neighbor memif1/{cnf + 1} " - f"{ip_address(if1_ip_addr) + tnl_incr} " + f"{ip_address(if1_ip_addr) + tnl * addr_incr} " f"02:02:00:00:{17:02X}:{cnf:02X} static\n" f"create ipsec tunnel local-ip {ip_address(if2_ip_addr) + cnf} " f"local-spi {spi_2 + tnl} " - f"remote-ip {ip_address(if1_ip_addr) + tnl_incr} " + f"remote-ip {ip_address(if1_ip_addr) + tnl * addr_incr} " f"remote-spi {spi_1 + tnl} " f"crypto-alg {crypto_alg.alg_name} " f"local-crypto-key {ckey} " f"remote-crypto-key {ckey} " - f"instance {i} " + f"instance {tnl // n_instances} " f"salt 0x0 " f"{integ}\n" - f"set interface unnumbered ipip{i} use memif1/{cnf + 1}\n" - f"set interface state ipip{i} up\n" - f"ip route add {ip_address(raddr_ip1) + tnl}/32 via ipip{i}\n\n" + f"set interface unnumbered ipip{tnl // n_instances} " + f"use memif1/{cnf + 1}\n" + f"set interface state ipip{tnl // n_instances} up\n" + f"ip route add {ip_address(raddr_ip1) + tnl}/32 " + f"via ipip{tnl // n_instances}\n\n" ) IPsecUtil._close_and_copy_ipsec_script_files( @@ -1345,7 +1916,7 @@ class IPsecUtil: :type interface2: str or int :type n_tunnels: int :type crypto_alg: CryptoAlg - :type integ_alg: IntegAlg + :type integ_alg: Optional[IntegAlg] :type tunnel_ip1: str :type tunnel_ip2: str :type raddr_ip1: string @@ -1438,3 +2009,15 @@ class IPsecUtil: :type node: dict """ PapiSocketExecutor.run_cli_cmd(node, u"show ipsec") + + @staticmethod + def show_ipsec_security_association(node): + """Show IPSec security association. + + :param node: DUT node. + :type node: dict + """ + cmds = [ + u"ipsec_sa_v2_dump" + ] + PapiSocketExecutor.dump_and_log(node, cmds)