Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions test/functional/feature_bind_extra.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ def set_test_params(self):
self.num_nodes = 3

def skip_test_if_missing_module(self):
# Due to OS-specific network stats queries, we only run on Linux.
self.skip_if_platform_not_linux()
self.skip_if_platform_not_posix()

def setup_network(self):
loopback_ipv4 = addr_to_hex("127.0.0.1")
Expand Down
8 changes: 5 additions & 3 deletions test/functional/rpc_bind.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ def set_test_params(self):
self.supports_cli = False

def skip_test_if_missing_module(self):
# due to OS-specific network stats queries, this test works only on Linux
self.skip_if_platform_not_linux()
self.skip_if_platform_not_posix()
Comment thread
l0rinc marked this conversation as resolved.

def setup_network(self):
self.add_nodes(self.num_nodes, None)
Expand Down Expand Up @@ -105,8 +104,11 @@ def run_test(self):
raise SkipTest("This test requires ipv6 support.")

self.log.info("Check for non-loopback interface")
interfaces = all_interfaces()
if not interfaces:
raise AssertionError("all_interfaces() returned no IPv4 interfaces")
self.non_loopback_ip = None
for name,ip in all_interfaces():
for name,ip in interfaces:
if ip != '127.0.0.1':
self.non_loopback_ip = ip
break
Expand Down
94 changes: 62 additions & 32 deletions test/functional/test_framework/netutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Copyright (c) 2014-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Linux network utilities.
"""Linux, macOS, and BSD network utilities.

Roughly based on https://web.archive.org/web/20190424172231/http://voorloopnul.com/blog/a-python-netstat-in-less-than-100-lines-of-code/ by Ricardo Pascal
"""
Expand Down Expand Up @@ -88,40 +88,70 @@ def get_bind_addrs(pid):
'''
Get bind addresses as (host,port) tuples for process pid.
'''
inodes = get_socket_inodes(pid)
bind_addrs = []
for conn in netstat('tcp') + netstat('tcp6'):
if conn[3] == STATE_LISTEN and conn[4] in inodes:
bind_addrs.append(conn[1])
return bind_addrs

# from: https://code.activestate.com/recipes/439093/
if sys.platform == 'linux':
inodes = get_socket_inodes(pid)
bind_addrs = []
for conn in netstat('tcp') + netstat('tcp6'):
if conn[3] == STATE_LISTEN and conn[4] in inodes:
bind_addrs.append(conn[1])
return bind_addrs
elif sys.platform.startswith(("darwin", "freebsd", "netbsd", "openbsd")):
import re
import subprocess
output = subprocess.check_output(["lsof",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenBSD does not provide the lsof port.

*(["-Di"] if sys.platform.startswith("freebsd") else []), # Ignore device cache to avoid stderr warnings.
"-nP", # Keep hosts and ports numeric.
"-a", # Require all filters to match.
"-p", str(pid), # Limit results to the target pid.
"-iTCP", # Only inspect TCP sockets.
"-sTCP:LISTEN", # Only keep listening sockets.
"-Ftn", # Emit machine-readable type and name fields.
], text=True)
return [
(addr_to_hex(("::" if sock_type == "IPv6" else "0.0.0.0") if host == "*" else host.strip("[]")), int(port))
for sock_type, host, port in re.findall(r"t(IPv[46])\nn(\*|\[.+?]|[^:]+):(\d+)", output)
Comment thread
l0rinc marked this conversation as resolved.
Comment thread
l0rinc marked this conversation as resolved.
]
else:
raise NotImplementedError(f"get_bind_addrs is not supported on {sys.platform}")

def all_interfaces():
'''
Return all interfaces that are up
Return all IPv4 interfaces that are up.
'''
import fcntl # Linux only, so only import when required

is_64bits = sys.maxsize > 2**32
struct_size = 40 if is_64bits else 32
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
max_possible = 8 # initial value
while True:
bytes = max_possible * struct_size
names = array.array('B', b'\0' * bytes)
outbytes = struct.unpack('iL', fcntl.ioctl(
s.fileno(),
0x8912, # SIOCGIFCONF
struct.pack('iL', bytes, names.buffer_info()[0])
))[0]
if outbytes == bytes:
max_possible *= 2
else:
break
namestr = names.tobytes()
return [(namestr[i:i+16].split(b'\0', 1)[0],
socket.inet_ntoa(namestr[i+20:i+24]))
for i in range(0, outbytes, struct_size)]
if sys.platform == 'linux':
import fcntl # Linux only, so only import when required

is_64bits = sys.maxsize > 2**32
struct_size = 40 if is_64bits else 32
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
max_possible = 8 # initial value
while True:
bytes = max_possible * struct_size
names = array.array('B', b'\0' * bytes)
outbytes = struct.unpack('iL', fcntl.ioctl(
s.fileno(),
0x8912, # SIOCGIFCONF
struct.pack('iL', bytes, names.buffer_info()[0])
))[0]
if outbytes == bytes:
max_possible *= 2
else:
break
namestr = names.tobytes()
return [(namestr[i:i+16].split(b'\0', 1)[0],
socket.inet_ntoa(namestr[i+20:i+24]))
for i in range(0, outbytes, struct_size)]
elif sys.platform.startswith(("darwin", "freebsd", "netbsd", "openbsd")):
import re
import subprocess
output = subprocess.check_output(["ifconfig", "-au"], text=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenBSD's ifconfig does not have the -u option.

return [
(m["iface"].encode(), ip)
for m in re.finditer(r"(?m)^(?P<iface>\S+):(?P<block>[^\n]*(?:\n[ \t]+[^\n]*)*)", output)
for ip in re.findall(r"inet (\S+)", m["block"])
Comment thread
l0rinc marked this conversation as resolved.
Comment on lines +150 to +151

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree it's a (slight) shame about all the regex needed, but don't know a better way to do it either without a large rewrite to ctypes or pulling in psutil (as Sjors already noted).

]
else:
raise NotImplementedError(f"all_interfaces is not supported on {sys.platform}")
Comment thread
l0rinc marked this conversation as resolved.

def addr_to_hex(addr):
'''
Expand Down
Loading