test: support get_bind_addrs and feature_bind_extra on macOS & BSD #34256

pull l0rinc wants to merge 2 commits into bitcoin:master from l0rinc:l0rinc/test-macos-bind-netutil changing 3 files +68 −37
  1. l0rinc commented at 9:42 pm on January 11, 2026: contributor

    Problem

    Some functional tests are shown as skipped when running on macOS & BSD because test_framework/netutil.py only implemented the Linux-specific logic for checking which TCP sockets a node is listening on.

    Fix

    Add macOS and BSD implementations in test/functional/test_framework/netutil.py so tests can query:

    • which TCP sockets a node is listening on (get_bind_addrs(), via lsof)
    • a non-loopback interface address (all_interfaces(), via ifconfig)

    Then enable the previously Linux-only tests by switching to a shared POSIX platform guard.

    Commands

    Command used

    0lsof -nP -a -p <pid> -iTCP -sTCP:LISTEN -Ftn
    

    Flags

    • -D: device cache warnings
    • -n: no hostname resolution
    • -P: no service/port-name resolution
    • -a: AND all conditions
    • -p : filter by process ID
    • -iTCP: TCP sockets only
    • -sTCP:LISTEN: listening sockets only
    • -Ftn: machine-readable output (fields: type t, name n)

    Regex parser

    0t(IPv[46])\nn(\*|\[.+?]|[^:]+):(\d+)
    

    Captured groups

    • group 1: IPv4 / IPv6 (used to disambiguate *)
    • group 2: host (*, [::1], 127.0.0.1, …)
    • group 3: port

    Command used

    0ifconfig -au
    

    Regex parsing

    Interface blocks:

    0(?m)^(?P<iface>\S+):(?P<block>[^\n]*(?:\n[ \t]+[^\n]*)*)
    

    IPv4 extraction within each block:

    0inet (\S+)
    

    Notes

    The only remaining platform skips on macOS are the USDT/BPF tracing tests (interface_usdt_*.py).

  2. DrahtBot added the label Tests on Jan 11, 2026
  3. DrahtBot commented at 9:42 pm on January 11, 2026: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/34256.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Stale ACK bensig, willcl-ark, ismaelsadeeq, Sjors

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

  4. in test/functional/test_framework/netutil.py:91 in 9b3a39cf60
    92-    bind_addrs = []
    93-    for conn in netstat('tcp') + netstat('tcp6'):
    94-        if conn[3] == STATE_LISTEN and conn[4] in inodes:
    95-            bind_addrs.append(conn[1])
    96-    return bind_addrs
    97+    if sys.platform.startswith("linux"):
    


    Sjors commented at 4:14 am on January 12, 2026:

    In 9b3a39cf60dbad453874d3474dc21ecb960ffcfc test: support get_bind_addrs on macOS nit: you can get rid of startswith, since our minimum Python version is well after 3.3 which removed “linux2” and “linux3”: https://docs.python.org/3.3/whatsnew/3.3.html#porting-python-code

    Replace sys.platform == ‘linux2’ with sys.platform.startswith(‘linux’), or directly sys.platform == ‘linux’ if you don’t need to support older Python versions.


    l0rinc commented at 7:47 am on January 12, 2026:
    Thanks, done, added you as coauthor

    Sjors commented at 1:05 pm on February 23, 2026:
    e70de0a406c072474b17d7c8ccff57fc952fae4b: it still says startswith

    Sjors commented at 1:13 pm on February 23, 2026:
    Nvm, you added a separate commit for this.
  5. in test/functional/test_framework/netutil.py:104 in 9b3a39cf60 outdated
    105+        import re
    106+        import subprocess
    107+        output = subprocess.check_output(["lsof", "-nP", "-a", "-p", str(pid), "-iTCP", "-sTCP:LISTEN", "-Ftn"], text=True)
    108+        return [
    109+            (addr_to_hex(("::" if sock_type == "IPv6" else "0.0.0.0") if host == "*" else host.strip("[]")), int(port))
    110+            for sock_type, host, port in re.findall(r"t(IPv[46])\nn(\*|\[.+?]|[^:]+):(\d+)", output)
    


    Sjors commented at 4:22 am on January 12, 2026:

    (delete wrong commit reference)

    Review note, this is what the command outputs (without -Ftv):

    0% lsof -nP -a -p 92306 -iTCP -sTCP:LISTEN
    1COMMAND    PID  USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
    2bitcoind 92306 sjors   10u  IPv6 0x41f09f8c114a7707      0t0  TCP [::1]:18443 (LISTEN)
    3bitcoind 92306 sjors   11u  IPv4 0x9b8ff0d88c43ff6f      0t0  TCP 127.0.0.1:18443 (LISTEN)
    4bitcoind 92306 sjors   19u  IPv4 0xd8de1cae67469884      0t0  TCP *:18444 (LISTEN)
    5bitcoind 92306 sjors   21u  IPv4 0x33676f2f9e2adc58      0t0  TCP 127.0.0.1:18445 (LISTEN)
    6bitcoind 92306 sjors   22u  IPv6 0x776a345d97d44893      0t0  TCP *:18444 (LISTEN)
    

    And with -Ftv:

     0p92306
     1f10
     2tIPv6
     3n[::1]:18443
     4f11
     5tIPv4
     6n127.0.0.1:18443
     7f19
     8tIPv4
     9n*:18444
    10f21
    11tIPv4
    12n127.0.0.1:18445
    13f22
    14tIPv6
    15n*:18444
    

    I didn’t study the regex in much detail, I guess it works…


    l0rinc commented at 7:47 am on January 12, 2026:
    Please see the collapsed sections in the PR description for more details. Let me know if you think more explanation is needed anywhere.

    willcl-ark commented at 9:48 am on February 5, 2026:

    Instead of the double ternary inside a list comp here, this could perhaps be written in slightly more human-readable python, as:

    0results = []
    1for sock_type, host, port in re.findall(r"t(IPv[46])\nn(\*|\[.+?]|[^:]+):(\d+)", output):
    2    if host == "*":
    3        addr = "::" if sock_type == "IPv6" else "0.0.0.0"
    4    else:
    5        addr = host.strip("[]")
    6    results.append((addr_to_hex(addr), int(port)))
    7return results
    

    I also did not study this regex precisely, but have observed that it works on my macbook.


    l0rinc commented at 10:15 am on February 5, 2026:
    If I need to touch it again, I’ll consider avoiding the nesting, thanks for the hint. Was going for compactness here, but maybe I overshot.

    l0rinc commented at 11:48 am on February 26, 2026:
    I haven’t applied this in the end, but if you feel strongly about it, let me know

    l0rinc commented at 3:25 pm on March 12, 2026:
    I kept the nesting for now, let me know if you feel strongly about it
  6. Sjors commented at 4:24 am on January 12, 2026: member

    Concept ACK, still need to look at the third and fourth commit.

    Tested that the two functional tests now run on macOS. I also introduced some regressions to see if they’re caught.

    Nit: commit messages contain literal \n instead of newline.

  7. l0rinc force-pushed on Jan 12, 2026
  8. bensig commented at 9:54 pm on January 12, 2026: contributor
    ACK a843fde881820a70b72f0be0ff6e9602bb7ae1dc
  9. DrahtBot requested review from Sjors on Jan 12, 2026
  10. in test/functional/test_framework/netutil.py:111 in 2296a19c96
    105@@ -106,33 +106,44 @@ def get_bind_addrs(pid):
    106     else:
    107         raise NotImplementedError(f"get_bind_addrs is not supported on {sys.platform}")
    108 
    109-# from: https://code.activestate.com/recipes/439093/
    110 def all_interfaces():
    111     '''
    112     Return all interfaces that are up
    


    Sjors commented at 8:58 am on January 13, 2026:
    Let’s clarify that this only returns IPv4 interfaces (only obvious to people who recognise AF_INET in the Linux code).

  11. in test/functional/test_framework/netutil.py:143 in 2296a19c96 outdated
    161+        import subprocess
    162+        output = subprocess.check_output(["ifconfig", "-au"], text=True)
    163+        return [
    164+            (m["iface"].encode(), ip)
    165+            for m in re.finditer(r"(?m)^(?P<iface>\S+):(?P<block>[^\n]*(?:\n[ \t]+[^\n]*)*)", output)
    166+            for ip in re.findall(r"inet (\S+)", m["block"])
    


    Sjors commented at 3:16 pm on January 13, 2026:

    In 2296a19c96294eca9f376249a1745cd077fb1f6a test: support all_interfaces on macOS: I don’t mind keeping the regex approach, but something like this is easier to read:

    0# Get list of "up" interfaces
    1ifaces = subprocess.check_output(["ifconfig", "-lu"], text=True).split()
    2return [
    3    (iface.encode(), res.strip()) for iface in ifaces
    4    if (res := subprocess.run(["ipconfig", "getifaddr", iface], text=True).stdout) is not None
    5]
    

    Sjors commented at 8:05 pm on January 13, 2026:

    Actually the above short version doesn’t work, it just returns an empty result, which the test seems to be happy with.

    Back to the more verbose version:

     0results = []
     1for iface in ifaces:
     2    try:
     3        res = subprocess.check_output(
     4            ["ipconfig", "getifaddr", iface],
     5            text=True
     6        ).strip()
     7    except subprocess.CalledProcessError:
     8        continue
     9    if res:
    10        results.append((iface.encode(), res))
    11
    12return results
    

    However it’s still not identical to your version, because it doesn’t the return the loopback IP. That’s because ipconfig getifaddr lo0 returns nothing. Might be a matter of tweaking the getifaddr command, so the man page doesn’t suggest any obvious way.


    l0rinc commented at 12:24 pm on January 16, 2026:
  12. DrahtBot requested review from Sjors on Jan 13, 2026
  13. l0rinc force-pushed on Jan 16, 2026
  14. ismaelsadeeq commented at 1:53 pm on February 4, 2026: member

    Tested ACK 67002b0b2cb4e2520c8f48cd048e3522d9c76c32 on macOS M2 Pro Tahoe 26.1.

    Confirmed that the previously skipped networking tests now run successfully:

    • feature_bind_extra.py
    • rpc_bind.py --ipv4
    • rpc_bind.py --ipv6
    • rpc_bind.py --nonloopback

    Before: 21 skipped tests (including the 4 above) After: 16 skipped tests (only USDT/tracing + compatibility tests remain skipped)

    Only did testing in this round.

  15. willcl-ark approved
  16. willcl-ark commented at 9:54 am on February 5, 2026: member

    tACK 13a791c85bbcbaa0c2bf7312a7b295dfd0a9f927

    This looks ok to me. Left a comment on breaking up the large list comp with two ternaries, in case you touch again.

    I was trying to think of cleaner approaches, but couldn’t think of any outside of adding psutil as a test dependency, which I think we can avoid while we only need this single function.

    All tests pass on an M3 Pro running MacOS Tahoe 26.2 with lsof 4.91

  17. DrahtBot requested review from willcl-ark on Feb 5, 2026
  18. willcl-ark commented at 10:09 am on February 5, 2026: member

    I have noticed that above I ACKed the commits I had rebased onto master.

    ACK 67002b0b2cb4e2520c8f48cd048e3522d9c76c32

    (after checking git range-diff 13a791c85bbcbaa0c2bf7312a7b295dfd0a9f927...67002b0b2cb4e2520c8f48cd048e3522d9c76c32)

  19. in test/functional/test_framework/test_framework.py:910 in 67002b0b2c
    906@@ -907,6 +907,11 @@ def skip_if_platform_not_linux(self):
    907         if platform.system() != "Linux":
    908             raise SkipTest("not on a Linux system")
    909 
    910+    def skip_if_platform_not_linux_or_mac(self):
    


    fanquake commented at 10:16 am on February 5, 2026:
    Why can’t this use skip_if_platform_not_posix?

    l0rinc commented at 12:42 pm on February 5, 2026:
    Valid question, I remember the CI giving me some problems, let me recheck https://github.com/l0rinc/bitcoin/pull/115

    fanquake commented at 1:51 pm on February 5, 2026:

    l0rinc commented at 2:45 pm on February 5, 2026:
    It does work for Mac, I already tried that locally, I remembered that it enabled something that I didn’t want on CI - let’s see if the PR passes and I’ll adjust it here if it does, thanks for the hint.

    l0rinc commented at 11:49 pm on February 5, 2026:
    Changed it to use the posix helper, not sure what went wrong originally, but seems to be working now, thanks: https://github.com/bitcoin/bitcoin/compare/67002b0b2cb4e2520c8f48cd048e3522d9c76c32..10e1e38c75312c5d5219c1afd1232df252aaaa9a
  20. l0rinc force-pushed on Feb 5, 2026
  21. l0rinc requested review from ismaelsadeeq on Feb 6, 2026
  22. l0rinc requested review from fanquake on Feb 6, 2026
  23. ismaelsadeeq commented at 5:14 pm on February 9, 2026: member

    re-tACK 10e1e38c75312c5d5219c1afd1232df252aaaa9a

    Previously skipped tests are now running as reported in my comment here #34256#pullrequestreview-3751197721 after the recent push

  24. Sjors commented at 1:14 pm on February 23, 2026: member
    ACK 10e1e38c75312c5d5219c1afd1232df252aaaa9a
  25. l0rinc commented at 1:38 pm on February 27, 2026: contributor
    rfm?
  26. fanquake commented at 10:28 am on March 6, 2026: member
    @willcl-ark want to take another look here?
  27. in test/functional/rpc_bind.py:20 in 9b6aa99710 outdated
    16@@ -17,8 +17,7 @@ def set_test_params(self):
    17         self.supports_cli = False
    18 
    19     def skip_test_if_missing_module(self):
    20-        # due to OS-specific network stats queries, this test works only on Linux
    21-        self.skip_if_platform_not_linux()
    22+        self.skip_if_platform_not_posix()
    


    willcl-ark commented at 1:28 pm on March 11, 2026:

    nit: the commit message still states

    allow rpc_bind to run there as well by switching to skip_if_platform_not_linux_or_mac().


    l0rinc commented at 2:34 pm on March 11, 2026:
    Indeed, will @ismaelsadeeq and @Sjors reack if I adjust it?
  28. in test/functional/test_framework/netutil.py:146 in 10e1e38c75 outdated
    172+            (m["iface"].encode(), ip)
    173+            for m in re.finditer(r"(?m)^(?P<iface>\S+):(?P<block>[^\n]*(?:\n[ \t]+[^\n]*)*)", output)
    174+            for ip in re.findall(r"inet (\S+)", m["block"])
    175+        ]
    176+    else:
    177+        raise NotImplementedError(f"all_interfaces is not supported on {sys.platform}")
    


    willcl-ark commented at 1:56 pm on March 11, 2026:
    How will this work on *BSD? We dropped the gate from if: linux to if: posix, but then only handle linux and darwin here. So will BSD now run these tests and always hit this exception?

    l0rinc commented at 2:33 pm on March 11, 2026:
    Not sure, but this only affects the tests - is anyone running them on BSD? If it ever becomes a priority, we can adjust the tests.

    willcl-ark commented at 2:35 pm on March 11, 2026:
    They are being run in a few places I know of, e.g. here https://github.com/hebasto/bitcoin-core-nightly/actions/runs/22936550559/workflow#L97

    maflcko commented at 2:48 pm on March 11, 2026:

    Yeah, also the docs recommend to run the tests:

    To run the test suite (recommended), you will need to have Python 3 installed:

    See doc/build-freebsd.md


    l0rinc commented at 3:10 pm on March 11, 2026:
    So that was the reason for #34256 (review). Did we remove a BSD CI task since then? I remember a similar failure, but it seems I forgot the reason. I’ll revert that change, thanks.

    fanquake commented at 3:12 pm on March 11, 2026:

    Did we remove a BSD CI task since then?

    No.


    fanquake commented at 3:13 pm on March 11, 2026:

    So will BSD now run these tests and always hit this exception?

    Why can’t this just skip, for an unimplemented platform? Rather than re-adding more redundant code?


    l0rinc commented at 3:21 pm on March 11, 2026:
    Yes, that’s what the origin skip_if_platform_not_linux_or_mac did - I just completely forgot about BSD

    fanquake commented at 3:22 pm on March 11, 2026:
    I mean skip in the test, not introduce a new function, to differentiate a subset of posix (for a single functional test).

    l0rinc commented at 12:48 pm on March 13, 2026:
    Thanks for the feedback. The PR is simpler now, and the tests are passing on the 3 BSD versions as well, without any significant change to the original implementation.
  29. in test/functional/test_framework/netutil.py:143 in 10e1e38c75 outdated
    169+        import subprocess
    170+        output = subprocess.check_output(["ifconfig", "-au"], text=True)
    171+        return [
    172+            (m["iface"].encode(), ip)
    173+            for m in re.finditer(r"(?m)^(?P<iface>\S+):(?P<block>[^\n]*(?:\n[ \t]+[^\n]*)*)", output)
    174+            for ip in re.findall(r"inet (\S+)", m["block"])
    


    willcl-ark commented at 1:57 pm on March 11, 2026:
    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).
  30. DrahtBot requested review from willcl-ark on Mar 11, 2026
  31. test: enable `feature_bind_extra` on macOS and BSD
    `feature_bind_extra` checks `-bind` and `-whitebind` by comparing the node's listening sockets with the expected addresses.
    Add `get_bind_addrs` support for macOS, FreeBSD, NetBSD, and OpenBSD using `lsof`, and switch the test to the POSIX platform guard so it runs there too.
    On FreeBSD, pass `-Di` to avoid device-cache warnings on stderr that the functional test runner treats as failures.
    
    Co-authored-by: willcl-ark <will8clark@gmail.com>
    Co-authored-by: fanquake <fanquake@gmail.com>
    f7ae76a84e
  32. test: enable `rpc_bind` on macOS and BSD
    `rpc_bind` uses `all_interfaces` to find a non-loopback IPv4 address and `get_bind_addrs` to verify the node's listening sockets.
    Add `all_interfaces` support for macOS, FreeBSD, NetBSD, and OpenBSD using `ifconfig -au`, switch the test to the POSIX platform guard so it runs there too, and fail early if no IPv4 interfaces are returned.
    
    Co-authored-by: Sjors Provoost <sjors@sprovoost.nl>
    b57910b21e
  33. l0rinc force-pushed on Mar 12, 2026
  34. l0rinc commented at 3:19 pm on March 12, 2026: contributor
  35. l0rinc referenced this in commit 8132885af0 on Mar 12, 2026
  36. l0rinc renamed this:
    test: support `get_bind_addrs` and `feature_bind_extra` on macOS
    test: support `get_bind_addrs` and `feature_bind_extra` on macOS & BSD
    on Mar 13, 2026

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-03-24 12:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me