http: support unix sockets #36434

pull pinheadmz wants to merge 10 commits into bitcoin:master from pinheadmz:http-unix-sockets changing 20 files +1549 −263
  1. pinheadmz commented at 6:10 PM on October 4, 2026: member

    Closes #5029

    Accept unix socket paths as options for -rpcbind= (server) and -rpcconnect= (bitcoin-cli). Options follow the same pattern as ipcbind:

    • =unix is a magic keyword that means <datadir>/http.sock
    • =unix:relative/path means <datadir>/relative/path/http.sock
    • =unix:/absolute/path means /absolute/path/http.sock

    We delete any existing http.sock before calling bind() and do not clean up at shutdown.

    Because access is handled by the filesystem, -rpcallowip is ignored for unix sockets and not required at all if we are only binding to unix sockets.

    Authentication by username/password/cookie is still required.

    A new option --httpunix is added to the test framework and added to test_runner for rpc_bind.py and interface_http.py for CI, but (like --usecli) most other tests should also pass with that option. When set, bitcoind is started with -rpcbind=unix...which overrides tcp://localhost entirely. AuthProxy has been extended to connect directly to the unix socket.

    Try it out!

    bitcoind -regtest -rpcbind=unix
    bitcoin-cli -regtest -rpcconnect=unix getbestblockhash
    

    Refactored code:

    Back in #27375 we added unix socket support but it was isloated to the Proxy class. That created a bit of tech debt which we pay off in this PR. Two new classes are implemented:

    • UnixSocketAddr reflects the CService API for generic unix sockets, without bitcoin-p2p-specific attributes
    • SocketAddr holds a variant of UnixSocketAddr and Cservice so classes like Proxy and HTTPServer can operate on an abstract level, with either socket type.
    • CNetAddr and CService are left untouched -- they are somewhat dirty combinations of generic socket classes and bitcoin p2p code, adding unix sockets to those classes wouldn't make sense for bitcoin p2p operations and that's why this design was chosen.

    Follow-up suggestions:

    • Deduplicate some of the unix socket path resolution code between IPC and HTTP
    • On macos the default unix socket buffer size is only ~8k. In the functional tests we have to bump the python client socket buffer up to ~256k or the "throttle" tests will stall on macos. We could apply this socket option on the server side as well, only on macos... requires some bike shedding.
    • This option replaces TCP but it does not replace HTTP, there may be a more efficient transport protocol.

    Production code and test framework changes designed and authored by my own cranial meatball. Unit tests added by Opus. C++ cleaned up by Fable, security tightened by Kimi-k3 🧠 🤖 🤖 🤖

  2. DrahtBot added the label RPC/REST/ZMQ on Oct 4, 2026
  3. DrahtBot commented at 6:10 PM on October 4, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    External sites

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    Concept ACK b-l-u-e
    Approach ACK winterrdog

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #36340 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36340.svg"></sub> (util: cap Sock::WaitMany timeout to fix -rpcclienttimeout=0 on macOS by kriss39)
    • #36303 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36303.svg"></sub> (http: Log bytes received from client, use to replace receive-throttle regression test by pinheadmz)
    • #36204 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36204.svg"></sub> (http: disconnect clients that never finish a request by janb84)
    • #36187 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36187.svg"></sub> (http: Restore requirement of IPv4 bind success by hodlinator)
    • #36159 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36159.svg"></sub> (http: Improve HTTPRemoteClient::MaybeDisconnect() by hodlinator)
    • #35713 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/35713.svg"></sub> (Remove boost as a unit test runner by rustaceanrob)
    • #34729 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/34729.svg"></sub> (Reduce log noise by ajtowns)
    • #33954 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/33954.svg"></sub> (test: add functional test for outbound connection management by mzumsande)
    • #31260 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/31260.svg"></sub> (scripted-diff: Type-safe settings retrieval by ryanofsky)
    • #19461 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/19461.svg"></sub> (multiprocess: Add bitcoin-gui -ipcconnect option by ryanofsky)
    • #19460 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/19460.svg"></sub> (multiprocess: Add bitcoin-wallet -ipcconnect option by ryanofsky)
    • #17783 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/17783.svg"></sub> (common: Disallow calling IsArgSet() on ALLOW_LIST options by ryanofsky)
    • #17581 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/17581.svg"></sub> (refactor: Remove settings merge reverse precedence code by ryanofsky)
    • #17580 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/17580.svg"></sub> (refactor: Add ALLOW_LIST flags and enforce usage in CheckArgFlags by ryanofsky)
    • #17493 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/17493.svg"></sub> (util: Forbid ambiguous multiple assignments in config file by ryanofsky)
    • #10102 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/10102.svg"></sub> (Multiprocess bitcoin by ryanofsky)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

    LLM Linter (✨ experimental)

    Possible places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):

    • src/httpserver.cpp: Lookup(strRPCBind, port, false)
    • src/httpserver.cpp: Lookup("::1", http_port, false)
    • src/httpserver.cpp: Lookup("127.0.0.1", http_port, false)
    • src/netbase.cpp: CreateSock(AF_UNIX, SOCK_STREAM, 0)
    • src/test/httpserver_tests.cpp: CreateSock(0, 0, 0)

    Possible places where comparison-specific test macros should replace generic comparisons:

    • test/functional/rpc_bind.py: assert sum([...]) > 1 → Prefer assert_greater_than(sum([...]), 1).
    • test/functional/test_framework/test_node.py: assert len(os.fsencode(self.http_unix_socket_path)) < UNIX_PATH_MAX → Prefer assert_greater_than(UNIX_PATH_MAX, len(os.fsencode(self.http_unix_socket_path))).

    <sup>2026-10-08 19:06:57</sup>

  4. winterrdog commented at 8:57 PM on October 4, 2026: contributor

    concept ACK


    users of other projects that rely on Core have always wanted sth similar to this and implemented workarounds in the meantime:

    there was also an attempt to add this back in 2017:

    unfortunately, that attempt ran into a limitation in libevent's HTTP client. supporting Unix Domain sockets in bitcoin-cli required a libevent patch to pass an already-open Unix socket connection to the HTTP client. this was eventually addressed in https://github.com/libevent/libevent/pull/1722, but by then Core had decided to moved away from libevent. anyway, with libevent dropped, this is no longer an issue

  5. pinheadmz commented at 9:09 PM on October 4, 2026: member

    Don't forget https://github.com/libevent/libevent/issues/1615 ! Its been a long time coming... now that that pesky little refactor is out of the way we can finally get to it ;-)

  6. b-l-u-e commented at 11:10 AM on October 5, 2026: contributor

    concept ACK...i found an issue by running two nodes and noticed that second node can take over a live rpc unix socket path, if two nodes use different datadirs but the same -rpcbind=unix:<path> the second node starts with no error..since it removes the first node socket file and creates its own then after that bitcoin cli talks to the second node.. the first node keeps running but becomes unreachable so with TCP the second bind fails with address already in use

    i reproduced this on regtest

    B=build/bin
    SOCK=/tmp/btc-hijack.sock
    mkdir -p /tmp/nodeA /tmp/nodeB
    ARGS="-regtest -daemon -listen=0 -rpcbind=unix:$SOCK -rpcuser=u -rpcpassword=p"
    CLI="$B/bitcoin-cli -regtest -rpcconnect=unix:$SOCK -rpcuser=u -rpcpassword=p -rpcwait"
    

    1: for node A

    $B/bitcoind -datadir=/tmp/nodeA $ARGS
    $CLI getrpcinfo | grep logpath && stat -c 'inode=%i' $SOCK
    
    output:
    Bitcoin Core starting
    "logpath": "/tmp/nodeA/regtest/debug.log"
    inode=2229678
    

    2: for node B

    $B/bitcoind -datadir=/tmp/nodeB $ARGS; sleep 2
    $CLI getrpcinfo | grep logpath && stat -c 'inode=%i' $SOCK
    
    output:
    Bitcoin Core starting                                  (as you can see node B starts without error)
    "logpath": "/tmp/nodeB/regtest/debug.log"              (cli now reaches B)
    inode=2229647   (and this replaces node A socket file)
    

    3: ss -xlp | grep btc-hijack

    output:
    u_str LISTEN ... /tmp/btc-hijack.sock ... pid=1684462  (this is for B)
    u_str LISTEN ... /tmp/btc-hijack.sock ... pid=1684363  (here A still listening but unreachable)
    

    4: $CLI stop; sleep 2

    output:
    Bitcoin Core stopping                                  (`stop` reached B)
    

    5: pgrep -a bitcoind

    output:
    1684363 build/bin/bitcoind -datadir=/tmp/nodeA ...     (A still running)
    

    6: $CLI -rpcwait=0 getblockcount

    output:
    error: Error while attempting to communicate with server unix:/tmp/btc-hijack.sock (Could not connect to the server)
    
    Make sure the bitcoind server is running and that you are connecting to the correct unix socket path.
    Use "bitcoin-cli -help" for more info.
    
  7. b-l-u-e commented at 5:44 PM on October 5, 2026: contributor

    i noticed a change in bitcoin-cli the host header contains the resolved IP instead of the hostname given in -rpcconnect=localhost the build from master sends Host: localhost but on this PR sends Host: 127.0.0.1

    i reproduced the result by running this py file on terminal 1

    import socket
    
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind(("127.0.0.1", 18555))
    srv.listen()
    print("listening on 127.0.0.1:18555")
    while True:
        conn, _ = srv.accept()
        request = conn.recv(4096).decode(errors="replace")
        for line in request.split("\r\n"):
            if line.lower().startswith("host:"):
                print(line)
        conn.close()
    

    on terminal 2 i ran this command

    build-master/bin/bitcoin-cli -rpcconnect=localhost -rpcport=18555 -rpcuser=u -rpcpassword=p -rpcclienttimeout=3 getblockcount
    build-pr36434/bin/bitcoin-cli       -rpcconnect=localhost -rpcport=18555 -rpcuser=u -rpcpassword=p -rpcclienttimeout=3 getblockcount
    

    and the output was

    python3 /tmp/show_host.py 
    listening on 127.0.0.1:18555
    Host: localhost
    Host: 127.0.0.1
    
  8. DrahtBot added the label Needs rebase on Oct 6, 2026
  9. pinheadmz force-pushed on Oct 8, 2026
  10. pinheadmz commented at 2:59 PM on October 8, 2026: member

    push to 022a6aa69ed423fa66f5747b1f0d6413a01e1645:

    • rebase on master, fix conflicts with #36303 and #34729
    • fix "Host: header in bitcoin-cli (@b-l-u-e)

    Major change is copying the parameter format from ipcbind, so default is to create http.sock in the appropriate network data directory. This should also adderss @b-l-u-e concern about multiple processes messing with each others sockets. Or at least, makes it harder for a user to footgun. Will update the PR description as well.

  11. netbase: introduce UnixSocketAddr class
    Include unit tests to ensure the API matches CService
    9a04c58270
  12. netbase: introduce SocketAddr class bbc5abd8f2
  13. test: cover existing Proxy API 3c4e23905d
  14. netbase: refactor Proxy to use SocketAddr c902d713ba
  15. http: use SocketAddr instead of CService 5ae90142d4
  16. http: support listening on UNIX domain sockets 826e6478f6
  17. http: allow empty -rpcallowip if all bind addresses are unix sockets c458e18df9
  18. pinheadmz force-pushed on Oct 8, 2026
  19. pinheadmz commented at 3:13 PM on October 8, 2026: member

    push to 81b8369ac862a7e888606c2220df94d347a7ca59

  20. DrahtBot removed the label Needs rebase on Oct 8, 2026
  21. DrahtBot added the label CI failed on Oct 8, 2026
  22. DrahtBot commented at 4:34 PM on October 8, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task lint: https://github.com/bitcoin/bitcoin/actions/runs/37798972114/job/113385788562</sub> <sub>LLM reason (✨ experimental): CI failed because Ruff’s Python lint found an unnecessary f prefix in test/functional/interface_http.py:758.</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  23. pinheadmz force-pushed on Oct 8, 2026
  24. pinheadmz commented at 4:39 PM on October 8, 2026: member

    push to c27a1fec1aa78cb721c31be5511bb1a542f09782

    • fix humiliating lint error 😭
  25. Enable unix sockets in test_framework: cover interface_http & rpc_bind
    Most functional tests will pass with --httpunix, which uses unix
    sockets instead of TCP for all RPC commands in the test. For now,
    only require interface_http, rpc_bind --httpunix in CI to cover
    the specific changes from this branch.
    39bc530b07
  26. bitcoin-cli: support connecting on UNIX domain sockets 454c72a7dc
  27. doc: add unix socket options to help and docs db638e0f44
  28. pinheadmz force-pushed on Oct 8, 2026
  29. pinheadmz commented at 7:08 PM on October 8, 2026: member

    push to db638e0f44f0aaffe798a4ec7d4757246de9fb56

    Fix a test in interface_http made flaky by unix sockets and failed last CI run:

    Unix sockets report server closure much faster than TCP which lost a race against the logic where we assert the connection is closed in python by trying to send more data

  30. DrahtBot removed the label CI failed on Oct 8, 2026
  31. DrahtBot added the label Needs rebase on Oct 10, 2026
  32. DrahtBot commented at 1:46 AM on October 10, 2026: contributor

    <!--cf906140f33d8803c4a75a2196329ecb-->

    🐙 This pull request conflicts with the target branch and needs rebase.

  33. in doc/release-notes-36434.md:27 in db638e0f44
      22 | +  The socket file is not removed on shutdown.
      23 | +
      24 | +Tools and Utilities
      25 | +-------------------
      26 | +
      27 | +- `bitcoin-cli` can connect to a node over a unix domain socket with
    


    winterrdog commented at 12:09 PM on October 10, 2026:

    nit: sometimes, i see that you use "unix socket", while on other occasions it is "unix domain socket". is that intentional, or should we choose uniformity, pick one option, and then just run with it all throughout ?

    "unix socket" seems more common in the docs and in the code comments in this PR's diff, so probably the best candidate to go with.

  34. in doc/release-notes-36434.md:5 in db638e0f44
       0 | @@ -0,0 +1,31 @@
       1 | +Updated settings
       2 | +----------------
       3 | +
       4 | +- `-rpcbind` now accepts a unix domain socket on platforms that support unix
       5 | +  sockets (not Windows), using the same syntax as `-ipcbind`: `-rpcbind=unix`
    


    winterrdog commented at 10:27 PM on October 10, 2026:

    (not Windows)

    TIL: windows has basic support for unix domain sockets (UDS).

    since Windows 10 version 1803 or later, windows has shipped UDS support but limited to SOCK_STREAM sockets only: https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/

  35. in src/netbase.h:98 in db638e0f44
      95 | +    explicit UnixSocketAddr(std::string path) : m_path(std::move(path)) {}
      96 | +
      97 | +    [[nodiscard]] bool IsValid() const { return IsUnixSocketPath(m_path); }
      98 | +    [[nodiscard]] sa_family_t GetSAFamily() const { return AF_UNIX; }
      99 | +    /** The full address string including the "unix:" prefix.
     100 | +     *  Unix sockets don't have a port but we like to match the CService API. */
    


    winterrdog commented at 11:30 PM on October 10, 2026:

    nit: could you have possibly meant this instead ?

         *  Unix sockets don't have a port but we'd like to match the CService API. */
    
  36. in src/netbase.cpp:736 in db638e0f44
     739 | +    } else {
     740 | +        path = std::string(sun->sun_path, len);
     741 | +        // The client path goes into logs and JSONRPCRequest::peerAddr; reject
     742 | +        // control characters a local client could place there to forge log
     743 | +        // lines (e.g. a Linux abstract-namespace name with a \0 prefix).
     744 | +        if (std::any_of(path.begin(), path.end(), [](char c) { return c < 0x20 || c == 0x7F; })) {
    


    winterrdog commented at 1:04 AM on October 11, 2026:

    char is signed on some platforms (e.g. x86), so bytes above 0x7f are interpreted as negative values (they wrap around) and also match c < 0x20. this means a utf-8 path like /tmp/josé.sock could end up being replaced with "unix" in the logs

    taking unsigned char (even uint8_t works OK) in the lambda fixes this (i also switched to std::ranges in case you find that more appealing)

            if (std::ranges::any_of(path, [](unsigned char c) { return c < 0x20 || c == 0x7F; })) {
    
  37. in src/httpserver.cpp:823 in db638e0f44
     818 | +    // if something other than a socket already exists at the path.
     819 | +    if (const auto prepared{to.PreparePath()}; !prepared) {
     820 | +        return util::Unexpected{strprintf("Cannot prepare unix socket path for %s: %s",
     821 | +                                          to.ToStringAddrPort(),
     822 | +                                          prepared.error())};
     823 | +    }
    


    winterrdog commented at 9:32 PM on October 14, 2026:

    why don't we just put this behind an #ifdef HAVE_SOCKADDR_UN guard so we skip this code entirely on platforms that do not support unix sockets ?

    sure! we can never get here with a UnixSocketAddr on those platforms anyway, since those addresses get rejected much earlier. currently this would be a no-op at runtime, but we could avoid compiling code that can never be used on those platforms. seems like we can just sidestep it altogether :)

  38. in src/httpserver.cpp:250 in db638e0f44
     264 | -                return {}; // empty
     265 | +                return {}; // empty, refuse to start with a misconfigured bind
     266 | +            }
     267 | +            const std::optional<CService> addr{Lookup(strRPCBind, port, false)};
     268 | +            if (!addr) {
     269 | +                LogWarning("Could not bind RPC on address %s port %i: Address lookup failed.", strRPCBind, port);
    


    winterrdog commented at 6:30 PM on October 15, 2026:

    strRPCBind can show up in the logs if the optional port is included in the bind address (e.g. 127.0.0.1:8332), leading to logs like this one: Could not bind RPC on address 127.0.0.1:8332 port 8332: Address lookup failed.

                    LogWarning("Could not bind RPC on address %s port %i: Address lookup failed.", host, port);
    
  39. in src/httpserver.cpp:762 in db638e0f44
     757 | @@ -715,7 +758,9 @@ util::Expected<void, std::string> HTTPServer::BindAndStartListening(const CServi
     758 |          return util::Unexpected{strprintf("Bind address family for %s not supported", to.ToStringAddrPort())};
     759 |      }
     760 |  
     761 | -    std::unique_ptr<Sock> sock{CreateSock(to.GetSAFamily(), SOCK_STREAM, IPPROTO_TCP)};
     762 | +    int protocol{IPPROTO_TCP};
     763 | +    if (to.IsUnix()) protocol = 0;
    


    winterrdog commented at 6:47 PM on October 15, 2026:

    optional style nit: we could make this a const initialised from a ternary expression:

        const int protocol{to.IsUnix() ? 0 : IPPROTO_TCP};
    
  40. in src/bitcoin-cli.cpp:1230 in db638e0f44
    1253 | +    std::vector<SocketAddr> ret;
    1254 | +    ret.reserve(services.size());
    1255 | +    for (const auto& service : services) {
    1256 | +        ret.emplace_back(service);
    1257 | +    }
    1258 | +    return {host, ret};
    


    winterrdog commented at 6:53 PM on October 15, 2026:

    nit: i think we could std::move both the host string & collection of socket addresses into the return pair object (trivial copy avoidance)

        return {std::move(host), std::move(ret)};
    
  41. in src/httpserver.cpp:237 in db638e0f44
     250 | +                const auto unix_addr{ResolveUnixSocketAddr(strRPCBind, gArgs.GetDataDirNet(), DEFAULT_HTTP_UNIX_SOCKET_NAME)};
     251 | +                if (!unix_addr) {
     252 | +                    LogError("Invalid unix socket path specified in -rpcbind: '%s' (%s)", strRPCBind, unix_addr.error());
     253 | +                    return {}; // empty, refuse to start with a misconfigured bind
     254 | +                }
     255 | +                endpoints.emplace_back(*unix_addr);
    


    winterrdog commented at 7:06 PM on October 15, 2026:

    when i went through GetBindAddresses() and UnixSocketAddr::PreparePath(), i noticed that duplicate unix endpoints can go unnoticed. if two -rpcbind values resolve to the same path (e.g. -rpcbind=unix and -rpcbind=unix:http.sock, or the same value in bitcoin.conf and perhaps on the command line), PreparePath() (later called in HTTPServer::BindAndStartListening) removes the first socket file before the second bind(). therefore, both binds succeed without sending a warning

    is this intentional, or just not something we need to worry about? in the case of TCP, the second bind just fails with EADDRINUSE and gets logged. would we expect the same here, or is silently rebinding just fine ?

  42. in src/httpserver.cpp:213 in db638e0f44
     209 | @@ -205,34 +210,72 @@ static void RejectRequest(std::unique_ptr<HTTPRequest> hreq)
     210 |      WriteNoStoreErrorReply(*hreq, HTTP_SERVICE_UNAVAILABLE);
     211 |  }
     212 |  
     213 | -static std::vector<std::pair<std::string, uint16_t>> GetBindAddresses()
     214 | +static std::vector<SocketAddr> GetBindAddresses()
    


    winterrdog commented at 9:02 AM on October 16, 2026:

    while reviewing GetBindAddresses(), i ended up rewriting it to lower the cognitive load a bit and help me get my head around the flow. thought i could share it and see what you think:

    <details><summary>diff</summary>

    diff --git a/src/httpserver.cpp b/src/httpserver.cpp
    index 4b17dc5d89..4d97422f4f 100644
    --- a/src/httpserver.cpp
    +++ b/src/httpserver.cpp
    @@ -212,51 +212,57 @@ static void RejectRequest(std::unique_ptr<HTTPRequest> hreq)
     
     static std::vector<SocketAddr> GetBindAddresses()
     {
    -    uint16_t http_port{static_cast<uint16_t>(gArgs.GetIntArg("-rpcport", BaseParams().RPCPort()))};
    -    std::vector<SocketAddr> endpoints;
    +    const uint16_t http_port{static_cast<uint16_t>(gArgs.GetIntArg("-rpcport", BaseParams().RPCPort()))};
    +    const auto binds{gArgs.GetArgs("-rpcbind")};
    +    const bool has_allowip{!gArgs.GetArgs("-rpcallowip").empty()};
    +    const auto localhost_endpoints{[http_port]() -> std::vector<SocketAddr> {
    +        return {SocketAddr{Lookup("::1", http_port, false).value()},
    +                SocketAddr{Lookup("127.0.0.1", http_port, false).value()}};
    +    }};
    +
    +    // default to loopback when no rpc binds are provided
    +    if (binds.empty()) {
    +        if (has_allowip) {
    +            LogWarning("Option -rpcallowip was specified without -rpcbind; this doesn't usually make sense");
    +        }
    +        return localhost_endpoints();
    +    }
     
    -    // Cleared as soon as any -rpcbind value is an IP address, whether or not it
    -    // resolves, so that a lookup failure can never make a TCP bind request
    -    // look like a unix-socket-only configuration.
    -    bool only_unix_sockets{true};
    -    // Set to true if no addresses were specified, or
    -    // if we need to override a user misconfiguration.
    -    bool force_localhost{false};
    +    std::vector<SocketAddr> endpoints;
    +    // Set before parsing or looking up, so that a failure can never make
    +    // a TCP bind request look like a unix-socket-only configuration.
    +    bool has_ip{false};
     
         // Determine what addresses to bind to.
    -    if (!gArgs.GetArgs("-rpcbind").empty()) {
    +    for (const std::string& bind : binds) {
             // Specific bind addresses.
    -        for (const std::string& strRPCBind : gArgs.GetArgs("-rpcbind")) {
    -            if (IsUnixSocketValue(strRPCBind, /*allow_default=*/true)) {
    -                // UNIX socket is a filesystem path, resolved like -ipcbind
    -                const auto unix_addr{ResolveUnixSocketAddr(strRPCBind, gArgs.GetDataDirNet(), DEFAULT_HTTP_UNIX_SOCKET_NAME)};
    -                if (!unix_addr) {
    -                    LogError("Invalid unix socket path specified in -rpcbind: '%s' (%s)", strRPCBind, unix_addr.error());
    -                    return {}; // empty, refuse to start with a misconfigured bind
    -                }
    -                endpoints.emplace_back(*unix_addr);
    -                continue;
    -            }
    -            // IPv4 or IPv6 address
    -            only_unix_sockets = false;
    -            uint16_t port{http_port};
    -            std::string host;
    -            if (!SplitHostPort(strRPCBind, port, host)) {
    -                LogError("%s\n", InvalidPortErrMsg("-rpcbind", strRPCBind).original);
    +        if (IsUnixSocketValue(bind, /*allow_default=*/true)) {
    +            // UNIX socket is a filesystem path, resolved like -ipcbind
    +            const auto unix_addr{ResolveUnixSocketAddr(bind, gArgs.GetDataDirNet(), DEFAULT_HTTP_UNIX_SOCKET_NAME)};
    +            if (!unix_addr) {
    +                LogError("Invalid unix socket path specified in -rpcbind: '%s' (%s)", bind, unix_addr.error());
                     return {}; // empty, refuse to start with a misconfigured bind
                 }
    -            const std::optional<CService> addr{Lookup(strRPCBind, port, false)};
    -            if (!addr) {
    -                LogWarning("Could not bind RPC on address %s port %i: Address lookup failed.", strRPCBind, port);
    -                continue;
    -            }
    -            if (addr->IsBindAny()) {
    -                LogWarning("The RPC server is not safe to expose to untrusted networks such as the public internet");
    -            }
    -            endpoints.emplace_back(addr.value());
    +            endpoints.emplace_back(*unix_addr);
    +            continue;
             }
    -    } else {
    -        force_localhost = true;
    +        // IPv4 or IPv6 address
    +        has_ip = true;
    +        uint16_t port{http_port};
    +        std::string host;
    +        if (!SplitHostPort(bind, port, host)) {
    +            LogError("%s\n", InvalidPortErrMsg("-rpcbind", bind).original);
    +            return {}; // empty, refuse to start with a misconfigured bind
    +        }
    +        const std::optional<CService> addr{Lookup(bind, port, false)};
    +        if (!addr) {
    +            LogWarning("Could not bind RPC on address %s port %i: Address lookup failed.", host, port);
    +            continue;
    +        }
    +        if (addr->IsBindAny()) {
    +            LogWarning("The RPC server is not safe to expose to untrusted networks such as the public internet");
    +        }
    +        endpoints.emplace_back(addr.value());
         }
     
         // Access to unix sockets is controlled by filesystem permissions, so if we
    @@ -264,19 +270,9 @@ static std::vector<SocketAddr> GetBindAddresses()
         // Otherwise, to prevent misconfiguration and accidental exposure of the HTTP
         // interface, require -rpcallowip whenever an IP address is bound.
         // If the user is misconfigured we restrict HTTP to localhost and log warnings.
    -    if (!only_unix_sockets && gArgs.GetArgs("-rpcallowip").empty()) {
    +    if (has_ip && !has_allowip) {
             LogWarning("Option -rpcbind was ignored because -rpcallowip was not specified, refusing to allow everyone to connect");
    -        force_localhost = true;
    -    }
    -
    -    // Default to loopback, or replace user misconfiguration.
    -    if (force_localhost) {
    -        endpoints.clear();
    -        endpoints.emplace_back(Lookup("::1", http_port, false).value());
    -        endpoints.emplace_back(Lookup("127.0.0.1", http_port, false).value());
    -        if (!gArgs.GetArgs("-rpcallowip").empty()) {
    -            LogWarning("Option -rpcallowip was specified without -rpcbind; this doesn't usually make sense");
    -        }
    +        return localhost_endpoints();
         }
         return endpoints;
     }
    

    </details>


    this is purely a suggestion, so feel free to take or leave it :)

  43. winterrdog commented at 1:22 PM on October 16, 2026: contributor

    approach ACK


    i am convinced by the implementation here. particularly, i like the choice of std::variant over polymorphism for the SocketAddr class

    after staring at the implementation for a while now, i think the changes are mostly in very good shape, with the introduction of SocketAddr being the cornerstone change that everything else seems to spring from. most of my reviews have therefore been nitpicks, since i am convinced by the overall design choices. the security is tight too 🔒

    i have mostly been looking at the implementation C++ code and have not yet had an extensive look at the tests or their infrastructure code, beyond a quick perusal

    one last question: i saw UnixSocketAddr::SetSockAddr() handles empty and abstract namespace addresses, but we do not support binding or connecting to abstract sockets. do you think it is worth supporting them in the future for linux-specific setups ?


    looks like the PR description is out of sync with the code

    • =unix:relative/path means <datadir>/relative/path/http.sock

    • =unix:/absolute/path means /absolute/path/http.sock

    it says =unix:relative/path resolves to <datadir>/relative/path/http.sock and =unix:/absolute/path to /absolute/path/http.sock, but ResolveUnixSocketAddr() uses the given path as the socket file itself. so unix:rpc.sock turns into <datadir>/rpc.sock, and unix:/tmp/rpc.sock stays /tmp/rpc.sock. only the bare unix (or unix:) keyword uses the default http.sock name

    the docs, release notes and -help text already match the code, so i think the PR description just needs updating to reflect this, so other reviewers do not get tripped up by the mismatch


    i also think this PR would be a good candidate for v33. there has been interest in this for a long time, and i think it is worth tracking as an upcoming feature for the release


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-10-26 01:51 UTC

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