Revert "fix(jobspec): Delete ipsec nfv density tests"
[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                 ipip_tunnel["src"] = IPAddress.create_ip_address_object(
1337                     tun_ips["ip1"] + i * addr_incr
1338                 )
1339                 ipip_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             common_flags = IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE
1363             sad_entry = dict(
1364                 sad_id=None,
1365                 spi=None,
1366                 protocol=int(IPsecProto.IPSEC_API_PROTO_ESP),
1367                 crypto_algorithm=crypto_alg.alg_int_repr,
1368                 crypto_key=c_key,
1369                 integrity_algorithm=integ_alg.alg_int_repr if integ_alg else 0,
1370                 integrity_key=i_key,
1371                 flags=common_flags,
1372                 tunnel=dict(
1373                     src=0,
1374                     dst=0,
1375                     table_id=0,
1376                     encap_decap_flags=int(
1377                         TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1378                     ),
1379                     dscp=int(IpDscp.IP_API_DSCP_CS0),
1380                 ),
1381                 salt=0,
1382                 udp_src_port=IPSEC_UDP_PORT_DEFAULT,
1383                 udp_dst_port=IPSEC_UDP_PORT_DEFAULT,
1384                 anti_replay_window_size=IPSEC_REPLAY_WINDOW_DEFAULT,
1385             )
1386             args = dict(entry=sad_entry)
1387             for i in range(existing_tunnels, n_tunnels):
1388                 ckeys.append(
1389                     gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg))
1390                 )
1391                 ikeys.append(
1392                     gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg))
1393                 )
1394                 # SAD entry for outband / tx path
1395                 sad_entry["sad_id"] = i
1396                 sad_entry["spi"] = spi_d["spi_1"] + i
1397
1398                 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1399                 sad_entry["crypto_key"]["data"] = ckeys[i]
1400                 if integ_alg:
1401                     sad_entry["integrity_key"]["length"] = len(ikeys[i])
1402                     sad_entry["integrity_key"]["data"] = ikeys[i]
1403                 papi_exec.add(
1404                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1405                 )
1406             sad_entry["flags"] |= IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_INBOUND
1407             for i in range(existing_tunnels, n_tunnels):
1408                 # SAD entry for inband / rx path
1409                 sad_entry["sad_id"] = 100000 + i
1410                 sad_entry["spi"] = spi_d["spi_2"] + i
1411
1412                 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1413                 sad_entry["crypto_key"]["data"] = ckeys[i]
1414                 if integ_alg:
1415                     sad_entry["integrity_key"]["length"] = len(ikeys[i])
1416                     sad_entry["integrity_key"]["data"] = ikeys[i]
1417                 papi_exec.add(
1418                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1419                 )
1420             err_msg = (
1421                 f"Failed to add IPsec SAD entries on host"
1422                 f" {nodes[u'DUT1'][u'host']}"
1423             )
1424             papi_exec.get_replies(err_msg)
1425             # Add protection for tunnels with IPSEC
1426             cmd = "ipsec_tunnel_protect_update"
1427             n_hop = dict(
1428                 address=0,
1429                 via_label=MPLS_LABEL_INVALID,
1430                 obj_id=Constants.BITWISE_NON_ZERO,
1431             )
1432             ipsec_tunnel_protect = dict(
1433                 sw_if_index=None, nh=n_hop, sa_out=None, n_sa_in=1, sa_in=None
1434             )
1435             args = dict(tunnel=ipsec_tunnel_protect)
1436             for i in range(existing_tunnels, n_tunnels):
1437                 args["tunnel"]["sw_if_index"] = ipip_tunnels[i]
1438                 args["tunnel"]["sa_out"] = i
1439                 args["tunnel"]["sa_in"] = [100000 + i]
1440                 papi_exec.add(
1441                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1442                 )
1443             err_msg = (
1444                 f"Failed to add protection for tunnels with IPSEC "
1445                 f"on host {nodes[u'DUT1'][u'host']}"
1446             )
1447             papi_exec.get_replies(err_msg)
1448
1449             # Configure unnumbered interfaces
1450             cmd = "sw_interface_set_unnumbered"
1451             args = dict(
1452                 is_add=True,
1453                 sw_if_index=InterfaceUtil.get_interface_index(
1454                     nodes["DUT1"], if1_key
1455                 ),
1456                 unnumbered_sw_if_index=0,
1457             )
1458             for i in range(existing_tunnels, n_tunnels):
1459                 args["unnumbered_sw_if_index"] = ipip_tunnels[i]
1460                 papi_exec.add(
1461                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1462                 )
1463             # Set interfaces up
1464             cmd = "sw_interface_set_flags"
1465             args = dict(
1466                 sw_if_index=0,
1467                 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1468             )
1469             for i in range(existing_tunnels, n_tunnels):
1470                 args["sw_if_index"] = ipip_tunnels[i]
1471                 papi_exec.add(
1472                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1473                 )
1474             # Configure IP routes
1475             cmd = "ip_route_add_del"
1476             args = dict(is_add=1, is_multipath=0, route=None)
1477             for i in range(existing_tunnels, n_tunnels):
1478                 args["route"] = IPUtil.compose_vpp_route_structure(
1479                     nodes["DUT1"],
1480                     (raddr_ip2 + i).compressed,
1481                     prefix_len=128 if raddr_ip2.version == 6 else 32,
1482                     interface=ipip_tunnels[i],
1483                 )
1484                 papi_exec.add(
1485                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1486                 )
1487             err_msg = (
1488                 f"Failed to add IP routes on host " f"{nodes[u'DUT1'][u'host']}"
1489             )
1490             papi_exec.get_replies(err_msg)
1491
1492         return ckeys, ikeys
1493
1494     @staticmethod
1495     def _ipsec_create_tunnel_interfaces_dut2_papi(
1496         nodes,
1497         tun_ips,
1498         if2_key,
1499         n_tunnels,
1500         crypto_alg,
1501         ckeys,
1502         integ_alg,
1503         ikeys,
1504         raddr_ip1,
1505         addr_incr,
1506         spi_d,
1507         existing_tunnels=0,
1508     ):
1509         """Create multiple IPsec tunnel interfaces on DUT2 node using PAPI.
1510
1511         This method accesses keys generated by DUT1 method
1512         and does not return anything.
1513
1514         :param nodes: VPP nodes to create tunnel interfaces.
1515         :param tun_ips: Dictionary with VPP node 1 ipsec tunnel interface
1516             IPv4/IPv6 address (ip1) and VPP node 2 ipsec tunnel interface
1517             IPv4/IPv6 address (ip2).
1518         :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1519             interface key from topology file.
1520         :param n_tunnels: Number of tunnel interfaces to be there at the end.
1521         :param crypto_alg: The encryption algorithm name.
1522         :param ckeys: List of encryption keys.
1523         :param integ_alg: The integrity algorithm name.
1524         :param ikeys: List of integrity keys.
1525         :param spi_d: Dictionary with SPIs for VPP node 1 and VPP node 2.
1526         :param addr_incr: IP / IPv6 address incremental step.
1527         :param existing_tunnels: Number of tunnel interfaces before creation.
1528             Useful mainly for reconf tests. Default 0.
1529         :type nodes: dict
1530         :type tun_ips: dict
1531         :type if2_key: str
1532         :type n_tunnels: int
1533         :type crypto_alg: CryptoAlg
1534         :type ckeys: Sequence[bytes]
1535         :type integ_alg: Optional[IntegAlg]
1536         :type ikeys: Sequence[bytes]
1537         :type addr_incr: int
1538         :type spi_d: dict
1539         :type existing_tunnels: int
1540         """
1541         with PapiSocketExecutor(nodes["DUT2"], is_async=True) as papi_exec:
1542             if not existing_tunnels:
1543                 # Set IP address on VPP node 2 interface
1544                 cmd = "sw_interface_add_del_address"
1545                 args = dict(
1546                     sw_if_index=InterfaceUtil.get_interface_index(
1547                         nodes["DUT2"], if2_key
1548                     ),
1549                     is_add=True,
1550                     del_all=False,
1551                     prefix=IPUtil.create_prefix_object(
1552                         tun_ips["ip2"],
1553                         96 if tun_ips["ip2"].version == 6 else 24,
1554                     ),
1555                 )
1556                 err_msg = (
1557                     f"Failed to set IP address on interface {if2_key} "
1558                     f"on host {nodes[u'DUT2'][u'host']}"
1559                 )
1560                 papi_exec.add(cmd, **args).get_replies(err_msg)
1561             # Configure IPIP tunnel interfaces
1562             cmd = "ipip_add_tunnel"
1563             ipip_tunnel = dict(
1564                 instance=Constants.BITWISE_NON_ZERO,
1565                 src=None,
1566                 dst=None,
1567                 table_id=0,
1568                 flags=int(
1569                     TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1570                 ),
1571                 mode=int(TunnelMode.TUNNEL_API_MODE_P2P),
1572                 dscp=int(IpDscp.IP_API_DSCP_CS0),
1573             )
1574             args = dict(tunnel=ipip_tunnel)
1575             ipip_tunnels = [None] * existing_tunnels
1576             for i in range(existing_tunnels, n_tunnels):
1577                 ipip_tunnel["src"] = IPAddress.create_ip_address_object(
1578                     tun_ips["ip2"]
1579                 )
1580                 ipip_tunnel["dst"] = IPAddress.create_ip_address_object(
1581                     tun_ips["ip1"] + i * addr_incr
1582                 )
1583                 papi_exec.add(
1584                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1585                 )
1586             err_msg = (
1587                 f"Failed to add IPIP tunnel interfaces on host"
1588                 f" {nodes[u'DUT2'][u'host']}"
1589             )
1590             ipip_tunnels.extend(
1591                 [
1592                     reply["sw_if_index"]
1593                     for reply in papi_exec.get_replies(err_msg)
1594                     if "sw_if_index" in reply
1595                 ]
1596             )
1597             # Configure IPSec SAD entries
1598             cmd = "ipsec_sad_entry_add_v2"
1599             c_key = dict(length=0, data=None)
1600             i_key = dict(length=0, data=None)
1601             common_flags = IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE
1602             sad_entry = dict(
1603                 sad_id=None,
1604                 spi=None,
1605                 protocol=int(IPsecProto.IPSEC_API_PROTO_ESP),
1606                 crypto_algorithm=crypto_alg.alg_int_repr,
1607                 crypto_key=c_key,
1608                 integrity_algorithm=integ_alg.alg_int_repr if integ_alg else 0,
1609                 integrity_key=i_key,
1610                 flags=common_flags,
1611                 tunnel=dict(
1612                     src=0,
1613                     dst=0,
1614                     table_id=0,
1615                     encap_decap_flags=int(
1616                         TunnelEncpaDecapFlags.TUNNEL_API_ENCAP_DECAP_FLAG_NONE
1617                     ),
1618                     dscp=int(IpDscp.IP_API_DSCP_CS0),
1619                 ),
1620                 salt=0,
1621                 udp_src_port=IPSEC_UDP_PORT_DEFAULT,
1622                 udp_dst_port=IPSEC_UDP_PORT_DEFAULT,
1623                 anti_replay_window_size=IPSEC_REPLAY_WINDOW_DEFAULT,
1624             )
1625             args = dict(entry=sad_entry)
1626             for i in range(existing_tunnels, n_tunnels):
1627                 ckeys.append(
1628                     gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg))
1629                 )
1630                 ikeys.append(
1631                     gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg))
1632                 )
1633                 # SAD entry for outband / tx path
1634                 sad_entry["sad_id"] = 100000 + i
1635                 sad_entry["spi"] = spi_d["spi_2"] + i
1636
1637                 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1638                 sad_entry["crypto_key"]["data"] = ckeys[i]
1639                 if integ_alg:
1640                     sad_entry["integrity_key"]["length"] = len(ikeys[i])
1641                     sad_entry["integrity_key"]["data"] = ikeys[i]
1642                 papi_exec.add(
1643                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1644                 )
1645             sad_entry["flags"] |= IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_INBOUND
1646             for i in range(existing_tunnels, n_tunnels):
1647                 # SAD entry for inband / rx path
1648                 sad_entry["sad_id"] = i
1649                 sad_entry["spi"] = spi_d["spi_1"] + i
1650
1651                 sad_entry["crypto_key"]["length"] = len(ckeys[i])
1652                 sad_entry["crypto_key"]["data"] = ckeys[i]
1653                 if integ_alg:
1654                     sad_entry["integrity_key"]["length"] = len(ikeys[i])
1655                     sad_entry["integrity_key"]["data"] = ikeys[i]
1656                 papi_exec.add(
1657                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1658                 )
1659             err_msg = (
1660                 f"Failed to add IPsec SAD entries on host"
1661                 f" {nodes[u'DUT2'][u'host']}"
1662             )
1663             papi_exec.get_replies(err_msg)
1664             # Add protection for tunnels with IPSEC
1665             cmd = "ipsec_tunnel_protect_update"
1666             n_hop = dict(
1667                 address=0,
1668                 via_label=MPLS_LABEL_INVALID,
1669                 obj_id=Constants.BITWISE_NON_ZERO,
1670             )
1671             ipsec_tunnel_protect = dict(
1672                 sw_if_index=None, nh=n_hop, sa_out=None, n_sa_in=1, sa_in=None
1673             )
1674             args = dict(tunnel=ipsec_tunnel_protect)
1675             for i in range(existing_tunnels, n_tunnels):
1676                 args["tunnel"]["sw_if_index"] = ipip_tunnels[i]
1677                 args["tunnel"]["sa_out"] = 100000 + i
1678                 args["tunnel"]["sa_in"] = [i]
1679                 papi_exec.add(
1680                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1681                 )
1682             err_msg = (
1683                 f"Failed to add protection for tunnels with IPSEC "
1684                 f"on host {nodes[u'DUT2'][u'host']}"
1685             )
1686             papi_exec.get_replies(err_msg)
1687
1688             if not existing_tunnels:
1689                 # Configure IP route
1690                 cmd = "ip_route_add_del"
1691                 route = IPUtil.compose_vpp_route_structure(
1692                     nodes["DUT2"],
1693                     tun_ips["ip1"].compressed,
1694                     prefix_len=32 if tun_ips["ip1"].version == 6 else 8,
1695                     interface=if2_key,
1696                     gateway=(tun_ips["ip2"] - 1).compressed,
1697                 )
1698                 args = dict(is_add=1, is_multipath=0, route=route)
1699                 papi_exec.add(cmd, **args)
1700             # Configure unnumbered interfaces
1701             cmd = "sw_interface_set_unnumbered"
1702             args = dict(
1703                 is_add=True,
1704                 sw_if_index=InterfaceUtil.get_interface_index(
1705                     nodes["DUT2"], if2_key
1706                 ),
1707                 unnumbered_sw_if_index=0,
1708             )
1709             for i in range(existing_tunnels, n_tunnels):
1710                 args["unnumbered_sw_if_index"] = ipip_tunnels[i]
1711                 papi_exec.add(
1712                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1713                 )
1714             # Set interfaces up
1715             cmd = "sw_interface_set_flags"
1716             args = dict(
1717                 sw_if_index=0,
1718                 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value,
1719             )
1720             for i in range(existing_tunnels, n_tunnels):
1721                 args["sw_if_index"] = ipip_tunnels[i]
1722                 papi_exec.add(
1723                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1724                 )
1725             # Configure IP routes
1726             cmd = "ip_route_add_del"
1727             args = dict(is_add=1, is_multipath=0, route=None)
1728             for i in range(existing_tunnels, n_tunnels):
1729                 args["route"] = IPUtil.compose_vpp_route_structure(
1730                     nodes["DUT1"],
1731                     (raddr_ip1 + i).compressed,
1732                     prefix_len=128 if raddr_ip1.version == 6 else 32,
1733                     interface=ipip_tunnels[i],
1734                 )
1735                 papi_exec.add(
1736                     cmd, history=bool(not 1 < i < n_tunnels - 2), **args
1737                 )
1738             err_msg = (
1739                 f"Failed to add IP routes " f"on host {nodes[u'DUT2'][u'host']}"
1740             )
1741             papi_exec.get_replies(err_msg)
1742
1743     @staticmethod
1744     def vpp_ipsec_create_tunnel_interfaces(
1745         nodes,
1746         tun_if1_ip_addr,
1747         tun_if2_ip_addr,
1748         if1_key,
1749         if2_key,
1750         n_tunnels,
1751         crypto_alg,
1752         integ_alg,
1753         raddr_ip1,
1754         raddr_ip2,
1755         raddr_range,
1756         existing_tunnels=0,
1757         return_keys=False,
1758     ):
1759         """Create multiple IPsec tunnel interfaces between two VPP nodes.
1760
1761         Some deployments (e.g. devicetest) need to know the generated keys.
1762         But other deployments (e.g. scale perf test) would get spammed
1763         if we returned keys every time.
1764
1765         :param nodes: VPP nodes to create tunnel interfaces.
1766         :param tun_if1_ip_addr: VPP node 1 ipsec tunnel interface IPv4/IPv6
1767             address.
1768         :param tun_if2_ip_addr: VPP node 2 ipsec tunnel interface IPv4/IPv6
1769             address.
1770         :param if1_key: VPP node 1 interface key from topology file.
1771         :param if2_key: VPP node 2 / TG node (in case of 2-node topology)
1772             interface key from topology file.
1773         :param n_tunnels: Number of tunnel interfaces to be there at the end.
1774         :param crypto_alg: The encryption algorithm name.
1775         :param integ_alg: The integrity algorithm name.
1776         :param raddr_ip1: Policy selector remote IPv4/IPv6 start address for the
1777             first tunnel in direction node1->node2.
1778         :param raddr_ip2: Policy selector remote IPv4/IPv6 start address for the
1779             first tunnel in direction node2->node1.
1780         :param raddr_range: Mask specifying range of Policy selector Remote
1781             IPv4/IPv6 addresses. Valid values are from 1 to 32 in case of IPv4
1782             and to 128 in case of IPv6.
1783         :param existing_tunnels: Number of tunnel interfaces before creation.
1784             Useful mainly for reconf tests. Default 0.
1785         :param return_keys: Whether generated keys should be returned.
1786         :type nodes: dict
1787         :type tun_if1_ip_addr: str
1788         :type tun_if2_ip_addr: str
1789         :type if1_key: str
1790         :type if2_key: str
1791         :type n_tunnels: int
1792         :type crypto_alg: CryptoAlg
1793         :type integ_alg: Optonal[IntegAlg]
1794         :type raddr_ip1: string
1795         :type raddr_ip2: string
1796         :type raddr_range: int
1797         :type existing_tunnels: int
1798         :type return_keys: bool
1799         :returns: Ckeys, ikeys, spi_1, spi_2.
1800         :rtype: Optional[List[bytes], List[bytes], int, int]
1801         """
1802         n_tunnels = int(n_tunnels)
1803         existing_tunnels = int(existing_tunnels)
1804         spi_d = dict(spi_1=100000, spi_2=200000)
1805         tun_ips = dict(
1806             ip1=ip_address(tun_if1_ip_addr), ip2=ip_address(tun_if2_ip_addr)
1807         )
1808         raddr_ip1 = ip_address(raddr_ip1)
1809         raddr_ip2 = ip_address(raddr_ip2)
1810         addr_incr = (
1811             1 << (128 - raddr_range)
1812             if tun_ips["ip1"].version == 6
1813             else 1 << (32 - raddr_range)
1814         )
1815
1816         ckeys, ikeys = IPsecUtil._ipsec_create_tunnel_interfaces_dut1_papi(
1817             nodes,
1818             tun_ips,
1819             if1_key,
1820             if2_key,
1821             n_tunnels,
1822             crypto_alg,
1823             integ_alg,
1824             raddr_ip2,
1825             addr_incr,
1826             spi_d,
1827             existing_tunnels,
1828         )
1829         if "DUT2" in nodes.keys():
1830             IPsecUtil._ipsec_create_tunnel_interfaces_dut2_papi(
1831                 nodes,
1832                 tun_ips,
1833                 if2_key,
1834                 n_tunnels,
1835                 crypto_alg,
1836                 ckeys,
1837                 integ_alg,
1838                 ikeys,
1839                 raddr_ip1,
1840                 addr_incr,
1841                 spi_d,
1842                 existing_tunnels,
1843             )
1844
1845         if return_keys:
1846             return ckeys, ikeys, spi_d["spi_1"], spi_d["spi_2"]
1847         return None
1848
1849     @staticmethod
1850     def _create_ipsec_script_files(dut, instances):
1851         """Create script files for configuring IPsec in containers
1852
1853         :param dut: DUT node on which to create the script files
1854         :param instances: number of containers on DUT node
1855         :type dut: string
1856         :type instances: int
1857         """
1858         scripts = []
1859         for cnf in range(0, instances):
1860             script_filename = (
1861                 f"/tmp/ipsec_create_tunnel_cnf_{dut}_{cnf + 1}.config"
1862             )
1863             scripts.append(open(script_filename, "w"))
1864         return scripts
1865
1866     @staticmethod
1867     def _close_and_copy_ipsec_script_files(dut, nodes, instances, scripts):
1868         """Close created scripts and copy them to containers
1869
1870         :param dut: DUT node on which to create the script files
1871         :param nodes: VPP nodes
1872         :param instances: number of containers on DUT node
1873         :param scripts: dictionary holding the script files
1874         :type dut: string
1875         :type nodes: dict
1876         :type instances: int
1877         :type scripts: dict
1878         """
1879         for cnf in range(0, instances):
1880             scripts[cnf].close()
1881             script_filename = (
1882                 f"/tmp/ipsec_create_tunnel_cnf_{dut}_{cnf + 1}.config"
1883             )
1884             scp_node(nodes[dut], script_filename, script_filename)
1885
1886     @staticmethod
1887     def vpp_ipsec_create_tunnel_interfaces_in_containers(
1888         nodes,
1889         if1_ip_addr,
1890         if2_ip_addr,
1891         n_tunnels,
1892         crypto_alg,
1893         integ_alg,
1894         raddr_ip1,
1895         raddr_ip2,
1896         raddr_range,
1897         n_instances,
1898     ):
1899         """Create multiple IPsec tunnel interfaces between two VPP nodes.
1900
1901         :param nodes: VPP nodes to create tunnel interfaces.
1902         :param if1_ip_addr: VPP node 1 interface IP4 address.
1903         :param if2_ip_addr: VPP node 2 interface IP4 address.
1904         :param n_tunnels: Number of tunnell interfaces to create.
1905         :param crypto_alg: The encryption algorithm name.
1906         :param integ_alg: The integrity algorithm name.
1907         :param raddr_ip1: Policy selector remote IPv4 start address for the
1908             first tunnel in direction node1->node2.
1909         :param raddr_ip2: Policy selector remote IPv4 start address for the
1910             first tunnel in direction node2->node1.
1911         :param raddr_range: Mask specifying range of Policy selector Remote
1912             IPv4 addresses. Valid values are from 1 to 32.
1913         :param n_instances: Number of containers.
1914         :type nodes: dict
1915         :type if1_ip_addr: str
1916         :type if2_ip_addr: str
1917         :type n_tunnels: int
1918         :type crypto_alg: CryptoAlg
1919         :type integ_alg: Optional[IntegAlg]
1920         :type raddr_ip1: string
1921         :type raddr_ip2: string
1922         :type raddr_range: int
1923         :type n_instances: int
1924         """
1925         spi_1 = 100000
1926         spi_2 = 200000
1927         addr_incr = 1 << (32 - raddr_range)
1928
1929         dut1_scripts = IPsecUtil._create_ipsec_script_files("DUT1", n_instances)
1930         dut2_scripts = IPsecUtil._create_ipsec_script_files("DUT2", n_instances)
1931
1932         for cnf in range(0, n_instances):
1933             dut1_scripts[cnf].write(
1934                 "create loopback interface\n" "set interface state loop0 up\n\n"
1935             )
1936             dut2_scripts[cnf].write(
1937                 f"ip route add {if1_ip_addr}/8 via "
1938                 f"{ip_address(if2_ip_addr) + cnf + 100} memif1/{cnf + 1}\n\n"
1939             )
1940
1941         for tnl in range(0, n_tunnels):
1942             cnf = tnl % n_instances
1943             ckey = getattr(
1944                 gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg)), "hex"
1945             )
1946             integ = ""
1947             ikey = getattr(
1948                 gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)), "hex"
1949             )
1950             if integ_alg:
1951                 integ = (
1952                     f"integ-alg {integ_alg.alg_name} "
1953                     f"local-integ-key {ikey} "
1954                     f"remote-integ-key {ikey} "
1955                 )
1956             # Configure tunnel end point(s) on left side
1957             dut1_scripts[cnf].write(
1958                 "set interface ip address loop0 "
1959                 f"{ip_address(if1_ip_addr) + tnl * addr_incr}/32\n"
1960                 f"create ipsec tunnel "
1961                 f"local-ip {ip_address(if1_ip_addr) + tnl * addr_incr} "
1962                 f"local-spi {spi_1 + tnl} "
1963                 f"remote-ip {ip_address(if2_ip_addr) + cnf} "
1964                 f"remote-spi {spi_2 + tnl} "
1965                 f"crypto-alg {crypto_alg.alg_name} "
1966                 f"local-crypto-key {ckey} "
1967                 f"remote-crypto-key {ckey} "
1968                 f"instance {tnl // n_instances} "
1969                 f"salt 0x0 "
1970                 f"{integ} \n"
1971                 f"set interface unnumbered ipip{tnl // n_instances} use loop0\n"
1972                 f"set interface state ipip{tnl // n_instances} up\n"
1973                 f"ip route add {ip_address(raddr_ip2)+tnl}/32 "
1974                 f"via ipip{tnl // n_instances}\n\n"
1975             )
1976             # Configure tunnel end point(s) on right side
1977             dut2_scripts[cnf].write(
1978                 f"set ip neighbor memif1/{cnf + 1} "
1979                 f"{ip_address(if1_ip_addr) + tnl * addr_incr} "
1980                 f"02:02:00:00:{17:02X}:{cnf:02X} static\n"
1981                 f"create ipsec tunnel local-ip {ip_address(if2_ip_addr) + cnf} "
1982                 f"local-spi {spi_2 + tnl} "
1983                 f"remote-ip {ip_address(if1_ip_addr) + tnl * addr_incr} "
1984                 f"remote-spi {spi_1 + tnl} "
1985                 f"crypto-alg {crypto_alg.alg_name} "
1986                 f"local-crypto-key {ckey} "
1987                 f"remote-crypto-key {ckey} "
1988                 f"instance {tnl // n_instances} "
1989                 f"salt 0x0 "
1990                 f"{integ}\n"
1991                 f"set interface unnumbered ipip{tnl // n_instances} "
1992                 f"use memif1/{cnf + 1}\n"
1993                 f"set interface state ipip{tnl // n_instances} up\n"
1994                 f"ip route add {ip_address(raddr_ip1) + tnl}/32 "
1995                 f"via ipip{tnl // n_instances}\n\n"
1996             )
1997
1998         IPsecUtil._close_and_copy_ipsec_script_files(
1999             "DUT1", nodes, n_instances, dut1_scripts
2000         )
2001         IPsecUtil._close_and_copy_ipsec_script_files(
2002             "DUT2", nodes, n_instances, dut2_scripts
2003         )
2004
2005     @staticmethod
2006     def vpp_ipsec_add_multiple_tunnels(
2007         nodes,
2008         interface1,
2009         interface2,
2010         n_tunnels,
2011         crypto_alg,
2012         integ_alg,
2013         tunnel_ip1,
2014         tunnel_ip2,
2015         raddr_ip1,
2016         raddr_ip2,
2017         raddr_range,
2018         tunnel_addr_incr=True,
2019     ):
2020         """Create multiple IPsec tunnels between two VPP nodes.
2021
2022         :param nodes: VPP nodes to create tunnels.
2023         :param interface1: Interface name or sw_if_index on node 1.
2024         :param interface2: Interface name or sw_if_index on node 2.
2025         :param n_tunnels: Number of tunnels to create.
2026         :param crypto_alg: The encryption algorithm name.
2027         :param integ_alg: The integrity algorithm name.
2028         :param tunnel_ip1: Tunnel node1 IPv4 address.
2029         :param tunnel_ip2: Tunnel node2 IPv4 address.
2030         :param raddr_ip1: Policy selector remote IPv4 start address for the
2031             first tunnel in direction node1->node2.
2032         :param raddr_ip2: Policy selector remote IPv4 start address for the
2033             first tunnel in direction node2->node1.
2034         :param raddr_range: Mask specifying range of Policy selector Remote
2035             IPv4 addresses. Valid values are from 1 to 32.
2036         :param tunnel_addr_incr: Enable or disable tunnel IP address
2037             incremental step.
2038         :type nodes: dict
2039         :type interface1: str or int
2040         :type interface2: str or int
2041         :type n_tunnels: int
2042         :type crypto_alg: CryptoAlg
2043         :type integ_alg: Optional[IntegAlg]
2044         :type tunnel_ip1: str
2045         :type tunnel_ip2: str
2046         :type raddr_ip1: string
2047         :type raddr_ip2: string
2048         :type raddr_range: int
2049         :type tunnel_addr_incr: bool
2050         """
2051         spd_id = 1
2052         p_hi = 100
2053         p_lo = 10
2054         sa_id_1 = 100000
2055         sa_id_2 = 200000
2056         spi_1 = 300000
2057         spi_2 = 400000
2058
2059         crypto_key = gen_key(
2060             IPsecUtil.get_crypto_alg_key_len(crypto_alg)
2061         ).decode()
2062         integ_key = (
2063             gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)).decode()
2064             if integ_alg
2065             else ""
2066         )
2067
2068         rmac = (
2069             Topology.get_interface_mac(nodes["DUT2"], interface2)
2070             if "DUT2" in nodes.keys()
2071             else Topology.get_interface_mac(nodes["TG"], interface2)
2072         )
2073         IPsecUtil.vpp_ipsec_set_ip_route(
2074             nodes["DUT1"],
2075             n_tunnels,
2076             tunnel_ip1,
2077             raddr_ip2,
2078             tunnel_ip2,
2079             interface1,
2080             raddr_range,
2081             rmac,
2082         )
2083
2084         IPsecUtil.vpp_ipsec_add_spd(nodes["DUT1"], spd_id)
2085         IPsecUtil.vpp_ipsec_spd_add_if(nodes["DUT1"], spd_id, interface1)
2086
2087         addr_incr = (
2088             1 << (128 - 96)
2089             if ip_address(tunnel_ip1).version == 6
2090             else 1 << (32 - 24)
2091         )
2092         for i in range(n_tunnels // (addr_incr**2) + 1):
2093             dut1_local_outbound_range = ip_network(
2094                 f"{ip_address(tunnel_ip1) + i*(addr_incr**3)}/8", False
2095             ).with_prefixlen
2096             dut1_remote_outbound_range = ip_network(
2097                 f"{ip_address(tunnel_ip2) + i*(addr_incr**3)}/8", False
2098             ).with_prefixlen
2099
2100             IPsecUtil.vpp_ipsec_add_spd_entry(
2101                 nodes["DUT1"],
2102                 spd_id,
2103                 p_hi,
2104                 PolicyAction.BYPASS,
2105                 inbound=False,
2106                 proto=50,
2107                 laddr_range=dut1_local_outbound_range,
2108                 raddr_range=dut1_remote_outbound_range,
2109             )
2110             IPsecUtil.vpp_ipsec_add_spd_entry(
2111                 nodes["DUT1"],
2112                 spd_id,
2113                 p_hi,
2114                 PolicyAction.BYPASS,
2115                 inbound=True,
2116                 proto=50,
2117                 laddr_range=dut1_remote_outbound_range,
2118                 raddr_range=dut1_local_outbound_range,
2119             )
2120
2121         IPsecUtil.vpp_ipsec_add_sad_entries(
2122             nodes["DUT1"],
2123             n_tunnels,
2124             sa_id_1,
2125             spi_1,
2126             crypto_alg,
2127             crypto_key,
2128             integ_alg,
2129             integ_key,
2130             tunnel_ip1,
2131             tunnel_ip2,
2132             tunnel_addr_incr,
2133         )
2134
2135         IPsecUtil.vpp_ipsec_add_spd_entries(
2136             nodes["DUT1"],
2137             n_tunnels,
2138             spd_id,
2139             priority=ObjIncrement(p_lo, 0),
2140             action=PolicyAction.PROTECT,
2141             inbound=False,
2142             sa_id=ObjIncrement(sa_id_1, 1),
2143             raddr_range=NetworkIncrement(ip_network(raddr_ip2)),
2144         )
2145
2146         IPsecUtil.vpp_ipsec_add_sad_entries(
2147             nodes["DUT1"],
2148             n_tunnels,
2149             sa_id_2,
2150             spi_2,
2151             crypto_alg,
2152             crypto_key,
2153             integ_alg,
2154             integ_key,
2155             tunnel_ip2,
2156             tunnel_ip1,
2157             tunnel_addr_incr,
2158         )
2159         IPsecUtil.vpp_ipsec_add_spd_entries(
2160             nodes["DUT1"],
2161             n_tunnels,
2162             spd_id,
2163             priority=ObjIncrement(p_lo, 0),
2164             action=PolicyAction.PROTECT,
2165             inbound=True,
2166             sa_id=ObjIncrement(sa_id_2, 1),
2167             raddr_range=NetworkIncrement(ip_network(raddr_ip1)),
2168         )
2169
2170         if "DUT2" in nodes.keys():
2171             rmac = Topology.get_interface_mac(nodes["DUT1"], interface1)
2172             IPsecUtil.vpp_ipsec_set_ip_route(
2173                 nodes["DUT2"],
2174                 n_tunnels,
2175                 tunnel_ip2,
2176                 raddr_ip1,
2177                 tunnel_ip1,
2178                 interface2,
2179                 raddr_range,
2180                 rmac,
2181             )
2182
2183             IPsecUtil.vpp_ipsec_add_spd(nodes["DUT2"], spd_id)
2184             IPsecUtil.vpp_ipsec_spd_add_if(nodes["DUT2"], spd_id, interface2)
2185             for i in range(n_tunnels // (addr_incr**2) + 1):
2186                 dut2_local_outbound_range = ip_network(
2187                     f"{ip_address(tunnel_ip1) + i*(addr_incr**3)}/8", False
2188                 ).with_prefixlen
2189                 dut2_remote_outbound_range = ip_network(
2190                     f"{ip_address(tunnel_ip2) + i*(addr_incr**3)}/8", False
2191                 ).with_prefixlen
2192
2193                 IPsecUtil.vpp_ipsec_add_spd_entry(
2194                     nodes["DUT2"],
2195                     spd_id,
2196                     p_hi,
2197                     PolicyAction.BYPASS,
2198                     inbound=False,
2199                     proto=50,
2200                     laddr_range=dut2_remote_outbound_range,
2201                     raddr_range=dut2_local_outbound_range,
2202                 )
2203                 IPsecUtil.vpp_ipsec_add_spd_entry(
2204                     nodes["DUT2"],
2205                     spd_id,
2206                     p_hi,
2207                     PolicyAction.BYPASS,
2208                     inbound=True,
2209                     proto=50,
2210                     laddr_range=dut2_local_outbound_range,
2211                     raddr_range=dut2_remote_outbound_range,
2212                 )
2213
2214             IPsecUtil.vpp_ipsec_add_sad_entries(
2215                 nodes["DUT2"],
2216                 n_tunnels,
2217                 sa_id_1,
2218                 spi_1,
2219                 crypto_alg,
2220                 crypto_key,
2221                 integ_alg,
2222                 integ_key,
2223                 tunnel_ip1,
2224                 tunnel_ip2,
2225                 tunnel_addr_incr,
2226             )
2227             IPsecUtil.vpp_ipsec_add_spd_entries(
2228                 nodes["DUT2"],
2229                 n_tunnels,
2230                 spd_id,
2231                 priority=ObjIncrement(p_lo, 0),
2232                 action=PolicyAction.PROTECT,
2233                 inbound=True,
2234                 sa_id=ObjIncrement(sa_id_1, 1),
2235                 raddr_range=NetworkIncrement(ip_network(raddr_ip2)),
2236             )
2237
2238             IPsecUtil.vpp_ipsec_add_sad_entries(
2239                 nodes["DUT2"],
2240                 n_tunnels,
2241                 sa_id_2,
2242                 spi_2,
2243                 crypto_alg,
2244                 crypto_key,
2245                 integ_alg,
2246                 integ_key,
2247                 tunnel_ip2,
2248                 tunnel_ip1,
2249                 tunnel_addr_incr,
2250             )
2251             IPsecUtil.vpp_ipsec_add_spd_entries(
2252                 nodes["DUT2"],
2253                 n_tunnels,
2254                 spd_id,
2255                 priority=ObjIncrement(p_lo, 0),
2256                 action=PolicyAction.PROTECT,
2257                 inbound=False,
2258                 sa_id=ObjIncrement(sa_id_2, 1),
2259                 raddr_range=NetworkIncrement(ip_network(raddr_ip1)),
2260             )
2261
2262     @staticmethod
2263     def vpp_ipsec_show_all(node):
2264         """Run "show ipsec all" debug CLI command.
2265
2266         :param node: Node to run command on.
2267         :type node: dict
2268         """
2269         PapiSocketExecutor.run_cli_cmd(node, "show ipsec all")
2270
2271     @staticmethod
2272     def show_ipsec_security_association(node):
2273         """Show IPSec security association.
2274
2275         :param node: DUT node.
2276         :type node: dict
2277         """
2278         cmd = "ipsec_sa_v5_dump"
2279         PapiSocketExecutor.dump_and_log(node, [cmd])
2280
2281     @staticmethod
2282     def vpp_ipsec_flow_enale_rss(node, proto, type, function="default"):
2283         """Ipsec flow enable rss action.
2284
2285         :param node: DUT node.
2286         :param proto: The flow protocol.
2287         :param type: RSS type.
2288         :param function: RSS function.
2289
2290         :type node: dict
2291         :type proto: str
2292         :type type: str
2293         :type function: str
2294         :returns: flow_index.
2295         """
2296         # TODO: to be fixed to use full PAPI when it is ready in VPP
2297         cmd = (
2298             f"test flow add src-ip any proto {proto} rss function "
2299             f"{function} rss types {type}"
2300         )
2301         stdout = PapiSocketExecutor.run_cli_cmd(node, cmd)
2302         flow_index = stdout.split()[1]
2303
2304         return flow_index
2305
2306     @staticmethod
2307     def vpp_create_ipsec_flows_on_dut(
2308         node, n_flows, rx_queues, spi_start, interface
2309     ):
2310         """Create mutiple ipsec flows and enable flows onto interface.
2311
2312         :param node: DUT node.
2313         :param n_flows: Number of flows to create.
2314         :param rx_queues: NUmber of RX queues.
2315         :param spi_start: The start spi.
2316         :param interface: Name of the interface.
2317
2318         :type node: dict
2319         :type n_flows: int
2320         :type rx_queues: int
2321         :type spi_start: int
2322         :type interface: str
2323         :returns: flow_index.
2324         """
2325
2326         for i in range(0, n_flows):
2327             rx_queue = i % rx_queues
2328             spi = spi_start + i
2329             flow_index = FlowUtil.vpp_create_ip4_ipsec_flow(
2330                 node, "ESP", spi, "redirect-to-queue", value=rx_queue
2331             )
2332             FlowUtil.vpp_flow_enable(node, interface, flow_index)