Skip to content

Commit 50bb09d

Browse files
Ethan HeilmanFuzzbawls
authored andcommitted
Add test-before-evict discipline to addrman
Changes addrman to use the test-before-evict discipline in which an address is to be evicted from the tried table is first tested and if it is still online it is not evicted. Adds tests to provide test coverage for this change. This change was suggested as Countermeasure 3 in Eclipse Attacks on Bitcoin’s Peer-to-Peer Network, Ethan Heilman, Alison Kendler, Aviv Zohar, Sharon Goldberg. ePrint Archive Report 2015/263. March 2015.
1 parent 846dca7 commit 50bb09d

File tree

4 files changed

+319
-19
lines changed

4 files changed

+319
-19
lines changed

src/addrman.cpp

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId)
196196
info.fInTried = true;
197197
}
198198

199-
void CAddrMan::Good_(const CService& addr, int64_t nTime)
199+
void CAddrMan::Good_(const CService& addr, bool test_before_evict, int64_t nTime)
200200
{
201201
int nId;
202202

@@ -242,10 +242,22 @@ void CAddrMan::Good_(const CService& addr, int64_t nTime)
242242
if (nUBucket == -1)
243243
return;
244244

245-
LogPrint(BCLog::ADDRMAN, "Moving %s to tried\n", addr.ToString());
245+
// which tried bucket to move the entry to
246+
int tried_bucket = info.GetTriedBucket(nKey);
247+
int tried_bucket_pos = info.GetBucketPosition(nKey, false, tried_bucket);
248+
249+
// Will moving this address into tried evict another entry?
250+
if (test_before_evict && (vvTried[tried_bucket][tried_bucket_pos] != -1)) {
251+
LogPrint(BCLog::ADDRMAN, "addrman", "Collision inserting element into tried table, moving %s to m_tried_collisions=%d\n", addr.ToString(), m_tried_collisions.size());
252+
if (m_tried_collisions.size() < ADDRMAN_SET_TRIED_COLLISION_SIZE) {
253+
m_tried_collisions.insert(nId);
254+
}
255+
} else {
256+
LogPrint(BCLog::ADDRMAN, "Moving %s to tried\n", addr.ToString());
246257

247-
// move nId to the tried tables
248-
MakeTried(info, nId);
258+
// move nId to the tried tables
259+
MakeTried(info, nId);
260+
}
249261
}
250262

251263
bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimePenalty)
@@ -529,3 +541,82 @@ void CAddrMan::SetServices_(const CService& addr, ServiceFlags nServices)
529541
int CAddrMan::RandomInt(int nMax){
530542
return GetRandInt(nMax);
531543
}
544+
545+
void CAddrMan::ResolveCollisions_()
546+
{
547+
for (std::set<int>::iterator it = m_tried_collisions.begin(); it != m_tried_collisions.end();) {
548+
int id_new = *it;
549+
550+
bool erase_collision = false;
551+
552+
// If id_new not found in mapInfo remove it from m_tried_collisions
553+
if (mapInfo.count(id_new) != 1) {
554+
erase_collision = true;
555+
} else {
556+
CAddrInfo& info_new = mapInfo[id_new];
557+
558+
// Which tried bucket to move the entry to.
559+
int tried_bucket = info_new.GetTriedBucket(nKey);
560+
int tried_bucket_pos = info_new.GetBucketPosition(nKey, false, tried_bucket);
561+
if (!info_new.IsValid()) { // id_new may no longer map to a valid address
562+
erase_collision = true;
563+
} else if (vvTried[tried_bucket][tried_bucket_pos] != -1) { // The position in the tried bucket is not empty
564+
565+
// Get the to-be-evicted address that is being tested
566+
int id_old = vvTried[tried_bucket][tried_bucket_pos];
567+
CAddrInfo& info_old = mapInfo[id_old];
568+
569+
// Has successfully connected in last X hours
570+
if (GetAdjustedTime() - info_old.nLastSuccess < ADDRMAN_REPLACEMENT_HOURS*(60*60)) {
571+
erase_collision = true;
572+
} else if (GetAdjustedTime() - info_old.nLastTry < ADDRMAN_REPLACEMENT_HOURS*(60*60)) { // attempted to connect and failed in last X hours
573+
574+
// Give address at least 60 seconds to successfully connect
575+
if (GetAdjustedTime() - info_old.nLastTry > 60) {
576+
LogPrint(BCLog::ADDRMAN, "addrman", "Swapping %s for %s in tried table\n", info_new.ToString(), info_old.ToString());
577+
578+
// Replaces an existing address already in the tried table with the new address
579+
Good_(info_new, false, GetAdjustedTime());
580+
erase_collision = true;
581+
}
582+
}
583+
} else { // Collision is not actually a collision anymore
584+
Good_(info_new, false, GetAdjustedTime());
585+
erase_collision = true;
586+
}
587+
}
588+
589+
if (erase_collision) {
590+
m_tried_collisions.erase(it++);
591+
} else {
592+
it++;
593+
}
594+
}
595+
}
596+
597+
CAddrInfo CAddrMan::SelectTriedCollision_()
598+
{
599+
if (m_tried_collisions.size() == 0) return CAddrInfo();
600+
601+
std::set<int>::iterator it = m_tried_collisions.begin();
602+
603+
// Selects a random element from m_tried_collisions
604+
std::advance(it, GetRandInt(m_tried_collisions.size()));
605+
int id_new = *it;
606+
607+
// If id_new not found in mapInfo remove it from m_tried_collisions
608+
if (mapInfo.count(id_new) != 1) {
609+
m_tried_collisions.erase(it);
610+
return CAddrInfo();
611+
}
612+
613+
CAddrInfo& newInfo = mapInfo[id_new];
614+
615+
// which tried bucket to move the entry to
616+
int tried_bucket = newInfo.GetTriedBucket(nKey);
617+
int tried_bucket_pos = newInfo.GetBucketPosition(nKey, false, tried_bucket);
618+
619+
int id_old = vvTried[tried_bucket][tried_bucket_pos];
620+
621+
return mapInfo[id_old];
622+
}

