JumpAvg: Fix string format
[csit.git] / resources / libraries / python / IPUtil.py
1 # Copyright (c) 2021 Cisco and/or its affiliates.
2 # Copyright (c) 2021 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 """Common IP utilities library."""
16
17 import re
18 import os
19
20 from enum import IntEnum
21
22 from ipaddress import ip_address, ip_network
23
24 from resources.libraries.python.Constants import Constants
25 from resources.libraries.python.IncrementUtil import ObjIncrement
26 from resources.libraries.python.InterfaceUtil import InterfaceUtil
27 from resources.libraries.python.IPAddress import IPAddress
28 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
29 from resources.libraries.python.ssh import exec_cmd_no_error, exec_cmd
30 from resources.libraries.python.topology import Topology
31 from resources.libraries.python.VatExecutor import VatExecutor
32 from resources.libraries.python.Namespaces import Namespaces
33
34
35 # from vpp/src/vnet/vnet/mpls/mpls_types.h
36 MPLS_IETF_MAX_LABEL = 0xfffff
37 MPLS_LABEL_INVALID = MPLS_IETF_MAX_LABEL + 1
38
39
40 class FibPathType(IntEnum):
41     """FIB path types."""
42     FIB_PATH_TYPE_NORMAL = 0
43     FIB_PATH_TYPE_LOCAL = 1
44     FIB_PATH_TYPE_DROP = 2
45     FIB_PATH_TYPE_UDP_ENCAP = 3
46     FIB_PATH_TYPE_BIER_IMP = 4
47     FIB_PATH_TYPE_ICMP_UNREACH = 5
48     FIB_PATH_TYPE_ICMP_PROHIBIT = 6
49     FIB_PATH_TYPE_SOURCE_LOOKUP = 7
50     FIB_PATH_TYPE_DVR = 8
51     FIB_PATH_TYPE_INTERFACE_RX = 9
52     FIB_PATH_TYPE_CLASSIFY = 10
53
54
55 class FibPathFlags(IntEnum):
56     """FIB path flags."""
57     FIB_PATH_FLAG_NONE = 0
58     FIB_PATH_FLAG_RESOLVE_VIA_ATTACHED = 1
59     FIB_PATH_FLAG_RESOLVE_VIA_HOST = 2
60
61
62 class FibPathNhProto(IntEnum):
63     """FIB path next-hop protocol."""
64     FIB_PATH_NH_PROTO_IP4 = 0
65     FIB_PATH_NH_PROTO_IP6 = 1
66     FIB_PATH_NH_PROTO_MPLS = 2
67     FIB_PATH_NH_PROTO_ETHERNET = 3
68     FIB_PATH_NH_PROTO_BIER = 4
69
70
71 class IpDscp(IntEnum):
72     """DSCP code points."""
73     IP_API_DSCP_CS0 = 0
74     IP_API_DSCP_CS1 = 8
75     IP_API_DSCP_AF11 = 10
76     IP_API_DSCP_AF12 = 12
77     IP_API_DSCP_AF13 = 14
78     IP_API_DSCP_CS2 = 16
79     IP_API_DSCP_AF21 = 18
80     IP_API_DSCP_AF22 = 20
81     IP_API_DSCP_AF23 = 22
82     IP_API_DSCP_CS3 = 24
83     IP_API_DSCP_AF31 = 26
84     IP_API_DSCP_AF32 = 28
85     IP_API_DSCP_AF33 = 30
86     IP_API_DSCP_CS4 = 32
87     IP_API_DSCP_AF41 = 34
88     IP_API_DSCP_AF42 = 36
89     IP_API_DSCP_AF43 = 38
90     IP_API_DSCP_CS5 = 40
91     IP_API_DSCP_EF = 46
92     IP_API_DSCP_CS6 = 48
93     IP_API_DSCP_CS7 = 50
94
95
96 class NetworkIncrement(ObjIncrement):
97     """
98     An iterator object which accepts an IPv4Network or IPv6Network and
99     returns a new network, its address part incremented by the increment
100     number of network sizes, each time it is iterated or when inc_fmt is called.
101     The increment may be positive, negative or 0
102     (in which case the network is always the same).
103
104     Both initial and subsequent IP address can have host bits set,
105     check the initial value before creating instance if needed.
106     String formatting is configurable via constructor argument.
107     """
108     def __init__(self, initial_value, increment=1, format=u"dash"):
109         """
110         :param initial_value: The initial network. Can have host bits set.
111         :param increment: The current network will be incremented by this
112             amount of network sizes in each iteration/var_str call.
113         :param format: Type of formatting to use, "dash" or "slash" or "addr".
114         :type initial_value: Union[ipaddress.IPv4Network, ipaddress.IPv6Network]
115         :type increment: int
116         :type format: str
117         """
118         super().__init__(initial_value, increment)
119         self._prefix_len = self._value.prefixlen
120         host_len = self._value.max_prefixlen - self._prefix_len
121         self._net_increment = self._increment * (1 << host_len)
122         self._format = str(format).lower()
123
124     def _incr(self):
125         """
126         Increment the network, e.g.:
127         '30.0.0.0/24' incremented by 1 (the next network) is '30.0.1.0/24'.
128         '30.0.0.0/24' incremented by 2 is '30.0.2.0/24'.
129         """
130         self._value = ip_network(
131             f"{self._value.network_address + self._net_increment}"
132             f"/{self._prefix_len}", strict=False
133         )
134
135     def _str_fmt(self):
136         """
137         The string representation of the network depends on format.
138
139         Dash format is '<ip_address_start> - <ip_address_stop>',
140         useful for 'ipsec policy add spd' CLI.
141
142         Slash format is '<ip_address_start>/<prefix_length>',
143         useful for other CLI.
144
145         Addr format is '<ip_address_start>', useful for PAPI.
146
147         :returns: Current value converted to string according to format.
148         :rtype: str
149         :raises RuntimeError: If the format is not supported.
150         """
151         if self._format == u"dash":
152             return f"{self._value.network_address} - " \
153                    f"{self._value.broadcast_address}"
154         elif self._format == u"slash":
155             return f"{self._value.network_address}/{self._prefix_len}"
156         elif self._format == u"addr":
157             return f"{self._value.network_address}"
158
159         raise RuntimeError(f"Unsupported format {self._format}")
160
161
162 class IPUtil:
163     """Common IP utilities"""
164
165     @staticmethod
166     def ip_to_int(ip_str):
167         """Convert IP address from string format (e.g. 10.0.0.1) to integer
168         representation (167772161).
169
170         :param ip_str: IP address in string representation.
171         :type ip_str: str
172         :returns: Integer representation of IP address.
173         :rtype: int
174         """
175         return int(ip_address(ip_str))
176
177     @staticmethod
178     def int_to_ip(ip_int):
179         """Convert IP address from integer representation (e.g. 167772161) to
180         string format (10.0.0.1).
181
182         :param ip_int: IP address in integer representation.
183         :type ip_int: int
184         :returns: String representation of IP address.
185         :rtype: str
186         """
187         return str(ip_address(ip_int))
188
189     @staticmethod
190     def vpp_get_interface_ip_addresses(node, interface, ip_version):
191         """Get list of IP addresses from an interface on a VPP node.
192
193         :param node: VPP node.
194         :param interface: Name of an interface on the VPP node.
195         :param ip_version: IP protocol version (ipv4 or ipv6).
196         :type node: dict
197         :type interface: str
198         :type ip_version: str
199         :returns: List of dictionaries, each containing IP address, subnet
200             prefix length and also the subnet mask for ipv4 addresses.
201             Note: A single interface may have multiple IP addresses assigned.
202         :rtype: list
203         """
204         sw_if_index = InterfaceUtil.get_interface_index(node, interface)
205
206         if not sw_if_index:
207             return list()
208
209         cmd = u"ip_address_dump"
210         args = dict(
211             sw_if_index=sw_if_index,
212             is_ipv6=bool(ip_version == u"ipv6")
213         )
214         err_msg = f"Failed to get L2FIB dump on host {node[u'host']}"
215
216         with PapiSocketExecutor(node) as papi_exec:
217             details = papi_exec.add(cmd, **args).get_details(err_msg)
218
219         return details
220
221     @staticmethod
222     def vpp_get_ip_tables(node):
223         """Get dump of all IP FIB tables on a VPP node.
224
225         :param node: VPP node.
226         :type node: dict
227         """
228         PapiSocketExecutor.run_cli_cmd(node, u"show ip fib")
229         PapiSocketExecutor.run_cli_cmd(node, u"show ip fib summary")
230         PapiSocketExecutor.run_cli_cmd(node, u"show ip6 fib")
231         PapiSocketExecutor.run_cli_cmd(node, u"show ip6 fib summary")
232
233     @staticmethod
234     def vpp_get_ip_table_summary(node):
235         """Get IPv4 FIB table summary on a VPP node.
236
237         :param node: VPP node.
238         :type node: dict
239         """
240         PapiSocketExecutor.run_cli_cmd(node, u"show ip fib summary")
241
242     @staticmethod
243     def vpp_get_ip_table(node):
244         """Get IPv4 FIB table on a VPP node.
245
246         :param node: VPP node.
247         :type node: dict
248         """
249         PapiSocketExecutor.run_cli_cmd(node, u"show ip fib")
250
251     @staticmethod
252     def vpp_get_ip_tables_prefix(node, address):
253         """Get dump of all IP FIB tables on a VPP node.
254
255         :param node: VPP node.
256         :param address: IP address.
257         :type node: dict
258         :type address: str
259         """
260         addr = ip_address(address)
261         ip_ver = u"ip6" if addr.version == 6 else u"ip"
262
263         PapiSocketExecutor.run_cli_cmd(
264             node, f"show {ip_ver} fib {addr}/{addr.max_prefixlen}"
265         )
266
267     @staticmethod
268     def get_interface_vrf_table(node, interface, ip_version='ipv4'):
269         """Get vrf ID for the given interface.
270
271         :param node: VPP node.
272         :param interface: Name or sw_if_index of a specific interface.
273         :type node: dict
274         :param ip_version: IP protocol version (ipv4 or ipv6).
275         :type interface: str or int
276         :type ip_version: str
277         :returns: vrf ID of the specified interface.
278         :rtype: int
279         """
280         sw_if_index = InterfaceUtil.get_interface_index(node, interface)
281
282         cmd = u"sw_interface_get_table"
283         args = dict(
284             sw_if_index=sw_if_index,
285             is_ipv6=bool(ip_version == u"ipv6")
286         )
287         err_msg = f"Failed to get VRF id assigned to interface {interface}"
288
289         with PapiSocketExecutor(node) as papi_exec:
290             reply = papi_exec.add(cmd, **args).get_reply(err_msg)
291
292         return reply[u"vrf_id"]
293
294     @staticmethod
295     def vpp_ip_source_check_setup(node, if_name):
296         """Setup Reverse Path Forwarding source check on interface.
297
298         :param node: VPP node.
299         :param if_name: Interface name to setup RPF source check.
300         :type node: dict
301         :type if_name: str
302         """
303         cmd = u"ip_source_check_interface_add_del"
304         args = dict(
305             sw_if_index=InterfaceUtil.get_interface_index(node, if_name),
306             is_add=1,
307             loose=0
308         )
309         err_msg = f"Failed to enable source check on interface {if_name}"
310         with PapiSocketExecutor(node) as papi_exec:
311             papi_exec.add(cmd, **args).get_reply(err_msg)
312
313     @staticmethod
314     def vpp_ip_probe(node, interface, addr):
315         """Run ip probe on VPP node.
316
317         :param node: VPP node.
318         :param interface: Interface key or name.
319         :param addr: IPv4/IPv6 address.
320         :type node: dict
321         :type interface: str
322         :type addr: str
323         """
324         cmd = u"ip_probe_neighbor"
325         args = dict(
326             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
327             dst=str(addr)
328         )
329         err_msg = f"VPP ip probe {interface} {addr} failed on {node[u'host']}"
330
331         with PapiSocketExecutor(node) as papi_exec:
332             papi_exec.add(cmd, **args).get_reply(err_msg)
333
334     @staticmethod
335     def ip_addresses_should_be_equal(ip1, ip2):
336         """Fails if the given IP addresses are unequal.
337
338         :param ip1: IPv4 or IPv6 address.
339         :param ip2: IPv4 or IPv6 address.
340         :type ip1: str
341         :type ip2: str
342         """
343         addr1 = ip_address(ip1)
344         addr2 = ip_address(ip2)
345
346         if addr1 != addr2:
347             raise AssertionError(f"IP addresses are not equal: {ip1} != {ip2}")
348
349     @staticmethod
350     def setup_network_namespace(node, namespace_name, interface_name,
351                                 ip_addr_list, prefix_length):
352         """Setup namespace on given node and attach interface and IP to
353         this namespace. Applicable also on TG node.
354
355         :param node: VPP node.
356         :param namespace_name: Namespace name.
357         :param interface_name: Interface name.
358         :param ip_addr_list: List of IP addresses of namespace's interface.
359         :param prefix_length: IP address prefix length.
360         :type node: dict
361         :type namespace_name: str
362         :type interface_name: str
363         :type ip_addr_list: list
364         :type prefix_length: int
365         """
366         Namespaces.create_namespace(node, namespace_name)
367
368         cmd = f"ip netns exec {namespace_name} ip link set {interface_name} up"
369         exec_cmd_no_error(node, cmd, sudo=True)
370
371         for ip_addr in ip_addr_list:
372             cmd = f"ip netns exec {namespace_name} ip addr add " \
373                 f"{ip_addr}/{prefix_length} dev {interface_name}"
374             exec_cmd_no_error(node, cmd, sudo=True)
375
376     @staticmethod
377     def linux_enable_forwarding(node, ip_ver=u"ipv4"):
378         """Enable forwarding on a Linux node, e.g. VM.
379
380         :param node: VPP node.
381         :param ip_ver: IP version, 'ipv4' or 'ipv6'.
382         :type node: dict
383         :type ip_ver: str
384         """
385         cmd = f"sysctl -w net.{ip_ver}.ip_forward=1"
386         exec_cmd_no_error(node, cmd, sudo=True)
387
388     @staticmethod
389     def get_linux_interface_name(node, pci_addr):
390         """Get the interface name.
391
392         :param node: VPP/TG node.
393         :param pci_addr: PCI address
394         :type node: dict
395         :type pci_addr: str
396         :returns: Interface name
397         :rtype: str
398         :raises RuntimeError: If cannot get the information about interfaces.
399         """
400         regex_intf_info = \
401             r"pci@([0-9a-f]{4}:[0-9a-f]{2}:[0-9a-f]{2}.[0-9a-f])\s" \
402             r"*([a-zA-Z0-9]*)\s*network"
403
404         cmd = u"lshw -class network -businfo"
405         ret_code, stdout, stderr = exec_cmd(node, cmd, timeout=30, sudo=True)
406         if ret_code != 0:
407             raise RuntimeError(
408                 f"Could not get information about interfaces:\n{stderr}"
409             )
410
411         for line in stdout.splitlines()[2:]:
412             try:
413                 if re.search(regex_intf_info, line).group(1) == pci_addr:
414                     return re.search(regex_intf_info, line).group(2)
415             except AttributeError:
416                 continue
417         return None
418
419     @staticmethod
420     def set_linux_interface_up(
421             node, interface, namespace=None):
422         """Set the specified interface up.
423         :param node: VPP/TG node.
424         :param interface: Interface in namespace.
425         :param namespace: Execute command in namespace. Optional
426         :type node: dict
427         :type interface: str
428         :type namespace: str
429         :raises RuntimeError: If the interface could not be set up.
430         """
431         if namespace is not None:
432             cmd = f"ip netns exec {namespace} ip link set dev {interface} up"
433         else:
434             cmd = f"ip link set dev {interface} up"
435         exec_cmd_no_error(node, cmd, timeout=30, sudo=True)
436
437
438     @staticmethod
439     def set_linux_interface_ip(
440             node, interface, ip_addr, prefix, namespace=None):
441         """Set IP address to interface in linux.
442
443         :param node: VPP/TG node.
444         :param interface: Interface in namespace.
445         :param ip_addr: IP to be set on interface.
446         :param prefix: IP prefix.
447         :param namespace: Execute command in namespace. Optional
448         :type node: dict
449         :type interface: str
450         :type ip_addr: str
451         :type prefix: int
452         :type namespace: str
453         :raises RuntimeError: IP could not be set.
454         """
455         if namespace is not None:
456             cmd = f"ip netns exec {namespace} ip addr add {ip_addr}/{prefix}" \
457                 f" dev {interface}"
458         else:
459             cmd = f"ip addr add {ip_addr}/{prefix} dev {interface}"
460
461         exec_cmd_no_error(node, cmd, timeout=5, sudo=True)
462
463     @staticmethod
464     def delete_linux_interface_ip(
465             node, interface, ip_addr, prefix_length, namespace=None):
466         """Delete IP address from interface in linux.
467
468         :param node: VPP/TG node.
469         :param interface: Interface in namespace.
470         :param ip_addr: IP to be deleted from interface.
471         :param prefix_length: IP prefix length.
472         :param namespace: Execute command in namespace. Optional
473         :type node: dict
474         :type interface: str
475         :type ip_addr: str
476         :type prefix_length: int
477         :type namespace: str
478         :raises RuntimeError: IP could not be deleted.
479         """
480         if namespace is not None:
481             cmd = f"ip netns exec {namespace} ip addr del " \
482                 f"{ip_addr}/{prefix_length} dev {interface}"
483         else:
484             cmd = f"ip addr del {ip_addr}/{prefix_length} dev {interface}"
485
486         exec_cmd_no_error(node, cmd, timeout=5, sudo=True)
487
488     @staticmethod
489     def linux_interface_has_ip(
490             node, interface, ip_addr, prefix_length, namespace=None):
491         """Return True if interface in linux has IP address.
492
493         :param node: VPP/TG node.
494         :param interface: Interface in namespace.
495         :param ip_addr: IP to be queried on interface.
496         :param prefix_length: IP prefix length.
497         :param namespace: Execute command in namespace. Optional
498         :type node: dict
499         :type interface: str
500         :type ip_addr: str
501         :type prefix_length: int
502         :type namespace: str
503         :rtype: boolean
504         :raises RuntimeError: Request fails.
505         """
506         ip_addr_with_prefix = f"{ip_addr}/{prefix_length}"
507         if namespace is not None:
508             cmd = f"ip netns exec {namespace} ip addr show dev {interface}"
509         else:
510             cmd = f"ip addr show dev {interface}"
511
512         cmd += u" | grep 'inet ' | awk -e '{print $2}'"
513         cmd += f" | grep '{ip_addr_with_prefix}'"
514         _, stdout, _ = exec_cmd(node, cmd, timeout=5, sudo=True)
515
516         has_ip = stdout.rstrip()
517         return bool(has_ip == ip_addr_with_prefix)
518
519     @staticmethod
520     def add_linux_route(node, ip_addr, prefix, gateway, namespace=None):
521         """Add linux route in namespace.
522
523         :param node: Node where to execute command.
524         :param ip_addr: Route destination IP address.
525         :param prefix: IP prefix.
526         :param namespace: Execute command in namespace. Optional.
527         :param gateway: Gateway address.
528         :type node: dict
529         :type ip_addr: str
530         :type prefix: int
531         :type gateway: str
532         :type namespace: str
533         """
534         if namespace is not None:
535             cmd = f"ip netns exec {namespace} ip route add {ip_addr}/{prefix}" \
536                 f" via {gateway}"
537         else:
538             cmd = f"ip route add {ip_addr}/{prefix} via {gateway}"
539
540         exec_cmd_no_error(node, cmd, sudo=True)
541
542     @staticmethod
543     def vpp_interface_set_ip_address(
544             node, interface, address, prefix_length=None):
545         """Set IP address to VPP interface.
546
547         :param node: VPP node.
548         :param interface: Interface name.
549         :param address: IP address.
550         :param prefix_length: Prefix length.
551         :type node: dict
552         :type interface: str
553         :type address: str
554         :type prefix_length: int
555         """
556         ip_addr = ip_address(address)
557
558         cmd = u"sw_interface_add_del_address"
559         args = dict(
560             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
561             is_add=True,
562             del_all=False,
563             prefix=IPUtil.create_prefix_object(
564                 ip_addr,
565                 prefix_length if prefix_length else 128
566                 if ip_addr.version == 6 else 32
567             )
568         )
569         err_msg = f"Failed to add IP address on interface {interface}"
570
571         with PapiSocketExecutor(node) as papi_exec:
572             papi_exec.add(cmd, **args).get_reply(err_msg)
573
574     @staticmethod
575     def vpp_interface_set_ip_addresses(node, interface, ip_addr_list,
576                                        prefix_length=None):
577         """Set IP addresses to VPP interface.
578
579         :param node: VPP node.
580         :param interface: Interface name.
581         :param ip_addr_list: IP addresses.
582         :param prefix_length: Prefix length.
583         :type node: dict
584         :type interface: str
585         :type ip_addr_list: list
586         :type prefix_length: int
587         """
588         for ip_addr in ip_addr_list:
589             IPUtil.vpp_interface_set_ip_address(node, interface, ip_addr,
590                                                 prefix_length)
591
592     @staticmethod
593     def vpp_add_ip_neighbor(node, iface_key, ip_addr, mac_address):
594         """Add IP neighbor on DUT node.
595
596         :param node: VPP node.
597         :param iface_key: Interface key.
598         :param ip_addr: IP address of the interface.
599         :param mac_address: MAC address of the interface.
600         :type node: dict
601         :type iface_key: str
602         :type ip_addr: str
603         :type mac_address: str
604         """
605         dst_ip = ip_address(ip_addr)
606
607         neighbor = dict(
608             sw_if_index=Topology.get_interface_sw_index(node, iface_key),
609             flags=0,
610             mac_address=str(mac_address),
611             ip_address=str(dst_ip)
612         )
613         cmd = u"ip_neighbor_add_del"
614         args = dict(
615             is_add=True,
616             neighbor=neighbor
617         )
618         err_msg = f"Failed to add IP neighbor on interface {iface_key}"
619
620         with PapiSocketExecutor(node) as papi_exec:
621             papi_exec.add(cmd, **args).get_reply(err_msg)
622
623     @staticmethod
624     def create_prefix_object(ip_addr, addr_len):
625         """Create prefix object.
626
627         :param ip_addr: IPv4 or IPv6 address.
628         :param addr_len: Length of IP address.
629         :type ip_addr: IPv4Address or IPv6Address
630         :type addr_len: int
631         :returns: Prefix object.
632         :rtype: dict
633         """
634         addr = IPAddress.create_ip_address_object(ip_addr)
635
636         return dict(
637             len=int(addr_len),
638             address=addr
639         )
640
641     @staticmethod
642     def compose_vpp_route_structure(node, network, prefix_len, **kwargs):
643         """Create route object for ip_route_add_del api call.
644
645         :param node: VPP node.
646         :param network: Route destination network address.
647         :param prefix_len: Route destination network prefix length.
648         :param kwargs: Optional key-value arguments:
649
650             gateway: Route gateway address. (str)
651             interface: Route interface. (str)
652             vrf: VRF table ID. (int)
653             count: number of IP addresses to add starting from network IP (int)
654             local: The route is local with same prefix (increment is 1).
655             If None, then is not used. (bool)
656             lookup_vrf: VRF table ID for lookup. (int)
657             weight: Weight value for unequal cost multipath routing. (int)
658             (Multipath value enters at higher level.)
659
660         :type node: dict
661         :type network: str
662         :type prefix_len: int
663         :type kwargs: dict
664         :returns: route parameter basic structure
665         :rtype: dict
666         """
667         interface = kwargs.get(u"interface", u"")
668         gateway = kwargs.get(u"gateway", u"")
669
670         net_addr = ip_address(network)
671
672         prefix = IPUtil.create_prefix_object(net_addr, prefix_len)
673
674         paths = list()
675         n_hop = dict(
676             address=IPAddress.union_addr(ip_address(gateway)) if gateway else 0,
677             via_label=MPLS_LABEL_INVALID,
678             obj_id=Constants.BITWISE_NON_ZERO
679         )
680         path = dict(
681             sw_if_index=InterfaceUtil.get_interface_index(node, interface)
682             if interface else Constants.BITWISE_NON_ZERO,
683             table_id=int(kwargs.get(u"lookup_vrf", 0)),
684             rpf_id=Constants.BITWISE_NON_ZERO,
685             weight=int(kwargs.get(u"weight", 1)),
686             preference=1,
687             type=getattr(
688                 FibPathType, u"FIB_PATH_TYPE_LOCAL"
689                 if kwargs.get(u"local", False)
690                 else u"FIB_PATH_TYPE_NORMAL"
691             ).value,
692             flags=getattr(FibPathFlags, u"FIB_PATH_FLAG_NONE").value,
693             proto=getattr(
694                 FibPathNhProto, u"FIB_PATH_NH_PROTO_IP6"
695                 if net_addr.version == 6
696                 else u"FIB_PATH_NH_PROTO_IP4"
697             ).value,
698             nh=n_hop,
699             n_labels=0,
700             label_stack=list(0 for _ in range(16))
701         )
702         paths.append(path)
703
704         route = dict(
705             table_id=int(kwargs.get(u"vrf", 0)),
706             prefix=prefix,
707             n_paths=len(paths),
708             paths=paths
709         )
710         return route
711
712     @staticmethod
713     def vpp_route_add(node, network, prefix_len, strict=True, **kwargs):
714         """Add route to the VPP node. Prefer multipath behavior.
715
716         :param node: VPP node.
717         :param network: Route destination network address.
718         :param prefix_len: Route destination network prefix length.
719         :param strict: If true, fail if address has host bits set.
720         :param kwargs: Optional key-value arguments:
721
722             gateway: Route gateway address. (str)
723             interface: Route interface. (str)
724             vrf: VRF table ID. (int)
725             count: number of IP addresses to add starting from network IP (int)
726             local: The route is local with same prefix (increment is 1 network)
727             If None, then is not used. (bool)
728             lookup_vrf: VRF table ID for lookup. (int)
729             multipath: Enable multipath routing. (bool) Default: True.
730             weight: Weight value for unequal cost multipath routing. (int)
731
732         :type node: dict
733         :type network: str
734         :type prefix_len: int
735         :type strict: bool
736         :type kwargs: dict
737         :raises RuntimeError: If the argument combination is not supported.
738         """
739         count = kwargs.get(u"count", 1)
740
741         if count > 100:
742             if not kwargs.get(u"multipath", True):
743                 raise RuntimeError(u"VAT exec supports only multipath behavior")
744             gateway = kwargs.get(u"gateway", u"")
745             interface = kwargs.get(u"interface", u"")
746             local = kwargs.get(u"local", u"")
747             if interface:
748                 interface = InterfaceUtil.vpp_get_interface_name(
749                     node, InterfaceUtil.get_interface_index(
750                         node, interface
751                     )
752                 )
753             vrf = kwargs.get(u"vrf", None)
754             trailers = list()
755             if vrf:
756                 trailers.append(f"table {vrf}")
757             if gateway:
758                 trailers.append(f"via {gateway}")
759                 if interface:
760                     trailers.append(interface)
761             elif interface:
762                 trailers.append(f"via {interface}")
763             if local:
764                 if gateway or interface:
765                     raise RuntimeError(u"Unsupported combination with local.")
766                 trailers.append(u"local")
767             trailer = u" ".join(trailers)
768             command_parts = [u"exec ip route add", u"network goes here"]
769             if trailer:
770                 command_parts.append(trailer)
771             netiter = NetworkIncrement(
772                 ip_network(f"{network}/{prefix_len}", strict=strict),
773                 format=u"slash"
774             )
775             tmp_filename = u"/tmp/routes.config"
776             with open(tmp_filename, u"w") as tmp_file:
777                 for _ in range(count):
778                     command_parts[1] = netiter.inc_fmt()
779                     print(u" ".join(command_parts), file=tmp_file)
780             VatExecutor().execute_script(
781                 tmp_filename, node, timeout=1800, json_out=False,
782                 copy_on_execute=True, history=False
783             )
784             os.remove(tmp_filename)
785             return
786
787         cmd = u"ip_route_add_del"
788         args = dict(
789             is_add=True,
790             is_multipath=kwargs.get(u"multipath", True),
791             route=None
792         )
793         err_msg = f"Failed to add route(s) on host {node[u'host']}"
794
795         netiter = NetworkIncrement(
796             ip_network(f"{network}/{prefix_len}", strict=strict),
797             format=u"addr"
798         )
799         with PapiSocketExecutor(node) as papi_exec:
800             for i in range(count):
801                 args[u"route"] = IPUtil.compose_vpp_route_structure(
802                     node, netiter.inc_fmt(), prefix_len, **kwargs
803                 )
804                 history = bool(not 0 < i < count - 1)
805                 papi_exec.add(cmd, history=history, **args)
806             papi_exec.get_replies(err_msg)
807
808     @staticmethod
809     def flush_ip_addresses(node, interface):
810         """Flush all IP addresses from specified interface.
811
812         :param node: VPP node.
813         :param interface: Interface name.
814         :type node: dict
815         :type interface: str
816         """
817         cmd = u"sw_interface_add_del_address"
818         args = dict(
819             sw_if_index=InterfaceUtil.get_interface_index(node, interface),
820             is_add=False,
821             del_all=True
822         )
823         err_msg = f"Failed to flush IP address on interface {interface}"
824
825         with PapiSocketExecutor(node) as papi_exec:
826             papi_exec.add(cmd, **args).get_reply(err_msg)
827
828     @staticmethod
829     def add_fib_table(node, table_id, ipv6=False):
830         """Create new FIB table according to ID.
831
832         :param node: Node to add FIB on.
833         :param table_id: FIB table ID.
834         :param ipv6: Is this an IPv6 table
835         :type node: dict
836         :type table_id: int
837         :type ipv6: bool
838         """
839         cmd = u"ip_table_add_del"
840         table = dict(
841             table_id=int(table_id),
842             is_ip6=ipv6
843         )
844         args = dict(
845             table=table,
846             is_add=True
847         )
848         err_msg = f"Failed to add FIB table on host {node[u'host']}"
849
850         with PapiSocketExecutor(node) as papi_exec:
851             papi_exec.add(cmd, **args).get_reply(err_msg)