test: support get_bind_addrs and feature_bind_extra on macOS #34256

pull l0rinc wants to merge 5 commits into bitcoin:master from l0rinc:l0rinc/test-macos-bind-netutil changing 3 files +60 −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 because test_framework/netutil.py only implemented the Linux-specific logic for checking which TCP sockets a node is listening on.

    Fix

    Add macOS 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 platform guard.

    Commands

    Command used

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

    Flags

    • -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
    ACK ismaelsadeeq
    Concept ACK Sjors
    Stale ACK bensig, willcl-ark

    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
  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.
  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. test: support `get_bind_addrs` on macOS
    The bind functional tests need to query which TCP sockets a node is listening on.
    
    Add a macOS implementation using `lsof` to query listening TCP sockets for a given PID and return results as `(addr_hex, port)` tuples, matching the Linux format.
    e70de0a406
  20. test: enable `feature_bind_extra` on macOS
    `feature_bind_extra` verifies `-bind/-whitebind` interactions and asserts that `bitcoind` listens on the expected addresses.
    
    With `get_bind_addrs()` now supported on macOS, allow the test to run there as well.
    3ff00875e2
  21. test: support `all_interfaces` on macOS
    `rpc_bind` uses `all_interfaces()` to pick a non-loopback IPv4 address for `rpcallowip/rpcbind` coverage.
    
    Add a macOS implementation by parsing `ifconfig` output and returning `(ifname, ip)` tuples in the same shape.
    62c2f6be96
  22. test: enable `rpc_bind` on macOS
    `rpc_bind` validates `-rpcbind` and `-rpcallowip` behavior and asserts the node listens on the expected addresses.
    
    With `get_bind_addrs()` and `all_interfaces()` now supported on macOS, allow `rpc_bind` to run there as well by switching to `skip_if_platform_not_linux_or_mac()`.
    
    Co-authored-by: Sjors Provoost <sjors@sprovoost.nl>
    9b6aa99710
  23. test: use exact platform check for Linux in `get_bind_addrs` and `all_interfaces`
    See 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.
    
    Co-authored-by: Sjors Provoost <sjors@sprovoost.nl>
    10e1e38c75
  24. 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
  25. l0rinc force-pushed on Feb 5, 2026
  26. l0rinc requested review from ismaelsadeeq on Feb 6, 2026
  27. l0rinc requested review from fanquake on Feb 6, 2026
  28. 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


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-02-22 18:12 UTC

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