aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWladimir J. van der Laan <laanwj@protonmail.com>2020-10-15 18:33:43 +0200
committerWladimir J. van der Laan <laanwj@protonmail.com>2020-10-15 20:19:55 +0200
commit9855422e65ca74691129114b8ae534c31596c34b (patch)
tree4568a3ae8fa285dbc56a1527798eb7511820ed74
parent0d22482353752ebfb11aa29f4b26113b817a448a (diff)
parenta490d074b3491427afbd677f5fa635b910f8bb34 (diff)
Merge #17428: p2p: Try to preserve outbound block-relay-only connections during restart
a490d074b3491427afbd677f5fa635b910f8bb34 doc: Add anchors.dat to files.md (Hennadii Stepanov) 0a85e5a7bc8dc6587963e2e37ac1b087a1fc97fe p2p: Try to connect to anchors once (Hennadii Stepanov) 5543c7ab285e90256cbbf9858249e028c9611cda p2p: Fix off-by-one error in fetching address loop (Hennadii Stepanov) 4170b46544231e7cf1d64ac3baa314083be37502 p2p: Integrate DumpAnchors() and ReadAnchors() into CConnman (Hennadii Stepanov) bad16aff490dcf87722fbfe202a869fb24c734e1 p2p: Add CConnman::GetCurrentBlockRelayOnlyConns() (Hennadii Stepanov) c29272a157d09a8125788c1b860e89b63b4cb36c p2p: Add ReadAnchors() (Hennadii Stepanov) 567008d2a0c95bd972f4031f31647c493d1bc2e8 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 a490d074b3 laanwj: Code review ACK a490d074b3491427afbd677f5fa635b910f8bb34 Tree-SHA512: 0f5098a3882f2814be1aa21de308cd09e6654f4e7054b79f3cfeaf26bc02b814ca271497ed00018d199ee596a8cb9b126acee8b666a29e225b08eb2a49b02ddd
-rw-r--r--doc/files.md1
-rw-r--r--src/addrdb.cpp20
-rw-r--r--src/addrdb.h20
-rw-r--r--src/net.cpp71
-rw-r--r--src/net.h15
5 files changed, 115 insertions, 12 deletions
diff --git a/doc/files.md b/doc/files.md
index e3f195de43..65f98e6986 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`<sup>[\[2\]](#note2)</sup> | 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
diff --git a/src/addrdb.cpp b/src/addrdb.cpp
index f3e8a19de2..27f22826a9 100644
--- a/src/addrdb.cpp
+++ b/src/addrdb.cpp
@@ -10,6 +10,7 @@
#include <clientversion.h>
#include <cstdint>
#include <hash.h>
+#include <logging/timer.h>
#include <random.h>
#include <streams.h>
#include <tinyformat.h>
@@ -156,3 +157,22 @@ bool CAddrDB::Read(CAddrMan& addr, CDataStream& ssPeers)
}
return ret;
}
+
+void DumpAnchors(const fs::path& anchors_db_path, const std::vector<CAddress>& anchors)
+{
+ 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<CAddress> ReadAnchors(const fs::path& anchors_db_path)
+{
+ std::vector<CAddress> 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 8410c3776c..4ac0e3e1b5 100644
--- a/src/addrdb.h
+++ b/src/addrdb.h
@@ -11,9 +11,9 @@
#include <serialize.h>
#include <string>
-#include <map>
+#include <vector>
-class CSubNet;
+class CAddress;
class CAddrMan;
class CDataStream;
@@ -73,4 +73,20 @@ public:
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<CAddress>& 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<CAddress> ReadAnchors(const fs::path& anchors_db_path);
+
#endif // BITCOIN_ADDRDB_H
diff --git a/src/net.cpp b/src/net.cpp
index 5db7fc9e73..e8a27c3530 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -47,6 +47,12 @@ static_assert(MINIUPNPC_API_VERSION >= 10, "miniUPnPc API version >= 10 assumed"
#include <math.h>
+/** 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<size_t>(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};
@@ -1933,10 +1939,12 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> 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
@@ -1944,7 +1952,10 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> 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;
@@ -1965,6 +1976,24 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> 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.
+ nTries++;
+ if (nTries > 100)
+ break;
+
CAddrInfo addr = addrman.SelectTriedCollision();
// SelectTriedCollision returns an invalid address if it is empty.
@@ -1982,13 +2011,6 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> 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;
@@ -2028,6 +2050,19 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
}
}
+std::vector<CAddress> CConnman::GetCurrentBlockRelayOnlyConns() const
+{
+ std::vector<CAddress> ret;
+ LOCK(cs_vNodes);
+ for (const CNode* pnode : vNodes) {
+ if (pnode->IsBlockOnlyConn()) {
+ ret.push_back(pnode->addr);
+ }
+ }
+
+ return ret;
+}
+
std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo()
{
std::vector<AddedNodeInfo> ret;
@@ -2427,6 +2462,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;
@@ -2542,6 +2586,15 @@ void CConnman::StopNodes()
if (fAddressesInitialized) {
DumpAddresses();
fAddressesInitialized = false;
+
+ if (m_use_addrman_outgoing) {
+ // Anchor connections are only dumped during clean shutdown.
+ std::vector<CAddress> 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 fb5c2bd328..40eab889a6 100644
--- a/src/net.h
+++ b/src/net.h
@@ -173,7 +173,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,
@@ -460,6 +462,11 @@ private:
void RecordBytesRecv(uint64_t bytes);
void RecordBytesSent(uint64_t bytes);
+ /**
+ * Return vector of current BLOCK_RELAY peers.
+ */
+ std::vector<CAddress> GetCurrentBlockRelayOnlyConns() const;
+
// Whether the node should be passed out in ForEach* callbacks
static bool NodeFullyConnected(const CNode* pnode);
@@ -561,6 +568,12 @@ private:
/** 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<CAddress> m_anchors;
+
/** SipHasher seeds for deterministic randomness */
const uint64_t nSeed0, nSeed1;