diff --git a/src/main.cpp b/src/main.cpp index 75f991d31..758a6dcce 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4903,6 +4903,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, if (strCommand == NetMsgType::VERSION) { + // Feeler connections exist only to verify if address is online. + if (pfrom->fFeeler) { + assert(pfrom->fInbound == false); + pfrom->fDisconnect = true; + } + // Each connection can only send one version message if (pfrom->nVersion != 0) { diff --git a/src/net.cpp b/src/net.cpp index 39c8d12e2..33dc10cbc 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -43,6 +43,9 @@ // Dump addresses to peers.dat and banlist.dat every 15 minutes (900s) #define DUMP_ADDRESSES_INTERVAL 900 +// We add a random period time (0 to 1 seconds) to feeler connections to prevent synchronization. +#define FEELER_SLEEP_WINDOW 1 + #if !defined(HAVE_MSG_NOSIGNAL) && !defined(MSG_NOSIGNAL) #define MSG_NOSIGNAL 0 #endif @@ -61,6 +64,7 @@ namespace { const int MAX_OUTBOUND_CONNECTIONS = 8; + const int MAX_FEELER_CONNECTIONS = 1; struct ListenSocket { SOCKET socket; @@ -1017,7 +1021,8 @@ static void AcceptConnection(const ListenSocket& hListenSocket) { SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len); CAddress addr; int nInbound = 0; - int nMaxInbound = nMaxConnections - MAX_OUTBOUND_CONNECTIONS; + int nMaxInbound = nMaxConnections - (MAX_OUTBOUND_CONNECTIONS + MAX_FEELER_CONNECTIONS); + assert(nMaxInbound > 0); if (hSocket != INVALID_SOCKET) if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) @@ -1613,6 +1618,9 @@ void ThreadOpenConnections() // Initiate network connections int64_t nStart = GetTime(); + + // Minimum time before next feeler connection (in microseconds). + int64_t nNextFeeler = PoissonNextSend(nStart*1000*1000, FEELER_INTERVAL); while (true) { ProcessOneShot(); @@ -1652,13 +1660,36 @@ void ThreadOpenConnections() } } } + assert(nOutbound <= (MAX_OUTBOUND_CONNECTIONS + MAX_FEELER_CONNECTIONS)); + + // Feeler Connections + // + // 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. + // * Only make a feeler connection once every few minutes. + // + 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 = GetAdjustedTime(); - int nTries = 0; while (true) { - CAddrInfo addr = addrman.Select(); + CAddrInfo addr = addrman.Select(fFeeler); // if we selected an invalid address, restart if (!addr.IsValid() || setConnected.count(addr.GetGroup()) || IsLocal(addr)) @@ -1694,8 +1725,17 @@ void ThreadOpenConnections() break; } - if (addrConnect.IsValid()) - OpenNetworkConnection(addrConnect, (int)setConnected.size() >= std::min(nMaxConnections - 1, 2), &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()); + } + + OpenNetworkConnection(addrConnect, (int)setConnected.size() >= std::min(nMaxConnections - 1, 2), &grant, NULL, false, fFeeler); + } } } @@ -1777,7 +1817,7 @@ void ThreadOpenAddedConnections() } // if successful, this moves the passed grant to the constructed node -bool OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, 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 @@ -1801,6 +1841,8 @@ bool OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSem pnode->fNetworkNode = true; if (fOneShot) pnode->fOneShot = true; + if (fFeeler) + pnode->fFeeler = true; return true; } @@ -2062,7 +2104,7 @@ void StartNode(boost::thread_group& threadGroup, CScheduler& scheduler) if (semOutbound == NULL) { // initialize semaphore - int nMaxOutbound = std::min(MAX_OUTBOUND_CONNECTIONS, nMaxConnections); + int nMaxOutbound = std::min((MAX_OUTBOUND_CONNECTIONS + MAX_FEELER_CONNECTIONS), nMaxConnections); semOutbound = new CSemaphore(nMaxOutbound); } @@ -2107,7 +2149,7 @@ bool StopNode() LogPrintf("StopNode()\n"); MapPort(false); if (semOutbound) - for (int i=0; ipost(); if (fAddressesInitialized) @@ -2448,6 +2490,7 @@ CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNa fWhitelisted = false; fOneShot = false; fClient = false; // set by version message + fFeeler = false; fInbound = fInboundIn; fNetworkNode = false; fSuccessfullyConnected = false; diff --git a/src/net.h b/src/net.h index ea03defc4..1ba2d47da 100644 --- a/src/net.h +++ b/src/net.h @@ -41,6 +41,8 @@ namespace boost { static const int PING_INTERVAL = 2 * 60; /** Time after which to disconnect, after waiting for a ping response (or inactivity). */ static const int TIMEOUT_INTERVAL = 20 * 60; +/** Run the feeler connection loop once every 2 minutes or 120 seconds. **/ +static const int FEELER_INTERVAL = 120; /** The maximum number of entries in an 'inv' protocol message */ static const unsigned int MAX_INV_SZ = 50000; /** The maximum number of new addresses to accumulate before announcing. */ @@ -89,7 +91,7 @@ CNode* FindNode(const CSubNet& subNet); CNode* FindNode(const std::string& addrName); CNode* FindNode(const CService& ip); CNode* FindNode(const NodeId id); //TODO: Remove this -bool OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false); +bool OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false, bool fFeeler = false); void MapPort(bool fUseUPnP); unsigned short GetListenPort(); bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhitelisted = false); @@ -350,6 +352,7 @@ public: // the network or wire types and the cleaned string used when displayed or logged. std::string strSubVer, cleanSubVer; bool fWhitelisted; // This peer can bypass DoS banning. + bool fFeeler; // If true this node is being used as a short lived feeler. bool fOneShot; bool fClient; bool fInbound; diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 6511e6ffa..267d1b55e 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -150,4 +150,26 @@ BOOST_AUTO_TEST_CASE(caddrdb_read_corrupted) BOOST_CHECK(addrman2.size() == 0); } +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_SUITE_END()