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 API names and numeric enums from ipsec_types.api (enum ipsec_crypto_alg).
92 Lowercase names from ipsec_sa.h (foreach_ipsec_crypto_alg).
95 https://github.com/secdev/scapy/blob/master/scapy/layers/ipsec.py
97 Key lengths from crypto.h
98 (foreach_crypto_cipher_alg and foreach_crypto_aead_alg).
101 NONE = ("none", 0, "none", 0)
102 AES_CBC_128 = ("aes-cbc-128", 1, "AES-CBC", 16)
103 AES_CBC_192 = ("aes-cbc-192", 2, "AES-CBC", 24)
104 AES_CBC_256 = ("aes-cbc-256", 3, "AES-CBC", 32)
105 AES_CTR_128 = ("aes-ctr-128", 4, "AES-CTR", 16)
106 AES_CTR_192 = ("aes-ctr-192", 5, "AES-CTR", 24)
107 AES_CTR_256 = ("aes-ctr-256", 6, "AES-CTR", 32)
108 AES_GCM_128 = ("aes-gcm-128", 7, "AES-GCM", 16)
109 AES_GCM_192 = ("aes-gcm-192", 8, "AES-GCM", 24)
110 AES_GCM_256 = ("aes-gcm-256", 9, "AES-GCM", 32)
111 DES_CBC = ("des-cbc", 10, "DES", 7)
112 _3DES_CBC = ("3des-cbc", 11, "3DES", 24)
113 CHACHA20_POLY1305 = ("chacha20-poly1305", 12, "CHACHA20-POLY1305", 32)
114 AES_NULL_GMAC_128 = ("aes-null-gmac-128", 13, "AES-NULL-GMAC", 16)
115 AES_NULL_GMAC_192 = ("aes-null-gmac-192", 14, "AES-NULL-GMAC", 24)
116 AES_NULL_GMAC_256 = ("aes-null-gmac-256", 15, "AES-NULL-GMAC", 32)
119 self, alg_name: str, alg_int_repr: int, scapy_name: str, key_len: int
121 self.alg_name = alg_name
122 self.alg_int_repr = alg_int_repr
123 self.scapy_name = scapy_name
124 self.key_len = key_len
126 # TODO: Investigate if __int__ works with PAPI. It was not enough for "if".
128 """A shorthand to enable "if crypto_alg:" constructs."""
129 return self.alg_int_repr != 0
132 class IntegAlg(Enum):
133 """Integrity algorithms.
135 API names and numeric enums from ipsec_types.api (enum ipsec_integ_alg).
137 Lowercase names from ipsec_sa.h (foreach_ipsec_integ_alg).
139 Scapy names are from:
140 https://github.com/secdev/scapy/blob/master/scapy/layers/ipsec.py
141 Among those, "AES-CMAC-96" may be a mismatch,
142 but there is no sha2-related item with "96" in it.
144 Key lengths seem to be given double of digest length
145 from crypto.h (foreach_crypto_link_async_alg),
146 but data there is not complete
147 (e.g. it does not distinguish sha-256-96 from sha-256-128).
148 The missing values are chosen based on last number (e.g. 192 / 4 = 48).
151 NONE = ("none", 0, "none", 0)
152 MD5_96 = ("md5-96", 1, "HMAC-MD5-96", 24)
153 SHA1_96 = ("sha1-96", 2, "HMAC-SHA1-96", 24)
154 SHA_256_96 = ("sha-256-96", 3, "AES-CMAC-96", 24)
155 SHA_256_128 = ("sha-256-128", 4, "SHA2-256-128", 32)
156 SHA_384_192 = ("sha-384-192", 5, "SHA2-384-192", 48)
157 SHA_512_256 = ("sha-512-256", 6, "SHA2-512-256", 64)
160 self, alg_name: str, alg_int_repr: int, scapy_name: str, key_len: int
162 self.alg_name = alg_name
163 self.alg_int_repr = alg_int_repr
164 self.scapy_name = scapy_name
165 self.key_len = key_len
168 """A shorthand to enable "if integ_alg:" constructs."""
169 return self.alg_int_repr != 0
172 # TODO: Base on Enum, so str values can be defined as in alg enums?
173 class IPsecProto(IntEnum):
176 Mirroring VPP: src/vnet/ipsec/ipsec_types.api enum ipsec_proto.
183 def __str__(self) -> str:
184 """Return string suitable for CLI commands.
186 None is not supported.
188 :returns: Lowercase name of the proto.
190 :raises: ValueError if the numeric value is not recognized.
197 raise ValueError(f"String form not defined for IPsecProto {num}")
200 # The rest of enums do not appear outside this file, so no no change needed yet.
201 class IPsecSadFlags(IntEnum):
202 """IPsec Security Association Database flags."""
204 IPSEC_API_SAD_FLAG_NONE = NONE = 0
205 # Enable extended sequence numbers
206 IPSEC_API_SAD_FLAG_USE_ESN = 0x01
207 # Enable Anti - replay
208 IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY = 0x02
209 # IPsec tunnel mode if non-zero, else transport mode
210 IPSEC_API_SAD_FLAG_IS_TUNNEL = 0x04
211 # IPsec tunnel mode is IPv6 if non-zero, else IPv4 tunnel
212 # only valid if is_tunnel is non-zero
213 IPSEC_API_SAD_FLAG_IS_TUNNEL_V6 = 0x08
214 # Enable UDP encapsulation for NAT traversal
215 IPSEC_API_SAD_FLAG_UDP_ENCAP = 0x10
216 # IPsec SA is or inbound traffic
217 IPSEC_API_SAD_FLAG_IS_INBOUND = 0x40
220 class TunnelEncpaDecapFlags(IntEnum):
221 """Flags controlling tunnel behaviour."""
223 TUNNEL_API_ENCAP_DECAP_FLAG_NONE = NONE = 0
224 # at encap, copy the DF bit of the payload into the tunnel header
225 TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_DF = 1
226 # at encap, set the DF bit in the tunnel header
227 TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_SET_DF = 2
228 # at encap, copy the DSCP bits of the payload into the tunnel header
229 TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_DSCP = 4
230 # at encap, copy the ECN bit of the payload into the tunnel header
231 TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_ECN = 8
232 # at decap, copy the ECN bit of the tunnel header into the payload
233 TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_SET_ECN = 16
236 class TunnelMode(IntEnum):
240 TUNNEL_API_MODE_P2P = NONE = 0
242 TUNNEL_API_MODE_MP = 1
245 # Derived types for type hints, based on capabilities of get_enum_instance.
246 IpsecSpdAction.InputType = Union[IpsecSpdAction, str, None]
247 CryptoAlg.InputType = Union[CryptoAlg, str, None]
248 IntegAlg.InputType = Union[IntegAlg, str, None]
249 IPsecProto.InputType = Union[IPsecProto, str, int, None]
250 # TODO: Introduce a metaclass that adds .find and .InputType automatically?
254 """IPsec utilities."""
256 # The following 4 methods are Python one-liners,
257 # but they are useful when called as a Robot keyword.
260 def get_crypto_alg_key_len(crypto_alg: CryptoAlg.InputType) -> int:
261 """Return encryption algorithm key length.
263 This is a Python one-liner, but useful when called as a Robot keyword.
265 :param crypto_alg: Encryption algorithm.
266 :type crypto_alg: CryptoAlg.InputType
267 :returns: Key length.
270 return get_enum_instance(CryptoAlg, crypto_alg).key_len
273 def get_crypto_alg_scapy_name(crypto_alg: CryptoAlg.InputType) -> str:
274 """Return encryption algorithm scapy name.
276 This is a Python one-liner, but useful when called as a Robot keyword.
278 :param crypto_alg: Encryption algorithm.
279 :type crypto_alg: CryptoAlg.InputType
280 :returns: Algorithm scapy name.
283 return get_enum_instance(CryptoAlg, crypto_alg).scapy_name
285 # The below to keywords differ only by enum type conversion from str.
287 def get_integ_alg_key_len(integ_alg: IntegAlg.InputType) -> int:
288 """Return integrity algorithm key length.
290 :param integ_alg: Integrity algorithm.
291 :type integ_alg: IntegAlg.InputType
292 :returns: Key length.
295 return get_enum_instance(IntegAlg, integ_alg).key_len
298 def get_integ_alg_scapy_name(integ_alg: IntegAlg.InputType) -> str:
299 """Return integrity algorithm scapy name.
301 :param integ_alg: Integrity algorithm.
302 :type integ_alg: IntegAlg.InputType
303 :returns: Algorithm scapy name.
306 return get_enum_instance(IntegAlg, integ_alg).scapy_name
309 def vpp_ipsec_select_backend(
310 node: dict, proto: IPsecProto.InputType, index: int = 1
312 """Select IPsec backend.
314 :param node: VPP node to select IPsec backend on.
315 :param proto: IPsec protocol.
316 :param index: Backend index.
318 :type proto: IPsecProto.InputType
320 :raises RuntimeError: If failed to select IPsec backend or if no API
323 proto = get_enum_instance(IPsecProto, proto)
324 cmd = "ipsec_select_backend"
325 err_msg = f"Failed to select IPsec backend on host {node['host']}"
326 args = dict(protocol=proto, index=index)
327 with PapiSocketExecutor(node) as papi_exec:
328 papi_exec.add(cmd, **args).get_reply(err_msg)
331 def vpp_ipsec_set_async_mode(node: dict, async_enable: int = 1) -> None:
332 """Set IPsec async mode on|off.
334 Unconditionally, attempt to switch crypto dispatch into polling mode.
336 :param node: VPP node to set IPsec async mode.
337 :param async_enable: Async mode on or off.
339 :type async_enable: int
340 :raises RuntimeError: If failed to set IPsec async mode or if no API
343 with PapiSocketExecutor(node) as papi_exec:
344 cmd = "ipsec_set_async_mode"
345 err_msg = f"Failed to set IPsec async mode on host {node['host']}"
346 args = dict(async_enable=async_enable)
347 papi_exec.add(cmd, **args).get_reply(err_msg)
348 cmd = "crypto_set_async_dispatch_v2"
349 err_msg = "Failed to set dispatch mode."
350 args = dict(mode=0, adaptive=False)
352 papi_exec.add(cmd, **args).get_reply(err_msg)
353 except (AttributeError, RuntimeError):
354 # Expected when VPP build does not have the _v2 yet
355 # (after and before the first CRC check).
356 # TODO: Fail here when testing of pre-23.10 builds is over.
360 def vpp_ipsec_crypto_sw_scheduler_set_worker(
361 node: dict, workers: Iterable[int], crypto_enable: bool = False
363 """Enable or disable crypto on specific vpp worker threads.
365 :param node: VPP node to enable or disable crypto for worker threads.
366 :param workers: List of VPP thread numbers.
367 :param crypto_enable: Disable or enable crypto work.
369 :type workers: Iterable[int]
370 :type crypto_enable: bool
371 :raises RuntimeError: If failed to enable or disable crypto for worker
372 thread or if no API reply received.
374 for worker in workers:
375 cmd = "crypto_sw_scheduler_set_worker"
377 "Failed to disable/enable crypto for worker thread"
378 f" on host {node['host']}"
380 args = dict(worker_index=worker - 1, crypto_enable=crypto_enable)
381 with PapiSocketExecutor(node) as papi_exec:
382 papi_exec.add(cmd, **args).get_reply(err_msg)
385 def vpp_ipsec_crypto_sw_scheduler_set_worker_on_all_duts(
386 nodes: dict, crypto_enable: bool = False
388 """Enable or disable crypto on specific vpp worker threads.
390 :param node: VPP node to enable or disable crypto for worker threads.
391 :param crypto_enable: Disable or enable crypto work.
393 :type crypto_enable: bool
394 :raises RuntimeError: If failed to enable or disable crypto for worker
395 thread or if no API reply received.
397 for node_name, node in nodes.items():
398 if node["type"] == NodeType.DUT:
399 thread_data = VPPUtil.vpp_show_threads(node)
400 worker_cnt = len(thread_data) - 1
404 workers = BuiltIn().get_variable_value(
405 f"${{{node_name}_cpu_dp}}"
407 for item in thread_data:
408 if str(item.cpu_id) in workers.split(","):
409 worker_ids.append(item.id)
411 IPsecUtil.vpp_ipsec_crypto_sw_scheduler_set_worker(
412 node, workers=worker_ids, crypto_enable=crypto_enable
416 def vpp_ipsec_add_sad_entry(
420 crypto_alg: CryptoAlg.InputType = None,
421 crypto_key: str = "",
422 integ_alg: IntegAlg.InputType = None,
424 tunnel_src: Optional[str] = None,
425 tunnel_dst: Optional[str] = None,
427 """Create Security Association Database entry on the VPP node.
429 :param node: VPP node to add SAD entry on.
430 :param sad_id: SAD entry ID.
431 :param spi: Security Parameter Index of this SAD entry.
432 :param crypto_alg: The encryption algorithm name.
433 :param crypto_key: The encryption key string.
434 :param integ_alg: The integrity algorithm name.
435 :param integ_key: The integrity key string.
436 :param tunnel_src: Tunnel header source IPv4 or IPv6 address. If not
437 specified ESP transport mode is used.
438 :param tunnel_dst: Tunnel header destination IPv4 or IPv6 address. If
439 not specified ESP transport mode is used.
443 :type crypto_alg: CryptoAlg.InputType
444 :type crypto_key: str
445 :type integ_alg: IntegAlg.InputType
447 :type tunnel_src: Optional[str]
448 :type tunnel_dst: Optional[str]
450 crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
451 integ_alg = get_enum_instance(IntegAlg, integ_alg)
452 if isinstance(crypto_key, str):
453 crypto_key = crypto_key.encode(encoding="utf-8")
454 if isinstance(integ_key, str):
455 integ_key = integ_key.encode(encoding="utf-8")
456 ckey = dict(length=len(crypto_key), data=crypto_key)
457 ikey = dict(length=len(integ_key), data=integ_key if integ_key else 0)
459 flags = int(IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE)
460 if tunnel_src and tunnel_dst:
461 flags = flags | int(IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL)
462 src_addr = ip_address(tunnel_src)
463 dst_addr = ip_address(tunnel_dst)
464 if src_addr.version == 6:
466 IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6
472 cmd = "ipsec_sad_entry_add_v2"
474 "Failed to add Security Association Database entry"
475 f" on host {node['host']}"
480 crypto_algorithm=crypto_alg.alg_int_repr,
482 integrity_algorithm=integ_alg.alg_int_repr,
489 encap_decap_flags=int(
490 TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
492 dscp=int(IpDscp.IP_API_DSCP_CS0),
494 protocol=IPsecProto.ESP,
495 udp_src_port=IPSEC_UDP_PORT_DEFAULT,
496 udp_dst_port=IPSEC_UDP_PORT_DEFAULT,
497 anti_replay_window_size=IPSEC_REPLAY_WINDOW_DEFAULT,
499 args = dict(entry=sad_entry)
500 with PapiSocketExecutor(node) as papi_exec:
501 papi_exec.add(cmd, **args).get_reply(err_msg)
504 def vpp_ipsec_add_sad_entries(
509 crypto_alg: CryptoAlg.InputType = None,
510 crypto_key: str = "",
511 integ_alg: IntegAlg.InputType = None,
513 tunnel_src: Optional[str] = None,
514 tunnel_dst: Optional[str] = None,
515 tunnel_addr_incr: bool = True,
517 """Create multiple Security Association Database entries on VPP node.
519 :param node: VPP node to add SAD entry on.
520 :param n_entries: Number of SAD entries to be created.
521 :param sad_id: First SAD entry ID. All subsequent SAD entries will have
523 :param spi: Security Parameter Index of first SAD entry. All subsequent
524 SAD entries will have spi incremented by 1.
525 :param crypto_alg: The encryption algorithm name.
526 :param crypto_key: The encryption key string.
527 :param integ_alg: The integrity algorithm name.
528 :param integ_key: The integrity key string.
529 :param tunnel_src: Tunnel header source IPv4 or IPv6 address. If not
530 specified ESP transport mode is used.
531 :param tunnel_dst: Tunnel header destination IPv4 or IPv6 address. If
532 not specified ESP transport mode is used.
533 :param tunnel_addr_incr: Enable or disable tunnel IP address
539 :type crypto_alg: CryptoAlg.InputType
540 :type crypto_key: str
541 :type integ_alg: IntegAlg.InputType
543 :type tunnel_src: Optional[str]
544 :type tunnel_dst: Optional[str]
545 :type tunnel_addr_incr: bool
547 crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
548 integ_alg = get_enum_instance(IntegAlg, integ_alg)
549 if isinstance(crypto_key, str):
550 crypto_key = crypto_key.encode(encoding="utf-8")
551 if isinstance(integ_key, str):
552 integ_key = integ_key.encode(encoding="utf-8")
553 if tunnel_src and tunnel_dst:
554 src_addr = ip_address(tunnel_src)
555 dst_addr = ip_address(tunnel_dst)
562 1 << (128 - 96) if src_addr.version == 6 else 1 << (32 - 24)
567 ckey = dict(length=len(crypto_key), data=crypto_key)
568 ikey = dict(length=len(integ_key), data=integ_key if integ_key else 0)
570 flags = int(IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE)
571 if tunnel_src and tunnel_dst:
572 flags = flags | int(IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL)
573 if src_addr.version == 6:
575 IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6
578 cmd = "ipsec_sad_entry_add_v2"
580 "Failed to add Security Association Database entry"
581 f" on host {node['host']}"
587 crypto_algorithm=crypto_alg.alg_int_repr,
589 integrity_algorithm=integ_alg.alg_int_repr,
596 encap_decap_flags=int(
597 TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
599 dscp=int(IpDscp.IP_API_DSCP_CS0),
601 protocol=IPsecProto.ESP,
602 udp_src_port=IPSEC_UDP_PORT_DEFAULT,
603 udp_dst_port=IPSEC_UDP_PORT_DEFAULT,
604 anti_replay_window_size=IPSEC_REPLAY_WINDOW_DEFAULT,
606 args = dict(entry=sad_entry)
607 with PapiSocketExecutor(node, is_async=True) as papi_exec:
608 for i in range(n_entries):
609 args["entry"]["sad_id"] = int(sad_id) + i
610 args["entry"]["spi"] = int(spi) + i
611 args["entry"]["tunnel"]["src"] = (
612 str(src_addr + i * addr_incr)
613 if tunnel_src and tunnel_dst
616 args["entry"]["tunnel"]["dst"] = (
617 str(dst_addr + i * addr_incr)
618 if tunnel_src and tunnel_dst
621 history = bool(not 1 < i < n_entries - 2)
622 papi_exec.add(cmd, history=history, **args)
623 papi_exec.get_replies(err_msg)
626 def vpp_ipsec_set_ip_route(
634 dst_mac: Optional[str] = None,
636 """Set IP address and route on interface.
638 :param node: VPP node to add config on.
639 :param n_tunnels: Number of tunnels to create.
640 :param tunnel_src: Tunnel header source IPv4 or IPv6 address.
641 :param traffic_addr: Traffic destination IP address to route.
642 :param tunnel_dst: Tunnel header destination IPv4 or IPv6 address.
643 :param interface: Interface key on node 1.
644 :param raddr_range: Mask specifying range of Policy selector Remote IP
645 addresses. Valid values are from 1 to 32 in case of IPv4 and to 128
647 :param dst_mac: The MAC address of destination tunnels.
650 :type tunnel_src: str
651 :type traffic_addr: str
652 :type tunnel_dst: str
654 :type raddr_range: int
655 :type dst_mac: Optional[str]
657 tunnel_src = ip_address(tunnel_src)
658 tunnel_dst = ip_address(tunnel_dst)
659 traffic_addr = ip_address(traffic_addr)
660 tunnel_dst_prefix = 128 if tunnel_dst.version == 6 else 32
662 1 << (128 - raddr_range)
663 if tunnel_src.version == 6
664 else 1 << (32 - raddr_range)
667 cmd1 = "sw_interface_add_del_address"
669 sw_if_index=InterfaceUtil.get_interface_index(node, interface),
674 cmd2 = "ip_route_add_del"
675 args2 = dict(is_add=1, is_multipath=0, route=None)
676 cmd3 = "ip_neighbor_add_del"
680 sw_if_index=Topology.get_interface_sw_index(node, interface),
682 mac_address=str(dst_mac),
687 "Failed to configure IP addresses, IP routes and"
688 f" IP neighbor on interface {interface} on host {node['host']}"
690 else "Failed to configure IP addresses and IP routes"
691 f" on interface {interface} on host {node['host']}"
694 with PapiSocketExecutor(node, is_async=True) as papi_exec:
695 for i in range(n_tunnels):
696 tunnel_dst_addr = tunnel_dst + i * addr_incr
697 args1["prefix"] = IPUtil.create_prefix_object(
698 tunnel_src + i * addr_incr, raddr_range
700 args2["route"] = IPUtil.compose_vpp_route_structure(
703 prefix_len=tunnel_dst_prefix,
705 gateway=tunnel_dst_addr,
707 history = bool(not 1 < i < n_tunnels - 2)
708 papi_exec.add(cmd1, history=history, **args1)
709 papi_exec.add(cmd2, history=history, **args2)
711 args2["route"] = IPUtil.compose_vpp_route_structure(
714 prefix_len=tunnel_dst_prefix,
716 gateway=tunnel_dst_addr,
718 papi_exec.add(cmd2, history=history, **args2)
721 args3["neighbor"]["ip_address"] = ip_address(
724 papi_exec.add(cmd3, history=history, **args3)
725 papi_exec.get_replies(err_msg)
728 def vpp_ipsec_add_spd(node: dict, spd_id: int) -> None:
729 """Create Security Policy Database on the VPP node.
731 :param node: VPP node to add SPD on.
732 :param spd_id: SPD ID.
736 cmd = "ipsec_spd_add_del"
738 f"Failed to add Security Policy Database on host {node['host']}"
740 args = dict(is_add=True, spd_id=int(spd_id))
741 with PapiSocketExecutor(node) as papi_exec:
742 papi_exec.add(cmd, **args).get_reply(err_msg)
745 def vpp_ipsec_spd_add_if(
746 node: dict, spd_id: int, interface: Union[str, int]
748 """Add interface to the Security Policy Database.
750 :param node: VPP node.
751 :param spd_id: SPD ID to add interface on.
752 :param interface: Interface name or sw_if_index.
755 :type interface: str or int
757 cmd = "ipsec_interface_add_del_spd"
759 f"Failed to add interface {interface} to Security Policy"
760 f" Database {spd_id} on host {node['host']}"
764 sw_if_index=InterfaceUtil.get_interface_index(node, interface),
767 with PapiSocketExecutor(node) as papi_exec:
768 papi_exec.add(cmd, **args).get_reply(err_msg)
771 def vpp_ipsec_create_spds_match_nth_entry(
773 dir1_interface: Union[str, int],
774 dir2_interface: Union[str, int],
776 local_addr_range: Union[str, IPv4Address, IPv6Address],
777 remote_addr_range: Union[str, IPv4Address, IPv6Address],
778 action: IpsecSpdAction.InputType = IpsecSpdAction.BYPASS,
779 inbound: bool = False,
780 bidirectional: bool = True,
782 """Create one matching SPD entry for inbound or outbound traffic on
783 a DUT for each traffic direction and also create entry_amount - 1
784 non-matching SPD entries. Create a Security Policy Database on each
785 outbound interface where these entries will be configured.
786 The matching SPD entry will have the lowest priority, input action and
787 will be configured to match the IP flow. The non-matching entries will
788 be the same, except with higher priority and non-matching IP flows.
790 Action Protect is currently not supported.
792 :param node: VPP node to configured the SPDs and their entries.
793 :param dir1_interface: The interface in direction 1 where the entries
795 :param dir2_interface: The interface in direction 2 where the entries
797 :param entry_amount: The number of SPD entries to configure. If
798 entry_amount == 1, no non-matching entries will be configured.
799 :param local_addr_range: Matching local address range in direction 1
800 in format IP/prefix or IP/mask. If no mask is provided, it's
801 considered to be /32.
802 :param remote_addr_range: Matching remote address range in
803 direction 1 in format IP/prefix or IP/mask. If no mask is
804 provided, it's considered to be /32.
805 :param action: IPsec SPD action.
806 :param inbound: If True policy is for inbound traffic, otherwise
808 :param bidirectional: When True, will create SPDs in both directions
809 of traffic. When False, only in one direction.
811 :type dir1_interface: Union[str, int]
812 :type dir2_interface: Union[str, int]
813 :type entry_amount: int
814 :type local_addr_range:
815 Union[str, IPv4Address, IPv6Address]
816 :type remote_addr_range:
817 Union[str, IPv4Address, IPv6Address]
818 :type action: IpsecSpdAction.InputType
820 :type bidirectional: bool
821 :raises NotImplementedError: When the action is IpsecSpdAction.PROTECT.
823 action = get_enum_instance(IpsecSpdAction, action)
824 if action == IpsecSpdAction.PROTECT:
825 raise NotImplementedError(
826 "IPsec SPD action PROTECT is not supported."
831 matching_priority = 1
833 IPsecUtil.vpp_ipsec_add_spd(node, spd_id_dir1)
834 IPsecUtil.vpp_ipsec_spd_add_if(node, spd_id_dir1, dir1_interface)
835 # matching entry direction 1
836 IPsecUtil.vpp_ipsec_add_spd_entry(
842 laddr_range=local_addr_range,
843 raddr_range=remote_addr_range,
847 IPsecUtil.vpp_ipsec_add_spd(node, spd_id_dir2)
848 IPsecUtil.vpp_ipsec_spd_add_if(node, spd_id_dir2, dir2_interface)
850 # matching entry direction 2, the address ranges are switched
851 IPsecUtil.vpp_ipsec_add_spd_entry(
857 laddr_range=remote_addr_range,
858 raddr_range=local_addr_range,
861 # non-matching entries
862 no_match_entry_amount = entry_amount - 1
863 if no_match_entry_amount > 0:
864 # create a NetworkIncrement representation of the network,
865 # then skip the matching network
866 no_match_local_addr_range = NetworkIncrement(
867 ip_network(local_addr_range)
869 next(no_match_local_addr_range)
871 no_match_remote_addr_range = NetworkIncrement(
872 ip_network(remote_addr_range)
874 next(no_match_remote_addr_range)
876 # non-matching entries direction 1
877 IPsecUtil.vpp_ipsec_add_spd_entries(
879 no_match_entry_amount,
881 ObjIncrement(matching_priority + 1, 1),
884 laddr_range=no_match_local_addr_range,
885 raddr_range=no_match_remote_addr_range,
889 # reset the networks so that we're using a unified config
890 # the address ranges are switched
891 no_match_remote_addr_range = NetworkIncrement(
892 ip_network(local_addr_range)
894 next(no_match_remote_addr_range)
896 no_match_local_addr_range = NetworkIncrement(
897 ip_network(remote_addr_range)
899 next(no_match_local_addr_range)
900 # non-matching entries direction 2
901 IPsecUtil.vpp_ipsec_add_spd_entries(
903 no_match_entry_amount,
905 ObjIncrement(matching_priority + 1, 1),
908 laddr_range=no_match_local_addr_range,
909 raddr_range=no_match_remote_addr_range,
912 IPsecUtil.vpp_ipsec_show_all(node)
915 def _vpp_ipsec_add_spd_entry_internal(
916 executor: PapiSocketExecutor,
919 action: IpsecSpdAction.InputType,
920 inbound: bool = True,
921 sa_id: Optional[int] = None,
922 proto: IPsecProto.InputType = None,
923 laddr_range: Optional[str] = None,
924 raddr_range: Optional[str] = None,
925 lport_range: Optional[str] = None,
926 rport_range: Optional[str] = None,
927 is_ipv6: bool = False,
929 """Prepare to create Security Policy Database entry on the VPP node.
931 This just adds one more command to the executor.
932 The call site shall get replies once all entries are added,
933 to get speed benefit from async PAPI.
935 :param executor: Open PAPI executor (async handling) to add commands to.
936 :param spd_id: SPD ID to add entry on.
937 :param priority: SPD entry priority, higher number = higher priority.
938 :param action: IPsec SPD action.
939 :param inbound: If True policy is for inbound traffic, otherwise
941 :param sa_id: SAD entry ID for action IpsecSpdAction.PROTECT.
942 :param proto: Policy selector next layer protocol number.
943 :param laddr_range: Policy selector local IPv4 or IPv6 address range
944 in format IP/prefix or IP/mask. If no mask is provided,
945 it's considered to be /32.
946 :param raddr_range: Policy selector remote IPv4 or IPv6 address range
947 in format IP/prefix or IP/mask. If no mask is provided,
948 it's considered to be /32.
949 :param lport_range: Policy selector local TCP/UDP port range in format
950 <port_start>-<port_end>.
951 :param rport_range: Policy selector remote TCP/UDP port range in format
952 <port_start>-<port_end>.
953 :param is_ipv6: True in case of IPv6 policy when IPv6 address range is
954 not defined so it will default to address ::/0, otherwise False.
955 :type executor: PapiSocketExecutor
958 :type action: IpsecSpdAction.InputType
960 :type sa_id: Optional[int]
961 :type proto: IPsecProto.InputType
962 :type laddr_range: Optional[str]
963 :type raddr_range: Optional[str]
964 :type lport_range: Optional[str]
965 :type rport_range: Optional[str]
968 action = get_enum_instance(IpsecSpdAction, action)
969 proto = get_enum_instance(IPsecProto, proto)
970 if laddr_range is None:
971 laddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
973 if raddr_range is None:
974 raddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
976 local_net = ip_network(laddr_range, strict=False)
977 remote_net = ip_network(raddr_range, strict=False)
979 cmd = "ipsec_spd_entry_add_del_v2"
983 priority=int(priority),
984 is_outbound=not inbound,
985 sa_id=int(sa_id) if sa_id else 0,
988 remote_address_start=IPAddress.create_ip_address_object(
989 remote_net.network_address
991 remote_address_stop=IPAddress.create_ip_address_object(
992 remote_net.broadcast_address
994 local_address_start=IPAddress.create_ip_address_object(
995 local_net.network_address
997 local_address_stop=IPAddress.create_ip_address_object(
998 local_net.broadcast_address
1001 int(rport_range.split("-")[0]) if rport_range else 0
1004 int(rport_range.split("-")[1]) if rport_range else 65535
1007 int(lport_range.split("-")[0]) if lport_range else 0
1010 int(lport_range.split("-")[1]) if rport_range else 65535
1013 args = dict(is_add=True, entry=spd_entry)
1014 executor.add(cmd, **args)
1017 def vpp_ipsec_add_spd_entry(
1021 action: IpsecSpdAction.InputType,
1022 inbound: bool = True,
1023 sa_id: Optional[int] = None,
1024 proto: IPsecProto.InputType = None,
1025 laddr_range: Optional[str] = None,
1026 raddr_range: Optional[str] = None,
1027 lport_range: Optional[str] = None,
1028 rport_range: Optional[str] = None,
1029 is_ipv6: bool = False,
1031 """Create Security Policy Database entry on the VPP node.
1033 :param node: VPP node to add SPD entry on.
1034 :param spd_id: SPD ID to add entry on.
1035 :param priority: SPD entry priority, higher number = higher priority.
1036 :param action: IPsec SPD action.
1037 :param inbound: If True policy is for inbound traffic, otherwise
1039 :param sa_id: SAD entry ID for action IpsecSpdAction.PROTECT.
1040 :param proto: Policy selector next layer protocol number.
1041 :param laddr_range: Policy selector local IPv4 or IPv6 address range
1042 in format IP/prefix or IP/mask. If no mask is provided,
1043 it's considered to be /32.
1044 :param raddr_range: Policy selector remote IPv4 or IPv6 address range
1045 in format IP/prefix or IP/mask. If no mask is provided,
1046 it's considered to be /32.
1047 :param lport_range: Policy selector local TCP/UDP port range in format
1048 <port_start>-<port_end>.
1049 :param rport_range: Policy selector remote TCP/UDP port range in format
1050 <port_start>-<port_end>.
1051 :param is_ipv6: True in case of IPv6 policy when IPv6 address range is
1052 not defined so it will default to address ::/0, otherwise False.
1056 :type action: IpsecSpdAction.InputType
1058 :type sa_id: Optional[int]
1059 :type proto: IPsecProto.InputType
1060 :type laddr_range: Optional[str]
1061 :type raddr_range: Optional[str]
1062 :type lport_range: Optional[str]
1063 :type rport_range: Optional[str]
1066 action = get_enum_instance(IpsecSpdAction, action)
1067 proto = get_enum_instance(IPsecProto, proto)
1069 "Failed to add entry to Security Policy Database"
1070 f" {spd_id} on host {node['host']}"
1072 with PapiSocketExecutor(node, is_async=True) as papi_exec:
1073 IPsecUtil._vpp_ipsec_add_spd_entry_internal(
1087 papi_exec.get_replies(err_msg)
1090 def vpp_ipsec_add_spd_entries(
1094 priority: Optional[ObjIncrement],
1095 action: IpsecSpdAction.InputType,
1097 sa_id: Optional[ObjIncrement] = None,
1098 proto: IPsecProto.InputType = None,
1099 laddr_range: Optional[NetworkIncrement] = None,
1100 raddr_range: Optional[NetworkIncrement] = None,
1101 lport_range: Optional[str] = None,
1102 rport_range: Optional[str] = None,
1103 is_ipv6: bool = False,
1105 """Create multiple Security Policy Database entries on the VPP node.
1107 :param node: VPP node to add SPD entries on.
1108 :param n_entries: Number of SPD entries to be added.
1109 :param spd_id: SPD ID to add entries on.
1110 :param priority: SPD entries priority, higher number = higher priority.
1111 :param action: IPsec SPD action.
1112 :param inbound: If True policy is for inbound traffic, otherwise
1114 :param sa_id: SAD entry ID for action IpsecSpdAction.PROTECT.
1115 :param proto: Policy selector next layer protocol number.
1116 :param laddr_range: Policy selector local IPv4 or IPv6 address range
1117 in format IP/prefix or IP/mask. If no mask is provided,
1118 it's considered to be /32.
1119 :param raddr_range: Policy selector remote IPv4 or IPv6 address range
1120 in format IP/prefix or IP/mask. If no mask is provided,
1121 it's considered to be /32.
1122 :param lport_range: Policy selector local TCP/UDP port range in format
1123 <port_start>-<port_end>.
1124 :param rport_range: Policy selector remote TCP/UDP port range in format
1125 <port_start>-<port_end>.
1126 :param is_ipv6: True in case of IPv6 policy when IPv6 address range is
1127 not defined so it will default to address ::/0, otherwise False.
1129 :type n_entries: int
1131 :type priority: Optional[ObjIncrement]
1132 :type action: IpsecSpdAction.InputType
1134 :type sa_id: Optional[ObjIncrement]
1135 :type proto: IPsecProto.InputType
1136 :type laddr_range: Optional[NetworkIncrement]
1137 :type raddr_range: Optional[NetworkIncrement]
1138 :type lport_range: Optional[str]
1139 :type rport_range: Optional[str]
1142 action = get_enum_instance(IpsecSpdAction, action)
1143 proto = get_enum_instance(IPsecProto, proto)
1144 if laddr_range is None:
1145 laddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
1146 laddr_range = NetworkIncrement(ip_network(laddr_range), 0)
1148 if raddr_range is None:
1149 raddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
1150 raddr_range = NetworkIncrement(ip_network(raddr_range), 0)
1153 "Failed to add entry to Security Policy Database"
1154 f" {spd_id} on host {node['host']}"
1156 with PapiSocketExecutor(node, is_async=True) as papi_exec:
1157 for _ in range(n_entries):
1158 IPsecUtil._vpp_ipsec_add_spd_entry_internal(
1164 next(sa_id) if sa_id is not None else sa_id,
1172 papi_exec.get_replies(err_msg)
1175 def _ipsec_create_loopback_dut1_papi(
1176 nodes: dict, tun_ips: dict, if1_key: str, if2_key: str
1178 """Create loopback interface and set IP address on VPP node 1 interface
1181 :param nodes: VPP nodes to create tunnel interfaces.
1182 :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface
1183 IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface
1184 IPv4/IPv6 address (ip2).
1185 :param if1_key: VPP node 1 interface key from topology file.
1186 :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1187 interface key from topology file.
1192 :returns: sw_if_idx Of the created loopback interface.
1195 with PapiSocketExecutor(nodes["DUT1"]) as papi_exec:
1196 # Create loopback interface on DUT1, set it to up state
1197 cmd = "create_loopback_instance"
1204 "Failed to create loopback interface"
1205 f" on host {nodes['DUT1']['host']}"
1207 papi_exec.add(cmd, **args)
1208 loop_sw_if_idx = papi_exec.get_sw_if_index(err_msg)
1209 cmd = "sw_interface_set_flags"
1211 sw_if_index=loop_sw_if_idx,
1212 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1215 "Failed to set loopback interface state up"
1216 f" on host {nodes['DUT1']['host']}"
1218 papi_exec.add(cmd, **args).get_reply(err_msg)
1219 # Set IP address on VPP node 1 interface
1220 cmd = "sw_interface_add_del_address"
1222 sw_if_index=InterfaceUtil.get_interface_index(
1223 nodes["DUT1"], if1_key
1227 prefix=IPUtil.create_prefix_object(
1229 96 if tun_ips["ip2"].version == 6 else 24,
1233 f"Failed to set IP address on interface {if1_key}"
1234 f" on host {nodes['DUT1']['host']}"
1236 papi_exec.add(cmd, **args).get_reply(err_msg)
1237 cmd2 = "ip_neighbor_add_del"
1241 sw_if_index=Topology.get_interface_sw_index(
1242 nodes["DUT1"], if1_key
1246 Topology.get_interface_mac(nodes["DUT2"], if2_key)
1247 if "DUT2" in nodes.keys()
1248 else Topology.get_interface_mac(nodes["TG"], if2_key)
1250 ip_address=tun_ips["ip2"].compressed,
1253 err_msg = f"Failed to add IP neighbor on interface {if1_key}"
1254 papi_exec.add(cmd2, **args2).get_reply(err_msg)
1256 return loop_sw_if_idx
1259 def _ipsec_create_tunnel_interfaces_dut1_papi(
1265 crypto_alg: CryptoAlg.InputType,
1266 integ_alg: IntegAlg.InputType,
1267 raddr_ip2: Union[IPv4Address, IPv6Address],
1270 existing_tunnels: int = 0,
1271 udp_encap: bool = False,
1272 anti_replay: bool = False,
1273 ) -> Tuple[List[bytes], List[bytes]]:
1274 """Create multiple IPsec tunnel interfaces on DUT1 node using PAPI.
1276 Generate random keys and return them (so DUT2 or TG can decrypt).
1278 :param nodes: VPP nodes to create tunnel interfaces.
1279 :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface
1280 IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface
1281 IPv4/IPv6 address (ip2).
1282 :param if1_key: VPP node 1 interface key from topology file.
1283 :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1284 interface key from topology file.
1285 :param n_tunnels: Number of tunnel interfaces to be there at the end.
1286 :param crypto_alg: The encryption algorithm name.
1287 :param integ_alg: The integrity algorithm name.
1288 :param raddr_ip2: Policy selector remote IPv4/IPv6 start address for the
1289 first tunnel in direction node2->node1.
1290 :param spi_d: Dictionary with SPIs for VPP node 1 and VPP node 2.
1291 :param addr_incr: IP / IPv6 address incremental step.
1292 :param existing_tunnels: Number of tunnel interfaces before creation.
1293 Useful mainly for reconf tests. Default 0.
1294 :param udp_encap: Whether to apply UDP_ENCAP flag.
1295 :param anti_replay: Whether to apply USE_ANTI_REPLAY flag.
1300 :type n_tunnels: int
1301 :type crypto_alg: CryptoAlg.InputType
1302 :type integ_alg: IntegAlg.InputType
1303 :type raddr_ip2: Union[IPv4Address, IPv6Address]
1304 :type addr_incr: int
1306 :type existing_tunnels: int
1307 :type udp_encap: bool
1308 :type anti_replay: bool
1309 :returns: Generated ckeys and ikeys.
1310 :rtype: List[bytes], List[bytes]
1312 crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
1313 integ_alg = get_enum_instance(IntegAlg, integ_alg)
1314 if not existing_tunnels:
1315 loop_sw_if_idx = IPsecUtil._ipsec_create_loopback_dut1_papi(
1316 nodes, tun_ips, if1_key, if2_key
1319 loop_sw_if_idx = InterfaceUtil.vpp_get_interface_sw_index(
1320 nodes["DUT1"], "loop0"
1322 with PapiSocketExecutor(nodes["DUT1"], is_async=True) as papi_exec:
1323 # Configure IP addresses on loop0 interface
1324 cmd = "sw_interface_add_del_address"
1326 sw_if_index=loop_sw_if_idx,
1331 for i in range(existing_tunnels, n_tunnels):
1332 args["prefix"] = IPUtil.create_prefix_object(
1333 tun_ips["ip1"] + i * addr_incr,
1334 128 if tun_ips["ip1"].version == 6 else 32,
1337 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1339 # Configure IPIP tunnel interfaces
1340 cmd = "ipip_add_tunnel"
1342 instance=Constants.BITWISE_NON_ZERO,
1347 TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1349 mode=int(TunnelMode.TUNNEL_API_MODE_P2P),
1350 dscp=int(IpDscp.IP_API_DSCP_CS0),
1352 args = dict(tunnel=ipip_tunnel)
1353 ipip_tunnels = [None] * existing_tunnels
1354 for i in range(existing_tunnels, n_tunnels):
1355 ipip_tunnel["src"] = IPAddress.create_ip_address_object(
1356 tun_ips["ip1"] + i * addr_incr
1358 ipip_tunnel["dst"] = IPAddress.create_ip_address_object(
1362 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1365 "Failed to add IPIP tunnel interfaces on host"
1366 f" {nodes['DUT1']['host']}"
1368 ipip_tunnels.extend(
1370 reply["sw_if_index"]
1371 for reply in papi_exec.get_replies(err_msg)
1372 if "sw_if_index" in reply
1375 # Configure IPSec SAD entries
1376 ckeys = [bytes()] * existing_tunnels
1377 ikeys = [bytes()] * existing_tunnels
1378 cmd = "ipsec_sad_entry_add_v2"
1379 c_key = dict(length=0, data=None)
1380 i_key = dict(length=0, data=None)
1381 common_flags = IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE
1383 common_flags |= IPsecSadFlags.IPSEC_API_SAD_FLAG_UDP_ENCAP
1385 common_flags |= IPsecSadFlags.IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY
1389 protocol=IPsecProto.ESP,
1390 crypto_algorithm=crypto_alg.alg_int_repr,
1392 integrity_algorithm=integ_alg.alg_int_repr,
1393 integrity_key=i_key,
1399 encap_decap_flags=int(
1400 TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1402 dscp=int(IpDscp.IP_API_DSCP_CS0),
1405 udp_src_port=IPSEC_UDP_PORT_DEFAULT,
1406 udp_dst_port=IPSEC_UDP_PORT_DEFAULT,
1407 anti_replay_window_size=IPSEC_REPLAY_WINDOW_DEFAULT,
1409 args = dict(entry=sad_entry)
1410 for i in range(existing_tunnels, n_tunnels):
1411 ckeys.append(gen_key(crypto_alg.key_len))
1412 ikeys.append(gen_key(integ_alg.key_len))
1413 # SAD entry for outband / tx path
1414 sad_entry["sad_id"] = i
1415 sad_entry["spi"] = spi_d["spi_1"] + i
1417 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1418 sad_entry["crypto_key"]["data"] = ckeys[i]
1420 sad_entry["integrity_key"]["length"] = len(ikeys[i])
1421 sad_entry["integrity_key"]["data"] = ikeys[i]
1423 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1425 sad_entry["flags"] |= IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_INBOUND
1426 for i in range(existing_tunnels, n_tunnels):
1427 # SAD entry for inband / rx path
1428 sad_entry["sad_id"] = 100000 + i
1429 sad_entry["spi"] = spi_d["spi_2"] + i
1431 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1432 sad_entry["crypto_key"]["data"] = ckeys[i]
1434 sad_entry["integrity_key"]["length"] = len(ikeys[i])
1435 sad_entry["integrity_key"]["data"] = ikeys[i]
1437 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1440 "Failed to add IPsec SAD entries on host"
1441 f" {nodes['DUT1']['host']}"
1443 papi_exec.get_replies(err_msg)
1444 # Add protection for tunnels with IPSEC
1445 cmd = "ipsec_tunnel_protect_update"
1448 via_label=MPLS_LABEL_INVALID,
1449 obj_id=Constants.BITWISE_NON_ZERO,
1451 ipsec_tunnel_protect = dict(
1452 sw_if_index=None, nh=n_hop, sa_out=None, n_sa_in=1, sa_in=None
1454 args = dict(tunnel=ipsec_tunnel_protect)
1455 for i in range(existing_tunnels, n_tunnels):
1456 args["tunnel"]["sw_if_index"] = ipip_tunnels[i]
1457 args["tunnel"]["sa_out"] = i
1458 args["tunnel"]["sa_in"] = [100000 + i]
1460 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1463 "Failed to add protection for tunnels with IPSEC"
1464 f" on host {nodes['DUT1']['host']}"
1466 papi_exec.get_replies(err_msg)
1468 # Configure unnumbered interfaces
1469 cmd = "sw_interface_set_unnumbered"
1472 sw_if_index=InterfaceUtil.get_interface_index(
1473 nodes["DUT1"], if1_key
1475 unnumbered_sw_if_index=0,
1477 for i in range(existing_tunnels, n_tunnels):
1478 args["unnumbered_sw_if_index"] = ipip_tunnels[i]
1480 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1483 cmd = "sw_interface_set_flags"
1486 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1488 for i in range(existing_tunnels, n_tunnels):
1489 args["sw_if_index"] = ipip_tunnels[i]
1491 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1493 # Configure IP routes
1494 cmd = "ip_route_add_del"
1495 args = dict(is_add=1, is_multipath=0, route=None)
1496 for i in range(existing_tunnels, n_tunnels):
1497 args["route"] = IPUtil.compose_vpp_route_structure(
1499 (raddr_ip2 + i).compressed,
1500 prefix_len=128 if raddr_ip2.version == 6 else 32,
1501 interface=ipip_tunnels[i],
1504 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1506 err_msg = f"Failed to add IP routes on host {nodes['DUT1']['host']}"
1507 papi_exec.get_replies(err_msg)
1512 def _ipsec_create_tunnel_interfaces_dut2_papi(
1517 crypto_alg: CryptoAlg.InputType,
1518 ckeys: Sequence[bytes],
1519 integ_alg: IntegAlg.InputType,
1520 ikeys: Sequence[bytes],
1521 raddr_ip1: Union[IPv4Address, IPv6Address],
1524 existing_tunnels: int = 0,
1525 udp_encap: bool = False,
1526 anti_replay: bool = False,
1528 """Create multiple IPsec tunnel interfaces on DUT2 node using PAPI.
1530 This method accesses keys generated by DUT1 method
1531 and does not return anything.
1533 :param nodes: VPP nodes to create tunnel interfaces.
1534 :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface
1535 IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface
1536 IPv4/IPv6 address (ip2).
1537 :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1538 interface key from topology file.
1539 :param n_tunnels: Number of tunnel interfaces to be there at the end.
1540 :param crypto_alg: The encryption algorithm name.
1541 :param ckeys: List of encryption keys.
1542 :param integ_alg: The integrity algorithm name.
1543 :param ikeys: List of integrity keys.
1544 :param raddr_ip1: Policy selector remote IPv4/IPv6 start address for the
1545 first tunnel in direction node1->node2.
1546 :param spi_d: Dictionary with SPIs for VPP node 1 and VPP node 2.
1547 :param addr_incr: IP / IPv6 address incremental step.
1548 :param existing_tunnels: Number of tunnel interfaces before creation.
1549 Useful mainly for reconf tests. Default 0.
1550 :param udp_encap: Whether to apply UDP_ENCAP flag.
1551 :param anti_replay: Whether to apply USE_ANTI_REPLAY flag.
1555 :type n_tunnels: int
1556 :type crypto_alg: CryptoAlg.InputType
1557 :type ckeys: Sequence[bytes]
1558 :type integ_alg: IntegAlg.InputType
1559 :type ikeys: Sequence[bytes]
1560 :type raddr_ip1: Union[IPv4Address, IPv6Address]
1561 :type addr_incr: int
1563 :type existing_tunnels: int
1564 :type udp_encap: bool
1565 :type anti_replay: bool
1567 crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
1568 integ_alg = get_enum_instance(IntegAlg, integ_alg)
1569 with PapiSocketExecutor(nodes["DUT2"], is_async=True) as papi_exec:
1570 if not existing_tunnels:
1571 # Set IP address on VPP node 2 interface
1572 cmd = "sw_interface_add_del_address"
1574 sw_if_index=InterfaceUtil.get_interface_index(
1575 nodes["DUT2"], if2_key
1579 prefix=IPUtil.create_prefix_object(
1581 96 if tun_ips["ip2"].version == 6 else 24,
1585 f"Failed to set IP address on interface {if2_key}"
1586 f" on host {nodes['DUT2']['host']}"
1588 papi_exec.add(cmd, **args).get_replies(err_msg)
1589 # Configure IPIP tunnel interfaces
1590 cmd = "ipip_add_tunnel"
1592 instance=Constants.BITWISE_NON_ZERO,
1597 TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1599 mode=int(TunnelMode.TUNNEL_API_MODE_P2P),
1600 dscp=int(IpDscp.IP_API_DSCP_CS0),
1602 args = dict(tunnel=ipip_tunnel)
1603 ipip_tunnels = [None] * existing_tunnels
1604 for i in range(existing_tunnels, n_tunnels):
1605 ipip_tunnel["src"] = IPAddress.create_ip_address_object(
1608 ipip_tunnel["dst"] = IPAddress.create_ip_address_object(
1609 tun_ips["ip1"] + i * addr_incr
1612 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1615 "Failed to add IPIP tunnel interfaces on host"
1616 f" {nodes['DUT2']['host']}"
1618 ipip_tunnels.extend(
1620 reply["sw_if_index"]
1621 for reply in papi_exec.get_replies(err_msg)
1622 if "sw_if_index" in reply
1625 # Configure IPSec SAD entries
1626 cmd = "ipsec_sad_entry_add_v2"
1627 c_key = dict(length=0, data=None)
1628 i_key = dict(length=0, data=None)
1629 common_flags = IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE
1631 common_flags |= IPsecSadFlags.IPSEC_API_SAD_FLAG_UDP_ENCAP
1633 common_flags |= IPsecSadFlags.IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY
1637 protocol=IPsecProto.ESP,
1638 crypto_algorithm=crypto_alg.alg_int_repr,
1640 integrity_algorithm=integ_alg.alg_int_repr,
1641 integrity_key=i_key,
1647 encap_decap_flags=int(
1648 TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1650 dscp=int(IpDscp.IP_API_DSCP_CS0),
1653 udp_src_port=IPSEC_UDP_PORT_DEFAULT,
1654 udp_dst_port=IPSEC_UDP_PORT_DEFAULT,
1655 anti_replay_window_size=IPSEC_REPLAY_WINDOW_DEFAULT,
1657 args = dict(entry=sad_entry)
1658 for i in range(existing_tunnels, n_tunnels):
1659 ckeys.append(gen_key(crypto_alg.key_len))
1660 ikeys.append(gen_key(integ_alg.key_len))
1661 # SAD entry for outband / tx path
1662 sad_entry["sad_id"] = 100000 + i
1663 sad_entry["spi"] = spi_d["spi_2"] + i
1665 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1666 sad_entry["crypto_key"]["data"] = ckeys[i]
1668 sad_entry["integrity_key"]["length"] = len(ikeys[i])
1669 sad_entry["integrity_key"]["data"] = ikeys[i]
1671 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1673 sad_entry["flags"] |= IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_INBOUND
1674 for i in range(existing_tunnels, n_tunnels):
1675 # SAD entry for inband / rx path
1676 sad_entry["sad_id"] = i
1677 sad_entry["spi"] = spi_d["spi_1"] + i
1679 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1680 sad_entry["crypto_key"]["data"] = ckeys[i]
1682 sad_entry["integrity_key"]["length"] = len(ikeys[i])
1683 sad_entry["integrity_key"]["data"] = ikeys[i]
1685 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1688 f"Failed to add IPsec SAD entries on host"
1689 f" {nodes['DUT2']['host']}"
1691 papi_exec.get_replies(err_msg)
1692 # Add protection for tunnels with IPSEC
1693 cmd = "ipsec_tunnel_protect_update"
1696 via_label=MPLS_LABEL_INVALID,
1697 obj_id=Constants.BITWISE_NON_ZERO,
1699 ipsec_tunnel_protect = dict(
1700 sw_if_index=None, nh=n_hop, sa_out=None, n_sa_in=1, sa_in=None
1702 args = dict(tunnel=ipsec_tunnel_protect)
1703 for i in range(existing_tunnels, n_tunnels):
1704 args["tunnel"]["sw_if_index"] = ipip_tunnels[i]
1705 args["tunnel"]["sa_out"] = 100000 + i
1706 args["tunnel"]["sa_in"] = [i]
1708 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1711 "Failed to add protection for tunnels with IPSEC"
1712 f" on host {nodes['DUT2']['host']}"
1714 papi_exec.get_replies(err_msg)
1716 if not existing_tunnels:
1717 # Configure IP route
1718 cmd = "ip_route_add_del"
1719 route = IPUtil.compose_vpp_route_structure(
1721 tun_ips["ip1"].compressed,
1722 prefix_len=32 if tun_ips["ip1"].version == 6 else 8,
1724 gateway=(tun_ips["ip2"] - 1).compressed,
1726 args = dict(is_add=1, is_multipath=0, route=route)
1727 papi_exec.add(cmd, **args)
1728 # Configure unnumbered interfaces
1729 cmd = "sw_interface_set_unnumbered"
1732 sw_if_index=InterfaceUtil.get_interface_index(
1733 nodes["DUT2"], if2_key
1735 unnumbered_sw_if_index=0,
1737 for i in range(existing_tunnels, n_tunnels):
1738 args["unnumbered_sw_if_index"] = ipip_tunnels[i]
1740 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1743 cmd = "sw_interface_set_flags"
1746 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1748 for i in range(existing_tunnels, n_tunnels):
1749 args["sw_if_index"] = ipip_tunnels[i]
1751 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1753 # Configure IP routes
1754 cmd = "ip_route_add_del"
1755 args = dict(is_add=1, is_multipath=0, route=None)
1756 for i in range(existing_tunnels, n_tunnels):
1757 args["route"] = IPUtil.compose_vpp_route_structure(
1759 (raddr_ip1 + i).compressed,
1760 prefix_len=128 if raddr_ip1.version == 6 else 32,
1761 interface=ipip_tunnels[i],
1764 cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1766 err_msg = f"Failed to add IP routes on host {nodes['DUT2']['host']}"
1767 papi_exec.get_replies(err_msg)
1770 def vpp_ipsec_create_tunnel_interfaces(
1772 tun_if1_ip_addr: str,
1773 tun_if2_ip_addr: str,
1777 crypto_alg: CryptoAlg.InputType,
1778 integ_alg: IntegAlg.InputType,
1782 existing_tunnels: int = 0,
1783 udp_encap: bool = False,
1784 anti_replay: bool = False,
1785 return_keys: bool = False,
1786 ) -> Optional[Tuple[List[bytes], List[bytes], int, int]]:
1787 """Create multiple IPsec tunnel interfaces between two VPP nodes.
1789 Some deployments (e.g. devicetest) need to know the generated keys.
1790 But other deployments (e.g. scale perf test) would get spammed
1791 if we returned keys every time.
1793 :param nodes: VPP nodes to create tunnel interfaces.
1794 :param tun_if1_ip_addr: VPP node 1 ipsec tunnel interface IPv4/IPv6
1796 :param tun_if2_ip_addr: VPP node 2 ipsec tunnel interface IPv4/IPv6
1798 :param if1_key: VPP node 1 interface key from topology file.
1799 :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1800 interface key from topology file.
1801 :param n_tunnels: Number of tunnel interfaces to be there at the end.
1802 :param crypto_alg: The encryption algorithm name.
1803 :param integ_alg: The integrity algorithm name.
1804 :param raddr_ip1: Policy selector remote IPv4/IPv6 start address for the
1805 first tunnel in direction node1->node2.
1806 :param raddr_ip2: Policy selector remote IPv4/IPv6 start address for the
1807 first tunnel in direction node2->node1.
1808 :param raddr_range: Mask specifying range of Policy selector Remote
1809 IPv4/IPv6 addresses. Valid values are from 1 to 32 in case of IPv4
1810 and to 128 in case of IPv6.
1811 :param existing_tunnels: Number of tunnel interfaces before creation.
1812 Useful mainly for reconf tests. Default 0.
1813 :param return_keys: Whether generated keys should be returned.
1814 :param udp_encap: Whether to apply UDP_ENCAP flag.
1815 :param anti_replay: Whether to apply USE_ANTI_REPLAY flag.
1817 :type tun_if1_ip_addr: str
1818 :type tun_if2_ip_addr: str
1821 :type n_tunnels: int
1822 :type crypto_alg: CryptoAlg.InputType
1823 :type integ_alg: IntegAlg.InputType
1824 :type raddr_ip1: str
1825 :type raddr_ip2: str
1826 :type raddr_range: int
1827 :type existing_tunnels: int
1828 :type return_keys: bool
1829 :type udp_encap: bool
1830 :type anti_replay: bool
1831 :returns: Ckeys, ikeys, spi_1, spi_2.
1832 :rtype: Optional[Tuple[List[bytes], List[bytes], int, int]]
1834 crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
1835 integ_alg = get_enum_instance(IntegAlg, integ_alg)
1836 n_tunnels = int(n_tunnels)
1837 existing_tunnels = int(existing_tunnels)
1838 spi_d = dict(spi_1=100000, spi_2=200000)
1840 ip1=ip_address(tun_if1_ip_addr), ip2=ip_address(tun_if2_ip_addr)
1842 raddr_ip1 = ip_address(raddr_ip1)
1843 raddr_ip2 = ip_address(raddr_ip2)
1845 1 << (128 - raddr_range)
1846 if tun_ips["ip1"].version == 6
1847 else 1 << (32 - raddr_range)
1850 ckeys, ikeys = IPsecUtil._ipsec_create_tunnel_interfaces_dut1_papi(
1865 if "DUT2" in nodes.keys():
1866 IPsecUtil._ipsec_create_tunnel_interfaces_dut2_papi(
1884 return ckeys, ikeys, spi_d["spi_1"], spi_d["spi_2"]
1888 def _create_ipsec_script_files(
1889 dut: str, instances: int
1890 ) -> List[TextIOWrapper]:
1891 """Create script files for configuring IPsec in containers
1893 :param dut: DUT node on which to create the script files
1894 :param instances: number of containers on DUT node
1896 :type instances: int
1897 :returns: Created opened file handles.
1898 :rtype: List[TextIOWrapper]
1901 for cnf in range(0, instances):
1903 f"/tmp/ipsec_create_tunnel_cnf_{dut}_{cnf + 1}.config"
1905 scripts.append(open(script_filename, "w", encoding="utf-8"))
1909 def _close_and_copy_ipsec_script_files(
1910 dut: str, nodes: dict, instances: int, scripts: Sequence[TextIOWrapper]
1912 """Close created scripts and copy them to containers
1914 :param dut: DUT node on which to create the script files
1915 :param nodes: VPP nodes
1916 :param instances: number of containers on DUT node
1917 :param scripts: dictionary holding the script files
1920 :type instances: int
1923 for cnf in range(0, instances):
1924 scripts[cnf].close()
1926 f"/tmp/ipsec_create_tunnel_cnf_{dut}_{cnf + 1}.config"
1928 scp_node(nodes[dut], script_filename, script_filename)
1931 def vpp_ipsec_add_multiple_tunnels(
1933 interface1: Union[str, int],
1934 interface2: Union[str, int],
1936 crypto_alg: CryptoAlg.InputType,
1937 integ_alg: IntegAlg.InputType,
1943 tunnel_addr_incr: bool = True,
1945 """Create multiple IPsec tunnels between two VPP nodes.
1947 :param nodes: VPP nodes to create tunnels.
1948 :param interface1: Interface name or sw_if_index on node 1.
1949 :param interface2: Interface name or sw_if_index on node 2.
1950 :param n_tunnels: Number of tunnels to create.
1951 :param crypto_alg: The encryption algorithm name.
1952 :param integ_alg: The integrity algorithm name.
1953 :param tunnel_ip1: Tunnel node1 IPv4 address.
1954 :param tunnel_ip2: Tunnel node2 IPv4 address.
1955 :param raddr_ip1: Policy selector remote IPv4 start address for the
1956 first tunnel in direction node1->node2.
1957 :param raddr_ip2: Policy selector remote IPv4 start address for the
1958 first tunnel in direction node2->node1.
1959 :param raddr_range: Mask specifying range of Policy selector Remote
1960 IPv4 addresses. Valid values are from 1 to 32.
1961 :param tunnel_addr_incr: Enable or disable tunnel IP address
1964 :type interface1: Union[str, int]
1965 :type interface2: Union[str, int]
1966 :type n_tunnels: int
1967 :type crypto_alg: CryptoAlg.InputType
1968 :type integ_alg: IntegAlg.InputType
1969 :type tunnel_ip1: str
1970 :type tunnel_ip2: str
1971 :type raddr_ip1: str
1972 :type raddr_ip2: str
1973 :type raddr_range: int
1974 :type tunnel_addr_incr: bool
1976 crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
1977 integ_alg = get_enum_instance(IntegAlg, integ_alg)
1987 crypto_key = gen_key(crypto_alg.key_len).decode()
1988 integ_key = gen_key(integ_alg.key_len).decode()
1990 Topology.get_interface_mac(nodes["DUT2"], interface2)
1991 if "DUT2" in nodes.keys()
1992 else Topology.get_interface_mac(nodes["TG"], interface2)
1994 IPsecUtil.vpp_ipsec_set_ip_route(
2005 IPsecUtil.vpp_ipsec_add_spd(nodes["DUT1"], spd_id)
2006 IPsecUtil.vpp_ipsec_spd_add_if(nodes["DUT1"], spd_id, interface1)
2010 if ip_address(tunnel_ip1).version == 6
2013 for i in range(n_tunnels // (addr_incr**2) + 1):
2014 dut1_local_outbound_range = ip_network(
2015 f"{ip_address(tunnel_ip1) + i*(addr_incr**3)}/8", False
2017 dut1_remote_outbound_range = ip_network(
2018 f"{ip_address(tunnel_ip2) + i*(addr_incr**3)}/8", False
2021 IPsecUtil.vpp_ipsec_add_spd_entry(
2025 IpsecSpdAction.BYPASS,
2027 proto=IPsecProto.ESP,
2028 laddr_range=dut1_local_outbound_range,
2029 raddr_range=dut1_remote_outbound_range,
2031 IPsecUtil.vpp_ipsec_add_spd_entry(
2035 IpsecSpdAction.BYPASS,
2037 proto=IPsecProto.ESP,
2038 laddr_range=dut1_remote_outbound_range,
2039 raddr_range=dut1_local_outbound_range,
2042 IPsecUtil.vpp_ipsec_add_sad_entries(
2056 IPsecUtil.vpp_ipsec_add_spd_entries(
2060 priority=ObjIncrement(p_lo, 0),
2061 action=IpsecSpdAction.PROTECT,
2063 sa_id=ObjIncrement(sa_id_1, 1),
2064 raddr_range=NetworkIncrement(ip_network(raddr_ip2)),
2067 IPsecUtil.vpp_ipsec_add_sad_entries(
2080 IPsecUtil.vpp_ipsec_add_spd_entries(
2084 priority=ObjIncrement(p_lo, 0),
2085 action=IpsecSpdAction.PROTECT,
2087 sa_id=ObjIncrement(sa_id_2, 1),
2088 raddr_range=NetworkIncrement(ip_network(raddr_ip1)),
2091 if "DUT2" in nodes.keys():
2092 rmac = Topology.get_interface_mac(nodes["DUT1"], interface1)
2093 IPsecUtil.vpp_ipsec_set_ip_route(
2104 IPsecUtil.vpp_ipsec_add_spd(nodes["DUT2"], spd_id)
2105 IPsecUtil.vpp_ipsec_spd_add_if(nodes["DUT2"], spd_id, interface2)
2106 for i in range(n_tunnels // (addr_incr**2) + 1):
2107 dut2_local_outbound_range = ip_network(
2108 f"{ip_address(tunnel_ip1) + i*(addr_incr**3)}/8", False
2110 dut2_remote_outbound_range = ip_network(
2111 f"{ip_address(tunnel_ip2) + i*(addr_incr**3)}/8", False
2114 IPsecUtil.vpp_ipsec_add_spd_entry(
2118 IpsecSpdAction.BYPASS,
2120 proto=IPsecProto.ESP,
2121 laddr_range=dut2_remote_outbound_range,
2122 raddr_range=dut2_local_outbound_range,
2124 IPsecUtil.vpp_ipsec_add_spd_entry(
2128 IpsecSpdAction.BYPASS,
2130 proto=IPsecProto.ESP,
2131 laddr_range=dut2_local_outbound_range,
2132 raddr_range=dut2_remote_outbound_range,
2135 IPsecUtil.vpp_ipsec_add_sad_entries(
2148 IPsecUtil.vpp_ipsec_add_spd_entries(
2152 priority=ObjIncrement(p_lo, 0),
2153 action=IpsecSpdAction.PROTECT,
2155 sa_id=ObjIncrement(sa_id_1, 1),
2156 raddr_range=NetworkIncrement(ip_network(raddr_ip2)),
2159 IPsecUtil.vpp_ipsec_add_sad_entries(
2172 IPsecUtil.vpp_ipsec_add_spd_entries(
2176 priority=ObjIncrement(p_lo, 0),
2177 action=IpsecSpdAction.PROTECT,
2179 sa_id=ObjIncrement(sa_id_2, 1),
2180 raddr_range=NetworkIncrement(ip_network(raddr_ip1)),
2184 def vpp_ipsec_show_all(node: dict) -> None:
2185 """Run "show ipsec all" debug CLI command.
2187 :param node: Node to run command on.
2190 PapiSocketExecutor.run_cli_cmd(node, "show ipsec all")
2193 def show_ipsec_security_association(node: dict) -> None:
2194 """Show IPSec security association.
2196 :param node: DUT node.
2199 cmd = "ipsec_sa_v5_dump"
2200 PapiSocketExecutor.dump_and_log(node, [cmd])
2203 def vpp_ipsec_flow_enable_rss(
2205 proto: str = "IPSEC_ESP",
2206 rss_type: str = "esp",
2207 function: str = "default",
2209 """Ipsec flow enable rss action.
2211 :param node: DUT node.
2212 :param proto: The flow protocol.
2213 :param rss_type: RSS type.
2214 :param function: RSS function.
2216 :type proto: IPsecProto.InputType
2219 :returns: flow_index.
2222 # The proto argument does not correspond to IPsecProto.
2223 # The allowed values come from src/vnet/ip/protocols.def
2224 # and we do not have a good enum for that yet.
2225 # FlowUtil.FlowType and FlowUtil.FlowProto are close,
2226 # but not exactly the same.
2228 # TODO: to be fixed to use full PAPI when it is ready in VPP
2230 f"test flow add src-ip any proto {proto} rss function"
2231 f" {function} rss types {rss_type}"
2233 stdout = PapiSocketExecutor.run_cli_cmd(node, cmd)
2234 flow_index = stdout.split()[1]
2239 def vpp_create_ipsec_flows_on_dut(
2240 node: dict, n_flows: int, rx_queues: int, spi_start: int, interface: str
2242 """Create mutiple ipsec flows and enable flows onto interface.
2244 :param node: DUT node.
2245 :param n_flows: Number of flows to create.
2246 :param rx_queues: NUmber of RX queues.
2247 :param spi_start: The start spi.
2248 :param interface: Name of the interface.
2252 :type rx_queues: int
2253 :type spi_start: int
2254 :type interface: str
2257 for i in range(0, n_flows):
2258 rx_queue = i % rx_queues
2260 flow_index = FlowUtil.vpp_create_ip4_ipsec_flow(
2261 node, "ESP", spi, "redirect-to-queue", value=rx_queue
2263 FlowUtil.vpp_flow_enable(node, interface, flow_index)