From 4d4789dffad55b96f1cb96b718cc6923f5344454 Mon Sep 17 00:00:00 2001 From: woltx <94266259+w0xlt@users.noreply.github.com> Date: Mon, 25 Aug 2025 20:22:33 -0700 Subject: [PATCH] net: Prevent node from binding to the same CService Currently, if the user inadvertently starts the node with duplicate bind options, such as `-bind=0.0.0.0 -bind=0.0.0.0`, it will cause a fatal error with the misleading message "Bitcoin Core is probably already running". This commit adds early validation to detect duplicate bindings across all binding configurations (-bind, -whitebind, and onion bindings) before attempting to bind. When duplicates are detected, the node terminates with a clear, specific error message: "Duplicate binding configuration for address . Please check your -bind, -bind=...=onion and -whitebind settings." The validation catches duplicates both within the same option type (e.g., `-bind=X -bind=X`) and across different types (e.g., `-bind=X -whitebind=Y@X`), helping users identify and fix configuration mistakes. --- src/init.cpp | 41 +++++++++++++++++++++++++++ test/functional/feature_bind_extra.py | 17 +++++++++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index b6b52e2cea5..2856b819643 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1233,6 +1233,40 @@ bool CheckHostPortOptions(const ArgsManager& args) { return true; } +/** + * @brief Checks for duplicate bindings across all binding configurations. + * + * @param[in] conn_options Connection options containing the binding vectors to check + * @return std::optional containing the first duplicate found, or std::nullopt if no duplicates + */ +static std::optional CheckBindingConflicts(const CConnman::Options& conn_options) +{ + std::set seen; + + // Check all whitelisted bindings + for (const auto& wb : conn_options.vWhiteBinds) { + if (!seen.insert(wb.m_service).second) { + return wb.m_service; + } + } + + // Check regular bindings + for (const auto& bind : conn_options.vBinds) { + if (!seen.insert(bind).second) { + return bind; + } + } + + // Check onion bindings + for (const auto& onion_bind : conn_options.onion_binds) { + if (!seen.insert(onion_bind).second) { + return onion_bind; + } + } + + return std::nullopt; +} + // A GUI user may opt to retry once with do_reindex set if there is a failure during chainstate initialization. // The function therefore has to support re-entry. static ChainstateLoadResult InitAndLoadChainstate( @@ -2142,6 +2176,13 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) connOptions.m_i2p_accept_incoming = args.GetBoolArg("-i2pacceptincoming", DEFAULT_I2P_ACCEPT_INCOMING); + if (auto conflict = CheckBindingConflicts(connOptions)) { + return InitError(strprintf( + _("Duplicate binding configuration for address %s. " + "Please check your -bind, -bind=...=onion and -whitebind settings."), + conflict->ToStringAddrPort())); + } + if (!node.connman->Start(scheduler, connOptions)) { return false; } diff --git a/test/functional/feature_bind_extra.py b/test/functional/feature_bind_extra.py index 6b53de188f4..d2f3daf93e8 100755 --- a/test/functional/feature_bind_extra.py +++ b/test/functional/feature_bind_extra.py @@ -3,10 +3,12 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ -Test starting bitcoind with -bind and/or -bind=...=onion and confirm -that bind happens on the expected ports. +Test starting bitcoind with -bind and/or -bind=...=onion, confirm that +it binds to the expected ports, and verify that duplicate or conflicting +-bind/-whitebind configurations are rejected with a descriptive error. """ +from itertools import combinations_with_replacement from test_framework.netutil import ( addr_to_hex, get_bind_addrs, @@ -14,13 +16,13 @@ from test_framework.netutil import ( from test_framework.test_framework import ( BitcoinTestFramework, ) +from test_framework.test_node import ErrorMatch from test_framework.util import ( assert_equal, p2p_port, rpc_port, ) - class BindExtraTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True @@ -87,5 +89,14 @@ class BindExtraTest(BitcoinTestFramework): binds = set(filter(lambda e: e[1] != rpc_port(i), binds)) assert_equal(binds, set(expected_services)) + self.stop_node(0) + + addr = "127.0.0.1:11012" + for opt1, opt2 in combinations_with_replacement([f"-bind={addr}", f"-bind={addr}=onion", f"-whitebind=noban@{addr}"], 2): + self.nodes[0].assert_start_raises_init_error( + [opt1, opt2], + "Error: Duplicate binding configuration", + match=ErrorMatch.PARTIAL_REGEX) + if __name__ == '__main__': BindExtraTest(__file__).main()