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