From 567008d2a0c95bd972f4031f31647c493d1bc2e8 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Sat, 12 Sep 2020 17:59:09 +0300 Subject: [PATCH 1/7] p2p: Add DumpAnchors() --- src/addrdb.cpp | 7 +++++++ src/addrdb.h | 12 ++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/addrdb.cpp b/src/addrdb.cpp index f3e8a19de2..ca2d66570b 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -156,3 +157,9 @@ bool CAddrDB::Read(CAddrMan& addr, CDataStream& ssPeers) } return ret; } + +void DumpAnchors(const fs::path& anchors_db_path, const std::vector& anchors) +{ + LOG_TIME_SECONDS(strprintf("Flush %d outbound block-relay-only peer addresses to anchors.dat", anchors.size())); + SerializeFileDB("anchors", anchors_db_path, anchors); +} diff --git a/src/addrdb.h b/src/addrdb.h index 8410c3776c..614a0c20e3 100644 --- a/src/addrdb.h +++ b/src/addrdb.h @@ -11,9 +11,9 @@ #include #include -#include +#include -class CSubNet; +class CAddress; class CAddrMan; class CDataStream; @@ -73,4 +73,12 @@ class CBanDB bool Read(banmap_t& banSet); }; +/** + * Dump the anchor IP address database (anchors.dat) + * + * Anchors are last known outgoing block-relay-only peers that are + * tried to re-connect to on startup. + */ +void DumpAnchors(const fs::path& anchors_db_path, const std::vector& anchors); + #endif // BITCOIN_ADDRDB_H From c29272a157d09a8125788c1b860e89b63b4cb36c Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Sat, 12 Sep 2020 18:01:19 +0300 Subject: [PATCH 2/7] p2p: Add ReadAnchors() --- src/addrdb.cpp | 13 +++++++++++++ src/addrdb.h | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/src/addrdb.cpp b/src/addrdb.cpp index ca2d66570b..27f22826a9 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -163,3 +163,16 @@ void DumpAnchors(const fs::path& anchors_db_path, const std::vector& a LOG_TIME_SECONDS(strprintf("Flush %d outbound block-relay-only peer addresses to anchors.dat", anchors.size())); SerializeFileDB("anchors", anchors_db_path, anchors); } + +std::vector ReadAnchors(const fs::path& anchors_db_path) +{ + std::vector anchors; + if (DeserializeFileDB(anchors_db_path, anchors)) { + LogPrintf("Loaded %i addresses from %s\n", anchors.size(), anchors_db_path.filename()); + } else { + anchors.clear(); + } + + fs::remove(anchors_db_path); + return anchors; +} diff --git a/src/addrdb.h b/src/addrdb.h index 614a0c20e3..4ac0e3e1b5 100644 --- a/src/addrdb.h +++ b/src/addrdb.h @@ -81,4 +81,12 @@ class CBanDB */ void DumpAnchors(const fs::path& anchors_db_path, const std::vector& anchors); +/** + * Read the anchor IP address database (anchors.dat) + * + * Deleting anchors.dat is intentional as it avoids renewed peering to anchors after + * an unclean shutdown and thus potential exploitation of the anchor peer policy. + */ +std::vector ReadAnchors(const fs::path& anchors_db_path); + #endif // BITCOIN_ADDRDB_H From bad16aff490dcf87722fbfe202a869fb24c734e1 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Sat, 12 Sep 2020 18:03:06 +0300 Subject: [PATCH 3/7] p2p: Add CConnman::GetCurrentBlockRelayOnlyConns() --- src/net.cpp | 13 +++++++++++++ src/net.h | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/src/net.cpp b/src/net.cpp index 95ba6da819..53cf4ca017 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2019,6 +2019,19 @@ void CConnman::ThreadOpenConnections(const std::vector connect) } } +std::vector CConnman::GetCurrentBlockRelayOnlyConns() const +{ + std::vector ret; + LOCK(cs_vNodes); + for (const CNode* pnode : vNodes) { + if (pnode->IsBlockOnlyConn()) { + ret.push_back(pnode->addr); + } + } + + return ret; +} + std::vector CConnman::GetAddedNodeInfo() { std::vector ret; diff --git a/src/net.h b/src/net.h index 2b99d3bd9d..adeee1579a 100644 --- a/src/net.h +++ b/src/net.h @@ -460,6 +460,11 @@ class CConnman void RecordBytesRecv(uint64_t bytes); void RecordBytesSent(uint64_t bytes); + /** + * Return vector of current BLOCK_RELAY peers. + */ + std::vector GetCurrentBlockRelayOnlyConns() const; + // Whether the node should be passed out in ForEach* callbacks static bool NodeFullyConnected(const CNode* pnode); From 4170b46544231e7cf1d64ac3baa314083be37502 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Sat, 12 Sep 2020 18:05:54 +0300 Subject: [PATCH 4/7] p2p: Integrate DumpAnchors() and ReadAnchors() into CConnman --- src/net.cpp | 24 ++++++++++++++++++++++++ src/net.h | 6 ++++++ 2 files changed, 30 insertions(+) diff --git a/src/net.cpp b/src/net.cpp index 53cf4ca017..0fb4bf9bde 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -46,6 +46,12 @@ static_assert(MINIUPNPC_API_VERSION >= 10, "miniUPnPc API version >= 10 assumed" #include +/** Maximum number of block-relay-only anchor connections */ +static constexpr size_t MAX_BLOCK_RELAY_ONLY_ANCHORS = 2; +static_assert (MAX_BLOCK_RELAY_ONLY_ANCHORS <= static_cast(MAX_BLOCK_RELAY_ONLY_CONNECTIONS), "MAX_BLOCK_RELAY_ONLY_ANCHORS must not exceed MAX_BLOCK_RELAY_ONLY_CONNECTIONS."); +/** Anchor IP address database file name */ +const char* const ANCHORS_DATABASE_FILENAME = "anchors.dat"; + // How often to dump addresses to peers.dat static constexpr std::chrono::minutes DUMP_PEERS_INTERVAL{15}; @@ -2431,6 +2437,15 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) } } + if (m_use_addrman_outgoing) { + // Load addresses from anchors.dat + m_anchors = ReadAnchors(GetDataDir() / ANCHORS_DATABASE_FILENAME); + if (m_anchors.size() > MAX_BLOCK_RELAY_ONLY_ANCHORS) { + m_anchors.resize(MAX_BLOCK_RELAY_ONLY_ANCHORS); + } + LogPrintf("%i block-relay-only anchors will be tried for connections.\n", m_anchors.size()); + } + uiInterface.InitMessage(_("Starting network threads...").translated); fAddressesInitialized = true; @@ -2546,6 +2561,15 @@ void CConnman::StopNodes() if (fAddressesInitialized) { DumpAddresses(); fAddressesInitialized = false; + + if (m_use_addrman_outgoing) { + // Anchor connections are only dumped during clean shutdown. + std::vector anchors_to_dump = GetCurrentBlockRelayOnlyConns(); + if (anchors_to_dump.size() > MAX_BLOCK_RELAY_ONLY_ANCHORS) { + anchors_to_dump.resize(MAX_BLOCK_RELAY_ONLY_ANCHORS); + } + DumpAnchors(GetDataDir() / ANCHORS_DATABASE_FILENAME, anchors_to_dump); + } } // Close sockets diff --git a/src/net.h b/src/net.h index adeee1579a..7b7885f839 100644 --- a/src/net.h +++ b/src/net.h @@ -566,6 +566,12 @@ class CConnman /** Pointer to this node's banman. May be nullptr - check existence before dereferencing. */ BanMan* m_banman; + /** + * Addresses that were saved during the previous clean shutdown. We'll + * attempt to make block-relay-only connections to them. + */ + std::vector m_anchors; + /** SipHasher seeds for deterministic randomness */ const uint64_t nSeed0, nSeed1; From 5543c7ab285e90256cbbf9858249e028c9611cda Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Sat, 12 Sep 2020 18:17:49 +0300 Subject: [PATCH 5/7] p2p: Fix off-by-one error in fetching address loop This is a move-only commit. --- src/net.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 0fb4bf9bde..0fe06641e1 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1962,6 +1962,13 @@ void CConnman::ThreadOpenConnections(const std::vector connect) int nTries = 0; while (!interruptNet) { + // If we didn't find an appropriate destination after trying 100 addresses fetched from addrman, + // stop this loop, and let the outer loop run again (which sleeps, adds seed nodes, recalculates + // already-connected network ranges, ...) before trying new addrman addresses. + nTries++; + if (nTries > 100) + break; + CAddrInfo addr = addrman.SelectTriedCollision(); // SelectTriedCollision returns an invalid address if it is empty. @@ -1979,13 +1986,6 @@ void CConnman::ThreadOpenConnections(const std::vector connect) break; } - // If we didn't find an appropriate destination after trying 100 addresses fetched from addrman, - // stop this loop, and let the outer loop run again (which sleeps, adds seed nodes, recalculates - // already-connected network ranges, ...) before trying new addrman addresses. - nTries++; - if (nTries > 100) - break; - if (!IsReachable(addr)) continue; From 0a85e5a7bc8dc6587963e2e37ac1b087a1fc97fe Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Fri, 5 Jun 2020 09:38:09 +0300 Subject: [PATCH 6/7] p2p: Try to connect to anchors once --- src/net.cpp | 20 ++++++++++++++++++-- src/net.h | 4 +++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 0fe06641e1..95cd9b8d16 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1930,10 +1930,12 @@ void CConnman::ThreadOpenConnections(const std::vector connect) ConnectionType conn_type = ConnectionType::OUTBOUND_FULL_RELAY; int64_t nTime = GetTimeMicros(); + bool anchor = false; bool fFeeler = false; // Determine what type of connection to open. Opening - // OUTBOUND_FULL_RELAY connections gets the highest priority until we + // BLOCK_RELAY connections to addresses from anchors.dat gets the highest + // priority. Then we open OUTBOUND_FULL_RELAY priority until we // meet our full-relay capacity. Then we open BLOCK_RELAY connection // until we hit our block-relay-only peer limit. // GetTryNewOutboundPeer() gets set when a stale tip is detected, so we @@ -1941,7 +1943,10 @@ void CConnman::ThreadOpenConnections(const std::vector connect) // these conditions are met, check the nNextFeeler timer to decide if // we should open a FEELER. - if (nOutboundFullRelay < m_max_outbound_full_relay) { + if (!m_anchors.empty() && (nOutboundBlockRelay < m_max_outbound_block_relay)) { + conn_type = ConnectionType::BLOCK_RELAY; + anchor = true; + } else if (nOutboundFullRelay < m_max_outbound_full_relay) { // OUTBOUND_FULL_RELAY } else if (nOutboundBlockRelay < m_max_outbound_block_relay) { conn_type = ConnectionType::BLOCK_RELAY; @@ -1962,6 +1967,17 @@ void CConnman::ThreadOpenConnections(const std::vector connect) int nTries = 0; while (!interruptNet) { + if (anchor && !m_anchors.empty()) { + const CAddress addr = m_anchors.back(); + m_anchors.pop_back(); + if (!addr.IsValid() || IsLocal(addr) || !IsReachable(addr) || + !HasAllDesirableServiceFlags(addr.nServices) || + setConnected.count(addr.GetGroup(addrman.m_asmap))) continue; + addrConnect = addr; + LogPrint(BCLog::NET, "Trying to make an anchor connection to %s\n", addrConnect.ToString()); + break; + } + // If we didn't find an appropriate destination after trying 100 addresses fetched from addrman, // stop this loop, and let the outer loop run again (which sleeps, adds seed nodes, recalculates // already-connected network ranges, ...) before trying new addrman addresses. diff --git a/src/net.h b/src/net.h index 7b7885f839..616b986e4a 100644 --- a/src/net.h +++ b/src/net.h @@ -174,7 +174,9 @@ enum class ConnectionType { * attacks. By not relaying transactions or addresses, these connections * are harder to detect by a third party, thus helping obfuscate the * network topology. We automatically attempt to open - * MAX_BLOCK_RELAY_ONLY_CONNECTIONS using addresses from our AddrMan. + * MAX_BLOCK_RELAY_ONLY_ANCHORS using addresses from our anchors.dat. Then + * addresses from our AddrMan if MAX_BLOCK_RELAY_ONLY_CONNECTIONS + * isn't reached yet. */ BLOCK_RELAY, From a490d074b3491427afbd677f5fa635b910f8bb34 Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Sat, 12 Sep 2020 19:47:53 +0300 Subject: [PATCH 7/7] doc: Add anchors.dat to files.md --- doc/files.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/files.md b/doc/files.md index c5c612114b..df252b5f49 100644 --- a/doc/files.md +++ b/doc/files.md @@ -50,6 +50,7 @@ Subdirectory | File(s) | Description `indexes/blockfilter/basic/db/` | LevelDB database | Blockfilter index LevelDB database for the basic filtertype; *optional*, used if `-blockfilterindex=basic` `indexes/blockfilter/basic/` | `fltrNNNNN.dat`[\[2\]](#note2) | Blockfilter index filters for the basic filtertype; *optional*, used if `-blockfilterindex=basic` `wallets/` | | [Contains wallets](#multi-wallet-environment); can be specified by `-walletdir` option; if `wallets/` subdirectory does not exist, wallets reside in the [data directory](#data-directory-location) +`./` | `anchors.dat` | Anchor IP address database, created on shutdown and deleted at startup. Anchors are last known outgoing block-relay-only peers that are tried to re-connect to on startup `./` | `banlist.dat` | Stores the IPs/subnets of banned nodes `./` | `bitcoin.conf` | User-defined [configuration settings](bitcoin-conf.md) for `bitcoind` or `bitcoin-qt`. File is not written to by the software and must be created manually. Path can be specified by `-conf` option `./` | `bitcoind.pid` | Stores the process ID (PID) of `bitcoind` or `bitcoin-qt` while running; created at start and deleted on shutdown; can be specified by `-pid` option