2e6574f40e31f6a1da7d28a6ce126865db53eb2e
[csit.git] / resources / libraries / python / IPsecUtil.py
1 # Copyright (c) 2020 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """IPsec utilities library."""
15
16 import os
17
18 from enum import Enum, IntEnum
19 from io import open
20 from random import choice
21 from string import ascii_letters
22
23 from ipaddress import ip_network, ip_address
24
25 from resources.libraries.python.InterfaceUtil import InterfaceUtil, \
26     InterfaceStatusFlags
27 from resources.libraries.python.IPAddress import IPAddress
28 from resources.libraries.python.IPUtil import IPUtil
29 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
30 from resources.libraries.python.ssh import scp_node
31 from resources.libraries.python.topology import Topology
32 from resources.libraries.python.VatExecutor import VatExecutor
33
34
35 def gen_key(length):
36     """Generate random string as a key.
37
38     :param length: Length of generated payload.
39     :type length: int
40     :returns: The generated payload.
41     :rtype: bytes
42     """
43     return u"".join(
44         choice(ascii_letters) for _ in range(length)
45     ).encode(encoding=u"utf-8")
46
47
48 class PolicyAction(Enum):
49     """Policy actions."""
50     BYPASS = (u"bypass", 0)
51     DISCARD = (u"discard", 1)
52     PROTECT = (u"protect", 3)
53
54     def __init__(self, policy_name, policy_int_repr):
55         self.policy_name = policy_name
56         self.policy_int_repr = policy_int_repr
57
58
59 class CryptoAlg(Enum):
60     """Encryption algorithms."""
61     AES_CBC_128 = (u"aes-cbc-128", 1, u"AES-CBC", 16)
62     AES_CBC_256 = (u"aes-cbc-256", 3, u"AES-CBC", 32)
63     AES_GCM_128 = (u"aes-gcm-128", 7, u"AES-GCM", 16)
64     AES_GCM_256 = (u"aes-gcm-256", 9, u"AES-GCM", 32)
65
66     def __init__(self, alg_name, alg_int_repr, scapy_name, key_len):
67         self.alg_name = alg_name
68         self.alg_int_repr = alg_int_repr
69         self.scapy_name = scapy_name
70         self.key_len = key_len
71
72
73 class IntegAlg(Enum):
74     """Integrity algorithm."""
75     SHA_256_128 = (u"sha-256-128", 4, u"SHA2-256-128", 32)
76     SHA_512_256 = (u"sha-512-256", 6, u"SHA2-512-256", 64)
77
78     def __init__(self, alg_name, alg_int_repr, scapy_name, key_len):
79         self.alg_name = alg_name
80         self.alg_int_repr = alg_int_repr
81         self.scapy_name = scapy_name
82         self.key_len = key_len
83
84
85 class IPsecProto(IntEnum):
86     """IPsec protocol."""
87     IPSEC_API_PROTO_ESP = 50
88     IPSEC_API_PROTO_AH = 51
89
90
91 class IPsecSadFlags(IntEnum):
92     """IPsec Security Association Database flags."""
93     IPSEC_API_SAD_FLAG_NONE = 0
94     IPSEC_API_SAD_FLAG_IS_TUNNEL = 4
95     IPSEC_API_SAD_FLAG_IS_TUNNEL_V6 = 8
96
97
98 class IPsecUtil:
99     """IPsec utilities."""
100
101     @staticmethod
102     def policy_action_bypass():
103         """Return policy action bypass.
104
105         :returns: PolicyAction enum BYPASS object.
106         :rtype: PolicyAction
107         """
108         return PolicyAction.BYPASS
109
110     @staticmethod
111     def policy_action_discard():
112         """Return policy action discard.
113
114         :returns: PolicyAction enum DISCARD object.
115         :rtype: PolicyAction
116         """
117         return PolicyAction.DISCARD
118
119     @staticmethod
120     def policy_action_protect():
121         """Return policy action protect.
122
123         :returns: PolicyAction enum PROTECT object.
124         :rtype: PolicyAction
125         """
126         return PolicyAction.PROTECT
127
128     @staticmethod
129     def crypto_alg_aes_cbc_128():
130         """Return encryption algorithm aes-cbc-128.
131
132         :returns: CryptoAlg enum AES_CBC_128 object.
133         :rtype: CryptoAlg
134         """
135         return CryptoAlg.AES_CBC_128
136
137     @staticmethod
138     def crypto_alg_aes_cbc_256():
139         """Return encryption algorithm aes-cbc-256.
140
141         :returns: CryptoAlg enum AES_CBC_256 object.
142         :rtype: CryptoAlg
143         """
144         return CryptoAlg.AES_CBC_256
145
146     @staticmethod
147     def crypto_alg_aes_gcm_128():
148         """Return encryption algorithm aes-gcm-128.
149
150         :returns: CryptoAlg enum AES_GCM_128 object.
151         :rtype: CryptoAlg
152         """
153         return CryptoAlg.AES_GCM_128
154
155     @staticmethod
156     def crypto_alg_aes_gcm_256():
157         """Return encryption algorithm aes-gcm-256.
158
159         :returns: CryptoAlg enum AES_GCM_128 object.
160         :rtype: CryptoAlg
161         """
162         return CryptoAlg.AES_GCM_256
163
164     @staticmethod
165     def get_crypto_alg_key_len(crypto_alg):
166         """Return encryption algorithm key length.
167
168         :param crypto_alg: Encryption algorithm.
169         :type crypto_alg: CryptoAlg
170         :returns: Key length.
171         :rtype: int
172         """
173         return crypto_alg.key_len
174
175     @staticmethod
176     def get_crypto_alg_scapy_name(crypto_alg):
177         """Return encryption algorithm scapy name.
178
179         :param crypto_alg: Encryption algorithm.
180         :type crypto_alg: CryptoAlg
181         :returns: Algorithm scapy name.
182         :rtype: str
183         """
184         return crypto_alg.scapy_name
185
186     @staticmethod
187     def integ_alg_sha_256_128():
188         """Return integrity algorithm SHA-256-128.
189
190         :returns: IntegAlg enum SHA_256_128 object.
191         :rtype: IntegAlg
192         """
193         return IntegAlg.SHA_256_128
194
195     @staticmethod
196     def integ_alg_sha_512_256():
197         """Return integrity algorithm SHA-512-256.
198
199         :returns: IntegAlg enum SHA_512_256 object.
200         :rtype: IntegAlg
201         """
202         return IntegAlg.SHA_512_256
203
204     @staticmethod
205     def get_integ_alg_key_len(integ_alg):
206         """Return integrity algorithm key length.
207
208         :param integ_alg: Integrity algorithm.
209         :type integ_alg: IntegAlg
210         :returns: Key length.
211         :rtype: int
212         """
213         return integ_alg.key_len
214
215     @staticmethod
216     def get_integ_alg_scapy_name(integ_alg):
217         """Return integrity algorithm scapy name.
218
219         :param integ_alg: Integrity algorithm.
220         :type integ_alg: IntegAlg
221         :returns: Algorithm scapy name.
222         :rtype: str
223         """
224         return integ_alg.scapy_name
225
226     @staticmethod
227     def ipsec_proto_esp():
228         """Return IPSec protocol ESP.
229
230         :returns: IPsecProto enum ESP object.
231         :rtype: IPsecProto
232         """
233         return int(IPsecProto.IPSEC_API_PROTO_ESP)
234
235     @staticmethod
236     def ipsec_proto_ah():
237         """Return IPSec protocol AH.
238
239         :returns: IPsecProto enum AH object.
240         :rtype: IPsecProto
241         """
242         return int(IPsecProto.IPSEC_API_PROTO_AH)
243
244     @staticmethod
245     def vpp_ipsec_select_backend(node, protocol, index=1):
246         """Select IPsec backend.
247
248         :param node: VPP node to select IPsec backend on.
249         :param protocol: IPsec protocol.
250         :param index: Backend index.
251         :type node: dict
252         :type protocol: IPsecProto
253         :type index: int
254         :raises RuntimeError: If failed to select IPsec backend or if no API
255             reply received.
256         """
257         cmd = u"ipsec_select_backend"
258         err_msg = f"Failed to select IPsec backend on host {node[u'host']}"
259         args = dict(
260             protocol=protocol,
261             index=index
262         )
263         with PapiSocketExecutor(node) as papi_exec:
264             papi_exec.add(cmd, **args).get_reply(err_msg)
265
266     @staticmethod
267     def vpp_ipsec_add_sad_entry(
268             node, sad_id, spi, crypto_alg, crypto_key, integ_alg=None,
269             integ_key=u"", tunnel_src=None, tunnel_dst=None):
270         """Create Security Association Database entry on the VPP node.
271
272         :param node: VPP node to add SAD entry on.
273         :param sad_id: SAD entry ID.
274         :param spi: Security Parameter Index of this SAD entry.
275         :param crypto_alg: The encryption algorithm name.
276         :param crypto_key: The encryption key string.
277         :param integ_alg: The integrity algorithm name.
278         :param integ_key: The integrity key string.
279         :param tunnel_src: Tunnel header source IPv4 or IPv6 address. If not
280             specified ESP transport mode is used.
281         :param tunnel_dst: Tunnel header destination IPv4 or IPv6 address. If
282             not specified ESP transport mode is used.
283         :type node: dict
284         :type sad_id: int
285         :type spi: int
286         :type crypto_alg: CryptoAlg
287         :type crypto_key: str
288         :type integ_alg: IntegAlg
289         :type integ_key: str
290         :type tunnel_src: str
291         :type tunnel_dst: str
292         """
293         if isinstance(crypto_key, str):
294             crypto_key = crypto_key.encode(encoding=u"utf-8")
295         if isinstance(integ_key, str):
296             integ_key = integ_key.encode(encoding=u"utf-8")
297         ckey = dict(
298             length=len(crypto_key),
299             data=crypto_key
300         )
301         ikey = dict(
302             length=len(integ_key),
303             data=integ_key if integ_key else 0
304         )
305
306         flags = int(IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE)
307         if tunnel_src and tunnel_dst:
308             flags = flags | int(IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL)
309             src_addr = ip_address(tunnel_src)
310             dst_addr = ip_address(tunnel_dst)
311             if src_addr.version == 6:
312                 flags = \
313                     flags | int(IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6)
314         else:
315             src_addr = u""
316             dst_addr = u""
317
318         cmd = u"ipsec_sad_entry_add_del"
319         err_msg = f"Failed to add Security Association Database entry " \
320             f"on host {node[u'host']}"
321         sad_entry = dict(
322             sad_id=int(sad_id),
323             spi=int(spi),
324             crypto_algorithm=crypto_alg.alg_int_repr,
325             crypto_key=ckey,
326             integrity_algorithm=integ_alg.alg_int_repr if integ_alg else 0,
327             integrity_key=ikey,
328             flags=flags,
329             tunnel_src=str(src_addr),
330             tunnel_dst=str(dst_addr),
331             protocol=int(IPsecProto.IPSEC_API_PROTO_ESP)
332         )
333         args = dict(
334             is_add=True,
335             entry=sad_entry
336         )
337         with PapiSocketExecutor(node) as papi_exec:
338             papi_exec.add(cmd, **args).get_reply(err_msg)
339
340     @staticmethod
341     def vpp_ipsec_add_sad_entries(
342             node, n_entries, sad_id, spi, crypto_alg, crypto_key,
343             integ_alg=None, integ_key=u"", tunnel_src=None, tunnel_dst=None):
344         """Create multiple Security Association Database entries on VPP node.
345
346         :param node: VPP node to add SAD entry on.
347         :param n_entries: Number of SAD entries to be created.
348         :param sad_id: First SAD entry ID. All subsequent SAD entries will have
349             id incremented by 1.
350         :param spi: Security Parameter Index of first SAD entry. All subsequent
351             SAD entries will have spi incremented by 1.
352         :param crypto_alg: The encryption algorithm name.
353         :param crypto_key: The encryption key string.
354         :param integ_alg: The integrity algorithm name.
355         :param integ_key: The integrity key string.
356         :param tunnel_src: Tunnel header source IPv4 or IPv6 address. If not
357             specified ESP transport mode is used.
358         :param tunnel_dst: Tunnel header destination IPv4 or IPv6 address. If
359             not specified ESP transport mode is used.
360         :type node: dict
361         :type n_entries: int
362         :type sad_id: int
363         :type spi: int
364         :type crypto_alg: CryptoAlg
365         :type crypto_key: str
366         :type integ_alg: IntegAlg
367         :type integ_key: str
368         :type tunnel_src: str
369         :type tunnel_dst: str
370         """
371         if isinstance(crypto_key, str):
372             crypto_key = crypto_key.encode(encoding=u"utf-8")
373         if isinstance(integ_key, str):
374             integ_key = integ_key.encode(encoding=u"utf-8")
375         if tunnel_src and tunnel_dst:
376             src_addr = ip_address(tunnel_src)
377             dst_addr = ip_address(tunnel_dst)
378         else:
379             src_addr = u""
380             dst_addr = u""
381
382         addr_incr = 1 << (128 - 96) if src_addr.version == 6 \
383             else 1 << (32 - 24)
384
385         if int(n_entries) > 10:
386             tmp_filename = f"/tmp/ipsec_sad_{sad_id}_add_del_entry.script"
387
388             with open(tmp_filename, 'w') as tmp_file:
389                 for i in range(n_entries):
390                     integ = f"integ-alg {integ_alg.alg_name} " \
391                         f"integ-key {integ_key.hex()}" \
392                         if integ_alg else u""
393                     tunnel = f"tunnel-src {src_addr + i * addr_incr} " \
394                         f"tunnel-dst {dst_addr + i * addr_incr}" \
395                         if tunnel_src and tunnel_dst else u""
396                     conf = f"exec ipsec sa add {sad_id + i} esp spi {spi + i} "\
397                         f"crypto-alg {crypto_alg.alg_name} " \
398                         f"crypto-key {crypto_key.hex()} " \
399                         f"{integ} {tunnel}\n"
400                     tmp_file.write(conf)
401             vat = VatExecutor()
402             vat.execute_script(
403                 tmp_filename, node, timeout=300, json_out=False,
404                 copy_on_execute=True
405             )
406             os.remove(tmp_filename)
407             return
408
409         ckey = dict(
410             length=len(crypto_key),
411             data=crypto_key
412         )
413         ikey = dict(
414             length=len(integ_key),
415             data=integ_key if integ_key else 0
416         )
417
418         flags = int(IPsecSadFlags.IPSEC_API_SAD_FLAG_NONE)
419         if tunnel_src and tunnel_dst:
420             flags = flags | int(IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL)
421             if src_addr.version == 6:
422                 flags = flags | int(
423                     IPsecSadFlags.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6
424                 )
425
426         cmd = u"ipsec_sad_entry_add_del"
427         err_msg = f"Failed to add Security Association Database entry " \
428             f"on host {node[u'host']}"
429
430         sad_entry = dict(
431             sad_id=int(sad_id),
432             spi=int(spi),
433             crypto_algorithm=crypto_alg.alg_int_repr,
434             crypto_key=ckey,
435             integrity_algorithm=integ_alg.alg_int_repr if integ_alg else 0,
436             integrity_key=ikey,
437             flags=flags,
438             tunnel_src=str(src_addr),
439             tunnel_dst=str(dst_addr),
440             protocol=int(IPsecProto.IPSEC_API_PROTO_ESP)
441         )
442         args = dict(
443             is_add=True,
444             entry=sad_entry
445         )
446         with PapiSocketExecutor(node) as papi_exec:
447             for i in range(n_entries):
448                 args[u"entry"][u"sad_id"] = int(sad_id) + i
449                 args[u"entry"][u"spi"] = int(spi) + i
450                 args[u"entry"][u"tunnel_src"] = str(src_addr + i * addr_incr) \
451                     if tunnel_src and tunnel_dst else src_addr
452                 args[u"entry"][u"tunnel_dst"] = str(dst_addr + i * addr_incr) \
453                     if tunnel_src and tunnel_dst else dst_addr
454                 history = bool(not 1 < i < n_entries - 2)
455                 papi_exec.add(cmd, history=history, **args)
456             papi_exec.get_replies(err_msg)
457
458     @staticmethod
459     def vpp_ipsec_set_ip_route(
460             node, n_tunnels, tunnel_src, traffic_addr, tunnel_dst, interface,
461             raddr_range):
462         """Set IP address and route on interface.
463
464         :param node: VPP node to add config on.
465         :param n_tunnels: Number of tunnels to create.
466         :param tunnel_src: Tunnel header source IPv4 or IPv6 address.
467         :param traffic_addr: Traffic destination IP address to route.
468         :param tunnel_dst: Tunnel header destination IPv4 or IPv6 address.
469         :param interface: Interface key on node 1.
470         :param raddr_range: Mask specifying range of Policy selector Remote IP
471             addresses. Valid values are from 1 to 32 in case of IPv4 and to 128
472             in case of IPv6.
473         :type node: dict
474         :type n_tunnels: int
475         :type tunnel_src: str
476         :type traffic_addr: str
477         :type tunnel_dst: str
478         :type interface: str
479         :type raddr_range: int
480         """
481         laddr = ip_address(tunnel_src)
482         raddr = ip_address(tunnel_dst)
483         taddr = ip_address(traffic_addr)
484         addr_incr = 1 << (128 - raddr_range) if laddr.version == 6 \
485             else 1 << (32 - raddr_range)
486
487         if int(n_tunnels) > 10:
488             tmp_filename = u"/tmp/ipsec_set_ip.script"
489
490             with open(tmp_filename, 'w') as tmp_file:
491                 if_name = Topology.get_interface_name(node, interface)
492                 for i in range(n_tunnels):
493                     conf = f"exec set interface ip address {if_name} " \
494                         f"{laddr + i * addr_incr}/{raddr_range}\n" \
495                         f"exec ip route add {taddr + i}/" \
496                         f"{128 if taddr.version == 6 else 32} " \
497                         f"via {raddr + i * addr_incr} {if_name}\n"
498                     tmp_file.write(conf)
499             vat = VatExecutor()
500             vat.execute_script(
501                 tmp_filename, node, timeout=300, json_out=False,
502                 copy_on_execute=True
503             )
504             os.remove(tmp_filename)
505             return
506
507         cmd1 = u"sw_interface_add_del_address"
508         args1 = dict(
509             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
510             is_add=True,
511             del_all=False,
512             prefix=None
513         )
514         cmd2 = u"ip_route_add_del"
515         args2 = dict(
516             is_add=1,
517             is_multipath=0,
518             route=None
519         )
520         err_msg = f"Failed to configure IP addresses and IP routes " \
521             f"on interface {interface} on host {node[u'host']}"
522
523         with PapiSocketExecutor(node) as papi_exec:
524             for i in range(n_tunnels):
525                 args1[u"prefix"] = IPUtil.create_prefix_object(
526                     laddr + i * addr_incr, raddr_range
527                 )
528                 args2[u"route"] = IPUtil.compose_vpp_route_structure(
529                     node, taddr + i,
530                     prefix_len=128 if taddr.version == 6 else 32,
531                     interface=interface, gateway=raddr + i * addr_incr
532                 )
533                 history = bool(not 1 < i < n_tunnels - 2)
534                 papi_exec.add(cmd1, history=history, **args1).\
535                     add(cmd2, history=history, **args2)
536             papi_exec.get_replies(err_msg)
537
538     @staticmethod
539     def vpp_ipsec_add_spd(node, spd_id):
540         """Create Security Policy Database on the VPP node.
541
542         :param node: VPP node to add SPD on.
543         :param spd_id: SPD ID.
544         :type node: dict
545         :type spd_id: int
546         """
547         cmd = u"ipsec_spd_add_del"
548         err_msg = f"Failed to add Security Policy Database " \
549             f"on host {node[u'host']}"
550         args = dict(
551             is_add=True,
552             spd_id=int(spd_id)
553         )
554         with PapiSocketExecutor(node) as papi_exec:
555             papi_exec.add(cmd, **args).get_reply(err_msg)
556
557     @staticmethod
558     def vpp_ipsec_spd_add_if(node, spd_id, interface):
559         """Add interface to the Security Policy Database.
560
561         :param node: VPP node.
562         :param spd_id: SPD ID to add interface on.
563         :param interface: Interface name or sw_if_index.
564         :type node: dict
565         :type spd_id: int
566         :type interface: str or int
567         """
568         cmd = u"ipsec_interface_add_del_spd"
569         err_msg = f"Failed to add interface {interface} to Security Policy " \
570             f"Database {spd_id} on host {node[u'host']}"
571         args = dict(
572             is_add=True,
573             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
574             spd_id=int(spd_id)
575         )
576         with PapiSocketExecutor(node) as papi_exec:
577             papi_exec.add(cmd, **args).get_reply(err_msg)
578
579     @staticmethod
580     def vpp_ipsec_policy_add(
581             node, spd_id, priority, action, inbound=True, sa_id=None,
582             laddr_range=None, raddr_range=None, proto=None, lport_range=None,
583             rport_range=None, is_ipv6=False):
584         """Create Security Policy Database entry on the VPP node.
585
586         :param node: VPP node to add SPD entry on.
587         :param spd_id: SPD ID to add entry on.
588         :param priority: SPD entry priority, higher number = higher priority.
589         :param action: Policy action.
590         :param inbound: If True policy is for inbound traffic, otherwise
591             outbound.
592         :param sa_id: SAD entry ID for protect action.
593         :param laddr_range: Policy selector local IPv4 or IPv6 address range in
594             format IP/prefix or IP/mask. If no mask is provided,
595             it's considered to be /32.
596         :param raddr_range: Policy selector remote IPv4 or IPv6 address range in
597             format IP/prefix or IP/mask. If no mask is provided,
598             it's considered to be /32.
599         :param proto: Policy selector next layer protocol number.
600         :param lport_range: Policy selector local TCP/UDP port range in format
601             <port_start>-<port_end>.
602         :param rport_range: Policy selector remote TCP/UDP port range in format
603             <port_start>-<port_end>.
604         :param is_ipv6: True in case of IPv6 policy when IPv6 address range is
605             not defined so it will default to address ::/0, otherwise False.
606         :type node: dict
607         :type spd_id: int
608         :type priority: int
609         :type action: PolicyAction
610         :type inbound: bool
611         :type sa_id: int
612         :type laddr_range: string
613         :type raddr_range: string
614         :type proto: int
615         :type lport_range: string
616         :type rport_range: string
617         :type is_ipv6: bool
618         """
619         if laddr_range is None:
620             laddr_range = u"::/0" if is_ipv6 else u"0.0.0.0/0"
621
622         if raddr_range is None:
623             raddr_range = u"::/0" if is_ipv6 else u"0.0.0.0/0"
624
625         cmd = u"ipsec_spd_entry_add_del"
626         err_msg = f"Failed to add entry to Security Policy Database {spd_id} " \
627             f"on host {node[u'host']}"
628
629         spd_entry = dict(
630             spd_id=int(spd_id),
631             priority=int(priority),
632             is_outbound=not inbound,
633             sa_id=int(sa_id) if sa_id else 0,
634             policy=action.policy_int_repr,
635             protocol=int(proto) if proto else 0,
636             remote_address_start=IPAddress.create_ip_address_object(
637                 ip_network(raddr_range, strict=False).network_address
638             ),
639             remote_address_stop=IPAddress.create_ip_address_object(
640                 ip_network(raddr_range, strict=False).broadcast_address
641             ),
642             local_address_start=IPAddress.create_ip_address_object(
643                 ip_network(laddr_range, strict=False).network_address
644             ),
645             local_address_stop=IPAddress.create_ip_address_object(
646                 ip_network(laddr_range, strict=False).broadcast_address
647             ),
648             remote_port_start=int(rport_range.split(u"-")[0]) if rport_range
649             else 0,
650             remote_port_stop=int(rport_range.split(u"-")[1]) if rport_range
651             else 65535,
652             local_port_start=int(lport_range.split(u"-")[0]) if lport_range
653             else 0,
654             local_port_stop=int(lport_range.split(u"-")[1]) if rport_range
655             else 65535
656         )
657         args = dict(
658             is_add=True,
659             entry=spd_entry
660         )
661         with PapiSocketExecutor(node) as papi_exec:
662             papi_exec.add(cmd, **args).get_reply(err_msg)
663
664     @staticmethod
665     def vpp_ipsec_spd_add_entries(
666             node, n_entries, spd_id, priority, inbound, sa_id, raddr_ip,
667             raddr_range=0):
668         """Create multiple Security Policy Database entries on the VPP node.
669
670         :param node: VPP node to add SPD entries on.
671         :param n_entries: Number of SPD entries to be added.
672         :param spd_id: SPD ID to add entries on.
673         :param priority: SPD entries priority, higher number = higher priority.
674         :param inbound: If True policy is for inbound traffic, otherwise
675             outbound.
676         :param sa_id: SAD entry ID for first entry. Each subsequent entry will
677             SAD entry ID incremented by 1.
678         :param raddr_ip: Policy selector remote IPv4 start address for the first
679             entry. Remote IPv4 end address will be calculated depending on
680             raddr_range parameter. Each subsequent entry will have start address
681             next after IPv4 end address of previous entry.
682         :param raddr_range: Required IP addres range.
683         :type node: dict
684         :type n_entries: int
685         :type spd_id: int
686         :type priority: int
687         :type inbound: bool
688         :type sa_id: int
689         :type raddr_ip: str
690         :type raddr_range: int
691         """
692         raddr_ip = ip_address(raddr_ip)
693         if int(n_entries) > 10:
694             tmp_filename = f"/tmp/ipsec_spd_{sa_id}_add_del_entry.script"
695
696             with open(tmp_filename, 'w') as tmp_file:
697                 for i in range(n_entries):
698                     direction = u'inbound' if inbound else u'outbound'
699                     tunnel = f"exec ipsec policy add spd {spd_id} " \
700                         f"priority {priority} {direction} " \
701                         f"action protect sa {sa_id+i} " \
702                         f"remote-ip-range {raddr_ip + i * (raddr_range + 1)} " \
703                         f"- {raddr_ip + (i  + 1) * raddr_range + i} " \
704                         f"local-ip-range 0.0.0.0 - 255.255.255.255\n"
705                     tmp_file.write(tunnel)
706             VatExecutor().execute_script(
707                 tmp_filename, node, timeout=300, json_out=False,
708                 copy_on_execute=True
709             )
710             os.remove(tmp_filename)
711             return
712
713         laddr_range = u"::/0" if raddr_ip.version == 6 else u"0.0.0.0/0"
714
715         cmd = u"ipsec_spd_entry_add_del"
716         err_msg = f"ailed to add entry to Security Policy Database '{spd_id} " \
717             f"on host {node[u'host']}"
718
719         spd_entry = dict(
720             spd_id=int(spd_id),
721             priority=int(priority),
722             is_outbound=not inbound,
723             sa_id=int(sa_id) if sa_id else 0,
724             policy=IPsecUtil.policy_action_protect().policy_int_repr,
725             protocol=0,
726             remote_address_start=IPAddress.create_ip_address_object(raddr_ip),
727             remote_address_stop=IPAddress.create_ip_address_object(raddr_ip),
728             local_address_start=IPAddress.create_ip_address_object(
729                 ip_network(laddr_range, strict=False).network_address
730             ),
731             local_address_stop=IPAddress.create_ip_address_object(
732                 ip_network(laddr_range, strict=False).broadcast_address
733             ),
734             remote_port_start=0,
735             remote_port_stop=65535,
736             local_port_start=0,
737             local_port_stop=65535
738         )
739         args = dict(
740             is_add=True,
741             entry=spd_entry
742         )
743
744         with PapiSocketExecutor(node) as papi_exec:
745             for i in range(n_entries):
746                 args[u"entry"][u"remote_address_start"][u"un"] = \
747                     IPAddress.union_addr(raddr_ip + i)
748                 args[u"entry"][u"remote_address_stop"][u"un"] = \
749                     IPAddress.union_addr(raddr_ip + i)
750                 history = bool(not 1 < i < n_entries - 2)
751                 papi_exec.add(cmd, history=history, **args)
752             papi_exec.get_replies(err_msg)
753
754     @staticmethod
755     def vpp_ipsec_create_tunnel_interfaces(
756             nodes, if1_ip_addr, if2_ip_addr, if1_key, if2_key, n_tunnels,
757             crypto_alg, integ_alg, raddr_ip1, raddr_ip2, raddr_range,
758             existing_tunnels=0):
759         """Create multiple IPsec tunnel interfaces between two VPP nodes.
760
761         :param nodes: VPP nodes to create tunnel interfaces.
762         :param if1_ip_addr: VPP node 1 interface IPv4/IPv6 address.
763         :param if2_ip_addr: VPP node 2 interface IPv4/IPv6 address.
764         :param if1_key: VPP node 1 interface key from topology file.
765         :param if2_key: VPP node 2 interface key from topology file.
766         :param n_tunnels: Number of tunnel interfaces to be there at the end.
767         :param crypto_alg: The encryption algorithm name.
768         :param integ_alg: The integrity algorithm name.
769         :param raddr_ip1: Policy selector remote IPv4/IPv6 start address for the
770             first tunnel in direction node1->node2.
771         :param raddr_ip2: Policy selector remote IPv4/IPv6 start address for the
772             first tunnel in direction node2->node1.
773         :param raddr_range: Mask specifying range of Policy selector Remote
774             IPv4/IPv6 addresses. Valid values are from 1 to 32 in case of IPv4
775             and to 128 in case of IPv6.
776         :param existing_tunnels: Number of tunnel interfaces before creation.
777             Useful mainly for reconf tests. Default 0.
778         :type nodes: dict
779         :type if1_ip_addr: str
780         :type if2_ip_addr: str
781         :type if1_key: str
782         :type if2_key: str
783         :type n_tunnels: int
784         :type crypto_alg: CryptoAlg
785         :type integ_alg: IntegAlg
786         :type raddr_ip1: string
787         :type raddr_ip2: string
788         :type raddr_range: int
789         :type existing_tunnels: int
790         """
791         n_tunnels = int(n_tunnels)
792         existing_tunnels = int(existing_tunnels)
793         spi_1 = 100000
794         spi_2 = 200000
795         if1_ip = ip_address(if1_ip_addr)
796         if2_ip = ip_address(if2_ip_addr)
797         raddr_ip1 = ip_address(raddr_ip1)
798         raddr_ip2 = ip_address(raddr_ip2)
799         addr_incr = 1 << (128 - raddr_range) if if1_ip.version == 6 \
800             else 1 << (32 - raddr_range)
801
802         if n_tunnels - existing_tunnels > 10:
803             tmp_fn1 = u"/tmp/ipsec_create_tunnel_dut1.config"
804             tmp_fn2 = u"/tmp/ipsec_create_tunnel_dut2.config"
805             if1_n = Topology.get_interface_name(nodes[u"DUT1"], if1_key)
806             if2_n = Topology.get_interface_name(nodes[u"DUT2"], if2_key)
807             mask = 96 if if2_ip.version == 6 else 24
808             mask2 = 128 if if2_ip.version == 6 else 32
809             vat = VatExecutor()
810             with open(tmp_fn1, 'w') as tmp_f1, open(tmp_fn2, 'w') as tmp_f2:
811                 rmac = Topology.get_interface_mac(nodes[u"DUT2"], if2_key)
812                 if not existing_tunnels:
813                     tmp_f1.write(
814                         f"exec create loopback interface\n"
815                         f"exec set interface state loop0 up\n"
816                         f"exec set interface ip address "
817                         f"{if1_n} {if2_ip - 1}/{mask}\n"
818                         f"exec set ip neighbor {if1_n} {if2_ip}/{mask2} {rmac}"
819                         f" static\n"
820                     )
821                     tmp_f2.write(
822                         f"exec set interface ip address {if2_n}"
823                         f" {if2_ip}/{mask}\n"
824                     )
825                 for i in range(existing_tunnels, n_tunnels):
826                     ckey = gen_key(
827                         IPsecUtil.get_crypto_alg_key_len(crypto_alg)
828                     ).hex()
829                     if integ_alg:
830                         ikey = gen_key(
831                             IPsecUtil.get_integ_alg_key_len(integ_alg)
832                         ).hex()
833                         integ = f"integ_alg {integ_alg.alg_name} " \
834                             f"local_integ_key {ikey} remote_integ_key {ikey} "
835                     else:
836                         integ = u""
837                     tmp_f1.write(
838                         f"exec set interface ip address loop0 "
839                         f"{if1_ip + i * addr_incr}/32\n"
840                         f"ipsec_tunnel_if_add_del "
841                         f"local_spi {spi_1 + i} remote_spi {spi_2 + i} "
842                         f"crypto_alg {crypto_alg.alg_name} "
843                         f"local_crypto_key {ckey} remote_crypto_key {ckey} "
844                         f"{integ} "
845                         f"local_ip {if1_ip + i * addr_incr} "
846                         f"remote_ip {if2_ip} "
847                         f"instance {i}\n"
848                     )
849                     tmp_f2.write(
850                         f"ipsec_tunnel_if_add_del "
851                         f"local_spi {spi_2 + i} remote_spi {spi_1 + i} "
852                         f"crypto_alg {crypto_alg.alg_name} "
853                         f"local_crypto_key {ckey} remote_crypto_key {ckey} "
854                         f"{integ} "
855                         f"local_ip {if2_ip} "
856                         f"remote_ip {if1_ip + i * addr_incr} "
857                         f"instance {i}\n"
858                     )
859             vat.execute_script(
860                 tmp_fn1, nodes[u"DUT1"], timeout=1800, json_out=False,
861                 copy_on_execute=True,
862                 history=bool(n_tunnels < 100)
863             )
864             vat.execute_script(
865                 tmp_fn2, nodes[u"DUT2"], timeout=1800, json_out=False,
866                 copy_on_execute=True,
867                 history=bool(n_tunnels < 100)
868             )
869             os.remove(tmp_fn1)
870             os.remove(tmp_fn2)
871
872             with open(tmp_fn1, 'w') as tmp_f1, open(tmp_fn2, 'w') as tmp_f2:
873                 if not existing_tunnels:
874                     tmp_f2.write(
875                         f"exec ip route add {if1_ip}/8 via {if2_ip - 1}"
876                         f" {if2_n}\n"
877                     )
878                 for i in range(existing_tunnels, n_tunnels):
879                     tmp_f1.write(
880                         f"exec set interface unnumbered ipip{i} use {if1_n}\n"
881                         f"exec set interface state ipip{i} up\n"
882                         f"exec ip route add {raddr_ip2 + i}/{mask2} "
883                         f"via ipip{i}\n"
884                     )
885                     tmp_f2.write(
886                         f"exec set interface unnumbered ipip{i} use {if2_n}\n"
887                         f"exec set interface state ipip{i} up\n"
888                         f"exec ip route add {raddr_ip1 + i}/{mask2} "
889                         f"via ipip{i}\n"
890                     )
891             vat.execute_script(
892                 tmp_fn1, nodes[u"DUT1"], timeout=1800, json_out=False,
893                 copy_on_execute=True,
894                 history=bool(n_tunnels < 100)
895             )
896             vat.execute_script(
897                 tmp_fn2, nodes[u"DUT2"], timeout=1800, json_out=False,
898                 copy_on_execute=True,
899                 history=bool(n_tunnels < 100)
900             )
901             os.remove(tmp_fn1)
902             os.remove(tmp_fn2)
903             return
904
905         if not existing_tunnels:
906             with PapiSocketExecutor(nodes[u"DUT1"]) as papi_exec:
907                 # Create loopback interface on DUT1, set it to up state
908                 cmd1 = u"create_loopback"
909                 args1 = dict(
910                     mac_address=0
911                 )
912                 err_msg = f"Failed to create loopback interface " \
913                     f"on host {nodes[u'DUT1'][u'host']}"
914                 loop_sw_if_idx = papi_exec.add(cmd1, **args1).\
915                     get_sw_if_index(err_msg)
916                 cmd1 = u"sw_interface_set_flags"
917                 args1 = dict(
918                     sw_if_index=loop_sw_if_idx,
919                     flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value
920                 )
921                 err_msg = f"Failed to set loopback interface state up " \
922                     f"on host {nodes[u'DUT1'][u'host']}"
923                 papi_exec.add(cmd1, **args1).get_reply(err_msg)
924                 # Set IP address on VPP node 1 interface
925                 cmd1 = u"sw_interface_add_del_address"
926                 args1 = dict(
927                     sw_if_index=InterfaceUtil.get_interface_index(
928                         nodes[u"DUT1"], if1_key
929                     ),
930                     is_add=True,
931                     del_all=False,
932                     prefix=IPUtil.create_prefix_object(
933                         if2_ip - 1, 96 if if2_ip.version == 6 else 24
934                     )
935                 )
936                 err_msg = f"Failed to set IP address on interface {if1_key} " \
937                     f"on host {nodes[u'DUT1'][u'host']}"
938                 papi_exec.add(cmd1, **args1).get_reply(err_msg)
939                 cmd4 = u"ip_neighbor_add_del"
940                 args4 = dict(
941                     is_add=1,
942                     neighbor=dict(
943                         sw_if_index=Topology.get_interface_sw_index(
944                             nodes[u"DUT1"], if1_key
945                         ),
946                         flags=1,
947                         mac_address=str(
948                             Topology.get_interface_mac(nodes[u"DUT2"], if2_key)
949                         ),
950                         ip_address=str(ip_address(if2_ip_addr))
951                     )
952                 )
953                 err_msg = f"Failed to add IP neighbor on interface {if1_key}"
954                 papi_exec.add(cmd4, **args4).get_reply(err_msg)
955         else:
956             # Executor not open, as InterfaceUtil will open its own.
957             loop_sw_if_idx = InterfaceUtil.vpp_get_interface_sw_index(
958                 nodes[u"DUT1"], u"loop0")
959             cmd1 = u"sw_interface_add_del_address"
960         with PapiSocketExecutor(nodes[u"DUT1"]) as papi_exec:
961             # Configure IPsec tunnel interfaces
962             args1 = dict(
963                 sw_if_index=loop_sw_if_idx,
964                 is_add=True,
965                 del_all=False,
966                 prefix=None
967             )
968             cmd2 = u"ipsec_tunnel_if_add_del"
969             args2 = dict(
970                 is_add=True,
971                 local_ip=None,
972                 remote_ip=None,
973                 local_spi=0,
974                 remote_spi=0,
975                 crypto_alg=crypto_alg.alg_int_repr,
976                 local_crypto_key_len=0,
977                 local_crypto_key=None,
978                 remote_crypto_key_len=0,
979                 remote_crypto_key=None,
980                 integ_alg=integ_alg.alg_int_repr if integ_alg else 0,
981                 local_integ_key_len=0,
982                 local_integ_key=None,
983                 remote_integ_key_len=0,
984                 remote_integ_key=None,
985                 tx_table_id=0
986             )
987             err_msg = f"Failed to add IPsec tunnel interfaces " \
988                 f"on host {nodes[u'DUT1'][u'host']}"
989             ipsec_tunnels = [None] * existing_tunnels
990             ckeys = [None] * existing_tunnels
991             ikeys = [None] * existing_tunnels
992             for i in range(existing_tunnels, n_tunnels):
993                 ckeys.append(
994                     gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg))
995                 )
996                 if integ_alg:
997                     ikeys.append(
998                         gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg))
999                     )
1000                 args1[u"prefix"] = IPUtil.create_prefix_object(
1001                     if1_ip + i * addr_incr, 128 if if1_ip.version == 6 else 32
1002                 )
1003                 args2[u"local_spi"] = spi_1 + i
1004                 args2[u"remote_spi"] = spi_2 + i
1005                 args2[u"local_ip"] = IPAddress.create_ip_address_object(
1006                     if1_ip + i * addr_incr
1007                 )
1008                 args2[u"remote_ip"] = IPAddress.create_ip_address_object(if2_ip)
1009                 args2[u"local_crypto_key_len"] = len(ckeys[i])
1010                 args2[u"local_crypto_key"] = ckeys[i]
1011                 args2[u"remote_crypto_key_len"] = len(ckeys[i])
1012                 args2[u"remote_crypto_key"] = ckeys[i]
1013                 if integ_alg:
1014                     args2[u"local_integ_key_len"] = len(ikeys[i])
1015                     args2[u"local_integ_key"] = ikeys[i]
1016                     args2[u"remote_integ_key_len"] = len(ikeys[i])
1017                     args2[u"remote_integ_key"] = ikeys[i]
1018                 history = bool(not 1 < i < n_tunnels - 2)
1019                 papi_exec.add(cmd1, history=history, **args1).\
1020                     add(cmd2, history=history, **args2)
1021             replies = papi_exec.get_replies(err_msg)
1022             for reply in replies:
1023                 if u"sw_if_index" in reply:
1024                     ipsec_tunnels.append(reply[u"sw_if_index"])
1025             # Configure IP routes
1026             cmd1 = u"sw_interface_set_unnumbered"
1027             args1 = dict(
1028                 is_add=True,
1029                 sw_if_index=InterfaceUtil.get_interface_index(
1030                     nodes[u"DUT1"], if1_key
1031                 ),
1032                 unnumbered_sw_if_index=0
1033             )
1034             cmd2 = u"sw_interface_set_flags"
1035             args2 = dict(
1036                 sw_if_index=0,
1037                 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value
1038             )
1039             cmd3 = u"ip_route_add_del"
1040             args3 = dict(
1041                 is_add=1,
1042                 is_multipath=0,
1043                 route=None
1044             )
1045             err_msg = f"Failed to add IP routes " \
1046                 f"on host {nodes[u'DUT1'][u'host']}"
1047             for i in range(existing_tunnels, n_tunnels):
1048                 args1[u"unnumbered_sw_if_index"] = ipsec_tunnels[i]
1049                 args2[u"sw_if_index"] = ipsec_tunnels[i]
1050                 args3[u"route"] = IPUtil.compose_vpp_route_structure(
1051                     nodes[u"DUT1"], (raddr_ip2 + i).compressed,
1052                     prefix_len=128 if raddr_ip2.version == 6 else 32,
1053                     interface=ipsec_tunnels[i]
1054                 )
1055                 history = bool(not 1 < i < n_tunnels - 2)
1056                 papi_exec.add(cmd1, history=history, **args1).\
1057                     add(cmd2, history=history, **args2).\
1058                     add(cmd3, history=history, **args3)
1059             papi_exec.get_replies(err_msg)
1060
1061         with PapiSocketExecutor(nodes[u"DUT2"]) as papi_exec:
1062             if not existing_tunnels:
1063                 # Set IP address on VPP node 2 interface
1064                 cmd1 = u"sw_interface_add_del_address"
1065                 args1 = dict(
1066                     sw_if_index=InterfaceUtil.get_interface_index(
1067                         nodes[u"DUT2"], if2_key
1068                     ),
1069                     is_add=True,
1070                     del_all=False,
1071                     prefix=IPUtil.create_prefix_object(
1072                         if2_ip, 96 if if2_ip.version == 6 else 24
1073                     )
1074                 )
1075                 err_msg = f"Failed to set IP address on interface {if2_key} " \
1076                     f"on host {nodes[u'DUT2'][u'host']}"
1077                 papi_exec.add(cmd1, **args1).get_reply(err_msg)
1078             # Configure IPsec tunnel interfaces
1079             cmd2 = u"ipsec_tunnel_if_add_del"
1080             args2 = dict(
1081                 is_add=True,
1082                 local_ip=IPAddress.create_ip_address_object(if2_ip),
1083                 remote_ip=None,
1084                 local_spi=0,
1085                 remote_spi=0,
1086                 crypto_alg=crypto_alg.alg_int_repr,
1087                 local_crypto_key_len=0,
1088                 local_crypto_key=None,
1089                 remote_crypto_key_len=0,
1090                 remote_crypto_key=None,
1091                 integ_alg=integ_alg.alg_int_repr if integ_alg else 0,
1092                 local_integ_key_len=0,
1093                 local_integ_key=None,
1094                 remote_integ_key_len=0,
1095                 remote_integ_key=None,
1096                 tx_table_id=0
1097             )
1098             err_msg = f"Failed to add IPsec tunnel interfaces " \
1099                 f"on host {nodes[u'DUT2'][u'host']}"
1100             ipsec_tunnels = [None] * existing_tunnels
1101             for i in range(existing_tunnels, n_tunnels):
1102                 args2[u"local_spi"] = spi_2 + i
1103                 args2[u"remote_spi"] = spi_1 + i
1104                 args2[u"local_ip"] = IPAddress.create_ip_address_object(if2_ip)
1105                 args2[u"remote_ip"] = IPAddress.create_ip_address_object(
1106                     if1_ip + i * addr_incr)
1107                 args2[u"local_crypto_key_len"] = len(ckeys[i])
1108                 args2[u"local_crypto_key"] = ckeys[i]
1109                 args2[u"remote_crypto_key_len"] = len(ckeys[i])
1110                 args2[u"remote_crypto_key"] = ckeys[i]
1111                 if integ_alg:
1112                     args2[u"local_integ_key_len"] = len(ikeys[i])
1113                     args2[u"local_integ_key"] = ikeys[i]
1114                     args2[u"remote_integ_key_len"] = len(ikeys[i])
1115                     args2[u"remote_integ_key"] = ikeys[i]
1116                 history = bool(not 1 < i < n_tunnels - 2)
1117                 papi_exec.add(cmd2, history=history, **args2)
1118             replies = papi_exec.get_replies(err_msg)
1119             for reply in replies:
1120                 if u"sw_if_index" in reply:
1121                     ipsec_tunnels.append(reply[u"sw_if_index"])
1122             if not existing_tunnels:
1123                 # Configure IP routes
1124                 cmd1 = u"ip_route_add_del"
1125                 route = IPUtil.compose_vpp_route_structure(
1126                     nodes[u"DUT2"], if1_ip.compressed,
1127                     prefix_len=32 if if1_ip.version == 6 else 8,
1128                     interface=if2_key,
1129                     gateway=(if2_ip - 1).compressed
1130                 )
1131                 args1 = dict(
1132                     is_add=1,
1133                     is_multipath=0,
1134                     route=route
1135                 )
1136                 papi_exec.add(cmd1, **args1)
1137             cmd1 = u"sw_interface_set_unnumbered"
1138             args1 = dict(
1139                 is_add=True,
1140                 sw_if_index=InterfaceUtil.get_interface_index(
1141                     nodes[u"DUT2"], if2_key
1142                 ),
1143                 unnumbered_sw_if_index=0
1144             )
1145             cmd2 = u"sw_interface_set_flags"
1146             args2 = dict(
1147                 sw_if_index=0,
1148                 flags=InterfaceStatusFlags.IF_STATUS_API_FLAG_ADMIN_UP.value
1149             )
1150             cmd3 = u"ip_route_add_del"
1151             args3 = dict(
1152                 is_add=1,
1153                 is_multipath=0,
1154                 route=None
1155             )
1156             err_msg = f"Failed to add IP routes " \
1157                 f"on host {nodes[u'DUT2'][u'host']}"
1158             for i in range(existing_tunnels, n_tunnels):
1159                 args1[u"unnumbered_sw_if_index"] = ipsec_tunnels[i]
1160                 args2[u"sw_if_index"] = ipsec_tunnels[i]
1161                 args3[u"route"] = IPUtil.compose_vpp_route_structure(
1162                     nodes[u"DUT1"], (raddr_ip1 + i).compressed,
1163                     prefix_len=128 if raddr_ip1.version == 6 else 32,
1164                     interface=ipsec_tunnels[i]
1165                 )
1166                 history = bool(not 1 < i < n_tunnels - 2)
1167                 papi_exec.add(cmd1, history=history, **args1). \
1168                     add(cmd2, history=history, **args2). \
1169                     add(cmd3, history=history, **args3)
1170             papi_exec.get_replies(err_msg)
1171
1172     @staticmethod
1173     def _create_ipsec_script_files(dut, instances):
1174         """Create script files for configuring IPsec in containers
1175
1176         :param dut: DUT node on which to create the script files
1177         :param instances: number of containers on DUT node
1178         :type dut: string
1179         :type instances: int
1180         """
1181         scripts = []
1182         for cnf in range(0, instances):
1183             script_filename = (
1184                 f"/tmp/ipsec_create_tunnel_cnf_{dut}_{cnf + 1}.config"
1185             )
1186             scripts.append(open(script_filename, 'w'))
1187         return scripts
1188
1189     @staticmethod
1190     def _close_and_copy_ipsec_script_files(
1191             dut, nodes, instances, scripts):
1192         """Close created scripts and copy them to containers
1193
1194         :param dut: DUT node on which to create the script files
1195         :param nodes: VPP nodes
1196         :param instances: number of containers on DUT node
1197         :param scripts: dictionary holding the script files
1198         :type dut: string
1199         :type nodes: dict
1200         :type instances: int
1201         :type scripts: dict
1202         """
1203         for cnf in range(0, instances):
1204             scripts[cnf].close()
1205             script_filename = (
1206                 f"/tmp/ipsec_create_tunnel_cnf_{dut}_{cnf + 1}.config"
1207             )
1208             scp_node(nodes[dut], script_filename, script_filename)
1209
1210
1211     @staticmethod
1212     def vpp_ipsec_create_tunnel_interfaces_in_containers(
1213             nodes, if1_ip_addr, if2_ip_addr, if1_key, if2_key, n_tunnels,
1214             crypto_alg, integ_alg, raddr_ip1, raddr_ip2, raddr_range,
1215             n_instances):
1216         """Create multiple IPsec tunnel interfaces between two VPP nodes.
1217
1218         :param nodes: VPP nodes to create tunnel interfaces.
1219         :param if1_ip_addr: VPP node 1 interface IP4 address.
1220         :param if2_ip_addr: VPP node 2 interface IP4 address.
1221         :param if1_key: VPP node 1 interface key from topology file.
1222         :param if2_key: VPP node 2 interface key from topology file.
1223         :param n_tunnels: Number of tunnell interfaces to create.
1224         :param crypto_alg: The encryption algorithm name.
1225         :param integ_alg: The integrity algorithm name.
1226         :param raddr_ip1: Policy selector remote IPv4 start address for the
1227             first tunnel in direction node1->node2.
1228         :param raddr_ip2: Policy selector remote IPv4 start address for the
1229             first tunnel in direction node2->node1.
1230         :param raddr_range: Mask specifying range of Policy selector Remote
1231             IPv4 addresses. Valid values are from 1 to 32.
1232         :param n_instances: Number of containers.
1233         :type nodes: dict
1234         :type if1_ip_addr: str
1235         :type if2_ip_addr: str
1236         :type if1_key: str
1237         :type if2_key: str
1238         :type n_tunnels: int
1239         :type crypto_alg: CryptoAlg
1240         :type integ_alg: IntegAlg
1241         :type raddr_ip1: string
1242         :type raddr_ip2: string
1243         :type raddr_range: int
1244         :type n_instances: int
1245         """
1246         spi_1 = 100000
1247         spi_2 = 200000
1248         addr_incr = 1 << (32 - raddr_range)
1249
1250         dut1_scripts = IPsecUtil._create_ipsec_script_files(
1251             u"DUT1", n_instances)
1252         dut2_scripts = IPsecUtil._create_ipsec_script_files(
1253             u"DUT2", n_instances)
1254
1255         for cnf in range(0, n_instances):
1256             dut1_scripts[cnf].write(
1257                 u"create loopback interface\n"
1258                 u"set interface state loop0 up\n\n"
1259             )
1260             dut2_scripts[cnf].write(
1261                 f"ip route add {if1_ip_addr}/8 via "
1262                 f"{ip_address(if2_ip_addr) + cnf + 100} memif1/{cnf + 1}\n\n"
1263             )
1264
1265         for tnl in range(0, n_tunnels):
1266             tnl_incr = tnl * addr_incr
1267             cnf = tnl % n_instances
1268             i = tnl // n_instances
1269             ckey = gen_key(IPsecUtil.get_crypto_alg_key_len(crypto_alg)).hex()
1270             integ = u""
1271             if integ_alg:
1272                 ikey = gen_key(IPsecUtil.get_integ_alg_key_len(integ_alg)).hex()
1273                 integ = (
1274                     f"integ-alg {integ_alg.alg_name} "
1275                     f"local-integ-key {ikey} "
1276                     f"remote-integ-key {ikey} "
1277                 )
1278
1279             # Configure tunnel end point(s) on left side
1280             dut1_scripts[cnf].write(
1281                 u"set interface ip address loop0 "
1282                 f"{ip_address(if1_ip_addr) + tnl_incr}/32\n"
1283                 f"create ipsec tunnel "
1284                 f"local-ip {ip_address(if1_ip_addr) + tnl_incr} "
1285                 f"local-spi {spi_1 + tnl} "
1286                 f"remote-ip {ip_address(if2_ip_addr) + cnf} "
1287                 f"remote-spi {spi_2 + tnl} "
1288                 f"crypto-alg {crypto_alg.alg_name} "
1289                 f"local-crypto-key {ckey} "
1290                 f"remote-crypto-key {ckey} "
1291                 f"instance {i} "
1292                 f"salt 0x0 "
1293                 f"{integ} \n"
1294                 f"set interface unnumbered ipip{i} use loop0\n"
1295                 f"set interface state ipip{i} up\n"
1296                 f"ip route add {ip_address(raddr_ip2)+tnl}/32 via ipip{i}\n\n"
1297             )
1298
1299             # Configure tunnel end point(s) on right side
1300             dut2_scripts[cnf].write(
1301                 f"set ip neighbor memif1/{cnf + 1} "
1302                 f"{ip_address(if1_ip_addr) + tnl_incr} "
1303                 f"02:02:00:00:{17:02X}:{cnf:02X} static\n"
1304                 f"create ipsec tunnel local-ip {ip_address(if2_ip_addr) + cnf} "
1305                 f"local-spi {spi_2 + tnl} "
1306                 f"remote-ip {ip_address(if1_ip_addr) + tnl_incr} "
1307                 f"remote-spi {spi_1 + tnl} "
1308                 f"crypto-alg {crypto_alg.alg_name} "
1309                 f"local-crypto-key {ckey} "
1310                 f"remote-crypto-key {ckey} "
1311                 f"instance {i} "
1312                 f"salt 0x0 "
1313                 f"{integ}\n"
1314                 f"set interface unnumbered ipip{i} use memif1/{cnf + 1}\n"
1315                 f"set interface state ipip{i} up\n"
1316                 f"ip route add {ip_address(raddr_ip1) + tnl}/32 via ipip{i}\n\n"
1317             )
1318
1319         IPsecUtil._close_and_copy_ipsec_script_files(
1320             u"DUT1", nodes, n_instances, dut1_scripts)
1321         IPsecUtil._close_and_copy_ipsec_script_files(
1322             u"DUT2", nodes, n_instances, dut2_scripts)
1323
1324     @staticmethod
1325     def vpp_ipsec_add_multiple_tunnels(
1326             nodes, interface1, interface2, n_tunnels, crypto_alg, integ_alg,
1327             tunnel_ip1, tunnel_ip2, raddr_ip1, raddr_ip2, raddr_range):
1328         """Create multiple IPsec tunnels between two VPP nodes.
1329
1330         :param nodes: VPP nodes to create tunnels.
1331         :param interface1: Interface name or sw_if_index on node 1.
1332         :param interface2: Interface name or sw_if_index on node 2.
1333         :param n_tunnels: Number of tunnels to create.
1334         :param crypto_alg: The encryption algorithm name.
1335         :param integ_alg: The integrity algorithm name.
1336         :param tunnel_ip1: Tunnel node1 IPv4 address.
1337         :param tunnel_ip2: Tunnel node2 IPv4 address.
1338         :param raddr_ip1: Policy selector remote IPv4 start address for the
1339             first tunnel in direction node1->node2.
1340         :param raddr_ip2: Policy selector remote IPv4 start address for the
1341             first tunnel in direction node2->node1.
1342         :param raddr_range: Mask specifying range of Policy selector Remote
1343             IPv4 addresses. Valid values are from 1 to 32.
1344         :type nodes: dict
1345         :type interface1: str or int
1346         :type interface2: str or int
1347         :type n_tunnels: int
1348         :type crypto_alg: CryptoAlg
1349         :type integ_alg: IntegAlg
1350         :type tunnel_ip1: str
1351         :type tunnel_ip2: str
1352         :type raddr_ip1: string
1353         :type raddr_ip2: string
1354         :type raddr_range: int
1355         """
1356         spd_id = 1
1357         p_hi = 100
1358         p_lo = 10
1359         sa_id_1 = 100000
1360         sa_id_2 = 200000
1361         spi_1 = 300000
1362         spi_2 = 400000
1363
1364         crypto_key = gen_key(
1365             IPsecUtil.get_crypto_alg_key_len(crypto_alg)
1366         ).decode()
1367         integ_key = gen_key(
1368             IPsecUtil.get_integ_alg_key_len(integ_alg)
1369         ).decode() if integ_alg else u""
1370
1371         IPsecUtil.vpp_ipsec_set_ip_route(
1372             nodes[u"DUT1"], n_tunnels, tunnel_ip1, raddr_ip2, tunnel_ip2,
1373             interface1, raddr_range)
1374         IPsecUtil.vpp_ipsec_set_ip_route(
1375             nodes[u"DUT2"], n_tunnels, tunnel_ip2, raddr_ip1, tunnel_ip1,
1376             interface2, raddr_range)
1377
1378         IPsecUtil.vpp_ipsec_add_spd(nodes[u"DUT1"], spd_id)
1379         IPsecUtil.vpp_ipsec_spd_add_if(nodes[u"DUT1"], spd_id, interface1)
1380         IPsecUtil.vpp_ipsec_policy_add(
1381             nodes[u"DUT1"], spd_id, p_hi, PolicyAction.BYPASS, inbound=False,
1382             proto=50, laddr_range=u"100.0.0.0/8", raddr_range=u"100.0.0.0/8"
1383         )
1384         IPsecUtil.vpp_ipsec_policy_add(
1385             nodes[u"DUT1"], spd_id, p_hi, PolicyAction.BYPASS, inbound=True,
1386             proto=50, laddr_range=u"100.0.0.0/8", raddr_range=u"100.0.0.0/8"
1387         )
1388
1389         IPsecUtil.vpp_ipsec_add_spd(nodes[u"DUT2"], spd_id)
1390         IPsecUtil.vpp_ipsec_spd_add_if(nodes[u"DUT2"], spd_id, interface2)
1391         IPsecUtil.vpp_ipsec_policy_add(
1392             nodes[u"DUT2"], spd_id, p_hi, PolicyAction.BYPASS, inbound=False,
1393             proto=50, laddr_range=u"100.0.0.0/8", raddr_range=u"100.0.0.0/8"
1394         )
1395         IPsecUtil.vpp_ipsec_policy_add(
1396             nodes[u"DUT2"], spd_id, p_hi, PolicyAction.BYPASS, inbound=True,
1397             proto=50, laddr_range=u"100.0.0.0/8", raddr_range=u"100.0.0.0/8"
1398         )
1399
1400         IPsecUtil.vpp_ipsec_add_sad_entries(
1401             nodes[u"DUT1"], n_tunnels, sa_id_1, spi_1, crypto_alg, crypto_key,
1402             integ_alg, integ_key, tunnel_ip1, tunnel_ip2
1403         )
1404         IPsecUtil.vpp_ipsec_spd_add_entries(
1405             nodes[u"DUT1"], n_tunnels, spd_id, p_lo, False, sa_id_1, raddr_ip2
1406         )
1407
1408         IPsecUtil.vpp_ipsec_add_sad_entries(
1409             nodes[u"DUT2"], n_tunnels, sa_id_1, spi_1, crypto_alg, crypto_key,
1410             integ_alg, integ_key, tunnel_ip1, tunnel_ip2
1411         )
1412         IPsecUtil.vpp_ipsec_spd_add_entries(
1413             nodes[u"DUT2"], n_tunnels, spd_id, p_lo, True, sa_id_1, raddr_ip2
1414         )
1415
1416         IPsecUtil.vpp_ipsec_add_sad_entries(
1417             nodes[u"DUT2"], n_tunnels, sa_id_2, spi_2, crypto_alg, crypto_key,
1418             integ_alg, integ_key, tunnel_ip2, tunnel_ip1
1419         )
1420
1421         IPsecUtil.vpp_ipsec_spd_add_entries(
1422             nodes[u"DUT2"], n_tunnels, spd_id, p_lo, False, sa_id_2, raddr_ip1
1423         )
1424
1425         IPsecUtil.vpp_ipsec_add_sad_entries(
1426             nodes[u"DUT1"], n_tunnels, sa_id_2, spi_2, crypto_alg, crypto_key,
1427             integ_alg, integ_key, tunnel_ip2, tunnel_ip1
1428         )
1429
1430         IPsecUtil.vpp_ipsec_spd_add_entries(
1431             nodes[u"DUT1"], n_tunnels, spd_id, p_lo, True, sa_id_2, raddr_ip1
1432         )
1433
1434     @staticmethod
1435     def vpp_ipsec_show(node):
1436         """Run "show ipsec" debug CLI command.
1437
1438         :param node: Node to run command on.
1439         :type node: dict
1440         """
1441         PapiSocketExecutor.run_cli_cmd(node, u"show ipsec")