Replace netifaces with ifaddr

Python netifaces has been unmaintained and its main repository has been
archived since June, 2021.

Python ifaddr is an alternative to netifaces, is currently maintained,
and provides an API which requires little change for FreeIPA current
usage.

This patch modifies FreeIPA to rely on ifaddr instead of neitfaces, due
to its current maintainance status.

Fixes: https://pagure.io/freeipa/issue/9555

Signed-off-by: Rafael Guterres Jeffman <rjeffman@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
This commit is contained in:
Rafael Guterres Jeffman 2024-04-25 13:01:09 -03:00 committed by Rob Crittenden
parent cce8dc4da8
commit 6c6b9354b5
8 changed files with 53 additions and 53 deletions

View File

@ -120,7 +120,7 @@ extraction:
- lxml
- gssapi
- netaddr
- netifaces
- ifaddr
- polib
- requests
- python-augeas

View File

@ -11,6 +11,7 @@ m2r2
## ipa dependencies
dnspython
jwcrypto
ifaddr
netaddr
qrcode
six

View File

@ -403,7 +403,7 @@ BuildRequires: python3-libipa_hbac
BuildRequires: python3-libsss_nss_idmap
BuildRequires: python3-lxml
BuildRequires: python3-netaddr >= %{python_netaddr_version}
BuildRequires: python3-netifaces
BuildRequires: python3-ifaddr
BuildRequires: python3-pki >= %{pki_version}
BuildRequires: python3-polib
BuildRequires: python3-pyasn1
@ -885,7 +885,7 @@ Requires: python3-gssapi >= 1.2.0
Requires: python3-jwcrypto >= 0.4.2
Requires: python3-libipa_hbac
Requires: python3-netaddr >= %{python_netaddr_version}
Requires: python3-netifaces >= 0.10.4
Requires: python3-ifaddr
Requires: python3-pyasn1 >= 0.3.2-2
Requires: python3-pyasn1-modules >= 0.3.2-2
Requires: python3-pyusb

View File

@ -18,7 +18,7 @@ import logging
import dns
import getpass
import gssapi
import netifaces
import ifaddr
import os
import re
import SSSDConfig
@ -1351,31 +1351,36 @@ def unconfigure_nisdomain(statestore):
def get_iface_from_ip(ip_addr):
for interface in netifaces.interfaces():
if_addrs = netifaces.ifaddresses(interface)
for family in [netifaces.AF_INET, netifaces.AF_INET6]:
for ip in if_addrs.get(family, []):
if ip['addr'] == ip_addr:
return interface
for adapter in ifaddr.get_adapters():
for ips in adapter.ips:
# IPv6 is reported as a tuple, IPv4 is reported as str
if ip_addr in (ips.ip[0], ips.ip):
return adapter.name
raise RuntimeError("IP %s not assigned to any interface." % ip_addr)
def get_local_ipaddresses(iface=None):
def __get_ifaddr_adapters(iface=None):
if iface:
interfaces = [iface]
interfaces = set(iface if isinstance(iface, (list, tuple)) else [iface])
else:
interfaces = netifaces.interfaces()
interfaces = set(adapter.name for adapter in ifaddr.get_adapters())
return [
adapter
for adapter in ifaddr.get_adapters()
if adapter.name in interfaces or adapter.nice_name in interfaces
]
def get_local_ipaddresses(iface=None):
ips = []
for interface in interfaces:
if_addrs = netifaces.ifaddresses(interface)
for family in [netifaces.AF_INET, netifaces.AF_INET6]:
for ip in if_addrs.get(family, []):
try:
ips.append(ipautil.CheckedIPAddress(ip['addr']))
logger.debug('IP check successful: %s', ip['addr'])
except ValueError as e:
logger.debug('IP check failed: %s', e)
for adapter in __get_ifaddr_adapters(iface):
for ifip in adapter.ips:
try:
ip_addr = ifip.ip[0] if isinstance(ifip.ip, tuple) else ifip.ip
ips.append(ipautil.CheckedIPAddress(ip_addr))
logger.debug('IP check successful: %s', ip_addr)
except ValueError as e:
logger.debug('IP check failed: %s', e)
return ips

View File

@ -48,9 +48,9 @@ import six
from six.moves import input
try:
import netifaces
import ifaddr
except ImportError:
netifaces = None
ifaddr = None
from ipapython.dn import DN
from ipaplatform.paths import paths
@ -203,42 +203,37 @@ class CheckedIPAddress(UnsafeIPAddress):
:return: InterfaceDetails named tuple or None if no interface has
this address
"""
if netifaces is None:
raise ImportError("netifaces")
if ifaddr is None:
raise ImportError("ifaddr")
logger.debug("Searching for an interface of IP address: %s", self)
if self.version == 4:
family = netifaces.AF_INET
family_ips = (
(ip.ip, ip.network_prefix, ip.nice_name)
for ips in [a.ips for a in ifaddr.get_adapters()]
for ip in ips if not isinstance(ip.ip, tuple)
)
elif self.version == 6:
family = netifaces.AF_INET6
family_ips = (
(ip.ip[0], ip.network_prefix, ip.nice_name)
for ips in [a.ips for a in ifaddr.get_adapters()]
for ip in ips if isinstance(ip.ip, tuple)
)
else:
raise ValueError(
"Unsupported address family ({})".format(self.version)
)
for interface in netifaces.interfaces():
for ifdata in netifaces.ifaddresses(interface).get(family, []):
for ip, prefix, ifname in family_ips:
ifaddrmask = "{ip}/{prefix}".format(ip=ip, prefix=prefix)
logger.debug(
"Testing local IP address: %s (interface: %s)",
ifaddrmask, ifname)
ifnet = netaddr.IPNetwork(ifaddrmask)
# link-local addresses contain '%suffix' that causes parse
# errors in IPNetwork
ifaddr = ifdata['addr'].split(u'%', 1)[0]
if ifnet.ip == self:
return InterfaceDetails(ifname, ifnet)
# newer versions of netifaces provide IPv6 netmask in format
# 'ffff:ffff:ffff:ffff::/64'. We have to split and use prefix
# or the netmask with older versions
ifmask = ifdata['netmask'].split(u'/')[-1]
ifaddrmask = '{addr}/{netmask}'.format(
addr=ifaddr,
netmask=ifmask
)
logger.debug(
"Testing local IP address: %s (interface: %s)",
ifaddrmask, interface)
ifnet = netaddr.IPNetwork(ifaddrmask)
if ifnet.ip == self:
return InterfaceDetails(interface, ifnet)
return None
def set_ip_net(self, ifnet):

View File

@ -48,6 +48,6 @@ if __name__ == '__main__':
extras_require={
"ldap": ["python-ldap"], # ipapython.ipaldap
# CheckedIPAddress.get_matching_interface
"netifaces": ["netifaces"],
"ifaddr": ["ifaddr"],
},
)

View File

@ -75,7 +75,7 @@ PACKAGE_VERSION = {
'ipaserver': 'ipaserver == {}'.format(VERSION),
'jwcrypto': 'jwcrypto >= 0.4.2',
'kdcproxy': 'kdcproxy >= 0.3',
'netifaces': 'netifaces >= 0.10.4',
'ifaddr': 'ifaddr >= 0.1.7',
'python-ldap': 'python-ldap >= 3.0.0',
'python-yubico': 'python-yubico >= 1.2.3',
'qrcode': 'qrcode >= 5.0',

View File

@ -13,7 +13,6 @@ extension-pkg-allow-list=
_ldap,
cryptography,
gssapi,
netifaces,
lxml.etree,
pysss_murmur,