diff --git a/src/init.cpp b/src/init.cpp index bc882e498f8..bc457e786ca 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1825,7 +1825,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) for (const std::string& strAddr : args.GetArgs("-externalip")) { const std::optional addrLocal{Lookup(strAddr, GetListenPort(), fNameLookup)}; if (addrLocal.has_value() && addrLocal->IsValid()) - AddLocal(addrLocal.value(), LOCAL_MANUAL); + AddLocal(addrLocal.value(), LOCAL_MANUAL, /*add_even_if_unreachable=*/true); else return InitError(ResolveErrMsg("externalip", strAddr)); } diff --git a/src/net.cpp b/src/net.cpp index ba7e59db1f0..73a062fe933 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -275,7 +275,7 @@ void ClearLocal() } // learn a new local address -bool AddLocal(const CService& addr_, int nScore) +bool AddLocal(const CService& addr_, int nScore, bool add_even_if_unreachable) { CService addr{MaybeFlipIPv6toCJDNS(addr_)}; @@ -285,7 +285,7 @@ bool AddLocal(const CService& addr_, int nScore) if (!fDiscover && nScore < LOCAL_MANUAL) return false; - if (!g_reachable_nets.Contains(addr)) + if (!g_reachable_nets.Contains(addr) && !add_even_if_unreachable) return false; if (fLogIPs) { @@ -305,9 +305,9 @@ bool AddLocal(const CService& addr_, int nScore) return true; } -bool AddLocal(const CNetAddr &addr, int nScore) +bool AddLocal(const CNetAddr& addr, int nScore, bool add_even_if_unreachable) { - return AddLocal(CService(addr, GetListenPort()), nScore); + return AddLocal(CService(addr, GetListenPort()), nScore, add_even_if_unreachable); } void RemoveLocal(const CService& addr) diff --git a/src/net.h b/src/net.h index d0cfe7c9ac3..e7a818f0c45 100644 --- a/src/net.h +++ b/src/net.h @@ -164,8 +164,8 @@ enum std::optional GetLocalAddrForPeer(CNode& node); void ClearLocal(); -bool AddLocal(const CService& addr, int nScore = LOCAL_NONE); -bool AddLocal(const CNetAddr& addr, int nScore = LOCAL_NONE); +bool AddLocal(const CService& addr, int nScore = LOCAL_NONE, bool add_even_if_unreachable = false); +bool AddLocal(const CNetAddr& addr, int nScore = LOCAL_NONE, bool add_even_if_unreachable = false); void RemoveLocal(const CService& addr); bool SeenLocal(const CService& addr); bool IsLocal(const CService& addr); diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index a2b647509a9..872cc0a02ef 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -1626,4 +1626,47 @@ BOOST_AUTO_TEST_CASE(private_broadcast_version_does_not_update_addrman_services) m_node.peerman->FinalizeNode(node); } +BOOST_AUTO_TEST_CASE(addlocal_onlynet_externalip) +{ + // Test that `-externalip` addresses bypass `-onlynet`, but score alone does + // not. + + CAddress addr_onion; + BOOST_REQUIRE(addr_onion.SetSpecial("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion")); + BOOST_REQUIRE(addr_onion.IsValid()); + BOOST_REQUIRE(addr_onion.IsTor()); + + const auto reachable_nets_at_start{g_reachable_nets.All()}; + const bool discover_orig{fDiscover}; + + // Simulate using -onlynet=ipv4 -externalip= + g_reachable_nets.RemoveAll(); + g_reachable_nets.Add(NET_IPV4); + fDiscover = false; + + // Now AddLocal with a non-manual score should fail for an unreachable network. + BOOST_CHECK(!AddLocal(addr_onion, LOCAL_BIND)); + BOOST_CHECK(!IsLocal(addr_onion)); + + BOOST_CHECK(!AddLocal(addr_onion, LOCAL_MANUAL)); + BOOST_CHECK(!IsLocal(addr_onion)); + + // Whereas AddLocal for -externalip should succeed. + BOOST_CHECK(AddLocal(addr_onion, LOCAL_MANUAL, /*add_even_if_unreachable=*/true)); + BOOST_CHECK(IsLocal(addr_onion)); + + // Normal AddLocal on a reachable network still works. + const CNetAddr addr_ipv4{LookupHost("1.2.3.4", false).value()}; + BOOST_CHECK(AddLocal(addr_ipv4, LOCAL_MANUAL)); + BOOST_CHECK(IsLocal(CService{addr_ipv4, GetListenPort()})); + + RemoveLocal(CService{addr_ipv4, GetListenPort()}); + RemoveLocal(addr_onion); + g_reachable_nets.RemoveAll(); + for (const auto& net : reachable_nets_at_start) { + g_reachable_nets.Add(net); + } + fDiscover = discover_orig; +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/test/functional/p2p_addr_selfannouncement.py b/test/functional/p2p_addr_selfannouncement.py index 631ecb4c00e..01221332511 100755 --- a/test/functional/p2p_addr_selfannouncement.py +++ b/test/functional/p2p_addr_selfannouncement.py @@ -23,6 +23,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_greater_than IP_TO_ANNOUNCE = "42.42.42.42" +ONION_ADDR = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion" ONE_DAY = 60 * 60 * 24 @@ -77,6 +78,7 @@ class AddrSelfAnnouncementTest(BitcoinTestFramework): self.self_announcement_test(outbound=False, addrv2=True) self.self_announcement_test(outbound=True, addrv2=False) self.self_announcement_test(outbound=True, addrv2=True) + self.test_externalip_bypasses_onlynet() @staticmethod def inbound_connection_open_assertions(addr_receiver): @@ -150,5 +152,17 @@ class AddrSelfAnnouncementTest(BitcoinTestFramework): self.nodes[0].disconnect_p2ps() + def test_externalip_bypasses_onlynet(self): + self.log.info("Test that -externalip onion is advertised despite -onlynet=ipv4") + self.restart_node(0, extra_args=["-onlynet=ipv4", f"-externalip={ONION_ADDR}"]) + + netinfo = self.nodes[0].getnetworkinfo() + + onion_net = next(n for n in netinfo["networks"] if n["name"] == "onion") + assert not onion_net["reachable"], "onion should not be reachable under -onlynet=ipv4" + + addrs = [a["address"] for a in netinfo["localaddresses"]] + assert ONION_ADDR in addrs, f"onion address missing from localaddresses: {addrs}" + if __name__ == '__main__': AddrSelfAnnouncementTest(__file__).main()