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