From 38f900290cc3a839e99bef13474d35e1c02e6b0d Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Mon, 31 May 2021 17:18:49 +0200 Subject: [PATCH 1/7] net: change assumed I2P port to 0 * When accepting an I2P connection, assume the peer has port 0 instead of the default 8333 (for mainnet). It is not being sent to us, so we must assume something. * When deriving our own I2P listen CService use port 0 instead of the default 8333 (for mainnet). So that we later advertise it to peers with port 0. In the I2P protocol SAM 3.1 and older (we use 3.1) ports are not used, so they are irrelevant. However in SAM 3.2 and newer ports are used and from the point of view of SAM 3.2, a peer using SAM 3.1 seems to have specified port=0. --- src/i2p.cpp | 4 ++-- src/init.cpp | 2 +- src/netaddress.h | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/i2p.cpp b/src/i2p.cpp index 2ae164633b..dbd4d46baa 100644 --- a/src/i2p.cpp +++ b/src/i2p.cpp @@ -159,7 +159,7 @@ bool Session::Accept(Connection& conn) const std::string& peer_dest = conn.sock->RecvUntilTerminator('\n', MAX_WAIT_FOR_IO, *m_interrupt, MAX_MSG_SIZE); - conn.peer = CService(DestB64ToAddr(peer_dest), Params().GetDefaultPort()); + conn.peer = CService(DestB64ToAddr(peer_dest), I2P_SAM31_PORT); return true; } @@ -366,7 +366,7 @@ void Session::CreateIfNotCreatedAlready() SendRequestAndGetReply(*sock, strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s", session_id, private_key_b64)); - m_my_addr = CService(DestBinToAddr(MyDestination()), Params().GetDefaultPort()); + m_my_addr = CService(DestBinToAddr(MyDestination()), I2P_SAM31_PORT); m_session_id = session_id; m_control_sock = std::move(sock); diff --git a/src/init.cpp b/src/init.cpp index ae96f510bc..7dc822bcf6 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -442,7 +442,7 @@ void SetupServerArgs(ArgsManager& argsman) 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("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-port=", strprintf("Listen for connections on . Nodes not using the default ports (default: %u, testnet: %u, signet: %u, regtest: %u) are unlikely to get incoming connections.", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + argsman.AddArg("-port=", strprintf("Listen for connections on . Nodes not using the default ports (default: %u, testnet: %u, signet: %u, regtest: %u) are unlikely to get incoming connections. Not relevant for I2P (see doc/i2p.md).", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-proxy=", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-seednode=", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); diff --git a/src/netaddress.h b/src/netaddress.h index dd47ab5749..d504996347 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -112,6 +112,9 @@ static constexpr size_t ADDR_CJDNS_SIZE = 16; /// Size of "internal" (NET_INTERNAL) address (in bytes). static constexpr size_t ADDR_INTERNAL_SIZE = 10; +/// SAM 3.1 and earlier do not support specifying ports and force the port to 0. +static constexpr uint16_t I2P_SAM31_PORT{0}; + /** * Network address. */ From aeac3bce3ead1f24ca782079ef0defa86fd8cb98 Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Mon, 31 May 2021 17:26:02 +0200 Subject: [PATCH 2/7] net: change I2P seeds' ports to 0 --- contrib/seeds/nodes_main.txt | 16 ++++++++-------- src/chainparamsseeds.h | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/contrib/seeds/nodes_main.txt b/contrib/seeds/nodes_main.txt index a62150a930..f7bfb6eb0a 100644 --- a/contrib/seeds/nodes_main.txt +++ b/contrib/seeds/nodes_main.txt @@ -678,11 +678,11 @@ vi5bnbxkleeqi6hfccjochnn65lcxlfqs4uwgmhudph554zibiusqnad.onion:8333 xqt25cobm5zqucac3634zfght72he6u3eagfyej5ellbhcdgos7t2had.onion:8333 # manually added 2021-05 for minimal i2p bootstrap support -72l3ucjkuscrbiiepoehuwqgknyzgo7zuix5ty4puwrkyhtmnsga.b32.i2p:8333 -c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p:8333 -gehtac45oaghz54ypyopim64mql7oad2bqclla74l6tfeolzmodq.b32.i2p:8333 -h3r6bkn46qxftwja53pxiykntegfyfjqtnzbm6iv6r5mungmqgmq.b32.i2p:8333 -hnbbyjpxx54623l555sta7pocy3se4sdgmuebi5k6reesz5rjp6q.b32.i2p:8333 -pjs7or2ctvteeo5tu4bwyrtydeuhqhvdprtujn4daxr75jpebjxa.b32.i2p:8333 -wwbw7nqr3ahkqv62cuqfwgtneekvvpnuc4i4f6yo7tpoqjswvcwa.b32.i2p:8333 -zsxwyo6qcn3chqzwxnseusqgsnuw3maqnztkiypyfxtya4snkoka.b32.i2p:8333 +72l3ucjkuscrbiiepoehuwqgknyzgo7zuix5ty4puwrkyhtmnsga.b32.i2p:0 +c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p:0 +gehtac45oaghz54ypyopim64mql7oad2bqclla74l6tfeolzmodq.b32.i2p:0 +h3r6bkn46qxftwja53pxiykntegfyfjqtnzbm6iv6r5mungmqgmq.b32.i2p:0 +hnbbyjpxx54623l555sta7pocy3se4sdgmuebi5k6reesz5rjp6q.b32.i2p:0 +pjs7or2ctvteeo5tu4bwyrtydeuhqhvdprtujn4daxr75jpebjxa.b32.i2p:0 +wwbw7nqr3ahkqv62cuqfwgtneekvvpnuc4i4f6yo7tpoqjswvcwa.b32.i2p:0 +zsxwyo6qcn3chqzwxnseusqgsnuw3maqnztkiypyfxtya4snkoka.b32.i2p:0 diff --git a/src/chainparamsseeds.h b/src/chainparamsseeds.h index 08587e2b37..a22529c386 100644 --- a/src/chainparamsseeds.h +++ b/src/chainparamsseeds.h @@ -683,14 +683,14 @@ static const uint8_t chainparams_seed_main[] = { 0x04,0x20,0x98,0xc6,0x44,0x27,0x90,0x41,0xa6,0x98,0xf9,0x25,0x6c,0x59,0x0f,0x06,0x6d,0x44,0x59,0x0e,0xb2,0x46,0xb0,0xa4,0x37,0x88,0x69,0x8f,0xc1,0x32,0xcd,0x9f,0x15,0xd7,0x20,0x8d, 0x04,0x20,0xaa,0x3a,0x16,0x86,0xea,0x59,0x09,0x04,0x78,0xe5,0x10,0x92,0xe1,0x1d,0xad,0xf7,0x56,0x2b,0xac,0xb0,0x97,0x29,0x63,0x30,0xf4,0x1b,0xcf,0xde,0xf3,0x28,0x0a,0x29,0x20,0x8d, 0x04,0x20,0xbc,0x27,0xae,0x89,0xc1,0x67,0x73,0x0a,0x08,0x02,0xdf,0xb7,0xcc,0x94,0xc7,0x9f,0xf4,0x72,0x7a,0x9b,0x20,0x0c,0x5c,0x11,0x3d,0x22,0xd6,0x13,0x88,0x66,0x74,0xbf,0x20,0x8d, - 0x05,0x20,0xfe,0x97,0xba,0x09,0x2a,0xa4,0x85,0x10,0xa1,0x04,0x7b,0x88,0x7a,0x5a,0x06,0x53,0x71,0x93,0x3b,0xf9,0xa2,0x2f,0xd9,0xe3,0x8f,0xa5,0xa2,0xac,0x1e,0x6c,0x6c,0x8c,0x20,0x8d, - 0x05,0x20,0x17,0x0c,0x56,0xce,0x72,0xa5,0xa0,0xe6,0x23,0x06,0xa3,0xc7,0x08,0x43,0x18,0xee,0x3a,0x46,0x35,0x5d,0x17,0xf6,0x78,0x96,0xa0,0x9c,0x51,0xef,0xbe,0x23,0xfd,0x71,0x20,0x8d, - 0x05,0x20,0x31,0x0f,0x30,0x0b,0x9d,0x70,0x0c,0x7c,0xf7,0x98,0x7e,0x1c,0xf4,0x33,0xdc,0x64,0x17,0xf7,0x00,0x7a,0x0c,0x04,0xb5,0x83,0xfc,0x5f,0xa6,0x52,0x39,0x79,0x63,0x87,0x20,0x8d, - 0x05,0x20,0x3e,0xe3,0xe0,0xa9,0xbc,0xf4,0x2e,0x59,0xd9,0x20,0xee,0xdf,0x74,0x61,0x4d,0x99,0x0c,0x5c,0x15,0x30,0x9b,0x72,0x16,0x79,0x15,0xf4,0x7a,0xca,0x34,0xcc,0x81,0x99,0x20,0x8d, - 0x05,0x20,0x3b,0x42,0x1c,0x25,0xf7,0xbf,0x79,0xed,0x6d,0x7d,0xef,0x65,0x30,0x7d,0xee,0x16,0x37,0x22,0x72,0x43,0x33,0x28,0x40,0xa3,0xaa,0xf4,0x48,0x49,0x67,0xb1,0x4b,0xfd,0x20,0x8d, - 0x05,0x20,0x7a,0x65,0xf7,0x47,0x42,0x9d,0x66,0x42,0x3b,0xb3,0xa7,0x03,0x6c,0x46,0x78,0x19,0x28,0x78,0x1e,0xa3,0x7c,0x67,0x44,0xb7,0x83,0x05,0xe3,0xfe,0xa5,0xe4,0x0a,0x6e,0x20,0x8d, - 0x05,0x20,0xb5,0x83,0x6f,0xb6,0x11,0xd8,0x0e,0xa8,0x57,0xda,0x15,0x20,0x5b,0x1a,0x6d,0x21,0x15,0x5a,0xbd,0xb4,0x17,0x11,0xc2,0xfb,0x0e,0xfc,0xde,0xe8,0x26,0x56,0xa8,0xac,0x20,0x8d, - 0x05,0x20,0xcc,0xaf,0x6c,0x3b,0xd0,0x13,0x76,0x23,0xc3,0x36,0xbb,0x64,0x4a,0x4a,0x06,0x93,0x69,0x6d,0xb0,0x10,0x6e,0x66,0xa4,0x61,0xf8,0x2d,0xe7,0x80,0x72,0x4d,0x53,0x94,0x20,0x8d, + 0x05,0x20,0xfe,0x97,0xba,0x09,0x2a,0xa4,0x85,0x10,0xa1,0x04,0x7b,0x88,0x7a,0x5a,0x06,0x53,0x71,0x93,0x3b,0xf9,0xa2,0x2f,0xd9,0xe3,0x8f,0xa5,0xa2,0xac,0x1e,0x6c,0x6c,0x8c,0x00,0x00, + 0x05,0x20,0x17,0x0c,0x56,0xce,0x72,0xa5,0xa0,0xe6,0x23,0x06,0xa3,0xc7,0x08,0x43,0x18,0xee,0x3a,0x46,0x35,0x5d,0x17,0xf6,0x78,0x96,0xa0,0x9c,0x51,0xef,0xbe,0x23,0xfd,0x71,0x00,0x00, + 0x05,0x20,0x31,0x0f,0x30,0x0b,0x9d,0x70,0x0c,0x7c,0xf7,0x98,0x7e,0x1c,0xf4,0x33,0xdc,0x64,0x17,0xf7,0x00,0x7a,0x0c,0x04,0xb5,0x83,0xfc,0x5f,0xa6,0x52,0x39,0x79,0x63,0x87,0x00,0x00, + 0x05,0x20,0x3e,0xe3,0xe0,0xa9,0xbc,0xf4,0x2e,0x59,0xd9,0x20,0xee,0xdf,0x74,0x61,0x4d,0x99,0x0c,0x5c,0x15,0x30,0x9b,0x72,0x16,0x79,0x15,0xf4,0x7a,0xca,0x34,0xcc,0x81,0x99,0x00,0x00, + 0x05,0x20,0x3b,0x42,0x1c,0x25,0xf7,0xbf,0x79,0xed,0x6d,0x7d,0xef,0x65,0x30,0x7d,0xee,0x16,0x37,0x22,0x72,0x43,0x33,0x28,0x40,0xa3,0xaa,0xf4,0x48,0x49,0x67,0xb1,0x4b,0xfd,0x00,0x00, + 0x05,0x20,0x7a,0x65,0xf7,0x47,0x42,0x9d,0x66,0x42,0x3b,0xb3,0xa7,0x03,0x6c,0x46,0x78,0x19,0x28,0x78,0x1e,0xa3,0x7c,0x67,0x44,0xb7,0x83,0x05,0xe3,0xfe,0xa5,0xe4,0x0a,0x6e,0x00,0x00, + 0x05,0x20,0xb5,0x83,0x6f,0xb6,0x11,0xd8,0x0e,0xa8,0x57,0xda,0x15,0x20,0x5b,0x1a,0x6d,0x21,0x15,0x5a,0xbd,0xb4,0x17,0x11,0xc2,0xfb,0x0e,0xfc,0xde,0xe8,0x26,0x56,0xa8,0xac,0x00,0x00, + 0x05,0x20,0xcc,0xaf,0x6c,0x3b,0xd0,0x13,0x76,0x23,0xc3,0x36,0xbb,0x64,0x4a,0x4a,0x06,0x93,0x69,0x6d,0xb0,0x10,0x6e,0x66,0xa4,0x61,0xf8,0x2d,0xe7,0x80,0x72,0x4d,0x53,0x94,0x00,0x00, }; static const uint8_t chainparams_seed_test[] = { From 1f096f091ebd88efb18154b8894a38122c39624f Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Mon, 31 May 2021 17:30:18 +0200 Subject: [PATCH 3/7] net: distinguish default port per network Change `CChainParams::GetDefaultPort()` to return 0 if the network is I2P. --- src/chainparams.h | 11 +++++++++++ src/net.cpp | 8 +++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/chainparams.h b/src/chainparams.h index 5c2351eea6..4faa6f8d06 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -8,11 +8,13 @@ #include #include +#include #include #include #include #include +#include #include typedef std::map MapCheckpoints; @@ -80,6 +82,15 @@ public: const Consensus::Params& GetConsensus() const { return consensus; } const CMessageHeader::MessageStartChars& MessageStart() const { return pchMessageStart; } uint16_t GetDefaultPort() const { return nDefaultPort; } + uint16_t GetDefaultPort(Network net) const + { + return net == NET_I2P ? I2P_SAM31_PORT : GetDefaultPort(); + } + uint16_t GetDefaultPort(const std::string& addr) const + { + CNetAddr a; + return a.SetSpecial(addr) ? GetDefaultPort(a.GetNetwork()) : GetDefaultPort(); + } const CBlock& GenesisBlock() const { return genesis; } /** Default value for -checkmempool and -checkblockindex argument */ diff --git a/src/net.cpp b/src/net.cpp index 60059249ed..a6f439674f 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -402,7 +402,8 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo pszDest ? 0.0 : (double)(GetAdjustedTime() - addrConnect.nTime)/3600.0); // Resolve - const uint16_t default_port{Params().GetDefaultPort()}; + const uint16_t default_port{pszDest != nullptr ? Params().GetDefaultPort(pszDest) : + Params().GetDefaultPort()}; if (pszDest) { std::vector resolved; if (Lookup(pszDest, resolved, default_port, fNameLookup && !HaveNameProxy(), 256) && !resolved.empty()) { @@ -2059,8 +2060,9 @@ void CConnman::ThreadOpenConnections(const std::vector connect) // from advertising themselves as a service on another host and // port, causing a DoS attack as nodes around the network attempt // to connect to it fruitlessly. - if (addr.GetPort() != Params().GetDefaultPort() && nTries < 50) + if (addr.GetPort() != Params().GetDefaultPort(addr.GetNetwork()) && nTries < 50) { continue; + } addrConnect = addr; break; @@ -2123,7 +2125,7 @@ std::vector CConnman::GetAddedNodeInfo() const } for (const std::string& strAddNode : lAddresses) { - CService service(LookupNumeric(strAddNode, Params().GetDefaultPort())); + CService service(LookupNumeric(strAddNode, Params().GetDefaultPort(strAddNode))); AddedNodeInfo addedNode{strAddNode, CService(), false, false}; if (service.IsValid()) { // strAddNode is an IP:port From 4f432bd738c420512a86a51ab3e00323f396b89e Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Mon, 31 May 2021 18:07:40 +0200 Subject: [PATCH 4/7] net: do not connect to I2P hosts on port!=0 When connecting to an I2P host we don't specify destination port and it is being forced to 0 by the SAM 3.1 proxy, so if we connect to the same host on two different ports, that would be actually two connections to the same service (listening on port 0). Fixes https://github.com/bitcoin/bitcoin/issues/21389 --- src/i2p.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/i2p.cpp b/src/i2p.cpp index dbd4d46baa..5e7e42fb77 100644 --- a/src/i2p.cpp +++ b/src/i2p.cpp @@ -172,6 +172,13 @@ bool Session::Accept(Connection& conn) bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error) { + // Refuse connecting to arbitrary ports. We don't specify any destination port to the SAM proxy + // when connecting (SAM 3.1 does not use ports) and it forces/defaults it to I2P_SAM31_PORT. + if (to.GetPort() != I2P_SAM31_PORT) { + proxy_error = false; + return false; + } + proxy_error = true; std::string session_id; From 41cda9d075ebcab1dbb950160ebe9d0ba7b5745e Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Wed, 23 Jun 2021 10:57:28 +0200 Subject: [PATCH 5/7] test: ensure I2P ports are handled as expected --- test/functional/p2p_i2p_ports.py | 43 ++++++++++++++++++++++++++++++++ test/functional/test_runner.py | 1 + 2 files changed, 44 insertions(+) create mode 100755 test/functional/p2p_i2p_ports.py diff --git a/test/functional/p2p_i2p_ports.py b/test/functional/p2p_i2p_ports.py new file mode 100755 index 0000000000..13188b9305 --- /dev/null +++ b/test/functional/p2p_i2p_ports.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021-2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" +Test ports handling for I2P hosts +""" + +import re + +from test_framework.test_framework import BitcoinTestFramework + + +class I2PPorts(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + # The test assumes that an I2P SAM proxy is not listening here. + self.extra_args = [["-i2psam=127.0.0.1:60000"]] + + def run_test(self): + node = self.nodes[0] + + self.log.info("Ensure we don't try to connect if port!=0") + addr = "zsxwyo6qcn3chqzwxnseusqgsnuw3maqnztkiypyfxtya4snkoka.b32.i2p:8333" + raised = False + try: + with node.assert_debug_log(expected_msgs=[f"Error connecting to {addr}"]): + node.addnode(node=addr, command="onetry") + except AssertionError as e: + raised = True + if not re.search(r"Expected messages .* does not partially match log", str(e)): + raise AssertionError(f"Assertion raised as expected, but with an unexpected message: {str(e)}") + if not raised: + raise AssertionError("Assertion should have been raised") + + self.log.info("Ensure we try to connect if port=0 and get an error due to missing I2P proxy") + addr = "h3r6bkn46qxftwja53pxiykntegfyfjqtnzbm6iv6r5mungmqgmq.b32.i2p:0" + with node.assert_debug_log(expected_msgs=[f"Error connecting to {addr}"]): + node.addnode(node=addr, command="onetry") + + +if __name__ == '__main__': + I2PPorts().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index ad1acd2e2f..fbd9d6028a 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -291,6 +291,7 @@ BASE_SCRIPTS = [ 'p2p_permissions.py', 'feature_blocksdir.py', 'wallet_startup.py', + 'p2p_i2p_ports.py', 'feature_config_args.py', 'feature_settings.py', 'rpc_getdescriptorinfo.py', From e0a2b390c144e123e2fc8a289fdff36815476964 Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Mon, 28 Jun 2021 16:54:45 +0200 Subject: [PATCH 6/7] addrman: reset I2P ports to 0 when loading from disk This is a temporary change to convert I2P addresses that have propagated with port 8333 to ones with port 0. It would cause a problem some day if indeed some bitcoin software is listening on port 8333 only and rejects connections to port 0 and we are still using SAM 3.1 which only supports port 0. In this case we would replace 8333 with 0 and try to connect to such nodes. This commit should be included in 22.0 and be reverted before 23.0 is released. --- src/addrman.cpp | 98 +++++++++++++++++++++++++++++++ src/addrman.h | 10 ++++ src/test/addrman_tests.cpp | 117 +++++++++++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+) diff --git a/src/addrman.cpp b/src/addrman.cpp index 8192b4eba6..389d106164 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -731,3 +732,100 @@ std::vector CAddrMan::DecodeAsmap(fs::path path) } return bits; } + +void CAddrMan::ResetI2PPorts() +{ + for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; ++bucket) { + for (int i = 0; i < ADDRMAN_BUCKET_SIZE; ++i) { + const auto id = vvNew[bucket][i]; + if (id == -1) { + continue; + } + auto it = mapInfo.find(id); + if (it == mapInfo.end()) { + return; + } + auto& addr_info = it->second; + if (!addr_info.IsI2P() || addr_info.GetPort() == I2P_SAM31_PORT) { + continue; + } + + auto addr_info_newport = addr_info; + // The below changes addr_info_newport.GetKey(), which is used in finding a + // bucket and a position within that bucket. So a re-bucketing may be necessary. + addr_info_newport.port = I2P_SAM31_PORT; + + // Reposition entries of vvNew within the same bucket because we don't know the source + // address which led to the decision to store the entry in vvNew[bucket] so we can't + // re-evaluate that decision, but even if we could, CAddrInfo::GetNewBucket() does not + // use CAddrInfo::GetKey() so it would end up in the same bucket as before the port + // change. + const auto i_target = addr_info_newport.GetBucketPosition(nKey, true, bucket); + + if (i_target == i) { // No need to re-position. + addr_info = addr_info_newport; + continue; + } + + // Reposition from i to i_target, removing the entry from i_target (if any). + ClearNew(bucket, i_target); + vvNew[bucket][i_target] = id; + vvNew[bucket][i] = -1; + addr_info = addr_info_newport; + } + } + + for (int bucket = 0; bucket < ADDRMAN_TRIED_BUCKET_COUNT; ++bucket) { + for (int i = 0; i < ADDRMAN_BUCKET_SIZE; ++i) { + const auto id = vvTried[bucket][i]; + if (id == -1) { + continue; + } + auto it = mapInfo.find(id); + if (it == mapInfo.end()) { + return; + } + auto& addr_info = it->second; + if (!addr_info.IsI2P() || addr_info.GetPort() == I2P_SAM31_PORT) { + continue; + } + + auto addr_info_newport = addr_info; + // The below changes addr_info_newport.GetKey(), which is used in finding a + // bucket and a position within that bucket. So a re-bucketing may be necessary. + addr_info_newport.port = I2P_SAM31_PORT; + + const auto bucket_target = addr_info_newport.GetTriedBucket(nKey, m_asmap); + const auto i_target = addr_info_newport.GetBucketPosition(nKey, false, bucket_target); + + if (bucket_target == bucket && i_target == i) { // No need to re-position. + addr_info = addr_info_newport; + continue; + } + + // Reposition from (bucket, i) to (bucket_target, i_target). If the latter is + // occupied, then move the entry from there to vvNew. + + const auto old_target_id = vvTried[bucket_target][i_target]; + if (old_target_id != -1) { + CAddrInfo& old_target_info = mapInfo[old_target_id]; + + old_target_info.fInTried = false; + vvTried[bucket_target][i_target] = -1; + --nTried; + + const auto new_bucket = old_target_info.GetNewBucket(nKey, m_asmap); + const auto new_bucket_i = old_target_info.GetBucketPosition(nKey, true, new_bucket); + ClearNew(new_bucket, new_bucket_i); + + old_target_info.nRefCount = 1; + vvNew[new_bucket][new_bucket_i] = old_target_id; + ++nNew; + } + + vvTried[bucket_target][i_target] = id; + vvTried[bucket][i] = -1; + addr_info = addr_info_newport; + } + } +} diff --git a/src/addrman.h b/src/addrman.h index c2f425f2fa..2a5c6c06b4 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -452,6 +452,8 @@ public: RemoveInvalid(); + ResetI2PPorts(); + Check(); } @@ -767,6 +769,14 @@ private: //! Remove invalid addresses. void RemoveInvalid() EXCLUSIVE_LOCKS_REQUIRED(cs); + /** + * Reset the ports of I2P peers to 0. + * This is needed as a temporary measure because now we enforce port 0 and + * only connect to I2P hosts if the port is 0, but in the early days some + * I2P addresses with port 8333 were rumoured and persisted into addrmans. + */ + void ResetI2PPorts() EXCLUSIVE_LOCKS_REQUIRED(cs); + friend class CAddrManTest; }; diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index 1103292c1a..f2eed956cd 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #include #include @@ -966,5 +967,121 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks) BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); } +BOOST_AUTO_TEST_CASE(reset_i2p_ports) +{ + CAddrManTest addrman1; + CAddrManTest addrman2; + const uint32_t good_time{static_cast(GetAdjustedTime())}; + constexpr uint16_t port = 8333; + + // Has its port changed, will be re-positioned within the same bucket in vvNew. + const CAddress i2p_new1{ + ResolveService("72l3ucjkuscrbiiepoehuwqgknyzgo7zuix5ty4puwrkyhtmnsga.b32.i2p", port), + NODE_NONE, + good_time}; + + // Has its port changed, will not be re-positioned in vvNew because ports 0 and 10075 result in + // the same bucket position. + const CAddress i2p_new2{ + ResolveService("gehtac45oaghz54ypyopim64mql7oad2bqclla74l6tfeolzmodq.b32.i2p", 10075), + NODE_NONE, + good_time}; + + // Remains unchanged, port is already as it should be. + const CAddress i2p_new3{ + ResolveService("c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p", + I2P_SAM31_PORT), + NODE_NONE, + good_time}; + + // Has its port changed, re-positioning in vvNew will cause i2p_new3 to be evicted. + const CAddress i2p_new4{ + ResolveService("c4cbbkn46qxftwja53pxiykntegfyfjqtnzbm6iv6r5mungmqgmq.b32.i2p", port), + NODE_NONE, + good_time}; + + // Remains unchanged. + const CAddress ipv4_new{ResolveService("1.2.3.4", port), NODE_NONE, good_time}; + + // Has its port changed, will be re-positioned in vvTried. + const CAddress i2p_tried1{ + ResolveService("h3r6bkn46qxftwja53pxiykntegfyfjqtnzbm6iv6r5mungmqgmq.b32.i2p", port), + NODE_NONE, + good_time}; + + // Has its port changed, will not be re-positioned in vvTried because ports 0 and 10537 + // result in the same position (bucket, i). + const CAddress i2p_tried2{ + ResolveService("pjs7or2ctvteeo5tu4bwyrtydeuhqhvdprtujn4daxr75jpebjxa.b32.i2p", 10537), + NODE_NONE, + good_time}; + + // Remains unchanged, port is already as it should be. + const CAddress i2p_tried3{ + ResolveService("hnbbyjpxx54623l555sta7pocy3se4sdgmuebi5k6reesz5rjp6q.b32.i2p", + I2P_SAM31_PORT), + NODE_NONE, + good_time}; + + // Has its port changed, re-positioning in vvTried will cause i2p_tried3 to be moved to vvNew. + const CAddress i2p_tried4{ + ResolveService("hna37nqr3ahkqv62cuqfwgtneekvvpnuc4i4f6yo7tpoqjswvcwa.b32.i2p", port), + NODE_NONE, + good_time}; + + // Remains unchanged. + const CAddress ipv4_tried{ResolveService("2.3.4.5", port), NODE_NONE, good_time}; + + const CNetAddr source; + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + + addrman1.Add(i2p_new1, source); + addrman1.Add(i2p_new2, source); + addrman1.Add(i2p_new3, source); + addrman1.Add(i2p_new4, source); + addrman1.Add(ipv4_new, source); + + addrman1.Add(i2p_tried1, source); + addrman1.Good(i2p_tried1); + addrman1.Add(i2p_tried2, source); + addrman1.Good(i2p_tried2); + addrman1.Add(i2p_tried3, source); + addrman1.Good(i2p_tried3); + addrman1.Add(i2p_tried4, source); + addrman1.Good(i2p_tried4); + addrman1.Add(ipv4_tried, source); + addrman1.Good(ipv4_tried); + + stream << addrman1; + stream >> addrman2; + + const size_t max_addresses{0}; + const size_t max_pct{0}; + + auto addresses = addrman2.GetAddr(max_addresses, max_pct, NET_I2P); + BOOST_REQUIRE_EQUAL(addresses.size(), 7UL); + std::sort(addresses.begin(), addresses.end()); // Just some deterministic order. + BOOST_CHECK_EQUAL(addresses[0].ToStringIP(), i2p_new4.ToStringIP()); + BOOST_CHECK_EQUAL(addresses[0].GetPort(), I2P_SAM31_PORT); + BOOST_CHECK_EQUAL(addresses[1].ToStringIP(), i2p_new2.ToStringIP()); + BOOST_CHECK_EQUAL(addresses[1].GetPort(), I2P_SAM31_PORT); + BOOST_CHECK_EQUAL(addresses[2].ToStringIP(), i2p_tried4.ToStringIP()); + BOOST_CHECK_EQUAL(addresses[2].GetPort(), I2P_SAM31_PORT); + BOOST_CHECK_EQUAL(addresses[3].ToStringIP(), i2p_tried3.ToStringIP()); + BOOST_CHECK_EQUAL(addresses[3].GetPort(), I2P_SAM31_PORT); + BOOST_CHECK_EQUAL(addresses[4].ToStringIP(), i2p_tried1.ToStringIP()); + BOOST_CHECK_EQUAL(addresses[4].GetPort(), I2P_SAM31_PORT); + BOOST_CHECK_EQUAL(addresses[5].ToStringIP(), i2p_tried2.ToStringIP()); + BOOST_CHECK_EQUAL(addresses[5].GetPort(), I2P_SAM31_PORT); + BOOST_CHECK_EQUAL(addresses[6].ToStringIP(), i2p_new1.ToStringIP()); + BOOST_CHECK_EQUAL(addresses[6].GetPort(), I2P_SAM31_PORT); + + addresses = addrman2.GetAddr(max_addresses, max_pct, NET_IPV4); + BOOST_REQUIRE_EQUAL(addresses.size(), 2UL); + std::sort(addresses.begin(), addresses.end()); // Just some deterministic order. + BOOST_CHECK_EQUAL(addresses[0].ToStringIPPort(), ipv4_new.ToStringIPPort()); + BOOST_CHECK_EQUAL(addresses[1].ToStringIPPort(), ipv4_tried.ToStringIPPort()); +} BOOST_AUTO_TEST_SUITE_END() From 4101ec9d2e05a35c35f587a28f1feee6cebcc61b Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Fri, 2 Jul 2021 10:33:38 +0200 Subject: [PATCH 7/7] doc: mention that we enforce port=0 in I2P Co-authored-by: Jon Atack --- doc/i2p.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/i2p.md b/doc/i2p.md index 8b4607208a..27ef4d9d9f 100644 --- a/doc/i2p.md +++ b/doc/i2p.md @@ -70,3 +70,18 @@ RPC. Bitcoin Core uses the [SAM v3.1](https://geti2p.net/en/docs/api/samv3) protocol to connect to the I2P network. Any I2P router that supports it can be used. + +## Ports in I2P and Bitcoin Core + +Bitcoin Core uses the [SAM v3.1](https://geti2p.net/en/docs/api/samv3) +protocol. One particularity of SAM v3.1 is that it does not support ports, +unlike newer versions of SAM (v3.2 and up) that do support them and default the +port numbers to 0. From the point of view of peers that use newer versions of +SAM or other protocols that support ports, a SAM v3.1 peer is connecting to them +on port 0, from source port 0. + +To allow future upgrades to newer versions of SAM, Bitcoin Core sets its +listening port to 0 when listening for incoming I2P connections and advertises +its own I2P address with port 0. Furthermore, it will not attempt to connect to +I2P addresses with a non-zero port number because with SAM v3.1 the destination +port (`TO_PORT`) is always set to 0 and is not in the control of Bitcoin Core.