X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=blobdiff_plain;f=resources%2Flibraries%2Fpython%2FIPsecUtil.py;h=c99105abebe103c492b7e5292f6d105c3c37d166;hp=a22a54a9c84701ae25c9f1de760e86ca8b9195a2;hb=333bab321770cbf99f8219e1dd6073193815b804;hpb=aada4992a8d0de65707df858a274a3f2e024748d diff --git a/resources/libraries/python/IPsecUtil.py b/resources/libraries/python/IPsecUtil.py index a22a54a9c8..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,16 +22,20 @@ from string import ascii_letters from ipaddress import ip_network, ip_address +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 +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. @@ -90,9 +94,43 @@ class IPsecProto(IntEnum): 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: @@ -205,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): @@ -263,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, @@ -285,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 @@ -315,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( @@ -328,7 +411,13 @@ class IPsecUtil: flags=flags, tunnel_src=str(src_addr), tunnel_dst=str(dst_addr), - protocol=int(IPsecProto.IPSEC_API_PROTO_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=True, @@ -363,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 @@ -390,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} " \ @@ -423,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']}" @@ -437,7 +526,13 @@ class IPsecUtil: flags=flags, tunnel_src=str(src_addr), tunnel_dst=str(dst_addr), - protocol=int(IPsecProto.IPSEC_API_PROTO_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=True, @@ -756,6 +851,8 @@ class IPsecUtil: 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 tun_ips: Dictionary with VPP node 1 ipsec tunnel interface IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface @@ -778,11 +875,13 @@ class IPsecUtil: :type if2_key: str :type n_tunnels: int :type crypto_alg: CryptoAlg - :type integ_alg: IntegAlg + :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] """ tmp_fn1 = u"/tmp/ipsec_create_tunnel_dut1.config" if1_n = Topology.get_interface_name(nodes[u"DUT1"], if1_key) @@ -809,28 +908,37 @@ class IPsecUtil: ckeys.append( gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg)) ) + ikeys.append( + gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)) + ) if integ_alg: - ikeys.append( - gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)) - ) - integ = f"integ_alg {integ_alg.alg_name} " \ - f"local_integ_key {ikeys[i].hex()} " \ - f"remote_integ_key {ikeys[i].hex()} " + 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"ipsec_tunnel_if_add_del " - f"local_spi {spi_d[u'spi_1'] + i} " - f"remote_spi {spi_d[u'spi_2'] + i} " - f"crypto_alg {crypto_alg.alg_name} " - f"local_crypto_key {ckeys[i].hex()} " - f"remote_crypto_key {ckeys[i].hex()} " - f"{integ} " - f"local_ip {tun_ips[u'ip1'] + i * addr_incr} " - f"remote_ip {tun_ips[u'ip2']} " - f"instance {i}\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, @@ -863,6 +971,9 @@ class IPsecUtil: 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 @@ -883,9 +994,9 @@ class IPsecUtil: :type if2_key: str :type n_tunnels: int :type crypto_alg: CryptoAlg - :type ckeys: list - :type integ_alg: IntegAlg - :type ikeys: list + :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 @@ -902,22 +1013,31 @@ class IPsecUtil: ) for i in range(existing_tunnels, n_tunnels): if integ_alg: - integ = f"integ_alg {integ_alg.alg_name} " \ - f"local_integ_key {ikeys[i].hex()} " \ - f"remote_integ_key {ikeys[i].hex()} " + integ = f"integ-alg {integ_alg.alg_name} " \ + f"integ-key {ikeys[i].hex()} " else: integ = u"" tmp_f2.write( - f"ipsec_tunnel_if_add_del " - f"local_spi {spi_d[u'spi_2'] + i} " - f"remote_spi {spi_d[u'spi_1'] + i} " - f"crypto_alg {crypto_alg.alg_name} " - f"local_crypto_key {ckeys[i].hex()} " - f"remote_crypto_key {ckeys[i].hex()} " - f"{integ} " - f"local_ip {tun_ips[u'ip2']} " - f"remote_ip {tun_ips[u'ip1'] + i * addr_incr} " - f"instance {i}\n" + 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, @@ -966,9 +1086,11 @@ class IPsecUtil: """ with PapiSocketExecutor(nodes[u"DUT1"]) as papi_exec: # Create loopback interface on DUT1, set it to up state - cmd = u"create_loopback" + cmd = u"create_loopback_instance" args = dict( - mac_address=0 + 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']}" @@ -1027,6 +1149,8 @@ class IPsecUtil: 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 @@ -1049,11 +1173,13 @@ class IPsecUtil: :type if2_key: str :type n_tunnels: int :type crypto_alg: CryptoAlg - :type integ_alg: IntegAlg + :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( @@ -1080,66 +1206,147 @@ class IPsecUtil: papi_exec.add( cmd, history=bool(not 1 < i < n_tunnels - 2), **args ) - # Configure IPsec tunnel interfaces - cmd = u"ipsec_tunnel_if_add_del" + # 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( - is_add=True, - 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 + tunnel=ipip_tunnel ) - ipsec_tunnels = [None] * existing_tunnels - ckeys = [bytes()] * existing_tunnels - ikeys = [bytes()] * existing_tunnels + ipip_tunnels = [None] * existing_tunnels for i in range(existing_tunnels, n_tunnels): - ckeys.append( - gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg)) - ) - if integ_alg: - ikeys.append( - gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)) - ) - args[u"local_spi"] = spi_d[u"spi_1"] + i - args[u"remote_spi"] = spi_d[u"spi_2"] + i - args[u"local_ip"] = IPAddress.create_ip_address_object( + args[u"tunnel"][u"src"] = IPAddress.create_ip_address_object( tun_ips[u"ip1"] + i * addr_incr ) - args[u"remote_ip"] = IPAddress.create_ip_address_object( + args[u"tunnel"][u"dst"] = IPAddress.create_ip_address_object( tun_ips[u"ip2"] ) - args[u"local_crypto_key_len"] = len(ckeys[i]) - args[u"local_crypto_key"] = ckeys[i] - args[u"remote_crypto_key_len"] = len(ckeys[i]) - args[u"remote_crypto_key"] = ckeys[i] - if integ_alg: - args[u"local_integ_key_len"] = len(ikeys[i]) - args[u"local_integ_key"] = ikeys[i] - args[u"remote_integ_key_len"] = len(ikeys[i]) - args[u"remote_integ_key"] = ikeys[i] papi_exec.add( cmd, history=bool(not 1 < i < n_tunnels - 2), **args ) - err_msg = f"Failed to add IPsec tunnel interfaces on host" \ + err_msg = f"Failed to add IPIP tunnel interfaces on host" \ f" {nodes[u'DUT1'][u'host']}" - ipsec_tunnels.extend( + 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 + ) + 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: + 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"] = 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: + 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( @@ -1150,7 +1357,7 @@ class IPsecUtil: unnumbered_sw_if_index=0 ) for i in range(existing_tunnels, n_tunnels): - args[u"unnumbered_sw_if_index"] = ipsec_tunnels[i] + args[u"unnumbered_sw_if_index"] = ipip_tunnels[i] papi_exec.add( cmd, history=bool(not 1 < i < n_tunnels - 2), **args ) @@ -1161,7 +1368,7 @@ class IPsecUtil: flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value ) for i in range(existing_tunnels, n_tunnels): - args[u"sw_if_index"] = ipsec_tunnels[i] + args[u"sw_if_index"] = ipip_tunnels[i] papi_exec.add( cmd, history=bool(not 1 < i < n_tunnels - 2), **args ) @@ -1176,7 +1383,7 @@ class IPsecUtil: 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] ) papi_exec.add( cmd, history=bool(not 1 < i < n_tunnels - 2), **args @@ -1193,6 +1400,9 @@ class IPsecUtil: 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 @@ -1213,9 +1423,9 @@ class IPsecUtil: :type if2_key: str :type n_tunnels: int :type crypto_alg: CryptoAlg - :type ckeys: list - :type integ_alg: IntegAlg - :type ikeys: list + :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 @@ -1238,57 +1448,147 @@ class IPsecUtil: err_msg = f"Failed to set IP address on interface {if2_key} " \ f"on host {nodes[u'DUT2'][u'host']}" papi_exec.add(cmd, **args).get_reply(err_msg) - # Configure IPsec tunnel interfaces - cmd = u"ipsec_tunnel_if_add_del" + # 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( - is_add=True, - local_ip=IPAddress.create_ip_address_object(tun_ips[u"ip2"]), - 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 + tunnel=ipip_tunnel ) - ipsec_tunnels = [None] * existing_tunnels + ipip_tunnels = [None] * existing_tunnels for i in range(existing_tunnels, n_tunnels): - args[u"local_spi"] = spi_d[u"spi_2"] + i - args[u"remote_spi"] = spi_d[u"spi_1"] + i - args[u"local_ip"] = IPAddress.create_ip_address_object( + args[u"tunnel"][u"src"] = IPAddress.create_ip_address_object( tun_ips[u"ip2"] ) - args[u"remote_ip"] = IPAddress.create_ip_address_object( + args[u"tunnel"][u"dst"] = IPAddress.create_ip_address_object( tun_ips[u"ip1"] + i * addr_incr ) - args[u"local_crypto_key_len"] = len(ckeys[i]) - args[u"local_crypto_key"] = ckeys[i] - args[u"remote_crypto_key_len"] = len(ckeys[i]) - args[u"remote_crypto_key"] = ckeys[i] - if integ_alg: - args[u"local_integ_key_len"] = len(ikeys[i]) - args[u"local_integ_key"] = ikeys[i] - args[u"remote_integ_key_len"] = len(ikeys[i]) - args[u"remote_integ_key"] = ikeys[i] papi_exec.add( cmd, history=bool(not 1 < i < n_tunnels - 2), **args ) - err_msg = f"Failed to add IPsec tunnel interfaces " \ - f"on host {nodes[u'DUT2'][u'host']}" - ipsec_tunnels.extend( + 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 + ) + 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"] = 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: + 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 route cmd = u"ip_route_add_del" @@ -1314,7 +1614,7 @@ class IPsecUtil: unnumbered_sw_if_index=0 ) for i in range(existing_tunnels, n_tunnels): - args[u"unnumbered_sw_if_index"] = ipsec_tunnels[i] + args[u"unnumbered_sw_if_index"] = ipip_tunnels[i] papi_exec.add( cmd, history=bool(not 1 < i < n_tunnels - 2), **args ) @@ -1325,7 +1625,7 @@ class IPsecUtil: flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value ) for i in range(existing_tunnels, n_tunnels): - args[u"sw_if_index"] = ipsec_tunnels[i] + args[u"sw_if_index"] = ipip_tunnels[i] papi_exec.add( cmd, history=bool(not 1 < i < n_tunnels - 2), **args ) @@ -1340,7 +1640,7 @@ class IPsecUtil: 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] ) papi_exec.add( cmd, history=bool(not 1 < i < n_tunnels - 2), **args @@ -1353,9 +1653,13 @@ class IPsecUtil: 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): + 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. @@ -1376,6 +1680,7 @@ class IPsecUtil: 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 @@ -1383,11 +1688,14 @@ class IPsecUtil: :type if2_key: str :type n_tunnels: int :type crypto_alg: CryptoAlg - :type integ_alg: IntegAlg + :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) @@ -1409,25 +1717,27 @@ class IPsecUtil: nodes, tun_ips, if1_key, if2_key, n_tunnels, crypto_alg, integ_alg, raddr_ip2, addr_incr, spi_d, existing_tunnels ) - if u"DUT2" not in nodes.keys(): - return ckeys[0], ikeys[0], spi_d[u"spi_1"], spi_d[u"spi_2"] - 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 - ) + 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" not in nodes.keys(): - return ckeys[0], ikeys[0], spi_d[u"spi_1"], spi_d[u"spi_2"] - 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 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 + ) - return None, None, None, None + 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): @@ -1492,7 +1802,7 @@ class IPsecUtil: :type if2_ip_addr: 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 @@ -1525,10 +1835,10 @@ class IPsecUtil: 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 = getattr( - gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)), u"hex" - ) integ = ( f"integ-alg {integ_alg.alg_name} " f"local-integ-key {ikey} " @@ -1606,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 @@ -1699,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)