59374ab73fd4c29b349720423bc12b217b6f5c24
[csit.git] / resources / libraries / python / IPsecUtil.py
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:
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 """IPsec utilities library."""
16
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
23
24 from robot.libraries.BuiltIn import BuiltIn
25
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 (
30     InterfaceUtil,
31     InterfaceStatusFlags,
32 )
33 from resources.libraries.python.IPAddress import IPAddress
34 from resources.libraries.python.IPUtil import (
35     IPUtil,
36     IpDscp,
37     MPLS_LABEL_INVALID,
38     NetworkIncrement,
39 )
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
45
46
47 IPSEC_UDP_PORT_DEFAULT = 4500
48 IPSEC_REPLAY_WINDOW_DEFAULT = 64
49
50
51 def gen_key(length: int) -> bytes:
52     """Generate random string as a key.
53
54     :param length: Length of generated payload.
55     :type length: int
56     :returns: The generated payload.
57     :rtype: bytes
58     """
59     return "".join(choice(ascii_letters) for _ in range(length)).encode(
60         encoding="utf-8"
61     )
62
63
64 # TODO: Introduce a metaclass that adds .find and .InputType automatically?
65 class IpsecSpdAction(Enum):
66     """IPsec SPD actions.
67
68     Mirroring VPP: src/vnet/ipsec/ipsec_types.api enum ipsec_spd_action.
69     """
70
71     BYPASS = NONE = ("bypass", 0)
72     DISCARD = ("discard", 1)
73     RESOLVE = ("resolve", 2)
74     PROTECT = ("protect", 3)
75
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
79
80     def __str__(self) -> str:
81         return self.action_name
82
83     def __int__(self) -> int:
84         return self.action_int_repr
85
86
87 class CryptoAlg(Enum):
88     """Encryption algorithms."""
89
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)
95
96     def __init__(
97         self, alg_name: str, alg_int_repr: int, scapy_name: str, key_len: int
98     ):
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
103
104     # TODO: Investigate if __int__ works with PAPI. It was not enough for "if".
105     def __bool__(self):
106         """A shorthand to enable "if crypto_alg:" constructs."""
107         return self.alg_int_repr != 0
108
109
110 class IntegAlg(Enum):
111     """Integrity algorithm."""
112
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)
116
117     def __init__(
118         self, alg_name: str, alg_int_repr: int, scapy_name: str, key_len: int
119     ):
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
124
125     def __bool__(self):
126         """A shorthand to enable "if integ_alg:" constructs."""
127         return self.alg_int_repr != 0
128
129
130 # TODO: Base on Enum, so str values can be defined as in alg enums?
131 class IPsecProto(IntEnum):
132     """IPsec protocol.
133
134     Mirroring VPP: src/vnet/ipsec/ipsec_types.api enum ipsec_proto.
135     """
136
137     ESP = 50
138     AH = 51
139     NONE = 255
140
141     def __str__(self) -> str:
142         """Return string suitable for CLI commands.
143
144         None is not supported.
145
146         :returns: Lowercase name of the proto.
147         :rtype: str
148         :raises: ValueError if the numeric value is not recognized.
149         """
150         num = int(self)
151         if num == 50:
152             return "esp"
153         if num == 51:
154             return "ah"
155         raise ValueError(f"String form not defined for IPsecProto {num}")
156
157
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."""
161
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
176
177
178 class TunnelEncpaDecapFlags(IntEnum):
179     """Flags controlling tunnel behaviour."""
180
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
192
193
194 class TunnelMode(IntEnum):
195     """Tunnel modes."""
196
197     # point-to-point
198     TUNNEL_API_MODE_P2P = NONE = 0
199     # multi-point
200     TUNNEL_API_MODE_MP = 1
201
202
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?
209
210
211 class IPsecUtil:
212     """IPsec utilities."""
213
214     # The following 4 methods are Python one-liners,
215     # but they are useful when called as a Robot keyword.
216
217     @staticmethod
218     def get_crypto_alg_key_len(crypto_alg: CryptoAlg.InputType) -> int:
219         """Return encryption algorithm key length.
220
221         This is a Python one-liner, but useful when called as a Robot keyword.
222
223         :param crypto_alg: Encryption algorithm.
224         :type crypto_alg: CryptoAlg.InputType
225         :returns: Key length.
226         :rtype: int
227         """
228         return get_enum_instance(CryptoAlg, crypto_alg).key_len
229
230     @staticmethod
231     def get_crypto_alg_scapy_name(crypto_alg: CryptoAlg.InputType) -> str:
232         """Return encryption algorithm scapy name.
233
234         This is a Python one-liner, but useful when called as a Robot keyword.
235
236         :param crypto_alg: Encryption algorithm.
237         :type crypto_alg: CryptoAlg.InputType
238         :returns: Algorithm scapy name.
239         :rtype: str
240         """
241         return get_enum_instance(CryptoAlg, crypto_alg).scapy_name
242
243     # The below to keywords differ only by enum type conversion from str.
244     @staticmethod
245     def get_integ_alg_key_len(integ_alg: IntegAlg.InputType) -> int:
246         """Return integrity algorithm key length.
247
248         :param integ_alg: Integrity algorithm.
249         :type integ_alg: IntegAlg.InputType
250         :returns: Key length.
251         :rtype: int
252         """
253         return get_enum_instance(IntegAlg, integ_alg).key_len
254
255     @staticmethod
256     def get_integ_alg_scapy_name(integ_alg: IntegAlg.InputType) -> str:
257         """Return integrity algorithm scapy name.
258
259         :param integ_alg: Integrity algorithm.
260         :type integ_alg: IntegAlg.InputType
261         :returns: Algorithm scapy name.
262         :rtype: str
263         """
264         return get_enum_instance(IntegAlg, integ_alg).scapy_name
265
266     @staticmethod
267     def vpp_ipsec_select_backend(
268         node: dict, proto: IPsecProto.InputType, index: int = 1
269     ) -> None:
270         """Select IPsec backend.
271
272         :param node: VPP node to select IPsec backend on.
273         :param proto: IPsec protocol.
274         :param index: Backend index.
275         :type node: dict
276         :type proto: IPsecProto.InputType
277         :type index: int
278         :raises RuntimeError: If failed to select IPsec backend or if no API
279             reply received.
280         """
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)
287
288     @staticmethod
289     def vpp_ipsec_set_async_mode(node: dict, async_enable: int = 1) -> None:
290         """Set IPsec async mode on|off.
291
292         Unconditionally, attempt to switch crypto dispatch into polling mode.
293
294         :param node: VPP node to set IPsec async mode.
295         :param async_enable: Async mode on or off.
296         :type node: dict
297         :type async_enable: int
298         :raises RuntimeError: If failed to set IPsec async mode or if no API
299             reply received.
300         """
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)
309             try:
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.
315                 pass
316
317     @staticmethod
318     def vpp_ipsec_crypto_sw_scheduler_set_worker(
319         node: dict, workers: Iterable[int], crypto_enable: bool = False
320     ) -> None:
321         """Enable or disable crypto on specific vpp worker threads.
322
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.
326         :type node: dict
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.
331         """
332         for worker in workers:
333             cmd = "crypto_sw_scheduler_set_worker"
334             err_msg = (
335                 "Failed to disable/enable crypto for worker thread"
336                 f" on host {node['host']}"
337             )
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)
341
342     @staticmethod
343     def vpp_ipsec_crypto_sw_scheduler_set_worker_on_all_duts(
344         nodes: dict, crypto_enable: bool = False
345     ) -> None:
346         """Enable or disable crypto on specific vpp worker threads.
347
348         :param node: VPP node to enable or disable crypto for worker threads.
349         :param crypto_enable: Disable or enable crypto work.
350         :type node: dict
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.
354         """
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
359                 if not worker_cnt:
360                     return
361                 worker_ids = list()
362                 workers = BuiltIn().get_variable_value(
363                     f"${{{node_name}_cpu_dp}}"
364                 )
365                 for item in thread_data:
366                     if str(item.cpu_id) in workers.split(","):
367                         worker_ids.append(item.id)
368
369                 IPsecUtil.vpp_ipsec_crypto_sw_scheduler_set_worker(
370                     node, workers=worker_ids, crypto_enable=crypto_enable
371                 )
372
373     @staticmethod
374     def vpp_ipsec_add_sad_entry(
375         node: dict,
376         sad_id: int,
377         spi: int,
378         crypto_alg: CryptoAlg.InputType = None,
379         crypto_key: str = "",
380         integ_alg: IntegAlg.InputType = None,
381         integ_key: str = "",
382         tunnel_src: Optional[str] = None,
383         tunnel_dst: Optional[str] = None,
384     ) -> None:
385         """Create Security Association Database entry on the VPP node.
386
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.
398         :type node: dict
399         :type sad_id: int
400         :type spi: int
401         :type crypto_alg: CryptoAlg.InputType
402         :type crypto_key: str
403         :type integ_alg: IntegAlg.InputType
404         :type integ_key: str
405         :type tunnel_src: Optional[str]
406         :type tunnel_dst: Optional[str]
407         """
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)
416
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:
423                 flags = flags | int(
424                     IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6
425                 )
426         else:
427             src_addr = ""
428             dst_addr = ""
429
430         cmd = "ipsec_sad_entry_add_v2"
431         err_msg = (
432             "Failed to add Security Association Database entry"
433             f" on host {node['host']}"
434         )
435         sad_entry = dict(
436             sad_id=int(sad_id),
437             spi=int(spi),
438             crypto_algorithm=crypto_alg.alg_int_repr,
439             crypto_key=ckey,
440             integrity_algorithm=integ_alg.alg_int_repr,
441             integrity_key=ikey,
442             flags=flags,
443             tunnel=dict(
444                 src=str(src_addr),
445                 dst=str(dst_addr),
446                 table_id=0,
447                 encap_decap_flags=int(
448                     TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
449                 ),
450                 dscp=int(IpDscp.IP_API_DSCP_CS0),
451             ),
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,
456         )
457         args = dict(entry=sad_entry)
458         with PapiSocketExecutor(node) as papi_exec:
459             papi_exec.add(cmd, **args).get_reply(err_msg)
460
461     @staticmethod
462     def vpp_ipsec_add_sad_entries(
463         node: dict,
464         n_entries: int,
465         sad_id: int,
466         spi: int,
467         crypto_alg: CryptoAlg.InputType = None,
468         crypto_key: str = "",
469         integ_alg: IntegAlg.InputType = None,
470         integ_key: str = "",
471         tunnel_src: Optional[str] = None,
472         tunnel_dst: Optional[str] = None,
473         tunnel_addr_incr: bool = True,
474     ) -> None:
475         """Create multiple Security Association Database entries on VPP node.
476
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
480             id incremented by 1.
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
492             incremental step.
493         :type node: dict
494         :type n_entries: int
495         :type sad_id: int
496         :type spi: int
497         :type crypto_alg: CryptoAlg.InputType
498         :type crypto_key: str
499         :type integ_alg: IntegAlg.InputType
500         :type integ_key: str
501         :type tunnel_src: Optional[str]
502         :type tunnel_dst: Optional[str]
503         :type tunnel_addr_incr: bool
504         """
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)
514         else:
515             src_addr = ""
516             dst_addr = ""
517
518         if tunnel_addr_incr:
519             addr_incr = (
520                 1 << (128 - 96) if src_addr.version == 6 else 1 << (32 - 24)
521             )
522         else:
523             addr_incr = 0
524
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)
527
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:
532                 flags = flags | int(
533                     IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6
534                 )
535
536         cmd = "ipsec_sad_entry_add_v2"
537         err_msg = (
538             "Failed to add Security Association Database entry"
539             f" on host {node['host']}"
540         )
541
542         sad_entry = dict(
543             sad_id=int(sad_id),
544             spi=int(spi),
545             crypto_algorithm=crypto_alg.alg_int_repr,
546             crypto_key=ckey,
547             integrity_algorithm=integ_alg.alg_int_repr,
548             integrity_key=ikey,
549             flags=flags,
550             tunnel=dict(
551                 src=str(src_addr),
552                 dst=str(dst_addr),
553                 table_id=0,
554                 encap_decap_flags=int(
555                     TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
556                 ),
557                 dscp=int(IpDscp.IP_API_DSCP_CS0),
558             ),
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,
563         )
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
572                     else src_addr
573                 )
574                 args["entry"]["tunnel"]["dst"] = (
575                     str(dst_addr + i * addr_incr)
576                     if tunnel_src and tunnel_dst
577                     else dst_addr
578                 )
579                 history = bool(not 1 < i < n_entries - 2)
580                 papi_exec.add(cmd, history=history, **args)
581             papi_exec.get_replies(err_msg)
582
583     @staticmethod
584     def vpp_ipsec_set_ip_route(
585         node: dict,
586         n_tunnels: int,
587         tunnel_src: str,
588         traffic_addr: str,
589         tunnel_dst: str,
590         interface: str,
591         raddr_range: int,
592         dst_mac: Optional[str] = None,
593     ) -> None:
594         """Set IP address and route on interface.
595
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
604             in case of IPv6.
605         :param dst_mac: The MAC address of destination tunnels.
606         :type node: dict
607         :type n_tunnels: int
608         :type tunnel_src: str
609         :type traffic_addr: str
610         :type tunnel_dst: str
611         :type interface: str
612         :type raddr_range: int
613         :type dst_mac: Optional[str]
614         """
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
619         addr_incr = (
620             1 << (128 - raddr_range)
621             if tunnel_src.version == 6
622             else 1 << (32 - raddr_range)
623         )
624
625         cmd1 = "sw_interface_add_del_address"
626         args1 = dict(
627             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
628             is_add=True,
629             del_all=False,
630             prefix=None,
631         )
632         cmd2 = "ip_route_add_del"
633         args2 = dict(is_add=1, is_multipath=0, route=None)
634         cmd3 = "ip_neighbor_add_del"
635         args3 = dict(
636             is_add=True,
637             neighbor=dict(
638                 sw_if_index=Topology.get_interface_sw_index(node, interface),
639                 flags=0,
640                 mac_address=str(dst_mac),
641                 ip_address=None,
642             ),
643         )
644         err_msg = (
645             "Failed to configure IP addresses, IP routes and"
646             f" IP neighbor on interface {interface} on host {node['host']}"
647             if dst_mac
648             else "Failed to configure IP addresses and IP routes"
649             f" on interface {interface} on host {node['host']}"
650         )
651
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
657                 )
658                 args2["route"] = IPUtil.compose_vpp_route_structure(
659                     node,
660                     traffic_addr + i,
661                     prefix_len=tunnel_dst_prefix,
662                     interface=interface,
663                     gateway=tunnel_dst_addr,
664                 )
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)
668
669                 args2["route"] = IPUtil.compose_vpp_route_structure(
670                     node,
671                     tunnel_dst_addr,
672                     prefix_len=tunnel_dst_prefix,
673                     interface=interface,
674                     gateway=tunnel_dst_addr,
675                 )
676                 papi_exec.add(cmd2, history=history, **args2)
677
678                 if dst_mac:
679                     args3["neighbor"]["ip_address"] = ip_address(
680                         tunnel_dst_addr
681                     )
682                     papi_exec.add(cmd3, history=history, **args3)
683             papi_exec.get_replies(err_msg)
684
685     @staticmethod
686     def vpp_ipsec_add_spd(node: dict, spd_id: int) -> None:
687         """Create Security Policy Database on the VPP node.
688
689         :param node: VPP node to add SPD on.
690         :param spd_id: SPD ID.
691         :type node: dict
692         :type spd_id: int
693         """
694         cmd = "ipsec_spd_add_del"
695         err_msg = (
696             f"Failed to add Security Policy Database on host {node['host']}"
697         )
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)
701
702     @staticmethod
703     def vpp_ipsec_spd_add_if(
704         node: dict, spd_id: int, interface: Union[str, int]
705     ) -> None:
706         """Add interface to the Security Policy Database.
707
708         :param node: VPP node.
709         :param spd_id: SPD ID to add interface on.
710         :param interface: Interface name or sw_if_index.
711         :type node: dict
712         :type spd_id: int
713         :type interface: str or int
714         """
715         cmd = "ipsec_interface_add_del_spd"
716         err_msg = (
717             f"Failed to add interface {interface} to Security Policy"
718             f" Database {spd_id} on host {node['host']}"
719         )
720         args = dict(
721             is_add=True,
722             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
723             spd_id=int(spd_id),
724         )
725         with PapiSocketExecutor(node) as papi_exec:
726             papi_exec.add(cmd, **args).get_reply(err_msg)
727
728     @staticmethod
729     def vpp_ipsec_create_spds_match_nth_entry(
730         node: dict,
731         dir1_interface: Union[str, int],
732         dir2_interface: Union[str, int],
733         entry_amount: 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,
739     ) -> None:
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.
747
748         Action Protect is currently not supported.
749
750         :param node: VPP node to configured the SPDs and their entries.
751         :param dir1_interface: The interface in direction 1 where the entries
752             will be checked.
753         :param dir2_interface: The interface in direction 2 where the entries
754             will be checked.
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
765             outbound.
766         :param bidirectional: When True, will create SPDs in both directions
767             of traffic. When False, only in one direction.
768         :type node: dict
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
777         :type inbound: bool
778         :type bidirectional: bool
779         :raises NotImplementedError: When the action is IpsecSpdAction.PROTECT.
780         """
781         action = get_enum_instance(IpsecSpdAction, action)
782         if action == IpsecSpdAction.PROTECT:
783             raise NotImplementedError(
784                 "IPsec SPD action PROTECT is not supported."
785             )
786
787         spd_id_dir1 = 1
788         spd_id_dir2 = 2
789         matching_priority = 1
790
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(
795             node,
796             spd_id_dir1,
797             matching_priority,
798             action,
799             inbound=inbound,
800             laddr_range=local_addr_range,
801             raddr_range=remote_addr_range,
802         )
803
804         if bidirectional:
805             IPsecUtil.vpp_ipsec_add_spd(node, spd_id_dir2)
806             IPsecUtil.vpp_ipsec_spd_add_if(node, spd_id_dir2, dir2_interface)
807
808             # matching entry direction 2, the address ranges are switched
809             IPsecUtil.vpp_ipsec_add_spd_entry(
810                 node,
811                 spd_id_dir2,
812                 matching_priority,
813                 action,
814                 inbound=inbound,
815                 laddr_range=remote_addr_range,
816                 raddr_range=local_addr_range,
817             )
818
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)
826             )
827             next(no_match_local_addr_range)
828
829             no_match_remote_addr_range = NetworkIncrement(
830                 ip_network(remote_addr_range)
831             )
832             next(no_match_remote_addr_range)
833
834             # non-matching entries direction 1
835             IPsecUtil.vpp_ipsec_add_spd_entries(
836                 node,
837                 no_match_entry_amount,
838                 spd_id_dir1,
839                 ObjIncrement(matching_priority + 1, 1),
840                 action,
841                 inbound=inbound,
842                 laddr_range=no_match_local_addr_range,
843                 raddr_range=no_match_remote_addr_range,
844             )
845
846             if bidirectional:
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)
851                 )
852                 next(no_match_remote_addr_range)
853
854                 no_match_local_addr_range = NetworkIncrement(
855                     ip_network(remote_addr_range)
856                 )
857                 next(no_match_local_addr_range)
858                 # non-matching entries direction 2
859                 IPsecUtil.vpp_ipsec_add_spd_entries(
860                     node,
861                     no_match_entry_amount,
862                     spd_id_dir2,
863                     ObjIncrement(matching_priority + 1, 1),
864                     action,
865                     inbound=inbound,
866                     laddr_range=no_match_local_addr_range,
867                     raddr_range=no_match_remote_addr_range,
868                 )
869
870         IPsecUtil.vpp_ipsec_show_all(node)
871
872     @staticmethod
873     def _vpp_ipsec_add_spd_entry_internal(
874         executor: PapiSocketExecutor,
875         spd_id: int,
876         priority: int,
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,
886     ) -> None:
887         """Prepare to create Security Policy Database entry on the VPP node.
888
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.
892
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
898             outbound.
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
914         :type spd_id: int
915         :type priority: int
916         :type action: IpsecSpdAction.InputType
917         :type inbound: bool
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]
924         :type is_ipv6: bool
925         """
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"
930
931         if raddr_range is None:
932             raddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
933
934         local_net = ip_network(laddr_range, strict=False)
935         remote_net = ip_network(raddr_range, strict=False)
936
937         cmd = "ipsec_spd_entry_add_del_v2"
938
939         spd_entry = dict(
940             spd_id=int(spd_id),
941             priority=int(priority),
942             is_outbound=not inbound,
943             sa_id=int(sa_id) if sa_id else 0,
944             policy=int(action),
945             protocol=proto,
946             remote_address_start=IPAddress.create_ip_address_object(
947                 remote_net.network_address
948             ),
949             remote_address_stop=IPAddress.create_ip_address_object(
950                 remote_net.broadcast_address
951             ),
952             local_address_start=IPAddress.create_ip_address_object(
953                 local_net.network_address
954             ),
955             local_address_stop=IPAddress.create_ip_address_object(
956                 local_net.broadcast_address
957             ),
958             remote_port_start=(
959                 int(rport_range.split("-")[0]) if rport_range else 0
960             ),
961             remote_port_stop=(
962                 int(rport_range.split("-")[1]) if rport_range else 65535
963             ),
964             local_port_start=(
965                 int(lport_range.split("-")[0]) if lport_range else 0
966             ),
967             local_port_stop=(
968                 int(lport_range.split("-")[1]) if rport_range else 65535
969             ),
970         )
971         args = dict(is_add=True, entry=spd_entry)
972         executor.add(cmd, **args)
973
974     @staticmethod
975     def vpp_ipsec_add_spd_entry(
976         node: dict,
977         spd_id: int,
978         priority: int,
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,
988     ) -> None:
989         """Create Security Policy Database entry on the VPP node.
990
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
996             outbound.
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.
1011         :type node: dict
1012         :type spd_id: int
1013         :type priority: int
1014         :type action: IpsecSpdAction.InputType
1015         :type inbound: bool
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]
1022         :type is_ipv6: bool
1023         """
1024         action = get_enum_instance(IpsecSpdAction, action)
1025         proto = get_enum_instance(IPsecProto, proto)
1026         err_msg = (
1027             "Failed to add entry to Security Policy Database"
1028             f" {spd_id} on host {node['host']}"
1029         )
1030         with PapiSocketExecutor(node, is_async=True) as papi_exec:
1031             IPsecUtil._vpp_ipsec_add_spd_entry_internal(
1032                 papi_exec,
1033                 spd_id,
1034                 priority,
1035                 action,
1036                 inbound,
1037                 sa_id,
1038                 proto,
1039                 laddr_range,
1040                 raddr_range,
1041                 lport_range,
1042                 rport_range,
1043                 is_ipv6,
1044             )
1045             papi_exec.get_replies(err_msg)
1046
1047     @staticmethod
1048     def vpp_ipsec_add_spd_entries(
1049         node: dict,
1050         n_entries: int,
1051         spd_id: int,
1052         priority: Optional[ObjIncrement],
1053         action: IpsecSpdAction.InputType,
1054         inbound: bool,
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,
1062     ) -> None:
1063         """Create multiple Security Policy Database entries on the VPP node.
1064
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
1071             outbound.
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.
1086         :type node: dict
1087         :type n_entries: int
1088         :type spd_id: int
1089         :type priority: Optional[ObjIncrement]
1090         :type action: IpsecSpdAction.InputType
1091         :type inbound: bool
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]
1098         :type is_ipv6: bool
1099         """
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)
1105
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)
1109
1110         err_msg = (
1111             "Failed to add entry to Security Policy Database"
1112             f" {spd_id} on host {node['host']}"
1113         )
1114         with PapiSocketExecutor(node, is_async=True) as papi_exec:
1115             for _ in range(n_entries):
1116                 IPsecUtil._vpp_ipsec_add_spd_entry_internal(
1117                     papi_exec,
1118                     spd_id,
1119                     next(priority),
1120                     action,
1121                     inbound,
1122                     next(sa_id) if sa_id is not None else sa_id,
1123                     proto,
1124                     next(laddr_range),
1125                     next(raddr_range),
1126                     lport_range,
1127                     rport_range,
1128                     is_ipv6,
1129                 )
1130             papi_exec.get_replies(err_msg)
1131
1132     @staticmethod
1133     def _ipsec_create_loopback_dut1_papi(
1134         nodes: dict, tun_ips: dict, if1_key: str, if2_key: str
1135     ) -> int:
1136         """Create loopback interface and set IP address on VPP node 1 interface
1137         using PAPI.
1138
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.
1146         :type nodes: dict
1147         :type tun_ips: dict
1148         :type if1_key: str
1149         :type if2_key: str
1150         :returns: sw_if_idx Of the created loopback interface.
1151         :rtype: int
1152         """
1153         with PapiSocketExecutor(nodes["DUT1"]) as papi_exec:
1154             # Create loopback interface on DUT1, set it to up state
1155             cmd = "create_loopback_instance"
1156             args = dict(
1157                 mac_address=0,
1158                 is_specified=False,
1159                 user_instance=0,
1160             )
1161             err_msg = (
1162                 "Failed to create loopback interface"
1163                 f" on host {nodes['DUT1']['host']}"
1164             )
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"
1168             args = dict(
1169                 sw_if_index=loop_sw_if_idx,
1170                 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1171             )
1172             err_msg = (
1173                 "Failed to set loopback interface state up"
1174                 f" on host {nodes['DUT1']['host']}"
1175             )
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"
1179             args = dict(
1180                 sw_if_index=InterfaceUtil.get_interface_index(
1181                     nodes["DUT1"], if1_key
1182                 ),
1183                 is_add=True,
1184                 del_all=False,
1185                 prefix=IPUtil.create_prefix_object(
1186                     tun_ips["ip2"] - 1,
1187                     96 if tun_ips["ip2"].version == 6 else 24,
1188                 ),
1189             )
1190             err_msg = (
1191                 f"Failed to set IP address on interface {if1_key}"
1192                 f" on host {nodes['DUT1']['host']}"
1193             )
1194             papi_exec.add(cmd, **args).get_reply(err_msg)
1195             cmd2 = "ip_neighbor_add_del"
1196             args2 = dict(
1197                 is_add=1,
1198                 neighbor=dict(
1199                     sw_if_index=Topology.get_interface_sw_index(
1200                         nodes["DUT1"], if1_key
1201                     ),
1202                     flags=1,
1203                     mac_address=str(
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)
1207                     ),
1208                     ip_address=tun_ips["ip2"].compressed,
1209                 ),
1210             )
1211             err_msg = f"Failed to add IP neighbor on interface {if1_key}"
1212             papi_exec.add(cmd2, **args2).get_reply(err_msg)
1213
1214             return loop_sw_if_idx
1215
1216     @staticmethod
1217     def _ipsec_create_tunnel_interfaces_dut1_papi(
1218         nodes: dict,
1219         tun_ips: dict,
1220         if1_key: str,
1221         if2_key: str,
1222         n_tunnels: int,
1223         crypto_alg: CryptoAlg.InputType,
1224         integ_alg: IntegAlg.InputType,
1225         raddr_ip2: Union[IPv4Address, IPv6Address],
1226         addr_incr: int,
1227         spi_d: dict,
1228         existing_tunnels: int = 0,
1229     ) -> Tuple[List[bytes], List[bytes]]:
1230         """Create multiple IPsec tunnel interfaces on DUT1 node using PAPI.
1231
1232         Generate random keys and return them (so DUT2 or TG can decrypt).
1233
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.
1250         :type nodes: dict
1251         :type tun_ips: dict
1252         :type if1_key: str
1253         :type if2_key: str
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
1259         :type spi_d: dict
1260         :type existing_tunnels: int
1261         :returns: Generated ckeys and ikeys.
1262         :rtype: List[bytes], List[bytes]
1263         """
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
1269             )
1270         else:
1271             loop_sw_if_idx = InterfaceUtil.vpp_get_interface_sw_index(
1272                 nodes["DUT1"], "loop0"
1273             )
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"
1277             args = dict(
1278                 sw_if_index=loop_sw_if_idx,
1279                 is_add=True,
1280                 del_all=False,
1281                 prefix=None,
1282             )
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,
1287                 )
1288                 papi_exec.add(
1289                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1290                 )
1291             # Configure IPIP tunnel interfaces
1292             cmd = "ipip_add_tunnel"
1293             ipip_tunnel = dict(
1294                 instance=Constants.BITWISE_NON_ZERO,
1295                 src=None,
1296                 dst=None,
1297                 table_id=0,
1298                 flags=int(
1299                     TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1300                 ),
1301                 mode=int(TunnelMode.TUNNEL_API_MODE_P2P),
1302                 dscp=int(IpDscp.IP_API_DSCP_CS0),
1303             )
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
1309                 )
1310                 ipip_tunnel["dst"] = IPAddress.create_ip_address_object(
1311                     tun_ips["ip2"]
1312                 )
1313                 papi_exec.add(
1314                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1315                 )
1316             err_msg = (
1317                 "Failed to add IPIP tunnel interfaces on host"
1318                 f" {nodes['DUT1']['host']}"
1319             )
1320             ipip_tunnels.extend(
1321                 [
1322                     reply["sw_if_index"]
1323                     for reply in papi_exec.get_replies(err_msg)
1324                     if "sw_if_index" in reply
1325                 ]
1326             )
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
1334             sad_entry = dict(
1335                 sad_id=None,
1336                 spi=None,
1337                 protocol=IPsecProto.ESP,
1338                 crypto_algorithm=crypto_alg.alg_int_repr,
1339                 crypto_key=c_key,
1340                 integrity_algorithm=integ_alg.alg_int_repr,
1341                 integrity_key=i_key,
1342                 flags=common_flags,
1343                 tunnel=dict(
1344                     src=0,
1345                     dst=0,
1346                     table_id=0,
1347                     encap_decap_flags=int(
1348                         TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1349                     ),
1350                     dscp=int(IpDscp.IP_API_DSCP_CS0),
1351                 ),
1352                 salt=0,
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,
1356             )
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
1364
1365                 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1366                 sad_entry["crypto_key"]["data"] = ckeys[i]
1367                 if integ_alg:
1368                     sad_entry["integrity_key"]["length"] = len(ikeys[i])
1369                     sad_entry["integrity_key"]["data"] = ikeys[i]
1370                 papi_exec.add(
1371                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1372                 )
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
1378
1379                 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1380                 sad_entry["crypto_key"]["data"] = ckeys[i]
1381                 if integ_alg:
1382                     sad_entry["integrity_key"]["length"] = len(ikeys[i])
1383                     sad_entry["integrity_key"]["data"] = ikeys[i]
1384                 papi_exec.add(
1385                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1386                 )
1387             err_msg = (
1388                 "Failed to add IPsec SAD entries on host"
1389                 f" {nodes['DUT1']['host']}"
1390             )
1391             papi_exec.get_replies(err_msg)
1392             # Add protection for tunnels with IPSEC
1393             cmd = "ipsec_tunnel_protect_update"
1394             n_hop = dict(
1395                 address=0,
1396                 via_label=MPLS_LABEL_INVALID,
1397                 obj_id=Constants.BITWISE_NON_ZERO,
1398             )
1399             ipsec_tunnel_protect = dict(
1400                 sw_if_index=None, nh=n_hop, sa_out=None, n_sa_in=1, sa_in=None
1401             )
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]
1407                 papi_exec.add(
1408                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1409                 )
1410             err_msg = (
1411                 "Failed to add protection for tunnels with IPSEC"
1412                 f" on host {nodes['DUT1']['host']}"
1413             )
1414             papi_exec.get_replies(err_msg)
1415
1416             # Configure unnumbered interfaces
1417             cmd = "sw_interface_set_unnumbered"
1418             args = dict(
1419                 is_add=True,
1420                 sw_if_index=InterfaceUtil.get_interface_index(
1421                     nodes["DUT1"], if1_key
1422                 ),
1423                 unnumbered_sw_if_index=0,
1424             )
1425             for i in range(existing_tunnels, n_tunnels):
1426                 args["unnumbered_sw_if_index"] = ipip_tunnels[i]
1427                 papi_exec.add(
1428                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1429                 )
1430             # Set interfaces up
1431             cmd = "sw_interface_set_flags"
1432             args = dict(
1433                 sw_if_index=0,
1434                 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1435             )
1436             for i in range(existing_tunnels, n_tunnels):
1437                 args["sw_if_index"] = ipip_tunnels[i]
1438                 papi_exec.add(
1439                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1440                 )
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(
1446                     nodes["DUT1"],
1447                     (raddr_ip2 + i).compressed,
1448                     prefix_len=128 if raddr_ip2.version == 6 else 32,
1449                     interface=ipip_tunnels[i],
1450                 )
1451                 papi_exec.add(
1452                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1453                 )
1454             err_msg = f"Failed to add IP routes on host {nodes['DUT1']['host']}"
1455             papi_exec.get_replies(err_msg)
1456
1457         return ckeys, ikeys
1458
1459     @staticmethod
1460     def _ipsec_create_tunnel_interfaces_dut2_papi(
1461         nodes: dict,
1462         tun_ips: dict,
1463         if2_key: str,
1464         n_tunnels: int,
1465         crypto_alg: CryptoAlg.InputType,
1466         ckeys: Sequence[bytes],
1467         integ_alg: IntegAlg.InputType,
1468         ikeys: Sequence[bytes],
1469         raddr_ip1: Union[IPv4Address, IPv6Address],
1470         addr_incr: int,
1471         spi_d: dict,
1472         existing_tunnels: int = 0,
1473     ) -> None:
1474         """Create multiple IPsec tunnel interfaces on DUT2 node using PAPI.
1475
1476         This method accesses keys generated by DUT1 method
1477         and does not return anything.
1478
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.
1496         :type nodes: dict
1497         :type tun_ips: dict
1498         :type if2_key: str
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
1506         :type spi_d: dict
1507         :type existing_tunnels: int
1508         """
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"
1515                 args = dict(
1516                     sw_if_index=InterfaceUtil.get_interface_index(
1517                         nodes["DUT2"], if2_key
1518                     ),
1519                     is_add=True,
1520                     del_all=False,
1521                     prefix=IPUtil.create_prefix_object(
1522                         tun_ips["ip2"],
1523                         96 if tun_ips["ip2"].version == 6 else 24,
1524                     ),
1525                 )
1526                 err_msg = (
1527                     f"Failed to set IP address on interface {if2_key}"
1528                     f" on host {nodes['DUT2']['host']}"
1529                 )
1530                 papi_exec.add(cmd, **args).get_replies(err_msg)
1531             # Configure IPIP tunnel interfaces
1532             cmd = "ipip_add_tunnel"
1533             ipip_tunnel = dict(
1534                 instance=Constants.BITWISE_NON_ZERO,
1535                 src=None,
1536                 dst=None,
1537                 table_id=0,
1538                 flags=int(
1539                     TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1540                 ),
1541                 mode=int(TunnelMode.TUNNEL_API_MODE_P2P),
1542                 dscp=int(IpDscp.IP_API_DSCP_CS0),
1543             )
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(
1548                     tun_ips["ip2"]
1549                 )
1550                 ipip_tunnel["dst"] = IPAddress.create_ip_address_object(
1551                     tun_ips["ip1"] + i * addr_incr
1552                 )
1553                 papi_exec.add(
1554                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1555                 )
1556             err_msg = (
1557                 "Failed to add IPIP tunnel interfaces on host"
1558                 f" {nodes['DUT2']['host']}"
1559             )
1560             ipip_tunnels.extend(
1561                 [
1562                     reply["sw_if_index"]
1563                     for reply in papi_exec.get_replies(err_msg)
1564                     if "sw_if_index" in reply
1565                 ]
1566             )
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
1572             sad_entry = dict(
1573                 sad_id=None,
1574                 spi=None,
1575                 protocol=IPsecProto.ESP,
1576                 crypto_algorithm=crypto_alg.alg_int_repr,
1577                 crypto_key=c_key,
1578                 integrity_algorithm=integ_alg.alg_int_repr,
1579                 integrity_key=i_key,
1580                 flags=common_flags,
1581                 tunnel=dict(
1582                     src=0,
1583                     dst=0,
1584                     table_id=0,
1585                     encap_decap_flags=int(
1586                         TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1587                     ),
1588                     dscp=int(IpDscp.IP_API_DSCP_CS0),
1589                 ),
1590                 salt=0,
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,
1594             )
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
1602
1603                 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1604                 sad_entry["crypto_key"]["data"] = ckeys[i]
1605                 if integ_alg:
1606                     sad_entry["integrity_key"]["length"] = len(ikeys[i])
1607                     sad_entry["integrity_key"]["data"] = ikeys[i]
1608                 papi_exec.add(
1609                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1610                 )
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
1616
1617                 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1618                 sad_entry["crypto_key"]["data"] = ckeys[i]
1619                 if integ_alg:
1620                     sad_entry["integrity_key"]["length"] = len(ikeys[i])
1621                     sad_entry["integrity_key"]["data"] = ikeys[i]
1622                 papi_exec.add(
1623                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1624                 )
1625             err_msg = (
1626                 f"Failed to add IPsec SAD entries on host"
1627                 f" {nodes['DUT2']['host']}"
1628             )
1629             papi_exec.get_replies(err_msg)
1630             # Add protection for tunnels with IPSEC
1631             cmd = "ipsec_tunnel_protect_update"
1632             n_hop = dict(
1633                 address=0,
1634                 via_label=MPLS_LABEL_INVALID,
1635                 obj_id=Constants.BITWISE_NON_ZERO,
1636             )
1637             ipsec_tunnel_protect = dict(
1638                 sw_if_index=None, nh=n_hop, sa_out=None, n_sa_in=1, sa_in=None
1639             )
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]
1645                 papi_exec.add(
1646                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1647                 )
1648             err_msg = (
1649                 "Failed to add protection for tunnels with IPSEC"
1650                 f" on host {nodes['DUT2']['host']}"
1651             )
1652             papi_exec.get_replies(err_msg)
1653
1654             if not existing_tunnels:
1655                 # Configure IP route
1656                 cmd = "ip_route_add_del"
1657                 route = IPUtil.compose_vpp_route_structure(
1658                     nodes["DUT2"],
1659                     tun_ips["ip1"].compressed,
1660                     prefix_len=32 if tun_ips["ip1"].version == 6 else 8,
1661                     interface=if2_key,
1662                     gateway=(tun_ips["ip2"] - 1).compressed,
1663                 )
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"
1668             args = dict(
1669                 is_add=True,
1670                 sw_if_index=InterfaceUtil.get_interface_index(
1671                     nodes["DUT2"], if2_key
1672                 ),
1673                 unnumbered_sw_if_index=0,
1674             )
1675             for i in range(existing_tunnels, n_tunnels):
1676                 args["unnumbered_sw_if_index"] = ipip_tunnels[i]
1677                 papi_exec.add(
1678                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1679                 )
1680             # Set interfaces up
1681             cmd = "sw_interface_set_flags"
1682             args = dict(
1683                 sw_if_index=0,
1684                 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1685             )
1686             for i in range(existing_tunnels, n_tunnels):
1687                 args["sw_if_index"] = ipip_tunnels[i]
1688                 papi_exec.add(
1689                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1690                 )
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(
1696                     nodes["DUT1"],
1697                     (raddr_ip1 + i).compressed,
1698                     prefix_len=128 if raddr_ip1.version == 6 else 32,
1699                     interface=ipip_tunnels[i],
1700                 )
1701                 papi_exec.add(
1702                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1703                 )
1704             err_msg = f"Failed to add IP routes on host {nodes['DUT2']['host']}"
1705             papi_exec.get_replies(err_msg)
1706
1707     @staticmethod
1708     def vpp_ipsec_create_tunnel_interfaces(
1709         nodes: dict,
1710         tun_if1_ip_addr: str,
1711         tun_if2_ip_addr: str,
1712         if1_key: str,
1713         if2_key: str,
1714         n_tunnels: int,
1715         crypto_alg: CryptoAlg.InputType,
1716         integ_alg: IntegAlg.InputType,
1717         raddr_ip1: str,
1718         raddr_ip2: str,
1719         raddr_range: int,
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.
1724
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.
1728
1729         :param nodes: VPP nodes to create tunnel interfaces.
1730         :param tun_if1_ip_addr: VPP node 1 ipsec tunnel interface IPv4/IPv6
1731             address.
1732         :param tun_if2_ip_addr: VPP node 2 ipsec tunnel interface IPv4/IPv6
1733             address.
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.
1750         :type nodes: dict
1751         :type tun_if1_ip_addr: str
1752         :type tun_if2_ip_addr: str
1753         :type if1_key: str
1754         :type if2_key: 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]]
1765         """
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)
1771         tun_ips = dict(
1772             ip1=ip_address(tun_if1_ip_addr), ip2=ip_address(tun_if2_ip_addr)
1773         )
1774         raddr_ip1 = ip_address(raddr_ip1)
1775         raddr_ip2 = ip_address(raddr_ip2)
1776         addr_incr = (
1777             1 << (128 - raddr_range)
1778             if tun_ips["ip1"].version == 6
1779             else 1 << (32 - raddr_range)
1780         )
1781
1782         ckeys, ikeys = IPsecUtil._ipsec_create_tunnel_interfaces_dut1_papi(
1783             nodes,
1784             tun_ips,
1785             if1_key,
1786             if2_key,
1787             n_tunnels,
1788             crypto_alg,
1789             integ_alg,
1790             raddr_ip2,
1791             addr_incr,
1792             spi_d,
1793             existing_tunnels,
1794         )
1795         if "DUT2" in nodes.keys():
1796             IPsecUtil._ipsec_create_tunnel_interfaces_dut2_papi(
1797                 nodes,
1798                 tun_ips,
1799                 if2_key,
1800                 n_tunnels,
1801                 crypto_alg,
1802                 ckeys,
1803                 integ_alg,
1804                 ikeys,
1805                 raddr_ip1,
1806                 addr_incr,
1807                 spi_d,
1808                 existing_tunnels,
1809             )
1810
1811         if return_keys:
1812             return ckeys, ikeys, spi_d["spi_1"], spi_d["spi_2"]
1813         return None
1814
1815     @staticmethod
1816     def _create_ipsec_script_files(
1817         dut: str, instances: int
1818     ) -> List[TextIOWrapper]:
1819         """Create script files for configuring IPsec in containers
1820
1821         :param dut: DUT node on which to create the script files
1822         :param instances: number of containers on DUT node
1823         :type dut: str
1824         :type instances: int
1825         :returns: Created opened file handles.
1826         :rtype: List[TextIOWrapper]
1827         """
1828         scripts = []
1829         for cnf in range(0, instances):
1830             script_filename = (
1831                 f"/tmp/ipsec_create_tunnel_cnf_{dut}_{cnf + 1}.config"
1832             )
1833             scripts.append(open(script_filename, "w", encoding="utf-8"))
1834         return scripts
1835
1836     @staticmethod
1837     def _close_and_copy_ipsec_script_files(
1838         dut: str, nodes: dict, instances: int, scripts: Sequence[TextIOWrapper]
1839     ) -> None:
1840         """Close created scripts and copy them to containers
1841
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
1846         :type dut: str
1847         :type nodes: dict
1848         :type instances: int
1849         :type scripts: dict
1850         """
1851         for cnf in range(0, instances):
1852             scripts[cnf].close()
1853             script_filename = (
1854                 f"/tmp/ipsec_create_tunnel_cnf_{dut}_{cnf + 1}.config"
1855             )
1856             scp_node(nodes[dut], script_filename, script_filename)
1857
1858     @staticmethod
1859     def vpp_ipsec_add_multiple_tunnels(
1860         nodes: dict,
1861         interface1: Union[str, int],
1862         interface2: Union[str, int],
1863         n_tunnels: int,
1864         crypto_alg: CryptoAlg.InputType,
1865         integ_alg: IntegAlg.InputType,
1866         tunnel_ip1: str,
1867         tunnel_ip2: str,
1868         raddr_ip1: str,
1869         raddr_ip2: str,
1870         raddr_range: int,
1871         tunnel_addr_incr: bool = True,
1872     ) -> None:
1873         """Create multiple IPsec tunnels between two VPP nodes.
1874
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
1890             incremental step.
1891         :type nodes: dict
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
1903         """
1904         crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
1905         integ_alg = get_enum_instance(IntegAlg, integ_alg)
1906
1907         spd_id = 1
1908         p_hi = 100
1909         p_lo = 10
1910         sa_id_1 = 100000
1911         sa_id_2 = 200000
1912         spi_1 = 300000
1913         spi_2 = 400000
1914
1915         crypto_key = gen_key(crypto_alg.key_len).decode()
1916         integ_key = gen_key(integ_alg.key_len).decode()
1917         rmac = (
1918             Topology.get_interface_mac(nodes["DUT2"], interface2)
1919             if "DUT2" in nodes.keys()
1920             else Topology.get_interface_mac(nodes["TG"], interface2)
1921         )
1922         IPsecUtil.vpp_ipsec_set_ip_route(
1923             nodes["DUT1"],
1924             n_tunnels,
1925             tunnel_ip1,
1926             raddr_ip2,
1927             tunnel_ip2,
1928             interface1,
1929             raddr_range,
1930             rmac,
1931         )
1932
1933         IPsecUtil.vpp_ipsec_add_spd(nodes["DUT1"], spd_id)
1934         IPsecUtil.vpp_ipsec_spd_add_if(nodes["DUT1"], spd_id, interface1)
1935
1936         addr_incr = (
1937             1 << (128 - 96)
1938             if ip_address(tunnel_ip1).version == 6
1939             else 1 << (32 - 24)
1940         )
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
1944             ).with_prefixlen
1945             dut1_remote_outbound_range = ip_network(
1946                 f"{ip_address(tunnel_ip2) + i*(addr_incr**3)}/8", False
1947             ).with_prefixlen
1948
1949             IPsecUtil.vpp_ipsec_add_spd_entry(
1950                 nodes["DUT1"],
1951                 spd_id,
1952                 p_hi,
1953                 IpsecSpdAction.BYPASS,
1954                 inbound=False,
1955                 proto=IPsecProto.ESP,
1956                 laddr_range=dut1_local_outbound_range,
1957                 raddr_range=dut1_remote_outbound_range,
1958             )
1959             IPsecUtil.vpp_ipsec_add_spd_entry(
1960                 nodes["DUT1"],
1961                 spd_id,
1962                 p_hi,
1963                 IpsecSpdAction.BYPASS,
1964                 inbound=True,
1965                 proto=IPsecProto.ESP,
1966                 laddr_range=dut1_remote_outbound_range,
1967                 raddr_range=dut1_local_outbound_range,
1968             )
1969
1970         IPsecUtil.vpp_ipsec_add_sad_entries(
1971             nodes["DUT1"],
1972             n_tunnels,
1973             sa_id_1,
1974             spi_1,
1975             crypto_alg,
1976             crypto_key,
1977             integ_alg,
1978             integ_key,
1979             tunnel_ip1,
1980             tunnel_ip2,
1981             tunnel_addr_incr,
1982         )
1983
1984         IPsecUtil.vpp_ipsec_add_spd_entries(
1985             nodes["DUT1"],
1986             n_tunnels,
1987             spd_id,
1988             priority=ObjIncrement(p_lo, 0),
1989             action=IpsecSpdAction.PROTECT,
1990             inbound=False,
1991             sa_id=ObjIncrement(sa_id_1, 1),
1992             raddr_range=NetworkIncrement(ip_network(raddr_ip2)),
1993         )
1994
1995         IPsecUtil.vpp_ipsec_add_sad_entries(
1996             nodes["DUT1"],
1997             n_tunnels,
1998             sa_id_2,
1999             spi_2,
2000             crypto_alg,
2001             crypto_key,
2002             integ_alg,
2003             integ_key,
2004             tunnel_ip2,
2005             tunnel_ip1,
2006             tunnel_addr_incr,
2007         )
2008         IPsecUtil.vpp_ipsec_add_spd_entries(
2009             nodes["DUT1"],
2010             n_tunnels,
2011             spd_id,
2012             priority=ObjIncrement(p_lo, 0),
2013             action=IpsecSpdAction.PROTECT,
2014             inbound=True,
2015             sa_id=ObjIncrement(sa_id_2, 1),
2016             raddr_range=NetworkIncrement(ip_network(raddr_ip1)),
2017         )
2018
2019         if "DUT2" in nodes.keys():
2020             rmac = Topology.get_interface_mac(nodes["DUT1"], interface1)
2021             IPsecUtil.vpp_ipsec_set_ip_route(
2022                 nodes["DUT2"],
2023                 n_tunnels,
2024                 tunnel_ip2,
2025                 raddr_ip1,
2026                 tunnel_ip1,
2027                 interface2,
2028                 raddr_range,
2029                 rmac,
2030             )
2031
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
2037                 ).with_prefixlen
2038                 dut2_remote_outbound_range = ip_network(
2039                     f"{ip_address(tunnel_ip2) + i*(addr_incr**3)}/8", False
2040                 ).with_prefixlen
2041
2042                 IPsecUtil.vpp_ipsec_add_spd_entry(
2043                     nodes["DUT2"],
2044                     spd_id,
2045                     p_hi,
2046                     IpsecSpdAction.BYPASS,
2047                     inbound=False,
2048                     proto=IPsecProto.ESP,
2049                     laddr_range=dut2_remote_outbound_range,
2050                     raddr_range=dut2_local_outbound_range,
2051                 )
2052                 IPsecUtil.vpp_ipsec_add_spd_entry(
2053                     nodes["DUT2"],
2054                     spd_id,
2055                     p_hi,
2056                     IpsecSpdAction.BYPASS,
2057                     inbound=True,
2058                     proto=IPsecProto.ESP,
2059                     laddr_range=dut2_local_outbound_range,
2060                     raddr_range=dut2_remote_outbound_range,
2061                 )
2062
2063             IPsecUtil.vpp_ipsec_add_sad_entries(
2064                 nodes["DUT2"],
2065                 n_tunnels,
2066                 sa_id_1,
2067                 spi_1,
2068                 crypto_alg,
2069                 crypto_key,
2070                 integ_alg,
2071                 integ_key,
2072                 tunnel_ip1,
2073                 tunnel_ip2,
2074                 tunnel_addr_incr,
2075             )
2076             IPsecUtil.vpp_ipsec_add_spd_entries(
2077                 nodes["DUT2"],
2078                 n_tunnels,
2079                 spd_id,
2080                 priority=ObjIncrement(p_lo, 0),
2081                 action=IpsecSpdAction.PROTECT,
2082                 inbound=True,
2083                 sa_id=ObjIncrement(sa_id_1, 1),
2084                 raddr_range=NetworkIncrement(ip_network(raddr_ip2)),
2085             )
2086
2087             IPsecUtil.vpp_ipsec_add_sad_entries(
2088                 nodes["DUT2"],
2089                 n_tunnels,
2090                 sa_id_2,
2091                 spi_2,
2092                 crypto_alg,
2093                 crypto_key,
2094                 integ_alg,
2095                 integ_key,
2096                 tunnel_ip2,
2097                 tunnel_ip1,
2098                 tunnel_addr_incr,
2099             )
2100             IPsecUtil.vpp_ipsec_add_spd_entries(
2101                 nodes["DUT2"],
2102                 n_tunnels,
2103                 spd_id,
2104                 priority=ObjIncrement(p_lo, 0),
2105                 action=IpsecSpdAction.PROTECT,
2106                 inbound=False,
2107                 sa_id=ObjIncrement(sa_id_2, 1),
2108                 raddr_range=NetworkIncrement(ip_network(raddr_ip1)),
2109             )
2110
2111     @staticmethod
2112     def vpp_ipsec_show_all(node: dict) -> None:
2113         """Run "show ipsec all" debug CLI command.
2114
2115         :param node: Node to run command on.
2116         :type node: dict
2117         """
2118         PapiSocketExecutor.run_cli_cmd(node, "show ipsec all")
2119
2120     @staticmethod
2121     def show_ipsec_security_association(node: dict) -> None:
2122         """Show IPSec security association.
2123
2124         :param node: DUT node.
2125         :type node: dict
2126         """
2127         cmd = "ipsec_sa_v5_dump"
2128         PapiSocketExecutor.dump_and_log(node, [cmd])
2129
2130     @staticmethod
2131     def vpp_ipsec_flow_enable_rss(
2132         node: dict,
2133         proto: str = "IPSEC_ESP",
2134         rss_type: str = "esp",
2135         function: str = "default",
2136     ) -> int:
2137         """Ipsec flow enable rss action.
2138
2139         :param node: DUT node.
2140         :param proto: The flow protocol.
2141         :param rss_type: RSS type.
2142         :param function: RSS function.
2143         :type node: dict
2144         :type proto: IPsecProto.InputType
2145         :type rss_type: str
2146         :type function: str
2147         :returns: flow_index.
2148         :rtype: int
2149         """
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.
2154
2155         # TODO: to be fixed to use full PAPI when it is ready in VPP
2156         cmd = (
2157             f"test flow add src-ip any proto {proto} rss function"
2158             f" {function} rss types {rss_type}"
2159         )
2160         stdout = PapiSocketExecutor.run_cli_cmd(node, cmd)
2161         flow_index = stdout.split()[1]
2162
2163         return flow_index
2164
2165     @staticmethod
2166     def vpp_create_ipsec_flows_on_dut(
2167         node: dict, n_flows: int, rx_queues: int, spi_start: int, interface: str
2168     ) -> None:
2169         """Create mutiple ipsec flows and enable flows onto interface.
2170
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.
2176
2177         :type node: dict
2178         :type n_flows: int
2179         :type rx_queues: int
2180         :type spi_start: int
2181         :type interface: str
2182         """
2183
2184         for i in range(0, n_flows):
2185             rx_queue = i % rx_queues
2186             spi = spi_start + i
2187             flow_index = FlowUtil.vpp_create_ip4_ipsec_flow(
2188                 node, "ESP", spi, "redirect-to-queue", value=rx_queue
2189             )
2190             FlowUtil.vpp_flow_enable(node, interface, flow_index)