Skip to content

Commit 9855422

Browse files
committed
Merge #17428: p2p: Try to preserve outbound block-relay-only connections during restart
a490d07 doc: Add anchors.dat to files.md (Hennadii Stepanov) 0a85e5a p2p: Try to connect to anchors once (Hennadii Stepanov) 5543c7a p2p: Fix off-by-one error in fetching address loop (Hennadii Stepanov) 4170b46 p2p: Integrate DumpAnchors() and ReadAnchors() into CConnman (Hennadii Stepanov) bad16af p2p: Add CConnman::GetCurrentBlockRelayOnlyConns() (Hennadii Stepanov) c29272a p2p: Add ReadAnchors() (Hennadii Stepanov) 567008d p2p: Add DumpAnchors() (Hennadii Stepanov) Pull request description: This is an implementation of #17326: - all (currently 2) outbound block-relay-only connections (#15759) are dumped to `anchors.dat` file - on restart a node tries to connect to the addresses from `anchors.dat` This PR prevents a type of eclipse attack when an attacker exploits a victim node restart to force it to connect to new, probably adversarial, peers. ACKs for top commit: jnewbery: code review ACK a490d07 laanwj: Code review ACK a490d07 Tree-SHA512: 0f5098a3882f2814be1aa21de308cd09e6654f4e7054b79f3cfeaf26bc02b814ca271497ed00018d199ee596a8cb9b126acee8b666a29e225b08eb2a49b02ddd
2 parents 0d22482 + a490d07 commit 9855422

File tree

5 files changed

+115
-12
lines changed

5 files changed

+115
-12
lines changed

doc/files.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Subdirectory | File(s) | Description
5050
`indexes/blockfilter/basic/db/` | LevelDB database | Blockfilter index LevelDB database for the basic filtertype; *optional*, used if `-blockfilterindex=basic`
5151
`indexes/blockfilter/basic/` | `fltrNNNNN.dat`<sup>[\[2\]](#note2)</sup> | Blockfilter index filters for the basic filtertype; *optional*, used if `-blockfilterindex=basic`
5252
`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)
53+
`./` | `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
5354
`./` | `banlist.dat` | Stores the IPs/subnets of banned nodes
5455
`./` | `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
5556
`./` | `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

src/addrdb.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <clientversion.h>
1111
#include <cstdint>
1212
#include <hash.h>
13+
#include <logging/timer.h>
1314
#include <random.h>
1415
#include <streams.h>
1516
#include <tinyformat.h>
@@ -156,3 +157,22 @@ bool CAddrDB::Read(CAddrMan& addr, CDataStream& ssPeers)
156157
}
157158
return ret;
158159
}
160+
161+
void DumpAnchors(const fs::path& anchors_db_path, const std::vector<CAddress>& anchors)
162+
{
163+
LOG_TIME_SECONDS(strprintf("Flush %d outbound block-relay-only peer addresses to anchors.dat", anchors.size()));
164+
SerializeFileDB("anchors", anchors_db_path, anchors);
165+
}
166+
167+
std::vector<CAddress> ReadAnchors(const fs::path& anchors_db_path)
168+
{
169+
std::vector<CAddress> anchors;
170+
if (DeserializeFileDB(anchors_db_path, anchors)) {
171+
LogPrintf("Loaded %i addresses from %s\n", anchors.size(), anchors_db_path.filename());
172+
} else {
173+
anchors.clear();
174+
}
175+
176+
fs::remove(anchors_db_path);
177+
return anchors;
178+
}

src/addrdb.h

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
#include <serialize.h>
1212

1313
#include <string>
14-
#include <map>
14+
#include <vector>
1515

16-
class CSubNet;
16+
class CAddress;
1717
class CAddrMan;
1818
class CDataStream;
1919

@@ -73,4 +73,20 @@ class CBanDB
7373
bool Read(banmap_t& banSet);
7474
};
7575

76+
/**
77+
* Dump the anchor IP address database (anchors.dat)
78+
*
79+
* Anchors are last known outgoing block-relay-only peers that are
80+
* tried to re-connect to on startup.
81+
*/
82+
void DumpAnchors(const fs::path& anchors_db_path, const std::vector<CAddress>& anchors);
83+
84+
/**
85+
* Read the anchor IP address database (anchors.dat)
86+
*
87+
* Deleting anchors.dat is intentional as it avoids renewed peering to anchors after
88+
* an unclean shutdown and thus potential exploitation of the anchor peer policy.
89+
*/
90+
std::vector<CAddress> ReadAnchors(const fs::path& anchors_db_path);
91+
7692
#endif // BITCOIN_ADDRDB_H

