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