diff --git a/test/functional/feature_bind_extra.py b/test/functional/feature_bind_extra.py index ad4c4b138e4..91f846d6f69 100755 --- a/test/functional/feature_bind_extra.py +++ b/test/functional/feature_bind_extra.py @@ -32,8 +32,7 @@ class BindExtraTest(BitcoinTestFramework): 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") diff --git a/test/functional/test_framework/netutil.py b/test/functional/test_framework/netutil.py index 5504029a766..c7ab756a0c8 100644 --- a/test/functional/test_framework/netutil.py +++ b/test/functional/test_framework/netutil.py @@ -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 """ @@ -88,12 +88,31 @@ 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 + 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", + *(["-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) + ] + else: + raise NotImplementedError(f"get_bind_addrs is not supported on {sys.platform}") # from: https://code.activestate.com/recipes/439093/ def all_interfaces():