{
 "number": 35027,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35027",
 "title": "net: add -outboundbind option for outgoing source address",
 "author": "8144225309",
 "author_association": "FIRST_TIME_CONTRIBUTOR",
 "created_at": "2026-04-07T21:48:46Z",
 "updated_at": "2026-09-12T16:16:26Z",
 "age_days": 162,
 "draft": false,
 "labels": [
  "P2P"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "5baa78a79cb0c2144b680360d3bb46d223546df3",
 "head_ref": "net-bind-outgoing",
 "head_repo": "8144225309/bitcoin",
 "head_history": [
  {
   "t": "2026-04-07T22:38:43Z",
   "sha": "1dc1234e7fa8775d347380042fd536574eafa912"
  },
  {
   "t": "2026-04-07T22:42:47Z",
   "sha": "16eefb27d8dc9ca3a59bb31f96524afb4be95c2a"
  },
  {
   "t": "2026-04-10T02:32:46Z",
   "sha": "13879e1dd0d7aae538af2e701f27b2ddf97099c3"
  },
  {
   "t": "2026-05-23T04:41:23Z",
   "sha": "c55b58c1ae3bcd488aae64d27be7bde261ef8efd"
  },
  {
   "t": "2026-05-23T23:44:19Z",
   "sha": "229af39c3664f8c56d90566d3a66656d3ddc8bfb"
  },
  {
   "t": "2026-05-24T15:52:50Z",
   "sha": "3e905a01d7b9f9cc2e5d1273f40d468b2e4160cc"
  },
  {
   "t": "2026-06-04T23:32:20Z",
   "sha": "fa19b8c43489ffcc7693317af3c754992078e536"
  },
  {
   "t": "2026-08-17T04:05:18Z",
   "sha": "e0a184553bafc34c3691f0038ae32197fc5d2186"
  },
  {
   "t": "2026-08-19T05:44:09Z",
   "sha": "5baa78a79cb0c2144b680360d3bb46d223546df3"
  }
 ],
 "additions": 559,
 "deletions": 8,
 "changed_files": 10,
 "commit_count": 3,
 "size_bucket": "L",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "frankomosh",
      "url": "https://github.com/bitcoin/bitcoin/pull/35027#pullrequestreview-4104595148"
     }
    ],
    "approach_ack": [
     {
      "login": "vasild",
      "url": "https://github.com/bitcoin/bitcoin/pull/35027#pullrequestreview-4458989700"
     },
     {
      "login": "winterrdog",
      "url": "https://github.com/bitcoin/bitcoin/pull/35027#pullrequestreview-5187086729"
     }
    ]
   },
   "conflicts": [
    {
     "number": 35936,
     "title": "net: reject oversized locators before allocating",
     "author": "l0rinc"
    },
    {
     "number": 17783,
     "title": "common: Disallow calling IsArgSet() on ALLOW_LIST options",
     "author": "ryanofsky"
    },
    {
     "number": 17581,
     "title": "refactor: Remove settings merge reverse precedence code",
     "author": "ryanofsky"
    },
    {
     "number": 17580,
     "title": "refactor: Add ALLOW_LIST flags and enforce usage in CheckArgFlags",
     "author": "ryanofsky"
    },
    {
     "number": 17493,
     "title": "util: Forbid ambiguous multiple assignments in config file",
     "author": "ryanofsky"
    }
   ]
  }
 },
 "acks_parsed": {
  "frankomosh": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-04-14T09:16:17Z",
   "stale": false
  },
  "vasild": {
   "kind": "approach_ack",
   "hash": "fa19b8c43489ffcc7693317af3c754992078e536",
   "t": "2026-06-10T13:58:21Z",
   "stale": false
  },
  "winterrdog": {
   "kind": "approach_ack",
   "hash": null,
   "t": "2026-09-12T16:16:23Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 1,
  "approach_ack": 2,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "frankomosh",
   "gmaxwell",
   "luke-jr",
   "sedited",
   "vasild",
   "winterrdog"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-08-19T06:01:43Z",
  "last_reviewer_activity": "2026-09-12T16:16:23Z",
  "last_reviewer": "winterrdog",
  "author_silent_days": 29,
  "waiting_on_author_days": 4,
  "days_since_update": 4
 },
 "refs": {
  "mentioned": [
   6476,
   35410
  ],
  "depends_on": [],
  "fixes": [
   6476
  ],
  "linked_issues": [
   {
    "number": 6476,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Does not use bind to local address for outgoing connections"
   }
  ],
  "references": [
   {
    "number": 6476,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Does not use bind to local address for outgoing connections"
   },
   {
    "number": 35410,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-06-04",
    "title": "net: use the proxy if overriden when doing v2->v1 reconnections"
   }
  ],
  "conflicts": [
   35936,
   17783,
   17581,
   17580,
   17493
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "doc/release-notes-35027.md",
  "src/init.cpp",
  "src/net.h",
  "src/netbase.cpp",
  "src/test/net_tests.cpp",
  "test/functional/feature_bind_outgoing.py"
 ],
 "body": "Closes #6476\n\nAdds `-outboundbind=<addr>` to set the source address of outgoing clearnet\nconnections, one per address family. It is separate from `-bind`; without it,\noutbound follows the OS routing table as before.\n\n- The command line wins over the config file, as for other options.\n- Clearnet only: Tor and I2P use their own transport, CJDNS is direct but not\n  IPv4/IPv6, and a proxy leaves the option inert for that family (with a\n  startup warning).\n- The effective address per family is trial-bound at startup, like `-bind` via\n  `BindListenPort`; a port, the wildcard, or a non-clearnet value is rejected. A\n  failed bind at connect time aborts the connection, with no fallback.\n\nFunctional and unit tests cover both families, the startup rejections, and the\nbind-before-connect path.\n\nBased on vasild's approach in\nhttps://github.com/bitcoin/bitcoin/issues/6476#issuecomment-1666472338.",
 "commits": [
  {
   "sha": "ad220492e1dbf044ac78520cdb7d1184312e473b",
   "date": "2026-08-19T05:07:32Z",
   "message": "net: add -outboundbind option for outgoing source address\n\nAdd -outboundbind=<addr> to set the source address of outgoing\nclearnet connections, one per address family, for multi-homed hosts.\nIt is separate from -bind (listening only); without it, outbound\nfollows the OS routing table as before.\n\n- The command line wins over the config file, as for other options.\n- Only clearnet is bound: Tor and I2P use their own transport, and\n  CJDNS is direct but not IPv4/IPv6, so all three are unaffected. A\n  proxy configured for a family leaves the option inert, with a\n  startup warning.\n- A port, the wildcard, or a non-clearnet value is rejected at\n  startup; the effective address per family is trial-bound, as -bind\n  does via BindListenPort, so a command-line value can override a\n  stale config-file one.\n- A failed bind at connect time aborts the connection, with no\n  fallback, and is not counted against the peer in addrman, as proxy\n  failures are not. Where available, IP_BIND_ADDRESS_NO_PORT keeps\n  source port selection at connect() time. Use -nooutboundbind to\n  disable the option.\n\nBased on vasild's 2023 sketch:\nhttps://github.com/bitcoin/bitcoin/issues/6476#issuecomment-1666472338\n\nCloses #6476"
  },
  {
   "sha": "9fae34c76fc8a839531687b23f4af193581ca214",
   "date": "2026-08-19T05:07:33Z",
   "message": "test: add tests for outbound bind address selection\n\nUnit tests: Init() stores the source per address family (an unset\nfamily stays unset); SelectOutboundBind() returns the per-family\nsource and nothing for a non-clearnet (Tor/I2P/CJDNS) target;\nIsAddrBindable() reports whether an address can be bound; and\nConnectDirectly() binds the socket before connecting (recording mock\nthat accepts only the exact per-family sockaddr length), does not\nbind without a source, aborts on a failed bind, and reports local\nbind failures through bind_failed.\n\nFunctional test: outgoing connections originate from the -outboundbind\naddress for both families, with a loopback destination so the observed\nsource differs from it. Also checks command-line-over-config override,\nthe family-specific proxy warning (with an onion-only negative case),\nand startup rejection of an unassigned address, a port, the wildcard,\na Tor address, and a CJDNS address. Skipped only when binding the CI\nsource addresses themselves fails."
  },
  {
   "sha": "5baa78a79cb0c2144b680360d3bb46d223546df3",
   "date": "2026-08-19T05:07:34Z",
   "message": "doc: add release notes for -outboundbind"
  }
 ],
 "timeline": [
  {
   "t": "2026-04-07T22:38:43Z",
   "kind": "force_push",
   "who": "8144225309",
   "commit": "1dc1234e7fa8775d347380042fd536574eafa912"
  },
  {
   "t": "2026-04-07T22:42:47Z",
   "kind": "force_push",
   "who": "8144225309",
   "commit": "16eefb27d8dc9ca3a59bb31f96524afb4be95c2a"
  },
  {
   "t": "2026-04-09T17:12:43Z",
   "kind": "comment",
   "who": "luke-jr",
   "assoc": "CONTRIBUTOR",
   "text": "This might break a scenario where someone wants to listen on one IP, but load balance outgoing connections. A new option might be better?"
  },
  {
   "t": "2026-04-09T18:38:53Z",
   "kind": "comment",
   "who": "8144225309",
   "assoc": "NONE",
   "text": "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."
  },
  {
   "t": "2026-04-10T02:32:46Z",
   "kind": "force_push",
   "who": "8144225309",
   "commit": "13879e1dd0d7aae538af2e701f27b2ddf97099c3"
  },
  {
   "t": "2026-04-14T08:54:01Z",
   "kind": "review_comment",
   "who": "frankomosh",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.h",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "`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?"
  },
  {
   "t": "2026-04-14T09:09:33Z",
   "kind": "review_comment",
   "who": "frankomosh",
   "assoc": "CONTRIBUTOR",
   "path": "src/net.h",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "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."
  },
  {
   "t": "2026-04-14T09:16:17Z",
   "kind": "review",
   "who": "frankomosh",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "13879e1dd0d7aae538af2e701f27b2ddf97099c3",
   "text": "concept ACK.\n\nLeft some inline comments"
  },
  {
   "t": "2026-04-14T13:01:59Z",
   "kind": "review_comment",
   "who": "frankomosh",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/net_tests.cpp",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "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."
  },
  {
   "t": "2026-04-14T13:13:19Z",
   "kind": "review_comment",
   "who": "frankomosh",
   "assoc": "CONTRIBUTOR",
   "path": "src/netbase.cpp",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "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."
  },
  {
   "t": "2026-04-25T02:38:04Z",
   "kind": "comment",
   "who": "gmaxwell",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nJust 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.\n\nI don't see any particular harm in making a separate obind to control output interfaces--  otherwise the OS's routing table makes binding decisions."
  },
  {
   "t": "2026-04-25T15:29:04Z",
   "kind": "comment",
   "who": "8144225309",
   "assoc": "NONE",
   "text": "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.\n\nI'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?"
  },
  {
   "t": "2026-05-23T04:41:23Z",
   "kind": "force_push",
   "who": "8144225309",
   "commit": "c55b58c1ae3bcd488aae64d27be7bde261ef8efd"
  },
  {
   "t": "2026-05-23T05:11:19Z",
   "kind": "comment",
   "who": "8144225309",
   "assoc": "NONE",
   "text": "Force-pushed a redesign based on the feedback above:\n\n- Added `-outboundbind=<addr>` as a separate option instead of extending `-bind`\n- Kept `-bind` semantics unchanged, so existing setups (including LAN/bridge peers) are unaffected\n- One non-local address per family, first wins; proxied (Tor, I2P, SOCKS5) and CJDNS excluded; loopback skipped\n- Named for consistency with `-whitebind`/`-rpcbind`; happy to rename if preferred"
  },
  {
   "t": "2026-05-23T23:44:19Z",
   "kind": "force_push",
   "who": "8144225309",
   "commit": "229af39c3664f8c56d90566d3a66656d3ddc8bfb"
  },
  {
   "t": "2026-05-24T15:52:50Z",
   "kind": "force_push",
   "who": "8144225309",
   "commit": "3e905a01d7b9f9cc2e5d1273f40d468b2e4160cc"
  },
  {
   "t": "2026-05-26T09:10:20Z",
   "kind": "review",
   "who": "vasild",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "3e905a01d7b9f9cc2e5d1273f40d468b2e4160cc",
   "text": "Concept ACK\n\nI 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."
  },
  {
   "t": "2026-06-04T23:32:20Z",
   "kind": "force_push",
   "who": "8144225309",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536"
  },
  {
   "t": "2026-06-04T23:33:29Z",
   "kind": "comment",
   "who": "8144225309",
   "assoc": "NONE",
   "text": "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."
  },
  {
   "t": "2026-06-09T13:24:48Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "path": "src/init.cpp",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "```suggestion\n    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);\n```\n\nBetter use \"address\" as \"host\" could be mistaken for a hostname."
  },
  {
   "t": "2026-06-09T13:30:15Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "path": "src/init.cpp",
   "commit": "5baa78a79cb0c2144b680360d3bb46d223546df3",
   "in_reply_to": null,
   "text": "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."
  },
  {
   "t": "2026-06-09T13:35:03Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "path": "src/netbase.cpp",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "If you want to have `struct`, that is fine. Just to mention that C++ can do without it:\n\n```suggestion\n    sockaddr_storage sa;\n    socklen_t len = sizeof(sa);\n    if (!service.GetSockAddr(reinterpret_cast<sockaddr*>(&sa), &len)) {\n```"
  },
  {
   "t": "2026-06-09T13:42:09Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "path": "src/net.h",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "In new code use `snake_case`.\n```suggestion\n        std::vector<CService> outbound_binds;\n```\nand also `s/CService/CNetAddr/`"
  },
  {
   "t": "2026-06-09T13:49:38Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "path": "src/net.h",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "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."
  },
  {
   "t": "2026-06-09T13:51:38Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "path": "src/net.h",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "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=...`."
  },
  {
   "t": "2026-06-10T09:52:05Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "path": "src/init.cpp",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "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."
  },
  {
   "t": "2026-06-10T09:54:48Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "path": "src/init.cpp",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "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."
  },
  {
   "t": "2026-06-10T10:01:07Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "path": "src/netbase.cpp",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "Here and everywhere the trailing `\\n` in the message is not needed anymore and is frowned upon. Drop it."
  },
  {
   "t": "2026-06-10T13:24:37Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "path": "src/test/net_tests.cpp",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "The port here is irrelevant. These should all be `CNetAddr`."
  },
  {
   "t": "2026-06-10T13:26:25Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "path": "src/test/net_tests.cpp",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "Instead of hardcoding the string address again `\"203.0.113.1\"` better use `addr1.ToStringAddr()` (here and elsewhere)."
  },
  {
   "t": "2026-06-10T13:27:42Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "path": "src/test/net_tests.cpp",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "This is not testing the new feature. Better drop it."
  },
  {
   "t": "2026-06-10T13:42:27Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "path": "test/functional/feature_bind_outgoing.py",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "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."
  },
  {
   "t": "2026-06-10T13:43:31Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "path": "doc/release-notes-35027.md",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "The number at the end should be the PR that contains the change:\n```suggestion\n  Existing `-bind` semantics are unchanged. (#35027)\n```"
  },
  {
   "t": "2026-06-10T13:56:02Z",
   "kind": "review_comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "path": "test/functional/feature_bind_outgoing.py",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": null,
   "text": "```suggestion\n        self.log.info(f\"Checking that node 0 sees inbound from {ADDR2}\")\n```\n(and below as well)"
  },
  {
   "t": "2026-06-10T13:58:21Z",
   "kind": "review",
   "who": "vasild",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "text": "Approach ACK fa19b8c43489ffcc7693317af3c754992078e536"
  },
  {
   "t": "2026-08-10T20:23:27Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "@8144225309 this needs a rebase and there have been unaddressed review comments for two months now. Are you still working on this?"
  },
  {
   "t": "2026-08-11T23:49:43Z",
   "kind": "comment",
   "who": "8144225309",
   "assoc": "NONE",
   "text": "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."
  },
  {
   "t": "2026-08-17T04:05:18Z",
   "kind": "force_push",
   "who": "8144225309",
   "commit": "e0a184553bafc34c3691f0038ae32197fc5d2186"
  },
  {
   "t": "2026-08-17T04:08:32Z",
   "kind": "comment",
   "who": "8144225309",
   "assoc": "NONE",
   "text": "Rebased and addressed the review comments:\n\n- `-outboundbind` takes a bare address (`LookupHost`, a port is rejected), stored as one `CNetAddr` per family; the command line wins over the config file\n- 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\n- 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)\n- a connect-time bind failure aborts the connection, with no fallback to the default source\n- 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)\n\nPer-commit details in the commit messages."
  },
  {
   "t": "2026-08-17T04:18:16Z",
   "kind": "review_comment",
   "who": "8144225309",
   "assoc": "NONE",
   "path": "src/net.h",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": 3078245893,
   "text": "Removed entirely. Addresses are used as given, then validated: parse, wildcard, and family checks, plus a trial `bind()` at init."
  },
  {
   "t": "2026-08-17T04:18:23Z",
   "kind": "review_comment",
   "who": "8144225309",
   "assoc": "NONE",
   "path": "src/net.h",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": 3078339596,
   "text": "Done. `IsAddrBindable()` does a trial `bind()` at init, so a misconfigured address fails startup. It has its own unit test."
  },
  {
   "t": "2026-08-17T04:18:36Z",
   "kind": "review_comment",
   "who": "8144225309",
   "assoc": "NONE",
   "path": "src/netbase.cpp",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": 3079687724,
   "text": "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."
  },
  {
   "t": "2026-08-17T04:18:43Z",
   "kind": "review_comment",
   "who": "8144225309",
   "assoc": "NONE",
   "path": "src/init.cpp",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": 3380951884,
   "text": "Done. The help now says to give a bare address without a port (`LookupHost()` rejects a value with a port)."
  },
  {
   "t": "2026-08-17T04:18:49Z",
   "kind": "review_comment",
   "who": "8144225309",
   "assoc": "NONE",
   "path": "src/init.cpp",
   "commit": "5baa78a79cb0c2144b680360d3bb46d223546df3",
   "in_reply_to": 3380992855,
   "text": "Done, switched to `LookupHost()` so a port is rejected at startup."
  },
  {
   "t": "2026-08-17T04:19:03Z",
   "kind": "review_comment",
   "who": "8144225309",
   "assoc": "NONE",
   "path": "src/net.h",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": 3381078759,
   "text": "Done, and replaced the vector with one `std::optional<CNetAddr>` per family."
  },
  {
   "t": "2026-08-17T04:19:09Z",
   "kind": "review_comment",
   "who": "8144225309",
   "assoc": "NONE",
   "path": "src/net.h",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": 3381134818,
   "text": "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."
  },
  {
   "t": "2026-08-17T04:19:16Z",
   "kind": "review_comment",
   "who": "8144225309",
   "assoc": "NONE",
   "path": "src/net.h",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": 3381150540,
   "text": "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."
  },
  {
   "t": "2026-08-17T04:19:31Z",
   "kind": "review_comment",
   "who": "8144225309",
   "assoc": "NONE",
   "path": "src/init.cpp",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": 3387250516,
   "text": "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?"
  },
  {
   "t": "2026-08-17T04:19:58Z",
   "kind": "review_comment",
   "who": "8144225309",
   "assoc": "NONE",
   "path": "src/test/net_tests.cpp",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": 3388680247,
   "text": "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`."
  },
  {
   "t": "2026-08-17T04:20:04Z",
   "kind": "review_comment",
   "who": "8144225309",
   "assoc": "NONE",
   "path": "test/functional/feature_bind_outgoing.py",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": 3388786600,
   "text": "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."
  },
  {
   "t": "2026-08-17T04:20:18Z",
   "kind": "review_comment",
   "who": "8144225309",
   "assoc": "NONE",
   "path": "test/functional/feature_bind_outgoing.py",
   "commit": "fa19b8c43489ffcc7693317af3c754992078e536",
   "in_reply_to": 3388891578,
   "text": "Done (the test was rewritten)."
  },
  {
   "t": "2026-08-19T05:44:09Z",
   "kind": "force_push",
   "who": "8144225309",
   "commit": "5baa78a79cb0c2144b680360d3bb46d223546df3"
  },
  {
   "t": "2026-08-19T06:01:43Z",
   "kind": "comment",
   "who": "8144225309",
   "assoc": "NONE",
   "text": "Rebased and fixed a few issues found while re-reviewing:\n\n- reject an fc00::/8 source as CJDNS when -cjdnsreachable is set, matching how the connect path classifies it\n- 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\n- don't count a failed local source bind as an addrman attempt against the peer, as proxy failures already don't\n- set IP_BIND_ADDRESS_NO_PORT on the source bind where available\n- name the destination in the connect-time bind failure log\n- tests: cover the new rejections and the per-family proxy warning, make the bind mock length-exact, tighten the skip condition"
  },
  {
   "t": "2026-09-12T16:16:23Z",
   "kind": "review",
   "who": "winterrdog",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "5baa78a79cb0c2144b680360d3bb46d223546df3",
   "text": "approach ACK\n\nthough, i might be TOO late for the name search party. i'd `-exitbind` in mind (inspired by TOR exit relays). will provide more review"
  }
 ],
 "labels_log": [
  {
   "t": "2026-04-07T21:48:55Z",
   "action": "labeled",
   "label": "P2P",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-09T22:51:50Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-10T03:28:21Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-24T09:04:59Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-25T16:05:59Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-04T11:29:54Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-05T00:22:06Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-14T23:49:01Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-17T05:53:58Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-05-23T04:41:51Z",
   "kind": "renamed",
   "who": "8144225309",
   "from": "net: use -bind address for outgoing connections",
   "to": "net: add -outboundbind option for outgoing source address"
  }
 ],
 "text_chars": 13745,
 "text_tokens_estimate": 3436,
 "changed_paths": [
  "doc/release-notes-35027.md",
  "src/init.cpp",
  "src/net.cpp",
  "src/net.h",
  "src/netbase.cpp",
  "src/netbase.h",
  "src/test/net_tests.cpp",
  "src/test/util/net.h",
  "test/functional/feature_bind_outgoing.py",
  "test/functional/test_runner.py"
 ],
 "files": [
  {
   "path": "doc/release-notes-35027.md",
   "add": 7,
   "del": 0
  },
  {
   "path": "src/init.cpp",
   "add": 48,
   "del": 0
  },
  {
   "path": "src/net.cpp",
   "add": 4,
   "del": 3
  },
  {
   "path": "src/net.h",
   "add": 28,
   "del": 0
  },
  {
   "path": "src/netbase.cpp",
   "add": 62,
   "del": 3
  },
  {
   "path": "src/netbase.h",
   "add": 19,
   "del": 2
  },
  {
   "path": "src/test/net_tests.cpp",
   "add": 202,
   "del": 0
  },
  {
   "path": "src/test/util/net.h",
   "add": 4,
   "del": 0
  },
  {
   "path": "test/functional/feature_bind_outgoing.py",
   "add": 184,
   "del": 0
  },
  {
   "path": "test/functional/test_runner.py",
   "add": 1,
   "del": 0
  }
 ],
 "test_lines": 391,
 "git": {
  "head": "5baa78a79cb0c2144b680360d3bb46d223546df3",
  "head_matches_backup": true,
  "base": "b88bffe550a3a017440aff6a189cdb5f79f9b060",
  "commits": [
   {
    "sha": "ad220492e1",
    "subject": "net: add -outboundbind option for outgoing source address",
    "files": 5,
    "add": 161,
    "del": 8
   },
   {
    "sha": "9fae34c76f",
    "subject": "test: add tests for outbound bind address selection",
    "files": 4,
    "add": 391,
    "del": 0
   },
   {
    "sha": "5baa78a79c",
    "subject": "doc: add release notes for -outboundbind",
    "files": 1,
    "add": 7,
    "del": 0
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "b8f6108e0d051afe",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}