From 5b3565e0a739eeeddeb1e99d252954fc9eac59b2 Mon Sep 17 00:00:00 2001 From: Catherine Redfield Date: Tue, 28 Nov 2023 09:40:52 -0500 Subject: [PATCH] refactor: remove dependency on netifaces netifaces is no longer being maintained and is only used by the VMWare data source. As such this commit replaces the calls to netifaces with cloudinit's native netinfo. --- cloudinit/sources/DataSourceVMware.py | 150 +++++++++++-------------- tests/unittests/sources/test_vmware.py | 2 +- 2 files changed, 69 insertions(+), 83 deletions(-) diff --git a/cloudinit/sources/DataSourceVMware.py b/cloudinit/sources/DataSourceVMware.py index 1591121d61aa..8bf0e7b02083 100644 --- a/cloudinit/sources/DataSourceVMware.py +++ b/cloudinit/sources/DataSourceVMware.py @@ -72,9 +72,7 @@ import socket import time -import netifaces - -from cloudinit import atomic_helper, dmi, log, net, sources, util +from cloudinit import atomic_helper, dmi, log, net, netinfo, sources, util from cloudinit.sources.helpers.vmware.imc import guestcust_util from cloudinit.subp import ProcessExecutionError, subp, which @@ -776,91 +774,52 @@ def get_default_ip_addrs(): addresses associated with the device used by the default route for a given address. """ - # TODO(promote and use netifaces in cloudinit.net* modules) - gateways = netifaces.gateways() - if "default" not in gateways: - return None, None - - default_gw = gateways["default"] - if ( - netifaces.AF_INET not in default_gw - and netifaces.AF_INET6 not in default_gw - ): - return None, None - ipv4 = None ipv6 = None + routes = netinfo.route_info() + for route in routes: + if "G" in route["flags"]: + if ipv4 and ipv4 != route["gateway"]: + LOG.debug( + "multiple ipv4 gateways: %s, %s", ipv4, route["gateway"] + ) + ipv4 = route["gateway"] + elif "UG" in route["flags"]: + if ipv6 and ipv6 != route["gateway"]: + LOG.debug( + "multiple ipv6 gateways: %s, %s", ipv6, route["gateway"] + ) + ipv6 = route["gateway"] - gw4 = default_gw.get(netifaces.AF_INET) - if gw4: - _, dev4 = gw4 - addr4_fams = netifaces.ifaddresses(dev4) - if addr4_fams: - af_inet4 = addr4_fams.get(netifaces.AF_INET) - if af_inet4: - if len(af_inet4) > 1: - LOG.debug( - "device %s has more than one ipv4 address: %s", - dev4, - af_inet4, - ) - elif "addr" in af_inet4[0]: - ipv4 = af_inet4[0]["addr"] - - # Try to get the default IPv6 address by first seeing if there is a default - # IPv6 route. - gw6 = default_gw.get(netifaces.AF_INET6) - if gw6: - _, dev6 = gw6 - addr6_fams = netifaces.ifaddresses(dev6) - if addr6_fams: - af_inet6 = addr6_fams.get(netifaces.AF_INET6) - if af_inet6: - if len(af_inet6) > 1: - LOG.debug( - "device %s has more than one ipv6 address: %s", - dev6, - af_inet6, - ) - elif "addr" in af_inet6[0]: - ipv6 = af_inet6[0]["addr"] + # If there is an IPv4 gateway and an IPv6 gateway, return immediately to + # avoid extra work + if ipv4 and ipv6: + return ipv4, ipv4 + netdev = netinfo.netdev_info() # If there is a default IPv4 address but not IPv6, then see if there is a # single IPv6 address associated with the same device associated with the # default IPv4 address. - if ipv4 and not ipv6: - af_inet6 = addr4_fams.get(netifaces.AF_INET6) - if af_inet6: - if len(af_inet6) > 1: - LOG.debug( - "device %s has more than one ipv6 address: %s", - dev4, - af_inet6, - ) - elif "addr" in af_inet6[0]: - ipv6 = af_inet6[0]["addr"] + if ipv4 is not None and ipv6 is None: + for dev_name in netdev.keys(): + for addr in netdev[dev_name]["ipv4"]: + if addr["ip"] == ipv4 and len(netdev[dev_name]["ipv6"]) == 1: + ipv6 = netdev[dev_name]["ipv6"][0]["ip"] + break # If there is a default IPv6 address but not IPv4, then see if there is a # single IPv4 address associated with the same device associated with the # default IPv6 address. - if not ipv4 and ipv6: - af_inet4 = addr6_fams.get(netifaces.AF_INET) - if af_inet4: - if len(af_inet4) > 1: - LOG.debug( - "device %s has more than one ipv4 address: %s", - dev6, - af_inet4, - ) - elif "addr" in af_inet4[0]: - ipv4 = af_inet4[0]["addr"] + if ipv4 is None and ipv6 is not None: + for dev_name in netdev: + for addr in netdev[dev_name]["ipv6"]: + if addr["ip"] == ipv6 and len(netdev[dev_name]["ipv4"]) == 1: + ipv4 = netdev[dev_name]["ipv6"][0]["ip"] + break return ipv4, ipv6 -# patched socket.getfqdn() - see https://bugs.python.org/issue5004 - - def getfqdn(name=""): """Get fully qualified domain name from name. An empty argument is interpreted as meaning the local host. @@ -895,6 +854,33 @@ def is_valid_ip_addr(val): ) +def convert_to_netifaces_format(addr): + """ + Takes a cloudinit.netinfo formatted address and converts to netifaces + format, since this module was originally written with netifaces as the + network introspection module. + netifaces format: + { + "broadcast": "10.15.255.255", + "netmask": "255.240.0.0", + "addr": "10.0.1.4" + } + + cloudinit.netinfo format: + { + "ip": "10.0.1.4", + "mask": "255.240.0.0", + "bcast": "10.15.255.255", + "scope": "global", + } + """ + return { + "broadcast": addr["bcast"], + "netmask": addr["mask"], + "addr": addr["ip"], + } + + def get_host_info(): """ Returns host information such as the host name and network interfaces. @@ -925,16 +911,16 @@ def get_host_info(): by_ipv4 = host_info["network"]["interfaces"]["by-ipv4"] by_ipv6 = host_info["network"]["interfaces"]["by-ipv6"] - ifaces = netifaces.interfaces() + ifaces = netinfo.netdev_info() for dev_name in ifaces: - addr_fams = netifaces.ifaddresses(dev_name) - af_link = addr_fams.get(netifaces.AF_LINK) - af_inet4 = addr_fams.get(netifaces.AF_INET) - af_inet6 = addr_fams.get(netifaces.AF_INET6) - - mac = None - if af_link and "addr" in af_link[0]: - mac = af_link[0]["addr"] + af_inet4 = [] + af_inet6 = [] + for addr in ifaces[dev_name]["ipv4"]: + af_inet4.append(convert_to_netifaces_format(addr)) + for addr in ifaces[dev_name]["ipv6"]: + af_inet6.append(convert_to_netifaces_format(addr)) + + mac = ifaces[dev_name].get("hwaddr") # Do not bother recording localhost if mac == "00:00:00:00:00:00": diff --git a/tests/unittests/sources/test_vmware.py b/tests/unittests/sources/test_vmware.py index a6ad19631868..6cedc716256e 100644 --- a/tests/unittests/sources/test_vmware.py +++ b/tests/unittests/sources/test_vmware.py @@ -74,7 +74,7 @@ def common_patches(): is_FreeBSD=mock.Mock(return_value=False), ), mock.patch( - "cloudinit.sources.DataSourceVMware.netifaces.interfaces", + "cloudinit.netinfo.netdev_info", return_value=[], ), mock.patch(