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