Skip to content

Commit 0cfc0cd

Browse files
committed
net: fix GetListenPort() to derive the proper port
`GetListenPort()` uses a simple logic: "if `-port=P` is given, then we must be listening on `P`, otherwise we must be listening on `8333`". This is however not true if `-bind=` has been provided with `:port` part or if `-whitebind=` has been provided. Thus, extend `GetListenPort()` to return the port from `-bind=` or `-whitebind=`, if any. Fixes bitcoin#20184 (cases 1. 2. 3. 5.)
1 parent f98cdcb commit 0cfc0cd

File tree

6 files changed

+307
-7
lines changed

6 files changed

+307
-7
lines changed

src/init.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -1689,6 +1689,10 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
16891689
connOptions.nMaxOutboundLimit = *opt_max_upload;
16901690
connOptions.m_peer_connect_timeout = peer_connect_timeout;
16911691

1692+
// Port to bind to if `-bind=addr` is provided without a `:port` suffix.
1693+
const uint16_t default_bind_port =
1694+
static_cast<uint16_t>(args.GetIntArg("-port", Params().GetDefaultPort()));
1695+
16921696
const auto BadPortWarning = [](const char* prefix, uint16_t port) {
16931697
return strprintf(_("%s request to listen on port %u. This port is considered \"bad\" and "
16941698
"thus it is unlikely that any Bitcoin Core peers connect to it. See "
@@ -1701,7 +1705,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
17011705
CService bind_addr;
17021706
const size_t index = bind_arg.rfind('=');
17031707
if (index == std::string::npos) {
1704-
if (Lookup(bind_arg, bind_addr, GetListenPort(), false)) {
1708+
if (Lookup(bind_arg, bind_addr, default_bind_port, /*fAllowLookup=*/false)) {
17051709
connOptions.vBinds.push_back(bind_addr);
17061710
if (IsBadPort(bind_addr.GetPort())) {
17071711
InitWarning(BadPortWarning("-bind", bind_addr.GetPort()));

src/net.cpp

+36-1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,31 @@ void CConnman::AddAddrFetch(const std::string& strDest)
126126

127127
uint16_t GetListenPort()
128128
{
129+
// If -bind= is provided with ":port" part, use that (first one if multiple are provided).
130+
for (const std::string& bind_arg : gArgs.GetArgs("-bind")) {
131+
CService bind_addr;
132+
constexpr uint16_t dummy_port = 0;
133+
134+
if (Lookup(bind_arg, bind_addr, dummy_port, /*fAllowLookup=*/false)) {
135+
if (bind_addr.GetPort() != dummy_port) {
136+
return bind_addr.GetPort();
137+
}
138+
}
139+
}
140+
141+
// Otherwise, if -whitebind= without NetPermissionFlags::NoBan is provided, use that
142+
// (-whitebind= is required to have ":port").
143+
for (const std::string& whitebind_arg : gArgs.GetArgs("-whitebind")) {
144+
NetWhitebindPermissions whitebind;
145+
bilingual_str error;
146+
if (NetWhitebindPermissions::TryParse(whitebind_arg, whitebind, error)) {
147+
if (!NetPermissions::HasFlag(whitebind.m_flags, NetPermissionFlags::NoBan)) {
148+
return whitebind.m_service.GetPort();
149+
}
150+
}
151+
}
152+
153+
// Otherwise, if -port= is provided, use that. Otherwise use the default port.
129154
return static_cast<uint16_t>(gArgs.GetIntArg("-port", Params().GetDefaultPort()));
130155
}
131156

@@ -221,7 +246,17 @@ std::optional<CAddress> GetLocalAddrForPeer(CNode *pnode)
221246
if (IsPeerAddrLocalGood(pnode) && (!addrLocal.IsRoutable() ||
222247
rng.randbits((GetnScore(addrLocal) > LOCAL_MANUAL) ? 3 : 1) == 0))
223248
{
224-
addrLocal.SetIP(pnode->GetAddrLocal());
249+
if (pnode->IsInboundConn()) {
250+
// For inbound connections, assume both the address and the port
251+
// as seen from the peer.
252+
addrLocal = CAddress{pnode->GetAddrLocal(), addrLocal.nServices};
253+
} else {
254+
// For outbound connections, assume just the address as seen from
255+
// the peer and leave the port in `addrLocal` as returned by
256+
// `GetLocalAddress()` above. The peer has no way to observe our
257+
// listening port when we have initiated the connection.
258+
addrLocal.SetIP(pnode->GetAddrLocal());
259+
}
225260
}
226261
if (addrLocal.IsRoutable() || gArgs.GetBoolArg("-addrmantest", false))
227262
{

src/net_processing.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -2711,6 +2711,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
27112711
LogPrint(BCLog::NET, "ProcessMessages: advertising address %s\n", addr.ToString());
27122712
PushAddress(*peer, addr, insecure_rand);
27132713
} else if (IsPeerAddrLocalGood(&pfrom)) {
2714+
// Override just the address with whatever the peer sees us as.
2715+
// Leave the port in addr as it was returned by GetLocalAddress()
2716+
// above, as this is an outbound connection and the peer cannot
2717+
// observe our listening port.
27142718
addr.SetIP(addrMe);
27152719
LogPrint(BCLog::NET, "ProcessMessages: advertising address %s\n", addr.ToString());
27162720
PushAddress(*peer, addr, insecure_rand);

src/test/net_tests.cpp

+186-5
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,23 @@
44

55
#include <chainparams.h>
66
#include <clientversion.h>
7+
#include <compat.h>
78
#include <cstdint>
89
#include <net.h>
10+
#include <net_processing.h>
911
#include <netaddress.h>
1012
#include <netbase.h>
13+
#include <netmessagemaker.h>
1114
#include <serialize.h>
1215
#include <span.h>
1316
#include <streams.h>
1417
#include <test/util/setup_common.h>
18+
#include <test/util/validation.h>
19+
#include <timedata.h>
1520
#include <util/strencodings.h>
1621
#include <util/string.h>
1722
#include <util/system.h>
23+
#include <validation.h>
1824
#include <version.h>
1925

2026
#include <boost/test/unit_test.hpp>
@@ -27,7 +33,7 @@
2733

2834
using namespace std::literals;
2935

30-
BOOST_FIXTURE_TEST_SUITE(net_tests, BasicTestingSetup)
36+
BOOST_FIXTURE_TEST_SUITE(net_tests, RegTestingSetup)
3137

3238
BOOST_AUTO_TEST_CASE(cnode_listen_port)
3339
{
@@ -607,15 +613,15 @@ BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test)
607613
// set up local addresses; all that's necessary to reproduce the bug is
608614
// that a normal IPv4 address is among the entries, but if this address is
609615
// !IsRoutable the undefined behavior is easier to trigger deterministically
616+
in_addr raw_addr;
617+
raw_addr.s_addr = htonl(0x7f000001);
618+
const CNetAddr mapLocalHost_entry = CNetAddr(raw_addr);
610619
{
611620
LOCK(g_maplocalhost_mutex);
612-
in_addr ipv4AddrLocal;
613-
ipv4AddrLocal.s_addr = 0x0100007f;
614-
CNetAddr addr = CNetAddr(ipv4AddrLocal);
615621
LocalServiceInfo lsi;
616622
lsi.nScore = 23;
617623
lsi.nPort = 42;
618-
mapLocalHost[addr] = lsi;
624+
mapLocalHost[mapLocalHost_entry] = lsi;
619625
}
620626

621627
// create a peer with an IPv4 address
@@ -646,8 +652,79 @@ BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test)
646652

647653
// suppress no-checks-run warning; if this test fails, it's by triggering a sanitizer
648654
BOOST_CHECK(1);
655+
656+
// Cleanup, so that we don't confuse other tests.
657+
{
658+
LOCK(g_maplocalhost_mutex);
659+
mapLocalHost.erase(mapLocalHost_entry);
660+
}
649661
}
650662

663+
BOOST_AUTO_TEST_CASE(get_local_addr_for_peer_port)
664+
{
665+
// Test that GetLocalAddrForPeer() properly selects the address to self-advertise:
666+
//
667+
// 1. GetLocalAddrForPeer() calls GetLocalAddress() which returns an address that is
668+
// not routable.
669+
// 2. GetLocalAddrForPeer() overrides the address with whatever the peer has told us
670+
// he sees us as.
671+
// 2.1. For inbound connections we must override both the address and the port.
672+
// 2.2. For outbound connections we must override only the address.
673+
674+
// Pretend that we bound to this port.
675+
const uint16_t bind_port = 20001;
676+
m_node.args->ForceSetArg("-bind", strprintf("3.4.5.6:%u", bind_port));
677+
678+
// Our address:port as seen from the peer, completely different from the above.
679+
in_addr peer_us_addr;
680+
peer_us_addr.s_addr = htonl(0x02030405);
681+
const CAddress peer_us{CService{peer_us_addr, 20002}, NODE_NETWORK};
682+
683+
// Create a peer with a routable IPv4 address (outbound).
684+
in_addr peer_out_in_addr;
685+
peer_out_in_addr.s_addr = htonl(0x01020304);
686+
CNode peer_out{/*id=*/0,
687+
/*nLocalServicesIn=*/NODE_NETWORK,
688+
/*sock=*/nullptr,
689+
/*addrIn=*/CAddress{CService{peer_out_in_addr, 8333}, NODE_NETWORK},
690+
/*nKeyedNetGroupIn=*/0,
691+
/*nLocalHostNonceIn=*/0,
692+
/*addrBindIn=*/CAddress{},
693+
/*addrNameIn=*/std::string{},
694+
/*conn_type_in=*/ConnectionType::OUTBOUND_FULL_RELAY,
695+
/*inbound_onion=*/false};
696+
peer_out.fSuccessfullyConnected = true;
697+
peer_out.SetAddrLocal(peer_us);
698+
699+
// Without the fix peer_us:8333 is chosen instead of the proper peer_us:bind_port.
700+
auto chosen_local_addr = GetLocalAddrForPeer(&peer_out);
701+
BOOST_REQUIRE(chosen_local_addr);
702+
const CService expected{peer_us_addr, bind_port};
703+
BOOST_CHECK(*chosen_local_addr == expected);
704+
705+
// Create a peer with a routable IPv4 address (inbound).
706+
in_addr peer_in_in_addr;
707+
peer_in_in_addr.s_addr = htonl(0x05060708);
708+
CNode peer_in{/*id=*/0,
709+
/*nLocalServicesIn=*/NODE_NETWORK,
710+
/*sock=*/nullptr,
711+
/*addrIn=*/CAddress{CService{peer_in_in_addr, 8333}, NODE_NETWORK},
712+
/*nKeyedNetGroupIn=*/0,
713+
/*nLocalHostNonceIn=*/0,
714+
/*addrBindIn=*/CAddress{},
715+
/*addrNameIn=*/std::string{},
716+
/*conn_type_in=*/ConnectionType::INBOUND,
717+
/*inbound_onion=*/false};
718+
peer_in.fSuccessfullyConnected = true;
719+
peer_in.SetAddrLocal(peer_us);
720+
721+
// Without the fix peer_us:8333 is chosen instead of the proper peer_us:peer_us.GetPort().
722+
chosen_local_addr = GetLocalAddrForPeer(&peer_in);
723+
BOOST_REQUIRE(chosen_local_addr);
724+
BOOST_CHECK(*chosen_local_addr == peer_us);
725+
726+
m_node.args->ForceSetArg("-bind", "");
727+
}
651728

652729
BOOST_AUTO_TEST_CASE(LimitedAndReachable_Network)
653730
{
@@ -728,4 +805,108 @@ BOOST_AUTO_TEST_CASE(LocalAddress_BasicLifecycle)
728805
BOOST_CHECK(!IsLocal(addr));
729806
}
730807

808+
BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message)
809+
{
810+
// Tests the following scenario:
811+
// * -bind=3.4.5.6:20001 is specified
812+
// * we make an outbound connection to a peer
813+
// * the peer reports he sees us as 2.3.4.5:20002 in the version message
814+
// (20002 is a random port assigned by our OS for the outgoing TCP connection,
815+
// we cannot accept connections to it)
816+
// * we should self-advertise to that peer as 2.3.4.5:20001
817+
818+
// Pretend that we bound to this port.
819+
const uint16_t bind_port = 20001;
820+
m_node.args->ForceSetArg("-bind", strprintf("3.4.5.6:%u", bind_port));
821+
m_node.args->ForceSetArg("-capturemessages", "1");
822+
823+
// Our address:port as seen from the peer - 2.3.4.5:20002 (different from the above).
824+
in_addr peer_us_addr;
825+
peer_us_addr.s_addr = htonl(0x02030405);
826+
const CService peer_us{peer_us_addr, 20002};
827+
828+
// Create a peer with a routable IPv4 address.
829+
in_addr peer_in_addr;
830+
peer_in_addr.s_addr = htonl(0x01020304);
831+
CNode peer{/*id=*/0,
832+
/*nLocalServicesIn=*/NODE_NETWORK,
833+
/*sock=*/nullptr,
834+
/*addrIn=*/CAddress{CService{peer_in_addr, 8333}, NODE_NETWORK},
835+
/*nKeyedNetGroupIn=*/0,
836+
/*nLocalHostNonceIn=*/0,
837+
/*addrBindIn=*/CAddress{},
838+
/*addrNameIn=*/std::string{},
839+
/*conn_type_in=*/ConnectionType::OUTBOUND_FULL_RELAY,
840+
/*inbound_onion=*/false};
841+
842+
const uint64_t services{NODE_NETWORK | NODE_WITNESS};
843+
const int64_t time{0};
844+
const CNetMsgMaker msg_maker{PROTOCOL_VERSION};
845+
846+
// Force CChainState::IsInitialBlockDownload() to return false.
847+
// Otherwise PushAddress() isn't called by PeerManager::ProcessMessage().
848+
TestChainState& chainstate =
849+
*static_cast<TestChainState*>(&m_node.chainman->ActiveChainstate());
850+
chainstate.JumpOutOfIbd();
851+
852+
m_node.peerman->InitializeNode(&peer);
853+
854+
std::atomic<bool> interrupt_dummy{false};
855+
std::chrono::microseconds time_received_dummy{0};
856+
857+
const auto msg_version =
858+
msg_maker.Make(NetMsgType::VERSION, PROTOCOL_VERSION, services, time, services, peer_us);
859+
CDataStream msg_version_stream{msg_version.data, SER_NETWORK, PROTOCOL_VERSION};
860+
861+
m_node.peerman->ProcessMessage(
862+
peer, NetMsgType::VERSION, msg_version_stream, time_received_dummy, interrupt_dummy);
863+
864+
const auto msg_verack = msg_maker.Make(NetMsgType::VERACK);
865+
CDataStream msg_verack_stream{msg_verack.data, SER_NETWORK, PROTOCOL_VERSION};
866+
867+
// Will set peer.fSuccessfullyConnected to true (necessary in SendMessages()).
868+
m_node.peerman->ProcessMessage(
869+
peer, NetMsgType::VERACK, msg_verack_stream, time_received_dummy, interrupt_dummy);
870+
871+
// Ensure that peer_us_addr:bind_port is sent to the peer.
872+
const CService expected{peer_us_addr, bind_port};
873+
bool sent{false};
874+
875+
const auto CaptureMessageOrig = CaptureMessage;
876+
CaptureMessage = [&sent, &expected](const CAddress& addr,
877+
const std::string& msg_type,
878+
Span<const unsigned char> data,
879+
bool is_incoming) -> void {
880+
if (!is_incoming && msg_type == "addr") {
881+
CDataStream s(data, SER_NETWORK, PROTOCOL_VERSION);
882+
std::vector<CAddress> addresses;
883+
884+
s >> addresses;
885+
886+
for (const auto& addr : addresses) {
887+
if (addr == expected) {
888+
sent = true;
889+
return;
890+
}
891+
}
892+
}
893+
};
894+
895+
{
896+
LOCK(peer.cs_sendProcessing);
897+
m_node.peerman->SendMessages(&peer);
898+
}
899+
900+
BOOST_CHECK(sent);
901+
902+
CaptureMessage = CaptureMessageOrig;
903+
chainstate.ResetIbd();
904+
m_node.args->ForceSetArg("-capturemessages", "0");
905+
m_node.args->ForceSetArg("-bind", "");
906+
// PeerManager::ProcessMessage() calls AddTimeData() which changes the internal state
907+
// in timedata.cpp and later confuses the test "timedata_tests/addtimedata". Thus reset
908+
// that state as it was before our test was run.
909+
TestOnlyResetTimeData();
910+
}
911+
731912
BOOST_AUTO_TEST_SUITE_END()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2020-2021 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""
6+
Test that the proper port is used for -externalip=
7+
"""
8+
9+
from test_framework.test_framework import BitcoinTestFramework, SkipTest
10+
from test_framework.util import assert_equal, p2p_port
11+
12+
# We need to bind to a routable address for this test to exercise the relevant code.
13+
# To set a routable address on the machine use:
14+
# Linux:
15+
# ifconfig lo:0 1.1.1.1/32 up # to set up
16+
# ifconfig lo:0 down # to remove it, after the test
17+
# FreeBSD:
18+
# ifconfig lo0 1.1.1.1/32 alias # to set up
19+
# ifconfig lo0 1.1.1.1 -alias # to remove it, after the test
20+
ADDR = '1.1.1.1'
21+
22+
# array of tuples [arguments, expected port in localaddresses]
23+
EXPECTED = [
24+
[['-externalip=2.2.2.2', '-port=30001'], 30001],
25+
[['-externalip=2.2.2.2', '-port=30002', f'-bind={ADDR}'], 30002],
26+
[['-externalip=2.2.2.2', f'-bind={ADDR}'], 'default_p2p_port'],
27+
[['-externalip=2.2.2.2', '-port=30003', f'-bind={ADDR}:30004'], 30004],
28+
[['-externalip=2.2.2.2', f'-bind={ADDR}:30005'], 30005],
29+
[['-externalip=2.2.2.2:30006', '-port=30007'], 30006],
30+
[['-externalip=2.2.2.2:30008', '-port=30009', f'-bind={ADDR}'], 30008],
31+
[['-externalip=2.2.2.2:30010', f'-bind={ADDR}'], 30010],
32+
[['-externalip=2.2.2.2:30011', '-port=30012', f'-bind={ADDR}:30013'], 30011],
33+
[['-externalip=2.2.2.2:30014', f'-bind={ADDR}:30015'], 30014],
34+
[['-externalip=2.2.2.2', '-port=30016', f'-bind={ADDR}:30017',
35+
f'-whitebind={ADDR}:30018'], 30017],
36+
[['-externalip=2.2.2.2', '-port=30019',
37+
f'-whitebind={ADDR}:30020'], 30020],
38+
]
39+
40+
class BindPortExternalIPTest(BitcoinTestFramework):
41+
def set_test_params(self):
42+
# Avoid any -bind= on the command line. Force the framework to avoid adding -bind=127.0.0.1.
43+
self.setup_clean_chain = True
44+
self.bind_to_localhost_only = False
45+
self.num_nodes = len(EXPECTED)
46+
self.extra_args = list(map(lambda e: e[0], EXPECTED))
47+
48+
def add_options(self, parser):
49+
parser.add_argument(
50+
"--ihave1111", action='store_true', dest="ihave1111",
51+
help=f"Run the test, assuming {ADDR} is configured on the machine",
52+
default=False)
53+
54+
def skip_test_if_missing_module(self):
55+
if not self.options.ihave1111:
56+
raise SkipTest(
57+
f"To run this test make sure that {ADDR} (a routable address) is assigned "
58+
"to one of the interfaces on this machine and rerun with --ihave1111")
59+
60+
def run_test(self):
61+
self.log.info("Test the proper port is used for -externalip=")
62+
for i in range(len(EXPECTED)):
63+
expected_port = EXPECTED[i][1]
64+
if expected_port == 'default_p2p_port':
65+
expected_port = p2p_port(i)
66+
found = False
67+
for local in self.nodes[i].getnetworkinfo()['localaddresses']:
68+
if local['address'] == '2.2.2.2':
69+
assert_equal(local['port'], expected_port)
70+
found = True
71+
break
72+
assert found
73+
74+
if __name__ == '__main__':
75+
BindPortExternalIPTest().main()

0 commit comments

Comments
 (0)