diff --git a/qa/pull-tester/run-bitcoind-for-test.sh.in b/qa/pull-tester/run-bitcoind-for-test.sh.in index 391046ab8..ecc42e12b 100755 --- a/qa/pull-tester/run-bitcoind-for-test.sh.in +++ b/qa/pull-tester/run-bitcoind-for-test.sh.in @@ -10,7 +10,7 @@ touch "$DATADIR/regtest/debug.log" tail -q -n 1 -F "$DATADIR/regtest/debug.log" | grep -m 1 -q "Done loading" & WAITER=$! PORT=`expr $BASHPID + 10000` -"@abs_top_builddir@/src/bitcoind@EXEEXT@" -connect=0.0.0.0 -datadir="$DATADIR" -rpcuser=user -rpcpassword=pass -listen -keypool=3 -debug -debug=net -logtimestamps -port=$PORT -regtest -rpcport=`expr $PORT + 1` & +"@abs_top_builddir@/src/bitcoind@EXEEXT@" -connect=0.0.0.0 -datadir="$DATADIR" -rpcuser=user -rpcpassword=pass -listen -keypool=3 -debug -debug=net -logtimestamps -port=$PORT -whitelist=127.0.0.1 -regtest -rpcport=`expr $PORT + 1` & BITCOIND=$! #Install a watchdog. diff --git a/src/addrman.cpp b/src/addrman.cpp index 3628af2ea..ac1250faa 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -9,34 +9,27 @@ using namespace std; -int CAddrInfo::GetTriedBucket(const std::vector &nKey) const +int CAddrInfo::GetTriedBucket(const uint256& nKey) const { - CDataStream ss1(SER_GETHASH, 0); - std::vector vchKey = GetKey(); - ss1 << nKey << vchKey; - uint64_t hash1 = Hash(ss1.begin(), ss1.end()).GetLow64(); - - CDataStream ss2(SER_GETHASH, 0); - std::vector vchGroupKey = GetGroup(); - ss2 << nKey << vchGroupKey << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP); - uint64_t hash2 = Hash(ss2.begin(), ss2.end()).GetLow64(); + uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetKey()).GetHash().GetLow64(); + uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup() << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP)).GetHash().GetLow64(); return hash2 % ADDRMAN_TRIED_BUCKET_COUNT; } -int CAddrInfo::GetNewBucket(const std::vector &nKey, const CNetAddr& src) const +int CAddrInfo::GetNewBucket(const uint256& nKey, const CNetAddr& src) const { - CDataStream ss1(SER_GETHASH, 0); - std::vector vchGroupKey = GetGroup(); std::vector vchSourceGroupKey = src.GetGroup(); - ss1 << nKey << vchGroupKey << vchSourceGroupKey; - uint64_t hash1 = Hash(ss1.begin(), ss1.end()).GetLow64(); - - CDataStream ss2(SER_GETHASH, 0); - ss2 << nKey << vchSourceGroupKey << (hash1 % ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP); - uint64_t hash2 = Hash(ss2.begin(), ss2.end()).GetLow64(); + uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup() << vchSourceGroupKey).GetHash().GetLow64(); + uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << vchSourceGroupKey << (hash1 % ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP)).GetHash().GetLow64(); return hash2 % ADDRMAN_NEW_BUCKET_COUNT; } +int CAddrInfo::GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const +{ + uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << (fNew ? 'N' : 'K') << nBucket << GetKey()).GetHash().GetLow64(); + return hash1 % ADDRMAN_BUCKET_SIZE; +} + bool CAddrInfo::IsTerrible(int64_t nNow) const { if (nLastTry && nLastTry >= nNow-60) // never remove things tried the last minute @@ -67,8 +60,6 @@ double CAddrInfo::GetChance(int64_t nNow) const if (nSinceLastSeen < 0) nSinceLastSeen = 0; if (nSinceLastTry < 0) nSinceLastTry = 0; - fChance *= 600.0 / (600.0 + nSinceLastSeen); - // deprioritize very recent attempts away if (nSinceLastTry < 60*10) fChance *= 0.01; @@ -125,93 +116,44 @@ void CAddrMan::SwapRandom(unsigned int nRndPos1, unsigned int nRndPos2) vRandom[nRndPos2] = nId1; } -int CAddrMan::SelectTried(int nKBucket) +void CAddrMan::Delete(int nId) { - std::vector &vTried = vvTried[nKBucket]; + assert(mapInfo.count(nId) != 0); + CAddrInfo& info = mapInfo[nId]; + assert(!info.fInTried); + assert(info.nRefCount == 0); - // random shuffle the first few elements (using the entire list) - // find the least recently tried among them - int64_t nOldest = -1; - int nOldestPos = -1; - for (unsigned int i = 0; i < ADDRMAN_TRIED_ENTRIES_INSPECT_ON_EVICT && i < vTried.size(); i++) - { - int nPos = GetRandInt(vTried.size() - i) + i; - int nTemp = vTried[nPos]; - vTried[nPos] = vTried[i]; - vTried[i] = nTemp; - assert(nOldest == -1 || mapInfo.count(nTemp) == 1); - if (nOldest == -1 || mapInfo[nTemp].nLastSuccess < mapInfo[nOldest].nLastSuccess) { - nOldest = nTemp; - nOldestPos = nPos; - } - } - - return nOldestPos; + SwapRandom(info.nRandomPos, vRandom.size() - 1); + vRandom.pop_back(); + mapAddr.erase(info); + mapInfo.erase(nId); + nNew--; } -int CAddrMan::ShrinkNew(int nUBucket) +void CAddrMan::ClearNew(int nUBucket, int nUBucketPos) { - assert(nUBucket >= 0 && (unsigned int)nUBucket < vvNew.size()); - std::set &vNew = vvNew[nUBucket]; - - // first look for deletable items - for (std::set::iterator it = vNew.begin(); it != vNew.end(); it++) - { - assert(mapInfo.count(*it)); - CAddrInfo &info = mapInfo[*it]; - if (info.IsTerrible()) - { - if (--info.nRefCount == 0) - { - SwapRandom(info.nRandomPos, vRandom.size()-1); - vRandom.pop_back(); - mapAddr.erase(info); - mapInfo.erase(*it); - nNew--; - } - vNew.erase(it); - return 0; + // if there is an entry in the specified bucket, delete it. + if (vvNew[nUBucket][nUBucketPos] != -1) { + int nIdDelete = vvNew[nUBucket][nUBucketPos]; + CAddrInfo& infoDelete = mapInfo[nIdDelete]; + assert(infoDelete.nRefCount > 0); + infoDelete.nRefCount--; + vvNew[nUBucket][nUBucketPos] = -1; + if (infoDelete.nRefCount == 0) { + Delete(nIdDelete); } } - - // otherwise, select four randomly, and pick the oldest of those to replace - int n[4] = {GetRandInt(vNew.size()), GetRandInt(vNew.size()), GetRandInt(vNew.size()), GetRandInt(vNew.size())}; - int nI = 0; - int nOldest = -1; - for (std::set::iterator it = vNew.begin(); it != vNew.end(); it++) - { - if (nI == n[0] || nI == n[1] || nI == n[2] || nI == n[3]) - { - assert(nOldest == -1 || mapInfo.count(*it) == 1); - if (nOldest == -1 || mapInfo[*it].nTime < mapInfo[nOldest].nTime) - nOldest = *it; - } - nI++; - } - assert(mapInfo.count(nOldest) == 1); - CAddrInfo &info = mapInfo[nOldest]; - if (--info.nRefCount == 0) - { - SwapRandom(info.nRandomPos, vRandom.size()-1); - vRandom.pop_back(); - mapAddr.erase(info); - mapInfo.erase(nOldest); - nNew--; - } - vNew.erase(nOldest); - - return 1; } -void CAddrMan::MakeTried(CAddrInfo& info, int nId, int nOrigin) +void CAddrMan::MakeTried(CAddrInfo& info, int nId) { - assert(vvNew[nOrigin].count(nId) == 1); - // remove the entry from all new buckets - for (std::vector >::iterator it = vvNew.begin(); it != vvNew.end(); it++) - { - if ((*it).erase(nId)) + for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) { + int pos = info.GetBucketPosition(nKey, true, bucket); + if (vvNew[bucket][pos] == nId) { + vvNew[bucket][pos] = -1; info.nRefCount--; + } } nNew--; @@ -219,46 +161,36 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId, int nOrigin) // what tried bucket to move the entry to int nKBucket = info.GetTriedBucket(nKey); - std::vector &vTried = vvTried[nKBucket]; + int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket); - // first check whether there is place to just add it - if (vTried.size() < ADDRMAN_TRIED_BUCKET_SIZE) - { - vTried.push_back(nId); - nTried++; - info.fInTried = true; - return; + // first make space to add it (the existing tried entry there is moved to new, deleting whatever is there). + if (vvTried[nKBucket][nKBucketPos] != -1) { + // find an item to evict + int nIdEvict = vvTried[nKBucket][nKBucketPos]; + assert(mapInfo.count(nIdEvict) == 1); + CAddrInfo& infoOld = mapInfo[nIdEvict]; + + // Remove the to-be-evicted item from the tried set. + infoOld.fInTried = false; + vvTried[nKBucket][nKBucketPos] = -1; + nTried--; + + // find which new bucket it belongs to + int nUBucket = infoOld.GetNewBucket(nKey); + int nUBucketPos = infoOld.GetBucketPosition(nKey, true, nUBucket); + ClearNew(nUBucket, nUBucketPos); + assert(vvNew[nUBucket][nUBucketPos] == -1); + + // Enter it into the new set again. + infoOld.nRefCount = 1; + vvNew[nUBucket][nUBucketPos] = nIdEvict; + nNew++; } + assert(vvTried[nKBucket][nKBucketPos] == -1); - // otherwise, find an item to evict - int nPos = SelectTried(nKBucket); - - // find which new bucket it belongs to - assert(mapInfo.count(vTried[nPos]) == 1); - int nUBucket = mapInfo[vTried[nPos]].GetNewBucket(nKey); - std::set &vNew = vvNew[nUBucket]; - - // remove the to-be-replaced tried entry from the tried set - CAddrInfo& infoOld = mapInfo[vTried[nPos]]; - infoOld.fInTried = false; - infoOld.nRefCount = 1; - // do not update nTried, as we are going to move something else there immediately - - // check whether there is place in that one, - if (vNew.size() < ADDRMAN_NEW_BUCKET_SIZE) - { - // if so, move it back there - vNew.insert(vTried[nPos]); - } else { - // otherwise, move it to the new bucket nId came from (there is certainly place there) - vvNew[nOrigin].insert(vTried[nPos]); - } - nNew++; - - vTried[nPos] = nId; - // we just overwrote an entry in vTried; no need to update nTried + vvTried[nKBucket][nKBucketPos] = nId; + nTried++; info.fInTried = true; - return; } void CAddrMan::Good_(const CService &addr, int64_t nTime) @@ -279,22 +211,21 @@ void CAddrMan::Good_(const CService &addr, int64_t nTime) // update info info.nLastSuccess = nTime; info.nLastTry = nTime; - info.nTime = nTime; info.nAttempts = 0; + // nTime is not updated here, to avoid leaking information about + // currently-connected peers. // if it is already in the tried set, don't do anything else if (info.fInTried) return; // find a bucket it is in now - int nRnd = GetRandInt(vvNew.size()); + int nRnd = GetRandInt(ADDRMAN_NEW_BUCKET_COUNT); int nUBucket = -1; - for (unsigned int n = 0; n < vvNew.size(); n++) - { - int nB = (n+nRnd) % vvNew.size(); - std::set &vNew = vvNew[nB]; - if (vNew.count(nId)) - { + for (unsigned int n = 0; n < ADDRMAN_NEW_BUCKET_COUNT; n++) { + int nB = (n + nRnd) % ADDRMAN_NEW_BUCKET_COUNT; + int nBpos = info.GetBucketPosition(nKey, true, nB); + if (vvNew[nB][nBpos] == nId) { nUBucket = nB; break; } @@ -307,7 +238,7 @@ void CAddrMan::Good_(const CService &addr, int64_t nTime) LogPrint("addrman", "Moving %s to tried\n", addr.ToString()); // move nId to the tried tables - MakeTried(info, nId, nUBucket); + MakeTried(info, nId); } bool CAddrMan::Add_(const CAddress &addr, const CNetAddr& source, int64_t nTimePenalty) @@ -356,13 +287,25 @@ bool CAddrMan::Add_(const CAddress &addr, const CNetAddr& source, int64_t nTimeP } int nUBucket = pinfo->GetNewBucket(nKey, source); - std::set &vNew = vvNew[nUBucket]; - if (!vNew.count(nId)) - { - pinfo->nRefCount++; - if (vNew.size() == ADDRMAN_NEW_BUCKET_SIZE) - ShrinkNew(nUBucket); - vvNew[nUBucket].insert(nId); + int nUBucketPos = pinfo->GetBucketPosition(nKey, true, nUBucket); + if (vvNew[nUBucket][nUBucketPos] != nId) { + bool fInsert = vvNew[nUBucket][nUBucketPos] == -1; + if (!fInsert) { + CAddrInfo& infoExisting = mapInfo[vvNew[nUBucket][nUBucketPos]]; + if (infoExisting.IsTerrible() || (infoExisting.nRefCount > 1 && pinfo->nRefCount == 0)) { + // Overwrite the existing new table entry. + fInsert = true; + } + } + if (fInsert) { + ClearNew(nUBucket, nUBucketPos); + pinfo->nRefCount++; + vvNew[nUBucket][nUBucketPos] = nId; + } else { + if (pinfo->nRefCount == 0) { + Delete(nId); + } + } } return fNew; } @@ -386,44 +329,39 @@ void CAddrMan::Attempt_(const CService &addr, int64_t nTime) info.nAttempts++; } -CAddress CAddrMan::Select_(int nUnkBias) +CAddress CAddrMan::Select_() { if (size() == 0) return CAddress(); - double nCorTried = sqrt(nTried) * (100.0 - nUnkBias); - double nCorNew = sqrt(nNew) * nUnkBias; - if ((nCorTried + nCorNew)*GetRandInt(1<<30)/(1<<30) < nCorTried) - { + // Use a 50% chance for choosing between tried and new table entries. + if (nTried > 0 && (nNew == 0 || GetRandInt(2) == 0)) { // use a tried node double fChanceFactor = 1.0; - while(1) - { - int nKBucket = GetRandInt(vvTried.size()); - std::vector &vTried = vvTried[nKBucket]; - if (vTried.size() == 0) continue; - int nPos = GetRandInt(vTried.size()); - assert(mapInfo.count(vTried[nPos]) == 1); - CAddrInfo &info = mapInfo[vTried[nPos]]; - if (GetRandInt(1<<30) < fChanceFactor*info.GetChance()*(1<<30)) + while (1) { + int nKBucket = GetRandInt(ADDRMAN_TRIED_BUCKET_COUNT); + int nKBucketPos = GetRandInt(ADDRMAN_BUCKET_SIZE); + if (vvTried[nKBucket][nKBucketPos] == -1) + continue; + int nId = vvTried[nKBucket][nKBucketPos]; + assert(mapInfo.count(nId) == 1); + CAddrInfo& info = mapInfo[nId]; + if (GetRandInt(1 << 30) < fChanceFactor * info.GetChance() * (1 << 30)) return info; fChanceFactor *= 1.2; } } else { // use a new node double fChanceFactor = 1.0; - while(1) - { - int nUBucket = GetRandInt(vvNew.size()); - std::set &vNew = vvNew[nUBucket]; - if (vNew.size() == 0) continue; - int nPos = GetRandInt(vNew.size()); - std::set::iterator it = vNew.begin(); - while (nPos--) - it++; - assert(mapInfo.count(*it) == 1); - CAddrInfo &info = mapInfo[*it]; - if (GetRandInt(1<<30) < fChanceFactor*info.GetChance()*(1<<30)) + while (1) { + int nUBucket = GetRandInt(ADDRMAN_NEW_BUCKET_COUNT); + int nUBucketPos = GetRandInt(ADDRMAN_BUCKET_SIZE); + if (vvNew[nUBucket][nUBucketPos] == -1) + continue; + int nId = vvNew[nUBucket][nUBucketPos]; + assert(mapInfo.count(nId) == 1); + CAddrInfo& info = mapInfo[nId]; + if (GetRandInt(1 << 30) < fChanceFactor * info.GetChance() * (1 << 30)) return info; fChanceFactor *= 1.2; } @@ -462,29 +400,37 @@ int CAddrMan::Check_() if (setTried.size() != nTried) return -9; if (mapNew.size() != nNew) return -10; - for (int n=0; n &vTried = vvTried[n]; - for (std::vector::iterator it = vTried.begin(); it != vTried.end(); it++) - { - if (!setTried.count(*it)) return -11; - setTried.erase(*it); + for (int n = 0; n < ADDRMAN_TRIED_BUCKET_COUNT; n++) { + for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) { + if (vvTried[n][i] != -1) { + if (!setTried.count(vvTried[n][i])) + return -11; + if (mapInfo[vvTried[n][i]].GetTriedBucket(nKey) != n) + return -17; + if (mapInfo[vvTried[n][i]].GetBucketPosition(nKey, false, n) != i) + return -18; + setTried.erase(vvTried[n][i]); + } } } - for (int n=0; n &vNew = vvNew[n]; - for (std::set::iterator it = vNew.begin(); it != vNew.end(); it++) - { - if (!mapNew.count(*it)) return -12; - if (--mapNew[*it] == 0) - mapNew.erase(*it); + for (int n = 0; n < ADDRMAN_NEW_BUCKET_COUNT; n++) { + for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) { + if (vvNew[n][i] != -1) { + if (!mapNew.count(vvNew[n][i])) + return -12; + if (mapInfo[vvNew[n][i]].GetBucketPosition(nKey, true, n) != i) + return -19; + if (--mapNew[vvNew[n][i]] == 0) + mapNew.erase(vvNew[n][i]); + } } } if (setTried.size()) return -13; if (mapNew.size()) return -15; + if (nKey.IsNull()) + return -16; return 0; } diff --git a/src/addrman.h b/src/addrman.h index e2b0cb109..916f4da4b 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -8,6 +8,7 @@ #include "netbase.h" #include "protocol.h" #include "sync.h" +#include "uint256.h" #include "util.h" #include @@ -75,17 +76,20 @@ public: } // Calculate in which "tried" bucket this entry belongs - int GetTriedBucket(const std::vector &nKey) const; + int GetTriedBucket(const uint256 &nKey) const; // Calculate in which "new" bucket this entry belongs, given a certain source - int GetNewBucket(const std::vector &nKey, const CNetAddr& src) const; + int GetNewBucket(const uint256 &nKey, const CNetAddr& src) const; // Calculate in which "new" bucket this entry belongs, using its default source - int GetNewBucket(const std::vector &nKey) const + int GetNewBucket(const uint256 &nKey) const { return GetNewBucket(nKey, source); } + //! Calculate in which position of a bucket to store this entry. + int GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const; + // Determine whether the statistics about this entry are bad enough so that it can just be deleted bool IsTerrible(int64_t nNow = GetAdjustedTime()) const; @@ -94,55 +98,49 @@ public: }; -// Stochastic address manager -// -// Design goals: -// * Only keep a limited number of addresses around, so that addr.dat and memory requirements do not grow without bound. -// * Keep the address tables in-memory, and asynchronously dump the entire to able in addr.dat. -// * Make sure no (localized) attacker can fill the entire table with his nodes/addresses. -// -// To that end: -// * Addresses are organized into buckets. -// * Address that have not yet been tried go into 256 "new" buckets. -// * Based on the address range (/16 for IPv4) of source of the information, 32 buckets are selected at random -// * The actual bucket is chosen from one of these, based on the range the address itself is located. -// * One single address can occur in up to 4 different buckets, to increase selection chances for addresses that -// are seen frequently. The chance for increasing this multiplicity decreases exponentially. -// * When adding a new address to a full bucket, a randomly chosen entry (with a bias favoring less recently seen -// ones) is removed from it first. -// * Addresses of nodes that are known to be accessible go into 64 "tried" buckets. -// * Each address range selects at random 4 of these buckets. -// * The actual bucket is chosen from one of these, based on the full address. -// * When adding a new good address to a full bucket, a randomly chosen entry (with a bias favoring less recently -// tried ones) is evicted from it, back to the "new" buckets. -// * Bucket selection is based on cryptographic hashing, using a randomly-generated 256-bit key, which should not -// be observable by adversaries. -// * Several indexes are kept for high performance. Defining DEBUG_ADDRMAN will introduce frequent (and expensive) -// consistency checks for the entire data structure. +/** Stochastic address manager + * + * Design goals: + * * Keep the address tables in-memory, and asynchronously dump the entire to able in peers.dat. + * * Make sure no (localized) attacker can fill the entire table with his nodes/addresses. + * + * To that end: + * * Addresses are organized into buckets. + * * Address that have not yet been tried go into 1024 "new" buckets. + * * Based on the address range (/16 for IPv4) of source of the information, 64 buckets are selected at random + * * The actual bucket is chosen from one of these, based on the range the address itself is located. + * * One single address can occur in up to 8 different buckets, to increase selection chances for addresses that + * are seen frequently. The chance for increasing this multiplicity decreases exponentially. + * * When adding a new address to a full bucket, a randomly chosen entry (with a bias favoring less recently seen + * ones) is removed from it first. + * * Addresses of nodes that are known to be accessible go into 256 "tried" buckets. + * * Each address range selects at random 8 of these buckets. + * * The actual bucket is chosen from one of these, based on the full address. + * * When adding a new good address to a full bucket, a randomly chosen entry (with a bias favoring less recently + * tried ones) is evicted from it, back to the "new" buckets. + * * Bucket selection is based on cryptographic hashing, using a randomly-generated 256-bit key, which should not + * be observable by adversaries. + * * Several indexes are kept for high performance. Defining DEBUG_ADDRMAN will introduce frequent (and expensive) + * consistency checks for the entire data structure. + */ // total number of buckets for tried addresses -#define ADDRMAN_TRIED_BUCKET_COUNT 64 - -// maximum allowed number of entries in buckets for tried addresses -#define ADDRMAN_TRIED_BUCKET_SIZE 64 +#define ADDRMAN_TRIED_BUCKET_COUNT 256 // total number of buckets for new addresses -#define ADDRMAN_NEW_BUCKET_COUNT 256 +#define ADDRMAN_NEW_BUCKET_COUNT 1024 -// maximum allowed number of entries in buckets for new addresses -#define ADDRMAN_NEW_BUCKET_SIZE 64 +// maximum allowed number of entries in buckets for new and tried addresses +#define ADDRMAN_BUCKET_SIZE 64 // over how many buckets entries with tried addresses from a single group (/16 for IPv4) are spread -#define ADDRMAN_TRIED_BUCKETS_PER_GROUP 4 +#define ADDRMAN_TRIED_BUCKETS_PER_GROUP 8 // over how many buckets entries with new addresses originating from a single group are spread -#define ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP 32 +#define ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP 64 // in how many buckets for entries with new addresses a single address may occur -#define ADDRMAN_NEW_BUCKETS_PER_ADDRESS 4 - -// how many entries in a bucket with tried addresses are inspected, when selecting one to replace -#define ADDRMAN_TRIED_ENTRIES_INSPECT_ON_EVICT 4 +#define ADDRMAN_NEW_BUCKETS_PER_ADDRESS 8 // how old addresses can maximally be #define ADDRMAN_HORIZON_DAYS 30 @@ -170,7 +168,7 @@ private: mutable CCriticalSection cs; // secret key to randomize bucket select with - std::vector nKey; + uint256 nKey; // last used nId int nIdCount; @@ -188,13 +186,13 @@ private: int nTried; // list of "tried" buckets - std::vector > vvTried; + int vvTried[ADDRMAN_TRIED_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE]; // number of (unique) "new" entries int nNew; // list of "new" buckets - std::vector > vvNew; + int vvNew[ADDRMAN_NEW_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE]; protected: @@ -208,17 +206,14 @@ protected: // Swap two elements in vRandom. void SwapRandom(unsigned int nRandomPos1, unsigned int nRandomPos2); - // Return position in given bucket to replace. - int SelectTried(int nKBucket); - - // Remove an element from a "new" bucket. - // This is the only place where actual deletes occur. - // They are never deleted while in the "tried" table, only possibly evicted back to the "new" table. - int ShrinkNew(int nUBucket); - // Move an entry from the "new" table(s) to the "tried" table - // @pre vvUnkown[nOrigin].count(nId) != 0 - void MakeTried(CAddrInfo& info, int nId, int nOrigin); + void MakeTried(CAddrInfo& info, int nId); + + // Delete an entry. It must not be in tried, and have refcount 0. + void Delete(int nId); + + // Clear a position in a "new" table. This is the only place where entries are actually deleted. + void ClearNew(int nUBucket, int nUBucketPos); // Mark an entry "good", possibly moving it from "new" to "tried". void Good_(const CService &addr, int64_t nTime); @@ -230,8 +225,7 @@ protected: void Attempt_(const CService &addr, int64_t nTime); // Select an address to connect to. - // nUnkBias determines how much to favor new addresses over tried ones (min=0, max=100) - CAddress Select_(int nUnkBias); + CAddress Select_(); #ifdef DEBUG_ADDRMAN // Perform consistency check. Returns an error code or zero. @@ -245,153 +239,220 @@ protected: void Connected_(const CService &addr, int64_t nTime); public: + /** + * serialized format: + * * version byte (currently 1) + * * 0x20 + nKey (serialized as if it were a vector, for backward compatibility) + * * nNew + * * nTried + * * number of "new" buckets XOR 2**30 + * * all nNew addrinfos in vvNew + * * all nTried addrinfos in vvTried + * * for each bucket: + * * number of elements + * * for each element: index + * + * 2**30 is xorred with the number of buckets to make addrman deserializer v0 detect it + * as incompatible. This is necessary because it did not check the version number on + * deserialization. + * + * Notice that vvTried, mapAddr and vVector are never encoded explicitly; + * they are instead reconstructed from the other information. + * + * vvNew is serialized, but only used if ADDRMAN_UNKOWN_BUCKET_COUNT didn't change, + * otherwise it is reconstructed as well. + * + * This format is more complex, but significantly smaller (at most 1.5 MiB), and supports + * changes to the ADDRMAN_ parameters without breaking the on-disk structure. + * + * We don't use ADD_SERIALIZE_METHODS since the serialization and deserialization code has + * very little in common. + */ + template + void Serialize(Stream &s, int nType, int nVersionDummy) const + { + LOCK(cs); - IMPLEMENT_SERIALIZE - (({ - // serialized format: - // * version byte (currently 0) - // * nKey - // * nNew - // * nTried - // * number of "new" buckets - // * all nNew addrinfos in vvNew - // * all nTried addrinfos in vvTried - // * for each bucket: - // * number of elements - // * for each element: index - // - // Notice that vvTried, mapAddr and vVector are never encoded explicitly; - // they are instead reconstructed from the other information. - // - // vvNew is serialized, but only used if ADDRMAN_UNKOWN_BUCKET_COUNT didn't change, - // otherwise it is reconstructed as well. - // - // This format is more complex, but significantly smaller (at most 1.5 MiB), and supports - // changes to the ADDRMAN_ parameters without breaking the on-disk structure. - { - LOCK(cs); - unsigned char nVersion = 0; - READWRITE(nVersion); - READWRITE(nKey); - READWRITE(nNew); - READWRITE(nTried); + unsigned char nVersion = 1; + s << nVersion; + s << ((unsigned char)32); + s << nKey; + s << nNew; + s << nTried; - CAddrMan *am = const_cast(this); - if (fWrite) - { - int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT; - READWRITE(nUBuckets); - std::map mapUnkIds; - int nIds = 0; - for (std::map::iterator it = am->mapInfo.begin(); it != am->mapInfo.end(); it++) - { - if (nIds == nNew) break; // this means nNew was wrong, oh ow - mapUnkIds[(*it).first] = nIds; - CAddrInfo &info = (*it).second; - if (info.nRefCount) - { - READWRITE(info); - nIds++; - } + int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30); + s << nUBuckets; + std::map mapUnkIds; + int nIds = 0; + for (std::map::const_iterator it = mapInfo.begin(); it != mapInfo.end(); it++) { + mapUnkIds[(*it).first] = nIds; + const CAddrInfo &info = (*it).second; + if (info.nRefCount) { + assert(nIds != nNew); // this means nNew was wrong, oh ow + s << info; + nIds++; + } + } + nIds = 0; + for (std::map::const_iterator it = mapInfo.begin(); it != mapInfo.end(); it++) { + const CAddrInfo &info = (*it).second; + if (info.fInTried) { + assert(nIds != nTried); // this means nTried was wrong, oh ow + s << info; + nIds++; + } + } + for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) { + int nSize = 0; + for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) { + if (vvNew[bucket][i] != -1) + nSize++; + } + s << nSize; + for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) { + if (vvNew[bucket][i] != -1) { + int nIndex = mapUnkIds[vvNew[bucket][i]]; + s << nIndex; } - nIds = 0; - for (std::map::iterator it = am->mapInfo.begin(); it != am->mapInfo.end(); it++) - { - if (nIds == nTried) break; // this means nTried was wrong, oh ow - CAddrInfo &info = (*it).second; - if (info.fInTried) - { - READWRITE(info); - nIds++; - } - } - for (std::vector >::iterator it = am->vvNew.begin(); it != am->vvNew.end(); it++) - { - const std::set &vNew = (*it); - int nSize = vNew.size(); - READWRITE(nSize); - for (std::set::iterator it2 = vNew.begin(); it2 != vNew.end(); it2++) - { - int nIndex = mapUnkIds[*it2]; - READWRITE(nIndex); - } + } + } + } + + template + void Unserialize(Stream& s, int nType, int nVersionDummy) + { + LOCK(cs); + + Clear(); + + unsigned char nVersion; + s >> nVersion; + unsigned char nKeySize; + s >> nKeySize; + if (nKeySize != 32) throw std::ios_base::failure("Incorrect keysize in addrman deserialization"); + s >> nKey; + s >> nNew; + s >> nTried; + int nUBuckets = 0; + s >> nUBuckets; + if (nVersion != 0) { + nUBuckets ^= (1 << 30); + } + + // Deserialize entries from the new table. + for (int n = 0; n < nNew; n++) { + CAddrInfo &info = mapInfo[n]; + s >> info; + mapAddr[info] = n; + info.nRandomPos = vRandom.size(); + vRandom.push_back(n); + if (nVersion != 1 || nUBuckets != ADDRMAN_NEW_BUCKET_COUNT) { + // In case the new table data cannot be used (nVersion unknown, or bucket count wrong), + // immediately try to give them a reference based on their primary source address. + int nUBucket = info.GetNewBucket(nKey); + int nUBucketPos = info.GetBucketPosition(nKey, true, nUBucket); + if (vvNew[nUBucket][nUBucketPos] == -1) { + vvNew[nUBucket][nUBucketPos] = n; + info.nRefCount++; } + } + } + nIdCount = nNew; + + // Deserialize entries from the tried table. + int nLost = 0; + for (int n = 0; n < nTried; n++) { + CAddrInfo info; + s >> info; + int nKBucket = info.GetTriedBucket(nKey); + int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket); + if (vvTried[nKBucket][nKBucketPos] == -1) { + info.nRandomPos = vRandom.size(); + info.fInTried = true; + vRandom.push_back(nIdCount); + mapInfo[nIdCount] = info; + mapAddr[info] = nIdCount; + vvTried[nKBucket][nKBucketPos] = nIdCount; + nIdCount++; } else { - int nUBuckets = 0; - READWRITE(nUBuckets); - am->nIdCount = 0; - am->mapInfo.clear(); - am->mapAddr.clear(); - am->vRandom.clear(); - am->vvTried = std::vector >(ADDRMAN_TRIED_BUCKET_COUNT, std::vector(0)); - am->vvNew = std::vector >(ADDRMAN_NEW_BUCKET_COUNT, std::set()); - for (int n = 0; n < am->nNew; n++) - { - CAddrInfo &info = am->mapInfo[n]; - READWRITE(info); - am->mapAddr[info] = n; - info.nRandomPos = vRandom.size(); - am->vRandom.push_back(n); - if (nUBuckets != ADDRMAN_NEW_BUCKET_COUNT) - { - am->vvNew[info.GetNewBucket(am->nKey)].insert(n); + nLost++; + } + } + nTried -= nLost; + + // Deserialize positions in the new table (if possible). + for (int bucket = 0; bucket < nUBuckets; bucket++) { + int nSize = 0; + s >> nSize; + for (int n = 0; n < nSize; n++) { + int nIndex = 0; + s >> nIndex; + if (nIndex >= 0 && nIndex < nNew) { + CAddrInfo &info = mapInfo[nIndex]; + int nUBucketPos = info.GetBucketPosition(nKey, true, bucket); + if (nVersion == 1 && nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && vvNew[bucket][nUBucketPos] == -1 && info.nRefCount < ADDRMAN_NEW_BUCKETS_PER_ADDRESS) { info.nRefCount++; - } - } - am->nIdCount = am->nNew; - int nLost = 0; - for (int n = 0; n < am->nTried; n++) - { - CAddrInfo info; - READWRITE(info); - std::vector &vTried = am->vvTried[info.GetTriedBucket(am->nKey)]; - if (vTried.size() < ADDRMAN_TRIED_BUCKET_SIZE) - { - info.nRandomPos = vRandom.size(); - info.fInTried = true; - am->vRandom.push_back(am->nIdCount); - am->mapInfo[am->nIdCount] = info; - am->mapAddr[info] = am->nIdCount; - vTried.push_back(am->nIdCount); - am->nIdCount++; - } else { - nLost++; - } - } - am->nTried -= nLost; - for (int b = 0; b < nUBuckets; b++) - { - std::set &vNew = am->vvNew[b]; - int nSize = 0; - READWRITE(nSize); - for (int n = 0; n < nSize; n++) - { - int nIndex = 0; - READWRITE(nIndex); - CAddrInfo &info = am->mapInfo[nIndex]; - if (nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && info.nRefCount < ADDRMAN_NEW_BUCKETS_PER_ADDRESS) - { - info.nRefCount++; - vNew.insert(nIndex); - } + vvNew[bucket][nUBucketPos] = nIndex; } } } } - });) - CAddrMan() : vRandom(0), vvTried(ADDRMAN_TRIED_BUCKET_COUNT, std::vector(0)), vvNew(ADDRMAN_NEW_BUCKET_COUNT, std::set()) + // Prune new entries with refcount 0 (as a result of collisions). + int nLostUnk = 0; + for (std::map::const_iterator it = mapInfo.begin(); it != mapInfo.end(); ) { + if (it->second.fInTried == false && it->second.nRefCount == 0) { + std::map::const_iterator itCopy = it++; + Delete(itCopy->first); + nLostUnk++; + } else { + it++; + } + } + if (nLost + nLostUnk > 0) { + LogPrint("addrman", "addrman lost %i new and %i tried addresses due to collisions\n", nLostUnk, nLost); + } + + Check(); + } + + unsigned int GetSerializeSize(int nType, int nVersion) const { - nKey.resize(32); - RAND_bytes(&nKey[0], 32); + return (CSizeComputer(nType, nVersion) << *this).size(); + } - nIdCount = 0; - nTried = 0; - nNew = 0; + void Clear() + { + std::vector().swap(vRandom); + RAND_bytes((unsigned char *)&nKey, 32); + for (size_t bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) { + for (size_t entry = 0; entry < ADDRMAN_BUCKET_SIZE; entry++) { + vvNew[bucket][entry] = -1; + } + } + for (size_t bucket = 0; bucket < ADDRMAN_TRIED_BUCKET_COUNT; bucket++) { + for (size_t entry = 0; entry < ADDRMAN_BUCKET_SIZE; entry++) { + vvTried[bucket][entry] = -1; + } + } + + nIdCount = 0; + nTried = 0; + nNew = 0; + } + + CAddrMan() + { + Clear(); + } + + ~CAddrMan() + { + nKey = uint256(0); } // Return the number of (unique) addresses in all tables. - int size() + int size() { return vRandom.size(); } @@ -462,15 +523,16 @@ public: } } - // Choose an address to connect to. - // nUnkBias determines how much "new" entries are favored over "tried" ones (0-100). - CAddress Select(int nUnkBias = 50) + /** + * Choose an address to connect to. + */ + CAddress Select() { CAddress addrRet; { LOCK(cs); Check(); - addrRet = Select_(nUnkBias); + addrRet = Select_(); Check(); } return addrRet; diff --git a/src/compat.h b/src/compat.h index 8fbafb6cc..1b3a60d11 100644 --- a/src/compat.h +++ b/src/compat.h @@ -59,19 +59,4 @@ typedef u_int SOCKET; #define SOCKET_ERROR -1 #endif -inline int myclosesocket(SOCKET& hSocket) -{ - if (hSocket == INVALID_SOCKET) - return WSAENOTSOCK; -#ifdef WIN32 - int ret = closesocket(hSocket); -#else - int ret = close(hSocket); -#endif - hSocket = INVALID_SOCKET; - return ret; -} -#define closesocket(s) myclosesocket(s) - - #endif diff --git a/src/init.cpp b/src/init.cpp index 1737bb069..90a9e681e 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -62,7 +62,8 @@ CWallet* pwalletMain; enum BindFlags { BF_NONE = 0, BF_EXPLICIT = (1U << 0), - BF_REPORT_ERROR = (1U << 1) + BF_REPORT_ERROR = (1U << 1), + BF_WHITELIST = (1U << 2), }; @@ -183,7 +184,7 @@ bool static Bind(const CService &addr, unsigned int flags) { if (!(flags & BF_EXPLICIT) && IsLimited(addr)) return false; std::string strError; - if (!BindListenPort(addr, strError)) { + if (!BindListenPort(addr, strError, flags & BF_WHITELIST)) { if (flags & BF_REPORT_ERROR) return InitError(strError); return false; @@ -236,9 +237,8 @@ std::string HelpMessage(HelpMessageMode hmm) strUsage += " -onion= " + _("Use separate SOCKS5 proxy to reach peers via Tor hidden services (default: -proxy)") + "\n"; strUsage += " -onlynet= " + _("Only connect to nodes in network (IPv4, IPv6 or Tor)") + "\n"; strUsage += " -port= " + _("Listen for connections on (default: 22556 or testnet: 44556)") + "\n"; - strUsage += " -proxy= " + _("Connect through SOCKS proxy") + "\n"; + strUsage += " -proxy= " + _("Connect through SOCKS5 proxy") + "\n"; strUsage += " -seednode= " + _("Connect to a node to retrieve peer addresses, and disconnect") + "\n"; - strUsage += " -socks= " + _("Select SOCKS version for -proxy (4 or 5, default: 5)") + "\n"; strUsage += " -timeout= " + _("Specify connection timeout in milliseconds (default: 5000)") + "\n"; #ifdef USE_UPNP #if USE_UPNP @@ -247,6 +247,8 @@ std::string HelpMessage(HelpMessageMode hmm) strUsage += " -upnp " + _("Use UPnP to map the listening port (default: 0)") + "\n"; #endif #endif + strUsage += " -whitebind= " + _("Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6") + "\n"; + strUsage += " -whitelist= " + _("Whitelist peers connecting from the given netmask or ip. Can be specified multiple times.") + "\n"; #ifdef ENABLE_WALLET strUsage += "\n" + _("Wallet options:") + "\n"; @@ -479,11 +481,11 @@ bool AppInit2(boost::thread_group& threadGroup) nLocalServices |= NODE_BLOOM; } - if (mapArgs.count("-bind")) { + if (mapArgs.count("-bind") || mapArgs.count("-whitebind")) { // when specifying an explicit binding address, you want to listen on it // even when -connect or -proxy is specified if (SoftSetBoolArg("-listen", true)) - LogPrintf("AppInit2 : parameter interaction: -bind set -> setting -listen=1\n"); + LogPrintf("AppInit2 : parameter interaction: -bind or -whitebind set -> setting -listen=1\n"); } if (mapArgs.count("-connect") && mapMultiArgs["-connect"].size() > 0) { @@ -527,7 +529,7 @@ bool AppInit2(boost::thread_group& threadGroup) } // Make sure enough file descriptors are available - int nBind = std::max((int)mapArgs.count("-bind"), 1); + int nBind = std::max((int)mapArgs.count("-bind") + (int)mapArgs.count("-whitebind"), 1); nMaxConnections = GetArg("-maxconnections", 125); nMaxConnections = std::max(std::min(nMaxConnections, (int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS)), 0); int nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS); @@ -549,6 +551,12 @@ bool AppInit2(boost::thread_group& threadGroup) // Check for -debugnet (deprecated) if (GetBoolArg("-debugnet", false)) InitWarning(_("Warning: Deprecated argument -debugnet ignored, use -debug=net")); + // Check for -socks - as this is a privacy risk to continue, exit here + if (mapArgs.count("-socks")) + return InitError(_("Error: Unsupported argument -socks found. Setting SOCKS version isn't possible anymore, only SOCKS5 proxies are supported.")); + // Check for -tor - as this is a privacy risk to continue, exit here + if (GetBoolArg("-tor", false)) + return InitError(_("Error: Unsupported argument -tor found, use -onion.")); fBenchmark = GetBoolArg("-benchmark", false); mempool.setSanityCheck(GetBoolArg("-checkmempool", RegTest())); @@ -732,10 +740,6 @@ bool AppInit2(boost::thread_group& threadGroup) RegisterNodeSignals(GetNodeSignals()); - int nSocksVersion = GetArg("-socks", 5); - if (nSocksVersion != 4 && nSocksVersion != 5) - return InitError(strprintf(_("Unknown -socks proxy version requested: %i"), nSocksVersion)); - if (mapArgs.count("-onlynet")) { std::set nets; BOOST_FOREACH(std::string snet, mapMultiArgs["-onlynet"]) { @@ -751,6 +755,15 @@ bool AppInit2(boost::thread_group& threadGroup) } } + if (mapArgs.count("-whitelist")) { + BOOST_FOREACH(const std::string& net, mapMultiArgs["-whitelist"]) { + CSubNet subnet(net); + if (!subnet.IsValid()) + return InitError(strprintf(_("Invalid netmask specified in -whitelist: '%s'"), net)); + CNode::AddWhitelistedRange(subnet); + } + } + CService addrProxy; bool fProxy = false; if (mapArgs.count("-proxy")) { @@ -759,12 +772,10 @@ bool AppInit2(boost::thread_group& threadGroup) return InitError(strprintf(_("Invalid -proxy address: '%s'"), mapArgs["-proxy"])); if (!IsLimited(NET_IPV4)) - SetProxy(NET_IPV4, addrProxy, nSocksVersion); - if (nSocksVersion > 4) { - if (!IsLimited(NET_IPV6)) - SetProxy(NET_IPV6, addrProxy, nSocksVersion); - SetNameProxy(addrProxy, nSocksVersion); - } + SetProxy(NET_IPV4, addrProxy); + if (!IsLimited(NET_IPV6)) + SetProxy(NET_IPV6, addrProxy); + SetNameProxy(addrProxy); fProxy = true; } @@ -782,24 +793,32 @@ bool AppInit2(boost::thread_group& threadGroup) addrOnion = mapArgs.count("-onion")?CService(mapArgs["-onion"], 9050):CService(mapArgs["-tor"], 9050); if (!addrOnion.IsValid()) return InitError(strprintf(_("Invalid -onion address: '%s'"), mapArgs.count("-onion")?mapArgs["-onion"]:mapArgs["-tor"])); - SetProxy(NET_TOR, addrOnion, 5); + SetProxy(NET_TOR, addrOnion); SetReachable(NET_TOR); } // see Step 2: parameter interactions for more information about these - fNoListen = !GetBoolArg("-listen", true); + fListen = GetBoolArg("-listen", true); fDiscover = GetBoolArg("-discover", true); fNameLookup = GetBoolArg("-dns", true); bool fBound = false; - if (!fNoListen) { - if (mapArgs.count("-bind")) { + if (fListen) { + if (mapArgs.count("-bind") || mapArgs.count("-whitebind")) { BOOST_FOREACH(std::string strBind, mapMultiArgs["-bind"]) { CService addrBind; if (!Lookup(strBind.c_str(), addrBind, GetListenPort(), false)) return InitError(strprintf(_("Cannot resolve -bind address: '%s'"), strBind)); fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR)); } + BOOST_FOREACH(std::string strBind, mapMultiArgs["-whitebind"]) { + CService addrBind; + if (!Lookup(strBind.c_str(), addrBind, 0, false)) + return InitError(strprintf(_("Cannot resolve -whitebind address: '%s'"), strBind)); + if (addrBind.GetPort() == 0) + return InitError(strprintf(_("Need to specify a port with -whitebind: '%s'"), strBind)); + fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR | BF_WHITELIST)); + } } else { struct in_addr inaddr_any; diff --git a/src/main.cpp b/src/main.cpp index ed237a73c..9d1eca948 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -207,9 +207,13 @@ struct CBlockReject { // processing of incoming data is done after the ProcessMessage call returns, // and we're no longer holding the node's locks. struct CNodeState { + //! The peer's address + CService address; + //! Whether we have a fully established connection. + bool fCurrentlyConnected; // Accumulated misbehaviour score for this peer. int nMisbehavior; - // Whether this peer should be disconnected and banned. + // Whether this peer should be disconnected and banned (unless whitelisted). bool fShouldBan; // String name of this peer (debugging/logging purposes). std::string name; @@ -223,6 +227,7 @@ struct CNodeState { int64_t nLastBlockProcess; CNodeState() { + fCurrentlyConnected = false; nMisbehavior = 0; fShouldBan = false; nBlocksToDownload = 0; @@ -253,12 +258,16 @@ void InitializeNode(NodeId nodeid, const CNode *pnode) { LOCK(cs_main); CNodeState &state = mapNodeState.insert(std::make_pair(nodeid, CNodeState())).first->second; state.name = pnode->addrName; + state.address = pnode->addr; } void FinalizeNode(NodeId nodeid) { LOCK(cs_main); CNodeState *state = State(nodeid); + if (state->nMisbehavior == 0 && state->fCurrentlyConnected) { + AddressCurrentlyConnected(state->address); + } BOOST_FOREACH(const QueuedBlock& entry, state->vBlocksInFlight) mapBlocksInFlight.erase(entry.hash); BOOST_FOREACH(const uint256& hash, state->vBlocksToDownload) @@ -1571,7 +1580,8 @@ void Misbehaving(NodeId pnode, int howmuch) return; state->nMisbehavior += howmuch; - if (state->nMisbehavior >= GetArg("-banscore", 100)) + int banscore = GetArg("-banscore", 100); + if (state->nMisbehavior >= banscore && state->nMisbehavior - howmuch < banscore) { LogPrintf("Misbehaving: %s (%d -> %d) BAN THRESHOLD EXCEEDED\n", state->name, state->nMisbehavior-howmuch, state->nMisbehavior); state->fShouldBan = true; @@ -3737,7 +3747,7 @@ void static ProcessGetData(CNode* pfrom) } } -bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) +bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, int64_t nTimeReceived) { RandAddSeedPerfmon(); LogPrint("net", "received: %s (%" PRIszu" bytes)\n", strCommand, vRecv.size()); @@ -3822,7 +3832,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) if (!pfrom->fInbound) { // Advertise our address - if (!fNoListen && !IsInitialBlockDownload()) + if (fListen && !IsInitialBlockDownload()) { CAddress addr = GetLocalAddress(&pfrom->addr); if (addr.IsRoutable()) @@ -3870,6 +3880,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) else if (strCommand == "verack") { pfrom->SetRecvVersion(min(pfrom->nVersion, PROTOCOL_VERSION)); + + // Mark this node as currently connected, so we update its timestamp later. + if (pfrom->fNetworkNode) { + LOCK(cs_main); + State(pfrom->GetId())->fCurrentlyConnected = true; + } } @@ -4163,6 +4179,11 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx); if (nEvicted > 0) LogPrint("mempool", "mapOrphan overflow, removed %u tx\n", nEvicted); + } else if (pfrom->fWhitelisted) { + // Always relay transactions received from whitelisted peers, even + // if they are already in the mempool (allowing the node to function + // as a gateway for nodes hidden behind it). + RelayTransaction(tx, tx.GetHash()); } int nDoS = 0; if (state.IsInvalid(nDoS)) @@ -4257,7 +4278,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) else if (strCommand == "pong") { - int64_t pingUsecEnd = GetTimeMicros(); + int64_t pingUsecEnd = nTimeReceived; uint64_t nonce = 0; size_t nAvail = vRecv.in_avail(); bool bPingFinished = false; @@ -4426,11 +4447,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) } - // Update the last seen time for this node's address - if (pfrom->fNetworkNode) - if (strCommand == "version" || strCommand == "addr" || strCommand == "inv" || strCommand == "getdata" || strCommand == "ping") - AddressCurrentlyConnected(pfrom->addr); - return true; } @@ -4513,7 +4529,7 @@ bool ProcessMessages(CNode* pfrom) bool fRet = false; try { - fRet = ProcessMessage(pfrom, strCommand, vRecv); + fRet = ProcessMessage(pfrom, strCommand, vRecv, msg.nTime); boost::this_thread::interruption_point(); } catch (std::ios_base::failure& e) @@ -4572,8 +4588,8 @@ bool SendMessages(CNode* pto, bool fSendTrickle) // RPC ping request by user pingSend = true; } - if (pto->nLastSend && GetTime() - pto->nLastSend > 30 * 60 && pto->vSendMsg.empty()) { - // Ping automatically sent as a keepalive + if (pto->nPingNonceSent == 0 && pto->nPingUsecStart + PING_INTERVAL * 1000000 < GetTimeMicros()) { + // Ping automatically sent as a latency probe & keepalive. pingSend = true; } if (pingSend) { @@ -4581,15 +4597,14 @@ bool SendMessages(CNode* pto, bool fSendTrickle) while (nonce == 0) { RAND_bytes((unsigned char*)&nonce, sizeof(nonce)); } - pto->nPingNonceSent = nonce; pto->fPingQueued = false; + pto->nPingUsecStart = GetTimeMicros(); if (pto->nVersion > BIP0031_VERSION) { - // Take timestamp as close as possible before transmitting ping - pto->nPingUsecStart = GetTimeMicros(); + pto->nPingNonceSent = nonce; pto->PushMessage("ping", nonce); } else { - // Peer is too old to support ping command with nonce, pong will never arrive, disable timing - pto->nPingUsecStart = 0; + // Peer is too old to support ping command with nonce, pong will never arrive. + pto->nPingNonceSent = 0; pto->PushMessage("ping"); } } @@ -4611,7 +4626,7 @@ bool SendMessages(CNode* pto, bool fSendTrickle) pnode->setAddrKnown.clear(); // Rebroadcast our address - if (!fNoListen) + if (fListen) { CAddress addr = GetLocalAddress(&pnode->addr); if (addr.IsRoutable()) @@ -4650,11 +4665,14 @@ bool SendMessages(CNode* pto, bool fSendTrickle) CNodeState &state = *State(pto->GetId()); if (state.fShouldBan) { - if (pto->addr.IsLocal()) - LogPrintf("Warning: not banning local node %s!\n", pto->addr.ToString()); + if (pto->fWhitelisted) + LogPrintf("Warning: not punishing whitelisted peer %s!\n", pto->addr.ToString()); else { pto->fDisconnect = true; - CNode::Ban(pto->addr); + if (pto->addr.IsLocal()) + LogPrintf("Warning: not banning local peer %s!\n", pto->addr.ToString()); + else + CNode::Ban(pto->addr); } state.fShouldBan = false; } diff --git a/src/net.cpp b/src/net.cpp index f337447ff..279ee4d76 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -36,9 +36,31 @@ #define MSG_NOSIGNAL 0 #endif +// Fix for ancient MinGW versions, that don't have defined these in ws2tcpip.h. +// Todo: Can be removed when our pull-tester is upgraded to a modern MinGW version. +#ifdef WIN32 +#ifndef PROTECTION_LEVEL_UNRESTRICTED +#define PROTECTION_LEVEL_UNRESTRICTED 10 +#endif +#ifndef IPV6_PROTECTION_LEVEL +#define IPV6_PROTECTION_LEVEL 23 +#endif +#endif + using namespace std; using namespace boost; +namespace { + const int MAX_OUTBOUND_CONNECTIONS = 8; + + struct ListenSocket { + SOCKET socket; + bool whitelisted; + + ListenSocket(SOCKET socket, bool whitelisted) : socket(socket), whitelisted(whitelisted) {} + }; +} + bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false); @@ -46,6 +68,7 @@ bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOu // Global state variables // bool fDiscover = true; +bool fListen = true; uint64_t nLocalServices = NODE_NETWORK; CCriticalSection cs_mapLocalHost; map mapLocalHost; @@ -54,7 +77,7 @@ static bool vfLimited[NET_MAX] = {}; static CNode* pnodeLocalHost = NULL; static CNode* pnodeSync = NULL; uint64_t nLocalHostNonce = 0; -static std::vector vhListenSocket; +static std::vector vhListenSocket; CAddrMan addrman; int nMaxConnections = 125; int nMaxOutboundConnections = 8; @@ -98,7 +121,7 @@ unsigned short GetListenPort() // find 'best' local address for a particular peer bool GetLocal(CService& addr, const CNetAddr *paddrPeer) { - if (fNoListen) + if (!fListen) return false; int nBestScore = -1; @@ -313,7 +336,7 @@ bool GetMyExternalIP2(const CService& addrConnect, const char* pszGet, const cha { if (!RecvLine(hSocket, strLine)) { - closesocket(hSocket); + CloseSocket(hSocket); return false; } if (pszKeyword == NULL) @@ -324,7 +347,7 @@ bool GetMyExternalIP2(const CService& addrConnect, const char* pszGet, const cha break; } } - closesocket(hSocket); + CloseSocket(hSocket); if (strLine.find("<") != string::npos) strLine = strLine.substr(0, strLine.find("<")); strLine = strLine.substr(strspn(strLine.c_str(), " \t\n\r")); @@ -338,7 +361,7 @@ bool GetMyExternalIP2(const CService& addrConnect, const char* pszGet, const cha return true; } } - closesocket(hSocket); + CloseSocket(hSocket); return error("GetMyExternalIP() : connection closed"); } @@ -499,8 +522,7 @@ void CNode::CloseSocketDisconnect() if (hSocket != INVALID_SOCKET) { LogPrint("net", "disconnecting node %s\n", addrName); - closesocket(hSocket); - hSocket = INVALID_SOCKET; + CloseSocket(hSocket); } // in case this fails, we'll empty the recv buffer when the CNode is deleted @@ -513,11 +535,6 @@ void CNode::CloseSocketDisconnect() pnodeSync = NULL; } -void CNode::Cleanup() -{ -} - - void CNode::PushVersion() { int nBestHeight = g_signals.GetHeight().get_value_or(0); @@ -570,6 +587,24 @@ bool CNode::Ban(const CNetAddr &addr) { return true; } + +std::vector CNode::vWhitelistedRange; +CCriticalSection CNode::cs_vWhitelistedRange; + +bool CNode::IsWhitelistedRange(const CNetAddr &addr) { + LOCK(cs_vWhitelistedRange); + BOOST_FOREACH(const CSubNet& subnet, vWhitelistedRange) { + if (subnet.Match(addr)) + return true; + } + return false; +} + +void CNode::AddWhitelistedRange(const CSubNet &subnet) { + LOCK(cs_vWhitelistedRange); + vWhitelistedRange.push_back(subnet); +} + #undef X #define X(name) stats.name = name void CNode::copyStats(CNodeStats &stats) @@ -586,6 +621,7 @@ void CNode::copyStats(CNodeStats &stats) X(nStartingHeight); X(nSendBytes); X(nRecvBytes); + X(fWhitelisted); stats.fSyncNode = (this == pnodeSync); // It is common for nodes with good ping times to suddenly become lagged, @@ -630,8 +666,16 @@ bool CNode::ReceiveMsgBytes(const char *pch, unsigned int nBytes) if (handled < 0) return false; + if (msg.in_data && msg.hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_LENGTH) { + LogPrint("net", "Oversized message from peer=%i, disconnecting", GetId()); + return false; + } + pch += handled; nBytes -= handled; + + if (msg.complete()) + msg.nTime = GetTimeMicros(); } return true; @@ -759,7 +803,6 @@ void ThreadSocketHandler() // close socket and cleanup pnode->CloseSocketDisconnect(); - pnode->Cleanup(); // hold in disconnected pool until all refs are released if (pnode->fNetworkNode || pnode->fInbound) @@ -803,7 +846,6 @@ void ThreadSocketHandler() uiInterface.NotifyNumConnectionsChanged(nPrevNodeCount); } - // // Find which sockets have data to receive // @@ -820,11 +862,12 @@ void ThreadSocketHandler() SOCKET hSocketMax = 0; bool have_fds = false; - BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket) { - FD_SET(hListenSocket, &fdsetRecv); - hSocketMax = max(hSocketMax, hListenSocket); + BOOST_FOREACH(const ListenSocket& hListenSocket, vhListenSocket) { + FD_SET(hListenSocket.socket, &fdsetRecv); + hSocketMax = max(hSocketMax, hListenSocket.socket); have_fds = true; } + { LOCK(cs_vNodes); BOOST_FOREACH(CNode* pnode, vNodes) @@ -885,58 +928,61 @@ void ThreadSocketHandler() MilliSleep(timeout.tv_usec/1000); } - // // Accept new connections // - BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket) - if (hListenSocket != INVALID_SOCKET && FD_ISSET(hListenSocket, &fdsetRecv)) + BOOST_FOREACH(const ListenSocket& hListenSocket, vhListenSocket) { - struct sockaddr_storage sockaddr; - socklen_t len = sizeof(sockaddr); - SOCKET hSocket = accept(hListenSocket, (struct sockaddr*)&sockaddr, &len); - CAddress addr; - int nInbound = 0; + if (hListenSocket.socket != INVALID_SOCKET && FD_ISSET(hListenSocket.socket, &fdsetRecv)) + { + struct sockaddr_storage sockaddr; + socklen_t len = sizeof(sockaddr); + SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len); + CAddress addr; + int nInbound = 0; - if (hSocket != INVALID_SOCKET) - if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) - LogPrintf("Warning: Unknown socket family\n"); + if (hSocket != INVALID_SOCKET) + if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) + LogPrintf("Warning: Unknown socket family\n"); - { - LOCK(cs_vNodes); - BOOST_FOREACH(CNode* pnode, vNodes) - if (pnode->fInbound) - nInbound++; - } - - if (hSocket == INVALID_SOCKET) - { - int nErr = WSAGetLastError(); - if (nErr != WSAEWOULDBLOCK) - LogPrintf("socket error accept failed: %s\n", NetworkErrorString(nErr)); - } - else if (nInbound >= nMaxConnections - nMaxOutboundConnections) - { - closesocket(hSocket); - } - else if (CNode::IsBanned(addr)) - { - LogPrintf("connection from %s dropped (banned)\n", addr.ToString()); - closesocket(hSocket); - } - else - { - LogPrint("net", "accepted connection %s\n", addr.ToString()); - CNode* pnode = new CNode(hSocket, addr, "", true); - pnode->AddRef(); + bool whitelisted = hListenSocket.whitelisted || CNode::IsWhitelistedRange(addr); { LOCK(cs_vNodes); - vNodes.push_back(pnode); + BOOST_FOREACH(CNode* pnode, vNodes) + if (pnode->fInbound) + nInbound++; + } + + if (hSocket == INVALID_SOCKET) + { + int nErr = WSAGetLastError(); + if (nErr != WSAEWOULDBLOCK) + LogPrintf("socket error accept failed: %s\n", NetworkErrorString(nErr)); + } + else if (nInbound >= nMaxConnections - MAX_OUTBOUND_CONNECTIONS) + { + CloseSocket(hSocket); + } + else if (CNode::IsBanned(addr) && !whitelisted) + { + LogPrintf("connection from %s dropped (banned)\n", addr.ToString()); + CloseSocket(hSocket); + } + else + { + LogPrint("net", "accepted connection %s\n", addr.ToString()); + CNode* pnode = new CNode(hSocket, addr, "", true); + pnode->AddRef(); + pnode->fWhitelisted = whitelisted; + + { + LOCK(cs_vNodes); + vNodes.push_back(pnode); + } } } } - // // Service each socket // @@ -1010,23 +1056,27 @@ void ThreadSocketHandler() // // Inactivity checking // - if (pnode->vSendMsg.empty()) - pnode->nLastSendEmpty = GetTime(); - if (GetTime() - pnode->nTimeConnected > 60) + int64_t nTime = GetTime(); + if (nTime - pnode->nTimeConnected > 60) { if (pnode->nLastRecv == 0 || pnode->nLastSend == 0) { LogPrint("net", "socket no message in first 60 seconds, %d %d\n", pnode->nLastRecv != 0, pnode->nLastSend != 0); pnode->fDisconnect = true; } - else if (GetTime() - pnode->nLastSend > 90*60 && GetTime() - pnode->nLastSendEmpty > 90*60) + else if (nTime - pnode->nLastSend > TIMEOUT_INTERVAL) { - LogPrintf("socket not sending\n"); + LogPrintf("socket sending timeout: %is\n", nTime - pnode->nLastSend); pnode->fDisconnect = true; } - else if (GetTime() - pnode->nLastRecv > 90*60) + else if (nTime - pnode->nLastRecv > (pnode->nVersion > BIP0031_VERSION ? TIMEOUT_INTERVAL : 90*60)) { - LogPrintf("socket inactivity timeout\n"); + LogPrintf("socket receive timeout: %is\n", nTime - pnode->nLastRecv); + pnode->fDisconnect = true; + } + else if (pnode->nPingNonceSent && pnode->nPingUsecStart + TIMEOUT_INTERVAL * 1000000 < GetTimeMicros()) + { + LogPrintf("ping timeout: %fs\n", 0.000001 * (GetTimeMicros() - pnode->nPingUsecStart)); pnode->fDisconnect = true; } } @@ -1310,8 +1360,7 @@ void ThreadOpenConnections() int nTries = 0; while (true) { - // use an nUnkBias between 10 (no outgoing connections) and 90 (8 outgoing connections) - CAddress addr = addrman.Select(10 + min(nOutbound,8)*10); + CAddress addr = addrman.Select(); // if we selected an invalid address, restart if (!addr.IsValid() || setConnected.count(addr.GetGroup()) || IsLocal(addr)) @@ -1557,7 +1606,7 @@ void ThreadMessageHandler() -bool BindListenPort(const CService &addrBind, string& strError) +bool BindListenPort(const CService &addrBind, string& strError, bool fWhitelisted) { strError = ""; int nOne = 1; @@ -1580,18 +1629,16 @@ bool BindListenPort(const CService &addrBind, string& strError) return false; } +#ifndef WIN32 #ifdef SO_NOSIGPIPE // Different way of disabling SIGPIPE on BSD setsockopt(hListenSocket, SOL_SOCKET, SO_NOSIGPIPE, (void*)&nOne, sizeof(int)); #endif - -#ifndef WIN32 // Allow binding if the port is still in TIME_WAIT state after - // the program was closed and restarted. Not an issue on windows. + // the program was closed and restarted. Not an issue on windows! setsockopt(hListenSocket, SOL_SOCKET, SO_REUSEADDR, (void*)&nOne, sizeof(int)); #endif - #ifdef WIN32 // Set to non-blocking, incoming connections will also inherit this if (ioctlsocket(hListenSocket, FIONBIO, (u_long*)&nOne) == SOCKET_ERROR) @@ -1615,10 +1662,8 @@ bool BindListenPort(const CService &addrBind, string& strError) #endif #endif #ifdef WIN32 - int nProtLevel = 10 /* PROTECTION_LEVEL_UNRESTRICTED */; - int nParameterId = 23 /* IPV6_PROTECTION_LEVEl */; - // this call is allowed to fail - setsockopt(hListenSocket, IPPROTO_IPV6, nParameterId, (const char*)&nProtLevel, sizeof(int)); + int nProtLevel = PROTECTION_LEVEL_UNRESTRICTED; + setsockopt(hListenSocket, IPPROTO_IPV6, IPV6_PROTECTION_LEVEL, (const char*)&nProtLevel, sizeof(int)); #endif } @@ -1630,6 +1675,7 @@ bool BindListenPort(const CService &addrBind, string& strError) else strError = strprintf(_("Unable to bind to %s on this computer (bind returned error %s)"), addrBind.ToString(), NetworkErrorString(nErr)); LogPrintf("%s\n", strError); + CloseSocket(hListenSocket); return false; } LogPrintf("Bound to %s\n", addrBind.ToString()); @@ -1639,12 +1685,13 @@ bool BindListenPort(const CService &addrBind, string& strError) { strError = strprintf(_("Error: Listening for incoming connections failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError())); LogPrintf("%s\n", strError); + CloseSocket(hListenSocket); return false; } - vhListenSocket.push_back(hListenSocket); + vhListenSocket.push_back(ListenSocket(hListenSocket, fWhitelisted)); - if (addrBind.IsRoutable() && fDiscover) + if (addrBind.IsRoutable() && fDiscover && !fWhitelisted) AddLocal(addrBind, LOCAL_BIND); return true; @@ -1771,11 +1818,11 @@ public: // Close sockets BOOST_FOREACH(CNode* pnode, vNodes) if (pnode->hSocket != INVALID_SOCKET) - closesocket(pnode->hSocket); - BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket) - if (hListenSocket != INVALID_SOCKET) - if (closesocket(hListenSocket) == SOCKET_ERROR) - LogPrintf("closesocket(hListenSocket) failed with error %s\n", NetworkErrorString(WSAGetLastError())); + CloseSocket(pnode->hSocket); + BOOST_FOREACH(ListenSocket& hListenSocket, vhListenSocket) + if (hListenSocket.socket != INVALID_SOCKET) + if (!CloseSocket(hListenSocket.socket)) + LogPrintf("CloseSocket(hListenSocket) failed with error %s\n", NetworkErrorString(WSAGetLastError())); // clean up some globals (to help leak detection) BOOST_FOREACH(CNode *pnode, vNodes) @@ -1784,6 +1831,7 @@ public: delete pnode; vNodes.clear(); vNodesDisconnected.clear(); + vhListenSocket.clear(); delete semOutbound; semOutbound = NULL; delete pnodeLocalHost; diff --git a/src/net.h b/src/net.h index 5862129fa..6fc1a8ba6 100644 --- a/src/net.h +++ b/src/net.h @@ -28,6 +28,7 @@ #include #include + class CAddrMan; class CBlockIndex; class CNode; @@ -36,8 +37,14 @@ namespace boost { class thread_group; } +/** Time between pings automatically sent out for latency probing and keepalive (in seconds). */ +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; /** The maximum number of entries in an 'inv' protocol message */ static const unsigned int MAX_INV_SZ = 50000; +/** Maximum length of incoming protocol messages (no message over 2 MiB is currently acceptable). */ +static const unsigned int MAX_PROTOCOL_MESSAGE_LENGTH = 2 * 1024 * 1024; /** The maximum number of entries in mapAskFor */ static const size_t MAPASKFOR_MAX_SZ = MAX_INV_SZ; @@ -53,7 +60,7 @@ CNode* FindNode(const CService& ip); CNode* ConnectNode(CAddress addrConnect, const char *strDest = NULL); void MapPort(bool fUseUPnP); unsigned short GetListenPort(); -bool BindListenPort(const CService &bindAddr, std::string& strError=REF(std::string())); +bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhitelisted = false); void StartNode(boost::thread_group& threadGroup); bool StopNode(); void SocketSendData(CNode *pnode); @@ -100,6 +107,7 @@ CAddress GetLocalAddress(const CNetAddr *paddrPeer = NULL); extern bool fDiscover; +extern bool fListen; extern uint64_t nLocalServices; extern uint64_t nLocalHostNonce; extern CAddrMan addrman; @@ -143,6 +151,7 @@ public: uint64_t nSendBytes; uint64_t nRecvBytes; bool fSyncNode; + bool fWhitelisted; double dPingTime; double dPingWait; std::string addrLocal; @@ -162,11 +171,14 @@ public: CDataStream vRecv; // received message data unsigned int nDataPos; + int64_t nTime; // time (in microseconds) of message receipt. + CNetMessage(int nTypeIn, int nVersionIn) : hdrbuf(nTypeIn, nVersionIn), vRecv(nTypeIn, nVersionIn) { hdrbuf.resize(24); in_data = false; nHdrPos = 0; nDataPos = 0; + nTime = 0; } bool complete() const @@ -212,7 +224,6 @@ public: int64_t nLastSend; int64_t nLastRecv; - int64_t nLastSendEmpty; int64_t nTimeConnected; CAddress addr; std::string addrName; @@ -223,6 +234,7 @@ public: // store the sanitized version in cleanSubVer. The original should be used when dealing with // 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 fOneShot; bool fClient; bool fInbound; @@ -246,6 +258,11 @@ protected: static std::map setBanned; static CCriticalSection cs_setBanned; + // Whitelisted ranges. Any node connecting from these is automatically + // whitelisted (as well as those connecting to whitelisted binds). + static std::vector vWhitelistedRange; + static CCriticalSection cs_vWhitelistedRange; + // Basic fuzz-testing void Fuzz(int nChance); // modifies ssSend @@ -268,10 +285,14 @@ public: CCriticalSection cs_inventory; std::multimap mapAskFor; - // Ping time measurement + // Ping time measurement: + // The pong reply we're expecting, or 0 if no pong expected. uint64_t nPingNonceSent; + // Time (in usec) the last ping was sent, or 0 if no ping was ever sent. int64_t nPingUsecStart; + // Last measured round-trip time. int64_t nPingUsecTime; + // Whether a ping is requested. bool fPingQueued; CNode(SOCKET hSocketIn, CAddress addrIn, std::string addrNameIn = "", bool fInboundIn=false) : ssSend(SER_NETWORK, INIT_PROTO_VERSION), setAddrKnown(5000) @@ -283,12 +304,12 @@ public: nLastRecv = 0; nSendBytes = 0; nRecvBytes = 0; - nLastSendEmpty = GetTime(); nTimeConnected = GetTime(); addr = addrIn; addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn; nVersion = 0; strSubVer = ""; + fWhitelisted = false; fOneShot = false; fClient = false; // set by version message fInbound = fInboundIn; @@ -328,8 +349,7 @@ public: { if (hSocket != INVALID_SOCKET) { - closesocket(hSocket); - hSocket = INVALID_SOCKET; + CloseSocket(hSocket); } if (pfilter) delete pfilter; @@ -682,8 +702,6 @@ public: void Subscribe(unsigned int nChannel, unsigned int nHops=0); void CancelSubscribe(unsigned int nChannel); void CloseSocketDisconnect(); - void Cleanup(); - // Denial-of-service detection/prevention // The idea is to detect peers that are behaving @@ -704,6 +722,9 @@ public: static bool Ban(const CNetAddr &ip); void copyStats(CNodeStats &stats); + static bool IsWhitelistedRange(const CNetAddr &ip); + static void AddWhitelistedRange(const CSubNet &subnet); + // Network stats static void RecordBytesRecv(uint64_t bytes); static void RecordBytesSent(uint64_t bytes); diff --git a/src/netbase.cpp b/src/netbase.cpp index e24a0a195..75b168322 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -25,7 +25,7 @@ using namespace std; // Settings static proxyType proxyInfo[NET_MAX]; -static proxyType nameproxyInfo; +static CService nameProxy; static CCriticalSection cs_proxyInfos; int nConnectTimeout = 5000; bool fNameLookup = false; @@ -161,56 +161,12 @@ bool LookupNumeric(const char *pszName, CService& addr, int portDefault) return Lookup(pszName, addr, portDefault, false); } -bool static Socks4(const CService &addrDest, SOCKET& hSocket) -{ - LogPrintf("SOCKS4 connecting %s\n", addrDest.ToString()); - if (!addrDest.IsIPv4()) - { - closesocket(hSocket); - return error("Proxy destination is not IPv4"); - } - char pszSocks4IP[] = "\4\1\0\0\0\0\0\0user"; - struct sockaddr_in addr; - socklen_t len = sizeof(addr); - if (!addrDest.GetSockAddr((struct sockaddr*)&addr, &len) || addr.sin_family != AF_INET) - { - closesocket(hSocket); - return error("Cannot get proxy destination address"); - } - memcpy(pszSocks4IP + 2, &addr.sin_port, 2); - memcpy(pszSocks4IP + 4, &addr.sin_addr, 4); - char* pszSocks4 = pszSocks4IP; - int nSize = sizeof(pszSocks4IP); - - int ret = send(hSocket, pszSocks4, nSize, MSG_NOSIGNAL); - if (ret != nSize) - { - closesocket(hSocket); - return error("Error sending to proxy"); - } - char pchRet[8]; - if (recv(hSocket, pchRet, 8, 0) != 8) - { - closesocket(hSocket); - return error("Error reading proxy response"); - } - if (pchRet[1] != 0x5a) - { - closesocket(hSocket); - if (pchRet[1] != 0x5b) - LogPrintf("ERROR: Proxy returned error %d\n", pchRet[1]); - return false; - } - LogPrintf("SOCKS4 connected %s\n", addrDest.ToString()); - return true; -} - bool static Socks5(string strDest, int port, SOCKET& hSocket) { LogPrintf("SOCKS5 connecting %s\n", strDest); if (strDest.size() > 255) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Hostname too long"); } char pszSocks5Init[] = "\5\1\0"; @@ -219,18 +175,18 @@ bool static Socks5(string strDest, int port, SOCKET& hSocket) ssize_t ret = send(hSocket, pszSocks5Init, nSize, MSG_NOSIGNAL); if (ret != nSize) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Error sending to proxy"); } char pchRet1[2]; if (recv(hSocket, pchRet1, 2, 0) != 2) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Error reading proxy response"); } if (pchRet1[0] != 0x05 || pchRet1[1] != 0x00) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Proxy failed to initialize"); } string strSocks5("\5\1"); @@ -242,23 +198,23 @@ bool static Socks5(string strDest, int port, SOCKET& hSocket) ret = send(hSocket, strSocks5.c_str(), strSocks5.size(), MSG_NOSIGNAL); if (ret != (ssize_t)strSocks5.size()) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Error sending to proxy"); } char pchRet2[4]; if (recv(hSocket, pchRet2, 4, 0) != 4) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Error reading proxy response"); } if (pchRet2[0] != 0x05) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Proxy failed to accept request"); } if (pchRet2[1] != 0x00) { - closesocket(hSocket); + CloseSocket(hSocket); switch (pchRet2[1]) { case 0x01: return error("Proxy error: general failure"); @@ -274,7 +230,7 @@ bool static Socks5(string strDest, int port, SOCKET& hSocket) } if (pchRet2[2] != 0x00) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Error: malformed proxy response"); } char pchRet3[256]; @@ -286,23 +242,23 @@ bool static Socks5(string strDest, int port, SOCKET& hSocket) { ret = recv(hSocket, pchRet3, 1, 0) != 1; if (ret) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Error reading from proxy"); } int nRecv = pchRet3[0]; ret = recv(hSocket, pchRet3, nRecv, 0) != nRecv; break; } - default: closesocket(hSocket); return error("Error: malformed proxy response"); + default: CloseSocket(hSocket); return error("Error: malformed proxy response"); } if (ret) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Error reading from proxy"); } if (recv(hSocket, pchRet3, 2, 0) != 2) { - closesocket(hSocket); + CloseSocket(hSocket); return error("Error reading from proxy"); } LogPrintf("SOCKS5 connected %s\n", strDest); @@ -336,7 +292,7 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe if (fcntl(hSocket, F_SETFL, fFlags | O_NONBLOCK) == -1) #endif { - closesocket(hSocket); + CloseSocket(hSocket); return false; } @@ -356,13 +312,13 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe if (nRet == 0) { LogPrint("net", "connection to %s timeout\n", addrConnect.ToString()); - closesocket(hSocket); + CloseSocket(hSocket); return false; } if (nRet == SOCKET_ERROR) { LogPrintf("select() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); - closesocket(hSocket); + CloseSocket(hSocket); return false; } socklen_t nRetSize = sizeof(nRet); @@ -373,13 +329,13 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe #endif { LogPrintf("getsockopt() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); - closesocket(hSocket); + CloseSocket(hSocket); return false; } if (nRet != 0) { LogPrintf("connect() to %s failed after select(): %s\n", addrConnect.ToString(), NetworkErrorString(nRet)); - closesocket(hSocket); + CloseSocket(hSocket); return false; } } @@ -390,7 +346,7 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe #endif { LogPrintf("connect() to %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); - closesocket(hSocket); + CloseSocket(hSocket); return false; } } @@ -406,7 +362,7 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe if (fcntl(hSocket, F_SETFL, fFlags & ~O_NONBLOCK) == SOCKET_ERROR) #endif { - closesocket(hSocket); + CloseSocket(hSocket); return false; } @@ -414,53 +370,49 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe return true; } -bool SetProxy(enum Network net, CService addrProxy, int nSocksVersion) { +bool SetProxy(enum Network net, CService addrProxy) { assert(net >= 0 && net < NET_MAX); - if (nSocksVersion != 0 && nSocksVersion != 4 && nSocksVersion != 5) - return false; - if (nSocksVersion != 0 && !addrProxy.IsValid()) + if (!addrProxy.IsValid()) return false; LOCK(cs_proxyInfos); - proxyInfo[net] = std::make_pair(addrProxy, nSocksVersion); + proxyInfo[net] = addrProxy; return true; } bool GetProxy(enum Network net, proxyType &proxyInfoOut) { assert(net >= 0 && net < NET_MAX); LOCK(cs_proxyInfos); - if (!proxyInfo[net].second) + if (!proxyInfo[net].IsValid()) return false; proxyInfoOut = proxyInfo[net]; return true; } -bool SetNameProxy(CService addrProxy, int nSocksVersion) { - if (nSocksVersion != 0 && nSocksVersion != 5) - return false; - if (nSocksVersion != 0 && !addrProxy.IsValid()) +bool SetNameProxy(CService addrProxy) { + if (!addrProxy.IsValid()) return false; LOCK(cs_proxyInfos); - nameproxyInfo = std::make_pair(addrProxy, nSocksVersion); + nameProxy = addrProxy; return true; } -bool GetNameProxy(proxyType &nameproxyInfoOut) { +bool GetNameProxy(CService &nameProxyOut) { LOCK(cs_proxyInfos); - if (!nameproxyInfo.second) + if(!nameProxy.IsValid()) return false; - nameproxyInfoOut = nameproxyInfo; + nameProxyOut = nameProxy; return true; } bool HaveNameProxy() { LOCK(cs_proxyInfos); - return nameproxyInfo.second != 0; + return nameProxy.IsValid(); } bool IsProxy(const CNetAddr &addr) { LOCK(cs_proxyInfos); for (int i = 0; i < NET_MAX; i++) { - if (proxyInfo[i].second && (addr == (CNetAddr)proxyInfo[i].first)) + if (addr == (CNetAddr)proxyInfo[i]) return true; } return false; @@ -469,31 +421,18 @@ bool IsProxy(const CNetAddr &addr) { bool ConnectSocket(const CService &addrDest, SOCKET& hSocketRet, int nTimeout) { proxyType proxy; - - // no proxy needed + // no proxy needed (none set for target network) if (!GetProxy(addrDest.GetNetwork(), proxy)) return ConnectSocketDirectly(addrDest, hSocketRet, nTimeout); SOCKET hSocket = INVALID_SOCKET; // first connect to proxy server - if (!ConnectSocketDirectly(proxy.first, hSocket, nTimeout)) + if (!ConnectSocketDirectly(proxy, hSocket, nTimeout)) return false; - // do socks negotiation - switch (proxy.second) { - case 4: - if (!Socks4(addrDest, hSocket)) - return false; - break; - case 5: - if (!Socks5(addrDest.ToStringIP(), addrDest.GetPort(), hSocket)) - return false; - break; - default: - closesocket(hSocket); + if (!Socks5(addrDest.ToStringIP(), addrDest.GetPort(), hSocket)) return false; - } hSocketRet = hSocket; return true; @@ -507,30 +446,25 @@ bool ConnectSocketByName(CService &addr, SOCKET& hSocketRet, const char *pszDest SOCKET hSocket = INVALID_SOCKET; - proxyType nameproxy; - GetNameProxy(nameproxy); + CService nameProxy; + GetNameProxy(nameProxy); - CService addrResolved(CNetAddr(strDest, fNameLookup && !nameproxy.second), port); + CService addrResolved(CNetAddr(strDest, fNameLookup && !HaveNameProxy()), port); if (addrResolved.IsValid()) { addr = addrResolved; return ConnectSocket(addr, hSocketRet, nTimeout); } - addr = CService("0.0.0.0:0"); - if (!nameproxy.second) - return false; - if (!ConnectSocketDirectly(nameproxy.first, hSocket, nTimeout)) - return false; - switch(nameproxy.second) { - default: - case 4: - closesocket(hSocket); - return false; - case 5: - if (!Socks5(strDest, port, hSocket)) - return false; - break; - } + addr = CService("0.0.0.0:0"); + + if (!HaveNameProxy()) + return false; + // first connect to name proxy server + if (!ConnectSocketDirectly(nameProxy, hSocket, nTimeout)) + return false; + // do socks negotiation + if (!Socks5(strDest, (unsigned short)port, hSocket)) + return false; hSocketRet = hSocket; return true; @@ -1270,3 +1204,16 @@ std::string NetworkErrorString(int err) return strprintf("%s (%d)", s, err); } #endif + +bool CloseSocket(SOCKET& hSocket) +{ + if (hSocket == INVALID_SOCKET) + return false; +#ifdef WIN32 + int ret = closesocket(hSocket); +#else + int ret = close(hSocket); +#endif + hSocket = INVALID_SOCKET; + return ret != SOCKET_ERROR; +} diff --git a/src/netbase.h b/src/netbase.h index 5fd8be4ac..f816c6437 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -163,14 +163,14 @@ class CService : public CNetAddr ) }; -typedef std::pair proxyType; +typedef CService proxyType; enum Network ParseNetwork(std::string net); void SplitHostPort(std::string in, int &portOut, std::string &hostOut); -bool SetProxy(enum Network net, CService addrProxy, int nSocksVersion = 5); +bool SetProxy(enum Network net, CService addrProxy); bool GetProxy(enum Network net, proxyType &proxyInfoOut); bool IsProxy(const CNetAddr &addr); -bool SetNameProxy(CService addrProxy, int nSocksVersion = 5); +bool SetNameProxy(CService addrProxy); bool HaveNameProxy(); bool LookupHost(const char *pszName, std::vector& vIP, unsigned int nMaxSolutions = 0, bool fAllowLookup = true); bool LookupHostNumeric(const char *pszName, std::vector& vIP, unsigned int nMaxSolutions = 0); @@ -181,5 +181,7 @@ bool ConnectSocket(const CService &addr, SOCKET& hSocketRet, int nTimeout = nCon bool ConnectSocketByName(CService &addr, SOCKET& hSocketRet, const char *pszDest, int portDefault = 0, int nTimeout = nConnectTimeout); /** Return readable error string for a network error code */ std::string NetworkErrorString(int err); +/** Close socket and set hSocket to INVALID_SOCKET */ +bool CloseSocket(SOCKET& hSocket); #endif diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index 8c961c825..c7341d315 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -318,26 +318,6 @@ - - - - SOCKS &Version: - - - Qt::PlainText - - - socksVersion - - - - - - - SOCKS version of the proxy (e.g. 5) - - - diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index e8365fc25..eccb0b803 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -49,15 +49,8 @@ OptionsDialog::OptionsDialog(QWidget *parent) : ui->proxyPort->setEnabled(false); ui->proxyPort->setValidator(new QIntValidator(1, 65535, this)); - /** SOCKS version is only selectable for default proxy and is always 5 for IPv6 and Tor */ - ui->socksVersion->setEnabled(false); - ui->socksVersion->addItem("5", 5); - ui->socksVersion->addItem("4", 4); - ui->socksVersion->setCurrentIndex(0); - connect(ui->connectSocks, SIGNAL(toggled(bool)), ui->proxyIp, SLOT(setEnabled(bool))); connect(ui->connectSocks, SIGNAL(toggled(bool)), ui->proxyPort, SLOT(setEnabled(bool))); - connect(ui->connectSocks, SIGNAL(toggled(bool)), ui->socksVersion, SLOT(setEnabled(bool))); ui->proxyIp->installEventFilter(this); @@ -175,7 +168,6 @@ void OptionsDialog::setMapper() mapper->addMapping(ui->connectSocks, OptionsModel::ProxyUse); mapper->addMapping(ui->proxyIp, OptionsModel::ProxyIP); mapper->addMapping(ui->proxyPort, OptionsModel::ProxyPort); - mapper->addMapping(ui->socksVersion, OptionsModel::ProxySocksVersion); /* Window */ #ifndef Q_OS_MAC diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 15ecca503..2c0bd23be 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -123,11 +123,6 @@ void OptionsModel::Init() // Only try to set -proxy, if user has enabled fUseProxy if (settings.value("fUseProxy").toBool() && !SoftSetArg("-proxy", settings.value("addrProxy").toString().toStdString())) addOverriddenOption("-proxy"); - if (!settings.contains("nSocksVersion")) - settings.setValue("nSocksVersion", 5); - // Only try to set -socks, if user has enabled fUseProxy - if (settings.value("fUseProxy").toBool() && !SoftSetArg("-socks", settings.value("nSocksVersion").toString().toStdString())) - addOverriddenOption("-socks"); // Display if (!settings.contains("language")) @@ -189,8 +184,6 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const QStringList strlIpPort = settings.value("addrProxy").toString().split(":", QString::SkipEmptyParts); return strlIpPort.at(1); } - case ProxySocksVersion: - return settings.value("nSocksVersion", 5); #ifdef ENABLE_WALLET case Fee: @@ -282,13 +275,6 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in } } break; - case ProxySocksVersion: { - if (settings.value("nSocksVersion") != value) { - settings.setValue("nSocksVersion", value.toInt()); - setRestartRequired(true); - } - } - break; #ifdef ENABLE_WALLET case Fee: // core option - can be changed on-the-fly // Todo: Add is valid check and warn via message, if not @@ -357,20 +343,16 @@ bool OptionsModel::getProxySettings(QNetworkProxy& proxy) const // GUI settings can be overridden with -proxy. proxyType curProxy; if (GetProxy(NET_IPV4, curProxy)) { - if (curProxy.second == 5) { - proxy.setType(QNetworkProxy::Socks5Proxy); - proxy.setHostName(QString::fromStdString(curProxy.first.ToStringIP())); - proxy.setPort(curProxy.first.GetPort()); + proxy.setType(QNetworkProxy::Socks5Proxy); + proxy.setHostName(QString::fromStdString(curProxy.ToStringIP())); + proxy.setPort(curProxy.GetPort()); - return true; - } - else - return false; + return true; } else proxy.setType(QNetworkProxy::NoProxy); - return true; + return false; } void OptionsModel::setRestartRequired(bool fRequired) diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index 31b7282b0..7fb35e6ee 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -345,20 +345,14 @@ void PaymentServer::initNetManager() QNetworkProxy proxy; - // Query active proxy (fails if no SOCKS5 proxy) + // Query active SOCKS5 proxy if (optionsModel->getProxySettings(proxy)) { - if (proxy.type() == QNetworkProxy::Socks5Proxy) { - netManager->setProxy(proxy); + netManager->setProxy(proxy); - qDebug() << "PaymentServer::initNetManager : Using SOCKS5 proxy" << proxy.hostName() << ":" << proxy.port(); - } - else - qDebug() << "PaymentServer::initNetManager : No active proxy server found."; + qDebug() << "PaymentServer::initNetManager : Using SOCKS5 proxy" << proxy.hostName() << ":" << proxy.port(); } else - emit message(tr("Net manager warning"), - tr("Your active proxy doesn't support SOCKS5, which is required for payment requests via proxy."), - CClientUIInterface::MSG_WARNING); + qDebug() << "PaymentServer::initNetManager : No active proxy server found."; connect(netManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(netRequestFinished(QNetworkReply*))); diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 267516c82..508c17cd0 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -71,7 +71,7 @@ Value getinfo(const Array& params, bool fHelp) obj.push_back(Pair("blocks", (int)chainActive.Height())); obj.push_back(Pair("timeoffset", GetTimeOffset())); obj.push_back(Pair("connections", (int)vNodes.size())); - obj.push_back(Pair("proxy", (proxy.first.IsValid() ? proxy.first.ToStringIPPort() : string()))); + obj.push_back(Pair("proxy", (proxy.IsValid() ? proxy.ToStringIPPort() : string()))); obj.push_back(Pair("difficulty", (double)GetDifficulty())); obj.push_back(Pair("testnet", TestNet())); #ifdef ENABLE_WALLET diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp index f5d8fad49..86f960222 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -134,8 +134,8 @@ Value getpeerinfo(const Array& params, bool fHelp) if (fStateStats) { obj.push_back(Pair("banscore", statestats.nMisbehavior)); } - if (stats.fSyncNode) - obj.push_back(Pair("syncnode", true)); + obj.push_back(Pair("syncnode", stats.fSyncNode)); + obj.push_back(Pair("whitelisted", stats.fWhitelisted)); ret.push_back(obj); } @@ -368,7 +368,7 @@ Value getnetworkinfo(const Array& params, bool fHelp) obj.push_back(Pair("protocolversion",(int)PROTOCOL_VERSION)); obj.push_back(Pair("timeoffset", GetTimeOffset())); obj.push_back(Pair("connections", (int)vNodes.size())); - obj.push_back(Pair("proxy", (proxy.first.IsValid() ? proxy.first.ToStringIPPort() : string()))); + obj.push_back(Pair("proxy", (proxy.IsValid() ? proxy.ToStringIPPort() : string()))); obj.push_back(Pair("relayfee", ValueFromAmount(CTransaction::nMinRelayTxFee))); Array localAddresses; { diff --git a/src/serialize.h b/src/serialize.h index a157048d5..738af4015 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -831,6 +831,35 @@ struct ser_streamplaceholder typedef std::vector > CSerializeData; +class CSizeComputer +{ +protected: + size_t nSize; + +public: + int nType; + int nVersion; + + CSizeComputer(int nTypeIn, int nVersionIn) : nSize(0), nType(nTypeIn), nVersion(nVersionIn) {} + + CSizeComputer& write(const char *psz, int nSize) + { + this->nSize += nSize; + return *this; + } + + template + CSizeComputer& operator<<(const T& obj) + { + ::Serialize(*this, obj, nType, nVersion); + return (*this); + } + + size_t size() const { + return nSize; + } +}; + /** Double ended buffer combining vector and stream-like interfaces. * * >> and << read and write unformatted data using the above serialization templates. diff --git a/src/util.cpp b/src/util.cpp index d1f76f6a5..d0e6bfee7 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -97,7 +97,6 @@ bool fDaemon = false; bool fServer = false; string strMiscWarning; bool fBloomFilters = true; -bool fNoListen = false; bool fLogTimestamps = false; volatile bool fReopenDebugLog = false; CClientUIInterface uiInterface; diff --git a/src/util.h b/src/util.h index 002946149..ffe346e5f 100644 --- a/src/util.h +++ b/src/util.h @@ -115,7 +115,6 @@ extern bool fPrintToDebugLog; extern bool fServer; extern std::string strMiscWarning; extern bool fBloomFilters; -extern bool fNoListen; extern bool fLogTimestamps; extern volatile bool fReopenDebugLog;