Skip to content

Conversation

@andremralves
Copy link
Contributor

@andremralves andremralves commented Dec 13, 2024

If -bind=0.0.0.0 or -bind=:: is specified, it indicates that we can bind to any available address. In this case, the Discover() function should be executed to retrieve the local addresses. This behavior should also work for "=onion" addresses.

I updated the GetListenPort() function to also consider ports from onion address. Without this change, when -bind=0.0.0.0:port=onion is used, the bound port would not match the specified port.

How to test

Prepare the addresses to be found by Discover()

ip link add name lo_dummy type dummy
ip addr add 127.0.1.2/8 dev lo_dummy
ip link set lo_dummy up
ifconfig lo_dummy:0 1.1.1.1/32 up && ifconfig lo_dummy:1 2.2.2.2/32 up

Execute the test

./build/test/functional/test_runner.py --ihave1111and2222 feature_bind_port_discover

Undo the changes after the test execution

ifconfig lo_dummy:0 down && ifconfig lo_dummy:1
ip link set lo_dummy down
ip link delete lo_dummy

Fixes #31293
This PR also fixes the test feature_bind_port_discover.py that was falling.
Fixes #31336

@DrahtBot
Copy link
Contributor

DrahtBot commented Dec 13, 2024

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

Code Coverage & Benchmarks

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

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK zaidmstrr, yuvicc, ryanofsky

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #29500 (test: create assert_not_equal util by kevkevinpal)

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.

@tnndbtc
Copy link

tnndbtc commented Dec 15, 2024

@andremralves Thanks for putting up this PR so that it helped me run the test on Mac. In addition to your changes, the full setup on mac would require comment made by @evgeniytar in #31293 (comment) , which I quote:

sudo ifconfig bridge1 create
sudo ifconfig bridge1 inet 1.1.1.1/24 up
sudo ifconfig bridge1 inet 2.2.2.2/24 alias

So, the following tweak on lo0 is not required:
sudo ifconfig lo0 alias 1.1.1.1/32
sudo ifconfig lo0 up

Then run:
build/test/functional/test_runner.py --ihave1111and2222 build/test/functional/feature_bind_port_discover.py
Temporary test directory at /var/folders/mw/_h9qr9ws33q1pc_jwk49l3n40000gp/T/test_runner_₿_🏃_20241214_225503
Remaining jobs: [feature_bind_port_discover.py]
1/1 - feature_bind_port_discover.py passed, Duration: 2 s
TEST | STATUS | DURATION
feature_bind_port_discover.py | ✓ Passed | 2 s
ALL | ✓ Passed | 2 s (accumulated)
Runtime: 2 s

sudo ifconfig bridge1 destroy    # to delete bridge1 on mac after test

Otherwise, it will hit error below:
2024-12-15T03:50:03.266000Z TestFramework (ERROR): Assertion failed
Traceback (most recent call last):
File "<...>/bitcoin/test/functional/test_framework/test_framework.py", line 135, in main
self.run_test()
~~~~~~~~~~~~~^^
File "<...>/bitcoin/build/test/functional/feature_bind_port_discover.py", line 90, in run_test
self.verify_addrs(self.nodes[0].getnetworkinfo()['localaddresses'], True, True, BIND_PORT)
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/tnnd/data/code/bitcoin/build/test/functional/feature_bind_port_discover.py", line 83, in verify_addrs
assert found_addr1 == expected_addr1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Let me know if a separated PR is preferable so that I can submit one after your PR is in.

Copy link
Contributor

@yuvicc yuvicc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested ACK a25ce3b22ff3b5771353ab4bcb66a932702bfbd1

Thanks for creating this PR @andremralves.

Log output at master@85bcfeea23568053ea09013fb8263fa1511d7123

Screenshot from 2024-12-15 16-09-33

Note: to reproduce, use the above code by @andremralves #31492 (comment)

Log output at commit a25ce3b22ff3b5771353ab4bcb66a932702bfbd1

Screenshot from 2024-12-15 15-39-14

For mac see the comment by @tnndbtc

P.S. I have also checked the following:

  • if no -bind is used then we are automatically binding 0.0.0.0, execute Discover()
  • -bind is used with either 0.0.0.0 or ::, execute Discover()

@tnndbtc
Copy link

tnndbtc commented Dec 18, 2024

