test: enable `rpc_bind` on macOS and BSD
What changed, and why it matters
This commit only changes Bitcoin Core's internal test code. It expands an existing test so it can run on macOS and BSD systems, and adds a helper function that reads network interface information from the operating system. There is no change to the actual Bitcoin node software that users run, and nothing in the commit introduces a security vulnerability or fixes one.
No security action needed. This is a test-infrastructure change; normal code review and CI verification are sufficient.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch modifies test/functional/rpc_bind.py and test/functional/test_framework/netutil.py. It replaces a Linux-only platform guard with a POSIX guard, adds macOS/BSD support to all_interfaces() by parsing ‘ifconfig -au’ output, and adds an early assertion if no IPv4 interfaces are found. The production Bitcoin Core networking/RPC code is untouched. No security-relevant behavior is altered.
Changed components
test/functional/rpc_bind.pytest/functional/test_framework/netutil.pyInspect captured patch +40 / −27
diff --git a/test/functional/rpc_bind.py b/test/functional/rpc_bind.py
index 3c6e3e4b..bff8ec24 100755
--- a/test/functional/rpc_bind.py
+++ b/test/functional/rpc_bind.py
@@ -17,8 +17,7 @@ class RPCBindTest(BitcoinTestFramework):
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()
def setup_network(self):
self.add_nodes(self.num_nodes, None)
@@ -105,8 +104,11 @@ class RPCBindTest(BitcoinTestFramework):
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
diff --git a/test/functional/test_framework/netutil.py b/test/functional/test_framework/netutil.py
index c7ab756a..85209322 100644
--- a/test/functional/test_framework/netutil.py
+++ b/test/functional/test_framework/netutil.py
@@ -114,33 +114,44 @@ def get_bind_addrs(pid):
else:
raise NotImplementedError(f"get_bind_addrs is not supported on {sys.platform}")
-# from: https://code.activestate.com/recipes/439093/
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)
+ 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"])
+ ]
+ else:
+ raise NotImplementedError(f"all_interfaces is not supported on {sys.platform}")
def addr_to_hex(addr):
'''
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.