diff options
Diffstat (limited to 'src/net.cpp')
-rw-r--r-- | src/net.cpp | 478 |
1 files changed, 184 insertions, 294 deletions
diff --git a/src/net.cpp b/src/net.cpp index 9c6d7b6375..4f74bbede4 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -16,6 +16,7 @@ #include <net_permissions.h> #include <netbase.h> #include <node/ui_interface.h> +#include <optional.h> #include <protocol.h> #include <random.h> #include <scheduler.h> @@ -32,15 +33,6 @@ #include <poll.h> #endif -#ifdef USE_UPNP -#include <miniupnpc/miniupnpc.h> -#include <miniupnpc/upnpcommands.h> -#include <miniupnpc/upnperrors.h> -// The minimum supported miniUPnPc API version is set to 10. This keeps compatibility -// with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages. -static_assert(MINIUPNPC_API_VERSION >= 10, "miniUPnPc API version >= 10 assumed"); -#endif - #include <algorithm> #include <cstdint> #include <unordered_map> @@ -72,6 +64,9 @@ static constexpr std::chrono::seconds DNSSEEDS_DELAY_FEW_PEERS{11}; static constexpr std::chrono::minutes DNSSEEDS_DELAY_MANY_PEERS{5}; static constexpr int DNSSEEDS_DELAY_PEER_THRESHOLD = 1000; // "many" vs "few" peers +/** The default timeframe for -maxuploadtarget. 1 day. */ +static constexpr std::chrono::seconds MAX_UPLOAD_TIMEFRAME{60 * 60 * 24}; + // We add a random period time (0 to 1 seconds) to feeler connections to prevent synchronization. #define FEELER_SLEEP_WINDOW 1 @@ -111,7 +106,6 @@ static const uint64_t RANDOMIZER_ID_ADDRCACHE = 0x1cf2e4ddd306dda9ULL; // SHA256 // bool fDiscover = true; bool fListen = true; -bool g_relay_txes = !DEFAULT_BLOCKSONLY; RecursiveMutex cs_mapLocalHost; std::map<CNetAddr, LocalServiceInfo> mapLocalHost GUARDED_BY(cs_mapLocalHost); static bool vfLimited[NET_MAX] GUARDED_BY(cs_mapLocalHost) = {}; @@ -478,7 +472,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 : "", conn_type); + CNode* pnode = new CNode(id, nLocalServices, hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", conn_type); pnode->AddRef(); // We're making a new connection, harvest entropy from the time (and our peer count) @@ -504,9 +498,9 @@ void CConnman::AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNet } } -std::string CNode::ConnectionTypeAsString() const +std::string ConnectionTypeAsString(ConnectionType conn_type) { - switch (m_conn_type) { + switch (conn_type) { case ConnectionType::INBOUND: return "inbound"; case ConnectionType::MANUAL: @@ -552,7 +546,7 @@ void CNode::SetAddrLocal(const CService& addrLocalIn) { Network CNode::ConnectedThroughNetwork() const { - return IsInboundConn() && m_inbound_onion ? NET_ONION : addr.GetNetClass(); + return m_inbound_onion ? NET_ONION : addr.GetNetClass(); } #undef X @@ -563,7 +557,7 @@ void CNode::copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap) X(nServices); X(addr); X(addrBind); - stats.m_network = GetNetworkName(ConnectedThroughNetwork()); + stats.m_network = ConnectedThroughNetwork(); stats.m_mapped_as = addr.GetMappedAS(m_asmap); if (m_tx_relay != nullptr) { LOCK(m_tx_relay->cs_filter); @@ -584,8 +578,8 @@ void CNode::copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap) X(cleanSubVer); } stats.fInbound = IsInboundConn(); - stats.m_manual_connection = IsManualConn(); - X(nStartingHeight); + X(m_bip152_highbandwidth_to); + X(m_bip152_highbandwidth_from); { LOCK(cs_vSend); X(mapSendBytesPerMsgCmd); @@ -596,10 +590,8 @@ void CNode::copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap) X(mapRecvBytesPerMsgCmd); X(nRecvBytes); } - X(m_legacyWhitelisted); X(m_permissionFlags); if (m_tx_relay != nullptr) { - LOCK(m_tx_relay->cs_feeFilter); stats.minFeeFilter = m_tx_relay->minFeeFilter; } else { stats.minFeeFilter = 0; @@ -625,7 +617,7 @@ void CNode::copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap) CService addrLocalUnlocked = GetAddrLocal(); stats.addrLocal = addrLocalUnlocked.IsValid() ? addrLocalUnlocked.ToString() : ""; - stats.m_conn_type_string = ConnectionTypeAsString(); + X(m_conn_type); } #undef X @@ -788,30 +780,30 @@ void V1TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vec CVectorWriter{SER_NETWORK, INIT_PROTO_VERSION, header, 0, hdr}; } -size_t CConnman::SocketSendData(CNode *pnode) const EXCLUSIVE_LOCKS_REQUIRED(pnode->cs_vSend) +size_t CConnman::SocketSendData(CNode& node) const { - auto it = pnode->vSendMsg.begin(); + auto it = node.vSendMsg.begin(); size_t nSentSize = 0; - while (it != pnode->vSendMsg.end()) { - const auto &data = *it; - assert(data.size() > pnode->nSendOffset); + while (it != node.vSendMsg.end()) { + const auto& data = *it; + assert(data.size() > node.nSendOffset); int nBytes = 0; { - LOCK(pnode->cs_hSocket); - if (pnode->hSocket == INVALID_SOCKET) + LOCK(node.cs_hSocket); + if (node.hSocket == INVALID_SOCKET) break; - nBytes = send(pnode->hSocket, reinterpret_cast<const char*>(data.data()) + pnode->nSendOffset, data.size() - pnode->nSendOffset, MSG_NOSIGNAL | MSG_DONTWAIT); + nBytes = send(node.hSocket, reinterpret_cast<const char*>(data.data()) + node.nSendOffset, data.size() - node.nSendOffset, MSG_NOSIGNAL | MSG_DONTWAIT); } if (nBytes > 0) { - pnode->nLastSend = GetSystemTimeInSeconds(); - pnode->nSendBytes += nBytes; - pnode->nSendOffset += nBytes; + node.nLastSend = GetSystemTimeInSeconds(); + node.nSendBytes += nBytes; + node.nSendOffset += nBytes; nSentSize += nBytes; - if (pnode->nSendOffset == data.size()) { - pnode->nSendOffset = 0; - pnode->nSendSize -= data.size(); - pnode->fPauseSend = pnode->nSendSize > nSendBufferMaxSize; + if (node.nSendOffset == data.size()) { + node.nSendOffset = 0; + node.nSendSize -= data.size(); + node.fPauseSend = node.nSendSize > nSendBufferMaxSize; it++; } else { // could not send full message; stop sending more @@ -821,10 +813,9 @@ size_t CConnman::SocketSendData(CNode *pnode) const EXCLUSIVE_LOCKS_REQUIRED(pno if (nBytes < 0) { // error int nErr = WSAGetLastError(); - if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS) - { + if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS) { LogPrintf("socket send error %s\n", NetworkErrorString(nErr)); - pnode->CloseSocketDisconnect(); + node.CloseSocketDisconnect(); } } // couldn't send anything at all @@ -832,29 +823,14 @@ size_t CConnman::SocketSendData(CNode *pnode) const EXCLUSIVE_LOCKS_REQUIRED(pno } } - if (it == pnode->vSendMsg.end()) { - assert(pnode->nSendOffset == 0); - assert(pnode->nSendSize == 0); + if (it == node.vSendMsg.end()) { + assert(node.nSendOffset == 0); + assert(node.nSendSize == 0); } - pnode->vSendMsg.erase(pnode->vSendMsg.begin(), it); + node.vSendMsg.erase(node.vSendMsg.begin(), it); return nSentSize; } -struct NodeEvictionCandidate -{ - NodeId id; - int64_t nTimeConnected; - int64_t nMinPingUsecTime; - int64_t nLastBlockTime; - int64_t nLastTXTime; - bool fRelevantServices; - bool fRelayTxes; - bool fBloomFilter; - uint64_t nKeyedNetGroup; - bool prefer_evict; - bool m_is_local; -}; - static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { return a.nMinPingUsecTime > b.nMinPingUsecTime; @@ -910,43 +886,8 @@ static void EraseLastKElements(std::vector<T> &elements, Comparator comparator, elements.erase(elements.end() - eraseSize, elements.end()); } -/** Try to find a connection to evict when the node is full. - * Extreme care must be taken to avoid opening the node to attacker - * triggered network partitioning. - * The strategy used here is to protect a small number of peers - * for each of several distinct characteristics which are difficult - * to forge. In order to partition a node the attacker must be - * simultaneously better at all of them than honest peers. - */ -bool CConnman::AttemptToEvictConnection() +[[nodiscard]] Optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates) { - std::vector<NodeEvictionCandidate> vEvictionCandidates; - { - LOCK(cs_vNodes); - - for (const CNode* node : vNodes) { - if (node->HasPermission(PF_NOBAN)) - continue; - if (!node->IsInboundConn()) - continue; - if (node->fDisconnect) - continue; - bool peer_relay_txes = false; - bool peer_filter_not_null = false; - if (node->m_tx_relay != nullptr) { - LOCK(node->m_tx_relay->cs_filter); - peer_relay_txes = node->m_tx_relay->fRelayTxes; - peer_filter_not_null = node->m_tx_relay->pfilter != nullptr; - } - NodeEvictionCandidate candidate = {node->GetId(), node->nTimeConnected, node->nMinPingUsecTime, - node->nLastBlockTime, node->nLastTXTime, - HasAllDesirableServiceFlags(node->nServices), - peer_relay_txes, peer_filter_not_null, node->nKeyedNetGroup, - node->m_prefer_evict, node->addr.IsLocal()}; - vEvictionCandidates.push_back(candidate); - } - } - // Protect connections with certain characteristics // Deterministically select 4 peers to protect by netgroup. @@ -984,7 +925,7 @@ bool CConnman::AttemptToEvictConnection() total_protect_size -= initial_size - vEvictionCandidates.size(); EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, total_protect_size); - if (vEvictionCandidates.empty()) return false; + if (vEvictionCandidates.empty()) return nullopt; // If any remaining peers are preferred for eviction consider only them. // This happens after the other preferences since if a peer is really the best by other criteria (esp relaying blocks) @@ -1016,10 +957,52 @@ bool CConnman::AttemptToEvictConnection() vEvictionCandidates = std::move(mapNetGroupNodes[naMostConnections]); // Disconnect from the network group with the most connections - NodeId evicted = vEvictionCandidates.front().id; + return vEvictionCandidates.front().id; +} + +/** Try to find a connection to evict when the node is full. + * Extreme care must be taken to avoid opening the node to attacker + * triggered network partitioning. + * The strategy used here is to protect a small number of peers + * for each of several distinct characteristics which are difficult + * to forge. In order to partition a node the attacker must be + * simultaneously better at all of them than honest peers. + */ +bool CConnman::AttemptToEvictConnection() +{ + std::vector<NodeEvictionCandidate> vEvictionCandidates; + { + + LOCK(cs_vNodes); + for (const CNode* node : vNodes) { + if (node->HasPermission(PF_NOBAN)) + continue; + if (!node->IsInboundConn()) + continue; + if (node->fDisconnect) + continue; + bool peer_relay_txes = false; + bool peer_filter_not_null = false; + if (node->m_tx_relay != nullptr) { + LOCK(node->m_tx_relay->cs_filter); + peer_relay_txes = node->m_tx_relay->fRelayTxes; + peer_filter_not_null = node->m_tx_relay->pfilter != nullptr; + } + NodeEvictionCandidate candidate = {node->GetId(), node->nTimeConnected, node->nMinPingUsecTime, + node->nLastBlockTime, node->nLastTXTime, + HasAllDesirableServiceFlags(node->nServices), + peer_relay_txes, peer_filter_not_null, node->nKeyedNetGroup, + node->m_prefer_evict, node->addr.IsLocal()}; + vEvictionCandidates.push_back(candidate); + } + } + const Optional<NodeId> node_id_to_evict = SelectNodeToEvict(std::move(vEvictionCandidates)); + if (!node_id_to_evict) { + return false; + } LOCK(cs_vNodes); for (CNode* pnode : vNodes) { - if (pnode->GetId() == evicted) { + if (pnode->GetId() == *node_id_to_evict) { pnode->fDisconnect = true; return true; } @@ -1044,14 +1027,12 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { NetPermissionFlags permissionFlags = NetPermissionFlags::PF_NONE; hListenSocket.AddSocketPermissionFlags(permissionFlags); AddWhitelistPermissionFlags(permissionFlags, addr); - bool legacyWhitelisted = false; if (NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_ISIMPLICIT)) { NetPermissions::ClearFlag(permissionFlags, PF_ISIMPLICIT); if (gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) NetPermissions::AddFlag(permissionFlags, PF_FORCERELAY); if (gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) NetPermissions::AddFlag(permissionFlags, PF_RELAY); NetPermissions::AddFlag(permissionFlags, PF_MEMPOOL); NetPermissions::AddFlag(permissionFlags, PF_NOBAN); - legacyWhitelisted = true; } { @@ -1124,11 +1105,9 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { } const bool inbound_onion = std::find(m_onion_binds.begin(), m_onion_binds.end(), addr_bind) != m_onion_binds.end(); - CNode* pnode = new CNode(id, nodeServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", ConnectionType::INBOUND, inbound_onion); + CNode* pnode = new CNode(id, nodeServices, hSocket, addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", ConnectionType::INBOUND, inbound_onion); pnode->AddRef(); pnode->m_permissionFlags = permissionFlags; - // If this flag is present, the user probably expect that RPC and QT report it as whitelisted (backward compatibility) - pnode->m_legacyWhitelisted = legacyWhitelisted; pnode->m_prefer_evict = discouraged; m_msgproc->InitializeNode(pnode); @@ -1143,6 +1122,27 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { RandAddEvent((uint32_t)id); } +bool CConnman::AddConnection(const std::string& address, ConnectionType conn_type) +{ + if (conn_type != ConnectionType::OUTBOUND_FULL_RELAY && conn_type != ConnectionType::BLOCK_RELAY) return false; + + const int max_connections = conn_type == ConnectionType::OUTBOUND_FULL_RELAY ? m_max_outbound_full_relay : m_max_outbound_block_relay; + + // Count existing connections + int existing_connections = WITH_LOCK(cs_vNodes, + return std::count_if(vNodes.begin(), vNodes.end(), [conn_type](CNode* node) { return node->m_conn_type == conn_type; });); + + // Max connections of specified type already exist + if (existing_connections >= max_connections) return false; + + // Max total outbound connections already exist + CSemaphoreGrant grant(*semOutbound, true); + if (!grant) return false; + + OpenNetworkConnection(CAddress(), false, &grant, address.c_str(), conn_type); + return true; +} + void CConnman::DisconnectNodes() { { @@ -1216,7 +1216,7 @@ void CConnman::NotifyNumConnectionsChanged() } } -void CConnman::InactivityCheck(CNode *pnode) +void CConnman::InactivityCheck(CNode *pnode) const { int64_t nTime = GetSystemTimeInSeconds(); if (nTime - pnode->nTimeConnected > m_peer_connect_timeout) @@ -1231,7 +1231,7 @@ void CConnman::InactivityCheck(CNode *pnode) LogPrintf("socket sending timeout: %is\n", nTime - pnode->nLastSend); pnode->fDisconnect = true; } - else if (nTime - pnode->nLastRecv > (pnode->GetCommonVersion() > BIP0031_VERSION ? TIMEOUT_INTERVAL : 90*60)) + else if (nTime - pnode->nLastRecv > TIMEOUT_INTERVAL) { LogPrintf("socket receive timeout: %is\n", nTime - pnode->nLastRecv); pnode->fDisconnect = true; @@ -1516,16 +1516,10 @@ void CConnman::SocketHandler() } } - // - // Send - // - if (sendSet) - { - LOCK(pnode->cs_vSend); - size_t nBytes = SocketSendData(pnode); - if (nBytes) { - RecordBytesSent(nBytes); - } + if (sendSet) { + // Send data + size_t bytes_sent = WITH_LOCK(pnode->cs_vSend, return SocketSendData(*pnode)); + if (bytes_sent) RecordBytesSent(bytes_sent); } InactivityCheck(pnode); @@ -1556,121 +1550,6 @@ void CConnman::WakeMessageHandler() condMsgProc.notify_one(); } - - - - - -#ifdef USE_UPNP -static CThreadInterrupt g_upnp_interrupt; -static std::thread g_upnp_thread; -static void ThreadMapPort() -{ - std::string port = strprintf("%u", GetListenPort()); - const char * multicastif = nullptr; - const char * minissdpdpath = nullptr; - struct UPNPDev * devlist = nullptr; - char lanaddr[64]; - - int error = 0; -#if MINIUPNPC_API_VERSION < 14 - devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, &error); -#else - devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error); -#endif - - struct UPNPUrls urls; - struct IGDdatas data; - int r; - - r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr)); - if (r == 1) - { - if (fDiscover) { - char externalIPAddress[40]; - r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress); - if (r != UPNPCOMMAND_SUCCESS) { - LogPrintf("UPnP: GetExternalIPAddress() returned %d\n", r); - } else { - if (externalIPAddress[0]) { - CNetAddr resolved; - if (LookupHost(externalIPAddress, resolved, false)) { - LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved.ToString()); - AddLocal(resolved, LOCAL_UPNP); - } - } else { - LogPrintf("UPnP: GetExternalIPAddress failed.\n"); - } - } - } - - std::string strDesc = PACKAGE_NAME " " + FormatFullVersion(); - - do { - r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0, "0"); - - if (r != UPNPCOMMAND_SUCCESS) { - LogPrintf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", port, port, lanaddr, r, strupnperror(r)); - } else { - LogPrintf("UPnP Port Mapping successful.\n"); - } - } while (g_upnp_interrupt.sleep_for(std::chrono::minutes(20))); - - r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", 0); - LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r); - freeUPNPDevlist(devlist); devlist = nullptr; - FreeUPNPUrls(&urls); - } else { - LogPrintf("No valid UPnP IGDs found\n"); - freeUPNPDevlist(devlist); devlist = nullptr; - if (r != 0) - FreeUPNPUrls(&urls); - } -} - -void StartMapPort() -{ - if (!g_upnp_thread.joinable()) { - assert(!g_upnp_interrupt); - g_upnp_thread = std::thread((std::bind(&TraceThread<void (*)()>, "upnp", &ThreadMapPort))); - } -} - -void InterruptMapPort() -{ - if(g_upnp_thread.joinable()) { - g_upnp_interrupt(); - } -} - -void StopMapPort() -{ - if(g_upnp_thread.joinable()) { - g_upnp_thread.join(); - g_upnp_interrupt.reset(); - } -} - -#else -void StartMapPort() -{ - // Intentionally left blank. -} -void InterruptMapPort() -{ - // Intentionally left blank. -} -void StopMapPort() -{ - // Intentionally left blank. -} -#endif - - - - - - void CConnman::ThreadDNSAddressSeed() { FastRandomContext rng; @@ -1826,18 +1705,32 @@ void CConnman::SetTryNewOutboundPeer(bool flag) // Also exclude peers that haven't finished initial connection handshake yet // (so that we don't decide we're over our desired connection limit, and then // evict some peer that has finished the handshake) -int CConnman::GetExtraOutboundCount() +int CConnman::GetExtraFullOutboundCount() { - int nOutbound = 0; + int full_outbound_peers = 0; { LOCK(cs_vNodes); for (const CNode* pnode : vNodes) { - if (pnode->fSuccessfullyConnected && !pnode->fDisconnect && pnode->IsOutboundOrBlockRelayConn()) { - ++nOutbound; + if (pnode->fSuccessfullyConnected && !pnode->fDisconnect && pnode->IsFullOutboundConn()) { + ++full_outbound_peers; } } } - return std::max(nOutbound - m_max_outbound_full_relay - m_max_outbound_block_relay, 0); + return std::max(full_outbound_peers - m_max_outbound_full_relay, 0); +} + +int CConnman::GetExtraBlockRelayCount() +{ + int block_relay_peers = 0; + { + LOCK(cs_vNodes); + for (const CNode* pnode : vNodes) { + if (pnode->fSuccessfullyConnected && !pnode->fDisconnect && pnode->IsBlockOnlyConn()) { + ++block_relay_peers; + } + } + } + return std::max(block_relay_peers - m_max_outbound_block_relay, 0); } void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) @@ -1868,6 +1761,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) // Minimum time before next feeler connection (in microseconds). int64_t nNextFeeler = PoissonNextSend(nStart*1000*1000, FEELER_INTERVAL); + int64_t nNextExtraBlockRelay = PoissonNextSend(nStart*1000*1000, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); while (!interruptNet) { ProcessAddrFetch(); @@ -1940,8 +1834,9 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) // until we hit our block-relay-only peer limit. // GetTryNewOutboundPeer() gets set when a stale tip is detected, so we // try opening an additional OUTBOUND_FULL_RELAY connection. If none of - // these conditions are met, check the nNextFeeler timer to decide if - // we should open a FEELER. + // these conditions are met, check to see if it's time to try an extra + // block-relay-only peer (to confirm our tip is current, see below) or the nNextFeeler + // timer to decide if we should open a FEELER. if (!m_anchors.empty() && (nOutboundBlockRelay < m_max_outbound_block_relay)) { conn_type = ConnectionType::BLOCK_RELAY; @@ -1952,6 +1847,30 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) conn_type = ConnectionType::BLOCK_RELAY; } else if (GetTryNewOutboundPeer()) { // OUTBOUND_FULL_RELAY + } else if (nTime > nNextExtraBlockRelay && m_start_extra_block_relay_peers) { + // Periodically connect to a peer (using regular outbound selection + // methodology from addrman) and stay connected long enough to sync + // headers, but not much else. + // + // Then disconnect the peer, if we haven't learned anything new. + // + // The idea is to make eclipse attacks very difficult to pull off, + // because every few minutes we're finding a new peer to learn headers + // from. + // + // This is similar to the logic for trying extra outbound (full-relay) + // peers, except: + // - we do this all the time on a poisson timer, rather than just when + // our tip is stale + // - we potentially disconnect our next-youngest block-relay-only peer, if our + // newest block-relay-only peer delivers a block more recently. + // See the eviction logic in net_processing.cpp. + // + // Because we can promote these connections to block-relay-only + // connections, they do not get their own ConnectionType enum + // (similar to how we deal with extra outbound peers). + nNextExtraBlockRelay = PoissonNextSend(nTime, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); + conn_type = ConnectionType::BLOCK_RELAY; } else if (nTime > nNextFeeler) { nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL); conn_type = ConnectionType::FEELER; @@ -2037,7 +1956,11 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) continue; } - // do not allow non-default ports, unless after 50 invalid addresses selected already + // Do not allow non-default ports, unless after 50 invalid + // addresses selected already. This is to prevent malicious peers + // 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) continue; @@ -2806,8 +2729,8 @@ void CConnman::RecordBytesSent(uint64_t bytes) LOCK(cs_totalBytesSent); nTotalBytesSent += bytes; - uint64_t now = GetTime(); - if (nMaxOutboundCycleStartTime + nMaxOutboundTimeframe < now) + const auto now = GetTime<std::chrono::seconds>(); + if (nMaxOutboundCycleStartTime + MAX_UPLOAD_TIMEFRAME < now) { // timeframe expired, reset cycle nMaxOutboundCycleStartTime = now; @@ -2818,48 +2741,29 @@ void CConnman::RecordBytesSent(uint64_t bytes) nMaxOutboundTotalBytesSentInCycle += bytes; } -void CConnman::SetMaxOutboundTarget(uint64_t limit) -{ - LOCK(cs_totalBytesSent); - nMaxOutboundLimit = limit; -} - uint64_t CConnman::GetMaxOutboundTarget() { LOCK(cs_totalBytesSent); return nMaxOutboundLimit; } -uint64_t CConnman::GetMaxOutboundTimeframe() +std::chrono::seconds CConnman::GetMaxOutboundTimeframe() { - LOCK(cs_totalBytesSent); - return nMaxOutboundTimeframe; + return MAX_UPLOAD_TIMEFRAME; } -uint64_t CConnman::GetMaxOutboundTimeLeftInCycle() +std::chrono::seconds CConnman::GetMaxOutboundTimeLeftInCycle() { LOCK(cs_totalBytesSent); if (nMaxOutboundLimit == 0) - return 0; + return 0s; - if (nMaxOutboundCycleStartTime == 0) - return nMaxOutboundTimeframe; - - uint64_t cycleEndTime = nMaxOutboundCycleStartTime + nMaxOutboundTimeframe; - uint64_t now = GetTime(); - return (cycleEndTime < now) ? 0 : cycleEndTime - GetTime(); -} + if (nMaxOutboundCycleStartTime.count() == 0) + return MAX_UPLOAD_TIMEFRAME; -void CConnman::SetMaxOutboundTimeframe(uint64_t timeframe) -{ - LOCK(cs_totalBytesSent); - if (nMaxOutboundTimeframe != timeframe) - { - // reset measure-cycle in case of changing - // the timeframe - nMaxOutboundCycleStartTime = GetTime(); - } - nMaxOutboundTimeframe = timeframe; + const std::chrono::seconds cycleEndTime = nMaxOutboundCycleStartTime + MAX_UPLOAD_TIMEFRAME; + const auto now = GetTime<std::chrono::seconds>(); + return (cycleEndTime < now) ? 0s : cycleEndTime - now; } bool CConnman::OutboundTargetReached(bool historicalBlockServingLimit) @@ -2871,8 +2775,8 @@ bool CConnman::OutboundTargetReached(bool historicalBlockServingLimit) if (historicalBlockServingLimit) { // keep a large enough buffer to at least relay each block once - uint64_t timeLeftInCycle = GetMaxOutboundTimeLeftInCycle(); - uint64_t buffer = timeLeftInCycle / 600 * MAX_BLOCK_SERIALIZED_SIZE; + const std::chrono::seconds timeLeftInCycle = GetMaxOutboundTimeLeftInCycle(); + const uint64_t buffer = timeLeftInCycle / std::chrono::minutes{10} * MAX_BLOCK_SERIALIZED_SIZE; if (buffer >= nMaxOutboundLimit || nMaxOutboundTotalBytesSentInCycle >= nMaxOutboundLimit - buffer) return true; } @@ -2908,36 +2812,22 @@ ServiceFlags CConnman::GetLocalServices() const return nLocalServices; } -void CConnman::SetBestHeight(int height) -{ - nBestHeight.store(height, std::memory_order_release); -} - -int CConnman::GetBestHeight() const -{ - return nBestHeight.load(std::memory_order_acquire); -} - 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, ConnectionType conn_type_in, bool inbound_onion) +CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, ConnectionType conn_type_in, bool inbound_onion) : nTimeConnected(GetSystemTimeInSeconds()), - addr(addrIn), - addrBind(addrBindIn), - nKeyedNetGroup(nKeyedNetGroupIn), - // Don't relay addr messages to peers that we connect to as block-relay-only - // peers (to prevent adversaries from inferring these links from addr - // traffic). - id(idIn), - nLocalHostNonce(nLocalHostNonceIn), - m_conn_type(conn_type_in), - nLocalServices(nLocalServicesIn), - nMyStartingHeight(nMyStartingHeightIn), - m_inbound_onion(inbound_onion) -{ + addr(addrIn), + addrBind(addrBindIn), + nKeyedNetGroup(nKeyedNetGroupIn), + id(idIn), + nLocalHostNonce(nLocalHostNonceIn), + m_conn_type(conn_type_in), + nLocalServices(nLocalServicesIn), + m_inbound_onion(inbound_onion) +{ + if (inbound_onion) assert(conn_type_in == ConnectionType::INBOUND); hSocket = hSocketIn; addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn; - hashContinue = uint256(); if (conn_type_in != ConnectionType::BLOCK_RELAY) { m_tx_relay = MakeUnique<TxRelay>(); } @@ -2997,7 +2887,7 @@ void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg) // If write queue empty, attempt "optimistic write" if (optimisticSend == true) - nBytesSent = SocketSendData(pnode); + nBytesSent = SocketSendData(*pnode); } if (nBytesSent) RecordBytesSent(nBytesSent); |