@andremralves I also tested your PR on mac and provided comments in #31293 (comment) . This is a pass to me as I validated on Mac and checked workflow by adding more debug traces.
CC: @i-am-yuvi

Copy link

@tnndbtc tnndbtc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes are validated on Mac (15.1.1, Apple M1 chipset). Review comment is provided in #31293 (comment). But, still need to fix the merge conflict.

@yuvicc
Copy link
Contributor

yuvicc commented Dec 18, 2024

@andremralves I also tested your PR on mac and provided comments in #31293 (comment) . This is a pass to me as I validated on Mac and checked workflow by adding more debug traces. CC: @i-am-yuvi

Agree with you, works on M2 chip as well!!

@andremralves
Copy link
Contributor Author

The changes are validated on Mac (15.1.1, Apple M1 chipset). Review comment is provided in #31293 (comment). But, still need to fix the merge conflict.

Thank you for the review @tnndbtc and @i-am-yuvi. I will fix the merge conflicts.

src/init.cpp Outdated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, I'm reviewing this PR; can you please explain to me the thoughts behind removing this line of code? 

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As onion_service_target will contain the one and only onion service target in this branch of logic, we don't need to explicitly add it to a list of onion service targets. This is especially true since connOptions.onion_binds is only used to make sure there is only one onion bind; we have already checked that above (line 1906).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, I'm reviewing this PR; can you please explain to me the thoughts behind removing this line of code?

Without this change the test case ./build/src/bitcoind --bind=0.0.0.0:port=onion would fail. Since we are now setting bind_on_any as true when -bind=0.0.0.0, the server would attempt to bind to 0.0.0.0:port two times in CConnman::InitBinds (lines 3272 and 3286). The addition in lines 3279-3296 handles the default bind for onion and fixes the issue.

@fanquake fanquake marked this pull request as draft February 20, 2025 22:14
@fanquake
Copy link
Member

I will fix the merge conflicts.

Moved to draft. @andremralves are you circling back to fix the conflicts?

@andremralves
Copy link
Contributor Author

Moved to draft. @andremralves are you circling back to fix the conflicts?

Sorry for the delay! I've been a little bit busy lately, but I'll take care of the conflicts now.

@DrahtBot
Copy link
Contributor

🚧 At least one of the CI tasks failed.
Debug: https://github.com/bitcoin/bitcoin/runs/37827464411

Hints

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.

@andremralves
Copy link
Contributor Author

Fixed the conflicts!

@andremralves andremralves marked this pull request as ready for review February 26, 2025 07:06
@zaidmstrr
Copy link
Contributor

zaidmstrr commented Feb 26, 2025

Tested ACK 1561575
After creating the dummy interfaces, I ran the test with args ihave1111and2222 and the test passed successfully.

@DrahtBot DrahtBot requested a review from yuvicc February 26, 2025 08:12
Copy link
Contributor

@yuvicc yuvicc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re-ACK 1561575

  • Tested all the changes

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review ACK 1561575, but would be nice to have feedback from @vasild here since this is supposed to fix #31293.

The changes here all seem to make sense but the interactions between them are complicated. I think this PR would be easier to review if the changes could be broken down as much as possible. It would be nice to add new tests in a first commit to clarify current buggy behaviors. Then it seems like the GetListenPort =onion parsing fix could be a separate commit. Then the refactoring change replacing connOptions.onion_binds.push_back(onion_service_target) with new code in InitBinds could be a another commit. After that the main Discover/bind_on_any part of the change would more clear.

Comment on lines +3292 to +3295
struct in_addr onion_service_target;
onion_service_target.s_addr = htonl(INADDR_LOOPBACK);
const uint16_t onion_port = GetListenPort() + 1;
const CService onion_addr = {onion_service_target, onion_port}; // 127.0.0.1
Copy link
Contributor

@ryanofsky ryanofsky Mar 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "net: execute Discover() when bind=0.0.0.0 or :: is set" (c1de112)

Might be missing something, but it seems like this could be simplified:

