net: add -outboundbind option for outgoing source address #35027

pull 8144225309 wants to merge 3 commits into bitcoin:master from 8144225309:net-bind-outgoing changing 10 files +559 −8
  1. 8144225309 commented at 9:48 PM on April 7, 2026: none

    Closes #6476

    Adds -outboundbind=<addr> to set the source address of outgoing clearnet connections, one per address family. It is separate from -bind; without it, outbound follows the OS routing table as before.

    • The command line wins over the config file, as for other options.
    • Clearnet only: Tor and I2P use their own transport, CJDNS is direct but not IPv4/IPv6, and a proxy leaves the option inert for that family (with a startup warning).
    • The effective address per family is trial-bound at startup, like -bind via BindListenPort; a port, the wildcard, or a non-clearnet value is rejected. A failed bind at connect time aborts the connection, with no fallback.

    Functional and unit tests cover both families, the startup rejections, and the bind-before-connect path.

    Based on vasild's approach in #6476 (comment).

  2. DrahtBot added the label P2P on Apr 7, 2026
  3. DrahtBot commented at 9:49 PM on April 7, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

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

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

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

    Type Reviewers
    Concept ACK frankomosh
    Approach ACK vasild

    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:

    • #35936 (net: reject oversized locators before allocating by l0rinc)
    • #17783 (common: Disallow calling IsArgSet() on ALLOW_LIST options by ryanofsky)
    • #17581 (refactor: Remove settings merge reverse precedence code by ryanofsky)
    • #17580 (refactor: Add ALLOW_LIST flags and enforce usage in CheckArgFlags by ryanofsky)
    • #17493 (util: Forbid ambiguous multiple assignments in config file 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-->

  4. 8144225309 force-pushed on Apr 7, 2026
  5. 8144225309 force-pushed on Apr 7, 2026
  6. luke-jr commented at 5:12 PM on April 9, 2026: contributor

    This might break a scenario where someone wants to listen on one IP, but load balance outgoing connections. A new option might be better?

  7. 8144225309 commented at 6:38 PM on April 9, 2026: none

    The original issue asks for -bind to control outgoing connections, and users who want listen-only can omit -bind. Outbound is capped at 11 (8 full relay + 2 block-only + 1 feeler) so there isn't much to load balance. A separate option could work but users already expect -bind to cover both directions.

  8. DrahtBot added the label Needs rebase on Apr 9, 2026
  9. 8144225309 force-pushed on Apr 10, 2026
  10. DrahtBot removed the label Needs rebase on Apr 10, 2026
  11. in src/net.h:1136 in 13879e1dd0 outdated
    1130 | @@ -1131,6 +1131,15 @@ class CConnman
    1131 |              }
    1132 |          }
    1133 |          m_onion_binds = connOptions.onion_binds;
    1134 | +        // Use -bind addresses for outgoing connections (one per address family).
    1135 | +        for (const auto& bind_addr : connOptions.vBinds) {
    1136 | +            if (bind_addr.IsLocal()) continue;
    


    frankomosh commented at 8:54 AM on April 14, 2026:

    IsLocal() also matches 0.0.0.0/8, not just loopback. The PR description says "loopback addresses are skipped", but the actual skip is broader. Worth a comment somewhere, clarifying that?


    8144225309 commented at 4:18 AM on August 17, 2026:

    Removed entirely. Addresses are used as given, then validated: parse, wildcard, and family checks, plus a trial bind() at init.

  12. in src/net.h:1137 in 13879e1dd0 outdated
    1130 | @@ -1131,6 +1131,15 @@ class CConnman
    1131 |              }
    1132 |          }
    1133 |          m_onion_binds = connOptions.onion_binds;
    1134 | +        // Use -bind addresses for outgoing connections (one per address family).
    1135 | +        for (const auto& bind_addr : connOptions.vBinds) {
    1136 | +            if (bind_addr.IsLocal()) continue;
    1137 | +            if (bind_addr.IsIPv4() && !m_outbound_bind_v4) {
    


    frankomosh commented at 9:09 AM on April 14, 2026:

    The listen path validates the address early through BindListenPort and surfaces errors at startup. Here we store it without checking if it's actually assigned to a local interface. Would it make sense to do a trial bind() or similar check here so a misconfigured address fails loudly at init rather than silently killing outbound for that family.


    8144225309 commented at 4:18 AM on August 17, 2026:

    Done. IsAddrBindable() does a trial bind() at init, so a misconfigured address fails startup. It has its own unit test.

  13. frankomosh commented at 9:16 AM on April 14, 2026: contributor

    concept ACK.

    Left some inline comments

  14. in src/test/net_tests.cpp:1610 in 13879e1dd0 outdated
    1605 | +
    1606 | +    // First non-local address per family is used; second is ignored.
    1607 | +    options.vBinds = {addr1, addr2};
    1608 | +    connman->Init(options);
    1609 | +    BOOST_REQUIRE(connman->GetOutboundBindV4());
    1610 | +    BOOST_CHECK_EQUAL(connman->GetOutboundBindV4()->ToStringAddr(), "203.0.113.1");
    


    frankomosh commented at 1:01 PM on April 14, 2026:

    The test verifies that the first IPv4 address is stored, but doesn't check that m_outbound_bind_v6 stays empty. If the else if condition were weakened, an IPv4 address could leak into the IPv6 slot undetected. You can avoid this by including BOOST_CHECK(!connman->GetOutboundBindV6()); around here.

  15. in src/netbase.cpp:662 in 13879e1dd0 outdated
     658 | +        socklen_t bind_len = sizeof(bind_sa);
     659 | +        if (!bind_service.GetSockAddr(reinterpret_cast<struct sockaddr*>(&bind_sa), &bind_len)) {
     660 | +            LogError("Cannot get sockaddr for bind address %s\n", bind_addr->ToStringAddr());
     661 | +            return {};
     662 | +        }
     663 | +        if (sock->Bind(reinterpret_cast<struct sockaddr*>(&bind_sa), bind_len) == SOCKET_ERROR) {
    


    frankomosh commented at 1:13 PM on April 14, 2026:

    I believe it would it be great to add a unit test that calls ConnectDirectly with a bind address, even if just to verify the syscall is attempted. Right now the unit test covers Init-time storage but this Bind() path has no automated coverage.


    8144225309 commented at 4:18 AM on August 17, 2026:

    Done. A unit test checks that ConnectDirectly() binds to the given source before connecting (recording mock socket via CreateSock), doesn't bind without one, and aborts on a failed bind.

  16. gmaxwell commented at 2:38 AM on April 25, 2026: contributor

    The original issue asks for -bind to control outgoing connections, and users who want listen-only can omit -bind.

    Just because someone asked that way doesn't mean it's the best solution. I believe that as proposed here -bind=mypublicinterface would suddenly make my node unable to connect to stuff on my local lan (or, in VM on bridge interfaces)-- pretty surprising.

    I don't see any particular harm in making a separate obind to control output interfaces-- otherwise the OS's routing table makes binding decisions.

  17. 8144225309 commented at 3:29 PM on April 25, 2026: none

    On the LAN/bridge case, that's a real regression for multi-homed setups (VM bridges especially). A separate outbound-only option avoids it without forcing existing -bind users to revisit their configs.

    I'd lean towards -bindoutgoing for clarity and alphabetic proximity to -bind in docs/tab-completion. That said, -outboundbind (or -obind) is arguably the better fit given the existing -whitebind/-rpcbind convention. Any preference?

  18. 8144225309 force-pushed on May 23, 2026
  19. 8144225309 renamed this:
    net: use -bind address for outgoing connections
    net: add -outboundbind option for outgoing source address
    on May 23, 2026
  20. 8144225309 commented at 5:11 AM on May 23, 2026: none

    Force-pushed a redesign based on the feedback above:

    • Added -outboundbind=<addr> as a separate option instead of extending -bind
    • Kept -bind semantics unchanged, so existing setups (including LAN/bridge peers) are unaffected
    • One non-local address per family, first wins; proxied (Tor, I2P, SOCKS5) and CJDNS excluded; loopback skipped
    • Named for consistency with -whitebind/-rpcbind; happy to rename if preferred
  21. 8144225309 force-pushed on May 23, 2026
  22. DrahtBot added the label CI failed on May 24, 2026
  23. DrahtBot commented at 9:05 AM on May 24, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task test ancestor commits: https://github.com/bitcoin/bitcoin/actions/runs/26346661562/job/77584098255</sub> <sub>LLM reason (✨ experimental): C++ build failed due to clang++ errors in src/netbase.cpp (use of undeclared identifier bind_addr, causing further CService constructor/type errors).</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>

  24. 8144225309 force-pushed on May 24, 2026
  25. DrahtBot removed the label CI failed on May 25, 2026
  26. vasild commented at 9:10 AM on May 26, 2026: contributor

    Concept ACK

    I agree that it is better to not change the semantic of the existent -bind and introduce a new option instead with a default value "leave it to the OS" which is the same as of before this PR.

  27. sedited requested review from frankomosh on May 26, 2026
  28. sedited removed review request from frankomosh on May 26, 2026
  29. sedited requested review from danielabrozzoni on May 26, 2026
  30. sedited requested review from frankomosh on May 26, 2026
  31. DrahtBot added the label Needs rebase on Jun 4, 2026
  32. 8144225309 force-pushed on Jun 4, 2026
  33. 8144225309 commented at 11:33 PM on June 4, 2026: none

    Rebased after conflict with #35410: the outbound bind selection in ConnectNode now sits inside the new private-broadcast guard. Also fixed an include-order nit and clarified wording in a net.h comment and the test commit message.

  34. DrahtBot removed the label Needs rebase on Jun 5, 2026
  35. in src/init.cpp:572 in fa19b8c434
     568 | @@ -569,6 +569,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
     569 |                  ), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
     570 |      argsman.AddArg("-bantime=<n>", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
     571 |      argsman.AddArg("-bind=<addr>[:<port>][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet3: 127.0.0.1:%u=onion, testnet4: 127.0.0.1:%u=onion, signet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", defaultChainParams->GetDefaultPort() + 1, testnetChainParams->GetDefaultPort() + 1, testnet4ChainParams->GetDefaultPort() + 1, signetChainParams->GetDefaultPort() + 1, regtestChainParams->GetDefaultPort() + 1), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
     572 | +    argsman.AddArg("-outboundbind=<addr>", "Bind outgoing connections to the given local address (one per address family). The address must be on a local interface; outgoing connections will use it as their source IP. Use [host] notation for IPv6.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
    


    vasild commented at 1:24 PM on June 9, 2026:
        argsman.AddArg("-outboundbind=<addr>", "Bind outgoing connections to the given local address (one per address family). The address must be on a local interface; outgoing connections will use it as their source IP. Use [address] notation for IPv6.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION);
    

    Better use "address" as "host" could be mistaken for a hostname.


    8144225309 commented at 4:18 AM on August 17, 2026:

    Done. The help now says to give a bare address without a port (LookupHost() rejects a value with a port).

  36. in src/init.cpp:2184 in fa19b8c434 outdated
    2179 | +        bilingual_str error;
    2180 | +        if (!IsAddrBindable(*bind_addr, error)) {
    2181 | +            return InitError(error);
    2182 | +        }
    2183 | +        connOptions.vOutboundBinds.push_back(bind_addr.value());
    2184 | +    }
    


    vasild commented at 1:30 PM on June 9, 2026:

    I didn't test, but I guess this will allow ports to be included, e.g. -outboundbind=1.2.4.5:5678 which better not be allowed to avoid confusion. LookupHost() can be used to parse just an address.


    8144225309 commented at 4:18 AM on August 17, 2026:

    Done, switched to LookupHost() so a port is rejected at startup.

  37. in src/netbase.cpp:1009 in fa19b8c434
    1004 | +bool IsAddrBindable(const CNetAddr& addr, bilingual_str& error)
    1005 | +{
    1006 | +    const CService service{addr, /*port=*/0};
    1007 | +    struct sockaddr_storage sa;
    1008 | +    socklen_t len = sizeof(sa);
    1009 | +    if (!service.GetSockAddr(reinterpret_cast<struct sockaddr*>(&sa), &len)) {
    


    vasild commented at 1:35 PM on June 9, 2026:

    If you want to have struct, that is fine. Just to mention that C++ can do without it:

        sockaddr_storage sa;
        socklen_t len = sizeof(sa);
        if (!service.GetSockAddr(reinterpret_cast<sockaddr*>(&sa), &len)) {
    
  38. in src/net.h:1095 in fa19b8c434
    1091 | @@ -1092,6 +1092,7 @@ class CConnman
    1092 |          std::vector<NetWhitelistPermissions> vWhitelistedRangeOutgoing;
    1093 |          std::vector<NetWhitebindPermissions> vWhiteBinds;
    1094 |          std::vector<CService> vBinds;
    1095 | +        std::vector<CService> vOutboundBinds;
    


    vasild commented at 1:42 PM on June 9, 2026:

    In new code use snake_case.

            std::vector<CService> outbound_binds;
    

    and also s/CService/CNetAddr/


    8144225309 commented at 4:19 AM on August 17, 2026:

    Done, and replaced the vector with one std::optional<CNetAddr> per family.

  39. in src/net.h:1144 in fa19b8c434
    1138 | @@ -1138,6 +1139,15 @@ class CConnman
    1139 |              }
    1140 |          }
    1141 |          m_onion_binds = connOptions.onion_binds;
    1142 | +        // Use -outboundbind addresses for outgoing connections (one per address family).
    1143 | +        for (const auto& bind_addr : connOptions.vOutboundBinds) {
    1144 | +            if (bind_addr.IsLocal()) continue;
    


    vasild commented at 1:49 PM on June 9, 2026:

    What's the reason to silently swallow such addresses? It would be more obvious if we return an error for those at startup, like we do for invalid values.


    vasild commented at 1:51 PM on June 9, 2026:

    Or even, why treat such addresses specially? It might be desirable to use -outboundbind=0.0.0.0 to remove the effect of a preceding -outboundbind=....


    8144225309 commented at 4:19 AM on August 17, 2026:

    No longer. A value that doesn't resolve, includes a port, is the wildcard, or isn't IPv4/IPv6 (a Tor or I2P address) is rejected at startup with its own error, and the effective address is then trial-bound. Nothing is dropped silently.


    8144225309 commented at 4:19 AM on August 17, 2026:

    Removed the special-casing. 0.0.0.0/:: pins nothing, so it's rejected at startup with its own error ("is not a usable source address"). Use -nooutboundbind to clear an earlier setting. Loopback is allowed; it fails at connect time for non-local destinations like any unroutable source, and is fine for nodes that only reach local peers.

  40. in src/init.cpp:2183 in fa19b8c434
    2178 | +        }
    2179 | +        bilingual_str error;
    2180 | +        if (!IsAddrBindable(*bind_addr, error)) {
    2181 | +            return InitError(error);
    2182 | +        }
    2183 | +        connOptions.vOutboundBinds.push_back(bind_addr.value());
    


    vasild commented at 9:52 AM on June 10, 2026:

    Here we collect all addresses supplied to -outboundbind= but we will only use one IPv4 and one IPv6 of them. I think it would be better to replace this vector with two std::optional<CNetAddr> members, then just copy them from connOptions into the CConnman members.


    vasild commented at 9:54 AM on June 10, 2026:

    Usually the notion is that the last one wins. E.g. -outboundbind=1.2.3.4 -outboundbind=5.6.7.8 -- 5.6.7.8 should be used. This helps override earlier settings which might be out of one's control.


    8144225309 commented at 4:19 AM on August 17, 2026:

    first-wins matches the config-file convention ("the first one is generally chosen", doc/bitcoin-conf.md; reverse_precedence in common/settings.cpp). Last-wins over GetArgs() would also invert command-line-over-config: an unbindable config address would then win over a valid command-line one and fail startup. Fully conventional resolution (last on the command line, first in the config) needs a per-source accessor ArgsManager doesn't have. Ok as-is, or want that added here?

  41. in src/netbase.cpp:673 in fa19b8c434
     671 | +    if (bind_addr) {
     672 | +        const CService bind_service{*bind_addr, /*port=*/0};
     673 | +        struct sockaddr_storage bind_sa;
     674 | +        socklen_t bind_len = sizeof(bind_sa);
     675 | +        if (!bind_service.GetSockAddr(reinterpret_cast<struct sockaddr*>(&bind_sa), &bind_len)) {
     676 | +            LogError("Cannot get sockaddr for bind address %s\n", bind_addr->ToStringAddr());
    


    vasild commented at 10:01 AM on June 10, 2026:

    Here and everywhere the trailing \n in the message is not needed anymore and is frowned upon. Drop it.

  42. in src/test/net_tests.cpp:1601 in fa19b8c434
    1596 | +    auto connman = std::make_unique<ConnmanTestMsg>(seed, seed, *m_node.addrman, *m_node.netgroupman, Params());
    1597 | +
    1598 | +    CConnman::Options options;
    1599 | +    options.m_msgproc = m_node.peerman.get();
    1600 | +
    1601 | +    const CService addr1{LookupNumeric("203.0.113.1", 8333)};
    


    vasild commented at 1:24 PM on June 10, 2026:

    The port here is irrelevant. These should all be CNetAddr.

  43. in src/test/net_tests.cpp:1610 in fa19b8c434
    1605 | +
    1606 | +    // First non-local address per family is used; second is ignored.
    1607 | +    options.vOutboundBinds = {addr1, addr2};
    1608 | +    connman->Init(options);
    1609 | +    BOOST_REQUIRE(connman->GetOutboundBindV4());
    1610 | +    BOOST_CHECK_EQUAL(connman->GetOutboundBindV4()->ToStringAddr(), "203.0.113.1");
    


    vasild commented at 1:26 PM on June 10, 2026:

    Instead of hardcoding the string address again "203.0.113.1" better use addr1.ToStringAddr() (here and elsewhere).

  44. in src/test/net_tests.cpp:1637 in fa19b8c434
    1632 | +    g_reachable_nets.Add(NET_CJDNS);
    1633 | +    const CService cjdns_service{LookupNumeric("fc00:1:2:3:4:5:6:7", 8333)};
    1634 | +    const CAddress cjdns_target{MaybeFlipIPv6toCJDNS(cjdns_service), NODE_NONE};
    1635 | +    BOOST_CHECK(cjdns_target.IsCJDNS());
    1636 | +    BOOST_CHECK(!cjdns_target.IsIPv4());
    1637 | +    BOOST_CHECK(!cjdns_target.IsIPv6());
    


    vasild commented at 1:27 PM on June 10, 2026:

    This is not testing the new feature. Better drop it.


    8144225309 commented at 4:19 AM on August 17, 2026:

    That block checked a CNetAddr property rather than the option, so I replaced it with a direct test of the feature: SelectOutboundBind() selects no source for a CJDNS target (and likewise Tor and I2P), in outbound_bind_address_selection.

  45. in test/functional/feature_bind_outgoing.py:14 in fa19b8c434
       9 | +Linux:
      10 | +  ifconfig lo:0 1.1.1.1/32 up && ifconfig lo:1 2.2.2.2/32 up
      11 | +  ifconfig lo:0 down && ifconfig lo:1 down  # to remove
      12 | +FreeBSD:
      13 | +  ifconfig lo0 1.1.1.1/32 alias && ifconfig lo0 2.2.2.2/32 alias
      14 | +  ifconfig lo0 1.1.1.1 -alias && ifconfig lo0 2.2.2.2 -alias  # to remove
    


    vasild commented at 1:42 PM on June 10, 2026:

    Recently the CI was extended to provide 1.1.1.5 and 1111:1111::5 on the VMs it creates to facilitate such tests. See ci/test/02_run_container.py and test/functional/feature_bind_port_discover.py and test/functional/feature_bind_port_externalip.py for example tests. It would be better if we keep the instructions consistent among tests.


    8144225309 commented at 4:20 AM on August 17, 2026:

    Done. The test binds 1.1.1.5/1111:1111::5 as the outgoing sources to a loopback destination (so the observed source differs from it), and self-skips when they're absent.

  46. in doc/release-notes-35027.md:7 in fa19b8c434
       0 | @@ -0,0 +1,7 @@
       1 | +P2P and network changes
       2 | +-----------------------
       3 | +
       4 | +- A new `-outboundbind=<addr>` option binds outgoing connections to the given
       5 | +  local address (one per address family). The address must be assigned to a
       6 | +  local interface. Proxied connections (Tor, I2P, SOCKS5) are not affected.
       7 | +  Existing `-bind` semantics are unchanged. (#6476)
    


    vasild commented at 1:43 PM on June 10, 2026:

    The number at the end should be the PR that contains the change:

      Existing `-bind` semantics are unchanged. (#35027)
    
  47. in test/functional/feature_bind_outgoing.py:59 in fa19b8c434
      54 | +        self.log.info("Connecting node 1 -> node 0")
      55 | +        target = f"{ADDR1}:{p2p_port(0)}"
      56 | +        self.nodes[1].addnode(target, "onetry")
      57 | +        self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == 1)
      58 | +
      59 | +        self.log.info("Checking that node 0 sees inbound from ADDR2")
    


    vasild commented at 1:56 PM on June 10, 2026:
            self.log.info(f"Checking that node 0 sees inbound from {ADDR2}")
    

    (and below as well)


    8144225309 commented at 4:20 AM on August 17, 2026:

    Done (the test was rewritten).

  48. vasild commented at 1:58 PM on June 10, 2026: contributor

    Approach ACK fa19b8c43489ffcc7693317af3c754992078e536

  49. DrahtBot added the label Needs rebase on Jul 14, 2026
  50. sedited commented at 8:23 PM on August 10, 2026: contributor

    @8144225309 this needs a rebase and there have been unaddressed review comments for two months now. Are you still working on this?

  51. 8144225309 commented at 11:49 PM on August 11, 2026: none

    Yes, rebasing now and addressing the review comments (LookupHost/CNetAddr storage, last-one-wins, CI-runnable functional test). Testing tonight; will push and update here once it's green.

  52. 8144225309 force-pushed on Aug 17, 2026
  53. 8144225309 commented at 4:08 AM on August 17, 2026: none

    Rebased and addressed the review comments:

    • -outboundbind takes a bare address (LookupHost, a port is rejected), stored as one CNetAddr per family; the command line wins over the config file
    • the local-address special-casing is gone. 0.0.0.0/:: and Tor/I2P addresses are rejected at startup, each with its own error; the effective address per family is trial-bound at init, so a command-line value can override an unbindable config one, and -nooutboundbind clears an earlier setting
    • clearnet only: Tor and I2P use their own transport, CJDNS is direct but not IPv4/IPv6, and a proxy for a family leaves the option inert (a startup warning)
    • a connect-time bind failure aborts the connection, with no fallback to the default source
    • tests: functional (both families with a loopback destination so source differs from destination; command-line-over-config override; rejection of unassigned/port/wildcard/Tor) and unit (per-family selection and nothing for Tor/I2P/CJDNS, trial bind, bind-before-connect, and fail-closed)

    Per-commit details in the commit messages.

  54. DrahtBot removed the label Needs rebase on Aug 17, 2026
  55. net: add -outboundbind option for outgoing source address
    Add -outboundbind=<addr> to set the source address of outgoing
    clearnet connections, one per address family, for multi-homed hosts.
    It is separate from -bind (listening only); without it, outbound
    follows the OS routing table as before.
    
    - The command line wins over the config file, as for other options.
    - Only clearnet is bound: Tor and I2P use their own transport, and
      CJDNS is direct but not IPv4/IPv6, so all three are unaffected. A
      proxy configured for a family leaves the option inert, with a
      startup warning.
    - A port, the wildcard, or a non-clearnet value is rejected at
      startup; the effective address per family is trial-bound, as -bind
      does via BindListenPort, so a command-line value can override a
      stale config-file one.
    - A failed bind at connect time aborts the connection, with no
      fallback, and is not counted against the peer in addrman, as proxy
      failures are not. Where available, IP_BIND_ADDRESS_NO_PORT keeps
      source port selection at connect() time. Use -nooutboundbind to
      disable the option.
    
    Based on vasild's 2023 sketch:
    https://github.com/bitcoin/bitcoin/issues/6476#issuecomment-1666472338
    
    Closes #6476
    ad220492e1
  56. test: add tests for outbound bind address selection
    Unit tests: Init() stores the source per address family (an unset
    family stays unset); SelectOutboundBind() returns the per-family
    source and nothing for a non-clearnet (Tor/I2P/CJDNS) target;
    IsAddrBindable() reports whether an address can be bound; and
    ConnectDirectly() binds the socket before connecting (recording mock
    that accepts only the exact per-family sockaddr length), does not
    bind without a source, aborts on a failed bind, and reports local
    bind failures through bind_failed.
    
    Functional test: outgoing connections originate from the -outboundbind
    address for both families, with a loopback destination so the observed
    source differs from it. Also checks command-line-over-config override,
    the family-specific proxy warning (with an onion-only negative case),
    and startup rejection of an unassigned address, a port, the wildcard,
    a Tor address, and a CJDNS address. Skipped only when binding the CI
    source addresses themselves fails.
    9fae34c76f
  57. doc: add release notes for -outboundbind 5baa78a79c
  58. 8144225309 force-pushed on Aug 19, 2026
  59. 8144225309 commented at 6:01 AM on August 19, 2026: none

    Rebased and fixed a few issues found while re-reviewing:

    • reject an fc00::/8 source as CJDNS when -cjdnsreachable is set, matching how the connect path classifies it
    • reject only the wildcard per value; other unusable values are left to the trial bind of the effective address, so a command line value can override a stale config one
    • don't count a failed local source bind as an addrman attempt against the peer, as proxy failures already don't
    • set IP_BIND_ADDRESS_NO_PORT on the source bind where available
    • name the destination in the connect-time bind failure log
    • tests: cover the new rejections and the per-family proxy warning, make the bind mock length-exact, tighten the skip condition

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-08-31 18:51 UTC

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