1 # Copyright (c) 2019 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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """Common IP utilities library."""
18 from socket import AF_INET, AF_INET6, inet_ntop, inet_pton
20 from ipaddress import ip_address
21 from ipaddress import IPv4Network, IPv6Network, IPv4Address, IPv6Address
22 from ipaddress import AddressValueError, NetmaskValueError
24 from resources.libraries.python.Constants import Constants
25 from resources.libraries.python.InterfaceUtil import InterfaceUtil
26 from resources.libraries.python.PapiExecutor import PapiExecutor
27 from resources.libraries.python.ssh import exec_cmd_no_error, exec_cmd
28 from resources.libraries.python.topology import NodeType, Topology
32 """Common IP utilities"""
35 def ip_to_int(ip_str):
36 """Convert IP address from string format (e.g. 10.0.0.1) to integer
37 representation (167772161).
39 :param ip_str: IP address in string representation.
41 :returns: Integer representation of IP address.
44 return int(ip_address(unicode(ip_str)))
47 def int_to_ip(ip_int):
48 """Convert IP address from integer representation (e.g. 167772161) to
49 string format (10.0.0.1).
51 :param ip_int: IP address in integer representation.
53 :returns: String representation of IP address.
56 return str(ip_address(ip_int))
59 def vpp_get_interface_ip_addresses(node, interface, ip_version):
60 """Get list of IP addresses from an interface on a VPP node.
62 :param node: VPP node.
63 :param interface: Name of an interface on the VPP node.
64 :param ip_version: IP protocol version (ipv4 or ipv6).
68 :returns: List of dictionaries, each containing IP address, subnet
69 prefix length and also the subnet mask for ipv4 addresses.
70 Note: A single interface may have multiple IP addresses assigned.
74 sw_if_index = Topology.convert_interface_reference(
75 node, interface, 'sw_if_index')
77 if isinstance(interface, basestring):
78 sw_if_index = InterfaceUtil.get_sw_if_index(node, interface)
82 is_ipv6 = 1 if ip_version == 'ipv6' else 0
84 cmd = 'ip_address_dump'
85 cmd_reply = 'ip_address_details'
86 args = dict(sw_if_index=sw_if_index,
88 err_msg = 'Failed to get L2FIB dump on host {host}'.format(
91 with PapiExecutor(node) as papi_exec:
92 papi_resp = papi_exec.add(cmd, **args).get_dump(err_msg)
95 for item in papi_resp.reply[0]['api_reply']:
96 item[cmd_reply]['ip'] = inet_ntop(AF_INET6, item[cmd_reply]['ip']) \
97 if is_ipv6 else inet_ntop(AF_INET, item[cmd_reply]['ip'][0:4])
98 item[cmd_reply]['netmask'] = str(
99 IPv6Network(unicode('::/{pl}'.format(
100 pl=item[cmd_reply]['prefix_length']))).netmask) if is_ipv6 \
101 else str(IPv4Network(unicode('0.0.0.0/{pl}'.format(
102 pl=item[cmd_reply]['prefix_length']))).netmask)
103 data.append(item[cmd_reply])
108 def get_interface_vrf_table(node, interface, ip_version='ipv4'):
109 """Get vrf ID for the given interface.
111 :param node: VPP node.
112 :param interface: Name or sw_if_index of a specific interface.
114 :param ip_version: IP protocol version (ipv4 or ipv6).
115 :type interface: str or int
116 :type ip_version: str
117 :returns: vrf ID of the specified interface.
120 if isinstance(interface, basestring):
121 sw_if_index = InterfaceUtil.get_sw_if_index(node, interface)
123 sw_if_index = interface
125 is_ipv6 = 1 if ip_version == 'ipv6' else 0
127 cmd = 'sw_interface_get_table'
128 args = dict(sw_if_index=sw_if_index,
130 err_msg = 'Failed to get VRF id assigned to interface {ifc}'.format(
133 with PapiExecutor(node) as papi_exec:
134 papi_resp = papi_exec.add(cmd, **args).get_replies(err_msg). \
135 verify_reply(err_msg=err_msg)
137 return papi_resp['vrf_id']
140 def vpp_ip_source_check_setup(node, if_name):
141 """Setup Reverse Path Forwarding source check on interface.
143 :param node: VPP node.
144 :param if_name: Interface name to setup RPF source check.
148 cmd = 'ip_source_check_interface_add_del'
150 sw_if_index=InterfaceUtil.get_interface_index(node, if_name),
153 err_msg = 'Failed to enable source check on interface {ifc}'.format(
155 with PapiExecutor(node) as papi_exec:
156 papi_exec.add(cmd, **args).get_replies(err_msg). \
157 verify_reply(err_msg=err_msg)
160 def vpp_ip_probe(node, interface, addr):
161 """Run ip probe on VPP node.
163 :param node: VPP node.
164 :param interface: Interface key or name.
165 :param addr: IPv4/IPv6 address.
170 cmd = 'ip_probe_neighbor'
171 cmd_reply = 'proxy_arp_intfc_enable_disable_reply'
173 sw_if_index=InterfaceUtil.get_interface_index(node, interface),
175 err_msg = 'VPP ip probe {dev} {ip} failed on {h}'.format(
176 dev=interface, ip=addr, h=node['host'])
178 with PapiExecutor(node) as papi_exec:
179 papi_exec.add(cmd, **args).get_replies(err_msg). \
180 verify_reply(cmd_reply=cmd_reply, err_msg=err_msg)
183 def ip_addresses_should_be_equal(ip1, ip2):
184 """Fails if the given IP addresses are unequal.
186 :param ip1: IPv4 or IPv6 address.
187 :param ip2: IPv4 or IPv6 address.
191 addr1 = ip_address(unicode(ip1))
192 addr2 = ip_address(unicode(ip2))
195 raise AssertionError('IP addresses are not equal: {0} != {1}'.
199 def setup_network_namespace(node, namespace_name, interface_name,
201 """Setup namespace on given node and attach interface and IP to
202 this namespace. Applicable also on TG node.
204 :param node: VPP node.
205 :param namespace_name: Namespace name.
206 :param interface_name: Interface name.
207 :param ip_addr: IP address of namespace's interface.
208 :param prefix: IP address prefix length.
210 :type namespace_name: str
211 :type interface_name: str
215 cmd = ('ip netns add {0}'.format(namespace_name))
216 exec_cmd_no_error(node, cmd, sudo=True)
218 cmd = ('ip link set dev {0} up netns {1}'.format(interface_name,
220 exec_cmd_no_error(node, cmd, sudo=True)
222 cmd = ('ip netns exec {0} ip addr add {1}/{2} dev {3}'.format(
223 namespace_name, ip_addr, prefix, interface_name))
224 exec_cmd_no_error(node, cmd, sudo=True)
227 def linux_enable_forwarding(node, ip_ver='ipv4'):
228 """Enable forwarding on a Linux node, e.g. VM.
230 :param node: VPP node.
231 :param ip_ver: IP version, 'ipv4' or 'ipv6'.
235 cmd = 'sysctl -w net.{0}.ip_forward=1'.format(ip_ver)
236 exec_cmd_no_error(node, cmd, sudo=True)
239 def get_linux_interface_name(node, pci_addr):
240 """Get the interface name.
242 :param node: VPP/TG node.
243 :param pci_addr: PCI address
246 :returns: Interface name
248 :raises RuntimeError: If cannot get the information about interfaces.
250 regex_intf_info = r"pci@" \
251 r"([0-9a-f]{4}:[0-9a-f]{2}:[0-9a-f]{2}.[0-9a-f])\s*" \
252 r"([a-zA-Z0-9]*)\s*network"
254 cmd = "lshw -class network -businfo"
255 ret_code, stdout, stderr = exec_cmd(node, cmd, timeout=30, sudo=True)
257 raise RuntimeError('Could not get information about interfaces:\n'
258 '{err}'.format(err=stderr))
260 for line in stdout.splitlines()[2:]:
262 if re.search(regex_intf_info, line).group(1) == pci_addr:
263 return re.search(regex_intf_info, line).group(2)
264 except AttributeError:
269 def set_linux_interface_up(node, interface):
270 """Set the specified interface up.
272 :param node: VPP/TG node.
273 :param interface: Interface in namespace.
276 :raises RuntimeError: If the interface could not be set up.
278 cmd = "ip link set {0} up".format(interface)
279 exec_cmd_no_error(node, cmd, timeout=30, sudo=True)
282 def set_linux_interface_ip(node, interface, ip_addr, prefix,
284 """Set IP address to interface in linux.
286 :param node: VPP/TG node.
287 :param interface: Interface in namespace.
288 :param ip_addr: IP to be set on interface.
289 :param prefix: IP prefix.
290 :param namespace: Execute command in namespace. Optional
296 :raises RuntimeError: IP could not be set.
298 if namespace is not None:
299 cmd = 'ip netns exec {ns} ip addr add {ip}/{p} dev {dev}'.format(
300 ns=namespace, ip=ip_addr, p=prefix, dev=interface)
302 cmd = 'ip addr add {ip}/{p} dev {dev}'.format(
303 ip=ip_addr, p=prefix, dev=interface)
305 exec_cmd_no_error(node, cmd, timeout=5, sudo=True)
308 def add_linux_route(node, ip_addr, prefix, gateway, namespace=None):
309 """Add linux route in namespace.
311 :param node: Node where to execute command.
312 :param ip_addr: Route destination IP address.
313 :param prefix: IP prefix.
314 :param namespace: Execute command in namespace. Optional.
315 :param gateway: Gateway address.
322 if namespace is not None:
323 cmd = 'ip netns exec {} ip route add {}/{} via {}'.format(
324 namespace, ip_addr, prefix, gateway)
326 cmd = 'ip route add {}/{} via {}'.format(ip_addr, prefix, gateway)
327 exec_cmd_no_error(node, cmd, sudo=True)
330 def vpp_interface_set_ip_address(node, interface, address,
332 """Set IP address to VPP interface.
334 :param node: VPP node.
335 :param interface: Interface name.
336 :param address: IP address.
337 :param prefix_length: Prefix length.
341 :type prefix_length: int
344 ip_addr = IPv6Address(unicode(address))
347 except (AddressValueError, NetmaskValueError):
348 ip_addr = IPv4Address(unicode(address))
352 cmd = 'sw_interface_add_del_address'
354 sw_if_index=InterfaceUtil.get_interface_index(node, interface),
358 address_length=int(prefix_length) if prefix_length else 128
360 address=inet_pton(af_inet, str(ip_addr)))
361 err_msg = 'Failed to add IP address on interface {ifc}'.format(
363 with PapiExecutor(node) as papi_exec:
364 papi_exec.add(cmd, **args).get_replies(err_msg). \
365 verify_reply(err_msg=err_msg)
368 def vpp_add_ip_neighbor(node, iface_key, ip_addr, mac_address):
369 """Add IP neighbor on DUT node.
371 :param node: VPP node.
372 :param iface_key: Interface key.
373 :param ip_addr: IP address of the interface.
374 :param mac_address: MAC address of the interface.
378 :type mac_address: str
381 dst_ip = IPv6Address(unicode(ip_addr))
382 except (AddressValueError, NetmaskValueError):
383 dst_ip = IPv4Address(unicode(ip_addr))
386 sw_if_index=Topology.get_interface_sw_index(
389 mac_address=str(mac_address),
390 ip_address=str(dst_ip))
391 cmd = 'ip_neighbor_add_del'
395 err_msg = 'Failed to add IP neighbor on interface {ifc}'.format(
397 with PapiExecutor(node) as papi_exec:
398 papi_exec.add(cmd, **args).get_replies(err_msg). \
399 verify_reply(err_msg=err_msg)
402 def vpp_route_add(node, network, prefix_len, **kwargs):
403 """Add route to the VPP node.
405 :param node: VPP node.
406 :param network: Route destination network address.
407 :param prefix_len: Route destination network prefix length.
408 :param kwargs: Optional key-value arguments:
410 gateway: Route gateway address. (str)
411 interface: Route interface. (str)
412 vrf: VRF table ID. (int)
413 count: number of IP addresses to add starting from network IP (int)
414 local: The route is local with same prefix (increment is 1).
415 If None, then is not used. (bool)
416 lookup_vrf: VRF table ID for lookup. (int)
417 multipath: Enable multipath routing. (bool)
418 weight: Weight value for unequal cost multipath routing. (int)
422 :type prefix_len: int
425 interface = kwargs.get('interface', None)
426 gateway = kwargs.get('gateway', None)
429 net_addr = IPv6Address(unicode(network))
432 except (AddressValueError, NetmaskValueError):
433 net_addr = IPv4Address(unicode(network))
439 gt_addr = IPv6Address(unicode(gateway))
440 af_inet_gt = AF_INET6
441 except (AddressValueError, NetmaskValueError):
442 gt_addr = IPv4Address(unicode(gateway))
445 cmd = 'ip_add_del_route'
447 next_hop_sw_if_index=InterfaceUtil.get_interface_index(
448 node, interface) if interface else Constants.BITWISE_NON_ZERO,
449 table_id=int(kwargs.get('vrf', 0)),
452 is_local=int(kwargs.get('local', False)),
453 is_multipath=int(kwargs.get('multipath', False)),
454 next_hop_weight=int(kwargs.get('weight', 1)),
455 next_hop_proto=1 if is_ipv6 else 0,
456 dst_address_length=int(prefix_len),
457 next_hop_address=inet_pton(af_inet_gt, str(gt_addr)) if gateway
459 next_hop_table_id=int(kwargs.get('lookup_vrf', 0)))
460 err_msg = 'Failed to add route(s) on host {host}'.format(
462 with PapiExecutor(node) as papi_exec:
463 for i in xrange(kwargs.get('count', 1)):
464 papi_exec.add(cmd, dst_address=inet_pton(
465 af_inet, str(net_addr+i)), **args)
466 papi_exec.get_replies(err_msg).verify_replies(err_msg=err_msg)
469 def vpp_nodes_set_ipv4_addresses(nodes, nodes_addr):
470 """Set IPv4 addresses on all VPP nodes in topology.
472 :param nodes: Nodes of the test topology.
473 :param nodes_addr: Available nodes IPv4 addresses.
475 :type nodes_addr: dict
476 :returns: Affected interfaces as list of (node, interface) tuples.
480 for net in nodes_addr.values():
481 for port in net['ports'].values():
482 host = port.get('node')
486 node = topo.get_node_by_hostname(nodes, host)
489 if node['type'] != NodeType.DUT:
491 iface_key = topo.get_interface_by_name(node, port['if'])
492 IPUtil.vpp_interface_set_ip_address(
493 node, iface_key, port['addr'], net['prefix'])
494 interfaces.append((node, port['if']))
499 def flush_ip_addresses(node, interface):
500 """Flush all IPv4addresses from specified interface.
502 :param node: VPP node.
503 :param interface: Interface name.
507 cmd = 'sw_interface_add_del_address'
509 sw_if_index=InterfaceUtil.get_interface_index(node, interface),
511 err_msg = 'Failed to flush IP address on interface {ifc}'.format(
513 with PapiExecutor(node) as papi_exec:
514 papi_exec.add(cmd, **args).get_replies(err_msg). \
515 verify_reply(err_msg=err_msg)
518 def add_fib_table(node, table_id, ipv6=False):
519 """Create new FIB table according to ID.
521 :param node: Node to add FIB on.
522 :param table_id: FIB table ID.
523 :param ipv6: Is this an IPv6 table
528 cmd = 'ip_table_add_del'
530 table_id=int(table_id),
533 err_msg = 'Failed to add FIB table on host {host}'.format(
535 with PapiExecutor(node) as papi_exec:
536 papi_exec.add(cmd, **args).get_replies(err_msg). \
537 verify_reply(err_msg=err_msg)