Remove hard dependency on netifaces
The project was archived in 2021, and we can fairly easily replace it with some ctypes code to call getifaddrs ourselves. Be willing to fall back to netifaces (with a warning) in case getifaddrs is not available, but I'm fairly certain it will be for all platforms we support. Could maybe use some more testing on big-endian arches / BSDs, but an attempt was at least made at supporting them. Partial-Bug: #2019233 Change-Id: I1189a60204cf96c291619f8d8ec957ed8a5be1ce
This commit is contained in:
parent
e29e2c3ae5
commit
23fa18d302
@ -4,7 +4,6 @@
|
||||
|
||||
eventlet>=0.25.0 # MIT
|
||||
greenlet>=0.3.2
|
||||
netifaces>=0.8,!=0.10.0,!=0.10.1
|
||||
PasteDeploy>=2.0.0
|
||||
lxml>=3.4.1
|
||||
requests>=2.14.2 # Apache-2.0
|
||||
|
@ -13,9 +13,13 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import netifaces
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import socket
|
||||
import warnings
|
||||
|
||||
|
||||
# Used by the parse_socket_string() function to validate IPv6 addresses
|
||||
@ -62,6 +66,83 @@ def expand_ipv6(address):
|
||||
return socket.inet_ntop(socket.AF_INET6, packed_ip)
|
||||
|
||||
|
||||
libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
|
||||
try:
|
||||
getifaddrs = libc.getifaddrs
|
||||
freeifaddrs = libc.freeifaddrs
|
||||
netifaces = None # for patching
|
||||
except AttributeError:
|
||||
getifaddrs = None
|
||||
freeifaddrs = None
|
||||
try:
|
||||
import netifaces
|
||||
except ImportError:
|
||||
raise ImportError('C function getifaddrs not available, '
|
||||
'and netifaces not installed')
|
||||
else:
|
||||
warnings.warn('getifaddrs is not available; falling back to the '
|
||||
'archived and no longer maintained netifaces project. '
|
||||
'This fallback will be removed in a future release; '
|
||||
'see https://bugs.launchpad.net/swift/+bug/2019233 for '
|
||||
'more information.', FutureWarning)
|
||||
else:
|
||||
class sockaddr_in4(ctypes.Structure):
|
||||
if platform.system() == 'Linux':
|
||||
_fields_ = [
|
||||
("sin_family", ctypes.c_uint16),
|
||||
("sin_port", ctypes.c_uint16),
|
||||
("sin_addr", ctypes.c_ubyte * 4),
|
||||
]
|
||||
else:
|
||||
# Assume BSD / OS X
|
||||
_fields_ = [
|
||||
("sin_len", ctypes.c_uint8),
|
||||
("sin_family", ctypes.c_uint8),
|
||||
("sin_port", ctypes.c_uint16),
|
||||
("sin_addr", ctypes.c_ubyte * 4),
|
||||
]
|
||||
|
||||
class sockaddr_in6(ctypes.Structure):
|
||||
if platform.system() == 'Linux':
|
||||
_fields_ = [
|
||||
("sin6_family", ctypes.c_uint16),
|
||||
("sin6_port", ctypes.c_uint16),
|
||||
("sin6_flowinfo", ctypes.c_uint32),
|
||||
("sin6_addr", ctypes.c_ubyte * 16),
|
||||
]
|
||||
else:
|
||||
# Assume BSD / OS X
|
||||
_fields_ = [
|
||||
("sin6_len", ctypes.c_uint8),
|
||||
("sin6_family", ctypes.c_uint8),
|
||||
("sin6_port", ctypes.c_uint16),
|
||||
("sin6_flowinfo", ctypes.c_uint32),
|
||||
("sin6_addr", ctypes.c_ubyte * 16),
|
||||
]
|
||||
|
||||
class ifaddrs(ctypes.Structure):
|
||||
pass
|
||||
|
||||
# Have to do this a little later so we can self-reference
|
||||
ifaddrs._fields_ = [
|
||||
("ifa_next", ctypes.POINTER(ifaddrs)),
|
||||
("ifa_name", ctypes.c_char_p),
|
||||
("ifa_flags", ctypes.c_int),
|
||||
# Use the smaller of the two to start, can cast later
|
||||
# when we *know* we're looking at INET6
|
||||
("ifa_addr", ctypes.POINTER(sockaddr_in4)),
|
||||
# Don't care about the rest of the fields
|
||||
]
|
||||
|
||||
def errcheck(result, func, arguments):
|
||||
if result != 0:
|
||||
errno = ctypes.set_errno(0)
|
||||
raise OSError(errno, "getifaddrs: %s" % os.strerror(errno))
|
||||
return result
|
||||
|
||||
getifaddrs.errcheck = errcheck
|
||||
|
||||
|
||||
def whataremyips(ring_ip=None):
|
||||
"""
|
||||
Get "our" IP addresses ("us" being the set of services configured by
|
||||
@ -85,6 +166,40 @@ def whataremyips(ring_ip=None):
|
||||
pass
|
||||
|
||||
addresses = []
|
||||
|
||||
if getifaddrs:
|
||||
addrs = ctypes.POINTER(ifaddrs)()
|
||||
getifaddrs(ctypes.byref(addrs))
|
||||
try:
|
||||
cur = addrs
|
||||
while cur:
|
||||
if not cur.contents.ifa_addr:
|
||||
# Not all interfaces will have addresses; move on
|
||||
cur = cur.contents.ifa_next
|
||||
continue
|
||||
sa_family = cur.contents.ifa_addr.contents.sin_family
|
||||
if sa_family == socket.AF_INET:
|
||||
addresses.append(
|
||||
socket.inet_ntop(
|
||||
socket.AF_INET,
|
||||
cur.contents.ifa_addr.contents.sin_addr,
|
||||
)
|
||||
)
|
||||
elif sa_family == socket.AF_INET6:
|
||||
addr = ctypes.cast(cur.contents.ifa_addr,
|
||||
ctypes.POINTER(sockaddr_in6))
|
||||
addresses.append(
|
||||
socket.inet_ntop(
|
||||
socket.AF_INET6,
|
||||
addr.contents.sin6_addr,
|
||||
)
|
||||
)
|
||||
cur = cur.contents.ifa_next
|
||||
finally:
|
||||
freeifaddrs(addrs)
|
||||
return addresses
|
||||
|
||||
# getifaddrs not available; try netifaces
|
||||
for interface in netifaces.interfaces():
|
||||
try:
|
||||
iface_data = netifaces.ifaddresses(interface)
|
||||
|
@ -13,6 +13,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import ctypes
|
||||
from mock import patch
|
||||
import socket
|
||||
import unittest
|
||||
@ -125,6 +126,57 @@ class TestWhatAreMyIPs(unittest.TestCase):
|
||||
def test_whataremyips_bind_ip_specific(self):
|
||||
self.assertEqual(['1.2.3.4'], utils.whataremyips('1.2.3.4'))
|
||||
|
||||
def test_whataremyips_getifaddrs(self):
|
||||
def mock_getifaddrs(ptr):
|
||||
addrs = [
|
||||
utils_ipaddrs.ifaddrs(None, b'lo', 0, ctypes.pointer(
|
||||
utils_ipaddrs.sockaddr_in4(
|
||||
sin_family=socket.AF_INET,
|
||||
sin_addr=(127, 0, 0, 1)))),
|
||||
utils_ipaddrs.ifaddrs(None, b'lo', 0, ctypes.cast(
|
||||
ctypes.pointer(utils_ipaddrs.sockaddr_in6(
|
||||
sin6_family=socket.AF_INET6,
|
||||
sin6_addr=(
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1))),
|
||||
ctypes.POINTER(utils_ipaddrs.sockaddr_in4))),
|
||||
utils_ipaddrs.ifaddrs(None, b'eth0', 0, ctypes.pointer(
|
||||
utils_ipaddrs.sockaddr_in4(
|
||||
sin_family=socket.AF_INET,
|
||||
sin_addr=(192, 168, 50, 63)))),
|
||||
utils_ipaddrs.ifaddrs(None, b'eth0', 0, ctypes.cast(
|
||||
ctypes.pointer(utils_ipaddrs.sockaddr_in6(
|
||||
sin6_family=socket.AF_INET6,
|
||||
sin6_addr=(
|
||||
254, 128, 0, 0, 0, 0, 0, 0,
|
||||
106, 191, 199, 168, 109, 243, 41, 35))),
|
||||
ctypes.POINTER(utils_ipaddrs.sockaddr_in4))),
|
||||
# MAC address will be ignored
|
||||
utils_ipaddrs.ifaddrs(None, b'eth0', 0, ctypes.cast(
|
||||
ctypes.pointer(utils_ipaddrs.sockaddr_in6(
|
||||
sin6_family=getattr(socket, 'AF_PACKET', 17),
|
||||
sin6_port=0,
|
||||
sin6_flowinfo=2,
|
||||
sin6_addr=(
|
||||
1, 0, 0, 6, 172, 116, 177, 85,
|
||||
64, 146, 0, 0, 0, 0, 0, 0))),
|
||||
ctypes.POINTER(utils_ipaddrs.sockaddr_in4))),
|
||||
# Seen in the wild: no addresses at all
|
||||
utils_ipaddrs.ifaddrs(None, b'cscotun0', 69841),
|
||||
]
|
||||
for cur, nxt in zip(addrs, addrs[1:]):
|
||||
cur.ifa_next = ctypes.pointer(nxt)
|
||||
ptr._obj.contents = addrs[0]
|
||||
|
||||
with patch.object(utils_ipaddrs, 'getifaddrs', mock_getifaddrs), \
|
||||
patch('swift.common.utils.ipaddrs.freeifaddrs') as mock_free:
|
||||
self.assertEqual(utils.whataremyips(), [
|
||||
'127.0.0.1',
|
||||
'::1',
|
||||
'192.168.50.63',
|
||||
'fe80::6abf:c7a8:6df3:2923',
|
||||
])
|
||||
self.assertEqual(len(mock_free.mock_calls), 1)
|
||||
|
||||
def test_whataremyips_netifaces_error(self):
|
||||
class FakeNetifaces(object):
|
||||
@staticmethod
|
||||
@ -135,7 +187,8 @@ class TestWhatAreMyIPs(unittest.TestCase):
|
||||
def ifaddresses(interface):
|
||||
raise ValueError
|
||||
|
||||
with patch.object(utils_ipaddrs, 'netifaces', FakeNetifaces):
|
||||
with patch.object(utils_ipaddrs, 'getifaddrs', None), \
|
||||
patch.object(utils_ipaddrs, 'netifaces', FakeNetifaces):
|
||||
self.assertEqual(utils.whataremyips(), [])
|
||||
|
||||
def test_whataremyips_netifaces_ipv6(self):
|
||||
@ -156,7 +209,8 @@ class TestWhatAreMyIPs(unittest.TestCase):
|
||||
{'netmask': 'ffff:ffff:ffff:ffff::',
|
||||
'addr': '%s%%%s' % (test_ipv6_address, test_interface)}]}
|
||||
|
||||
with patch.object(utils_ipaddrs, 'netifaces', FakeNetifaces):
|
||||
with patch.object(utils_ipaddrs, 'getifaddrs', None), \
|
||||
patch.object(utils_ipaddrs, 'netifaces', FakeNetifaces):
|
||||
myips = utils.whataremyips()
|
||||
self.assertEqual(len(myips), 1)
|
||||
self.assertEqual(myips[0], test_ipv6_address)
|
||||
|
Loading…
Reference in New Issue
Block a user