1 # Copyright (c) 2024 Cisco and/or its affiliates.
2 # Copyright (c) 2024 PANTHEON.tech s.r.o.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at:
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 """IPsec utilities library."""
17 from enum import Enum, IntEnum
18 from io import open, TextIOWrapper
19 from ipaddress import ip_network, ip_address, IPv4Address, IPv6Address
20 from random import choice
21 from string import ascii_letters
22 from typing import Iterable, List, Optional, Sequence, Tuple, Union
24 from robot.libraries.BuiltIn import BuiltIn
26 from resources.libraries.python.Constants import Constants
27 from resources.libraries.python.enum_util import get_enum_instance
28 from resources.libraries.python.IncrementUtil import ObjIncrement
29 from resources.libraries.python.InterfaceUtil import (
33 from resources.libraries.python.IPAddress import IPAddress
34 from resources.libraries.python.IPUtil import (
40 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
41 from resources.libraries.python.ssh import scp_node
42 from resources.libraries.python.topology import Topology, NodeType
43 from resources.libraries.python.VPPUtil import VPPUtil
44 from resources.libraries.python.FlowUtil import FlowUtil
47 IPSEC_UDP_PORT_DEFAULT = 4500
48 IPSEC_REPLAY_WINDOW_DEFAULT = 64
51 def gen_key(length: int) -> bytes:
52 """Generate random string as a key.
54 :param length: Length of generated payload.
56 :returns: The generated payload.
59 return "".join(choice(ascii_letters) for _ in range(length)).encode(
64 # TODO: Introduce a metaclass that adds .find and .InputType automatically?
65 class IpsecSpdAction(Enum):
68 Mirroring VPP: src/vnet/ipsec/ipsec_types.api enum ipsec_spd_action.
71 BYPASS = NONE = ("bypass", 0)
72 DISCARD = ("discard", 1)
73 RESOLVE = ("resolve", 2)
74 PROTECT = ("protect", 3)
76 def __init__(self, action_name: str, action_int_repr: int):
77 self.action_name = action_name
78 self.action_int_repr = action_int_repr
80 def __str__(self) -> str:
81 return self.action_name
83 def __int__(self) -> int:
84 return self.action_int_repr
87 class CryptoAlg(Enum):
88 """Encryption algorithms."""
90 NONE = ("none", 0, "none", 0)
91 AES_CBC_128 = ("aes-cbc-128", 1, "AES-CBC", 16)
92 AES_CBC_256 = ("aes-cbc-256", 3, "AES-CBC", 32)
93 AES_GCM_128 = ("aes-gcm-128", 7, "AES-GCM", 16)
94 AES_GCM_256 = ("aes-gcm-256", 9, "AES-GCM", 32)
97 self, alg_name: str, alg_int_repr: int, scapy_name: str, key_len: int
99 self.alg_name = alg_name
100 self.alg_int_repr = alg_int_repr
101 self.scapy_name = scapy_name
102 self.key_len = key_len
104 # TODO: Investigate if __int__ works with PAPI. It was not enough for "if".
106 """A shorthand to enable "if crypto_alg:" constructs."""
107 return self.alg_int_repr != 0
110 class IntegAlg(Enum):
111 """Integrity algorithm."""
113 NONE = ("none", 0, "none", 0)
114 SHA_256_128 = ("sha-256-128", 4, "SHA2-256-128", 32)
115 SHA_512_256 = ("sha-512-256", 6, "SHA2-512-256", 64)
118 self, alg_name: str, alg_int_repr: int, scapy_name: str, key_len: int
120 self.alg_name = alg_name
121 self.alg_int_repr = alg_int_repr
122 self.scapy_name = scapy_name
123 self.key_len = key_len
126 """A shorthand to enable "if integ_alg:" constructs."""
127 return self.alg_int_repr != 0
130 # TODO: Base on Enum, so str values can be defined as in alg enums?
131 class IPsecProto(IntEnum):
134 Mirroring VPP: src/vnet/ipsec/ipsec_types.api enum ipsec_proto.
141 def __str__(self) -> str:
142 """Return string suitable for CLI commands.
144 None is not supported.
146 :returns: Lowercase name of the proto.
148 :raises: ValueError if the numeric value is not recognized.
155 raise ValueError(f"String form not defined for IPsecProto {num}")
158 # The rest of enums do not appear outside this file, so no no change needed yet.
159 class IPsecSadFlags(IntEnum):
160 """IPsec Security Association Database flags."""
162 IPSEC_API_SAD_FLAG_NONE = NONE = 0
163 # Enable extended sequence numbers
164 IPSEC_API_SAD_FLAG_USE_ESN = 0x01
165 # Enable Anti - replay
166 IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY = 0x02
167 # IPsec tunnel mode if non-zero, else transport mode
168 IPSEC_API_SAD_FLAG_IS_TUNNEL = 0x04
169 # IPsec tunnel mode is IPv6 if non-zero, else IPv4 tunnel
170 # only valid if is_tunnel is non-zero
171 IPSEC_API_SAD_FLAG_IS_TUNNEL_V6 = 0x08
172 # Enable UDP encapsulation for NAT traversal
173 IPSEC_API_SAD_FLAG_UDP_ENCAP = 0x10
174 # IPsec SA is or inbound traffic
175 IPSEC_API_SAD_FLAG_IS_INBOUND = 0x40
178 class TunnelEncpaDecapFlags(IntEnum):
179 """Flags controlling tunnel behaviour."""
181 TUNNEL_API_ENCAP_DECAP_FLAG_NONE = NONE = 0
182 # at encap, copy the DF bit of the payload into the tunnel header
183 TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_DF = 1
184 # at encap, set the DF bit in the tunnel header
185 TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_SET_DF = 2
186 # at encap, copy the DSCP bits of the payload into the tunnel header
187 TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_DSCP = 4
188 # at encap, copy the ECN bit of the payload into the tunnel header
189 TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_ECN = 8
190 # at decap, copy the ECN bit of the tunnel header into the payload
191 TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_SET_ECN = 16
194 class TunnelMode(IntEnum):
198 TUNNEL_API_MODE_P2P = NONE = 0
200 TUNNEL_API_MODE_MP = 1
203 # Derived types for type hints, based on capabilities of get_enum_instance.
204 IpsecSpdAction.InputType = Union[IpsecSpdAction, str, None]
205 CryptoAlg.InputType = Union[CryptoAlg, str, None]
206 IntegAlg.InputType = Union[IntegAlg, str, None]
207 IPsecProto.InputType = Union[IPsecProto, str, int, None]
208 # TODO: Introduce a metaclass that adds .find and .InputType automatically?
212 """IPsec utilities."""
214 # The following 4 methods are Python one-liners,
215 # but they are useful when called as a Robot keyword.
218 def get_crypto_alg_key_len(crypto_alg: CryptoAlg.InputType) -> int:
219 """Return encryption algorithm key length.
221 This is a Python one-liner, but useful when called as a Robot keyword.
223 :param crypto_alg: Encryption algorithm.
224 :type crypto_alg: CryptoAlg.InputType
225 :returns: Key length.
228 return get_enum_instance(CryptoAlg, crypto_alg).key_len
231 def get_crypto_alg_scapy_name(crypto_alg: CryptoAlg.InputType) -> str:
232 """Return encryption algorithm scapy name.
234 This is a Python one-liner, but useful when called as a Robot keyword.
236 :param crypto_alg: Encryption algorithm.
237 :type crypto_alg: CryptoAlg.InputType
238 :returns: Algorithm scapy name.
241 return get_enum_instance(CryptoAlg, crypto_alg).scapy_name
243 # The below to keywords differ only by enum type conversion from str.
245 def get_integ_alg_key_len(integ_alg: IntegAlg.InputType) -> int:
246 """Return integrity algorithm key length.
248 :param integ_alg: Integrity algorithm.
249 :type integ_alg: IntegAlg.InputType
250 :returns: Key length.
253 return get_enum_instance(IntegAlg, integ_alg).key_len
256 def get_integ_alg_scapy_name(integ_alg: IntegAlg.InputType) -> str:
257 """Return integrity algorithm scapy name.
259 :param integ_alg: Integrity algorithm.
260 :type integ_alg: IntegAlg.InputType
261 :returns: Algorithm scapy name.
264 return get_enum_instance(IntegAlg, integ_alg).scapy_name
267 def vpp_ipsec_select_backend(
268 node: dict, proto: IPsecProto.InputType, index: int = 1
270 """Select IPsec backend.
272 :param node: VPP node to select IPsec backend on.
273 :param proto: IPsec protocol.
274 :param index: Backend index.
276 :type proto: IPsecProto.InputType
278 :raises RuntimeError: If failed to select IPsec backend or if no API
281 proto = get_enum_instance(IPsecProto, proto)
282 cmd = "ipsec_select_backend"
283 err_msg = f"Failed to select IPsec backend on host {node['host']}"
284 args = dict(protocol=proto, index=index)
285 with PapiSocketExecutor(node) as papi_exec:
286 papi_exec.add(cmd, **args).get_reply(err_msg)
289 def vpp_ipsec_set_async_mode(node: dict, async_enable: int = 1) -> None:
290 """Set IPsec async mode on|off.
292 Unconditionally, attempt to switch crypto dispatch into polling mode.
294 :param node: VPP node to set IPsec async mode.
295 :param async_enable: Async mode on or off.
297 :type async_enable: int
298 :raises RuntimeError: If failed to set IPsec async mode or if no API
301 with PapiSocketExecutor(node) as papi_exec:
302 cmd = "ipsec_set_async_mode"
303 err_msg = f"Failed to set IPsec async mode on host {node['host']}"
304 args = dict(async_enable=async_enable)
305 papi_exec.add(cmd, **args).get_reply(err_msg)
306 cmd = "crypto_set_async_dispatch_v2"
307 err_msg = "Failed to set dispatch mode."
308 args = dict(mode=0, adaptive=False)
310 papi_exec.add(cmd, **args).get_reply(err_msg)
311 except (AttributeError, RuntimeError):
312 # Expected when VPP build does not have the _v2 yet
313 # (after and before the first CRC check).
314 # TODO: Fail here when testing of pre-23.10 builds is over.
318 def vpp_ipsec_crypto_sw_scheduler_set_worker(
319 node: dict, workers: Iterable[int], crypto_enable: bool = False
321 """Enable or disable crypto on specific vpp worker threads.
323 :param node: VPP node to enable or disable crypto for worker threads.
324 :param workers: List of VPP thread numbers.
325 :param crypto_enable: Disable or enable crypto work.
327 :type workers: Iterable[int]
328 :type crypto_enable: bool
329 :raises RuntimeError: If failed to enable or disable crypto for worker
330 thread or if no API reply received.
332 for worker in workers:
333 cmd = "crypto_sw_scheduler_set_worker"
335 "Failed to disable/enable crypto for worker thread"
336 f" on host {node['host']}"
338 args = dict(worker_index=worker - 1, crypto_enable=crypto_enable)
339 with PapiSocketExecutor(node) as papi_exec:
340 papi_exec.add(cmd, **args).get_reply(err_msg)
343 def vpp_ipsec_crypto_sw_scheduler_set_worker_on_all_duts(
344 nodes: dict, crypto_enable: bool = False
346 """Enable or disable crypto on specific vpp worker threads.
348 :param node: VPP node to enable or disable crypto for worker threads.
349 :param crypto_enable: Disable or enable crypto work.
351 :type crypto_enable: bool
352 :raises RuntimeError: If failed to enable or disable crypto for worker
353 thread or if no API reply received.
355 for node_name, node in nodes.items():
356 if node["type"] == NodeType.DUT:
357 thread_data = VPPUtil.vpp_show_threads(node)
358 worker_cnt = len(thread_data) - 1
362 workers = BuiltIn().get_variable_value(
363 f"${{{node_name}_cpu_dp}}"
365 for item in thread_data:
366 if str(item.cpu_id) in workers.split(","):
367 worker_ids.append(item.id)
369 IPsecUtil.vpp_ipsec_crypto_sw_scheduler_set_worker(
370 node, workers=worker_ids, crypto_enable=crypto_enable
374 def vpp_ipsec_add_sad_entry(
378 crypto_alg: CryptoAlg.InputType = None,
379 crypto_key: str = "",
380 integ_alg: IntegAlg.InputType = None,
382 tunnel_src: Optional[str] = None,
383 tunnel_dst: Optional[str] = None,
385 """Create Security Association Database entry on the VPP node.
387 :param node: VPP node to add SAD entry on.
388 :param sad_id: SAD entry ID.
389 :param spi: Security Parameter Index of this SAD entry.
390 :param crypto_alg: The encryption algorithm name.
391 :param crypto_key: The encryption key string.
392 :param integ_alg: The integrity algorithm name.
393 :param integ_key: The integrity key string.
394 :param tunnel_src: Tunnel header source IPv4 or IPv6 address. If not
395 specified ESP transport mode is used.
396 :param tunnel_dst: Tunnel header destination IPv4 or IPv6 address. If
397 not specified ESP transport mode is used.
401 :type crypto_alg: CryptoAlg.InputType
402 :type crypto_key: str
403 :type integ_alg: IntegAlg.InputType
405 :type tunnel_src: Optional[str]
406 :type tunnel_dst: Optional[str]
408 crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
409 integ_alg = get_enum_instance(IntegAlg, integ_alg)
410 if isinstance(crypto_key, str):
411 crypto_key = crypto_key.encode(encoding="utf-8")
412 if isinstance(integ_key, str):
413 integ_key = integ_key.encode(encoding="utf-8")
414 ckey = dict(length=len(crypto_key), data=crypto_key)
415 ikey = dict(length=len(integ_key), data=integ_key if integ_key else 0)
417 flags = int(IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE)
418 if tunnel_src and tunnel_dst:
419 flags = flags | int(IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL)
420 src_addr = ip_address(tunnel_src)
421 dst_addr = ip_address(tunnel_dst)
422 if src_addr.version == 6:
424 IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6
430 cmd = "ipsec_sad_entry_add_v2"
432 "Failed to add Security Association Database entry"
433 f" on host {node['host']}"
438 crypto_algorithm=crypto_alg.alg_int_repr,
440 integrity_algorithm=integ_alg.alg_int_repr,
447 encap_decap_flags=int(
448 TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
450 dscp=int(IpDscp.IP_API_DSCP_CS0),
452 protocol=IPsecProto.ESP,
453 udp_src_port=IPSEC_UDP_PORT_DEFAULT,
454 udp_dst_port=IPSEC_UDP_PORT_DEFAULT,
455 anti_replay_window_size=IPSEC_REPLAY_WINDOW_DEFAULT,
457 args = dict(entry=sad_entry)
458 with PapiSocketExecutor(node) as papi_exec:
459 papi_exec.add(cmd, **args).get_reply(err_msg)
462 def vpp_ipsec_add_sad_entries(
467 crypto_alg: CryptoAlg.InputType = None,
468 crypto_key: str = "",
469 integ_alg: IntegAlg.InputType = None,
471 tunnel_src: Optional[str] = None,
472 tunnel_dst: Optional[str] = None,
473 tunnel_addr_incr: bool = True,
475 """Create multiple Security Association Database entries on VPP node.
477 :param node: VPP node to add SAD entry on.
478 :param n_entries: Number of SAD entries to be created.
479 :param sad_id: First SAD entry ID. All subsequent SAD entries will have
481 :param spi: Security Parameter Index of first SAD entry. All subsequent
482 SAD entries will have spi incremented by 1.
483 :param crypto_alg: The encryption algorithm name.
484 :param crypto_key: The encryption key string.
485 :param integ_alg: The integrity algorithm name.
486 :param integ_key: The integrity key string.
487 :param tunnel_src: Tunnel header source IPv4 or IPv6 address. If not
488 specified ESP transport mode is used.
489 :param tunnel_dst: Tunnel header destination IPv4 or IPv6 address. If
490 not specified ESP transport mode is used.
491 :param tunnel_addr_incr: Enable or disable tunnel IP address
497 :type crypto_alg: CryptoAlg.InputType
498 :type crypto_key: str
499 :type integ_alg: IntegAlg.InputType
501 :type tunnel_src: Optional[str]
502 :type tunnel_dst: Optional[str]
503 :type tunnel_addr_incr: bool
505 crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
506 integ_alg = get_enum_instance(IntegAlg, integ_alg)
507 if isinstance(crypto_key, str):
508 crypto_key = crypto_key.encode(encoding="utf-8")
509 if isinstance(integ_key, str):
510 integ_key = integ_key.encode(encoding="utf-8")
511 if tunnel_src and tunnel_dst:
512 src_addr = ip_address(tunnel_src)
513 dst_addr = ip_address(tunnel_dst)
520 1 << (128 - 96) if src_addr.version == 6 else 1 << (32 - 24)
525 ckey = dict(length=len(crypto_key), data=crypto_key)
526 ikey = dict(length=len(integ_key), data=integ_key if integ_key else 0)
528 flags = int(IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE)
529 if tunnel_src and tunnel_dst:
530 flags = flags | int(IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL)
531 if src_addr.version == 6:
533 IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6
536 cmd = "ipsec_sad_entry_add_v2"
538 "Failed to add Security Association Database entry"
539 f" on host {node['host']}"
545 crypto_algorithm=crypto_alg.alg_int_repr,
547 integrity_algorithm=integ_alg.alg_int_repr,
554 encap_decap_flags=int(
555 TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
557 dscp=int(IpDscp.IP_API_DSCP_CS0),
559 protocol=IPsecProto.ESP,
560 udp_src_port=IPSEC_UDP_PORT_DEFAULT,
561 udp_dst_port=IPSEC_UDP_PORT_DEFAULT,
562 anti_replay_window_size=IPSEC_REPLAY_WINDOW_DEFAULT,
564 args = dict(entry=sad_entry)
565 with PapiSocketExecutor(node, is_async=True) as papi_exec:
566 for i in range(n_entries):
567 args["entry"]["sad_id"] = int(sad_id) + i
568 args["entry"]["spi"] = int(spi) + i
569 args["entry"]["tunnel"]["src"] = (
570 str(src_addr + i * addr_incr)
571 if tunnel_src and tunnel_dst
574 args["entry"]["tunnel"]["dst"] = (
575 str(dst_addr + i * addr_incr)
576 if tunnel_src and tunnel_dst
579 history = bool(not 1 < i < n_entries - 2)
580 papi_exec.add(cmd, history=history, **args)
581 papi_exec.get_replies(err_msg)
584 def vpp_ipsec_set_ip_route(
592 dst_mac: Optional[str] = None,
594 """Set IP address and route on interface.
596 :param node: VPP node to add config on.
597 :param n_tunnels: Number of tunnels to create.
598 :param tunnel_src: Tunnel header source IPv4 or IPv6 address.
599 :param traffic_addr: Traffic destination IP address to route.
600 :param tunnel_dst: Tunnel header destination IPv4 or IPv6 address.
601 :param interface: Interface key on node 1.
602 :param raddr_range: Mask specifying range of Policy selector Remote IP
603 addresses. Valid values are from 1 to 32 in case of IPv4 and to 128
605 :param dst_mac: The MAC address of destination tunnels.
608 :type tunnel_src: str
609 :type traffic_addr: str
610 :type tunnel_dst: str
612 :type raddr_range: int
613 :type dst_mac: Optional[str]
615 tunnel_src = ip_address(tunnel_src)
616 tunnel_dst = ip_address(tunnel_dst)
617 traffic_addr = ip_address(traffic_addr)
618 tunnel_dst_prefix = 128 if tunnel_dst.version == 6 else 32
620 1 << (128 - raddr_range)
621 if tunnel_src.version == 6
622 else 1 << (32 - raddr_range)
625 cmd1 = "sw_interface_add_del_address"
627 sw_if_index=InterfaceUtil.get_interface_index(node, interface),
632 cmd2 = "ip_route_add_del"
633 args2 = dict(is_add=1, is_multipath=0, route=None)
634 cmd3 = "ip_neighbor_add_del"
638 sw_if_index=Topology.get_interface_sw_index(node, interface),
640 mac_address=str(dst_mac),
645 "Failed to configure IP addresses, IP routes and"
646 f" IP neighbor on interface {interface} on host {node['host']}"
648 else "Failed to configure IP addresses and IP routes"
649 f" on interface {interface} on host {node['host']}"
652 with PapiSocketExecutor(node, is_async=True) as papi_exec:
653 for i in range(n_tunnels):
654 tunnel_dst_addr = tunnel_dst + i * addr_incr
655 args1["prefix"] = IPUtil.create_prefix_object(
656 tunnel_src + i * addr_incr, raddr_range
658 args2["route"] = IPUtil.compose_vpp_route_structure(
661 prefix_len=tunnel_dst_prefix,
663 gateway=tunnel_dst_addr,
665 history = bool(not 1 < i < n_tunnels - 2)
666 papi_exec.add(cmd1, history=history, **args1)
667 papi_exec.add(cmd2, history=history, **args2)
669 args2["route"] = IPUtil.compose_vpp_route_structure(
672 prefix_len=tunnel_dst_prefix,
674 gateway=tunnel_dst_addr,
676 papi_exec.add(cmd2, history=history, **args2)
679 args3["neighbor"]["ip_address"] = ip_address(
682 papi_exec.add(cmd3, history=history, **args3)
683 papi_exec.get_replies(err_msg)
686 def vpp_ipsec_add_spd(node: dict, spd_id: int) -> None:
687 """Create Security Policy Database on the VPP node.
689 :param node: VPP node to add SPD on.
690 :param spd_id: SPD ID.
694 cmd = "ipsec_spd_add_del"
696 f"Failed to add Security Policy Database on host {node['host']}"
698 args = dict(is_add=True, spd_id=int(spd_id))
699 with PapiSocketExecutor(node) as papi_exec:
700 papi_exec.add(cmd, **args).get_reply(err_msg)
703 def vpp_ipsec_spd_add_if(
704 node: dict, spd_id: int, interface: Union[str, int]
706 """Add interface to the Security Policy Database.
708 :param node: VPP node.
709 :param spd_id: SPD ID to add interface on.
710 :param interface: Interface name or sw_if_index.
713 :type interface: str or int
715 cmd = "ipsec_interface_add_del_spd"
717 f"Failed to add interface {interface} to Security Policy"
718 f" Database {spd_id} on host {node['host']}"
722 sw_if_index=InterfaceUtil.get_interface_index(node, interface),
725 with PapiSocketExecutor(node) as papi_exec:
726 papi_exec.add(cmd, **args).get_reply(err_msg)
729 def vpp_ipsec_create_spds_match_nth_entry(
731 dir1_interface: Union[str, int],
732 dir2_interface: Union[str, int],
734 local_addr_range: Union[str, IPv4Address, IPv6Address],
735 remote_addr_range: Union[str, IPv4Address, IPv6Address],
736 action: IpsecSpdAction.InputType = IpsecSpdAction.BYPASS,
737 inbound: bool = False,
738 bidirectional: bool = True,
740 """Create one matching SPD entry for inbound or outbound traffic on
741 a DUT for each traffic direction and also create entry_amount - 1
742 non-matching SPD entries. Create a Security Policy Database on each
743 outbound interface where these entries will be configured.
744 The matching SPD entry will have the lowest priority, input action and
745 will be configured to match the IP flow. The non-matching entries will
746 be the same, except with higher priority and non-matching IP flows.
748 Action Protect is currently not supported.
750 :param node: VPP node to configured the SPDs and their entries.
751 :param dir1_interface: The interface in direction 1 where the entries
753 :param dir2_interface: The interface in direction 2 where the entries
755 :param entry_amount: The number of SPD entries to configure. If
756 entry_amount == 1, no non-matching entries will be configured.
757 :param local_addr_range: Matching local address range in direction 1
758 in format IP/prefix or IP/mask. If no mask is provided, it's
759 considered to be /32.
760 :param remote_addr_range: Matching remote address range in
761 direction 1 in format IP/prefix or IP/mask. If no mask is
762 provided, it's considered to be /32.
763 :param action: IPsec SPD action.
764 :param inbound: If True policy is for inbound traffic, otherwise
766 :param bidirectional: When True, will create SPDs in both directions
767 of traffic. When False, only in one direction.
769 :type dir1_interface: Union[str, int]
770 :type dir2_interface: Union[str, int]
771 :type entry_amount: int
772 :type local_addr_range:
773 Union[str, IPv4Address, IPv6Address]
774 :type remote_addr_range:
775 Union[str, IPv4Address, IPv6Address]
776 :type action: IpsecSpdAction.InputType
778 :type bidirectional: bool
779 :raises NotImplementedError: When the action is IpsecSpdAction.PROTECT.
781 action = get_enum_instance(IpsecSpdAction, action)
782 if action == IpsecSpdAction.PROTECT:
783 raise NotImplementedError(
784 "IPsec SPD action PROTECT is not supported."
789 matching_priority = 1
791 IPsecUtil.vpp_ipsec_add_spd(node, spd_id_dir1)
792 IPsecUtil.vpp_ipsec_spd_add_if(node, spd_id_dir1, dir1_interface)
793 # matching entry direction 1
794 IPsecUtil.vpp_ipsec_add_spd_entry(
800 laddr_range=local_addr_range,
801 raddr_range=remote_addr_range,
805 IPsecUtil.vpp_ipsec_add_spd(node, spd_id_dir2)
806 IPsecUtil.vpp_ipsec_spd_add_if(node, spd_id_dir2, dir2_interface)
808 # matching entry direction 2, the address ranges are switched
809 IPsecUtil.vpp_ipsec_add_spd_entry(
815 laddr_range=remote_addr_range,
816 raddr_range=local_addr_range,
819 # non-matching entries
820 no_match_entry_amount = entry_amount - 1
821 if no_match_entry_amount > 0:
822 # create a NetworkIncrement representation of the network,
823 # then skip the matching network
824 no_match_local_addr_range = NetworkIncrement(
825 ip_network(local_addr_range)
827 next(no_match_local_addr_range)
829 no_match_remote_addr_range = NetworkIncrement(
830 ip_network(remote_addr_range)
832 next(no_match_remote_addr_range)
834 # non-matching entries direction 1
835 IPsecUtil.vpp_ipsec_add_spd_entries(
837 no_match_entry_amount,
839 ObjIncrement(matching_priority + 1, 1),
842 laddr_range=no_match_local_addr_range,
843 raddr_range=no_match_remote_addr_range,
847 # reset the networks so that we're using a unified config
848 # the address ranges are switched
849 no_match_remote_addr_range = NetworkIncrement(
850 ip_network(local_addr_range)
852 next(no_match_remote_addr_range)
854 no_match_local_addr_range = NetworkIncrement(
855 ip_network(remote_addr_range)
857 next(no_match_local_addr_range)
858 # non-matching entries direction 2
859 IPsecUtil.vpp_ipsec_add_spd_entries(
861 no_match_entry_amount,
863 ObjIncrement(matching_priority + 1, 1),
866 laddr_range=no_match_local_addr_range,
867 raddr_range=no_match_remote_addr_range,
870 IPsecUtil.vpp_ipsec_show_all(node)
873 def _vpp_ipsec_add_spd_entry_internal(
874 executor: PapiSocketExecutor,
877 action: IpsecSpdAction.InputType,
878 inbound: bool = True,
879 sa_id: Optional[int] = None,
880 proto: IPsecProto.InputType = None,
881 laddr_range: Optional[str] = None,
882 raddr_range: Optional[str] = None,
883 lport_range: Optional[str] = None,
884 rport_range: Optional[str] = None,
885 is_ipv6: bool = False,
887 """Prepare to create Security Policy Database entry on the VPP node.
889 This just adds one more command to the executor.
890 The call site shall get replies once all entries are added,
891 to get speed benefit from async PAPI.
893 :param executor: Open PAPI executor (async handling) to add commands to.
894 :param spd_id: SPD ID to add entry on.
895 :param priority: SPD entry priority, higher number = higher priority.
896 :param action: IPsec SPD action.
897 :param inbound: If True policy is for inbound traffic, otherwise
899 :param sa_id: SAD entry ID for action IpsecSpdAction.PROTECT.
900 :param proto: Policy selector next layer protocol number.
901 :param laddr_range: Policy selector local IPv4 or IPv6 address range
902 in format IP/prefix or IP/mask. If no mask is provided,
903 it's considered to be /32.
904 :param raddr_range: Policy selector remote IPv4 or IPv6 address range
905 in format IP/prefix or IP/mask. If no mask is provided,
906 it's considered to be /32.
907 :param lport_range: Policy selector local TCP/UDP port range in format
908 <port_start>-<port_end>.
909 :param rport_range: Policy selector remote TCP/UDP port range in format
910 <port_start>-<port_end>.
911 :param is_ipv6: True in case of IPv6 policy when IPv6 address range is
912 not defined so it will default to address ::/0, otherwise False.
913 :type executor: PapiSocketExecutor
916 :type action: IpsecSpdAction.InputType
918 :type sa_id: Optional[int]
919 :type proto: IPsecProto.InputType
920 :type laddr_range: Optional[str]
921 :type raddr_range: Optional[str]
922 :type lport_range: Optional[str]
923 :type rport_range: Optional[str]
926 action = get_enum_instance(IpsecSpdAction, action)
927 proto = get_enum_instance(IPsecProto, proto)
928 if laddr_range is None:
929 laddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
931 if raddr_range is None:
932 raddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
934 local_net = ip_network(laddr_range, strict=False)
935 remote_net = ip_network(raddr_range, strict=False)
937 cmd = "ipsec_spd_entry_add_del_v2"
941 priority=int(priority),
942 is_outbound=not inbound,
943 sa_id=int(sa_id) if sa_id else 0,
946 remote_address_start=IPAddress.create_ip_address_object(
947 remote_net.network_address
949 remote_address_stop=IPAddress.create_ip_address_object(
950 remote_net.broadcast_address
952 local_address_start=IPAddress.create_ip_address_object(
953 local_net.network_address
955 local_address_stop=IPAddress.create_ip_address_object(
956 local_net.broadcast_address
959 int(rport_range.split("-")[0]) if rport_range else 0
962 int(rport_range.split("-")[1]) if rport_range else 65535
965 int(lport_range.split("-")[0]) if lport_range else 0
968 int(lport_range.split("-")[1]) if rport_range else 65535
971 args = dict(is_add=True, entry=spd_entry)
972 executor.add(cmd, **args)
975 def vpp_ipsec_add_spd_entry(
979 action: IpsecSpdAction.InputType,
980 inbound: bool = True,
981 sa_id: Optional[int] = None,
982 proto: IPsecProto.InputType = None,
983 laddr_range: Optional[str] = None,
984 raddr_range: Optional[str] = None,
985 lport_range: Optional[str] = None,
986 rport_range: Optional[str] = None,
987 is_ipv6: bool = False,
989 """Create Security Policy Database entry on the VPP node.
991 :param node: VPP node to add SPD entry on.
992 :param spd_id: SPD ID to add entry on.
993 :param priority: SPD entry priority, higher number = higher priority.
994 :param action: IPsec SPD action.
995 :param inbound: If True policy is for inbound traffic, otherwise
997 :param sa_id: SAD entry ID for action IpsecSpdAction.PROTECT.
998 :param proto: Policy selector next layer protocol number.
999 :param laddr_range: Policy selector local IPv4 or IPv6 address range
1000 in format IP/prefix or IP/mask. If no mask is provided,
1001 it's considered to be /32.
1002 :param raddr_range: Policy selector remote IPv4 or IPv6 address range
1003 in format IP/prefix or IP/mask. If no mask is provided,
1004 it's considered to be /32.
1005 :param lport_range: Policy selector local TCP/UDP port range in format
1006 <port_start>-<port_end>.
1007 :param rport_range: Policy selector remote TCP/UDP port range in format
1008 <port_start>-<port_end>.
1009 :param is_ipv6: True in case of IPv6 policy when IPv6 address range is
1010 not defined so it will default to address ::/0, otherwise False.
1014 :type action: IpsecSpdAction.InputType
1016 :type sa_id: Optional[int]
1017 :type proto: IPsecProto.InputType
1018 :type laddr_range: Optional[str]
1019 :type raddr_range: Optional[str]
1020 :type lport_range: Optional[str]
1021 :type rport_range: Optional[str]
1024 action = get_enum_instance(IpsecSpdAction, action)
1025 proto = get_enum_instance(IPsecProto, proto)
1027 "Failed to add entry to Security Policy Database"
1028 f" {spd_id} on host {node['host']}"
1030 with PapiSocketExecutor(node, is_async=True) as papi_exec:
1031 IPsecUtil._vpp_ipsec_add_spd_entry_internal(
1045 papi_exec.get_replies(err_msg)
1048 def vpp_ipsec_add_spd_entries(
1052 priority: Optional[ObjIncrement],
1053 action: IpsecSpdAction.InputType,
1055 sa_id: Optional[ObjIncrement] = None,
1056 proto: IPsecProto.InputType = None,
1057 laddr_range: Optional[NetworkIncrement] = None,
1058 raddr_range: Optional[NetworkIncrement] = None,
1059 lport_range: Optional[str] = None,
1060 rport_range: Optional[str] = None,
1061 is_ipv6: bool = False,
1063 """Create multiple Security Policy Database entries on the VPP node.
1065 :param node: VPP node to add SPD entries on.
1066 :param n_entries: Number of SPD entries to be added.
1067 :param spd_id: SPD ID to add entries on.
1068 :param priority: SPD entries priority, higher number = higher priority.
1069 :param action: IPsec SPD action.
1070 :param inbound: If True policy is for inbound traffic, otherwise
1072 :param sa_id: SAD entry ID for action IpsecSpdAction.PROTECT.
1073 :param proto: Policy selector next layer protocol number.
1074 :param laddr_range: Policy selector local IPv4 or IPv6 address range
1075 in format IP/prefix or IP/mask. If no mask is provided,
1076 it's considered to be /32.
1077 :param raddr_range: Policy selector remote IPv4 or IPv6 address range
1078 in format IP/prefix or IP/mask. If no mask is provided,
1079 it's considered to be /32.
1080 :param lport_range: Policy selector local TCP/UDP port range in format
1081 <port_start>-<port_end>.
1082 :param rport_range: Policy selector remote TCP/UDP port range in format
1083 <port_start>-<port_end>.
1084 :param is_ipv6: True in case of IPv6 policy when IPv6 address range is
1085 not defined so it will default to address ::/0, otherwise False.
1087 :type n_entries: int
1089 :type priority: Optional[ObjIncrement]
1090 :type action: IpsecSpdAction.InputType
1092 :type sa_id: Optional[ObjIncrement]
1093 :type proto: IPsecProto.InputType
1094 :type laddr_range: Optional[NetworkIncrement]
1095 :type raddr_range: Optional[NetworkIncrement]
1096 :type lport_range: Optional[str]
1097 :type rport_range: Optional[str]
1100 action = get_enum_instance(IpsecSpdAction, action)
1101 proto = get_enum_instance(IPsecProto, proto)
1102 if laddr_range is None:
1103 laddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
1104 laddr_range = NetworkIncrement(ip_network(laddr_range), 0)
1106 if raddr_range is None:
1107 raddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
1108 raddr_range = NetworkIncrement(ip_network(raddr_range), 0)
1111 "Failed to add entry to Security Policy Database"
1112 f" {spd_id} on host {node['host']}"
1114 with PapiSocketExecutor(node, is_async=True) as papi_exec:
1115 for _ in range(n_entries):
1116 IPsecUtil._vpp_ipsec_add_spd_entry_internal(
1122 next(sa_id) if sa_id is not None else sa_id,
1130 papi_exec.get_replies(err_msg)
1133 def _ipsec_create_loopback_dut1_papi(
1134 nodes: dict, tun_ips: dict, if1_key: str, if2_key: str
1136 """Create loopback interface and set IP address on VPP node 1 interface
1139 :param nodes: VPP nodes to create tunnel interfaces.
1140 :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface
1141 IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface
1142 IPv4/IPv6 address (ip2).
1143 :param if1_key: VPP node 1 interface key from topology file.
1144 :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1145 interface key from topology file.
1150 :returns: sw_if_idx Of the created loopback interface.
1153 with PapiSocketExecutor(nodes["DUT1"]) as papi_exec:
1154 # Create loopback interface on DUT1, set it to up state
1155 cmd = "create_loopback_instance"
1162 "Failed to create loopback interface"
1163 f" on host {nodes['DUT1']['host']}"
1165 papi_exec.add(cmd, **args)
1166 loop_sw_if_idx = papi_exec.get_sw_if_index(err_msg)
1167 cmd = "sw_interface_set_flags"
1169 sw_if_index=loop_sw_if_idx,
1170 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1173 "Failed to set loopback interface state up"
1174 f" on host {nodes['DUT1']['host']}"
1176 papi_exec.add(cmd, **args).get_reply(err_msg)
1177 # Set IP address on VPP node 1 interface
1178 cmd = "sw_interface_add_del_address"
1180 sw_if_index=InterfaceUtil.get_interface_index(
1181 nodes["DUT1"], if1_key
1185 prefix=IPUtil.create_prefix_object(
1187 96 if tun_ips["ip2"].version == 6 else 24,
1191 f"Failed to set IP address on interface {if1_key}"
1192 f" on host {nodes['DUT1']['host']}"
1194 papi_exec.add(cmd, **args).get_reply(err_msg)
1195 cmd2 = "ip_neighbor_add_del"
1199 sw_if_index=Topology.get_interface_sw_index(
1200 nodes["DUT1"], if1_key
1204 Topology.get_interface_mac(nodes["DUT2"], if2_key)
1205 if "DUT2" in nodes.keys()
1206 else Topology.get_interface_mac(nodes["TG"], if2_key)
1208 ip_address=tun_ips["ip2"].compressed,
1211 err_msg = f"Failed to add IP neighbor on interface {if1_key}"
1212 papi_exec.add(cmd2, **args2).get_reply(err_msg)
1214 return loop_sw_if_idx
1217 def _ipsec_create_tunnel_interfaces_dut1_papi(
1223 crypto_alg: CryptoAlg.InputType,
1224 integ_alg: IntegAlg.InputType,
1225 raddr_ip2: Union[IPv4Address, IPv6Address],
1228 existing_tunnels: int = 0,
1229 ) -> Tuple[List[bytes], List[bytes]]:
1230 """Create multiple IPsec tunnel interfaces on DUT1 node using PAPI.
1232 Generate random keys and return them (so DUT2 or TG can decrypt).
1234 :param nodes: VPP nodes to create tunnel interfaces.
1235 :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface
1236 IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface
1237 IPv4/IPv6 address (ip2).
1238 :param if1_key: VPP node 1 interface key from topology file.
1239 :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1240 interface key from topology file.
1241 :param n_tunnels: Number of tunnel interfaces to be there at the end.
1242 :param crypto_alg: The encryption algorithm name.
1243 :param integ_alg: The integrity algorithm name.
1244 :param raddr_ip2: Policy selector remote IPv4/IPv6 start address for the
1245 first tunnel in direction node2->node1.
1246 :param spi_d: Dictionary with SPIs for VPP node 1 and VPP node 2.
1247 :param addr_incr: IP / IPv6 address incremental step.
1248 :param existing_tunnels: Number of tunnel interfaces before creation.
1249 Useful mainly for reconf tests. Default 0.
1254 :type n_tunnels: int
1255 :type crypto_alg: CryptoAlg.InputType
1256 :type integ_alg: IntegAlg.InputType
1257 :type raddr_ip2: Union[IPv4Address, IPv6Address]
1258 :type addr_incr: int
1260 :type existing_tunnels: int
1261 :returns: Generated ckeys and ikeys.
1262 :rtype: List[bytes], List[bytes]
1264 crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
1265 integ_alg = get_enum_instance(IntegAlg, integ_alg)
1266 if not existing_tunnels:
1267 loop_sw_if_idx = IPsecUtil._ipsec_create_loopback_dut1_papi(
1268 nodes, tun_ips, if1_key, if2_key
1271 loop_sw_if_idx = InterfaceUtil.vpp_get_interface_sw_index(
1272 nodes["DUT1"], "loop0"
1274 with PapiSocketExecutor(nodes["DUT1"], is_async=True) as papi_exec:
1275 # Configure IP addresses on loop0 interface
1276 cmd = "sw_interface_add_del_address"
1278 sw_if_index=loop_sw_if_idx,
1283 for i in range(existing_tunnels, n_tunnels):
1284 args["prefix"] = IPUtil.create_prefix_object(
1285 tun_ips["ip1"] + i * addr_incr,
1286 128 if tun_ips["ip1"].version == 6 else 32,
1289 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1291 # Configure IPIP tunnel interfaces
1292 cmd = "ipip_add_tunnel"
1294 instance=Constants.BITWISE_NON_ZERO,
1299 TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1301 mode=int(TunnelMode.TUNNEL_API_MODE_P2P),
1302 dscp=int(IpDscp.IP_API_DSCP_CS0),
1304 args = dict(tunnel=ipip_tunnel)
1305 ipip_tunnels = [None] * existing_tunnels
1306 for i in range(existing_tunnels, n_tunnels):
1307 ipip_tunnel["src"] = IPAddress.create_ip_address_object(
1308 tun_ips["ip1"] + i * addr_incr
1310 ipip_tunnel["dst"] = IPAddress.create_ip_address_object(
1314 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1317 "Failed to add IPIP tunnel interfaces on host"
1318 f" {nodes['DUT1']['host']}"
1320 ipip_tunnels.extend(
1322 reply["sw_if_index"]
1323 for reply in papi_exec.get_replies(err_msg)
1324 if "sw_if_index" in reply
1327 # Configure IPSec SAD entries
1328 ckeys = [bytes()] * existing_tunnels
1329 ikeys = [bytes()] * existing_tunnels
1330 cmd = "ipsec_sad_entry_add_v2"
1331 c_key = dict(length=0, data=None)
1332 i_key = dict(length=0, data=None)
1333 common_flags = IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE
1337 protocol=IPsecProto.ESP,
1338 crypto_algorithm=crypto_alg.alg_int_repr,
1340 integrity_algorithm=integ_alg.alg_int_repr,
1341 integrity_key=i_key,
1347 encap_decap_flags=int(
1348 TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1350 dscp=int(IpDscp.IP_API_DSCP_CS0),
1353 udp_src_port=IPSEC_UDP_PORT_DEFAULT,
1354 udp_dst_port=IPSEC_UDP_PORT_DEFAULT,
1355 anti_replay_window_size=IPSEC_REPLAY_WINDOW_DEFAULT,
1357 args = dict(entry=sad_entry)
1358 for i in range(existing_tunnels, n_tunnels):
1359 ckeys.append(gen_key(crypto_alg.key_len))
1360 ikeys.append(gen_key(integ_alg.key_len))
1361 # SAD entry for outband / tx path
1362 sad_entry["sad_id"] = i
1363 sad_entry["spi"] = spi_d["spi_1"] + i
1365 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1366 sad_entry["crypto_key"]["data"] = ckeys[i]
1368 sad_entry["integrity_key"]["length"] = len(ikeys[i])
1369 sad_entry["integrity_key"]["data"] = ikeys[i]
1371 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1373 sad_entry["flags"] |= IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_INBOUND
1374 for i in range(existing_tunnels, n_tunnels):
1375 # SAD entry for inband / rx path
1376 sad_entry["sad_id"] = 100000 + i
1377 sad_entry["spi"] = spi_d["spi_2"] + i
1379 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1380 sad_entry["crypto_key"]["data"] = ckeys[i]
1382 sad_entry["integrity_key"]["length"] = len(ikeys[i])
1383 sad_entry["integrity_key"]["data"] = ikeys[i]
1385 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1388 "Failed to add IPsec SAD entries on host"
1389 f" {nodes['DUT1']['host']}"
1391 papi_exec.get_replies(err_msg)
1392 # Add protection for tunnels with IPSEC
1393 cmd = "ipsec_tunnel_protect_update"
1396 via_label=MPLS_LABEL_INVALID,
1397 obj_id=Constants.BITWISE_NON_ZERO,
1399 ipsec_tunnel_protect = dict(
1400 sw_if_index=None, nh=n_hop, sa_out=None, n_sa_in=1, sa_in=None
1402 args = dict(tunnel=ipsec_tunnel_protect)
1403 for i in range(existing_tunnels, n_tunnels):
1404 args["tunnel"]["sw_if_index"] = ipip_tunnels[i]
1405 args["tunnel"]["sa_out"] = i
1406 args["tunnel"]["sa_in"] = [100000 + i]
1408 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1411 "Failed to add protection for tunnels with IPSEC"
1412 f" on host {nodes['DUT1']['host']}"
1414 papi_exec.get_replies(err_msg)
1416 # Configure unnumbered interfaces
1417 cmd = "sw_interface_set_unnumbered"
1420 sw_if_index=InterfaceUtil.get_interface_index(
1421 nodes["DUT1"], if1_key
1423 unnumbered_sw_if_index=0,
1425 for i in range(existing_tunnels, n_tunnels):
1426 args["unnumbered_sw_if_index"] = ipip_tunnels[i]
1428 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1431 cmd = "sw_interface_set_flags"
1434 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1436 for i in range(existing_tunnels, n_tunnels):
1437 args["sw_if_index"] = ipip_tunnels[i]
1439 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1441 # Configure IP routes
1442 cmd = "ip_route_add_del"
1443 args = dict(is_add=1, is_multipath=0, route=None)
1444 for i in range(existing_tunnels, n_tunnels):
1445 args["route"] = IPUtil.compose_vpp_route_structure(
1447 (raddr_ip2 + i).compressed,
1448 prefix_len=128 if raddr_ip2.version == 6 else 32,
1449 interface=ipip_tunnels[i],
1452 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1454 err_msg = f"Failed to add IP routes on host {nodes['DUT1']['host']}"
1455 papi_exec.get_replies(err_msg)
1460 def _ipsec_create_tunnel_interfaces_dut2_papi(
1465 crypto_alg: CryptoAlg.InputType,
1466 ckeys: Sequence[bytes],
1467 integ_alg: IntegAlg.InputType,
1468 ikeys: Sequence[bytes],
1469 raddr_ip1: Union[IPv4Address, IPv6Address],
1472 existing_tunnels: int = 0,
1474 """Create multiple IPsec tunnel interfaces on DUT2 node using PAPI.
1476 This method accesses keys generated by DUT1 method
1477 and does not return anything.
1479 :param nodes: VPP nodes to create tunnel interfaces.
1480 :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface
1481 IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface
1482 IPv4/IPv6 address (ip2).
1483 :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1484 interface key from topology file.
1485 :param n_tunnels: Number of tunnel interfaces to be there at the end.
1486 :param crypto_alg: The encryption algorithm name.
1487 :param ckeys: List of encryption keys.
1488 :param integ_alg: The integrity algorithm name.
1489 :param ikeys: List of integrity keys.
1490 :param raddr_ip1: Policy selector remote IPv4/IPv6 start address for the
1491 first tunnel in direction node1->node2.
1492 :param spi_d: Dictionary with SPIs for VPP node 1 and VPP node 2.
1493 :param addr_incr: IP / IPv6 address incremental step.
1494 :param existing_tunnels: Number of tunnel interfaces before creation.
1495 Useful mainly for reconf tests. Default 0.
1499 :type n_tunnels: int
1500 :type crypto_alg: CryptoAlg.InputType
1501 :type ckeys: Sequence[bytes]
1502 :type integ_alg: IntegAlg.InputType
1503 :type ikeys: Sequence[bytes]
1504 :type raddr_ip1: Union[IPv4Address, IPv6Address]
1505 :type addr_incr: int
1507 :type existing_tunnels: int
1509 crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
1510 integ_alg = get_enum_instance(IntegAlg, integ_alg)
1511 with PapiSocketExecutor(nodes["DUT2"], is_async=True) as papi_exec:
1512 if not existing_tunnels:
1513 # Set IP address on VPP node 2 interface
1514 cmd = "sw_interface_add_del_address"
1516 sw_if_index=InterfaceUtil.get_interface_index(
1517 nodes["DUT2"], if2_key
1521 prefix=IPUtil.create_prefix_object(
1523 96 if tun_ips["ip2"].version == 6 else 24,
1527 f"Failed to set IP address on interface {if2_key}"
1528 f" on host {nodes['DUT2']['host']}"
1530 papi_exec.add(cmd, **args).get_replies(err_msg)
1531 # Configure IPIP tunnel interfaces
1532 cmd = "ipip_add_tunnel"
1534 instance=Constants.BITWISE_NON_ZERO,
1539 TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1541 mode=int(TunnelMode.TUNNEL_API_MODE_P2P),
1542 dscp=int(IpDscp.IP_API_DSCP_CS0),
1544 args = dict(tunnel=ipip_tunnel)
1545 ipip_tunnels = [None] * existing_tunnels
1546 for i in range(existing_tunnels, n_tunnels):
1547 ipip_tunnel["src"] = IPAddress.create_ip_address_object(
1550 ipip_tunnel["dst"] = IPAddress.create_ip_address_object(
1551 tun_ips["ip1"] + i * addr_incr
1554 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1557 "Failed to add IPIP tunnel interfaces on host"
1558 f" {nodes['DUT2']['host']}"
1560 ipip_tunnels.extend(
1562 reply["sw_if_index"]
1563 for reply in papi_exec.get_replies(err_msg)
1564 if "sw_if_index" in reply
1567 # Configure IPSec SAD entries
1568 cmd = "ipsec_sad_entry_add_v2"
1569 c_key = dict(length=0, data=None)
1570 i_key = dict(length=0, data=None)
1571 common_flags = IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE
1575 protocol=IPsecProto.ESP,
1576 crypto_algorithm=crypto_alg.alg_int_repr,
1578 integrity_algorithm=integ_alg.alg_int_repr,
1579 integrity_key=i_key,
1585 encap_decap_flags=int(
1586 TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1588 dscp=int(IpDscp.IP_API_DSCP_CS0),
1591 udp_src_port=IPSEC_UDP_PORT_DEFAULT,
1592 udp_dst_port=IPSEC_UDP_PORT_DEFAULT,
1593 anti_replay_window_size=IPSEC_REPLAY_WINDOW_DEFAULT,
1595 args = dict(entry=sad_entry)
1596 for i in range(existing_tunnels, n_tunnels):
1597 ckeys.append(gen_key(crypto_alg.key_len))
1598 ikeys.append(gen_key(integ_alg.key_len))
1599 # SAD entry for outband / tx path
1600 sad_entry["sad_id"] = 100000 + i
1601 sad_entry["spi"] = spi_d["spi_2"] + i
1603 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1604 sad_entry["crypto_key"]["data"] = ckeys[i]
1606 sad_entry["integrity_key"]["length"] = len(ikeys[i])
1607 sad_entry["integrity_key"]["data"] = ikeys[i]
1609 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1611 sad_entry["flags"] |= IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_INBOUND
1612 for i in range(existing_tunnels, n_tunnels):
1613 # SAD entry for inband / rx path
1614 sad_entry["sad_id"] = i
1615 sad_entry["spi"] = spi_d["spi_1"] + i
1617 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1618 sad_entry["crypto_key"]["data"] = ckeys[i]
1620 sad_entry["integrity_key"]["length"] = len(ikeys[i])
1621 sad_entry["integrity_key"]["data"] = ikeys[i]
1623 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1626 f"Failed to add IPsec SAD entries on host"
1627 f" {nodes['DUT2']['host']}"
1629 papi_exec.get_replies(err_msg)
1630 # Add protection for tunnels with IPSEC
1631 cmd = "ipsec_tunnel_protect_update"
1634 via_label=MPLS_LABEL_INVALID,
1635 obj_id=Constants.BITWISE_NON_ZERO,
1637 ipsec_tunnel_protect = dict(
1638 sw_if_index=None, nh=n_hop, sa_out=None, n_sa_in=1, sa_in=None
1640 args = dict(tunnel=ipsec_tunnel_protect)
1641 for i in range(existing_tunnels, n_tunnels):
1642 args["tunnel"]["sw_if_index"] = ipip_tunnels[i]
1643 args["tunnel"]["sa_out"] = 100000 + i
1644 args["tunnel"]["sa_in"] = [i]
1646 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1649 "Failed to add protection for tunnels with IPSEC"
1650 f" on host {nodes['DUT2']['host']}"
1652 papi_exec.get_replies(err_msg)
1654 if not existing_tunnels:
1655 # Configure IP route
1656 cmd = "ip_route_add_del"
1657 route = IPUtil.compose_vpp_route_structure(
1659 tun_ips["ip1"].compressed,
1660 prefix_len=32 if tun_ips["ip1"].version == 6 else 8,
1662 gateway=(tun_ips["ip2"] - 1).compressed,
1664 args = dict(is_add=1, is_multipath=0, route=route)
1665 papi_exec.add(cmd, **args)
1666 # Configure unnumbered interfaces
1667 cmd = "sw_interface_set_unnumbered"
1670 sw_if_index=InterfaceUtil.get_interface_index(
1671 nodes["DUT2"], if2_key
1673 unnumbered_sw_if_index=0,
1675 for i in range(existing_tunnels, n_tunnels):
1676 args["unnumbered_sw_if_index"] = ipip_tunnels[i]
1678 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1681 cmd = "sw_interface_set_flags"
1684 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1686 for i in range(existing_tunnels, n_tunnels):
1687 args["sw_if_index"] = ipip_tunnels[i]
1689 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1691 # Configure IP routes
1692 cmd = "ip_route_add_del"
1693 args = dict(is_add=1, is_multipath=0, route=None)
1694 for i in range(existing_tunnels, n_tunnels):
1695 args["route"] = IPUtil.compose_vpp_route_structure(
1697 (raddr_ip1 + i).compressed,
1698 prefix_len=128 if raddr_ip1.version == 6 else 32,
1699 interface=ipip_tunnels[i],
1702 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1704 err_msg = f"Failed to add IP routes on host {nodes['DUT2']['host']}"
1705 papi_exec.get_replies(err_msg)
1708 def vpp_ipsec_create_tunnel_interfaces(
1710 tun_if1_ip_addr: str,
1711 tun_if2_ip_addr: str,
1715 crypto_alg: CryptoAlg.InputType,
1716 integ_alg: IntegAlg.InputType,
1720 existing_tunnels: int = 0,
1721 return_keys: bool = False,
1722 ) -> Optional[Tuple[List[bytes], List[bytes], int, int]]:
1723 """Create multiple IPsec tunnel interfaces between two VPP nodes.
1725 Some deployments (e.g. devicetest) need to know the generated keys.
1726 But other deployments (e.g. scale perf test) would get spammed
1727 if we returned keys every time.
1729 :param nodes: VPP nodes to create tunnel interfaces.
1730 :param tun_if1_ip_addr: VPP node 1 ipsec tunnel interface IPv4/IPv6
1732 :param tun_if2_ip_addr: VPP node 2 ipsec tunnel interface IPv4/IPv6
1734 :param if1_key: VPP node 1 interface key from topology file.
1735 :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1736 interface key from topology file.
1737 :param n_tunnels: Number of tunnel interfaces to be there at the end.
1738 :param crypto_alg: The encryption algorithm name.
1739 :param integ_alg: The integrity algorithm name.
1740 :param raddr_ip1: Policy selector remote IPv4/IPv6 start address for the
1741 first tunnel in direction node1->node2.
1742 :param raddr_ip2: Policy selector remote IPv4/IPv6 start address for the
1743 first tunnel in direction node2->node1.
1744 :param raddr_range: Mask specifying range of Policy selector Remote
1745 IPv4/IPv6 addresses. Valid values are from 1 to 32 in case of IPv4
1746 and to 128 in case of IPv6.
1747 :param existing_tunnels: Number of tunnel interfaces before creation.
1748 Useful mainly for reconf tests. Default 0.
1749 :param return_keys: Whether generated keys should be returned.
1751 :type tun_if1_ip_addr: str
1752 :type tun_if2_ip_addr: str
1755 :type n_tunnels: int
1756 :type crypto_alg: CryptoAlg.InputType
1757 :type integ_alg: IntegAlg.InputType
1758 :type raddr_ip1: str
1759 :type raddr_ip2: str
1760 :type raddr_range: int
1761 :type existing_tunnels: int
1762 :type return_keys: bool
1763 :returns: Ckeys, ikeys, spi_1, spi_2.
1764 :rtype: Optional[Tuple[List[bytes], List[bytes], int, int]]
1766 crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
1767 integ_alg = get_enum_instance(IntegAlg, integ_alg)
1768 n_tunnels = int(n_tunnels)
1769 existing_tunnels = int(existing_tunnels)
1770 spi_d = dict(spi_1=100000, spi_2=200000)
1772 ip1=ip_address(tun_if1_ip_addr), ip2=ip_address(tun_if2_ip_addr)
1774 raddr_ip1 = ip_address(raddr_ip1)
1775 raddr_ip2 = ip_address(raddr_ip2)
1777 1 << (128 - raddr_range)
1778 if tun_ips["ip1"].version == 6
1779 else 1 << (32 - raddr_range)
1782 ckeys, ikeys = IPsecUtil._ipsec_create_tunnel_interfaces_dut1_papi(
1795 if "DUT2" in nodes.keys():
1796 IPsecUtil._ipsec_create_tunnel_interfaces_dut2_papi(
1812 return ckeys, ikeys, spi_d["spi_1"], spi_d["spi_2"]
1816 def _create_ipsec_script_files(
1817 dut: str, instances: int
1818 ) -> List[TextIOWrapper]:
1819 """Create script files for configuring IPsec in containers
1821 :param dut: DUT node on which to create the script files
1822 :param instances: number of containers on DUT node
1824 :type instances: int
1825 :returns: Created opened file handles.
1826 :rtype: List[TextIOWrapper]
1829 for cnf in range(0, instances):
1831 f"/tmp/ipsec_create_tunnel_cnf_{dut}_{cnf + 1}.config"
1833 scripts.append(open(script_filename, "w", encoding="utf-8"))
1837 def _close_and_copy_ipsec_script_files(
1838 dut: str, nodes: dict, instances: int, scripts: Sequence[TextIOWrapper]
1840 """Close created scripts and copy them to containers
1842 :param dut: DUT node on which to create the script files
1843 :param nodes: VPP nodes
1844 :param instances: number of containers on DUT node
1845 :param scripts: dictionary holding the script files
1848 :type instances: int
1851 for cnf in range(0, instances):
1852 scripts[cnf].close()
1854 f"/tmp/ipsec_create_tunnel_cnf_{dut}_{cnf + 1}.config"
1856 scp_node(nodes[dut], script_filename, script_filename)
1859 def vpp_ipsec_add_multiple_tunnels(
1861 interface1: Union[str, int],
1862 interface2: Union[str, int],
1864 crypto_alg: CryptoAlg.InputType,
1865 integ_alg: IntegAlg.InputType,
1871 tunnel_addr_incr: bool = True,
1873 """Create multiple IPsec tunnels between two VPP nodes.
1875 :param nodes: VPP nodes to create tunnels.
1876 :param interface1: Interface name or sw_if_index on node 1.
1877 :param interface2: Interface name or sw_if_index on node 2.
1878 :param n_tunnels: Number of tunnels to create.
1879 :param crypto_alg: The encryption algorithm name.
1880 :param integ_alg: The integrity algorithm name.
1881 :param tunnel_ip1: Tunnel node1 IPv4 address.
1882 :param tunnel_ip2: Tunnel node2 IPv4 address.
1883 :param raddr_ip1: Policy selector remote IPv4 start address for the
1884 first tunnel in direction node1->node2.
1885 :param raddr_ip2: Policy selector remote IPv4 start address for the
1886 first tunnel in direction node2->node1.
1887 :param raddr_range: Mask specifying range of Policy selector Remote
1888 IPv4 addresses. Valid values are from 1 to 32.
1889 :param tunnel_addr_incr: Enable or disable tunnel IP address
1892 :type interface1: Union[str, int]
1893 :type interface2: Union[str, int]
1894 :type n_tunnels: int
1895 :type crypto_alg: CryptoAlg.InputType
1896 :type integ_alg: IntegAlg.InputType
1897 :type tunnel_ip1: str
1898 :type tunnel_ip2: str
1899 :type raddr_ip1: str
1900 :type raddr_ip2: str
1901 :type raddr_range: int
1902 :type tunnel_addr_incr: bool
1904 crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
1905 integ_alg = get_enum_instance(IntegAlg, integ_alg)
1915 crypto_key = gen_key(crypto_alg.key_len).decode()
1916 integ_key = gen_key(integ_alg.key_len).decode()
1918 Topology.get_interface_mac(nodes["DUT2"], interface2)
1919 if "DUT2" in nodes.keys()
1920 else Topology.get_interface_mac(nodes["TG"], interface2)
1922 IPsecUtil.vpp_ipsec_set_ip_route(
1933 IPsecUtil.vpp_ipsec_add_spd(nodes["DUT1"], spd_id)
1934 IPsecUtil.vpp_ipsec_spd_add_if(nodes["DUT1"], spd_id, interface1)
1938 if ip_address(tunnel_ip1).version == 6
1941 for i in range(n_tunnels // (addr_incr**2) + 1):
1942 dut1_local_outbound_range = ip_network(
1943 f"{ip_address(tunnel_ip1) + i*(addr_incr**3)}/8", False
1945 dut1_remote_outbound_range = ip_network(
1946 f"{ip_address(tunnel_ip2) + i*(addr_incr**3)}/8", False
1949 IPsecUtil.vpp_ipsec_add_spd_entry(
1953 IpsecSpdAction.BYPASS,
1955 proto=IPsecProto.ESP,
1956 laddr_range=dut1_local_outbound_range,
1957 raddr_range=dut1_remote_outbound_range,
1959 IPsecUtil.vpp_ipsec_add_spd_entry(
1963 IpsecSpdAction.BYPASS,
1965 proto=IPsecProto.ESP,
1966 laddr_range=dut1_remote_outbound_range,
1967 raddr_range=dut1_local_outbound_range,
1970 IPsecUtil.vpp_ipsec_add_sad_entries(
1984 IPsecUtil.vpp_ipsec_add_spd_entries(
1988 priority=ObjIncrement(p_lo, 0),
1989 action=IpsecSpdAction.PROTECT,
1991 sa_id=ObjIncrement(sa_id_1, 1),
1992 raddr_range=NetworkIncrement(ip_network(raddr_ip2)),
1995 IPsecUtil.vpp_ipsec_add_sad_entries(
2008 IPsecUtil.vpp_ipsec_add_spd_entries(
2012 priority=ObjIncrement(p_lo, 0),
2013 action=IpsecSpdAction.PROTECT,
2015 sa_id=ObjIncrement(sa_id_2, 1),
2016 raddr_range=NetworkIncrement(ip_network(raddr_ip1)),
2019 if "DUT2" in nodes.keys():
2020 rmac = Topology.get_interface_mac(nodes["DUT1"], interface1)
2021 IPsecUtil.vpp_ipsec_set_ip_route(
2032 IPsecUtil.vpp_ipsec_add_spd(nodes["DUT2"], spd_id)
2033 IPsecUtil.vpp_ipsec_spd_add_if(nodes["DUT2"], spd_id, interface2)
2034 for i in range(n_tunnels // (addr_incr**2) + 1):
2035 dut2_local_outbound_range = ip_network(
2036 f"{ip_address(tunnel_ip1) + i*(addr_incr**3)}/8", False
2038 dut2_remote_outbound_range = ip_network(
2039 f"{ip_address(tunnel_ip2) + i*(addr_incr**3)}/8", False
2042 IPsecUtil.vpp_ipsec_add_spd_entry(
2046 IpsecSpdAction.BYPASS,
2048 proto=IPsecProto.ESP,
2049 laddr_range=dut2_remote_outbound_range,
2050 raddr_range=dut2_local_outbound_range,
2052 IPsecUtil.vpp_ipsec_add_spd_entry(
2056 IpsecSpdAction.BYPASS,
2058 proto=IPsecProto.ESP,
2059 laddr_range=dut2_local_outbound_range,
2060 raddr_range=dut2_remote_outbound_range,
2063 IPsecUtil.vpp_ipsec_add_sad_entries(
2076 IPsecUtil.vpp_ipsec_add_spd_entries(
2080 priority=ObjIncrement(p_lo, 0),
2081 action=IpsecSpdAction.PROTECT,
2083 sa_id=ObjIncrement(sa_id_1, 1),
2084 raddr_range=NetworkIncrement(ip_network(raddr_ip2)),
2087 IPsecUtil.vpp_ipsec_add_sad_entries(
2100 IPsecUtil.vpp_ipsec_add_spd_entries(
2104 priority=ObjIncrement(p_lo, 0),
2105 action=IpsecSpdAction.PROTECT,
2107 sa_id=ObjIncrement(sa_id_2, 1),
2108 raddr_range=NetworkIncrement(ip_network(raddr_ip1)),
2112 def vpp_ipsec_show_all(node: dict) -> None:
2113 """Run "show ipsec all" debug CLI command.
2115 :param node: Node to run command on.
2118 PapiSocketExecutor.run_cli_cmd(node, "show ipsec all")
2121 def show_ipsec_security_association(node: dict) -> None:
2122 """Show IPSec security association.
2124 :param node: DUT node.
2127 cmd = "ipsec_sa_v5_dump"
2128 PapiSocketExecutor.dump_and_log(node, [cmd])
2131 def vpp_ipsec_flow_enable_rss(
2133 proto: str = "IPSEC_ESP",
2134 rss_type: str = "esp",
2135 function: str = "default",
2137 """Ipsec flow enable rss action.
2139 :param node: DUT node.
2140 :param proto: The flow protocol.
2141 :param rss_type: RSS type.
2142 :param function: RSS function.
2144 :type proto: IPsecProto.InputType
2147 :returns: flow_index.
2150 # The proto argument does not correspond to IPsecProto.
2151 # The allowed values come from src/vnet/ip/protocols.def
2152 # and we do not have a good enum for that yet.
2153 # FlowUti. and FlowUtil. are close but not exactly the same.
2155 # TODO: to be fixed to use full PAPI when it is ready in VPP
2157 f"test flow add src-ip any proto {proto} rss function"
2158 f" {function} rss types {rss_type}"
2160 stdout = PapiSocketExecutor.run_cli_cmd(node, cmd)
2161 flow_index = stdout.split()[1]
2166 def vpp_create_ipsec_flows_on_dut(
2167 node: dict, n_flows: int, rx_queues: int, spi_start: int, interface: str
2169 """Create mutiple ipsec flows and enable flows onto interface.
2171 :param node: DUT node.
2172 :param n_flows: Number of flows to create.
2173 :param rx_queues: NUmber of RX queues.
2174 :param spi_start: The start spi.
2175 :param interface: Name of the interface.
2179 :type rx_queues: int
2180 :type spi_start: int
2181 :type interface: str
2184 for i in range(0, n_flows):
2185 rx_queue = i % rx_queues
2187 flow_index = FlowUtil.vpp_create_ip4_ipsec_flow(
2188 node, "ESP", spi, "redirect-to-queue", value=rx_queue
2190 FlowUtil.vpp_flow_enable(node, interface, flow_index)