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