Skip to content

Commit

Permalink
Merge pull request bitcoin#1055 from sickpig/pick-xt-391
Browse files Browse the repository at this point in the history
[Port] Add test-before-evict countermeasure (bitcoin#3) to Eclipse Attack
  • Loading branch information
gandrewstone committed May 14, 2018
2 parents 5e6adb2 + 562fe5d commit 9f77923
Show file tree
Hide file tree
Showing 4 changed files with 343 additions and 17 deletions.
121 changes: 117 additions & 4 deletions src/addrman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ void CAddrMan::MakeTried(CAddrInfo &info, int nId)
info.fInTried = true;
}

void CAddrMan::Good_(const CService &addr, int64_t nTime)
void CAddrMan::Good_(const CService &addr, bool test_before_evict, int64_t nTime)
{
int nId;

Expand Down Expand Up @@ -254,10 +254,27 @@ void CAddrMan::Good_(const CService &addr, int64_t nTime)
if (nUBucket == -1)
return;

LOG(ADDRMAN, "Moving %s to tried\n", addr.ToString());
// which tried bucket to move the entry to
int tried_bucket = info.GetTriedBucket(nKey);
int tried_bucket_pos = info.GetBucketPosition(nKey, false, tried_bucket);

// move nId to the tried tables
MakeTried(info, nId);
// Will moving this address into tried evict another entry?
if (test_before_evict && (vvTried[tried_bucket][tried_bucket_pos] != -1))
{
LOG(ADDRMAN, "Collision inserting element into tried table, moving %s to m_tried_collisions=%d\n",
addr.ToString(), m_tried_collisions.size());
if (m_tried_collisions.size() < ADDRMAN_SET_TRIED_COLLISION_SIZE)
{
m_tried_collisions.insert(nId);
}
}
else
{
LOG(ADDRMAN, "Moving %s to tried\n", addr.ToString());

// move nId to the tried tables
MakeTried(info, nId);
}
}

bool CAddrMan::Add_(const CAddress &addr, const CNetAddr &source, int64_t nTimePenalty)
Expand Down Expand Up @@ -560,3 +577,99 @@ void CAddrMan::Connected_(const CService &addr, int64_t nTime)
}

int CAddrMan::RandomInt(int nMax) { return GetRandInt(nMax); }
void CAddrMan::ResolveCollisions_()
{
for (std::set<int>::iterator it = m_tried_collisions.begin(); it != m_tried_collisions.end();)
{
int id_new = *it;

bool erase_collision = false;

// If id_new not found in mapInfo remove it from m_tried_collisions
if (mapInfo.count(id_new) != 1)
{
erase_collision = true;
}
else
{
CAddrInfo &info_new = mapInfo[id_new];

// Which tried bucket to move the entry to.
int tried_bucket = info_new.GetTriedBucket(nKey);
int tried_bucket_pos = info_new.GetBucketPosition(nKey, false, tried_bucket);
if (!info_new.IsValid())
{ // id_new may no longer map to a valid address
erase_collision = true;
}
else if (vvTried[tried_bucket][tried_bucket_pos] != -1)
{ // The position in the tried bucket is not empty

// Get the to-be-evicted address that is being tested
int id_old = vvTried[tried_bucket][tried_bucket_pos];
CAddrInfo &info_old = mapInfo[id_old];

// Has successfully connected in last X hours
if (GetAdjustedTime() - info_old.nLastSuccess < ADDRMAN_REPLACEMENT_HOURS * (60 * 60))
{
erase_collision = true;
}
else if (GetAdjustedTime() - info_old.nLastTry < ADDRMAN_REPLACEMENT_HOURS * (60 * 60))
{ // attempted to connect and failed in last X hours

// Give address at least 60 seconds to successfully connect
if (GetAdjustedTime() - info_old.nLastTry > 60)
{
LOG(ADDRMAN, "Swapping %s for %s in tried table\n", info_new.ToString(), info_old.ToString());

// Replaces an existing address already in the tried table with the new address
Good_(info_new, false, GetAdjustedTime());
erase_collision = true;
}
}
}
else
{ // Collision is not actually a collision anymore
Good_(info_new, false, GetAdjustedTime());
erase_collision = true;
}
}

if (erase_collision)
{
m_tried_collisions.erase(it++);
}
else
{
it++;
}
}
}

