diff options
author | Suhas Daftuar <sdaftuar@gmail.com> | 2019-03-09 12:55:06 -0500 |
---|---|---|
committer | Suhas Daftuar <sdaftuar@gmail.com> | 2019-09-04 14:58:36 -0400 |
commit | 3a5e885306ea954d7eccdc11502e91a51dab8ec6 (patch) | |
tree | d0fa4fd1f947425ca84bf0c4b6f93a033c58c75d | |
parent | b83f51a4bbe29bf130a2b0c0e85e5bffea107f75 (diff) |
Add 2 outbound block-relay-only connections
Transaction relay is primarily optimized for balancing redundancy/robustness
with bandwidth minimization -- as a result transaction relay leaks information
that adversaries can use to infer the network topology.
Network topology is better kept private for (at least) two reasons:
(a) Knowledge of the network graph can make it easier to find the source IP of
a given transaction.
(b) Knowledge of the network graph could be used to split a target node or
nodes from the honest network (eg by knowing which peers to attack in order to
achieve a network split).
We can eliminate the risks of (b) by separating block relay from transaction
relay; inferring network connectivity from the relay of blocks/block headers is
much more expensive for an adversary.
After this commit, bitcoind will make 2 additional outbound connections that
are only used for block relay. (In the future, we might consider rotating our
transaction-relay peers to help limit the effects of (a).)
-rw-r--r-- | src/init.cpp | 3 | ||||
-rw-r--r-- | src/net.cpp | 42 | ||||
-rw-r--r-- | src/net.h | 33 | ||||
-rw-r--r-- | src/net_processing.cpp | 25 | ||||
-rw-r--r-- | src/test/denialofservice_tests.cpp | 12 |
5 files changed, 75 insertions, 40 deletions
diff --git a/src/init.cpp b/src/init.cpp index 25c964205a..1bc5eb3f7b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1753,7 +1753,8 @@ bool AppInitMain(InitInterfaces& interfaces) CConnman::Options connOptions; connOptions.nLocalServices = nLocalServices; connOptions.nMaxConnections = nMaxConnections; - connOptions.nMaxOutbound = std::min(MAX_OUTBOUND_CONNECTIONS, connOptions.nMaxConnections); + connOptions.m_max_outbound_full_relay = std::min(MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, connOptions.nMaxConnections); + connOptions.m_max_outbound_block_relay = std::min(MAX_BLOCKS_ONLY_CONNECTIONS, connOptions.nMaxConnections-connOptions.m_max_outbound_full_relay); connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS; connOptions.nMaxFeeler = 1; connOptions.nBestHeight = chain_active_height; diff --git a/src/net.cpp b/src/net.cpp index 9514da8809..e589b940ad 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -352,7 +352,7 @@ static CAddress GetBindAddress(SOCKET sock) return addr_bind; } -CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection) +CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection, bool block_relay_only) { if (pszDest == nullptr) { if (IsLocal(addrConnect)) @@ -442,7 +442,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo NodeId id = GetNewNodeId(); uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize(); CAddress addr_bind = GetBindAddress(hSocket); - CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", false); + CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", false, block_relay_only); pnode->AddRef(); return pnode; @@ -905,7 +905,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len); CAddress addr; int nInbound = 0; - int nMaxInbound = nMaxConnections - (nMaxOutbound + nMaxFeeler); + int nMaxInbound = nMaxConnections - m_max_outbound; if (hSocket != INVALID_SOCKET) { if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) { @@ -1666,7 +1666,7 @@ int CConnman::GetExtraOutboundCount() } } } - return std::max(nOutbound - nMaxOutbound, 0); + return std::max(nOutbound - m_max_outbound_full_relay - m_max_outbound_block_relay, 0); } void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) @@ -1726,7 +1726,8 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) CAddress addrConnect; // Only connect out to one peer per network group (/16 for IPv4). - int nOutbound = 0; + int nOutboundFullRelay = 0; + int nOutboundBlockRelay = 0; std::set<std::vector<unsigned char> > setConnected; { LOCK(cs_vNodes); @@ -1738,7 +1739,11 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) // also have the added issue that they're attacker controlled and could be used // to prevent us from connecting to particular hosts if we used them here. setConnected.insert(pnode->addr.GetGroup()); - nOutbound++; + if (pnode->m_tx_relay == nullptr) { + nOutboundBlockRelay++; + } else if (!pnode->fFeeler) { + nOutboundFullRelay++; + } } } } @@ -1757,7 +1762,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) // bool fFeeler = false; - if (nOutbound >= nMaxOutbound && !GetTryNewOutboundPeer()) { + if (nOutboundFullRelay >= m_max_outbound_full_relay && nOutboundBlockRelay >= m_max_outbound_block_relay && !GetTryNewOutboundPeer()) { int64_t nTime = GetTimeMicros(); // The current time right now (in microseconds). if (nTime > nNextFeeler) { nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL); @@ -1831,7 +1836,14 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) LogPrint(BCLog::NET, "Making feeler connection to %s\n", addrConnect.ToString()); } - OpenNetworkConnection(addrConnect, (int)setConnected.size() >= std::min(nMaxConnections - 1, 2), &grant, nullptr, false, fFeeler); + // Open this connection as block-relay-only if we're already at our + // full-relay capacity, but not yet at our block-relay peer limit. + // (It should not be possible for fFeeler to be set if we're not + // also at our block-relay peer limit, but check against that as + // well for sanity.) + bool block_relay_only = nOutboundBlockRelay < m_max_outbound_block_relay && !fFeeler && nOutboundFullRelay >= m_max_outbound_full_relay; + + OpenNetworkConnection(addrConnect, (int)setConnected.size() >= std::min(nMaxConnections - 1, 2), &grant, nullptr, false, fFeeler, false, block_relay_only); } } } @@ -1918,7 +1930,7 @@ void CConnman::ThreadOpenAddedConnections() } // if successful, this moves the passed grant to the constructed node -void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot, bool fFeeler, bool manual_connection) +void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot, bool fFeeler, bool manual_connection, bool block_relay_only) { // // Initiate outbound network connection @@ -1937,7 +1949,7 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai } else if (FindNode(std::string(pszDest))) return; - CNode* pnode = ConnectNode(addrConnect, pszDest, fCountFailure, manual_connection); + CNode* pnode = ConnectNode(addrConnect, pszDest, fCountFailure, manual_connection, block_relay_only); if (!pnode) return; @@ -2240,7 +2252,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) if (semOutbound == nullptr) { // initialize semaphore - semOutbound = MakeUnique<CSemaphore>(std::min((nMaxOutbound + nMaxFeeler), nMaxConnections)); + semOutbound = MakeUnique<CSemaphore>(std::min(m_max_outbound, nMaxConnections)); } if (semAddnode == nullptr) { // initialize semaphore @@ -2318,7 +2330,7 @@ void CConnman::Interrupt() InterruptSocks5(true); if (semOutbound) { - for (int i=0; i<(nMaxOutbound + nMaxFeeler); i++) { + for (int i=0; i<m_max_outbound; i++) { semOutbound->post(); } } @@ -2628,7 +2640,7 @@ int CConnman::GetBestHeight() const unsigned int CConnman::GetReceiveFloodSize() const { return nReceiveFloodSize; } -CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, bool fInboundIn) +CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, bool fInboundIn, bool block_relay_only) : nTimeConnected(GetSystemTimeInSeconds()), addr(addrIn), addrBind(addrBindIn), @@ -2643,7 +2655,9 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn hSocket = hSocketIn; addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn; hashContinue = uint256(); - m_tx_relay = MakeUnique<TxRelay>(); + if (!block_relay_only) { + m_tx_relay = MakeUnique<TxRelay>(); + } for (const std::string &msg : getAllNetMessageTypes()) mapRecvBytesPerMsgCmd[msg] = 0; @@ -56,10 +56,12 @@ static const unsigned int MAX_ADDR_TO_SEND = 1000; static const unsigned int MAX_PROTOCOL_MESSAGE_LENGTH = 4 * 1000 * 1000; /** Maximum length of the user agent string in `version` message */ static const unsigned int MAX_SUBVERSION_LENGTH = 256; -/** Maximum number of automatic outgoing nodes */ -static const int MAX_OUTBOUND_CONNECTIONS = 8; +/** Maximum number of automatic outgoing nodes over which we'll relay everything (blocks, tx, addrs, etc) */ +static const int MAX_OUTBOUND_FULL_RELAY_CONNECTIONS = 8; /** Maximum number of addnode outgoing nodes */ static const int MAX_ADDNODE_CONNECTIONS = 8; +/** Maximum number of block-relay-only outgoing connections */ +static const int MAX_BLOCKS_ONLY_CONNECTIONS = 2; /** -listen default */ static const bool DEFAULT_LISTEN = true; /** -upnp default */ @@ -126,7 +128,8 @@ public: { ServiceFlags nLocalServices = NODE_NONE; int nMaxConnections = 0; - int nMaxOutbound = 0; + int m_max_outbound_full_relay = 0; + int m_max_outbound_block_relay = 0; int nMaxAddnode = 0; int nMaxFeeler = 0; int nBestHeight = 0; @@ -150,10 +153,12 @@ public: void Init(const Options& connOptions) { nLocalServices = connOptions.nLocalServices; nMaxConnections = connOptions.nMaxConnections; - nMaxOutbound = std::min(connOptions.nMaxOutbound, connOptions.nMaxConnections); + m_max_outbound_full_relay = std::min(connOptions.m_max_outbound_full_relay, connOptions.nMaxConnections); + m_max_outbound_block_relay = connOptions.m_max_outbound_block_relay; m_use_addrman_outgoing = connOptions.m_use_addrman_outgoing; nMaxAddnode = connOptions.nMaxAddnode; nMaxFeeler = connOptions.nMaxFeeler; + m_max_outbound = m_max_outbound_full_relay + m_max_outbound_block_relay + nMaxFeeler; nBestHeight = connOptions.nBestHeight; clientInterface = connOptions.uiInterface; m_banman = connOptions.m_banman; @@ -192,7 +197,7 @@ public: bool GetNetworkActive() const { return fNetworkActive; }; bool GetUseAddrmanOutgoing() const { return m_use_addrman_outgoing; }; void SetNetworkActive(bool active); - void OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = nullptr, const char *strDest = nullptr, bool fOneShot = false, bool fFeeler = false, bool manual_connection = false); + void OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = nullptr, const char *strDest = nullptr, bool fOneShot = false, bool fFeeler = false, bool manual_connection = false, bool block_relay_only = false); bool CheckIncomingNonce(uint64_t nonce); bool ForNode(NodeId id, std::function<bool(CNode* pnode)> func); @@ -248,7 +253,7 @@ public: void AddNewAddresses(const std::vector<CAddress>& vAddr, const CAddress& addrFrom, int64_t nTimePenalty = 0); std::vector<CAddress> GetAddresses(); - // This allows temporarily exceeding nMaxOutbound, with the goal of finding + // This allows temporarily exceeding m_max_outbound_full_relay, with the goal of finding // a peer that is better than all our current peers. void SetTryNewOutboundPeer(bool flag); bool GetTryNewOutboundPeer(); @@ -350,7 +355,7 @@ private: CNode* FindNode(const CService& addr); bool AttemptToEvictConnection(); - CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection); + CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection, bool block_relay_only); void AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const; void DeleteNode(CNode* pnode); @@ -409,9 +414,17 @@ private: std::unique_ptr<CSemaphore> semOutbound; std::unique_ptr<CSemaphore> semAddnode; int nMaxConnections; - int nMaxOutbound; + + // How many full-relay (tx, block, addr) outbound peers we want + int m_max_outbound_full_relay; + + // How many block-relay only outbound peers we want + // We do not relay tx or addr messages with these peers + int m_max_outbound_block_relay; + int nMaxAddnode; int nMaxFeeler; + int m_max_outbound; bool m_use_addrman_outgoing; std::atomic<int> nBestHeight; CClientUIInterface* clientInterface; @@ -437,7 +450,7 @@ private: std::thread threadMessageHandler; /** flag for deciding to connect to an extra outbound peer, - * in excess of nMaxOutbound + * in excess of m_max_outbound_full_relay * This takes the place of a feeler connection */ std::atomic_bool m_try_another_outbound_peer; @@ -756,7 +769,7 @@ public: std::set<uint256> orphan_work_set; - CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn = "", bool fInboundIn = false); + CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn = "", bool fInboundIn = false, bool block_relay_only = false); ~CNode(); CNode(const CNode&) = delete; CNode& operator=(const CNode&) = delete; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 0802cf424d..48a7b91dcb 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -262,7 +262,7 @@ struct CNodeState { bool fSupportsDesiredCmpctVersion; /** State used to enforce CHAIN_SYNC_TIMEOUT - * Only in effect for outbound, non-manual connections, with + * Only in effect for outbound, non-manual, full-relay connections, with * m_protect == false * Algorithm: if a peer's best known block has less work than our tip, * set a timeout CHAIN_SYNC_TIMEOUT seconds in the future: @@ -425,7 +425,7 @@ static void PushNodeVersion(CNode *pnode, CConnman* connman, int64_t nTime) CAddress addrMe = CAddress(CService(), nLocalNodeServices); connman->PushMessage(pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, (uint64_t)nLocalNodeServices, nTime, addrYou, addrMe, - nonce, strSubVersion, nNodeStartingHeight, ::g_relay_txes)); + nonce, strSubVersion, nNodeStartingHeight, ::g_relay_txes && pnode->m_tx_relay != nullptr)); if (fLogIPs) { LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, us=%s, them=%s, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), addrYou.ToString(), nodeid); @@ -757,7 +757,7 @@ void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) } // Returns true for outbound peers, excluding manual connections, feelers, and -// one-shots +// one-shots. static bool IsOutboundDisconnectionCandidate(const CNode *node) { return !(node->fInbound || node->m_manual_connection || node->fFeeler || node->fOneShot); @@ -1772,9 +1772,11 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve } } - if (!pfrom->fDisconnect && IsOutboundDisconnectionCandidate(pfrom) && nodestate->pindexBestKnownBlock != nullptr) { - // If this is an outbound peer, check to see if we should protect + if (!pfrom->fDisconnect && IsOutboundDisconnectionCandidate(pfrom) && nodestate->pindexBestKnownBlock != nullptr && pfrom->m_tx_relay != nullptr) { + // If this is an outbound full-relay peer, check to see if we should protect // it from the bad/lagging chain logic. + // Note that block-relay-only peers are already implicitly protected, so we + // only consider setting m_protect for the full-relay peers. if (g_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && nodestate->pindexBestKnownBlock->nChainWork >= ::ChainActive().Tip()->nChainWork && !nodestate->m_chain_sync.m_protect) { LogPrint(BCLog::NET, "Protecting outbound peer=%d from eviction\n", pfrom->GetId()); nodestate->m_chain_sync.m_protect = true; @@ -2088,9 +2090,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // Mark this node as currently connected, so we update its timestamp later. LOCK(cs_main); State(pfrom->GetId())->fCurrentlyConnected = true; - LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s\n", - pfrom->nVersion.load(), pfrom->nStartingHeight, pfrom->GetId(), - (fLogIPs ? strprintf(", peeraddr=%s", pfrom->addr.ToString()) : "")); + LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s (%s)\n", + pfrom->nVersion.load(), pfrom->nStartingHeight, + pfrom->GetId(), (fLogIPs ? strprintf(", peeraddr=%s", pfrom->addr.ToString()) : ""), + pfrom->m_tx_relay == nullptr ? "block-relay" : "full-relay"); } if (pfrom->nVersion >= SENDHEADERS_VERSION) { @@ -2214,7 +2217,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr return false; } - bool fBlocksOnly = !g_relay_txes; + // We won't accept tx inv's if we're in blocks-only mode, or this is a + // block-relay-only peer + bool fBlocksOnly = !g_relay_txes || (pfrom->m_tx_relay == nullptr); // Allow whitelisted peers to send data other than blocks in blocks only mode if whitelistrelay is true if (pfrom->HasPermission(PF_RELAY)) @@ -3453,6 +3458,8 @@ void PeerLogicValidation::EvictExtraOutboundPeers(int64_t time_in_seconds) if (state == nullptr) return; // shouldn't be possible, but just in case // Don't evict our protected peers if (state->m_chain_sync.m_protect) return; + // Don't evict our block-relay-only peers. + if (pnode->m_tx_relay == nullptr) return; if (state->m_last_block_announcement < oldest_block_announcement || (state->m_last_block_announcement == oldest_block_announcement && pnode->GetId() > worst_peer)) { worst_peer = pnode->GetId(); oldest_block_announcement = state->m_last_block_announcement; diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index a50d6854f8..b0a613372f 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -151,17 +151,17 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, scheduler, false); const Consensus::Params& consensusParams = Params().GetConsensus(); - constexpr int nMaxOutbound = 8; + constexpr int max_outbound_full_relay = 8; CConnman::Options options; options.nMaxConnections = 125; - options.nMaxOutbound = nMaxOutbound; + options.m_max_outbound_full_relay = max_outbound_full_relay; options.nMaxFeeler = 1; connman->Init(options); std::vector<CNode *> vNodes; // Mock some outbound peers - for (int i=0; i<nMaxOutbound; ++i) { + for (int i=0; i<max_outbound_full_relay; ++i) { AddRandomOutboundPeer(vNodes, *peerLogic, connman.get()); } @@ -190,7 +190,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) AddRandomOutboundPeer(vNodes, *peerLogic, connman.get()); peerLogic->CheckForStaleTipAndEvictPeers(consensusParams); - for (int i=0; i<nMaxOutbound; ++i) { + for (int i=0; i<max_outbound_full_relay; ++i) { BOOST_CHECK(vNodes[i]->fDisconnect == false); } // Last added node should get marked for eviction @@ -203,10 +203,10 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) UpdateLastBlockAnnounceTime(vNodes.back()->GetId(), GetTime()); peerLogic->CheckForStaleTipAndEvictPeers(consensusParams); - for (int i=0; i<nMaxOutbound-1; ++i) { + for (int i=0; i<max_outbound_full_relay-1; ++i) { BOOST_CHECK(vNodes[i]->fDisconnect == false); } - BOOST_CHECK(vNodes[nMaxOutbound-1]->fDisconnect == true); + BOOST_CHECK(vNodes[max_outbound_full_relay-1]->fDisconnect == true); BOOST_CHECK(vNodes.back()->fDisconnect == false); bool dummy; |