const CService onion_addr{DefaultOnionServiceTarget(GetListenPort() + 1);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. This new code duplicates the code from DefaultOnionServiceTarget():

bitcoin/src/torcontrol.cpp

Lines 726 to 731 in 5b8046a

CService DefaultOnionServiceTarget(uint16_t port)
{
struct in_addr onion_service_target;
onion_service_target.s_addr = htonl(INADDR_LOOPBACK);
return {onion_service_target, port};
}

and the port logic from init.cpp:

const uint16_t default_bind_port_onion = default_bind_port + 1;

Which is problematic because if this logic has to be changed then it will have to be changed in more than one place.

Before this PR the outcome of DefaultOnionServiceTarget(default_bind_port_onion) was communicated to InitBinds() (here) via the entry in options.onion_binds. Adding that entry however was removed in this PR to facilitate the new condition above: options.bind_on_any && options.vBinds.empty() && options.onion_binds.empty() (onion_binds needs to be empty now).

I think a smaller, less invasive change can replace commit c1de112 net: execute Discover() when bind=0.0.0.0 or :: is set:

[patch] call Discover() also if -bind=0.0.0.0[:port] is configured
--- i/src/init.cpp
+++ w/src/init.cpp
@@ -1995,13 +1995,20 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
                                     "for the automatically created Tor onion service."),
                                   onion_service_target.ToStringAddrPort()));
         }
         StartTorControl(onion_service_target);
     }
 
-    if (connOptions.bind_on_any) {
+    // If -bind=0.0.0.0[:port] or -whitebind=0.0.0.0[:port] was configured.
+    // Deliberately omit -bind=0.0.0.0[:port]=onion (if configured) because that
+    // listening address should not be advertised.
+    const bool some_binds_are_any{
+        std::ranges::any_of(connOptions.vBinds, [](const CService& b) { return b.IsBindAny(); }) ||
+        std::ranges::any_of(connOptions.vWhiteBinds, [](const NetWhitebindPermissions& w) { return w.m_service.IsBindAny(); })};
+
+    if (connOptions.bind_on_any || some_binds_are_any) {
         // Only add all IP addresses of the machine if we would be listening on
         // any address - 0.0.0.0 (IPv4) and :: (IPv6).
         Discover();
     }
 
     for (const auto& net : args.GetArgs("-whitelist")) {

@DrahtBot
Copy link
Contributor

DrahtBot commented Apr 1, 2025

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

Comment on lines +3292 to +3295
struct in_addr onion_service_target;
onion_service_target.s_addr = htonl(INADDR_LOOPBACK);
const uint16_t onion_port = GetListenPort() + 1;
const CService onion_addr = {onion_service_target, onion_port}; // 127.0.0.1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. This new code duplicates the code from DefaultOnionServiceTarget():

bitcoin/src/torcontrol.cpp

Lines 726 to 731 in 5b8046a

CService DefaultOnionServiceTarget(uint16_t port)
{
struct in_addr onion_service_target;
onion_service_target.s_addr = htonl(INADDR_LOOPBACK);
return {onion_service_target, port};
}

and the port logic from init.cpp:

const uint16_t default_bind_port_onion = default_bind_port + 1;

Which is problematic because if this logic has to be changed then it will have to be changed in more than one place.

Before this PR the outcome of DefaultOnionServiceTarget(default_bind_port_onion) was communicated to InitBinds() (here) via the entry in options.onion_binds. Adding that entry however was removed in this PR to facilitate the new condition above: options.bind_on_any && options.vBinds.empty() && options.onion_binds.empty() (onion_binds needs to be empty now).

I think a smaller, less invasive change can replace commit c1de112 net: execute Discover() when bind=0.0.0.0 or :: is set:

[patch] call Discover() also if -bind=0.0.0.0[:port] is configured
--- i/src/init.cpp
+++ w/src/init.cpp
@@ -1995,13 +1995,20 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
                                     "for the automatically created Tor onion service."),
                                   onion_service_target.ToStringAddrPort()));
         }
         StartTorControl(onion_service_target);
     }
 
