1abfee2cecfcd37d88b26ba85c785dcd0ed0f0dc
[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     API names and numeric enums from ipsec_types.api (enum ipsec_crypto_alg).
91
92     Lowercase names from ipsec_sa.h (foreach_ipsec_crypto_alg).
93
94     Scapy names are from:
95     https://github.com/secdev/scapy/blob/master/scapy/layers/ipsec.py
96
97     Key lengths from crypto.h
98     (foreach_crypto_cipher_alg and foreach_crypto_aead_alg).
99     """
100
101     NONE = ("none", 0, "none", 0)
102     AES_CBC_128 = ("aes-cbc-128", 1, "AES-CBC", 16)
103     AES_CBC_192 = ("aes-cbc-192", 2, "AES-CBC", 24)
104     AES_CBC_256 = ("aes-cbc-256", 3, "AES-CBC", 32)
105     AES_CTR_128 = ("aes-ctr-128", 4, "AES-CTR", 16)
106     AES_CTR_192 = ("aes-ctr-192", 5, "AES-CTR", 24)
107     AES_CTR_256 = ("aes-ctr-256", 6, "AES-CTR", 32)
108     AES_GCM_128 = ("aes-gcm-128", 7, "AES-GCM", 16)
109     AES_GCM_192 = ("aes-gcm-192", 8, "AES-GCM", 24)
110     AES_GCM_256 = ("aes-gcm-256", 9, "AES-GCM", 32)
111     DES_CBC = ("des-cbc", 10, "DES", 7)
112     _3DES_CBC = ("3des-cbc", 11, "3DES", 24)
113     CHACHA20_POLY1305 = ("chacha20-poly1305", 12, "CHACHA20-POLY1305", 32)
114     AES_NULL_GMAC_128 = ("aes-null-gmac-128", 13, "AES-NULL-GMAC", 16)
115     AES_NULL_GMAC_192 = ("aes-null-gmac-192", 14, "AES-NULL-GMAC", 24)
116     AES_NULL_GMAC_256 = ("aes-null-gmac-256", 15, "AES-NULL-GMAC", 32)
117
118     def __init__(
119         self, alg_name: str, alg_int_repr: int, scapy_name: str, key_len: int
120     ):
121         self.alg_name = alg_name
122         self.alg_int_repr = alg_int_repr
123         self.scapy_name = scapy_name
124         self.key_len = key_len
125
126     # TODO: Investigate if __int__ works with PAPI. It was not enough for "if".
127     def __bool__(self):
128         """A shorthand to enable "if crypto_alg:" constructs."""
129         return self.alg_int_repr != 0
130
131
132 class IntegAlg(Enum):
133     """Integrity algorithms.
134
135     API names and numeric enums from ipsec_types.api (enum ipsec_integ_alg).
136
137     Lowercase names from ipsec_sa.h (foreach_ipsec_integ_alg).
138
139     Scapy names are from:
140     https://github.com/secdev/scapy/blob/master/scapy/layers/ipsec.py
141     Among those, "AES-CMAC-96" may be a mismatch,
142     but there is no sha2-related item with "96" in it.
143
144     Key lengths seem to be given double of digest length
145     from crypto.h (foreach_crypto_link_async_alg),
146     but data there is not complete
147     (e.g. it does not distinguish sha-256-96 from sha-256-128).
148     The missing values are chosen based on last number (e.g. 192 / 4 = 48).
149     """
150
151     NONE = ("none", 0, "none", 0)
152     MD5_96 = ("md5-96", 1, "HMAC-MD5-96", 24)
153     SHA1_96 = ("sha1-96", 2, "HMAC-SHA1-96", 24)
154     SHA_256_96 = ("sha-256-96", 3, "AES-CMAC-96", 24)
155     SHA_256_128 = ("sha-256-128", 4, "SHA2-256-128", 32)
156     SHA_384_192 = ("sha-384-192", 5, "SHA2-384-192", 48)
157     SHA_512_256 = ("sha-512-256", 6, "SHA2-512-256", 64)
158
159     def __init__(
160         self, alg_name: str, alg_int_repr: int, scapy_name: str, key_len: int
161     ):
162         self.alg_name = alg_name
163         self.alg_int_repr = alg_int_repr
164         self.scapy_name = scapy_name
165         self.key_len = key_len
166
167     def __bool__(self):
168         """A shorthand to enable "if integ_alg:" constructs."""
169         return self.alg_int_repr != 0
170
171
172 # TODO: Base on Enum, so str values can be defined as in alg enums?
173 class IPsecProto(IntEnum):
174     """IPsec protocol.
175
176     Mirroring VPP: src/vnet/ipsec/ipsec_types.api enum ipsec_proto.
177     """
178
179     ESP = 50
180     AH = 51
181     NONE = 255
182
183     def __str__(self) -> str:
184         """Return string suitable for CLI commands.
185
186         None is not supported.
187
188         :returns: Lowercase name of the proto.
189         :rtype: str
190         :raises: ValueError if the numeric value is not recognized.
191         """
192         num = int(self)
193         if num == 50:
194             return "esp"
195         if num == 51:
196             return "ah"
197         raise ValueError(f"String form not defined for IPsecProto {num}")
198
199
200 # The rest of enums do not appear outside this file, so no no change needed yet.
201 class IPsecSadFlags(IntEnum):
202     """IPsec Security Association Database flags."""
203
204     IPSEC_API_SAD_FLAG_NONE = NONE = 0
205     # Enable extended sequence numbers
206     IPSEC_API_SAD_FLAG_USE_ESN = 0x01
207     # Enable Anti - replay
208     IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY = 0x02
209     # IPsec tunnel mode if non-zero, else transport mode
210     IPSEC_API_SAD_FLAG_IS_TUNNEL = 0x04
211     # IPsec tunnel mode is IPv6 if non-zero, else IPv4 tunnel
212     # only valid if is_tunnel is non-zero
213     IPSEC_API_SAD_FLAG_IS_TUNNEL_V6 = 0x08
214     # Enable UDP encapsulation for NAT traversal
215     IPSEC_API_SAD_FLAG_UDP_ENCAP = 0x10
216     # IPsec SA is or inbound traffic
217     IPSEC_API_SAD_FLAG_IS_INBOUND = 0x40
218
219
220 class TunnelEncpaDecapFlags(IntEnum):
221     """Flags controlling tunnel behaviour."""
222
223     TUNNEL_API_ENCAP_DECAP_FLAG_NONE = NONE = 0
224     # at encap, copy the DF bit of the payload into the tunnel header
225     TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_DF = 1
226     # at encap, set the DF bit in the tunnel header
227     TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_SET_DF = 2
228     # at encap, copy the DSCP bits of the payload into the tunnel header
229     TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_DSCP = 4
230     # at encap, copy the ECN bit of the payload into the tunnel header
231     TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_ECN = 8
232     # at decap, copy the ECN bit of the tunnel header into the payload
233     TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_SET_ECN = 16
234
235
236 class TunnelMode(IntEnum):
237     """Tunnel modes."""
238
239     # point-to-point
240     TUNNEL_API_MODE_P2P = NONE = 0
241     # multi-point
242     TUNNEL_API_MODE_MP = 1
243
244
245 # Derived types for type hints, based on capabilities of get_enum_instance.
246 IpsecSpdAction.InputType = Union[IpsecSpdAction, str, None]
247 CryptoAlg.InputType = Union[CryptoAlg, str, None]
248 IntegAlg.InputType = Union[IntegAlg, str, None]
249 IPsecProto.InputType = Union[IPsecProto, str, int, None]
250 # TODO: Introduce a metaclass that adds .find and .InputType automatically?
251
252
253 class IPsecUtil:
254     """IPsec utilities."""
255
256     # The following 4 methods are Python one-liners,
257     # but they are useful when called as a Robot keyword.
258
259     @staticmethod
260     def get_crypto_alg_key_len(crypto_alg: CryptoAlg.InputType) -> int:
261         """Return encryption algorithm key length.
262
263         This is a Python one-liner, but useful when called as a Robot keyword.
264
265         :param crypto_alg: Encryption algorithm.
266         :type crypto_alg: CryptoAlg.InputType
267         :returns: Key length.
268         :rtype: int
269         """
270         return get_enum_instance(CryptoAlg, crypto_alg).key_len
271
272     @staticmethod
273     def get_crypto_alg_scapy_name(crypto_alg: CryptoAlg.InputType) -> str:
274         """Return encryption algorithm scapy name.
275
276         This is a Python one-liner, but useful when called as a Robot keyword.
277
278         :param crypto_alg: Encryption algorithm.
279         :type crypto_alg: CryptoAlg.InputType
280         :returns: Algorithm scapy name.
281         :rtype: str
282         """
283         return get_enum_instance(CryptoAlg, crypto_alg).scapy_name
284
285     # The below to keywords differ only by enum type conversion from str.
286     @staticmethod
287     def get_integ_alg_key_len(integ_alg: IntegAlg.InputType) -> int:
288         """Return integrity algorithm key length.
289
290         :param integ_alg: Integrity algorithm.
291         :type integ_alg: IntegAlg.InputType
292         :returns: Key length.
293         :rtype: int
294         """
295         return get_enum_instance(IntegAlg, integ_alg).key_len
296
297     @staticmethod
298     def get_integ_alg_scapy_name(integ_alg: IntegAlg.InputType) -> str:
299         """Return integrity algorithm scapy name.
300
301         :param integ_alg: Integrity algorithm.
302         :type integ_alg: IntegAlg.InputType
303         :returns: Algorithm scapy name.
304         :rtype: str
305         """
306         return get_enum_instance(IntegAlg, integ_alg).scapy_name
307
308     @staticmethod
309     def vpp_ipsec_select_backend(
310         node: dict, proto: IPsecProto.InputType, index: int = 1
311     ) -> None:
312         """Select IPsec backend.
313
314         :param node: VPP node to select IPsec backend on.
315         :param proto: IPsec protocol.
316         :param index: Backend index.
317         :type node: dict
318         :type proto: IPsecProto.InputType
319         :type index: int
320         :raises RuntimeError: If failed to select IPsec backend or if no API
321             reply received.
322         """
323         proto = get_enum_instance(IPsecProto, proto)
324         cmd = "ipsec_select_backend"
325         err_msg = f"Failed to select IPsec backend on host {node['host']}"
326         args = dict(protocol=proto, index=index)
327         with PapiSocketExecutor(node) as papi_exec:
328             papi_exec.add(cmd, **args).get_reply(err_msg)
329
330     @staticmethod
331     def vpp_ipsec_set_async_mode(node: dict, async_enable: int = 1) -> None:
332         """Set IPsec async mode on|off.
333
334         Unconditionally, attempt to switch crypto dispatch into polling mode.
335
336         :param node: VPP node to set IPsec async mode.
337         :param async_enable: Async mode on or off.
338         :type node: dict
339         :type async_enable: int
340         :raises RuntimeError: If failed to set IPsec async mode or if no API
341             reply received.
342         """
343         with PapiSocketExecutor(node) as papi_exec:
344             cmd = "ipsec_set_async_mode"
345             err_msg = f"Failed to set IPsec async mode on host {node['host']}"
346             args = dict(async_enable=async_enable)
347             papi_exec.add(cmd, **args).get_reply(err_msg)
348             cmd = "crypto_set_async_dispatch_v2"
349             err_msg = "Failed to set dispatch mode."
350             args = dict(mode=0, adaptive=False)
351             try:
352                 papi_exec.add(cmd, **args).get_reply(err_msg)
353             except (AttributeError, RuntimeError):
354                 # Expected when VPP build does not have the _v2 yet
355                 # (after and before the first CRC check).
356                 # TODO: Fail here when testing of pre-23.10 builds is over.
357                 pass
358
359     @staticmethod
360     def vpp_ipsec_crypto_sw_scheduler_set_worker(
361         node: dict, workers: Iterable[int], crypto_enable: bool = False
362     ) -> None:
363         """Enable or disable crypto on specific vpp worker threads.
364
365         :param node: VPP node to enable or disable crypto for worker threads.
366         :param workers: List of VPP thread numbers.
367         :param crypto_enable: Disable or enable crypto work.
368         :type node: dict
369         :type workers: Iterable[int]
370         :type crypto_enable: bool
371         :raises RuntimeError: If failed to enable or disable crypto for worker
372             thread or if no API reply received.
373         """
374         for worker in workers:
375             cmd = "crypto_sw_scheduler_set_worker"
376             err_msg = (
377                 "Failed to disable/enable crypto for worker thread"
378                 f" on host {node['host']}"
379             )
380             args = dict(worker_index=worker - 1, crypto_enable=crypto_enable)
381             with PapiSocketExecutor(node) as papi_exec:
382                 papi_exec.add(cmd, **args).get_reply(err_msg)
383
384     @staticmethod
385     def vpp_ipsec_crypto_sw_scheduler_set_worker_on_all_duts(
386         nodes: dict, crypto_enable: bool = False
387     ) -> None:
388         """Enable or disable crypto on specific vpp worker threads.
389
390         :param node: VPP node to enable or disable crypto for worker threads.
391         :param crypto_enable: Disable or enable crypto work.
392         :type node: dict
393         :type crypto_enable: bool
394         :raises RuntimeError: If failed to enable or disable crypto for worker
395             thread or if no API reply received.
396         """
397         for node_name, node in nodes.items():
398             if node["type"] == NodeType.DUT:
399                 thread_data = VPPUtil.vpp_show_threads(node)
400                 worker_cnt = len(thread_data) - 1
401                 if not worker_cnt:
402                     return
403                 worker_ids = list()
404                 workers = BuiltIn().get_variable_value(
405                     f"${{{node_name}_cpu_dp}}"
406                 )
407                 for item in thread_data:
408                     if str(item.cpu_id) in workers.split(","):
409                         worker_ids.append(item.id)
410
411                 IPsecUtil.vpp_ipsec_crypto_sw_scheduler_set_worker(
412                     node, workers=worker_ids, crypto_enable=crypto_enable
413                 )
414
415     @staticmethod
416     def vpp_ipsec_add_sad_entry(
417         node: dict,
418         sad_id: int,
419         spi: int,
420         crypto_alg: CryptoAlg.InputType = None,
421         crypto_key: str = "",
422         integ_alg: IntegAlg.InputType = None,
423         integ_key: str = "",
424         tunnel_src: Optional[str] = None,
425         tunnel_dst: Optional[str] = None,
426     ) -> None:
427         """Create Security Association Database entry on the VPP node.
428
429         :param node: VPP node to add SAD entry on.
430         :param sad_id: SAD entry ID.
431         :param spi: Security Parameter Index of this SAD entry.
432         :param crypto_alg: The encryption algorithm name.
433         :param crypto_key: The encryption key string.
434         :param integ_alg: The integrity algorithm name.
435         :param integ_key: The integrity key string.
436         :param tunnel_src: Tunnel header source IPv4 or IPv6 address. If not
437             specified ESP transport mode is used.
438         :param tunnel_dst: Tunnel header destination IPv4 or IPv6 address. If
439             not specified ESP transport mode is used.
440         :type node: dict
441         :type sad_id: int
442         :type spi: int
443         :type crypto_alg: CryptoAlg.InputType
444         :type crypto_key: str
445         :type integ_alg: IntegAlg.InputType
446         :type integ_key: str
447         :type tunnel_src: Optional[str]
448         :type tunnel_dst: Optional[str]
449         """
450         crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
451         integ_alg = get_enum_instance(IntegAlg, integ_alg)
452         if isinstance(crypto_key, str):
453             crypto_key = crypto_key.encode(encoding="utf-8")
454         if isinstance(integ_key, str):
455             integ_key = integ_key.encode(encoding="utf-8")
456         ckey = dict(length=len(crypto_key), data=crypto_key)
457         ikey = dict(length=len(integ_key), data=integ_key if integ_key else 0)
458
459         flags = int(IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE)
460         if tunnel_src and tunnel_dst:
461             flags = flags | int(IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL)
462             src_addr = ip_address(tunnel_src)
463             dst_addr = ip_address(tunnel_dst)
464             if src_addr.version == 6:
465                 flags = flags | int(
466                     IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6
467                 )
468         else:
469             src_addr = ""
470             dst_addr = ""
471
472         cmd = "ipsec_sad_entry_add_v2"
473         err_msg = (
474             "Failed to add Security Association Database entry"
475             f" on host {node['host']}"
476         )
477         sad_entry = dict(
478             sad_id=int(sad_id),
479             spi=int(spi),
480             crypto_algorithm=crypto_alg.alg_int_repr,
481             crypto_key=ckey,
482             integrity_algorithm=integ_alg.alg_int_repr,
483             integrity_key=ikey,
484             flags=flags,
485             tunnel=dict(
486                 src=str(src_addr),
487                 dst=str(dst_addr),
488                 table_id=0,
489                 encap_decap_flags=int(
490                     TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
491                 ),
492                 dscp=int(IpDscp.IP_API_DSCP_CS0),
493             ),
494             protocol=IPsecProto.ESP,
495             udp_src_port=IPSEC_UDP_PORT_DEFAULT,
496             udp_dst_port=IPSEC_UDP_PORT_DEFAULT,
497             anti_replay_window_size=IPSEC_REPLAY_WINDOW_DEFAULT,
498         )
499         args = dict(entry=sad_entry)
500         with PapiSocketExecutor(node) as papi_exec:
501             papi_exec.add(cmd, **args).get_reply(err_msg)
502
503     @staticmethod
504     def vpp_ipsec_add_sad_entries(
505         node: dict,
506         n_entries: int,
507         sad_id: int,
508         spi: int,
509         crypto_alg: CryptoAlg.InputType = None,
510         crypto_key: str = "",
511         integ_alg: IntegAlg.InputType = None,
512         integ_key: str = "",
513         tunnel_src: Optional[str] = None,
514         tunnel_dst: Optional[str] = None,
515         tunnel_addr_incr: bool = True,
516     ) -> None:
517         """Create multiple Security Association Database entries on VPP node.
518
519         :param node: VPP node to add SAD entry on.
520         :param n_entries: Number of SAD entries to be created.
521         :param sad_id: First SAD entry ID. All subsequent SAD entries will have
522             id incremented by 1.
523         :param spi: Security Parameter Index of first SAD entry. All subsequent
524             SAD entries will have spi incremented by 1.
525         :param crypto_alg: The encryption algorithm name.
526         :param crypto_key: The encryption key string.
527         :param integ_alg: The integrity algorithm name.
528         :param integ_key: The integrity key string.
529         :param tunnel_src: Tunnel header source IPv4 or IPv6 address. If not
530             specified ESP transport mode is used.
531         :param tunnel_dst: Tunnel header destination IPv4 or IPv6 address. If
532             not specified ESP transport mode is used.
533         :param tunnel_addr_incr: Enable or disable tunnel IP address
534             incremental step.
535         :type node: dict
536         :type n_entries: int
537         :type sad_id: int
538         :type spi: int
539         :type crypto_alg: CryptoAlg.InputType
540         :type crypto_key: str
541         :type integ_alg: IntegAlg.InputType
542         :type integ_key: str
543         :type tunnel_src: Optional[str]
544         :type tunnel_dst: Optional[str]
545         :type tunnel_addr_incr: bool
546         """
547         crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
548         integ_alg = get_enum_instance(IntegAlg, integ_alg)
549         if isinstance(crypto_key, str):
550             crypto_key = crypto_key.encode(encoding="utf-8")
551         if isinstance(integ_key, str):
552             integ_key = integ_key.encode(encoding="utf-8")
553         if tunnel_src and tunnel_dst:
554             src_addr = ip_address(tunnel_src)
555             dst_addr = ip_address(tunnel_dst)
556         else:
557             src_addr = ""
558             dst_addr = ""
559
560         if tunnel_addr_incr:
561             addr_incr = (
562                 1 << (128 - 96) if src_addr.version == 6 else 1 << (32 - 24)
563             )
564         else:
565             addr_incr = 0
566
567         ckey = dict(length=len(crypto_key), data=crypto_key)
568         ikey = dict(length=len(integ_key), data=integ_key if integ_key else 0)
569
570         flags = int(IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE)
571         if tunnel_src and tunnel_dst:
572             flags = flags | int(IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL)
573             if src_addr.version == 6:
574                 flags = flags | int(
575                     IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6
576                 )
577
578         cmd = "ipsec_sad_entry_add_v2"
579         err_msg = (
580             "Failed to add Security Association Database entry"
581             f" on host {node['host']}"
582         )
583
584         sad_entry = dict(
585             sad_id=int(sad_id),
586             spi=int(spi),
587             crypto_algorithm=crypto_alg.alg_int_repr,
588             crypto_key=ckey,
589             integrity_algorithm=integ_alg.alg_int_repr,
590             integrity_key=ikey,
591             flags=flags,
592             tunnel=dict(
593                 src=str(src_addr),
594                 dst=str(dst_addr),
595                 table_id=0,
596                 encap_decap_flags=int(
597                     TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
598                 ),
599                 dscp=int(IpDscp.IP_API_DSCP_CS0),
600             ),
601             protocol=IPsecProto.ESP,
602             udp_src_port=IPSEC_UDP_PORT_DEFAULT,
603             udp_dst_port=IPSEC_UDP_PORT_DEFAULT,
604             anti_replay_window_size=IPSEC_REPLAY_WINDOW_DEFAULT,
605         )
606         args = dict(entry=sad_entry)
607         with PapiSocketExecutor(node, is_async=True) as papi_exec:
608             for i in range(n_entries):
609                 args["entry"]["sad_id"] = int(sad_id) + i
610                 args["entry"]["spi"] = int(spi) + i
611                 args["entry"]["tunnel"]["src"] = (
612                     str(src_addr + i * addr_incr)
613                     if tunnel_src and tunnel_dst
614                     else src_addr
615                 )
616                 args["entry"]["tunnel"]["dst"] = (
617                     str(dst_addr + i * addr_incr)
618                     if tunnel_src and tunnel_dst
619                     else dst_addr
620                 )
621                 history = bool(not 1 < i < n_entries - 2)
622                 papi_exec.add(cmd, history=history, **args)
623             papi_exec.get_replies(err_msg)
624
625     @staticmethod
626     def vpp_ipsec_set_ip_route(
627         node: dict,
628         n_tunnels: int,
629         tunnel_src: str,
630         traffic_addr: str,
631         tunnel_dst: str,
632         interface: str,
633         raddr_range: int,
634         dst_mac: Optional[str] = None,
635     ) -> None:
636         """Set IP address and route on interface.
637
638         :param node: VPP node to add config on.
639         :param n_tunnels: Number of tunnels to create.
640         :param tunnel_src: Tunnel header source IPv4 or IPv6 address.
641         :param traffic_addr: Traffic destination IP address to route.
642         :param tunnel_dst: Tunnel header destination IPv4 or IPv6 address.
643         :param interface: Interface key on node 1.
644         :param raddr_range: Mask specifying range of Policy selector Remote IP
645             addresses. Valid values are from 1 to 32 in case of IPv4 and to 128
646             in case of IPv6.
647         :param dst_mac: The MAC address of destination tunnels.
648         :type node: dict
649         :type n_tunnels: int
650         :type tunnel_src: str
651         :type traffic_addr: str
652         :type tunnel_dst: str
653         :type interface: str
654         :type raddr_range: int
655         :type dst_mac: Optional[str]
656         """
657         tunnel_src = ip_address(tunnel_src)
658         tunnel_dst = ip_address(tunnel_dst)
659         traffic_addr = ip_address(traffic_addr)
660         tunnel_dst_prefix = 128 if tunnel_dst.version == 6 else 32
661         addr_incr = (
662             1 << (128 - raddr_range)
663             if tunnel_src.version == 6
664             else 1 << (32 - raddr_range)
665         )
666
667         cmd1 = "sw_interface_add_del_address"
668         args1 = dict(
669             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
670             is_add=True,
671             del_all=False,
672             prefix=None,
673         )
674         cmd2 = "ip_route_add_del"
675         args2 = dict(is_add=1, is_multipath=0, route=None)
676         cmd3 = "ip_neighbor_add_del"
677         args3 = dict(
678             is_add=True,
679             neighbor=dict(
680                 sw_if_index=Topology.get_interface_sw_index(node, interface),
681                 flags=0,
682                 mac_address=str(dst_mac),
683                 ip_address=None,
684             ),
685         )
686         err_msg = (
687             "Failed to configure IP addresses, IP routes and"
688             f" IP neighbor on interface {interface} on host {node['host']}"
689             if dst_mac
690             else "Failed to configure IP addresses and IP routes"
691             f" on interface {interface} on host {node['host']}"
692         )
693
694         with PapiSocketExecutor(node, is_async=True) as papi_exec:
695             for i in range(n_tunnels):
696                 tunnel_dst_addr = tunnel_dst + i * addr_incr
697                 args1["prefix"] = IPUtil.create_prefix_object(
698                     tunnel_src + i * addr_incr, raddr_range
699                 )
700                 args2["route"] = IPUtil.compose_vpp_route_structure(
701                     node,
702                     traffic_addr + i,
703                     prefix_len=tunnel_dst_prefix,
704                     interface=interface,
705                     gateway=tunnel_dst_addr,
706                 )
707                 history = bool(not 1 < i < n_tunnels - 2)
708                 papi_exec.add(cmd1, history=history, **args1)
709                 papi_exec.add(cmd2, history=history, **args2)
710
711                 args2["route"] = IPUtil.compose_vpp_route_structure(
712                     node,
713                     tunnel_dst_addr,
714                     prefix_len=tunnel_dst_prefix,
715                     interface=interface,
716                     gateway=tunnel_dst_addr,
717                 )
718                 papi_exec.add(cmd2, history=history, **args2)
719
720                 if dst_mac:
721                     args3["neighbor"]["ip_address"] = ip_address(
722                         tunnel_dst_addr
723                     )
724                     papi_exec.add(cmd3, history=history, **args3)
725             papi_exec.get_replies(err_msg)
726
727     @staticmethod
728     def vpp_ipsec_add_spd(node: dict, spd_id: int) -> None:
729         """Create Security Policy Database on the VPP node.
730
731         :param node: VPP node to add SPD on.
732         :param spd_id: SPD ID.
733         :type node: dict
734         :type spd_id: int
735         """
736         cmd = "ipsec_spd_add_del"
737         err_msg = (
738             f"Failed to add Security Policy Database on host {node['host']}"
739         )
740         args = dict(is_add=True, spd_id=int(spd_id))
741         with PapiSocketExecutor(node) as papi_exec:
742             papi_exec.add(cmd, **args).get_reply(err_msg)
743
744     @staticmethod
745     def vpp_ipsec_spd_add_if(
746         node: dict, spd_id: int, interface: Union[str, int]
747     ) -> None:
748         """Add interface to the Security Policy Database.
749
750         :param node: VPP node.
751         :param spd_id: SPD ID to add interface on.
752         :param interface: Interface name or sw_if_index.
753         :type node: dict
754         :type spd_id: int
755         :type interface: str or int
756         """
757         cmd = "ipsec_interface_add_del_spd"
758         err_msg = (
759             f"Failed to add interface {interface} to Security Policy"
760             f" Database {spd_id} on host {node['host']}"
761         )
762         args = dict(
763             is_add=True,
764             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
765             spd_id=int(spd_id),
766         )
767         with PapiSocketExecutor(node) as papi_exec:
768             papi_exec.add(cmd, **args).get_reply(err_msg)
769
770     @staticmethod
771     def vpp_ipsec_create_spds_match_nth_entry(
772         node: dict,
773         dir1_interface: Union[str, int],
774         dir2_interface: Union[str, int],
775         entry_amount: int,
776         local_addr_range: Union[str, IPv4Address, IPv6Address],
777         remote_addr_range: Union[str, IPv4Address, IPv6Address],
778         action: IpsecSpdAction.InputType = IpsecSpdAction.BYPASS,
779         inbound: bool = False,
780         bidirectional: bool = True,
781     ) -> None:
782         """Create one matching SPD entry for inbound or outbound traffic on
783         a DUT for each traffic direction and also create entry_amount - 1
784         non-matching SPD entries. Create a Security Policy Database on each
785         outbound interface where these entries will be configured.
786         The matching SPD entry will have the lowest priority, input action and
787         will be configured to match the IP flow. The non-matching entries will
788         be the same, except with higher priority and non-matching IP flows.
789
790         Action Protect is currently not supported.
791
792         :param node: VPP node to configured the SPDs and their entries.
793         :param dir1_interface: The interface in direction 1 where the entries
794             will be checked.
795         :param dir2_interface: The interface in direction 2 where the entries
796             will be checked.
797         :param entry_amount: The number of SPD entries to configure. If
798             entry_amount == 1, no non-matching entries will be configured.
799         :param local_addr_range: Matching local address range in direction 1
800             in format IP/prefix or IP/mask. If no mask is provided, it's
801             considered to be /32.
802         :param remote_addr_range: Matching remote address range in
803             direction 1 in format IP/prefix or IP/mask. If no mask is
804             provided, it's considered to be /32.
805         :param action: IPsec SPD action.
806         :param inbound: If True policy is for inbound traffic, otherwise
807             outbound.
808         :param bidirectional: When True, will create SPDs in both directions
809             of traffic. When False, only in one direction.
810         :type node: dict
811         :type dir1_interface: Union[str, int]
812         :type dir2_interface: Union[str, int]
813         :type entry_amount: int
814         :type local_addr_range:
815             Union[str, IPv4Address, IPv6Address]
816         :type remote_addr_range:
817             Union[str, IPv4Address, IPv6Address]
818         :type action: IpsecSpdAction.InputType
819         :type inbound: bool
820         :type bidirectional: bool
821         :raises NotImplementedError: When the action is IpsecSpdAction.PROTECT.
822         """
823         action = get_enum_instance(IpsecSpdAction, action)
824         if action == IpsecSpdAction.PROTECT:
825             raise NotImplementedError(
826                 "IPsec SPD action PROTECT is not supported."
827             )
828
829         spd_id_dir1 = 1
830         spd_id_dir2 = 2
831         matching_priority = 1
832
833         IPsecUtil.vpp_ipsec_add_spd(node, spd_id_dir1)
834         IPsecUtil.vpp_ipsec_spd_add_if(node, spd_id_dir1, dir1_interface)
835         # matching entry direction 1
836         IPsecUtil.vpp_ipsec_add_spd_entry(
837             node,
838             spd_id_dir1,
839             matching_priority,
840             action,
841             inbound=inbound,
842             laddr_range=local_addr_range,
843             raddr_range=remote_addr_range,
844         )
845
846         if bidirectional:
847             IPsecUtil.vpp_ipsec_add_spd(node, spd_id_dir2)
848             IPsecUtil.vpp_ipsec_spd_add_if(node, spd_id_dir2, dir2_interface)
849
850             # matching entry direction 2, the address ranges are switched
851             IPsecUtil.vpp_ipsec_add_spd_entry(
852                 node,
853                 spd_id_dir2,
854                 matching_priority,
855                 action,
856                 inbound=inbound,
857                 laddr_range=remote_addr_range,
858                 raddr_range=local_addr_range,
859             )
860
861         # non-matching entries
862         no_match_entry_amount = entry_amount - 1
863         if no_match_entry_amount > 0:
864             # create a NetworkIncrement representation of the network,
865             # then skip the matching network
866             no_match_local_addr_range = NetworkIncrement(
867                 ip_network(local_addr_range)
868             )
869             next(no_match_local_addr_range)
870
871             no_match_remote_addr_range = NetworkIncrement(
872                 ip_network(remote_addr_range)
873             )
874             next(no_match_remote_addr_range)
875
876             # non-matching entries direction 1
877             IPsecUtil.vpp_ipsec_add_spd_entries(
878                 node,
879                 no_match_entry_amount,
880                 spd_id_dir1,
881                 ObjIncrement(matching_priority + 1, 1),
882                 action,
883                 inbound=inbound,
884                 laddr_range=no_match_local_addr_range,
885                 raddr_range=no_match_remote_addr_range,
886             )
887
888             if bidirectional:
889                 # reset the networks so that we're using a unified config
890                 # the address ranges are switched
891                 no_match_remote_addr_range = NetworkIncrement(
892                     ip_network(local_addr_range)
893                 )
894                 next(no_match_remote_addr_range)
895
896                 no_match_local_addr_range = NetworkIncrement(
897                     ip_network(remote_addr_range)
898                 )
899                 next(no_match_local_addr_range)
900                 # non-matching entries direction 2
901                 IPsecUtil.vpp_ipsec_add_spd_entries(
902                     node,
903                     no_match_entry_amount,
904                     spd_id_dir2,
905                     ObjIncrement(matching_priority + 1, 1),
906                     action,
907                     inbound=inbound,
908                     laddr_range=no_match_local_addr_range,
909                     raddr_range=no_match_remote_addr_range,
910                 )
911
912         IPsecUtil.vpp_ipsec_show_all(node)
913
914     @staticmethod
915     def _vpp_ipsec_add_spd_entry_internal(
916         executor: PapiSocketExecutor,
917         spd_id: int,
918         priority: int,
919         action: IpsecSpdAction.InputType,
920         inbound: bool = True,
921         sa_id: Optional[int] = None,
922         proto: IPsecProto.InputType = None,
923         laddr_range: Optional[str] = None,
924         raddr_range: Optional[str] = None,
925         lport_range: Optional[str] = None,
926         rport_range: Optional[str] = None,
927         is_ipv6: bool = False,
928     ) -> None:
929         """Prepare to create Security Policy Database entry on the VPP node.
930
931         This just adds one more command to the executor.
932         The call site shall get replies once all entries are added,
933         to get speed benefit from async PAPI.
934
935         :param executor: Open PAPI executor (async handling) to add commands to.
936         :param spd_id: SPD ID to add entry on.
937         :param priority: SPD entry priority, higher number = higher priority.
938         :param action: IPsec SPD action.
939         :param inbound: If True policy is for inbound traffic, otherwise
940             outbound.
941         :param sa_id: SAD entry ID for action IpsecSpdAction.PROTECT.
942         :param proto: Policy selector next layer protocol number.
943         :param laddr_range: Policy selector local IPv4 or IPv6 address range
944             in format IP/prefix or IP/mask. If no mask is provided,
945             it's considered to be /32.
946         :param raddr_range: Policy selector remote IPv4 or IPv6 address range
947             in format IP/prefix or IP/mask. If no mask is provided,
948             it's considered to be /32.
949         :param lport_range: Policy selector local TCP/UDP port range in format
950             <port_start>-<port_end>.
951         :param rport_range: Policy selector remote TCP/UDP port range in format
952             <port_start>-<port_end>.
953         :param is_ipv6: True in case of IPv6 policy when IPv6 address range is
954             not defined so it will default to address ::/0, otherwise False.
955         :type executor: PapiSocketExecutor
956         :type spd_id: int
957         :type priority: int
958         :type action: IpsecSpdAction.InputType
959         :type inbound: bool
960         :type sa_id: Optional[int]
961         :type proto: IPsecProto.InputType
962         :type laddr_range: Optional[str]
963         :type raddr_range: Optional[str]
964         :type lport_range: Optional[str]
965         :type rport_range: Optional[str]
966         :type is_ipv6: bool
967         """
968         action = get_enum_instance(IpsecSpdAction, action)
969         proto = get_enum_instance(IPsecProto, proto)
970         if laddr_range is None:
971             laddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
972
973         if raddr_range is None:
974             raddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
975
976         local_net = ip_network(laddr_range, strict=False)
977         remote_net = ip_network(raddr_range, strict=False)
978
979         cmd = "ipsec_spd_entry_add_del_v2"
980
981         spd_entry = dict(
982             spd_id=int(spd_id),
983             priority=int(priority),
984             is_outbound=not inbound,
985             sa_id=int(sa_id) if sa_id else 0,
986             policy=int(action),
987             protocol=proto,
988             remote_address_start=IPAddress.create_ip_address_object(
989                 remote_net.network_address
990             ),
991             remote_address_stop=IPAddress.create_ip_address_object(
992                 remote_net.broadcast_address
993             ),
994             local_address_start=IPAddress.create_ip_address_object(
995                 local_net.network_address
996             ),
997             local_address_stop=IPAddress.create_ip_address_object(
998                 local_net.broadcast_address
999             ),
1000             remote_port_start=(
1001                 int(rport_range.split("-")[0]) if rport_range else 0
1002             ),
1003             remote_port_stop=(
1004                 int(rport_range.split("-")[1]) if rport_range else 65535
1005             ),
1006             local_port_start=(
1007                 int(lport_range.split("-")[0]) if lport_range else 0
1008             ),
1009             local_port_stop=(
1010                 int(lport_range.split("-")[1]) if rport_range else 65535
1011             ),
1012         )
1013         args = dict(is_add=True, entry=spd_entry)
1014         executor.add(cmd, **args)
1015
1016     @staticmethod
1017     def vpp_ipsec_add_spd_entry(
1018         node: dict,
1019         spd_id: int,
1020         priority: int,
1021         action: IpsecSpdAction.InputType,
1022         inbound: bool = True,
1023         sa_id: Optional[int] = None,
1024         proto: IPsecProto.InputType = None,
1025         laddr_range: Optional[str] = None,
1026         raddr_range: Optional[str] = None,
1027         lport_range: Optional[str] = None,
1028         rport_range: Optional[str] = None,
1029         is_ipv6: bool = False,
1030     ) -> None:
1031         """Create Security Policy Database entry on the VPP node.
1032
1033         :param node: VPP node to add SPD entry on.
1034         :param spd_id: SPD ID to add entry on.
1035         :param priority: SPD entry priority, higher number = higher priority.
1036         :param action: IPsec SPD action.
1037         :param inbound: If True policy is for inbound traffic, otherwise
1038             outbound.
1039         :param sa_id: SAD entry ID for action IpsecSpdAction.PROTECT.
1040         :param proto: Policy selector next layer protocol number.
1041         :param laddr_range: Policy selector local IPv4 or IPv6 address range
1042             in format IP/prefix or IP/mask. If no mask is provided,
1043             it's considered to be /32.
1044         :param raddr_range: Policy selector remote IPv4 or IPv6 address range
1045             in format IP/prefix or IP/mask. If no mask is provided,
1046             it's considered to be /32.
1047         :param lport_range: Policy selector local TCP/UDP port range in format
1048             <port_start>-<port_end>.
1049         :param rport_range: Policy selector remote TCP/UDP port range in format
1050             <port_start>-<port_end>.
1051         :param is_ipv6: True in case of IPv6 policy when IPv6 address range is
1052             not defined so it will default to address ::/0, otherwise False.
1053         :type node: dict
1054         :type spd_id: int
1055         :type priority: int
1056         :type action: IpsecSpdAction.InputType
1057         :type inbound: bool
1058         :type sa_id: Optional[int]
1059         :type proto: IPsecProto.InputType
1060         :type laddr_range: Optional[str]
1061         :type raddr_range: Optional[str]
1062         :type lport_range: Optional[str]
1063         :type rport_range: Optional[str]
1064         :type is_ipv6: bool
1065         """
1066         action = get_enum_instance(IpsecSpdAction, action)
1067         proto = get_enum_instance(IPsecProto, proto)
1068         err_msg = (
1069             "Failed to add entry to Security Policy Database"
1070             f" {spd_id} on host {node['host']}"
1071         )
1072         with PapiSocketExecutor(node, is_async=True) as papi_exec:
1073             IPsecUtil._vpp_ipsec_add_spd_entry_internal(
1074                 papi_exec,
1075                 spd_id,
1076                 priority,
1077                 action,
1078                 inbound,
1079                 sa_id,
1080                 proto,
1081                 laddr_range,
1082                 raddr_range,
1083                 lport_range,
1084                 rport_range,
1085                 is_ipv6,
1086             )
1087             papi_exec.get_replies(err_msg)
1088
1089     @staticmethod
1090     def vpp_ipsec_add_spd_entries(
1091         node: dict,
1092         n_entries: int,
1093         spd_id: int,
1094         priority: Optional[ObjIncrement],
1095         action: IpsecSpdAction.InputType,
1096         inbound: bool,
1097         sa_id: Optional[ObjIncrement] = None,
1098         proto: IPsecProto.InputType = None,
1099         laddr_range: Optional[NetworkIncrement] = None,
1100         raddr_range: Optional[NetworkIncrement] = None,
1101         lport_range: Optional[str] = None,
1102         rport_range: Optional[str] = None,
1103         is_ipv6: bool = False,
1104     ) -> None:
1105         """Create multiple Security Policy Database entries on the VPP node.
1106
1107         :param node: VPP node to add SPD entries on.
1108         :param n_entries: Number of SPD entries to be added.
1109         :param spd_id: SPD ID to add entries on.
1110         :param priority: SPD entries priority, higher number = higher priority.
1111         :param action: IPsec SPD action.
1112         :param inbound: If True policy is for inbound traffic, otherwise
1113             outbound.
1114         :param sa_id: SAD entry ID for action IpsecSpdAction.PROTECT.
1115         :param proto: Policy selector next layer protocol number.
1116         :param laddr_range: Policy selector local IPv4 or IPv6 address range
1117             in format IP/prefix or IP/mask. If no mask is provided,
1118             it's considered to be /32.
1119         :param raddr_range: Policy selector remote IPv4 or IPv6 address range
1120             in format IP/prefix or IP/mask. If no mask is provided,
1121             it's considered to be /32.
1122         :param lport_range: Policy selector local TCP/UDP port range in format
1123             <port_start>-<port_end>.
1124         :param rport_range: Policy selector remote TCP/UDP port range in format
1125             <port_start>-<port_end>.
1126         :param is_ipv6: True in case of IPv6 policy when IPv6 address range is
1127             not defined so it will default to address ::/0, otherwise False.
1128         :type node: dict
1129         :type n_entries: int
1130         :type spd_id: int
1131         :type priority: Optional[ObjIncrement]
1132         :type action: IpsecSpdAction.InputType
1133         :type inbound: bool
1134         :type sa_id: Optional[ObjIncrement]
1135         :type proto: IPsecProto.InputType
1136         :type laddr_range: Optional[NetworkIncrement]
1137         :type raddr_range: Optional[NetworkIncrement]
1138         :type lport_range: Optional[str]
1139         :type rport_range: Optional[str]
1140         :type is_ipv6: bool
1141         """
1142         action = get_enum_instance(IpsecSpdAction, action)
1143         proto = get_enum_instance(IPsecProto, proto)
1144         if laddr_range is None:
1145             laddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
1146             laddr_range = NetworkIncrement(ip_network(laddr_range), 0)
1147
1148         if raddr_range is None:
1149             raddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
1150             raddr_range = NetworkIncrement(ip_network(raddr_range), 0)
1151
1152         err_msg = (
1153             "Failed to add entry to Security Policy Database"
1154             f" {spd_id} on host {node['host']}"
1155         )
1156         with PapiSocketExecutor(node, is_async=True) as papi_exec:
1157             for _ in range(n_entries):
1158                 IPsecUtil._vpp_ipsec_add_spd_entry_internal(
1159                     papi_exec,
1160                     spd_id,
1161                     next(priority),
1162                     action,
1163                     inbound,
1164                     next(sa_id) if sa_id is not None else sa_id,
1165                     proto,
1166                     next(laddr_range),
1167                     next(raddr_range),
1168                     lport_range,
1169                     rport_range,
1170                     is_ipv6,
1171                 )
1172             papi_exec.get_replies(err_msg)
1173
1174     @staticmethod
1175     def _ipsec_create_loopback_dut1_papi(
1176         nodes: dict, tun_ips: dict, if1_key: str, if2_key: str
1177     ) -> int:
1178         """Create loopback interface and set IP address on VPP node 1 interface
1179         using PAPI.
1180
1181         :param nodes: VPP nodes to create tunnel interfaces.
1182         :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface
1183             IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface
1184             IPv4/IPv6 address (ip2).
1185         :param if1_key: VPP node 1 interface key from topology file.
1186         :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1187             interface key from topology file.
1188         :type nodes: dict
1189         :type tun_ips: dict
1190         :type if1_key: str
1191         :type if2_key: str
1192         :returns: sw_if_idx Of the created loopback interface.
1193         :rtype: int
1194         """
1195         with PapiSocketExecutor(nodes["DUT1"]) as papi_exec:
1196             # Create loopback interface on DUT1, set it to up state
1197             cmd = "create_loopback_instance"
1198             args = dict(
1199                 mac_address=0,
1200                 is_specified=False,
1201                 user_instance=0,
1202             )
1203             err_msg = (
1204                 "Failed to create loopback interface"
1205                 f" on host {nodes['DUT1']['host']}"
1206             )
1207             papi_exec.add(cmd, **args)
1208             loop_sw_if_idx = papi_exec.get_sw_if_index(err_msg)
1209             cmd = "sw_interface_set_flags"
1210             args = dict(
1211                 sw_if_index=loop_sw_if_idx,
1212                 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1213             )
1214             err_msg = (
1215                 "Failed to set loopback interface state up"
1216                 f" on host {nodes['DUT1']['host']}"
1217             )
1218             papi_exec.add(cmd, **args).get_reply(err_msg)
1219             # Set IP address on VPP node 1 interface
1220             cmd = "sw_interface_add_del_address"
1221             args = dict(
1222                 sw_if_index=InterfaceUtil.get_interface_index(
1223                     nodes["DUT1"], if1_key
1224                 ),
1225                 is_add=True,
1226                 del_all=False,
1227                 prefix=IPUtil.create_prefix_object(
1228                     tun_ips["ip2"] - 1,
1229                     96 if tun_ips["ip2"].version == 6 else 24,
1230                 ),
1231             )
1232             err_msg = (
1233                 f"Failed to set IP address on interface {if1_key}"
1234                 f" on host {nodes['DUT1']['host']}"
1235             )
1236             papi_exec.add(cmd, **args).get_reply(err_msg)
1237             cmd2 = "ip_neighbor_add_del"
1238             args2 = dict(
1239                 is_add=1,
1240                 neighbor=dict(
1241                     sw_if_index=Topology.get_interface_sw_index(
1242                         nodes["DUT1"], if1_key
1243                     ),
1244                     flags=1,
1245                     mac_address=str(
1246                         Topology.get_interface_mac(nodes["DUT2"], if2_key)
1247                         if "DUT2" in nodes.keys()
1248                         else Topology.get_interface_mac(nodes["TG"], if2_key)
1249                     ),
1250                     ip_address=tun_ips["ip2"].compressed,
1251                 ),
1252             )
1253             err_msg = f"Failed to add IP neighbor on interface {if1_key}"
1254             papi_exec.add(cmd2, **args2).get_reply(err_msg)
1255
1256             return loop_sw_if_idx
1257
1258     @staticmethod
1259     def _ipsec_create_tunnel_interfaces_dut1_papi(
1260         nodes: dict,
1261         tun_ips: dict,
1262         if1_key: str,
1263         if2_key: str,
1264         n_tunnels: int,
1265         crypto_alg: CryptoAlg.InputType,
1266         integ_alg: IntegAlg.InputType,
1267         raddr_ip2: Union[IPv4Address, IPv6Address],
1268         addr_incr: int,
1269         spi_d: dict,
1270         existing_tunnels: int = 0,
1271         udp_encap: bool = False,
1272         anti_replay: bool = False,
1273     ) -> Tuple[List[bytes], List[bytes]]:
1274         """Create multiple IPsec tunnel interfaces on DUT1 node using PAPI.
1275
1276         Generate random keys and return them (so DUT2 or TG can decrypt).
1277
1278         :param nodes: VPP nodes to create tunnel interfaces.
1279         :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface
1280             IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface
1281             IPv4/IPv6 address (ip2).
1282         :param if1_key: VPP node 1 interface key from topology file.
1283         :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1284             interface key from topology file.
1285         :param n_tunnels: Number of tunnel interfaces to be there at the end.
1286         :param crypto_alg: The encryption algorithm name.
1287         :param integ_alg: The integrity algorithm name.
1288         :param raddr_ip2: Policy selector remote IPv4/IPv6 start address for the
1289             first tunnel in direction node2->node1.
1290         :param spi_d: Dictionary with SPIs for VPP node 1 and VPP node 2.
1291         :param addr_incr: IP / IPv6 address incremental step.
1292         :param existing_tunnels: Number of tunnel interfaces before creation.
1293             Useful mainly for reconf tests. Default 0.
1294         :param udp_encap: Whether to apply UDP_ENCAP flag.
1295         :param anti_replay: Whether to apply USE_ANTI_REPLAY flag.
1296         :type nodes: dict
1297         :type tun_ips: dict
1298         :type if1_key: str
1299         :type if2_key: str
1300         :type n_tunnels: int
1301         :type crypto_alg: CryptoAlg.InputType
1302         :type integ_alg: IntegAlg.InputType
1303         :type raddr_ip2: Union[IPv4Address, IPv6Address]
1304         :type addr_incr: int
1305         :type spi_d: dict
1306         :type existing_tunnels: int
1307         :type udp_encap: bool
1308         :type anti_replay: bool
1309         :returns: Generated ckeys and ikeys.
1310         :rtype: List[bytes], List[bytes]
1311         """
1312         crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
1313         integ_alg = get_enum_instance(IntegAlg, integ_alg)
1314         if not existing_tunnels:
1315             loop_sw_if_idx = IPsecUtil._ipsec_create_loopback_dut1_papi(
1316                 nodes, tun_ips, if1_key, if2_key
1317             )
1318         else:
1319             loop_sw_if_idx = InterfaceUtil.vpp_get_interface_sw_index(
1320                 nodes["DUT1"], "loop0"
1321             )
1322         with PapiSocketExecutor(nodes["DUT1"], is_async=True) as papi_exec:
1323             # Configure IP addresses on loop0 interface
1324             cmd = "sw_interface_add_del_address"
1325             args = dict(
1326                 sw_if_index=loop_sw_if_idx,
1327                 is_add=True,
1328                 del_all=False,
1329                 prefix=None,
1330             )
1331             for i in range(existing_tunnels, n_tunnels):
1332                 args["prefix"] = IPUtil.create_prefix_object(
1333                     tun_ips["ip1"] + i * addr_incr,
1334                     128 if tun_ips["ip1"].version == 6 else 32,
1335                 )
1336                 papi_exec.add(
1337                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1338                 )
1339             # Configure IPIP tunnel interfaces
1340             cmd = "ipip_add_tunnel"
1341             ipip_tunnel = dict(
1342                 instance=Constants.BITWISE_NON_ZERO,
1343                 src=None,
1344                 dst=None,
1345                 table_id=0,
1346                 flags=int(
1347                     TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1348                 ),
1349                 mode=int(TunnelMode.TUNNEL_API_MODE_P2P),
1350                 dscp=int(IpDscp.IP_API_DSCP_CS0),
1351             )
1352             args = dict(tunnel=ipip_tunnel)
1353             ipip_tunnels = [None] * existing_tunnels
1354             for i in range(existing_tunnels, n_tunnels):
1355                 ipip_tunnel["src"] = IPAddress.create_ip_address_object(
1356                     tun_ips["ip1"] + i * addr_incr
1357                 )
1358                 ipip_tunnel["dst"] = IPAddress.create_ip_address_object(
1359                     tun_ips["ip2"]
1360                 )
1361                 papi_exec.add(
1362                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1363                 )
1364             err_msg = (
1365                 "Failed to add IPIP tunnel interfaces on host"
1366                 f" {nodes['DUT1']['host']}"
1367             )
1368             ipip_tunnels.extend(
1369                 [
1370                     reply["sw_if_index"]
1371                     for reply in papi_exec.get_replies(err_msg)
1372                     if "sw_if_index" in reply
1373                 ]
1374             )
1375             # Configure IPSec SAD entries
1376             ckeys = [bytes()] * existing_tunnels
1377             ikeys = [bytes()] * existing_tunnels
1378             cmd = "ipsec_sad_entry_add_v2"
1379             c_key = dict(length=0, data=None)
1380             i_key = dict(length=0, data=None)
1381             common_flags = IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE
1382             if udp_encap:
1383                 common_flags |= IPsecSadFlags.IPSEC_API_SAD_FLAG_UDP_ENCAP
1384             if anti_replay:
1385                 common_flags |= IPsecSadFlags.IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY
1386             sad_entry = dict(
1387                 sad_id=None,
1388                 spi=None,
1389                 protocol=IPsecProto.ESP,
1390                 crypto_algorithm=crypto_alg.alg_int_repr,
1391                 crypto_key=c_key,
1392                 integrity_algorithm=integ_alg.alg_int_repr,
1393                 integrity_key=i_key,
1394                 flags=common_flags,
1395                 tunnel=dict(
1396                     src=0,
1397                     dst=0,
1398                     table_id=0,
1399                     encap_decap_flags=int(
1400                         TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1401                     ),
1402                     dscp=int(IpDscp.IP_API_DSCP_CS0),
1403                 ),
1404                 salt=0,
1405                 udp_src_port=IPSEC_UDP_PORT_DEFAULT,
1406                 udp_dst_port=IPSEC_UDP_PORT_DEFAULT,
1407                 anti_replay_window_size=IPSEC_REPLAY_WINDOW_DEFAULT,
1408             )
1409             args = dict(entry=sad_entry)
1410             for i in range(existing_tunnels, n_tunnels):
1411                 ckeys.append(gen_key(crypto_alg.key_len))
1412                 ikeys.append(gen_key(integ_alg.key_len))
1413                 # SAD entry for outband / tx path
1414                 sad_entry["sad_id"] = i
1415                 sad_entry["spi"] = spi_d["spi_1"] + i
1416
1417                 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1418                 sad_entry["crypto_key"]["data"] = ckeys[i]
1419                 if integ_alg:
1420                     sad_entry["integrity_key"]["length"] = len(ikeys[i])
1421                     sad_entry["integrity_key"]["data"] = ikeys[i]
1422                 papi_exec.add(
1423                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1424                 )
1425             sad_entry["flags"] |= IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_INBOUND
1426             for i in range(existing_tunnels, n_tunnels):
1427                 # SAD entry for inband / rx path
1428                 sad_entry["sad_id"] = 100000 + i
1429                 sad_entry["spi"] = spi_d["spi_2"] + i
1430
1431                 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1432                 sad_entry["crypto_key"]["data"] = ckeys[i]
1433                 if integ_alg:
1434                     sad_entry["integrity_key"]["length"] = len(ikeys[i])
1435                     sad_entry["integrity_key"]["data"] = ikeys[i]
1436                 papi_exec.add(
1437                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1438                 )
1439             err_msg = (
1440                 "Failed to add IPsec SAD entries on host"
1441                 f" {nodes['DUT1']['host']}"
1442             )
1443             papi_exec.get_replies(err_msg)
1444             # Add protection for tunnels with IPSEC
1445             cmd = "ipsec_tunnel_protect_update"
1446             n_hop = dict(
1447                 address=0,
1448                 via_label=MPLS_LABEL_INVALID,
1449                 obj_id=Constants.BITWISE_NON_ZERO,
1450             )
1451             ipsec_tunnel_protect = dict(
1452                 sw_if_index=None, nh=n_hop, sa_out=None, n_sa_in=1, sa_in=None
1453             )
1454             args = dict(tunnel=ipsec_tunnel_protect)
1455             for i in range(existing_tunnels, n_tunnels):
1456                 args["tunnel"]["sw_if_index"] = ipip_tunnels[i]
1457                 args["tunnel"]["sa_out"] = i
1458                 args["tunnel"]["sa_in"] = [100000 + i]
1459                 papi_exec.add(
1460                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1461                 )
1462             err_msg = (
1463                 "Failed to add protection for tunnels with IPSEC"
1464                 f" on host {nodes['DUT1']['host']}"
1465             )
1466             papi_exec.get_replies(err_msg)
1467
1468             # Configure unnumbered interfaces
1469             cmd = "sw_interface_set_unnumbered"
1470             args = dict(
1471                 is_add=True,
1472                 sw_if_index=InterfaceUtil.get_interface_index(
1473                     nodes["DUT1"], if1_key
1474                 ),
1475                 unnumbered_sw_if_index=0,
1476             )
1477             for i in range(existing_tunnels, n_tunnels):
1478                 args["unnumbered_sw_if_index"] = ipip_tunnels[i]
1479                 papi_exec.add(
1480                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1481                 )
1482             # Set interfaces up
1483             cmd = "sw_interface_set_flags"
1484             args = dict(
1485                 sw_if_index=0,
1486                 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1487             )
1488             for i in range(existing_tunnels, n_tunnels):
1489                 args["sw_if_index"] = ipip_tunnels[i]
1490                 papi_exec.add(
1491                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1492                 )
1493             # Configure IP routes
1494             cmd = "ip_route_add_del"
1495             args = dict(is_add=1, is_multipath=0, route=None)
1496             for i in range(existing_tunnels, n_tunnels):
1497                 args["route"] = IPUtil.compose_vpp_route_structure(
1498                     nodes["DUT1"],
1499                     (raddr_ip2 + i).compressed,
1500                     prefix_len=128 if raddr_ip2.version == 6 else 32,
1501                     interface=ipip_tunnels[i],
1502                 )
1503                 papi_exec.add(
1504                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1505                 )
1506             err_msg = f"Failed to add IP routes on host {nodes['DUT1']['host']}"
1507             papi_exec.get_replies(err_msg)
1508
1509         return ckeys, ikeys
1510
1511     @staticmethod
1512     def _ipsec_create_tunnel_interfaces_dut2_papi(
1513         nodes: dict,
1514         tun_ips: dict,
1515         if2_key: str,
1516         n_tunnels: int,
1517         crypto_alg: CryptoAlg.InputType,
1518         ckeys: Sequence[bytes],
1519         integ_alg: IntegAlg.InputType,
1520         ikeys: Sequence[bytes],
1521         raddr_ip1: Union[IPv4Address, IPv6Address],
1522         addr_incr: int,
1523         spi_d: dict,
1524         existing_tunnels: int = 0,
1525         udp_encap: bool = False,
1526         anti_replay: bool = False,
1527     ) -> None:
1528         """Create multiple IPsec tunnel interfaces on DUT2 node using PAPI.
1529
1530         This method accesses keys generated by DUT1 method
1531         and does not return anything.
1532
1533         :param nodes: VPP nodes to create tunnel interfaces.
1534         :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface
1535             IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface
1536             IPv4/IPv6 address (ip2).
1537         :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1538             interface key from topology file.
1539         :param n_tunnels: Number of tunnel interfaces to be there at the end.
1540         :param crypto_alg: The encryption algorithm name.
1541         :param ckeys: List of encryption keys.
1542         :param integ_alg: The integrity algorithm name.
1543         :param ikeys: List of integrity keys.
1544         :param raddr_ip1: Policy selector remote IPv4/IPv6 start address for the
1545             first tunnel in direction node1->node2.
1546         :param spi_d: Dictionary with SPIs for VPP node 1 and VPP node 2.
1547         :param addr_incr: IP / IPv6 address incremental step.
1548         :param existing_tunnels: Number of tunnel interfaces before creation.
1549             Useful mainly for reconf tests. Default 0.
1550         :param udp_encap: Whether to apply UDP_ENCAP flag.
1551         :param anti_replay: Whether to apply USE_ANTI_REPLAY flag.
1552         :type nodes: dict
1553         :type tun_ips: dict
1554         :type if2_key: str
1555         :type n_tunnels: int
1556         :type crypto_alg: CryptoAlg.InputType
1557         :type ckeys: Sequence[bytes]
1558         :type integ_alg: IntegAlg.InputType
1559         :type ikeys: Sequence[bytes]
1560         :type raddr_ip1: Union[IPv4Address, IPv6Address]
1561         :type addr_incr: int
1562         :type spi_d: dict
1563         :type existing_tunnels: int
1564         :type udp_encap: bool
1565         :type anti_replay: bool
1566         """
1567         crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
1568         integ_alg = get_enum_instance(IntegAlg, integ_alg)
1569         with PapiSocketExecutor(nodes["DUT2"], is_async=True) as papi_exec:
1570             if not existing_tunnels:
1571                 # Set IP address on VPP node 2 interface
1572                 cmd = "sw_interface_add_del_address"
1573                 args = dict(
1574                     sw_if_index=InterfaceUtil.get_interface_index(
1575                         nodes["DUT2"], if2_key
1576                     ),
1577                     is_add=True,
1578                     del_all=False,
1579                     prefix=IPUtil.create_prefix_object(
1580                         tun_ips["ip2"],
1581                         96 if tun_ips["ip2"].version == 6 else 24,
1582                     ),
1583                 )
1584                 err_msg = (
1585                     f"Failed to set IP address on interface {if2_key}"
1586                     f" on host {nodes['DUT2']['host']}"
1587                 )
1588                 papi_exec.add(cmd, **args).get_replies(err_msg)
1589             # Configure IPIP tunnel interfaces
1590             cmd = "ipip_add_tunnel"
1591             ipip_tunnel = dict(
1592                 instance=Constants.BITWISE_NON_ZERO,
1593                 src=None,
1594                 dst=None,
1595                 table_id=0,
1596                 flags=int(
1597                     TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1598                 ),
1599                 mode=int(TunnelMode.TUNNEL_API_MODE_P2P),
1600                 dscp=int(IpDscp.IP_API_DSCP_CS0),
1601             )
1602             args = dict(tunnel=ipip_tunnel)
1603             ipip_tunnels = [None] * existing_tunnels
1604             for i in range(existing_tunnels, n_tunnels):
1605                 ipip_tunnel["src"] = IPAddress.create_ip_address_object(
1606                     tun_ips["ip2"]
1607                 )
1608                 ipip_tunnel["dst"] = IPAddress.create_ip_address_object(
1609                     tun_ips["ip1"] + i * addr_incr
1610                 )
1611                 papi_exec.add(
1612                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1613                 )
1614             err_msg = (
1615                 "Failed to add IPIP tunnel interfaces on host"
1616                 f" {nodes['DUT2']['host']}"
1617             )
1618             ipip_tunnels.extend(
1619                 [
1620                     reply["sw_if_index"]
1621                     for reply in papi_exec.get_replies(err_msg)
1622                     if "sw_if_index" in reply
1623                 ]
1624             )
1625             # Configure IPSec SAD entries
1626             cmd = "ipsec_sad_entry_add_v2"
1627             c_key = dict(length=0, data=None)
1628             i_key = dict(length=0, data=None)
1629             common_flags = IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE
1630             if udp_encap:
1631                 common_flags |= IPsecSadFlags.IPSEC_API_SAD_FLAG_UDP_ENCAP
1632             if anti_replay:
1633                 common_flags |= IPsecSadFlags.IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY
1634             sad_entry = dict(
1635                 sad_id=None,
1636                 spi=None,
1637                 protocol=IPsecProto.ESP,
1638                 crypto_algorithm=crypto_alg.alg_int_repr,
1639                 crypto_key=c_key,
1640                 integrity_algorithm=integ_alg.alg_int_repr,
1641                 integrity_key=i_key,
1642                 flags=common_flags,
1643                 tunnel=dict(
1644                     src=0,
1645                     dst=0,
1646                     table_id=0,
1647                     encap_decap_flags=int(
1648                         TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1649                     ),
1650                     dscp=int(IpDscp.IP_API_DSCP_CS0),
1651                 ),
1652                 salt=0,
1653                 udp_src_port=IPSEC_UDP_PORT_DEFAULT,
1654                 udp_dst_port=IPSEC_UDP_PORT_DEFAULT,
1655                 anti_replay_window_size=IPSEC_REPLAY_WINDOW_DEFAULT,
1656             )
1657             args = dict(entry=sad_entry)
1658             for i in range(existing_tunnels, n_tunnels):
1659                 ckeys.append(gen_key(crypto_alg.key_len))
1660                 ikeys.append(gen_key(integ_alg.key_len))
1661                 # SAD entry for outband / tx path
1662                 sad_entry["sad_id"] = 100000 + i
1663                 sad_entry["spi"] = spi_d["spi_2"] + i
1664
1665                 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1666                 sad_entry["crypto_key"]["data"] = ckeys[i]
1667                 if integ_alg:
1668                     sad_entry["integrity_key"]["length"] = len(ikeys[i])
1669                     sad_entry["integrity_key"]["data"] = ikeys[i]
1670                 papi_exec.add(
1671                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1672                 )
1673             sad_entry["flags"] |= IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_INBOUND
1674             for i in range(existing_tunnels, n_tunnels):
1675                 # SAD entry for inband / rx path
1676                 sad_entry["sad_id"] = i
1677                 sad_entry["spi"] = spi_d["spi_1"] + i
1678
1679                 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1680                 sad_entry["crypto_key"]["data"] = ckeys[i]
1681                 if integ_alg:
1682                     sad_entry["integrity_key"]["length"] = len(ikeys[i])
1683                     sad_entry["integrity_key"]["data"] = ikeys[i]
1684                 papi_exec.add(
1685                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1686                 )
1687             err_msg = (
1688                 f"Failed to add IPsec SAD entries on host"
1689                 f" {nodes['DUT2']['host']}"
1690             )
1691             papi_exec.get_replies(err_msg)
1692             # Add protection for tunnels with IPSEC
1693             cmd = "ipsec_tunnel_protect_update"
1694             n_hop = dict(
1695                 address=0,
1696                 via_label=MPLS_LABEL_INVALID,
1697                 obj_id=Constants.BITWISE_NON_ZERO,
1698             )
1699             ipsec_tunnel_protect = dict(
1700                 sw_if_index=None, nh=n_hop, sa_out=None, n_sa_in=1, sa_in=None
1701             )
1702             args = dict(tunnel=ipsec_tunnel_protect)
1703             for i in range(existing_tunnels, n_tunnels):
1704                 args["tunnel"]["sw_if_index"] = ipip_tunnels[i]
1705                 args["tunnel"]["sa_out"] = 100000 + i
1706                 args["tunnel"]["sa_in"] = [i]
1707                 papi_exec.add(
1708                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1709                 )
1710             err_msg = (
1711                 "Failed to add protection for tunnels with IPSEC"
1712                 f" on host {nodes['DUT2']['host']}"
1713             )
1714             papi_exec.get_replies(err_msg)
1715
1716             if not existing_tunnels:
1717                 # Configure IP route
1718                 cmd = "ip_route_add_del"
1719                 route = IPUtil.compose_vpp_route_structure(
1720                     nodes["DUT2"],
1721                     tun_ips["ip1"].compressed,
1722                     prefix_len=32 if tun_ips["ip1"].version == 6 else 8,
1723                     interface=if2_key,
1724                     gateway=(tun_ips["ip2"] - 1).compressed,
1725                 )
1726                 args = dict(is_add=1, is_multipath=0, route=route)
1727                 papi_exec.add(cmd, **args)
1728             # Configure unnumbered interfaces
1729             cmd = "sw_interface_set_unnumbered"
1730             args = dict(
1731                 is_add=True,
1732                 sw_if_index=InterfaceUtil.get_interface_index(
1733                     nodes["DUT2"], if2_key
1734                 ),
1735                 unnumbered_sw_if_index=0,
1736             )
1737             for i in range(existing_tunnels, n_tunnels):
1738                 args["unnumbered_sw_if_index"] = ipip_tunnels[i]
1739                 papi_exec.add(
1740                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1741                 )
1742             # Set interfaces up
1743             cmd = "sw_interface_set_flags"
1744             args = dict(
1745                 sw_if_index=0,
1746                 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1747             )
1748             for i in range(existing_tunnels, n_tunnels):
1749                 args["sw_if_index"] = ipip_tunnels[i]
1750                 papi_exec.add(
1751                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1752                 )
1753             # Configure IP routes
1754             cmd = "ip_route_add_del"
1755             args = dict(is_add=1, is_multipath=0, route=None)
1756             for i in range(existing_tunnels, n_tunnels):
1757                 args["route"] = IPUtil.compose_vpp_route_structure(
1758                     nodes["DUT1"],
1759                     (raddr_ip1 + i).compressed,
1760                     prefix_len=128 if raddr_ip1.version == 6 else 32,
1761                     interface=ipip_tunnels[i],
1762                 )
1763                 papi_exec.add(
1764                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1765                 )
1766             err_msg = f"Failed to add IP routes on host {nodes['DUT2']['host']}"
1767             papi_exec.get_replies(err_msg)
1768
1769     @staticmethod
1770     def vpp_ipsec_create_tunnel_interfaces(
1771         nodes: dict,
1772         tun_if1_ip_addr: str,
1773         tun_if2_ip_addr: str,
1774         if1_key: str,
1775         if2_key: str,
1776         n_tunnels: int,
1777         crypto_alg: CryptoAlg.InputType,
1778         integ_alg: IntegAlg.InputType,
1779         raddr_ip1: str,
1780         raddr_ip2: str,
1781         raddr_range: int,
1782         existing_tunnels: int = 0,
1783         udp_encap: bool = False,
1784         anti_replay: bool = False,
1785         return_keys: bool = False,
1786     ) -> Optional[Tuple[List[bytes], List[bytes], int, int]]:
1787         """Create multiple IPsec tunnel interfaces between two VPP nodes.
1788
1789         Some deployments (e.g. devicetest) need to know the generated keys.
1790         But other deployments (e.g. scale perf test) would get spammed
1791         if we returned keys every time.
1792
1793         :param nodes: VPP nodes to create tunnel interfaces.
1794         :param tun_if1_ip_addr: VPP node 1 ipsec tunnel interface IPv4/IPv6
1795             address.
1796         :param tun_if2_ip_addr: VPP node 2 ipsec tunnel interface IPv4/IPv6
1797             address.
1798         :param if1_key: VPP node 1 interface key from topology file.
1799         :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1800             interface key from topology file.
1801         :param n_tunnels: Number of tunnel interfaces to be there at the end.
1802         :param crypto_alg: The encryption algorithm name.
1803         :param integ_alg: The integrity algorithm name.
1804         :param raddr_ip1: Policy selector remote IPv4/IPv6 start address for the
1805             first tunnel in direction node1->node2.
1806         :param raddr_ip2: Policy selector remote IPv4/IPv6 start address for the
1807             first tunnel in direction node2->node1.
1808         :param raddr_range: Mask specifying range of Policy selector Remote
1809             IPv4/IPv6 addresses. Valid values are from 1 to 32 in case of IPv4
1810             and to 128 in case of IPv6.
1811         :param existing_tunnels: Number of tunnel interfaces before creation.
1812             Useful mainly for reconf tests. Default 0.
1813         :param return_keys: Whether generated keys should be returned.
1814         :param udp_encap: Whether to apply UDP_ENCAP flag.
1815         :param anti_replay: Whether to apply USE_ANTI_REPLAY flag.
1816         :type nodes: dict
1817         :type tun_if1_ip_addr: str
1818         :type tun_if2_ip_addr: str
1819         :type if1_key: str
1820         :type if2_key: str
1821         :type n_tunnels: int
1822         :type crypto_alg: CryptoAlg.InputType
1823         :type integ_alg: IntegAlg.InputType
1824         :type raddr_ip1: str
1825         :type raddr_ip2: str
1826         :type raddr_range: int
1827         :type existing_tunnels: int
1828         :type return_keys: bool
1829         :type udp_encap: bool
1830         :type anti_replay: bool
1831         :returns: Ckeys, ikeys, spi_1, spi_2.
1832         :rtype: Optional[Tuple[List[bytes], List[bytes], int, int]]
1833         """
1834         crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
1835         integ_alg = get_enum_instance(IntegAlg, integ_alg)
1836         n_tunnels = int(n_tunnels)
1837         existing_tunnels = int(existing_tunnels)
1838         spi_d = dict(spi_1=100000, spi_2=200000)
1839         tun_ips = dict(
1840             ip1=ip_address(tun_if1_ip_addr), ip2=ip_address(tun_if2_ip_addr)
1841         )
1842         raddr_ip1 = ip_address(raddr_ip1)
1843         raddr_ip2 = ip_address(raddr_ip2)
1844         addr_incr = (
1845             1 << (128 - raddr_range)
1846             if tun_ips["ip1"].version == 6
1847             else 1 << (32 - raddr_range)
1848         )
1849
1850         ckeys, ikeys = IPsecUtil._ipsec_create_tunnel_interfaces_dut1_papi(
1851             nodes,
1852             tun_ips,
1853             if1_key,
1854             if2_key,
1855             n_tunnels,
1856             crypto_alg,
1857             integ_alg,
1858             raddr_ip2,
1859             addr_incr,
1860             spi_d,
1861             existing_tunnels,
1862             udp_encap,
1863             anti_replay,
1864         )
1865         if "DUT2" in nodes.keys():
1866             IPsecUtil._ipsec_create_tunnel_interfaces_dut2_papi(
1867                 nodes,
1868                 tun_ips,
1869                 if2_key,
1870                 n_tunnels,
1871                 crypto_alg,
1872                 ckeys,
1873                 integ_alg,
1874                 ikeys,
1875                 raddr_ip1,
1876                 addr_incr,
1877                 spi_d,
1878                 existing_tunnels,
1879                 udp_encap,
1880                 anti_replay,
1881             )
1882
1883         if return_keys:
1884             return ckeys, ikeys, spi_d["spi_1"], spi_d["spi_2"]
1885         return None
1886
1887     @staticmethod
1888     def _create_ipsec_script_files(
1889         dut: str, instances: int
1890     ) -> List[TextIOWrapper]:
1891         """Create script files for configuring IPsec in containers
1892
1893         :param dut: DUT node on which to create the script files
1894         :param instances: number of containers on DUT node
1895         :type dut: str
1896         :type instances: int
1897         :returns: Created opened file handles.
1898         :rtype: List[TextIOWrapper]
1899         """
1900         scripts = []
1901         for cnf in range(0, instances):
1902             script_filename = (
1903                 f"/tmp/ipsec_create_tunnel_cnf_{dut}_{cnf + 1}.config"
1904             )
1905             scripts.append(open(script_filename, "w", encoding="utf-8"))
1906         return scripts
1907
1908     @staticmethod
1909     def _close_and_copy_ipsec_script_files(
1910         dut: str, nodes: dict, instances: int, scripts: Sequence[TextIOWrapper]
1911     ) -> None:
1912         """Close created scripts and copy them to containers
1913
1914         :param dut: DUT node on which to create the script files
1915         :param nodes: VPP nodes
1916         :param instances: number of containers on DUT node
1917         :param scripts: dictionary holding the script files
1918         :type dut: str
1919         :type nodes: dict
1920         :type instances: int
1921         :type scripts: dict
1922         """
1923         for cnf in range(0, instances):
1924             scripts[cnf].close()
1925             script_filename = (
1926                 f"/tmp/ipsec_create_tunnel_cnf_{dut}_{cnf + 1}.config"
1927             )
1928             scp_node(nodes[dut], script_filename, script_filename)
1929
1930     @staticmethod
1931     def vpp_ipsec_add_multiple_tunnels(
1932         nodes: dict,
1933         interface1: Union[str, int],
1934         interface2: Union[str, int],
1935         n_tunnels: int,
1936         crypto_alg: CryptoAlg.InputType,
1937         integ_alg: IntegAlg.InputType,
1938         tunnel_ip1: str,
1939         tunnel_ip2: str,
1940         raddr_ip1: str,
1941         raddr_ip2: str,
1942         raddr_range: int,
1943         tunnel_addr_incr: bool = True,
1944     ) -> None:
1945         """Create multiple IPsec tunnels between two VPP nodes.
1946
1947         :param nodes: VPP nodes to create tunnels.
1948         :param interface1: Interface name or sw_if_index on node 1.
1949         :param interface2: Interface name or sw_if_index on node 2.
1950         :param n_tunnels: Number of tunnels to create.
1951         :param crypto_alg: The encryption algorithm name.
1952         :param integ_alg: The integrity algorithm name.
1953         :param tunnel_ip1: Tunnel node1 IPv4 address.
1954         :param tunnel_ip2: Tunnel node2 IPv4 address.
1955         :param raddr_ip1: Policy selector remote IPv4 start address for the
1956             first tunnel in direction node1->node2.
1957         :param raddr_ip2: Policy selector remote IPv4 start address for the
1958             first tunnel in direction node2->node1.
1959         :param raddr_range: Mask specifying range of Policy selector Remote
1960             IPv4 addresses. Valid values are from 1 to 32.
1961         :param tunnel_addr_incr: Enable or disable tunnel IP address
1962             incremental step.
1963         :type nodes: dict
1964         :type interface1: Union[str, int]
1965         :type interface2: Union[str, int]
1966         :type n_tunnels: int
1967         :type crypto_alg: CryptoAlg.InputType
1968         :type integ_alg: IntegAlg.InputType
1969         :type tunnel_ip1: str
1970         :type tunnel_ip2: str
1971         :type raddr_ip1: str
1972         :type raddr_ip2: str
1973         :type raddr_range: int
1974         :type tunnel_addr_incr: bool
1975         """
1976         crypto_alg = get_enum_instance(CryptoAlg, crypto_alg)
1977         integ_alg = get_enum_instance(IntegAlg, integ_alg)
1978
1979         spd_id = 1
1980         p_hi = 100
1981         p_lo = 10
1982         sa_id_1 = 100000
1983         sa_id_2 = 200000
1984         spi_1 = 300000
1985         spi_2 = 400000
1986
1987         crypto_key = gen_key(crypto_alg.key_len).decode()
1988         integ_key = gen_key(integ_alg.key_len).decode()
1989         rmac = (
1990             Topology.get_interface_mac(nodes["DUT2"], interface2)
1991             if "DUT2" in nodes.keys()
1992             else Topology.get_interface_mac(nodes["TG"], interface2)
1993         )
1994         IPsecUtil.vpp_ipsec_set_ip_route(
1995             nodes["DUT1"],
1996             n_tunnels,
1997             tunnel_ip1,
1998             raddr_ip2,
1999             tunnel_ip2,
2000             interface1,
2001             raddr_range,
2002             rmac,
2003         )
2004
2005         IPsecUtil.vpp_ipsec_add_spd(nodes["DUT1"], spd_id)
2006         IPsecUtil.vpp_ipsec_spd_add_if(nodes["DUT1"], spd_id, interface1)
2007
2008         addr_incr = (
2009             1 << (128 - 96)
2010             if ip_address(tunnel_ip1).version == 6
2011             else 1 << (32 - 24)
2012         )
2013         for i in range(n_tunnels // (addr_incr**2) + 1):
2014             dut1_local_outbound_range = ip_network(
2015                 f"{ip_address(tunnel_ip1) + i*(addr_incr**3)}/8", False
2016             ).with_prefixlen
2017             dut1_remote_outbound_range = ip_network(
2018                 f"{ip_address(tunnel_ip2) + i*(addr_incr**3)}/8", False
2019             ).with_prefixlen
2020
2021             IPsecUtil.vpp_ipsec_add_spd_entry(
2022                 nodes["DUT1"],
2023                 spd_id,
2024                 p_hi,
2025                 IpsecSpdAction.BYPASS,
2026                 inbound=False,
2027                 proto=IPsecProto.ESP,
2028                 laddr_range=dut1_local_outbound_range,
2029                 raddr_range=dut1_remote_outbound_range,
2030             )
2031             IPsecUtil.vpp_ipsec_add_spd_entry(
2032                 nodes["DUT1"],
2033                 spd_id,
2034                 p_hi,
2035                 IpsecSpdAction.BYPASS,
2036                 inbound=True,
2037                 proto=IPsecProto.ESP,
2038                 laddr_range=dut1_remote_outbound_range,
2039                 raddr_range=dut1_local_outbound_range,
2040             )
2041
2042         IPsecUtil.vpp_ipsec_add_sad_entries(
2043             nodes["DUT1"],
2044             n_tunnels,
2045             sa_id_1,
2046             spi_1,
2047             crypto_alg,
2048             crypto_key,
2049             integ_alg,
2050             integ_key,
2051             tunnel_ip1,
2052             tunnel_ip2,
2053             tunnel_addr_incr,
2054         )
2055
2056         IPsecUtil.vpp_ipsec_add_spd_entries(
2057             nodes["DUT1"],
2058             n_tunnels,
2059             spd_id,
2060             priority=ObjIncrement(p_lo, 0),
2061             action=IpsecSpdAction.PROTECT,
2062             inbound=False,
2063             sa_id=ObjIncrement(sa_id_1, 1),
2064             raddr_range=NetworkIncrement(ip_network(raddr_ip2)),
2065         )
2066
2067         IPsecUtil.vpp_ipsec_add_sad_entries(
2068             nodes["DUT1"],
2069             n_tunnels,
2070             sa_id_2,
2071             spi_2,
2072             crypto_alg,
2073             crypto_key,
2074             integ_alg,
2075             integ_key,
2076             tunnel_ip2,
2077             tunnel_ip1,
2078             tunnel_addr_incr,
2079         )
2080         IPsecUtil.vpp_ipsec_add_spd_entries(
2081             nodes["DUT1"],
2082             n_tunnels,
2083             spd_id,
2084             priority=ObjIncrement(p_lo, 0),
2085             action=IpsecSpdAction.PROTECT,
2086             inbound=True,
2087             sa_id=ObjIncrement(sa_id_2, 1),
2088             raddr_range=NetworkIncrement(ip_network(raddr_ip1)),
2089         )
2090
2091         if "DUT2" in nodes.keys():
2092             rmac = Topology.get_interface_mac(nodes["DUT1"], interface1)
2093             IPsecUtil.vpp_ipsec_set_ip_route(
2094                 nodes["DUT2"],
2095                 n_tunnels,
2096                 tunnel_ip2,
2097                 raddr_ip1,
2098                 tunnel_ip1,
2099                 interface2,
2100                 raddr_range,
2101                 rmac,
2102             )
2103
2104             IPsecUtil.vpp_ipsec_add_spd(nodes["DUT2"], spd_id)
2105             IPsecUtil.vpp_ipsec_spd_add_if(nodes["DUT2"], spd_id, interface2)
2106             for i in range(n_tunnels // (addr_incr**2) + 1):
2107                 dut2_local_outbound_range = ip_network(
2108                     f"{ip_address(tunnel_ip1) + i*(addr_incr**3)}/8", False
2109                 ).with_prefixlen
2110                 dut2_remote_outbound_range = ip_network(
2111                     f"{ip_address(tunnel_ip2) + i*(addr_incr**3)}/8", False
2112                 ).with_prefixlen
2113
2114                 IPsecUtil.vpp_ipsec_add_spd_entry(
2115                     nodes["DUT2"],
2116                     spd_id,
2117                     p_hi,
2118                     IpsecSpdAction.BYPASS,
2119                     inbound=False,
2120                     proto=IPsecProto.ESP,
2121                     laddr_range=dut2_remote_outbound_range,
2122                     raddr_range=dut2_local_outbound_range,
2123                 )
2124                 IPsecUtil.vpp_ipsec_add_spd_entry(
2125                     nodes["DUT2"],
2126                     spd_id,
2127                     p_hi,
2128                     IpsecSpdAction.BYPASS,
2129                     inbound=True,
2130                     proto=IPsecProto.ESP,
2131                     laddr_range=dut2_local_outbound_range,
2132                     raddr_range=dut2_remote_outbound_range,
2133                 )
2134
2135             IPsecUtil.vpp_ipsec_add_sad_entries(
2136                 nodes["DUT2"],
2137                 n_tunnels,
2138                 sa_id_1,
2139                 spi_1,
2140                 crypto_alg,
2141                 crypto_key,
2142                 integ_alg,
2143                 integ_key,
2144                 tunnel_ip1,
2145                 tunnel_ip2,
2146                 tunnel_addr_incr,
2147             )
2148             IPsecUtil.vpp_ipsec_add_spd_entries(
2149                 nodes["DUT2"],
2150                 n_tunnels,
2151                 spd_id,
2152                 priority=ObjIncrement(p_lo, 0),
2153                 action=IpsecSpdAction.PROTECT,
2154                 inbound=True,
2155                 sa_id=ObjIncrement(sa_id_1, 1),
2156                 raddr_range=NetworkIncrement(ip_network(raddr_ip2)),
2157             )
2158
2159             IPsecUtil.vpp_ipsec_add_sad_entries(
2160                 nodes["DUT2"],
2161                 n_tunnels,
2162                 sa_id_2,
2163                 spi_2,
2164                 crypto_alg,
2165                 crypto_key,
2166                 integ_alg,
2167                 integ_key,
2168                 tunnel_ip2,
2169                 tunnel_ip1,
2170                 tunnel_addr_incr,
2171             )
2172             IPsecUtil.vpp_ipsec_add_spd_entries(
2173                 nodes["DUT2"],
2174                 n_tunnels,
2175                 spd_id,
2176                 priority=ObjIncrement(p_lo, 0),
2177                 action=IpsecSpdAction.PROTECT,
2178                 inbound=False,
2179                 sa_id=ObjIncrement(sa_id_2, 1),
2180                 raddr_range=NetworkIncrement(ip_network(raddr_ip1)),
2181             )
2182
2183     @staticmethod
2184     def vpp_ipsec_show_all(node: dict) -> None:
2185         """Run "show ipsec all" debug CLI command.
2186
2187         :param node: Node to run command on.
2188         :type node: dict
2189         """
2190         PapiSocketExecutor.run_cli_cmd(node, "show ipsec all")
2191
2192     @staticmethod
2193     def show_ipsec_security_association(node: dict) -> None:
2194         """Show IPSec security association.
2195
2196         :param node: DUT node.
2197         :type node: dict
2198         """
2199         cmd = "ipsec_sa_v5_dump"
2200         PapiSocketExecutor.dump_and_log(node, [cmd])
2201
2202     @staticmethod
2203     def vpp_ipsec_flow_enable_rss(
2204         node: dict,
2205         proto: str = "IPSEC_ESP",
2206         rss_type: str = "esp",
2207         function: str = "default",
2208     ) -> int:
2209         """Ipsec flow enable rss action.
2210
2211         :param node: DUT node.
2212         :param proto: The flow protocol.
2213         :param rss_type: RSS type.
2214         :param function: RSS function.
2215         :type node: dict
2216         :type proto: IPsecProto.InputType
2217         :type rss_type: str
2218         :type function: str
2219         :returns: flow_index.
2220         :rtype: int
2221         """
2222         # The proto argument does not correspond to IPsecProto.
2223         # The allowed values come from src/vnet/ip/protocols.def
2224         # and we do not have a good enum for that yet.
2225         # FlowUtil.FlowType and FlowUtil.FlowProto are close,
2226         # but not exactly the same.
2227
2228         # TODO: to be fixed to use full PAPI when it is ready in VPP
2229         cmd = (
2230             f"test flow add src-ip any proto {proto} rss function"
2231             f" {function} rss types {rss_type}"
2232         )
2233         stdout = PapiSocketExecutor.run_cli_cmd(node, cmd)
2234         flow_index = stdout.split()[1]
2235
2236         return flow_index
2237
2238     @staticmethod
2239     def vpp_create_ipsec_flows_on_dut(
2240         node: dict, n_flows: int, rx_queues: int, spi_start: int, interface: str
2241     ) -> None:
2242         """Create mutiple ipsec flows and enable flows onto interface.
2243
2244         :param node: DUT node.
2245         :param n_flows: Number of flows to create.
2246         :param rx_queues: NUmber of RX queues.
2247         :param spi_start: The start spi.
2248         :param interface: Name of the interface.
2249
2250         :type node: dict
2251         :type n_flows: int
2252         :type rx_queues: int
2253         :type spi_start: int
2254         :type interface: str
2255         """
2256
2257         for i in range(0, n_flows):
2258             rx_queue = i % rx_queues
2259             spi = spi_start + i
2260             flow_index = FlowUtil.vpp_create_ip4_ipsec_flow(
2261                 node, "ESP", spi, "redirect-to-queue", value=rx_queue
2262             )
2263             FlowUtil.vpp_flow_enable(node, interface, flow_index)