From c90323865a7b30f93c750a0747b23f6fe85933b1 Mon Sep 17 00:00:00 2001 From: Duke Leto Date: Fri, 5 Feb 2021 06:04:16 -0500 Subject: [PATCH 1/4] Shitty VPS limiting disk i/o should not crash a full node... --- src/main.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 286ef28b0..c37b704ba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6740,16 +6740,12 @@ void static ProcessGetData(CNode* pfrom) } // Pruned nodes may have deleted the block, so check whether // it's available before trying to send. - if (send && (mi->second->nStatus & BLOCK_HAVE_DATA)) - { + if (send && (mi->second->nStatus & BLOCK_HAVE_DATA)) { // Send block from disk CBlock block; - if (!ReadBlockFromDisk(block, (*mi).second,1)) - { - assert(!"cannot load block from disk"); - } - else - { + if (!ReadBlockFromDisk(block, (*mi).second,1)) { + fprintf(stderr,"Cannot load block from disk, disk I/O may be limited by VPS or failing hard drive..."); + } else { if (inv.type == MSG_BLOCK) { //uint256 hash; int32_t z; From cadab16cdbeb98caae7190eb403c0c99f8ae795a Mon Sep 17 00:00:00 2001 From: Duke Leto Date: Sun, 28 Feb 2021 09:40:59 -0500 Subject: [PATCH 2/4] Revert "Shitty VPS limiting disk i/o should not crash a full node..." This reverts commit c90323865a7b30f93c750a0747b23f6fe85933b1. Turns out this is a bad idea. --- src/main.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index c37b704ba..286ef28b0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6740,12 +6740,16 @@ void static ProcessGetData(CNode* pfrom) } // Pruned nodes may have deleted the block, so check whether // it's available before trying to send. - if (send && (mi->second->nStatus & BLOCK_HAVE_DATA)) { + if (send && (mi->second->nStatus & BLOCK_HAVE_DATA)) + { // Send block from disk CBlock block; - if (!ReadBlockFromDisk(block, (*mi).second,1)) { - fprintf(stderr,"Cannot load block from disk, disk I/O may be limited by VPS or failing hard drive..."); - } else { + if (!ReadBlockFromDisk(block, (*mi).second,1)) + { + assert(!"cannot load block from disk"); + } + else + { if (inv.type == MSG_BLOCK) { //uint256 hash; int32_t z; From ea2b68c1d32db51e3b7fb1d5429be33158950025 Mon Sep 17 00:00:00 2001 From: Duke Leto Date: Sun, 28 Feb 2021 23:28:49 -0500 Subject: [PATCH 3/4] Feeler connections ported from BTC core, eclipse attack mitigation --- src/main.cpp | 21 ++++---- src/net.cpp | 103 ++++++++++++++++++++++++++++--------- src/net.h | 14 ++--- src/test/netbase_tests.cpp | 31 ++++++++++- 4 files changed, 126 insertions(+), 43 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 286ef28b0..18b96aba5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6862,10 +6862,16 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, return true; } - //fprintf(stderr,"netmsg: %s\n", strCommand.c_str()); + fprintf(stderr,"%s: netmsg: %s from %s\n", __func__, strCommand.c_str(), pfrom->addr.ToString().c_str() ); + + if (strCommand == "version") { + // Feeler connections exist only to verify if node is online + if (pfrom->fFeeler) { + assert(pfrom->fInbound == false); + pfrom->fDisconnect = true; + fprintf(stderr,"%s: disconnecting feelerconn from %s\n", __func__, pfrom->addr.ToString().c_str() ); + } - if (strCommand == "version") - { // Each connection can only send one version message if (pfrom->nVersion != 0) { @@ -6978,15 +6984,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, } } - // Do Not Relay alerts - /* - { - LOCK(cs_mapAlerts); - BOOST_FOREACH(PAIRTYPE(const uint256, CAlert)& item, mapAlerts) - item.second.RelayTo(pfrom); - } - */ - pfrom->fSuccessfullyConnected = true; string remoteAddr; diff --git a/src/net.cpp b/src/net.cpp index 67a9f6cbb..e2e7e71a7 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -62,6 +62,9 @@ using namespace hush; #endif #endif +// We add a random period time (0 to 1 seconds) to feeler connections to prevent synchronization. +#define FEELER_SLEEP_WINDOW 1 + #define USE_TLS "encrypted as fuck" #if defined(USE_TLS) && !defined(TLS1_3_VERSION) @@ -75,6 +78,7 @@ using namespace std; namespace { //TODO: Make these CLI args const int MAX_OUTBOUND_CONNECTIONS = 64; + const int MAX_FEELER_CONNECTIONS = 1; const int MAX_INBOUND_FROMIP = 3; struct ListenSocket { @@ -1023,8 +1027,8 @@ static void AcceptConnection(const ListenSocket& hListenSocket) { socklen_t len = sizeof(sockaddr); SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len); CAddress addr; - int nInbound = 0; - int nMaxInbound = nMaxConnections - MAX_OUTBOUND_CONNECTIONS; + int nInbound = 0; + int nMaxInbound = nMaxConnections - (MAX_OUTBOUND_CONNECTIONS + MAX_FEELER_CONNECTIONS); if (hSocket != INVALID_SOCKET) if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) @@ -1412,6 +1416,11 @@ void static ProcessOneShot() } } +int64_t PoissonNextSend(int64_t now, int average_interval_seconds) +{ + return now + (int64_t)(log1p(GetRand(1ULL << 48) * -0.0000000000000035527136788 /* -1/2^48 */) * average_interval_seconds * -1000000.0 + 0.5); +} + void ThreadOpenConnections() { // Connect to specific addresses @@ -1436,8 +1445,11 @@ void ThreadOpenConnections() // Initiate network connections int64_t nStart = GetTime(); - while (true) - { + + // Minimum time before next feeler connection (in microseconds). + int64_t nNextFeeler = PoissonNextSend(nStart*1000*1000, FEELER_INTERVAL); + + while (true) { ProcessOneShot(); MilliSleep(500); @@ -1450,19 +1462,17 @@ void ThreadOpenConnections() if (GetTime() - nStart > 60) { static bool done = false; if (!done) { - // skip DNS seeds for staked chains. LogPrintf("Adding fixed seed nodes.\n"); addrman.Add(convertSeed6(Params().FixedSeeds()), CNetAddr("127.0.0.1")); done = true; } } - // Choose an address to connect to based on most recently seen CAddress addrConnect; - // Only connect out to one peer per network group (/16 for IPv4). - // Use -asmap for ASN bucketing + // Only connect out to one peer per network group. Originally /16 for IPv4, now ASNs via + // -asmap for ASN bucketing, which is enabled by default // Do this here so we don't have to critsect vNodes inside mapAddresses critsect. int nOutbound = 0; set > setConnected; @@ -1475,13 +1485,38 @@ void ThreadOpenConnections() } } } + assert(nOutbound <= (MAX_OUTBOUND_CONNECTIONS + MAX_FEELER_CONNECTIONS)); + + // "Feeler Connections" as per https://eprint.iacr.org/2015/263.pdf + // "Eclipse Attacks on Bitcoin’s Peer-to-Peer Network" by + // Ethan Heilman, Alison Kendler, Aviv Zohar, Sharon Goldberg. + // + // Design goals: + // * Increase the number of connectable addresses in the tried table. + // + // Method: + // * Choose a random address from new and attempt to connect to it if we can connect + // successfully it is added to tried. + // * Start attempting feeler connections only after node finishes making outbound + // connections. + // * Make feeler connections randomly with 120s average interval via PoissonNextSend. + // Originally from https://github.com/bitcoin/bitcoin/pull/8282 + // Modified for API changes by Duke Leto + bool fFeeler = false; + if (nOutbound >= MAX_OUTBOUND_CONNECTIONS) { + int64_t nTime = GetTimeMicros(); // The current time right now (in microseconds). + if (nTime > nNextFeeler) { + nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL); + fFeeler = true; + } else { + continue; + } + } int64_t nANow = GetTime(); - - int nTries = 0; - while (true) - { - CAddrInfo addr = addrman.Select(); + int nTries = 0; + while (true) { + CAddrInfo addr = addrman.Select(fFeeler); // if we selected an invalid address, restart if (!addr.IsValid() || setConnected.count(addr.GetGroup(addrman.m_asmap)) || IsLocal(addr)) @@ -1501,6 +1536,13 @@ void ThreadOpenConnections() if (nANow - addr.nLastTry < 600 && nTries < 30) continue; + /* TODO: port this code + // only consider nodes missing relevant services after 40 failed attempts + if ((addr.nServices & nRelevantServices) != nRelevantServices && nTries < 40) + continue; + */ + + //TODO: why is this a good thing? // do not allow non-default ports, unless after 50 invalid addresses selected already if (addr.GetPort() != Params().GetDefaultPort() && nTries < 50) continue; @@ -1509,8 +1551,18 @@ void ThreadOpenConnections() break; } - if (addrConnect.IsValid()) - OpenNetworkConnection(addrConnect, &grant); + if (addrConnect.IsValid()) { + if (fFeeler) { + // Add small amount of random noise before connection to avoid synchronization + int randsleep = GetRandInt(FEELER_SLEEP_WINDOW * 1000); + MilliSleep(randsleep); + LogPrint("net", "Making feeler connection to %s\n", addrConnect.ToString().c_str()); + printf("%s: Making feeler connection to %s\n", __func__, addrConnect.ToString().c_str()); + } + + //int failures = setConnected.size() >= std::min(nMaxConnections - 1, 2); + OpenNetworkConnection(addrConnect,/*failures,*/ &grant, NULL, false, fFeeler); + } } } @@ -1591,7 +1643,7 @@ void ThreadOpenAddedConnections() } // if successful, this moves the passed grant to the constructed node -bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot) +bool OpenNetworkConnection(const CAddress& addrConnect, /* bool fCountFailure, */ CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot, bool fFeeler) { // Initiate outbound network connection boost::this_thread::interruption_point(); @@ -1604,6 +1656,7 @@ bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOu return false; CNode* pnode = ConnectNode(addrConnect, pszDest); + //CNode* pnode = ConnectNode(addrConnect, pszDest, fCountFailure); boost::this_thread::interruption_point(); if (!pnode) @@ -1611,8 +1664,11 @@ bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOu if (grantOutbound) grantOutbound->MoveTo(pnode->grantOutbound); pnode->fNetworkNode = true; + if (fOneShot) pnode->fOneShot = true; + if (fFeeler) + pnode->fFeeler = true; return true; } @@ -1858,7 +1914,7 @@ void StartNode(boost::thread_group& threadGroup, CScheduler& scheduler) if (semOutbound == NULL) { // initialize semaphore - int nMaxOutbound = min(MAX_OUTBOUND_CONNECTIONS, nMaxConnections); + int nMaxOutbound = min((MAX_OUTBOUND_CONNECTIONS + MAX_FEELER_CONNECTIONS), nMaxConnections); semOutbound = new CSemaphore(nMaxOutbound); } @@ -1907,7 +1963,7 @@ bool StopNode() { LogPrintf("StopNode()\n"); if (semOutbound) - for (int i=0; ipost(); if (HUSH_NSPV_FULLNODE && fAddressesInitialized) @@ -2166,12 +2222,13 @@ CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNa nTimeOffset = 0; addr = addrIn; addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn; - nVersion = 0; - strSubVer = ""; + nVersion = 0; + strSubVer = ""; fAllowlisted = false; - fOneShot = false; - fClient = false; // set by version message - fInbound = fInboundIn; + fOneShot = false; + fClient = false; // set by version message + fFeeler = false; + fInbound = fInboundIn; fNetworkNode = false; fSuccessfullyConnected = false; fDisconnect = false; diff --git a/src/net.h b/src/net.h index d4b042cde..825fe8ef3 100644 --- a/src/net.h +++ b/src/net.h @@ -82,10 +82,13 @@ static const size_t SETASKFOR_MAX_SZ = 2 * MAX_INV_SZ; static const unsigned int DEFAULT_MAX_PEER_CONNECTIONS = 384; /** The period before a network upgrade activates, where connections to upgrading peers are preferred (in blocks). */ static const int NETWORK_UPGRADE_PEER_PREFERENCE_BLOCK_PERIOD = 24 * 24 * 3; +/** Run the feeler connection loop once every 120 seconds. **/ +static const int FEELER_INTERVAL = 120; unsigned int ReceiveFloodSize(); unsigned int SendBufferSize(); +int64_t PoissonNextSend(int64_t now, int average_interval_seconds); void AddOneShot(const std::string& strDest); void AddressCurrentlyConnected(const CService& addr); CNode* FindNode(const CNetAddr& ip); @@ -93,7 +96,7 @@ CNode* FindNode(const CSubNet& subNet); CNode* FindNode(const std::string& addrName); CNode* FindNode(const CService& ip); CNode* ConnectNode(CAddress addrConnect, const char *pszDest = NULL); -bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false); +bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false, bool fFeeler = false); unsigned short GetListenPort(); bool BindListenPort(const CService &bindAddr, std::string& strError, bool fAllowlisted = false); void StartNode(boost::thread_group& threadGroup, CScheduler& scheduler); @@ -220,7 +223,8 @@ public: int nStartingHeight; uint64_t nSendBytes; uint64_t nRecvBytes; - bool fAllowlisted; + bool fAllowlisted; // If true this node bypasses DoS ban limits + bool fFeeler; // If true this node is being used as a short lived feeler. double dPingTime; double dPingWait; std::string addrLocal; @@ -228,7 +232,7 @@ public: CAddress addr; // Bind address of our side of the connection // CAddress addrBind; // https://github.com/bitcoin/bitcoin/commit/a7e3c2814c8e49197889a4679461be42254e5c51 - uint32_t m_mapped_as; + uint32_t m_mapped_as; // Mapped ASN for this address }; @@ -273,9 +277,6 @@ public: }; - - - /** Information about a peer */ class CNode { @@ -323,6 +324,7 @@ public: bool fOneShot; bool fClient; bool fInbound; + bool fFeeler; bool fNetworkNode; bool fSuccessfullyConnected; bool fDisconnect; diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index c9ce4550f..694e8a3a8 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -5,9 +5,7 @@ #include "netbase.h" #include "test/test_bitcoin.h" - #include - #include #include @@ -24,6 +22,35 @@ BOOST_AUTO_TEST_CASE(netbase_networks) BOOST_CHECK(CNetAddr("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetNetwork() == NET_ONION); } + +/* TODO: port this feeler test +BOOST_AUTO_TEST_CASE(cnode_simple_test) +{ + SOCKET hSocket = INVALID_SOCKET; + + in_addr ipv4Addr; + ipv4Addr.s_addr = 0xa0b0c001; + + CAddress addr = CAddress(CService(ipv4Addr, 7777), NODE_NETWORK); + std::string pszDest = ""; + bool fInboundIn = false; + + // Test that fFeeler is false by default. + CNode* pnode1 = new CNode(hSocket, addr, pszDest, fInboundIn); + + + BOOST_CHECK(pnode1->fInbound == false); + BOOST_CHECK(pnode1->fFeeler == false); + + fInboundIn = true; + CNode* pnode2 = new CNode(hSocket, addr, pszDest, fInboundIn); + BOOST_CHECK(pnode2->fInbound == true); + BOOST_CHECK(pnode2->fFeeler == false); +} + +*/ + + BOOST_AUTO_TEST_CASE(netbase_properties) { BOOST_CHECK(CNetAddr("127.0.0.1").IsIPv4()); From dd1453422ba94358ba8818c42b2c14803d852a26 Mon Sep 17 00:00:00 2001 From: Duke Leto Date: Mon, 1 Mar 2021 16:51:39 -0500 Subject: [PATCH 4/4] Add some p2p CLI options --- src/net.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index e2e7e71a7..c03e2da05 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -76,10 +76,9 @@ using namespace hush; using namespace std; namespace { - //TODO: Make these CLI args - const int MAX_OUTBOUND_CONNECTIONS = 64; - const int MAX_FEELER_CONNECTIONS = 1; - const int MAX_INBOUND_FROMIP = 3; + int MAX_OUTBOUND_CONNECTIONS = GetArg("-maxoutboundconnections",64); + int MAX_FEELER_CONNECTIONS = GetArg("-maxfeelerconnections",1); + int MAX_INBOUND_FROMIP = GetArg("-maxinboundfromip",3); struct ListenSocket { SOCKET socket;