-    if (connOptions.bind_on_any) {
+    // If -bind=0.0.0.0[:port] or -whitebind=0.0.0.0[:port] was configured.
+    // Deliberately omit -bind=0.0.0.0[:port]=onion (if configured) because that
+    // listening address should not be advertised.
+    const bool some_binds_are_any{
+        std::ranges::any_of(connOptions.vBinds, [](const CService& b) { return b.IsBindAny(); }) ||
+        std::ranges::any_of(connOptions.vWhiteBinds, [](const NetWhitebindPermissions& w) { return w.m_service.IsBindAny(); })};
+
+    if (connOptions.bind_on_any || some_binds_are_any) {
         // Only add all IP addresses of the machine if we would be listening on
         // any address - 0.0.0.0 (IPv4) and :: (IPv6).
         Discover();
     }
 
     for (const auto& net : args.GetArgs("-whitelist")) {

Comment on lines 146 to 154
// If -bind= is provided with ":port" part, use that (first one if multiple are provided).
for (const std::string& bind_arg : gArgs.GetArgs("-bind")) {
constexpr uint16_t dummy_port = 0;
// maybe bind_arg has "=onion"
const std::string truncated_bind_arg = bind_arg.substr(0, bind_arg.rfind('='));

const std::optional<CService> bind_addr{Lookup(bind_arg, dummy_port, /*fAllowLookup=*/false)};
const std::optional<CService> bind_addr{Lookup(truncated_bind_arg, dummy_port, /*fAllowLookup=*/false)};
if (bind_addr.has_value() && bind_addr->GetPort() != dummy_port) return bind_addr->GetPort();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this change to GetListenPort() will produce unwanted results if the configuration goes like:
-bind=1.1.1.1:1111=onion -bind=2.2.2.2:2222. In that case, I guess it should return 2222 like before this change, but after it, it would return 1111.

if (index == std::string::npos) {
bind_addr = Lookup(bind_arg, default_bind_port, /*fAllowLookup=*/false);
if (bind_addr.has_value()) {
connOptions.bind_on_any |= bind_addr.value().IsBindAny();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the semantic of bind_on_any is to be changed then its comment deserves an update:

bitcoin/src/net.h

Lines 1072 to 1074 in 5b8046a

/// True if the user did not specify -bind= or -whitebind= and thus
/// we should bind on `0.0.0.0` (IPv4) and `::` (IPv6).
bool bind_on_any;

@b-l-u-e
Copy link

b-l-u-e commented Jun 4, 2025

I've been working on this same issue and my findings align well with @vasild feedback for a simpler approach.

Regarding the core fix: @vasild suggested approach is exactly right - the fix can be much simpler without the code duplication issues. Here's what I implemented successfully:
bitcoin/src/init.cpp

// We should discover addresses in two cases:
// 1. When no -bind or -whitebind is specified (bind_on_any is true)  
// 2. When any -bind address is 0.0.0.0 or ::
bool should_discover = connOptions.bind_on_any;
if (!should_discover) {
    for (const auto& bind_addr : connOptions.vBinds) {
        if (bind_addr.IsBindAny()) {
            should_discover = true;
            break;
        }
    }
}
if (should_discover) {
    Discover();
}

This avoids:

  • Modifying GetListenPort()
  • Code duplication with DefaultOnionServiceTarget()
  • Complex changes to bind_on_any semantics

Regarding the test fix: I also resolved the feature_bind_port_discover.py test failure. The issue was that the test framework overrides port settings, so the test expected port 31001 but nodes were actually using different ports (14251, 14252). The fix is to use the actual port from the node's network info rather than expecting a hardcoded port.

The test now passes successfully with this approach. Happy to share the complete working implementation if helpful.

@vasild
Copy link
Contributor

vasild commented Jun 12, 2025

@andremralves, what do you think about the above feedback? This PR is in "needs rebase" for about 2 months now, do you plan to work on it further?

@b-l-u-e, if there is no activity here for some time, then at some point it would make sense to open a separate PR with your patch, up to your judgement.

@yuvicc
Copy link
Contributor

yuvicc commented Jun 12, 2025

// We should discover addresses in two cases:
// 1. When no -bind or -whitebind is specified (bind_on_any is true)  
// 2. When any -bind address is 0.0.0.0 or ::
bool should_discover = connOptions.bind_on_any;
if (!should_discover) {
    for (const auto& bind_addr : connOptions.vBinds) {
        if (bind_addr.IsBindAny()) {
            should_discover = true;
            break;
        }
    }
}

ACK on the approach, if there's no activity open a separate pr as suggested by @vasild !

@yuvicc
Copy link
Contributor

yuvicc commented Jun 20, 2025

Summary

This PR can be closed as its taken up by #32757 with more better approach.

@maflcko
Copy link
Member

maflcko commented Jun 21, 2025

Closing for now, due to inactivity. Please leave a comment, if you want this reopened and want to work on this again. You may also create a fresh pull request, referencing this one.

@maflcko maflcko closed this Jun 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Functional tests: feature_bind_port_discover.py is failing Discover() will not run if listening on any address with an explicit bind=0.0.0.0