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