From 1d445c92493a245668a493f3e3e4b844ec757370 Mon Sep 17 00:00:00 2001 From: stratospher <44024636+stratospher@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:21:03 +0530 Subject: [PATCH 1/4] net: add option in CConman to disable v1 clearnet connections a boolean option `disable_v1conn_clearnet` is introduced in CConman which will (in a later commit) store if the user wishes to disable outbound v1 connections on IPV4 and IPV6 networks since they are unencrypted. this option is accessible outside CConman using `DisableV1OnClearnet()` function with the network we're trying to connect to passed as an argument. --- src/net.cpp | 5 +++++ src/net.h | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/net.cpp b/src/net.cpp index fc0edc1a5c1..31185da9c09 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2498,6 +2498,11 @@ bool CConnman::MultipleManualOrFullOutboundConns(Network net) const return m_network_conn_counts[net] > 1; } +bool CConnman::DisableV1OnClearnet(Network net) const +{ + return disable_v1conn_clearnet && (net == NET_IPV4 || net == NET_IPV6); +} + bool CConnman::MaybePickPreferredNetwork(std::optional& network) { std::array nets{NET_IPV4, NET_IPV6, NET_ONION, NET_I2P, NET_CJDNS}; diff --git a/src/net.h b/src/net.h index 9fdec52115e..d1e56f58c7f 100644 --- a/src/net.h +++ b/src/net.h @@ -1078,6 +1078,7 @@ public: bool m_i2p_accept_incoming; bool whitelist_forcerelay = DEFAULT_WHITELISTFORCERELAY; bool whitelist_relay = DEFAULT_WHITELISTRELAY; + bool disable_v1conn_clearnet = false; }; void Init(const Options& connOptions) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex, !m_total_bytes_sent_mutex) @@ -1115,6 +1116,7 @@ public: m_onion_binds = connOptions.onion_binds; whitelist_forcerelay = connOptions.whitelist_forcerelay; whitelist_relay = connOptions.whitelist_relay; + disable_v1conn_clearnet = connOptions.disable_v1conn_clearnet; } CConnman(uint64_t seed0, uint64_t seed1, AddrMan& addrman, const NetGroupManager& netgroupman, @@ -1272,6 +1274,9 @@ public: bool MultipleManualOrFullOutboundConns(Network net) const EXCLUSIVE_LOCKS_REQUIRED(m_nodes_mutex); + /* Returns true if outbound v1 connections need to be disabled on IPV4/IPV6 network. */ + bool DisableV1OnClearnet(Network net) const; + private: struct ListenSocket { public: @@ -1591,6 +1596,13 @@ private: */ bool whitelist_relay; + /** + * option for disabling outbound v1 connections on IPV4 and IPV6. + * outbound connections on IPV4/IPV6 need to be v2 connections. + * outbound connections on Tor/I2P/CJDNS can be v1 or v2 connections. + */ + bool disable_v1conn_clearnet; + /** * Mutex protecting m_i2p_sam_sessions. */ From d166ed79e5012aae7e64f7e768271c11fb68cdf7 Mon Sep 17 00:00:00 2001 From: stratospher <44024636+stratospher@users.noreply.github.com> Date: Tue, 30 Jul 2024 10:49:02 +0530 Subject: [PATCH 2/4] init: add -v2onlyclearnet config option if this option is set by the user, v1 connections on unencrypted networks like IPV4/IPV6 will be disallowed. Only users with real need are recommended to turn this on because it could risk network partitioning in the unlikely scenario that everyone turns it on. --- src/init.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/init.cpp b/src/init.cpp index f35a547c929..73dc01bbbce 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -548,6 +548,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) argsman.AddArg("-i2pacceptincoming", strprintf("Whether to accept inbound I2P connections (default: %i). Ignored if -i2psam is not set. Listening for inbound I2P connections is done through the SAM proxy, not by binding to a local address and port.", DEFAULT_I2P_ACCEPT_INCOMING), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-onlynet=", "Make automatic outbound connections only to network (" + Join(GetNetworkNames(), ", ") + "). Inbound and manual connections are not affected by this option. It can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-v2transport", strprintf("Support v2 transport (default: %u)", DEFAULT_V2_TRANSPORT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-v2onlyclearnet", strprintf("Disallow outbound v1 connections on IPV4/IPV6 (default: %u). Enable this option only if you really need it. Use -listen=0 to disable inbound connections since they can be unencrypted.", false), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-txreconciliation", strprintf("Enable transaction reconciliations per BIP 330 (default: %d)", DEFAULT_TXRECONCILIATION_ENABLE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION); @@ -959,6 +960,8 @@ bool AppInitParameterInteraction(const ArgsManager& args) // Signal NODE_P2P_V2 if BIP324 v2 transport is enabled. if (args.GetBoolArg("-v2transport", DEFAULT_V2_TRANSPORT)) { g_local_services = ServiceFlags(g_local_services | NODE_P2P_V2); + } else if (args.GetBoolArg("-v2onlyclearnet", false)) { + return InitError(_("Cannot set -v2onlyclearnet to true when v2transport is disabled.")); } // Signal NODE_COMPACT_FILTERS if peerblockfilters and basic filters index are both enabled. @@ -1918,6 +1921,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) connOptions.m_peer_connect_timeout = peer_connect_timeout; connOptions.whitelist_forcerelay = args.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY); connOptions.whitelist_relay = args.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY); + connOptions.disable_v1conn_clearnet = args.GetBoolArg("-v2onlyclearnet", false); // Port to bind to if `-bind=addr` is provided without a `:port` suffix. const uint16_t default_bind_port = From 19d1bc2328a85718c7496715d69c9adc94e69db7 Mon Sep 17 00:00:00 2001 From: stratospher <44024636+stratospher@users.noreply.github.com> Date: Fri, 23 Aug 2024 12:57:42 +0530 Subject: [PATCH 3/4] net: disable v1 connections, reconnections on clearnet if `-v2onlyclearnet` is turned on, - v1 addresses from addrman aren't selected and manual connections aren't attempted for outbound connections if it's from IPV4/IPV6 networks. - v1 downgrade mechainm is not attempted if v2 connection wasn't successful --- src/net.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/net.cpp b/src/net.cpp index 31185da9c09..216d0489df1 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -463,6 +463,9 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo std::unique_ptr i2p_transient_session; for (auto& target_addr: connect_to) { + if (DisableV1OnClearnet(target_addr.GetNetClass()) && !use_v2transport) { + continue; + } if (target_addr.IsValid()) { const bool use_proxy{GetProxy(target_addr.GetNetwork(), proxy)}; bool proxyConnectionFailed = false; @@ -1935,7 +1938,7 @@ void CConnman::DisconnectNodes() // Add to reconnection list if appropriate. We don't reconnect right here, because // the creation of a connection is a blocking operation (up to several seconds), // and we don't want to hold up the socket handler thread for that long. - if (network_active && pnode->m_transport->ShouldReconnectV1()) { + if (network_active && pnode->m_transport->ShouldReconnectV1() && !DisableV1OnClearnet(pnode->addr.GetNetClass())) { reconnections_to_add.push_back({ .addr_connect = pnode->addr, .grant = std::move(pnode->grantOutbound), From 27e90008835a4c980447d97e80910742e041dfaf Mon Sep 17 00:00:00 2001 From: stratospher <44024636+stratospher@users.noreply.github.com> Date: Fri, 6 Sep 2024 18:51:05 +0530 Subject: [PATCH 4/4] test: Check that v1 connections to clearnet peers don't work when `-v2onlyclearnet` is turned on: - v1 connections to clearnet peers don't work - v2 connections to clearnet peers work - v1 conneections to tor/i2p/cjdns peer works a proxy is used because otherwise NET_UNROUTABLE is the default network in the tests. --- test/functional/p2p_v2_encrypted.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/functional/p2p_v2_encrypted.py b/test/functional/p2p_v2_encrypted.py index 3e8ce09d24d..acb5bf0c226 100755 --- a/test/functional/p2p_v2_encrypted.py +++ b/test/functional/p2p_v2_encrypted.py @@ -18,8 +18,10 @@ from test_framework.util import ( assert_equal, assert_greater_than, check_node_connections, + p2p_port, ) from test_framework.crypto.chacha20 import REKEY_INTERVAL +from test_framework.socks5 import Socks5Configuration, Socks5Server class P2PEncrypted(BitcoinTestFramework): @@ -129,6 +131,27 @@ class P2PEncrypted(BitcoinTestFramework): assert_equal(node0.getpeerinfo()[-1]["transport_protocol_type"], "v1") check_node_connections(node=node0, num_in=1, num_out=0) + conf = Socks5Configuration() + conf.auth = True + conf.unauth = True + conf.addr = ('127.0.0.1', p2p_port(self.num_nodes)) + conf.keep_alive = True + proxy = Socks5Server(conf) + proxy.start() + args = ['-listen', f'-proxy={conf.addr[0]}:{conf.addr[1]}', '-proxyrandomize=0', '-v2onlyclearnet=1', '-v2transport=1'] + self.restart_node(0, extra_args=args) + self.log.info("Test -v2onlyclearnet=1 behaviour") + self.log.info("Check that outbound v2 connection to an ipv4 peer is successful") + node0.addnode("15.61.23.23:1234", "onetry", True) + assert_equal(node0.getpeerinfo()[-1]["addr"], "15.61.23.23:1234") + self.log.info("Check that outbound v1 connection to an ipv4 peer is unsuccessful") + node0.addnode("8.8.8.8:1234", "onetry", False) + assert all(peer["addr"] != "8.8.8.8:1234" for peer in node0.getpeerinfo()) + self.log.info("Check that outbound v1 connection to an onion peer is successful") + addr = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:8333" + node0.addnode(addr, "onetry", False) + assert_equal(node0.getpeerinfo()[-1]["addr"], addr) + if __name__ == '__main__': P2PEncrypted(__file__).main()