src/net.cpp

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ static_assert(MINIUPNPC_API_VERSION >= 10, "miniUPnPc API version >= 10 assumed"
4747

4848
#include <math.h>
4949

50+
/** Maximum number of block-relay-only anchor connections */
51+
static constexpr size_t MAX_BLOCK_RELAY_ONLY_ANCHORS = 2;
52+
static_assert (MAX_BLOCK_RELAY_ONLY_ANCHORS <= static_cast<size_t>(MAX_BLOCK_RELAY_ONLY_CONNECTIONS), "MAX_BLOCK_RELAY_ONLY_ANCHORS must not exceed MAX_BLOCK_RELAY_ONLY_CONNECTIONS.");
53+
/** Anchor IP address database file name */
54+
const char* const ANCHORS_DATABASE_FILENAME = "anchors.dat";
55+
5056
// How often to dump addresses to peers.dat
5157
static constexpr std::chrono::minutes DUMP_PEERS_INTERVAL{15};
5258

@@ -1933,18 +1939,23 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
19331939

19341940
ConnectionType conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
19351941
int64_t nTime = GetTimeMicros();
1942+
bool anchor = false;
19361943
bool fFeeler = false;
19371944

19381945
// Determine what type of connection to open. Opening
1939-
// OUTBOUND_FULL_RELAY connections gets the highest priority until we
1946+
// BLOCK_RELAY connections to addresses from anchors.dat gets the highest
1947+
// priority. Then we open OUTBOUND_FULL_RELAY priority until we
19401948
// meet our full-relay capacity. Then we open BLOCK_RELAY connection
19411949
// until we hit our block-relay-only peer limit.
19421950
// GetTryNewOutboundPeer() gets set when a stale tip is detected, so we
19431951
// try opening an additional OUTBOUND_FULL_RELAY connection. If none of
19441952
// these conditions are met, check the nNextFeeler timer to decide if
19451953
// we should open a FEELER.
19461954

1947-
if (nOutboundFullRelay < m_max_outbound_full_relay) {
1955+
if (!m_anchors.empty() && (nOutboundBlockRelay < m_max_outbound_block_relay)) {
1956+
conn_type = ConnectionType::BLOCK_RELAY;
1957+
anchor = true;
1958+
} else if (nOutboundFullRelay < m_max_outbound_full_relay) {
19481959
// OUTBOUND_FULL_RELAY
19491960
} else if (nOutboundBlockRelay < m_max_outbound_block_relay) {
19501961
conn_type = ConnectionType::BLOCK_RELAY;
@@ -1965,6 +1976,24 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
19651976
int nTries = 0;
19661977
while (!interruptNet)
19671978
{
1979+
if (anchor && !m_anchors.empty()) {
1980+
const CAddress addr = m_anchors.back();
1981+
m_anchors.pop_back();
1982+
if (!addr.IsValid() || IsLocal(addr) || !IsReachable(addr) ||
1983+
!HasAllDesirableServiceFlags(addr.nServices) ||
1984+
setConnected.count(addr.GetGroup(addrman.m_asmap))) continue;
1985+
addrConnect = addr;
1986+
LogPrint(BCLog::NET, "Trying to make an anchor connection to %s\n", addrConnect.ToString());
1987+
break;
1988+
}
1989+
1990+
// If we didn't find an appropriate destination after trying 100 addresses fetched from addrman,
1991+
// stop this loop, and let the outer loop run again (which sleeps, adds seed nodes, recalculates
1992+
// already-connected network ranges, ...) before trying new addrman addresses.
1993+
nTries++;
1994+
if (nTries > 100)
1995+
break;
1996+
19681997
CAddrInfo addr = addrman.SelectTriedCollision();
19691998

19701999
// SelectTriedCollision returns an invalid address if it is empty.
@@ -1982,13 +2011,6 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
19822011
break;
19832012
}
19842013

1985-
// If we didn't find an appropriate destination after trying 100 addresses fetched from addrman,
1986-
// stop this loop, and let the outer loop run again (which sleeps, adds seed nodes, recalculates
1987-
// already-connected network ranges, ...) before trying new addrman addresses.
1988-
nTries++;
1989-
if (nTries > 100)
1990-
break;
1991-
19922014
if (!IsReachable(addr))
19932015
continue;
19942016

@@ -2028,6 +2050,19 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
20282050
}
20292051
}
20302052

2053+
std::vector<CAddress> CConnman::GetCurrentBlockRelayOnlyConns() const
2054+
{
2055+
std::vector<CAddress> ret;
2056+
LOCK(cs_vNodes);
2057+
for (const CNode* pnode : vNodes) {
2058+
if (pnode->IsBlockOnlyConn()) {
2059+
ret.push_back(pnode->addr);
2060+
}
2061+
}
2062+
2063+
return ret;
2064+
}
2065+
20312066
std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo()
20322067
{
20332068
std::vector<AddedNodeInfo> ret;
@@ -2427,6 +2462,15 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
24272462
}
24282463
}
24292464

