style(ipsec): Unify mutiline strings
[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                 "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
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             "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             "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             "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 "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 on host {node['host']}"
731         )
732         args = dict(is_add=True, spd_id=int(spd_id))
733         with PapiSocketExecutor(node) as papi_exec:
734             papi_exec.add(cmd, **args).get_reply(err_msg)
735
736     @staticmethod
737     def vpp_ipsec_spd_add_if(node, spd_id, interface):
738         """Add interface to the Security Policy Database.
739
740         :param node: VPP node.
741         :param spd_id: SPD ID to add interface on.
742         :param interface: Interface name or sw_if_index.
743         :type node: dict
744         :type spd_id: int
745         :type interface: str or int
746         """
747         cmd = "ipsec_interface_add_del_spd"
748         err_msg = (
749             f"Failed to add interface {interface} to Security Policy"
750             f" Database {spd_id} on host {node['host']}"
751         )
752         args = dict(
753             is_add=True,
754             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
755             spd_id=int(spd_id),
756         )
757         with PapiSocketExecutor(node) as papi_exec:
758             papi_exec.add(cmd, **args).get_reply(err_msg)
759
760     @staticmethod
761     def vpp_ipsec_create_spds_match_nth_entry(
762         node,
763         dir1_interface,
764         dir2_interface,
765         entry_amount,
766         local_addr_range,
767         remote_addr_range,
768         action=PolicyAction.BYPASS,
769         inbound=False,
770         bidirectional=True,
771     ):
772         """Create one matching SPD entry for inbound or outbound traffic on
773         a DUT for each traffic direction and also create entry_amount - 1
774         non-matching SPD entries. Create a Security Policy Database on each
775         outbound interface where these entries will be configured.
776         The matching SPD entry will have the lowest priority, input action and
777         will be configured to match the IP flow. The non-matching entries will
778         be the same, except with higher priority and non-matching IP flows.
779
780         Action Protect is currently not supported.
781
782         :param node: VPP node to configured the SPDs and their entries.
783         :param dir1_interface: The interface in direction 1 where the entries
784             will be checked.
785         :param dir2_interface: The interface in direction 2 where the entries
786             will be checked.
787         :param entry_amount: The number of SPD entries to configure. If
788             entry_amount == 1, no non-matching entries will be configured.
789         :param local_addr_range: Matching local address range in direction 1
790             in format IP/prefix or IP/mask. If no mask is provided, it's
791             considered to be /32.
792         :param remote_addr_range: Matching remote address range in
793             direction 1 in format IP/prefix or IP/mask. If no mask is
794             provided, it's considered to be /32.
795         :param action: Policy action.
796         :param inbound: If True policy is for inbound traffic, otherwise
797             outbound.
798         :param bidirectional: When True, will create SPDs in both directions
799             of traffic. When False, only in one direction.
800         :type node: dict
801         :type dir1_interface: Union[string, int]
802         :type dir2_interface: Union[string, int]
803         :type entry_amount: int
804         :type local_addr_range:
805             Union[string, ipaddress.IPv4Address, ipaddress.IPv6Address]
806         :type remote_addr_range:
807             Union[string, ipaddress.IPv4Address, ipaddress.IPv6Address]
808         :type action: IPsecUtil.PolicyAction
809         :type inbound: bool
810         :type bidirectional: bool
811         :raises NotImplementedError: When the action is PolicyAction.PROTECT.
812         """
813
814         if action == PolicyAction.PROTECT:
815             raise NotImplementedError("Policy action PROTECT is not supported.")
816
817         spd_id_dir1 = 1
818         spd_id_dir2 = 2
819         matching_priority = 1
820
821         IPsecUtil.vpp_ipsec_add_spd(node, spd_id_dir1)
822         IPsecUtil.vpp_ipsec_spd_add_if(node, spd_id_dir1, dir1_interface)
823         # matching entry direction 1
824         IPsecUtil.vpp_ipsec_add_spd_entry(
825             node,
826             spd_id_dir1,
827             matching_priority,
828             action,
829             inbound=inbound,
830             laddr_range=local_addr_range,
831             raddr_range=remote_addr_range,
832         )
833
834         if bidirectional:
835             IPsecUtil.vpp_ipsec_add_spd(node, spd_id_dir2)
836             IPsecUtil.vpp_ipsec_spd_add_if(node, spd_id_dir2, dir2_interface)
837
838             # matching entry direction 2, the address ranges are switched
839             IPsecUtil.vpp_ipsec_add_spd_entry(
840                 node,
841                 spd_id_dir2,
842                 matching_priority,
843                 action,
844                 inbound=inbound,
845                 laddr_range=remote_addr_range,
846                 raddr_range=local_addr_range,
847             )
848
849         # non-matching entries
850         no_match_entry_amount = entry_amount - 1
851         if no_match_entry_amount > 0:
852             # create a NetworkIncrement representation of the network,
853             # then skip the matching network
854             no_match_local_addr_range = NetworkIncrement(
855                 ip_network(local_addr_range)
856             )
857             next(no_match_local_addr_range)
858
859             no_match_remote_addr_range = NetworkIncrement(
860                 ip_network(remote_addr_range)
861             )
862             next(no_match_remote_addr_range)
863
864             # non-matching entries direction 1
865             IPsecUtil.vpp_ipsec_add_spd_entries(
866                 node,
867                 no_match_entry_amount,
868                 spd_id_dir1,
869                 ObjIncrement(matching_priority + 1, 1),
870                 action,
871                 inbound=inbound,
872                 laddr_range=no_match_local_addr_range,
873                 raddr_range=no_match_remote_addr_range,
874             )
875
876             if bidirectional:
877                 # reset the networks so that we're using a unified config
878                 # the address ranges are switched
879                 no_match_remote_addr_range = NetworkIncrement(
880                     ip_network(local_addr_range)
881                 )
882                 next(no_match_remote_addr_range)
883
884                 no_match_local_addr_range = NetworkIncrement(
885                     ip_network(remote_addr_range)
886                 )
887                 next(no_match_local_addr_range)
888                 # non-matching entries direction 2
889                 IPsecUtil.vpp_ipsec_add_spd_entries(
890                     node,
891                     no_match_entry_amount,
892                     spd_id_dir2,
893                     ObjIncrement(matching_priority + 1, 1),
894                     action,
895                     inbound=inbound,
896                     laddr_range=no_match_local_addr_range,
897                     raddr_range=no_match_remote_addr_range,
898                 )
899
900         IPsecUtil.vpp_ipsec_show_all(node)
901
902     @staticmethod
903     def _vpp_ipsec_add_spd_entry_internal(
904         executor,
905         spd_id,
906         priority,
907         action,
908         inbound=True,
909         sa_id=None,
910         proto=None,
911         laddr_range=None,
912         raddr_range=None,
913         lport_range=None,
914         rport_range=None,
915         is_ipv6=False,
916     ):
917         """Prepare to create Security Policy Database entry on the VPP node.
918
919         This just adds one more command to the executor.
920         The call site shall get replies once all entries are added,
921         to get speed benefit from async PAPI.
922
923         :param executor: Open PAPI executor (async handling) to add commands to.
924         :param spd_id: SPD ID to add entry on.
925         :param priority: SPD entry priority, higher number = higher priority.
926         :param action: Policy action.
927         :param inbound: If True policy is for inbound traffic, otherwise
928             outbound.
929         :param sa_id: SAD entry ID for action PolicyAction.PROTECT.
930         :param proto: Policy selector next layer protocol number.
931         :param laddr_range: Policy selector local IPv4 or IPv6 address range
932             in format IP/prefix or IP/mask. If no mask is provided,
933             it's considered to be /32.
934         :param raddr_range: Policy selector remote IPv4 or IPv6 address range
935             in format IP/prefix or IP/mask. If no mask is provided,
936             it's considered to be /32.
937         :param lport_range: Policy selector local TCP/UDP port range in format
938             <port_start>-<port_end>.
939         :param rport_range: Policy selector remote TCP/UDP port range in format
940             <port_start>-<port_end>.
941         :param is_ipv6: True in case of IPv6 policy when IPv6 address range is
942             not defined so it will default to address ::/0, otherwise False.
943         :type executor: PapiSocketExecutor
944         :type spd_id: int
945         :type priority: int
946         :type action: IPsecUtil.PolicyAction
947         :type inbound: bool
948         :type sa_id: int
949         :type proto: int
950         :type laddr_range: string
951         :type raddr_range: string
952         :type lport_range: string
953         :type rport_range: string
954         :type is_ipv6: bool
955         """
956         if laddr_range is None:
957             laddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
958
959         if raddr_range is None:
960             raddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
961
962         local_net = ip_network(laddr_range, strict=False)
963         remote_net = ip_network(raddr_range, strict=False)
964
965         cmd = "ipsec_spd_entry_add_del_v2"
966
967         spd_entry = dict(
968             spd_id=int(spd_id),
969             priority=int(priority),
970             is_outbound=not inbound,
971             sa_id=int(sa_id) if sa_id else 0,
972             policy=int(action),
973             protocol=255 if proto is None else int(proto),
974             remote_address_start=IPAddress.create_ip_address_object(
975                 remote_net.network_address
976             ),
977             remote_address_stop=IPAddress.create_ip_address_object(
978                 remote_net.broadcast_address
979             ),
980             local_address_start=IPAddress.create_ip_address_object(
981                 local_net.network_address
982             ),
983             local_address_stop=IPAddress.create_ip_address_object(
984                 local_net.broadcast_address
985             ),
986             remote_port_start=(
987                 int(rport_range.split("-")[0]) if rport_range else 0
988             ),
989             remote_port_stop=(
990                 int(rport_range.split("-")[1]) if rport_range else 65535
991             ),
992             local_port_start=(
993                 int(lport_range.split("-")[0]) if lport_range else 0
994             ),
995             local_port_stop=(
996                 int(lport_range.split("-")[1]) if rport_range else 65535
997             ),
998         )
999         args = dict(is_add=True, entry=spd_entry)
1000         executor.add(cmd, **args)
1001
1002     @staticmethod
1003     def vpp_ipsec_add_spd_entry(
1004         node,
1005         spd_id,
1006         priority,
1007         action,
1008         inbound=True,
1009         sa_id=None,
1010         proto=None,
1011         laddr_range=None,
1012         raddr_range=None,
1013         lport_range=None,
1014         rport_range=None,
1015         is_ipv6=False,
1016     ):
1017         """Create Security Policy Database entry on the VPP node.
1018
1019         :param node: VPP node to add SPD entry on.
1020         :param spd_id: SPD ID to add entry on.
1021         :param priority: SPD entry priority, higher number = higher priority.
1022         :param action: Policy action.
1023         :param inbound: If True policy is for inbound traffic, otherwise
1024             outbound.
1025         :param sa_id: SAD entry ID for action PolicyAction.PROTECT.
1026         :param proto: Policy selector next layer protocol number.
1027         :param laddr_range: Policy selector local IPv4 or IPv6 address range
1028             in format IP/prefix or IP/mask. If no mask is provided,
1029             it's considered to be /32.
1030         :param raddr_range: Policy selector remote IPv4 or IPv6 address range
1031             in format IP/prefix or IP/mask. If no mask is provided,
1032             it's considered to be /32.
1033         :param lport_range: Policy selector local TCP/UDP port range in format
1034             <port_start>-<port_end>.
1035         :param rport_range: Policy selector remote TCP/UDP port range in format
1036             <port_start>-<port_end>.
1037         :param is_ipv6: True in case of IPv6 policy when IPv6 address range is
1038             not defined so it will default to address ::/0, otherwise False.
1039         :type node: dict
1040         :type spd_id: int
1041         :type priority: int
1042         :type action: IPsecUtil.PolicyAction
1043         :type inbound: bool
1044         :type sa_id: int
1045         :type proto: int
1046         :type laddr_range: string
1047         :type raddr_range: string
1048         :type lport_range: string
1049         :type rport_range: string
1050         :type is_ipv6: bool
1051         """
1052         err_msg = (
1053             "Failed to add entry to Security Policy Database"
1054             f" {spd_id} on host {node['host']}"
1055         )
1056         with PapiSocketExecutor(node, is_async=True) as papi_exec:
1057             IPsecUtil._vpp_ipsec_add_spd_entry_internal(
1058                 papi_exec,
1059                 spd_id,
1060                 priority,
1061                 action,
1062                 inbound,
1063                 sa_id,
1064                 proto,
1065                 laddr_range,
1066                 raddr_range,
1067                 lport_range,
1068                 rport_range,
1069                 is_ipv6,
1070             )
1071             papi_exec.get_replies(err_msg)
1072
1073     @staticmethod
1074     def vpp_ipsec_add_spd_entries(
1075         node,
1076         n_entries,
1077         spd_id,
1078         priority,
1079         action,
1080         inbound,
1081         sa_id=None,
1082         proto=None,
1083         laddr_range=None,
1084         raddr_range=None,
1085         lport_range=None,
1086         rport_range=None,
1087         is_ipv6=False,
1088     ):
1089         """Create multiple Security Policy Database entries on the VPP node.
1090
1091         :param node: VPP node to add SPD entries on.
1092         :param n_entries: Number of SPD entries to be added.
1093         :param spd_id: SPD ID to add entries on.
1094         :param priority: SPD entries priority, higher number = higher priority.
1095         :param action: Policy action.
1096         :param inbound: If True policy is for inbound traffic, otherwise
1097             outbound.
1098         :param sa_id: SAD entry ID for action PolicyAction.PROTECT.
1099         :param proto: Policy selector next layer protocol number.
1100         :param laddr_range: Policy selector local IPv4 or IPv6 address range
1101             in format IP/prefix or IP/mask. If no mask is provided,
1102             it's considered to be /32.
1103         :param raddr_range: Policy selector remote IPv4 or IPv6 address range
1104             in format IP/prefix or IP/mask. If no mask is provided,
1105             it's considered to be /32.
1106         :param lport_range: Policy selector local TCP/UDP port range in format
1107             <port_start>-<port_end>.
1108         :param rport_range: Policy selector remote TCP/UDP port range in format
1109             <port_start>-<port_end>.
1110         :param is_ipv6: True in case of IPv6 policy when IPv6 address range is
1111             not defined so it will default to address ::/0, otherwise False.
1112         :type node: dict
1113         :type n_entries: int
1114         :type spd_id: int
1115         :type priority: IPsecUtil.ObjIncrement
1116         :type action: IPsecUtil.PolicyAction
1117         :type inbound: bool
1118         :type sa_id: IPsecUtil.ObjIncrement
1119         :type proto: int
1120         :type laddr_range: IPsecUtil.NetworkIncrement
1121         :type raddr_range: IPsecUtil.NetworkIncrement
1122         :type lport_range: string
1123         :type rport_range: string
1124         :type is_ipv6: bool
1125         """
1126         if laddr_range is None:
1127             laddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
1128             laddr_range = NetworkIncrement(ip_network(laddr_range), 0)
1129
1130         if raddr_range is None:
1131             raddr_range = "::/0" if is_ipv6 else "0.0.0.0/0"
1132             raddr_range = NetworkIncrement(ip_network(raddr_range), 0)
1133
1134         err_msg = (
1135             "Failed to add entry to Security Policy Database"
1136             f" {spd_id} on host {node['host']}"
1137         )
1138         with PapiSocketExecutor(node, is_async=True) as papi_exec:
1139             for _ in range(n_entries):
1140                 IPsecUtil._vpp_ipsec_add_spd_entry_internal(
1141                     papi_exec,
1142                     spd_id,
1143                     next(priority),
1144                     action,
1145                     inbound,
1146                     next(sa_id) if sa_id is not None else sa_id,
1147                     proto,
1148                     next(laddr_range),
1149                     next(raddr_range),
1150                     lport_range,
1151                     rport_range,
1152                     is_ipv6,
1153                 )
1154             papi_exec.get_replies(err_msg)
1155
1156     @staticmethod
1157     def _ipsec_create_loopback_dut1_papi(nodes, tun_ips, if1_key, if2_key):
1158         """Create loopback interface and set IP address on VPP node 1 interface
1159         using PAPI.
1160
1161         :param nodes: VPP nodes to create tunnel interfaces.
1162         :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface
1163             IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface
1164             IPv4/IPv6 address (ip2).
1165         :param if1_key: VPP node 1 interface key from topology file.
1166         :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1167             interface key from topology file.
1168         :type nodes: dict
1169         :type tun_ips: dict
1170         :type if1_key: str
1171         :type if2_key: str
1172         """
1173         with PapiSocketExecutor(nodes["DUT1"]) as papi_exec:
1174             # Create loopback interface on DUT1, set it to up state
1175             cmd = "create_loopback_instance"
1176             args = dict(
1177                 mac_address=0,
1178                 is_specified=False,
1179                 user_instance=0,
1180             )
1181             err_msg = (
1182                 "Failed to create loopback interface"
1183                 f" on host {nodes['DUT1']['host']}"
1184             )
1185             papi_exec.add(cmd, **args)
1186             loop_sw_if_idx = papi_exec.get_sw_if_index(err_msg)
1187             cmd = "sw_interface_set_flags"
1188             args = dict(
1189                 sw_if_index=loop_sw_if_idx,
1190                 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1191             )
1192             err_msg = (
1193                 "Failed to set loopback interface state up"
1194                 f" on host {nodes['DUT1']['host']}"
1195             )
1196             papi_exec.add(cmd, **args).get_reply(err_msg)
1197             # Set IP address on VPP node 1 interface
1198             cmd = "sw_interface_add_del_address"
1199             args = dict(
1200                 sw_if_index=InterfaceUtil.get_interface_index(
1201                     nodes["DUT1"], if1_key
1202                 ),
1203                 is_add=True,
1204                 del_all=False,
1205                 prefix=IPUtil.create_prefix_object(
1206                     tun_ips["ip2"] - 1,
1207                     96 if tun_ips["ip2"].version == 6 else 24,
1208                 ),
1209             )
1210             err_msg = (
1211                 f"Failed to set IP address on interface {if1_key}"
1212                 f" on host {nodes['DUT1']['host']}"
1213             )
1214             papi_exec.add(cmd, **args).get_reply(err_msg)
1215             cmd2 = "ip_neighbor_add_del"
1216             args2 = dict(
1217                 is_add=1,
1218                 neighbor=dict(
1219                     sw_if_index=Topology.get_interface_sw_index(
1220                         nodes["DUT1"], if1_key
1221                     ),
1222                     flags=1,
1223                     mac_address=str(
1224                         Topology.get_interface_mac(nodes["DUT2"], if2_key)
1225                         if "DUT2" in nodes.keys()
1226                         else Topology.get_interface_mac(nodes["TG"], if2_key)
1227                     ),
1228                     ip_address=tun_ips["ip2"].compressed,
1229                 ),
1230             )
1231             err_msg = f"Failed to add IP neighbor on interface {if1_key}"
1232             papi_exec.add(cmd2, **args2).get_reply(err_msg)
1233
1234             return loop_sw_if_idx
1235
1236     @staticmethod
1237     def _ipsec_create_tunnel_interfaces_dut1_papi(
1238         nodes,
1239         tun_ips,
1240         if1_key,
1241         if2_key,
1242         n_tunnels,
1243         crypto_alg,
1244         integ_alg,
1245         raddr_ip2,
1246         addr_incr,
1247         spi_d,
1248         existing_tunnels=0,
1249     ):
1250         """Create multiple IPsec tunnel interfaces on DUT1 node using PAPI.
1251
1252         Generate random keys and return them (so DUT2 or TG can decrypt).
1253
1254         :param nodes: VPP nodes to create tunnel interfaces.
1255         :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface
1256             IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface
1257             IPv4/IPv6 address (ip2).
1258         :param if1_key: VPP node 1 interface key from topology file.
1259         :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1260             interface key from topology file.
1261         :param n_tunnels: Number of tunnel interfaces to be there at the end.
1262         :param crypto_alg: The encryption algorithm name.
1263         :param integ_alg: The integrity algorithm name.
1264         :param raddr_ip2: Policy selector remote IPv4/IPv6 start address for the
1265             first tunnel in direction node2->node1.
1266         :param spi_d: Dictionary with SPIs for VPP node 1 and VPP node 2.
1267         :param addr_incr: IP / IPv6 address incremental step.
1268         :param existing_tunnels: Number of tunnel interfaces before creation.
1269             Useful mainly for reconf tests. Default 0.
1270         :type nodes: dict
1271         :type tun_ips: dict
1272         :type if1_key: str
1273         :type if2_key: str
1274         :type n_tunnels: int
1275         :type crypto_alg: CryptoAlg
1276         :type integ_alg: Optional[IntegAlg]
1277         :type raddr_ip2: IPv4Address or IPv6Address
1278         :type addr_incr: int
1279         :type spi_d: dict
1280         :type existing_tunnels: int
1281         :returns: Generated ckeys and ikeys.
1282         :rtype: List[bytes], List[bytes]
1283         """
1284         if not existing_tunnels:
1285             loop_sw_if_idx = IPsecUtil._ipsec_create_loopback_dut1_papi(
1286                 nodes, tun_ips, if1_key, if2_key
1287             )
1288         else:
1289             loop_sw_if_idx = InterfaceUtil.vpp_get_interface_sw_index(
1290                 nodes["DUT1"], "loop0"
1291             )
1292         with PapiSocketExecutor(nodes["DUT1"], is_async=True) as papi_exec:
1293             # Configure IP addresses on loop0 interface
1294             cmd = "sw_interface_add_del_address"
1295             args = dict(
1296                 sw_if_index=loop_sw_if_idx,
1297                 is_add=True,
1298                 del_all=False,
1299                 prefix=None,
1300             )
1301             for i in range(existing_tunnels, n_tunnels):
1302                 args["prefix"] = IPUtil.create_prefix_object(
1303                     tun_ips["ip1"] + i * addr_incr,
1304                     128 if tun_ips["ip1"].version == 6 else 32,
1305                 )
1306                 papi_exec.add(
1307                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1308                 )
1309             # Configure IPIP tunnel interfaces
1310             cmd = "ipip_add_tunnel"
1311             ipip_tunnel = dict(
1312                 instance=Constants.BITWISE_NON_ZERO,
1313                 src=None,
1314                 dst=None,
1315                 table_id=0,
1316                 flags=int(
1317                     TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1318                 ),
1319                 mode=int(TunnelMode.TUNNEL_API_MODE_P2P),
1320                 dscp=int(IpDscp.IP_API_DSCP_CS0),
1321             )
1322             args = dict(tunnel=ipip_tunnel)
1323             ipip_tunnels = [None] * existing_tunnels
1324             for i in range(existing_tunnels, n_tunnels):
1325                 ipip_tunnel["src"] = IPAddress.create_ip_address_object(
1326                     tun_ips["ip1"] + i * addr_incr
1327                 )
1328                 ipip_tunnel["dst"] = IPAddress.create_ip_address_object(
1329                     tun_ips["ip2"]
1330                 )
1331                 papi_exec.add(
1332                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1333                 )
1334             err_msg = (
1335                 "Failed to add IPIP tunnel interfaces on host"
1336                 f" {nodes['DUT1']['host']}"
1337             )
1338             ipip_tunnels.extend(
1339                 [
1340                     reply["sw_if_index"]
1341                     for reply in papi_exec.get_replies(err_msg)
1342                     if "sw_if_index" in reply
1343                 ]
1344             )
1345             # Configure IPSec SAD entries
1346             ckeys = [bytes()] * existing_tunnels
1347             ikeys = [bytes()] * existing_tunnels
1348             cmd = "ipsec_sad_entry_add_v2"
1349             c_key = dict(length=0, data=None)
1350             i_key = dict(length=0, data=None)
1351             common_flags = IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE
1352             sad_entry = dict(
1353                 sad_id=None,
1354                 spi=None,
1355                 protocol=int(IPsecProto.IPSEC_API_PROTO_ESP),
1356                 crypto_algorithm=crypto_alg.alg_int_repr,
1357                 crypto_key=c_key,
1358                 integrity_algorithm=integ_alg.alg_int_repr if integ_alg else 0,
1359                 integrity_key=i_key,
1360                 flags=common_flags,
1361                 tunnel=dict(
1362                     src=0,
1363                     dst=0,
1364                     table_id=0,
1365                     encap_decap_flags=int(
1366                         TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1367                     ),
1368                     dscp=int(IpDscp.IP_API_DSCP_CS0),
1369                 ),
1370                 salt=0,
1371                 udp_src_port=IPSEC_UDP_PORT_DEFAULT,
1372                 udp_dst_port=IPSEC_UDP_PORT_DEFAULT,
1373                 anti_replay_window_size=IPSEC_REPLAY_WINDOW_DEFAULT,
1374             )
1375             args = dict(entry=sad_entry)
1376             for i in range(existing_tunnels, n_tunnels):
1377                 ckeys.append(
1378                     gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg))
1379                 )
1380                 ikeys.append(
1381                     gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg))
1382                 )
1383                 # SAD entry for outband / tx path
1384                 sad_entry["sad_id"] = i
1385                 sad_entry["spi"] = spi_d["spi_1"] + i
1386
1387                 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1388                 sad_entry["crypto_key"]["data"] = ckeys[i]
1389                 if integ_alg:
1390                     sad_entry["integrity_key"]["length"] = len(ikeys[i])
1391                     sad_entry["integrity_key"]["data"] = ikeys[i]
1392                 papi_exec.add(
1393                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1394                 )
1395             sad_entry["flags"] |= IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_INBOUND
1396             for i in range(existing_tunnels, n_tunnels):
1397                 # SAD entry for inband / rx path
1398                 sad_entry["sad_id"] = 100000 + i
1399                 sad_entry["spi"] = spi_d["spi_2"] + i
1400
1401                 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1402                 sad_entry["crypto_key"]["data"] = ckeys[i]
1403                 if integ_alg:
1404                     sad_entry["integrity_key"]["length"] = len(ikeys[i])
1405                     sad_entry["integrity_key"]["data"] = ikeys[i]
1406                 papi_exec.add(
1407                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1408                 )
1409             err_msg = (
1410                 "Failed to add IPsec SAD entries on host"
1411                 f" {nodes['DUT1']['host']}"
1412             )
1413             papi_exec.get_replies(err_msg)
1414             # Add protection for tunnels with IPSEC
1415             cmd = "ipsec_tunnel_protect_update"
1416             n_hop = dict(
1417                 address=0,
1418                 via_label=MPLS_LABEL_INVALID,
1419                 obj_id=Constants.BITWISE_NON_ZERO,
1420             )
1421             ipsec_tunnel_protect = dict(
1422                 sw_if_index=None, nh=n_hop, sa_out=None, n_sa_in=1, sa_in=None
1423             )
1424             args = dict(tunnel=ipsec_tunnel_protect)
1425             for i in range(existing_tunnels, n_tunnels):
1426                 args["tunnel"]["sw_if_index"] = ipip_tunnels[i]
1427                 args["tunnel"]["sa_out"] = i
1428                 args["tunnel"]["sa_in"] = [100000 + i]
1429                 papi_exec.add(
1430                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1431                 )
1432             err_msg = (
1433                 "Failed to add protection for tunnels with IPSEC"
1434                 f" on host {nodes['DUT1']['host']}"
1435             )
1436             papi_exec.get_replies(err_msg)
1437
1438             # Configure unnumbered interfaces
1439             cmd = "sw_interface_set_unnumbered"
1440             args = dict(
1441                 is_add=True,
1442                 sw_if_index=InterfaceUtil.get_interface_index(
1443                     nodes["DUT1"], if1_key
1444                 ),
1445                 unnumbered_sw_if_index=0,
1446             )
1447             for i in range(existing_tunnels, n_tunnels):
1448                 args["unnumbered_sw_if_index"] = ipip_tunnels[i]
1449                 papi_exec.add(
1450                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1451                 )
1452             # Set interfaces up
1453             cmd = "sw_interface_set_flags"
1454             args = dict(
1455                 sw_if_index=0,
1456                 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1457             )
1458             for i in range(existing_tunnels, n_tunnels):
1459                 args["sw_if_index"] = ipip_tunnels[i]
1460                 papi_exec.add(
1461                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1462                 )
1463             # Configure IP routes
1464             cmd = "ip_route_add_del"
1465             args = dict(is_add=1, is_multipath=0, route=None)
1466             for i in range(existing_tunnels, n_tunnels):
1467                 args["route"] = IPUtil.compose_vpp_route_structure(
1468                     nodes["DUT1"],
1469                     (raddr_ip2 + i).compressed,
1470                     prefix_len=128 if raddr_ip2.version == 6 else 32,
1471                     interface=ipip_tunnels[i],
1472                 )
1473                 papi_exec.add(
1474                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1475                 )
1476             err_msg = f"Failed to add IP routes on host {nodes['DUT1']['host']}"
1477             papi_exec.get_replies(err_msg)
1478
1479         return ckeys, ikeys
1480
1481     @staticmethod
1482     def _ipsec_create_tunnel_interfaces_dut2_papi(
1483         nodes,
1484         tun_ips,
1485         if2_key,
1486         n_tunnels,
1487         crypto_alg,
1488         ckeys,
1489         integ_alg,
1490         ikeys,
1491         raddr_ip1,
1492         addr_incr,
1493         spi_d,
1494         existing_tunnels=0,
1495     ):
1496         """Create multiple IPsec tunnel interfaces on DUT2 node using PAPI.
1497
1498         This method accesses keys generated by DUT1 method
1499         and does not return anything.
1500
1501         :param nodes: VPP nodes to create tunnel interfaces.
1502         :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface
1503             IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface
1504             IPv4/IPv6 address (ip2).
1505         :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1506             interface key from topology file.
1507         :param n_tunnels: Number of tunnel interfaces to be there at the end.
1508         :param crypto_alg: The encryption algorithm name.
1509         :param ckeys: List of encryption keys.
1510         :param integ_alg: The integrity algorithm name.
1511         :param ikeys: List of integrity keys.
1512         :param spi_d: Dictionary with SPIs for VPP node 1 and VPP node 2.
1513         :param addr_incr: IP / IPv6 address incremental step.
1514         :param existing_tunnels: Number of tunnel interfaces before creation.
1515             Useful mainly for reconf tests. Default 0.
1516         :type nodes: dict
1517         :type tun_ips: dict
1518         :type if2_key: str
1519         :type n_tunnels: int
1520         :type crypto_alg: CryptoAlg
1521         :type ckeys: Sequence[bytes]
1522         :type integ_alg: Optional[IntegAlg]
1523         :type ikeys: Sequence[bytes]
1524         :type addr_incr: int
1525         :type spi_d: dict
1526         :type existing_tunnels: int
1527         """
1528         with PapiSocketExecutor(nodes["DUT2"], is_async=True) as papi_exec:
1529             if not existing_tunnels:
1530                 # Set IP address on VPP node 2 interface
1531                 cmd = "sw_interface_add_del_address"
1532                 args = dict(
1533                     sw_if_index=InterfaceUtil.get_interface_index(
1534                         nodes["DUT2"], if2_key
1535                     ),
1536                     is_add=True,
1537                     del_all=False,
1538                     prefix=IPUtil.create_prefix_object(
1539                         tun_ips["ip2"],
1540                         96 if tun_ips["ip2"].version == 6 else 24,
1541                     ),
1542                 )
1543                 err_msg = (
1544                     f"Failed to set IP address on interface {if2_key}"
1545                     f" on host {nodes['DUT2']['host']}"
1546                 )
1547                 papi_exec.add(cmd, **args).get_replies(err_msg)
1548             # Configure IPIP tunnel interfaces
1549             cmd = "ipip_add_tunnel"
1550             ipip_tunnel = dict(
1551                 instance=Constants.BITWISE_NON_ZERO,
1552                 src=None,
1553                 dst=None,
1554                 table_id=0,
1555                 flags=int(
1556                     TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1557                 ),
1558                 mode=int(TunnelMode.TUNNEL_API_MODE_P2P),
1559                 dscp=int(IpDscp.IP_API_DSCP_CS0),
1560             )
1561             args = dict(tunnel=ipip_tunnel)
1562             ipip_tunnels = [None] * existing_tunnels
1563             for i in range(existing_tunnels, n_tunnels):
1564                 ipip_tunnel["src"] = IPAddress.create_ip_address_object(
1565                     tun_ips["ip2"]
1566                 )
1567                 ipip_tunnel["dst"] = IPAddress.create_ip_address_object(
1568                     tun_ips["ip1"] + i * addr_incr
1569                 )
1570                 papi_exec.add(
1571                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1572                 )
1573             err_msg = (
1574                 "Failed to add IPIP tunnel interfaces on host"
1575                 f" {nodes['DUT2']['host']}"
1576             )
1577             ipip_tunnels.extend(
1578                 [
1579                     reply["sw_if_index"]
1580                     for reply in papi_exec.get_replies(err_msg)
1581                     if "sw_if_index" in reply
1582                 ]
1583             )
1584             # Configure IPSec SAD entries
1585             cmd = "ipsec_sad_entry_add_v2"
1586             c_key = dict(length=0, data=None)
1587             i_key = dict(length=0, data=None)
1588             common_flags = IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE
1589             sad_entry = dict(
1590                 sad_id=None,
1591                 spi=None,
1592                 protocol=int(IPsecProto.IPSEC_API_PROTO_ESP),
1593                 crypto_algorithm=crypto_alg.alg_int_repr,
1594                 crypto_key=c_key,
1595                 integrity_algorithm=integ_alg.alg_int_repr if integ_alg else 0,
1596                 integrity_key=i_key,
1597                 flags=common_flags,
1598                 tunnel=dict(
1599                     src=0,
1600                     dst=0,
1601                     table_id=0,
1602                     encap_decap_flags=int(
1603                         TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1604                     ),
1605                     dscp=int(IpDscp.IP_API_DSCP_CS0),
1606                 ),
1607                 salt=0,
1608                 udp_src_port=IPSEC_UDP_PORT_DEFAULT,
1609                 udp_dst_port=IPSEC_UDP_PORT_DEFAULT,
1610                 anti_replay_window_size=IPSEC_REPLAY_WINDOW_DEFAULT,
1611             )
1612             args = dict(entry=sad_entry)
1613             for i in range(existing_tunnels, n_tunnels):
1614                 ckeys.append(
1615                     gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg))
1616                 )
1617                 ikeys.append(
1618                     gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg))
1619                 )
1620                 # SAD entry for outband / tx path
1621                 sad_entry["sad_id"] = 100000 + i
1622                 sad_entry["spi"] = spi_d["spi_2"] + i
1623
1624                 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1625                 sad_entry["crypto_key"]["data"] = ckeys[i]
1626                 if integ_alg:
1627                     sad_entry["integrity_key"]["length"] = len(ikeys[i])
1628                     sad_entry["integrity_key"]["data"] = ikeys[i]
1629                 papi_exec.add(
1630                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1631                 )
1632             sad_entry["flags"] |= IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_INBOUND
1633             for i in range(existing_tunnels, n_tunnels):
1634                 # SAD entry for inband / rx path
1635                 sad_entry["sad_id"] = i
1636                 sad_entry["spi"] = spi_d["spi_1"] + i
1637
1638                 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1639                 sad_entry["crypto_key"]["data"] = ckeys[i]
1640                 if integ_alg:
1641                     sad_entry["integrity_key"]["length"] = len(ikeys[i])
1642                     sad_entry["integrity_key"]["data"] = ikeys[i]
1643                 papi_exec.add(
1644                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1645                 )
1646             err_msg = (
1647                 f"Failed to add IPsec SAD entries on host"
1648                 f" {nodes['DUT2']['host']}"
1649             )
1650             papi_exec.get_replies(err_msg)
1651             # Add protection for tunnels with IPSEC
1652             cmd = "ipsec_tunnel_protect_update"
1653             n_hop = dict(
1654                 address=0,
1655                 via_label=MPLS_LABEL_INVALID,
1656                 obj_id=Constants.BITWISE_NON_ZERO,
1657             )
1658             ipsec_tunnel_protect = dict(
1659                 sw_if_index=None, nh=n_hop, sa_out=None, n_sa_in=1, sa_in=None
1660             )
1661             args = dict(tunnel=ipsec_tunnel_protect)
1662             for i in range(existing_tunnels, n_tunnels):
1663                 args["tunnel"]["sw_if_index"] = ipip_tunnels[i]
1664                 args["tunnel"]["sa_out"] = 100000 + i
1665                 args["tunnel"]["sa_in"] = [i]
1666                 papi_exec.add(
1667                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1668                 )
1669             err_msg = (
1670                 "Failed to add protection for tunnels with IPSEC"
1671                 f" on host {nodes['DUT2']['host']}"
1672             )
1673             papi_exec.get_replies(err_msg)
1674
1675             if not existing_tunnels:
1676                 # Configure IP route
1677                 cmd = "ip_route_add_del"
1678                 route = IPUtil.compose_vpp_route_structure(
1679                     nodes["DUT2"],
1680                     tun_ips["ip1"].compressed,
1681                     prefix_len=32 if tun_ips["ip1"].version == 6 else 8,
1682                     interface=if2_key,
1683                     gateway=(tun_ips["ip2"] - 1).compressed,
1684                 )
1685                 args = dict(is_add=1, is_multipath=0, route=route)
1686                 papi_exec.add(cmd, **args)
1687             # Configure unnumbered interfaces
1688             cmd = "sw_interface_set_unnumbered"
1689             args = dict(
1690                 is_add=True,
1691                 sw_if_index=InterfaceUtil.get_interface_index(
1692                     nodes["DUT2"], if2_key
1693                 ),
1694                 unnumbered_sw_if_index=0,
1695             )
1696             for i in range(existing_tunnels, n_tunnels):
1697                 args["unnumbered_sw_if_index"] = ipip_tunnels[i]
1698                 papi_exec.add(
1699                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1700                 )
1701             # Set interfaces up
1702             cmd = "sw_interface_set_flags"
1703             args = dict(
1704                 sw_if_index=0,
1705                 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1706             )
1707             for i in range(existing_tunnels, n_tunnels):
1708                 args["sw_if_index"] = ipip_tunnels[i]
1709                 papi_exec.add(
1710                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1711                 )
1712             # Configure IP routes
1713             cmd = "ip_route_add_del"
1714             args = dict(is_add=1, is_multipath=0, route=None)
1715             for i in range(existing_tunnels, n_tunnels):
1716                 args["route"] = IPUtil.compose_vpp_route_structure(
1717                     nodes["DUT1"],
1718                     (raddr_ip1 + i).compressed,
1719                     prefix_len=128 if raddr_ip1.version == 6 else 32,
1720                     interface=ipip_tunnels[i],
1721                 )
1722                 papi_exec.add(
1723                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1724                 )
1725             err_msg = f"Failed to add IP routes on host {nodes['DUT2']['host']}"
1726             papi_exec.get_replies(err_msg)
1727
1728     @staticmethod
1729     def vpp_ipsec_create_tunnel_interfaces(
1730         nodes,
1731         tun_if1_ip_addr,
1732         tun_if2_ip_addr,
1733         if1_key,
1734         if2_key,
1735         n_tunnels,
1736         crypto_alg,
1737         integ_alg,
1738         raddr_ip1,
1739         raddr_ip2,
1740         raddr_range,
1741         existing_tunnels=0,
1742         return_keys=False,
1743     ):
1744         """Create multiple IPsec tunnel interfaces between two VPP nodes.
1745
1746         Some deployments (e.g. devicetest) need to know the generated keys.
1747         But other deployments (e.g. scale perf test) would get spammed
1748         if we returned keys every time.
1749
1750         :param nodes: VPP nodes to create tunnel interfaces.
1751         :param tun_if1_ip_addr: VPP node 1 ipsec tunnel interface IPv4/IPv6
1752             address.
1753         :param tun_if2_ip_addr: VPP node 2 ipsec tunnel interface IPv4/IPv6
1754             address.
1755         :param if1_key: VPP node 1 interface key from topology file.
1756         :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1757             interface key from topology file.
1758         :param n_tunnels: Number of tunnel interfaces to be there at the end.
1759         :param crypto_alg: The encryption algorithm name.
1760         :param integ_alg: The integrity algorithm name.
1761         :param raddr_ip1: Policy selector remote IPv4/IPv6 start address for the
1762             first tunnel in direction node1->node2.
1763         :param raddr_ip2: Policy selector remote IPv4/IPv6 start address for the
1764             first tunnel in direction node2->node1.
1765         :param raddr_range: Mask specifying range of Policy selector Remote
1766             IPv4/IPv6 addresses. Valid values are from 1 to 32 in case of IPv4
1767             and to 128 in case of IPv6.
1768         :param existing_tunnels: Number of tunnel interfaces before creation.
1769             Useful mainly for reconf tests. Default 0.
1770         :param return_keys: Whether generated keys should be returned.
1771         :type nodes: dict
1772         :type tun_if1_ip_addr: str
1773         :type tun_if2_ip_addr: str
1774         :type if1_key: str
1775         :type if2_key: str
1776         :type n_tunnels: int
1777         :type crypto_alg: CryptoAlg
1778         :type integ_alg: Optonal[IntegAlg]
1779         :type raddr_ip1: string
1780         :type raddr_ip2: string
1781         :type raddr_range: int
1782         :type existing_tunnels: int
1783         :type return_keys: bool
1784         :returns: Ckeys, ikeys, spi_1, spi_2.
1785         :rtype: Optional[List[bytes], List[bytes], int, int]
1786         """
1787         n_tunnels = int(n_tunnels)
1788         existing_tunnels = int(existing_tunnels)
1789         spi_d = dict(spi_1=100000, spi_2=200000)
1790         tun_ips = dict(
1791             ip1=ip_address(tun_if1_ip_addr), ip2=ip_address(tun_if2_ip_addr)
1792         )
1793         raddr_ip1 = ip_address(raddr_ip1)
1794         raddr_ip2 = ip_address(raddr_ip2)
1795         addr_incr = (
1796             1 << (128 - raddr_range)
1797             if tun_ips["ip1"].version == 6
1798             else 1 << (32 - raddr_range)
1799         )
1800
1801         ckeys, ikeys = IPsecUtil._ipsec_create_tunnel_interfaces_dut1_papi(
1802             nodes,
1803             tun_ips,
1804             if1_key,
1805             if2_key,
1806             n_tunnels,
1807             crypto_alg,
1808             integ_alg,
1809             raddr_ip2,
1810             addr_incr,
1811             spi_d,
1812             existing_tunnels,
1813         )
1814         if "DUT2" in nodes.keys():
1815             IPsecUtil._ipsec_create_tunnel_interfaces_dut2_papi(
1816                 nodes,
1817                 tun_ips,
1818                 if2_key,
1819                 n_tunnels,
1820                 crypto_alg,
1821                 ckeys,
1822                 integ_alg,
1823                 ikeys,
1824                 raddr_ip1,
1825                 addr_incr,
1826                 spi_d,
1827                 existing_tunnels,
1828             )
1829
1830         if return_keys:
1831             return ckeys, ikeys, spi_d["spi_1"], spi_d["spi_2"]
1832         return None
1833
1834     @staticmethod
1835     def _create_ipsec_script_files(dut, instances):
1836         """Create script files for configuring IPsec in containers
1837
1838         :param dut: DUT node on which to create the script files
1839         :param instances: number of containers on DUT node
1840         :type dut: string
1841         :type instances: int
1842         """
1843         scripts = []
1844         for cnf in range(0, instances):
1845             script_filename = (
1846                 f"/tmp/ipsec_create_tunnel_cnf_{dut}_{cnf + 1}.config"
1847             )
1848             scripts.append(open(script_filename, "w", encoding="utf-8"))
1849         return scripts
1850
1851     @staticmethod
1852     def _close_and_copy_ipsec_script_files(dut, nodes, instances, scripts):
1853         """Close created scripts and copy them to containers
1854
1855         :param dut: DUT node on which to create the script files
1856         :param nodes: VPP nodes
1857         :param instances: number of containers on DUT node
1858         :param scripts: dictionary holding the script files
1859         :type dut: string
1860         :type nodes: dict
1861         :type instances: int
1862         :type scripts: dict
1863         """
1864         for cnf in range(0, instances):
1865             scripts[cnf].close()
1866             script_filename = (
1867                 f"/tmp/ipsec_create_tunnel_cnf_{dut}_{cnf + 1}.config"
1868             )
1869             scp_node(nodes[dut], script_filename, script_filename)
1870
1871     @staticmethod
1872     def vpp_ipsec_create_tunnel_interfaces_in_containers(
1873         nodes,
1874         if1_ip_addr,
1875         if2_ip_addr,
1876         n_tunnels,
1877         crypto_alg,
1878         integ_alg,
1879         raddr_ip1,
1880         raddr_ip2,
1881         raddr_range,
1882         n_instances,
1883     ):
1884         """Create multiple IPsec tunnel interfaces between two VPP nodes.
1885
1886         :param nodes: VPP nodes to create tunnel interfaces.
1887         :param if1_ip_addr: VPP node 1 interface IP4 address.
1888         :param if2_ip_addr: VPP node 2 interface IP4 address.
1889         :param n_tunnels: Number of tunnell interfaces to create.
1890         :param crypto_alg: The encryption algorithm name.
1891         :param integ_alg: The integrity algorithm name.
1892         :param raddr_ip1: Policy selector remote IPv4 start address for the
1893             first tunnel in direction node1->node2.
1894         :param raddr_ip2: Policy selector remote IPv4 start address for the
1895             first tunnel in direction node2->node1.
1896         :param raddr_range: Mask specifying range of Policy selector Remote
1897             IPv4 addresses. Valid values are from 1 to 32.
1898         :param n_instances: Number of containers.
1899         :type nodes: dict
1900         :type if1_ip_addr: str
1901         :type if2_ip_addr: str
1902         :type n_tunnels: int
1903         :type crypto_alg: CryptoAlg
1904         :type integ_alg: Optional[IntegAlg]
1905         :type raddr_ip1: string
1906         :type raddr_ip2: string
1907         :type raddr_range: int
1908         :type n_instances: int
1909         """
1910         spi_1 = 100000
1911         spi_2 = 200000
1912         addr_incr = 1 << (32 - raddr_range)
1913
1914         dut1_scripts = IPsecUtil._create_ipsec_script_files("DUT1", n_instances)
1915         dut2_scripts = IPsecUtil._create_ipsec_script_files("DUT2", n_instances)
1916
1917         for cnf in range(0, n_instances):
1918             dut1_scripts[cnf].write(
1919                 "create loopback interface\nset interface state loop0 up\n\n"
1920             )
1921             dut2_scripts[cnf].write(
1922                 f"ip route add {if1_ip_addr}/8 via"
1923                 f" {ip_address(if2_ip_addr) + cnf + 100} memif1/{cnf + 1}\n\n"
1924             )
1925
1926         for tnl in range(0, n_tunnels):
1927             cnf = tnl % n_instances
1928             ckey = getattr(
1929                 gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg)), "hex"
1930             )
1931             integ = ""
1932             ikey = getattr(
1933                 gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)), "hex"
1934             )
1935             if integ_alg:
1936                 integ = (
1937                     f"integ-alg {integ_alg.alg_name}"
1938                     f" local-integ-key {ikey}"
1939                     f" remote-integ-key {ikey}"
1940                 )
1941             # Configure tunnel end point(s) on left side
1942             dut1_scripts[cnf].write(
1943                 "set interface ip address loop0"
1944                 f" {ip_address(if1_ip_addr) + tnl * addr_incr}/32\n"
1945                 "create ipsec tunnel"
1946                 f" local-ip {ip_address(if1_ip_addr) + tnl * addr_incr}"
1947                 f" local-spi {spi_1 + tnl}"
1948                 f" remote-ip {ip_address(if2_ip_addr) + cnf}"
1949                 f" remote-spi {spi_2 + tnl}"
1950                 f" crypto-alg {crypto_alg.alg_name}"
1951                 f" local-crypto-key {ckey}"
1952                 f" remote-crypto-key {ckey}"
1953                 f" instance {tnl // n_instances}"
1954                 f" salt 0x0 {integ}\n"
1955                 f"set interface unnumbered ipip{tnl // n_instances} use loop0\n"
1956                 f"set interface state ipip{tnl // n_instances} up\n"
1957                 f"ip route add {ip_address(raddr_ip2)+tnl}/32"
1958                 f" via ipip{tnl // n_instances}\n\n"
1959             )
1960             # Configure tunnel end point(s) on right side
1961             dut2_scripts[cnf].write(
1962                 f"set ip neighbor memif1/{cnf + 1}"
1963                 f" {ip_address(if1_ip_addr) + tnl * addr_incr}"
1964                 f" 02:02:00:00:{17:02X}:{cnf:02X} static\n"
1965                 f"create ipsec tunnel local-ip {ip_address(if2_ip_addr) + cnf}"
1966                 f" local-spi {spi_2 + tnl}"
1967                 f" remote-ip {ip_address(if1_ip_addr) + tnl * addr_incr}"
1968                 f" remote-spi {spi_1 + tnl}"
1969                 f" crypto-alg {crypto_alg.alg_name}"
1970                 f" local-crypto-key {ckey}"
1971                 f" remote-crypto-key {ckey}"
1972                 f" instance {tnl // n_instances}"
1973                 f" salt 0x0 {integ}\n"
1974                 f"set interface unnumbered ipip{tnl // n_instances}"
1975                 f" use memif1/{cnf + 1}\n"
1976                 f"set interface state ipip{tnl // n_instances} up\n"
1977                 f"ip route add {ip_address(raddr_ip1) + tnl}/32"
1978                 f" via ipip{tnl // n_instances}\n\n"
1979             )
1980
1981         IPsecUtil._close_and_copy_ipsec_script_files(
1982             "DUT1", nodes, n_instances, dut1_scripts
1983         )
1984         IPsecUtil._close_and_copy_ipsec_script_files(
1985             "DUT2", nodes, n_instances, dut2_scripts
1986         )
1987
1988     @staticmethod
1989     def vpp_ipsec_add_multiple_tunnels(
1990         nodes,
1991         interface1,
1992         interface2,
1993         n_tunnels,
1994         crypto_alg,
1995         integ_alg,
1996         tunnel_ip1,
1997         tunnel_ip2,
1998         raddr_ip1,
1999         raddr_ip2,
2000         raddr_range,
2001         tunnel_addr_incr=True,
2002     ):
2003         """Create multiple IPsec tunnels between two VPP nodes.
2004
2005         :param nodes: VPP nodes to create tunnels.
2006         :param interface1: Interface name or sw_if_index on node 1.
2007         :param interface2: Interface name or sw_if_index on node 2.
2008         :param n_tunnels: Number of tunnels to create.
2009         :param crypto_alg: The encryption algorithm name.
2010         :param integ_alg: The integrity algorithm name.
2011         :param tunnel_ip1: Tunnel node1 IPv4 address.
2012         :param tunnel_ip2: Tunnel node2 IPv4 address.
2013         :param raddr_ip1: Policy selector remote IPv4 start address for the
2014             first tunnel in direction node1->node2.
2015         :param raddr_ip2: Policy selector remote IPv4 start address for the
2016             first tunnel in direction node2->node1.
2017         :param raddr_range: Mask specifying range of Policy selector Remote
2018             IPv4 addresses. Valid values are from 1 to 32.
2019         :param tunnel_addr_incr: Enable or disable tunnel IP address
2020             incremental step.
2021         :type nodes: dict
2022         :type interface1: str or int
2023         :type interface2: str or int
2024         :type n_tunnels: int
2025         :type crypto_alg: CryptoAlg
2026         :type integ_alg: Optional[IntegAlg]
2027         :type tunnel_ip1: str
2028         :type tunnel_ip2: str
2029         :type raddr_ip1: string
2030         :type raddr_ip2: string
2031         :type raddr_range: int
2032         :type tunnel_addr_incr: bool
2033         """
2034         spd_id = 1
2035         p_hi = 100
2036         p_lo = 10
2037         sa_id_1 = 100000
2038         sa_id_2 = 200000
2039         spi_1 = 300000
2040         spi_2 = 400000
2041
2042         crypto_key = gen_key(
2043             IPsecUtil.get_crypto_alg_key_len(crypto_alg)
2044         ).decode()
2045         integ_key = (
2046             gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)).decode()
2047             if integ_alg
2048             else ""
2049         )
2050
2051         rmac = (
2052             Topology.get_interface_mac(nodes["DUT2"], interface2)
2053             if "DUT2" in nodes.keys()
2054             else Topology.get_interface_mac(nodes["TG"], interface2)
2055         )
2056         IPsecUtil.vpp_ipsec_set_ip_route(
2057             nodes["DUT1"],
2058             n_tunnels,
2059             tunnel_ip1,
2060             raddr_ip2,
2061             tunnel_ip2,
2062             interface1,
2063             raddr_range,
2064             rmac,
2065         )
2066
2067         IPsecUtil.vpp_ipsec_add_spd(nodes["DUT1"], spd_id)
2068         IPsecUtil.vpp_ipsec_spd_add_if(nodes["DUT1"], spd_id, interface1)
2069
2070         addr_incr = (
2071             1 << (128 - 96)
2072             if ip_address(tunnel_ip1).version == 6
2073             else 1 << (32 - 24)
2074         )
2075         for i in range(n_tunnels // (addr_incr**2) + 1):
2076             dut1_local_outbound_range = ip_network(
2077                 f"{ip_address(tunnel_ip1) + i*(addr_incr**3)}/8", False
2078             ).with_prefixlen
2079             dut1_remote_outbound_range = ip_network(
2080                 f"{ip_address(tunnel_ip2) + i*(addr_incr**3)}/8", False
2081             ).with_prefixlen
2082
2083             IPsecUtil.vpp_ipsec_add_spd_entry(
2084                 nodes["DUT1"],
2085                 spd_id,
2086                 p_hi,
2087                 PolicyAction.BYPASS,
2088                 inbound=False,
2089                 proto=50,
2090                 laddr_range=dut1_local_outbound_range,
2091                 raddr_range=dut1_remote_outbound_range,
2092             )
2093             IPsecUtil.vpp_ipsec_add_spd_entry(
2094                 nodes["DUT1"],
2095                 spd_id,
2096                 p_hi,
2097                 PolicyAction.BYPASS,
2098                 inbound=True,
2099                 proto=50,
2100                 laddr_range=dut1_remote_outbound_range,
2101                 raddr_range=dut1_local_outbound_range,
2102             )
2103
2104         IPsecUtil.vpp_ipsec_add_sad_entries(
2105             nodes["DUT1"],
2106             n_tunnels,
2107             sa_id_1,
2108             spi_1,
2109             crypto_alg,
2110             crypto_key,
2111             integ_alg,
2112             integ_key,
2113             tunnel_ip1,
2114             tunnel_ip2,
2115             tunnel_addr_incr,
2116         )
2117
2118         IPsecUtil.vpp_ipsec_add_spd_entries(
2119             nodes["DUT1"],
2120             n_tunnels,
2121             spd_id,
2122             priority=ObjIncrement(p_lo, 0),
2123             action=PolicyAction.PROTECT,
2124             inbound=False,
2125             sa_id=ObjIncrement(sa_id_1, 1),
2126             raddr_range=NetworkIncrement(ip_network(raddr_ip2)),
2127         )
2128
2129         IPsecUtil.vpp_ipsec_add_sad_entries(
2130             nodes["DUT1"],
2131             n_tunnels,
2132             sa_id_2,
2133             spi_2,
2134             crypto_alg,
2135             crypto_key,
2136             integ_alg,
2137             integ_key,
2138             tunnel_ip2,
2139             tunnel_ip1,
2140             tunnel_addr_incr,
2141         )
2142         IPsecUtil.vpp_ipsec_add_spd_entries(
2143             nodes["DUT1"],
2144             n_tunnels,
2145             spd_id,
2146             priority=ObjIncrement(p_lo, 0),
2147             action=PolicyAction.PROTECT,
2148             inbound=True,
2149             sa_id=ObjIncrement(sa_id_2, 1),
2150             raddr_range=NetworkIncrement(ip_network(raddr_ip1)),
2151         )
2152
2153         if "DUT2" in nodes.keys():
2154             rmac = Topology.get_interface_mac(nodes["DUT1"], interface1)
2155             IPsecUtil.vpp_ipsec_set_ip_route(
2156                 nodes["DUT2"],
2157                 n_tunnels,
2158                 tunnel_ip2,
2159                 raddr_ip1,
2160                 tunnel_ip1,
2161                 interface2,
2162                 raddr_range,
2163                 rmac,
2164             )
2165
2166             IPsecUtil.vpp_ipsec_add_spd(nodes["DUT2"], spd_id)
2167             IPsecUtil.vpp_ipsec_spd_add_if(nodes["DUT2"], spd_id, interface2)
2168             for i in range(n_tunnels // (addr_incr**2) + 1):
2169                 dut2_local_outbound_range = ip_network(
2170                     f"{ip_address(tunnel_ip1) + i*(addr_incr**3)}/8", False
2171                 ).with_prefixlen
2172                 dut2_remote_outbound_range = ip_network(
2173                     f"{ip_address(tunnel_ip2) + i*(addr_incr**3)}/8", False
2174                 ).with_prefixlen
2175
2176                 IPsecUtil.vpp_ipsec_add_spd_entry(
2177                     nodes["DUT2"],
2178                     spd_id,
2179                     p_hi,
2180                     PolicyAction.BYPASS,
2181                     inbound=False,
2182                     proto=50,
2183                     laddr_range=dut2_remote_outbound_range,
2184                     raddr_range=dut2_local_outbound_range,
2185                 )
2186                 IPsecUtil.vpp_ipsec_add_spd_entry(
2187                     nodes["DUT2"],
2188                     spd_id,
2189                     p_hi,
2190                     PolicyAction.BYPASS,
2191                     inbound=True,
2192                     proto=50,
2193                     laddr_range=dut2_local_outbound_range,
2194                     raddr_range=dut2_remote_outbound_range,
2195                 )
2196
2197             IPsecUtil.vpp_ipsec_add_sad_entries(
2198                 nodes["DUT2"],
2199                 n_tunnels,
2200                 sa_id_1,
2201                 spi_1,
2202                 crypto_alg,
2203                 crypto_key,
2204                 integ_alg,
2205                 integ_key,
2206                 tunnel_ip1,
2207                 tunnel_ip2,
2208                 tunnel_addr_incr,
2209             )
2210             IPsecUtil.vpp_ipsec_add_spd_entries(
2211                 nodes["DUT2"],
2212                 n_tunnels,
2213                 spd_id,
2214                 priority=ObjIncrement(p_lo, 0),
2215                 action=PolicyAction.PROTECT,
2216                 inbound=True,
2217                 sa_id=ObjIncrement(sa_id_1, 1),
2218                 raddr_range=NetworkIncrement(ip_network(raddr_ip2)),
2219             )
2220
2221             IPsecUtil.vpp_ipsec_add_sad_entries(
2222                 nodes["DUT2"],
2223                 n_tunnels,
2224                 sa_id_2,
2225                 spi_2,
2226                 crypto_alg,
2227                 crypto_key,
2228                 integ_alg,
2229                 integ_key,
2230                 tunnel_ip2,
2231                 tunnel_ip1,
2232                 tunnel_addr_incr,
2233             )
2234             IPsecUtil.vpp_ipsec_add_spd_entries(
2235                 nodes["DUT2"],
2236                 n_tunnels,
2237                 spd_id,
2238                 priority=ObjIncrement(p_lo, 0),
2239                 action=PolicyAction.PROTECT,
2240                 inbound=False,
2241                 sa_id=ObjIncrement(sa_id_2, 1),
2242                 raddr_range=NetworkIncrement(ip_network(raddr_ip1)),
2243             )
2244
2245     @staticmethod
2246     def vpp_ipsec_show_all(node):
2247         """Run "show ipsec all" debug CLI command.
2248
2249         :param node: Node to run command on.
2250         :type node: dict
2251         """
2252         PapiSocketExecutor.run_cli_cmd(node, "show ipsec all")
2253
2254     @staticmethod
2255     def show_ipsec_security_association(node):
2256         """Show IPSec security association.
2257
2258         :param node: DUT node.
2259         :type node: dict
2260         """
2261         cmd = "ipsec_sa_v5_dump"
2262         PapiSocketExecutor.dump_and_log(node, [cmd])
2263
2264     @staticmethod
2265     def vpp_ipsec_flow_enable_rss(node, proto, rss_type, function="default"):
2266         """Ipsec flow enable rss action.
2267
2268         :param node: DUT node.
2269         :param proto: The flow protocol.
2270         :param rss_type: RSS type.
2271         :param function: RSS function.
2272
2273         :type node: dict
2274         :type proto: str
2275         :type rss_type: str
2276         :type function: str
2277         :returns: flow_index.
2278         """
2279         # TODO: to be fixed to use full PAPI when it is ready in VPP
2280         cmd = (
2281             f"test flow add src-ip any proto {proto} rss function"
2282             f" {function} rss types {rss_type}"
2283         )
2284         stdout = PapiSocketExecutor.run_cli_cmd(node, cmd)
2285         flow_index = stdout.split()[1]
2286
2287         return flow_index
2288
2289     @staticmethod
2290     def vpp_create_ipsec_flows_on_dut(
2291         node, n_flows, rx_queues, spi_start, interface
2292     ):
2293         """Create mutiple ipsec flows and enable flows onto interface.
2294
2295         :param node: DUT node.
2296         :param n_flows: Number of flows to create.
2297         :param rx_queues: NUmber of RX queues.
2298         :param spi_start: The start spi.
2299         :param interface: Name of the interface.
2300
2301         :type node: dict
2302         :type n_flows: int
2303         :type rx_queues: int
2304         :type spi_start: int
2305         :type interface: str
2306         :returns: flow_index.
2307         """
2308
2309         for i in range(0, n_flows):
2310             rx_queue = i % rx_queues
2311             spi = spi_start + i
2312             flow_index = FlowUtil.vpp_create_ip4_ipsec_flow(
2313                 node, "ESP", spi, "redirect-to-queue", value=rx_queue
2314             )
2315             FlowUtil.vpp_flow_enable(node, interface, flow_index)