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:
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 """Common IP utilities library."""
18 from enum import IntEnum
20 from ipaddress import ip_address, ip_network
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
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
38 class FibPathType(IntEnum):
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
49 FIB_PATH_TYPE_INTERFACE_RX = 9
50 FIB_PATH_TYPE_CLASSIFY = 10
53 class FibPathFlags(IntEnum):
55 FIB_PATH_FLAG_NONE = 0
56 FIB_PATH_FLAG_RESOLVE_VIA_ATTACHED = 1
57 FIB_PATH_FLAG_RESOLVE_VIA_HOST = 2
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
69 class IpDscp(IntEnum):
70 """DSCP code points."""
94 class NetworkIncrement(ObjIncrement):
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).
101 def __init__(self, initial_value, increment):
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.
107 Union[ipaddress.IPv4Network, ipaddress.IPv6Network].
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)
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'.
121 self._value = ip_network(
122 f"{self._value.network_address + self._net_increment}"
123 f"/{self._prefix_len}"
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.
132 return f"{self._value.network_address} - " \
133 f"{self._value.broadcast_address}"
137 """Common IP utilities"""
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).
144 :param ip_str: IP address in string representation.
146 :returns: Integer representation of IP address.
149 return int(ip_address(ip_str))
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).
156 :param ip_int: IP address in integer representation.
158 :returns: String representation of IP address.
161 return str(ip_address(ip_int))
164 def vpp_get_interface_ip_addresses(node, interface, ip_version):
165 """Get list of IP addresses from an interface on a VPP node.
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).
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.
178 sw_if_index = InterfaceUtil.get_interface_index(node, interface)
183 cmd = u"ip_address_dump"
185 sw_if_index=sw_if_index,
186 is_ipv6=bool(ip_version == u"ipv6")
188 err_msg = f"Failed to get L2FIB dump on host {node[u'host']}"
190 with PapiSocketExecutor(node) as papi_exec:
191 details = papi_exec.add(cmd, **args).get_details(err_msg)
196 def vpp_get_ip_tables(node):
197 """Get dump of all IP FIB tables on a VPP node.
199 :param node: VPP node.
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")
208 def vpp_get_ip_table_summary(node):
209 """Get IPv4 FIB table summary on a VPP node.
211 :param node: VPP node.
214 PapiSocketExecutor.run_cli_cmd(node, u"show ip fib summary")
217 def vpp_get_ip_table(node):
218 """Get IPv4 FIB table on a VPP node.
220 :param node: VPP node.
223 PapiSocketExecutor.run_cli_cmd(node, u"show ip fib")
226 def vpp_get_ip_tables_prefix(node, address):
227 """Get dump of all IP FIB tables on a VPP node.
229 :param node: VPP node.
230 :param address: IP address.
234 addr = ip_address(address)
235 ip_ver = u"ip6" if addr.version == 6 else u"ip"
237 PapiSocketExecutor.run_cli_cmd(
238 node, f"show {ip_ver} fib {addr}/{addr.max_prefixlen}"
242 def get_interface_vrf_table(node, interface, ip_version='ipv4'):
243 """Get vrf ID for the given interface.
245 :param node: VPP node.
246 :param interface: Name or sw_if_index of a specific interface.
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.
254 sw_if_index = InterfaceUtil.get_interface_index(node, interface)
256 cmd = u"sw_interface_get_table"
258 sw_if_index=sw_if_index,
259 is_ipv6=bool(ip_version == u"ipv6")
261 err_msg = f"Failed to get VRF id assigned to interface {interface}"
263 with PapiSocketExecutor(node) as papi_exec:
264 reply = papi_exec.add(cmd, **args).get_reply(err_msg)
266 return reply[u"vrf_id"]
269 def vpp_ip_source_check_setup(node, if_name):
270 """Setup Reverse Path Forwarding source check on interface.
272 :param node: VPP node.
273 :param if_name: Interface name to setup RPF source check.
277 cmd = u"ip_source_check_interface_add_del"
279 sw_if_index=InterfaceUtil.get_interface_index(node, if_name),
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)
288 def vpp_ip_probe(node, interface, addr):
289 """Run ip probe on VPP node.
291 :param node: VPP node.
292 :param interface: Interface key or name.
293 :param addr: IPv4/IPv6 address.
298 cmd = u"ip_probe_neighbor"
300 sw_if_index=InterfaceUtil.get_interface_index(node, interface),
303 err_msg = f"VPP ip probe {interface} {addr} failed on {node[u'host']}"
305 with PapiSocketExecutor(node) as papi_exec:
306 papi_exec.add(cmd, **args).get_reply(err_msg)
309 def ip_addresses_should_be_equal(ip1, ip2):
310 """Fails if the given IP addresses are unequal.
312 :param ip1: IPv4 or IPv6 address.
313 :param ip2: IPv4 or IPv6 address.
317 addr1 = ip_address(ip1)
318 addr2 = ip_address(ip2)
321 raise AssertionError(f"IP addresses are not equal: {ip1} != {ip2}")
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.
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.
335 :type namespace_name: str
336 :type interface_name: str
337 :type ip_addr_list: list
338 :type prefix_length: int
340 Namespaces.create_namespace(node, namespace_name)
342 cmd = f"ip netns exec {namespace_name} ip link set {interface_name} up"
343 exec_cmd_no_error(node, cmd, sudo=True)
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)
351 def linux_enable_forwarding(node, ip_ver=u"ipv4"):
352 """Enable forwarding on a Linux node, e.g. VM.
354 :param node: VPP node.
355 :param ip_ver: IP version, 'ipv4' or 'ipv6'.
359 cmd = f"sysctl -w net.{ip_ver}.ip_forward=1"
360 exec_cmd_no_error(node, cmd, sudo=True)
363 def get_linux_interface_name(node, pci_addr):
364 """Get the interface name.
366 :param node: VPP/TG node.
367 :param pci_addr: PCI address
370 :returns: Interface name
372 :raises RuntimeError: If cannot get the information about interfaces.
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"
378 cmd = u"lshw -class network -businfo"
379 ret_code, stdout, stderr = exec_cmd(node, cmd, timeout=30, sudo=True)
382 f"Could not get information about interfaces:\n{stderr}"
385 for line in stdout.splitlines()[2:]:
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:
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
403 :raises RuntimeError: If the interface could not be set up.
405 if namespace is not None:
406 cmd = f"ip netns exec {namespace} ip link set dev {interface} up"
408 cmd = f"ip link set dev {interface} up"
409 exec_cmd_no_error(node, cmd, timeout=30, sudo=True)
413 def set_linux_interface_ip(
414 node, interface, ip_addr, prefix, namespace=None):
415 """Set IP address to interface in linux.
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
427 :raises RuntimeError: IP could not be set.
429 if namespace is not None:
430 cmd = f"ip netns exec {namespace} ip addr add {ip_addr}/{prefix}" \
433 cmd = f"ip addr add {ip_addr}/{prefix} dev {interface}"
435 exec_cmd_no_error(node, cmd, timeout=5, sudo=True)
438 def delete_linux_interface_ip(
439 node, interface, ip_addr, prefix_length, namespace=None):
440 """Delete IP address from interface in linux.
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
450 :type prefix_length: int
452 :raises RuntimeError: IP could not be deleted.
454 if namespace is not None:
455 cmd = f"ip netns exec {namespace} ip addr del " \
456 f"{ip_addr}/{prefix_length} dev {interface}"
458 cmd = f"ip addr del {ip_addr}/{prefix_length} dev {interface}"
460 exec_cmd_no_error(node, cmd, timeout=5, sudo=True)
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.
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
475 :type prefix_length: int
478 :raises RuntimeError: Request fails.
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}"
484 cmd = f"ip addr show dev {interface}"
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)
490 has_ip = stdout.rstrip()
491 return bool(has_ip == ip_addr_with_prefix)
494 def add_linux_route(node, ip_addr, prefix, gateway, namespace=None):
495 """Add linux route in namespace.
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.
508 if namespace is not None:
509 cmd = f"ip netns exec {namespace} ip route add {ip_addr}/{prefix}" \
512 cmd = f"ip route add {ip_addr}/{prefix} via {gateway}"
514 exec_cmd_no_error(node, cmd, sudo=True)
517 def vpp_interface_set_ip_address(
518 node, interface, address, prefix_length=None):
519 """Set IP address to VPP interface.
521 :param node: VPP node.
522 :param interface: Interface name.
523 :param address: IP address.
524 :param prefix_length: Prefix length.
528 :type prefix_length: int
530 ip_addr = ip_address(address)
532 cmd = u"sw_interface_add_del_address"
534 sw_if_index=InterfaceUtil.get_interface_index(node, interface),
537 prefix=IPUtil.create_prefix_object(
539 prefix_length if prefix_length else 128
540 if ip_addr.version == 6 else 32
543 err_msg = f"Failed to add IP address on interface {interface}"
545 with PapiSocketExecutor(node) as papi_exec:
546 papi_exec.add(cmd, **args).get_reply(err_msg)
549 def vpp_interface_set_ip_addresses(node, interface, ip_addr_list,
551 """Set IP addresses to VPP interface.
553 :param node: VPP node.
554 :param interface: Interface name.
555 :param ip_addr_list: IP addresses.
556 :param prefix_length: Prefix length.
559 :type ip_addr_list: list
560 :type prefix_length: int
562 for ip_addr in ip_addr_list:
563 IPUtil.vpp_interface_set_ip_address(node, interface, ip_addr,
567 def vpp_add_ip_neighbor(node, iface_key, ip_addr, mac_address):
568 """Add IP neighbor on DUT node.
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.
577 :type mac_address: str
579 dst_ip = ip_address(ip_addr)
582 sw_if_index=Topology.get_interface_sw_index(node, iface_key),
584 mac_address=str(mac_address),
585 ip_address=str(dst_ip)
587 cmd = u"ip_neighbor_add_del"
592 err_msg = f"Failed to add IP neighbor on interface {iface_key}"
594 with PapiSocketExecutor(node) as papi_exec:
595 papi_exec.add(cmd, **args).get_reply(err_msg)
598 def create_prefix_object(ip_addr, addr_len):
599 """Create prefix object.
601 :param ip_addr: IPv4 or IPv6 address.
602 :param addr_len: Length of IP address.
603 :type ip_addr: IPv4Address or IPv6Address
605 :returns: Prefix object.
608 addr = IPAddress.create_ip_address_object(ip_addr)
616 def compose_vpp_route_structure(node, network, prefix_len, **kwargs):
617 """Create route object for ip_route_add_del api call.
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:
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)
636 :type prefix_len: int
638 :returns: route parameter basic structure
641 interface = kwargs.get(u"interface", u"")
642 gateway = kwargs.get(u"gateway", u"")
644 net_addr = ip_address(network)
646 prefix = IPUtil.create_prefix_object(net_addr, prefix_len)
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
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)),
662 FibPathType, u"FIB_PATH_TYPE_LOCAL"
663 if kwargs.get(u"local", False)
664 else u"FIB_PATH_TYPE_NORMAL"
666 flags=getattr(FibPathFlags, u"FIB_PATH_FLAG_NONE").value,
668 FibPathNhProto, u"FIB_PATH_NH_PROTO_IP6"
669 if net_addr.version == 6
670 else u"FIB_PATH_NH_PROTO_IP4"
674 label_stack=list(0 for _ in range(16))
679 table_id=int(kwargs.get(u"vrf", 0)),
687 def vpp_route_add(node, network, prefix_len, **kwargs):
688 """Add route to the VPP node.
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:
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)
707 :type prefix_len: int
710 count = kwargs.get(u"count", 1)
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)
718 with VatTerminal(node, json_param=False) as vat:
720 vat.vat_terminal_exec_cmd_from_template(
721 u"vpp_route_add.vat",
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""
734 net_addr = ip_address(network)
735 cmd = u"ip_route_add_del"
738 is_multipath=kwargs.get(u"multipath", False),
741 err_msg = f"Failed to add route(s) on host {node[u'host']}"
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
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)
753 def flush_ip_addresses(node, interface):
754 """Flush all IP addresses from specified interface.
756 :param node: VPP node.
757 :param interface: Interface name.
761 cmd = u"sw_interface_add_del_address"
763 sw_if_index=InterfaceUtil.get_interface_index(node, interface),
767 err_msg = f"Failed to flush IP address on interface {interface}"
769 with PapiSocketExecutor(node) as papi_exec:
770 papi_exec.add(cmd, **args).get_reply(err_msg)
773 def add_fib_table(node, table_id, ipv6=False):
774 """Create new FIB table according to ID.
776 :param node: Node to add FIB on.
777 :param table_id: FIB table ID.
778 :param ipv6: Is this an IPv6 table
783 cmd = u"ip_table_add_del"
785 table_id=int(table_id),
792 err_msg = f"Failed to add FIB table on host {node[u'host']}"
794 with PapiSocketExecutor(node) as papi_exec:
795 papi_exec.add(cmd, **args).get_reply(err_msg)