src/addrman.h

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ class CAddrInfo : public CAddress
165165
//! ... in at least this many days
166166
#define ADDRMAN_MIN_FAIL_DAYS 7
167167

168+
//! how recent a successful connection should be before we allow an address to be evicted from tried
169+
#define ADDRMAN_REPLACEMENT_HOURS 4
170+
168171
//! the maximum percentage of nodes to return in a getaddr call
169172
#define ADDRMAN_GETADDR_MAX_PCT 23
170173

@@ -176,6 +179,9 @@ class CAddrInfo : public CAddress
176179
#define ADDRMAN_NEW_BUCKET_COUNT (1 << ADDRMAN_NEW_BUCKET_COUNT_LOG2)
177180
#define ADDRMAN_BUCKET_SIZE (1 << ADDRMAN_BUCKET_SIZE_LOG2)
178181

182+
//! the maximum number of tried addr collisions to store
183+
#define ADDRMAN_SET_TRIED_COLLISION_SIZE 10
184+
179185
/**
180186
* Stochastical (IP) address manager
181187
*/
@@ -212,6 +218,9 @@ class CAddrMan
212218
//! last time Good was called (memory only)
213219
int64_t nLastGood;
214220

221+
//! Holds addrs inserted into tried table that collide with existing entries. Test-before-evict discpline used to resolve these collisions.
222+
std::set<int> m_tried_collisions;
223+
215224
protected:
216225
//! secret key to randomize bucket select with
217226
uint256 nKey;
@@ -239,7 +248,7 @@ class CAddrMan
239248
void ClearNew(int nUBucket, int nUBucketPos);
240249

241250
//! Mark an entry "good", possibly moving it from "new" to "tried".
242-
void Good_(const CService& addr, int64_t nTime);
251+
void Good_(const CService& addr, bool test_before_evict, int64_t time);
243252

244253
//! Add an entry to the "new" table.
245254
bool Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimePenalty);
@@ -250,6 +259,12 @@ class CAddrMan
250259
//! Select an address to connect to, if newOnly is set to true, only the new table is selected from.
251260
CAddrInfo Select_(bool newOnly);
252261

262+
//! See if any to-be-evicted tried table entries have been tested and if so resolve the collisions.
263+
void ResolveCollisions_();
264+
265+
//! Return a random to-be-evicted tried table address.
266+
CAddrInfo SelectTriedCollision_();
267+
253268
//! Wraps GetRandInt to allow tests to override RandomInt and make it determinismistic.
254269
virtual int RandomInt(int nMax);
255270

@@ -525,11 +540,11 @@ class CAddrMan
525540
}
526541

527542
//! Mark an entry as accessible.
528-
void Good(const CService& addr, int64_t nTime = GetAdjustedTime())
543+
void Good(const CService& addr, bool test_before_evict = true, int64_t nTime = GetAdjustedTime())
529544
{
530545
LOCK(cs);
531546
Check();
532-
Good_(addr, nTime);
547+
Good_(addr, test_before_evict, nTime);
533548
Check();
534549
}
535550

@@ -542,6 +557,28 @@ class CAddrMan
542557
Check();
543558
}
544559

560+
//! See if any to-be-evicted tried table entries have been tested and if so resolve the collisions.
561+
void ResolveCollisions()
562+
{
563+
LOCK(cs);
564+
Check();
565+
ResolveCollisions_();
566+
Check();
567+
}
568+
569+
//! Randomly select an address in tried that another address is attempting to evict.
570+
CAddrInfo SelectTriedCollision()
571+
{
572+
CAddrInfo ret;
573+
{
574+
LOCK(cs);
575+
Check();
576+
ret = SelectTriedCollision_();
577+
Check();
578+
}
579+
return ret;
580+
}
581+
545582
/**
546583
* Choose an address to connect to.
547584
* nUnkBias determines how much "new" entries are favored over "tried" ones (0-100).

src/net.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1606,10 +1606,17 @@ void CConnman::ThreadOpenConnections()
16061606
}
16071607
}
16081608

1609+
addrman.ResolveCollisions();
1610+
16091611
int64_t nANow = GetAdjustedTime();
16101612
int nTries = 0;
16111613
while (!interruptNet) {
1612-
CAddrInfo addr = addrman.Select(fFeeler);
1614+
CAddrInfo addr = addrman.SelectTriedCollision();
1615+
1616+
// SelectTriedCollision returns an invalid address if it is empty.
1617+
if (!fFeeler || !addr.IsValid()) {
1618+
addr = addrman.Select(fFeeler);
1619+
}
16131620

16141621
// if we selected an invalid address, restart
16151622
if (!addr.IsValid() || setConnected.count(addr.GetGroup()) || IsLocal(addr))

0 commit comments

Comments
 (0)