CAddrInfo CAddrMan::SelectTriedCollision_()
{
if (m_tried_collisions.size() == 0)
return CAddrInfo();

std::set<int>::iterator it = m_tried_collisions.begin();

// Selects a random element from m_tried_collisions
std::advance(it, GetRandInt(m_tried_collisions.size()));
int id_new = *it;

// If id_new not found in mapInfo remove it from m_tried_collisions
if (mapInfo.count(id_new) != 1)
{
m_tried_collisions.erase(it);
return CAddrInfo();
}

CAddrInfo &newInfo = mapInfo[id_new];

// which tried bucket to move the entry to
int tried_bucket = newInfo.GetTriedBucket(nKey);
int tried_bucket_pos = newInfo.GetBucketPosition(nKey, false, tried_bucket);

int id_old = vvTried[tried_bucket][tried_bucket_pos];

return mapInfo[id_old];
}
44 changes: 41 additions & 3 deletions src/addrman.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,18 @@ class CAddrInfo : public CAddress
//! ... in at least this many days
#define ADDRMAN_MIN_FAIL_DAYS 7

//! how recent a successful connection should be before we allow an address to be evicted from tried
#define ADDRMAN_REPLACEMENT_HOURS 4

//! the maximum percentage of nodes to return in a getaddr call
#define ADDRMAN_GETADDR_MAX_PCT 23

//! the maximum number of nodes to return in a getaddr call
#define ADDRMAN_GETADDR_MAX 2500

//! the maximum number of tried addr collisions to store
#define ADDRMAN_SET_TRIED_COLLISION_SIZE 10

/**
* Stochastical (IP) address manager
*/
Expand Down Expand Up @@ -191,6 +197,10 @@ class CAddrMan
//! last time Good was called (memory only)
int64_t nLastGood;

//! Holds addrs inserted into tried table that collide with existing entries. Test-before-evict discpline used to
//! resolve these collisions.
std::set<int> m_tried_collisions;

protected:
//! secret key to randomize bucket select with
uint256 nKey;
Expand Down Expand Up @@ -218,7 +228,7 @@ class CAddrMan
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);
void Good_(const CService &addr, bool test_before_evict, int64_t time);

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

//! See if any to-be-evicted tried table entries have been tested and if so resolve the collisions.
void ResolveCollisions_();

//! Return a random to-be-evicted tried table address.
CAddrInfo SelectTriedCollision_();

//! Wraps GetRandInt to allow tests to override RandomInt and make it determinismistic.
virtual int RandomInt(int nMax);

Expand Down Expand Up @@ -533,12 +549,12 @@ class CAddrMan
}

//! Mark an entry as accessible.
void Good(const CService &addr, int64_t nTime = GetAdjustedTime())
void Good(const CService &addr, bool test_before_evict = true, int64_t nTime = GetAdjustedTime())
{
{
LOCK(cs);
Check();
Good_(addr, nTime);
Good_(addr, test_before_evict, nTime);
Check();
}
}
Expand All @@ -554,6 +570,28 @@ class CAddrMan
}
}

//! See if any to-be-evicted tried table entries have been tested and if so resolve the collisions.
void ResolveCollisions()
{
LOCK(cs);
Check();
ResolveCollisions_();
Check();
}

//! Randomly select an address in tried that another address is attempting to evict.
CAddrInfo SelectTriedCollision()
{
CAddrInfo ret;
{
LOCK(cs);
Check();
ret = SelectTriedCollision_();
Check();
}
return ret;
}

/**
* Choose an address to connect to.
*/
Expand Down
10 changes: 9 additions & 1 deletion src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1882,11 +1882,19 @@ void ThreadOpenConnections()
}
}

addrman.ResolveCollisions();

int64_t nANow = GetAdjustedTime();
int nTries = 0;
while (true)
{
CAddrInfo addr = addrman.Select(fFeeler);
CAddrInfo addr = addrman.SelectTriedCollision();

// SelectTriedCollision returns an invalid address if it is empty.
if (!fFeeler || !addr.IsValid())
{
addr = addrman.Select(fFeeler);
}

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

0 comments on commit 9f77923

Please sign in to comment.