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