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