2465+
if (m_use_addrman_outgoing) {
2466+
// Load addresses from anchors.dat
2467+
m_anchors = ReadAnchors(GetDataDir() / ANCHORS_DATABASE_FILENAME);
2468+
if (m_anchors.size() > MAX_BLOCK_RELAY_ONLY_ANCHORS) {
2469+
m_anchors.resize(MAX_BLOCK_RELAY_ONLY_ANCHORS);
2470+
}
2471+
LogPrintf("%i block-relay-only anchors will be tried for connections.\n", m_anchors.size());
2472+
}
2473+
24302474
uiInterface.InitMessage(_("Starting network threads...").translated);
24312475

24322476
fAddressesInitialized = true;
@@ -2542,6 +2586,15 @@ void CConnman::StopNodes()
25422586
if (fAddressesInitialized) {
25432587
DumpAddresses();
25442588
fAddressesInitialized = false;
2589+
2590+
if (m_use_addrman_outgoing) {
2591+
// Anchor connections are only dumped during clean shutdown.
2592+
std::vector<CAddress> anchors_to_dump = GetCurrentBlockRelayOnlyConns();
2593+
if (anchors_to_dump.size() > MAX_BLOCK_RELAY_ONLY_ANCHORS) {
2594+
anchors_to_dump.resize(MAX_BLOCK_RELAY_ONLY_ANCHORS);
2595+
}
2596+
DumpAnchors(GetDataDir() / ANCHORS_DATABASE_FILENAME, anchors_to_dump);
2597+
}
25452598
}
25462599

25472600
// Close sockets

src/net.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,9 @@ enum class ConnectionType {
173173
* attacks. By not relaying transactions or addresses, these connections
174174
* are harder to detect by a third party, thus helping obfuscate the
175175
* network topology. We automatically attempt to open
176-
* MAX_BLOCK_RELAY_ONLY_CONNECTIONS using addresses from our AddrMan.
176+
* MAX_BLOCK_RELAY_ONLY_ANCHORS using addresses from our anchors.dat. Then
177+
* addresses from our AddrMan if MAX_BLOCK_RELAY_ONLY_CONNECTIONS
178+
* isn't reached yet.
177179
*/
178180
BLOCK_RELAY,
179181

@@ -460,6 +462,11 @@ class CConnman
460462
void RecordBytesRecv(uint64_t bytes);
461463
void RecordBytesSent(uint64_t bytes);
462464

465+
/**
466+
* Return vector of current BLOCK_RELAY peers.
467+
*/
468+
std::vector<CAddress> GetCurrentBlockRelayOnlyConns() const;
469+
463470
// Whether the node should be passed out in ForEach* callbacks
464471
static bool NodeFullyConnected(const CNode* pnode);
465472

@@ -561,6 +568,12 @@ class CConnman
561568
/** Pointer to this node's banman. May be nullptr - check existence before dereferencing. */
562569
BanMan* m_banman;
563570

571+
/**
572+
* Addresses that were saved during the previous clean shutdown. We'll
573+
* attempt to make block-relay-only connections to them.
574+
*/
575+
std::vector<CAddress> m_anchors;
576+
564577
/** SipHasher seeds for deterministic randomness */
565578
const uint64_t nSeed0, nSeed1;
566579

0 commit comments

Comments
 (0)