2012-01-04 23:39:45 +01:00
// Copyright (c) 2012 Pieter Wuille
2016-01-27 12:05:25 +01:00
// Copyright (c) 2012-2015 The Bitcoin Core developers
2014-10-24 06:04:27 +02:00
// Distributed under the MIT software license, see the accompanying
2012-05-18 16:02:28 +02:00
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
2012-01-04 23:39:45 +01:00
2020-03-19 23:46:56 +01:00
# include <addrman.h>
2013-04-13 07:13:08 +02:00
2020-03-19 23:46:56 +01:00
# include <hash.h>
# include <serialize.h>
# include <streams.h>
2012-01-04 23:39:45 +01:00
2015-03-08 14:30:05 +01:00
int CAddrInfo : : GetTriedBucket ( const uint256 & nKey ) const
2012-01-04 23:39:45 +01:00
{
2015-03-19 16:50:04 +01:00
uint64_t hash1 = ( CHashWriter ( SER_GETHASH , 0 ) < < nKey < < GetKey ( ) ) . GetHash ( ) . GetCheapHash ( ) ;
uint64_t hash2 = ( CHashWriter ( SER_GETHASH , 0 ) < < nKey < < GetGroup ( ) < < ( hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP ) ) . GetHash ( ) . GetCheapHash ( ) ;
2012-01-04 23:39:45 +01:00
return hash2 % ADDRMAN_TRIED_BUCKET_COUNT ;
}
2015-03-08 14:30:05 +01:00
int CAddrInfo : : GetNewBucket ( const uint256 & nKey , const CNetAddr & src ) const
2012-01-04 23:39:45 +01:00
{
std : : vector < unsigned char > vchSourceGroupKey = src . GetGroup ( ) ;
2015-03-19 16:50:04 +01:00
uint64_t hash1 = ( CHashWriter ( SER_GETHASH , 0 ) < < nKey < < GetGroup ( ) < < vchSourceGroupKey ) . GetHash ( ) . GetCheapHash ( ) ;
uint64_t hash2 = ( CHashWriter ( SER_GETHASH , 0 ) < < nKey < < vchSourceGroupKey < < ( hash1 % ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP ) ) . GetHash ( ) . GetCheapHash ( ) ;
2012-01-04 23:39:45 +01:00
return hash2 % ADDRMAN_NEW_BUCKET_COUNT ;
}
2015-03-18 17:31:49 +01:00
int CAddrInfo : : GetBucketPosition ( const uint256 & nKey , bool fNew , int nBucket ) const
{
2015-03-19 16:50:04 +01:00
uint64_t hash1 = ( CHashWriter ( SER_GETHASH , 0 ) < < nKey < < ( fNew ? ' N ' : ' K ' ) < < nBucket < < GetKey ( ) ) . GetHash ( ) . GetCheapHash ( ) ;
2015-03-18 17:31:49 +01:00
return hash1 % ADDRMAN_BUCKET_SIZE ;
}
2013-04-13 07:13:08 +02:00
bool CAddrInfo : : IsTerrible ( int64_t nNow ) const
2012-01-04 23:39:45 +01:00
{
2014-10-24 06:04:27 +02:00
if ( nLastTry & & nLastTry > = nNow - 60 ) // never remove things tried in the last minute
2012-01-04 23:39:45 +01:00
return false ;
2014-09-19 19:21:46 +02:00
if ( nTime > nNow + 10 * 60 ) // came in a flying DeLorean
2012-01-04 23:39:45 +01:00
return true ;
2014-09-19 19:21:46 +02:00
if ( nTime = = 0 | | nNow - nTime > ADDRMAN_HORIZON_DAYS * 24 * 60 * 60 ) // not seen in recent history
2012-01-04 23:39:45 +01:00
return true ;
2014-09-19 19:21:46 +02:00
if ( nLastSuccess = = 0 & & nAttempts > = ADDRMAN_RETRIES ) // tried N times and never a success
2012-01-04 23:39:45 +01:00
return true ;
2014-09-19 19:21:46 +02:00
if ( nNow - nLastSuccess > ADDRMAN_MIN_FAIL_DAYS * 24 * 60 * 60 & & nAttempts > = ADDRMAN_MAX_FAILURES ) // N successive failures in the last week
2012-01-04 23:39:45 +01:00
return true ;
return false ;
}
2013-04-13 07:13:08 +02:00
double CAddrInfo : : GetChance ( int64_t nNow ) const
2012-01-04 23:39:45 +01:00
{
double fChance = 1.0 ;
2017-02-15 09:26:12 +01:00
int64_t nSinceLastTry = std : : max < int64_t > ( nNow - nLastTry , 0 ) ;
2012-01-04 23:39:45 +01:00
// deprioritize very recent attempts away
2014-09-19 19:21:46 +02:00
if ( nSinceLastTry < 60 * 10 )
2012-01-04 23:39:45 +01:00
fChance * = 0.01 ;
2015-04-19 20:47:56 +02:00
// deprioritize 66% after each failed attempt, but at most 1/28th to avoid the search taking forever or overly penalizing outages.
2015-06-15 14:45:19 +02:00
fChance * = pow ( 0.66 , std : : min ( nAttempts , 8 ) ) ;
2012-01-04 23:39:45 +01:00
return fChance ;
}
2018-07-07 23:19:33 +02:00
CAddrInfo * CAddrMan : : Find ( const CService & addr , int * pnId )
2012-01-04 23:39:45 +01:00
{
2018-07-07 23:19:33 +02:00
CService addr2 = addr ;
if ( ! discriminatePorts ) {
addr2 . SetPort ( 0 ) ;
}
std : : map < CService , int > : : iterator it = mapAddr . find ( addr2 ) ;
2012-01-04 23:39:45 +01:00
if ( it = = mapAddr . end ( ) )
2019-08-06 05:08:33 +02:00
return nullptr ;
2012-01-04 23:39:45 +01:00
if ( pnId )
* pnId = ( * it ) . second ;
std : : map < int , CAddrInfo > : : iterator it2 = mapInfo . find ( ( * it ) . second ) ;
if ( it2 ! = mapInfo . end ( ) )
return & ( * it2 ) . second ;
2019-08-06 05:08:33 +02:00
return nullptr ;
2012-01-04 23:39:45 +01:00
}
2014-09-19 19:21:46 +02:00
CAddrInfo * CAddrMan : : Create ( const CAddress & addr , const CNetAddr & addrSource , int * pnId )
2012-01-04 23:39:45 +01:00
{
2018-07-07 23:19:33 +02:00
CService addr2 = addr ;
if ( ! discriminatePorts ) {
addr2 . SetPort ( 0 ) ;
}
2012-01-04 23:39:45 +01:00
int nId = nIdCount + + ;
mapInfo [ nId ] = CAddrInfo ( addr , addrSource ) ;
2018-07-07 23:19:33 +02:00
mapAddr [ addr2 ] = nId ;
2012-01-04 23:39:45 +01:00
mapInfo [ nId ] . nRandomPos = vRandom . size ( ) ;
vRandom . push_back ( nId ) ;
if ( pnId )
* pnId = nId ;
return & mapInfo [ nId ] ;
}
2012-05-09 03:48:14 +02:00
void CAddrMan : : SwapRandom ( unsigned int nRndPos1 , unsigned int nRndPos2 )
2012-01-04 23:39:45 +01:00
{
if ( nRndPos1 = = nRndPos2 )
return ;
2012-05-05 21:30:38 +02:00
assert ( nRndPos1 < vRandom . size ( ) & & nRndPos2 < vRandom . size ( ) ) ;
2012-01-04 23:39:45 +01:00
int nId1 = vRandom [ nRndPos1 ] ;
int nId2 = vRandom [ nRndPos2 ] ;
2012-05-05 21:30:38 +02:00
assert ( mapInfo . count ( nId1 ) = = 1 ) ;
assert ( mapInfo . count ( nId2 ) = = 1 ) ;
2012-01-04 23:39:45 +01:00
mapInfo [ nId1 ] . nRandomPos = nRndPos2 ;
mapInfo [ nId2 ] . nRandomPos = nRndPos1 ;
vRandom [ nRndPos1 ] = nId2 ;
vRandom [ nRndPos2 ] = nId1 ;
}
2015-03-18 17:31:49 +01:00
void CAddrMan : : Delete ( int nId )
2012-01-04 23:39:45 +01:00
{
2015-03-18 17:31:49 +01:00
assert ( mapInfo . count ( nId ) ! = 0 ) ;
CAddrInfo & info = mapInfo [ nId ] ;
assert ( ! info . fInTried ) ;
assert ( info . nRefCount = = 0 ) ;
2012-01-04 23:39:45 +01:00
2018-07-07 23:19:33 +02:00
CService addr = info ;
if ( ! discriminatePorts ) {
addr . SetPort ( 0 ) ;
}
2015-03-18 17:31:49 +01:00
SwapRandom ( info . nRandomPos , vRandom . size ( ) - 1 ) ;
vRandom . pop_back ( ) ;
2018-07-07 23:19:33 +02:00
mapAddr . erase ( addr ) ;
2015-03-18 17:31:49 +01:00
mapInfo . erase ( nId ) ;
nNew - - ;
2012-01-04 23:39:45 +01:00
}
2015-03-18 17:31:49 +01:00
void CAddrMan : : ClearNew ( int nUBucket , int nUBucketPos )
2012-01-04 23:39:45 +01:00
{
2015-03-18 17:31:49 +01:00
// 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 ) ;
2012-01-04 23:39:45 +01:00
}
}
}
2015-03-18 17:31:49 +01:00
void CAddrMan : : MakeTried ( CAddrInfo & info , int nId )
2012-01-04 23:39:45 +01:00
{
// remove the entry from all new buckets
2015-03-18 17:31:49 +01:00
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 ;
2012-01-04 23:39:45 +01:00
info . nRefCount - - ;
2015-03-18 17:31:49 +01:00
}
2012-01-04 23:39:45 +01:00
}
nNew - - ;
2012-05-05 21:30:38 +02:00
assert ( info . nRefCount = = 0 ) ;
2014-10-24 06:04:27 +02:00
// which tried bucket to move the entry to
2012-01-04 23:39:45 +01:00
int nKBucket = info . GetTriedBucket ( nKey ) ;
2015-03-18 17:31:49 +01:00
int nKBucketPos = info . GetBucketPosition ( nKey , false , nKBucket ) ;
// 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 + + ;
2012-01-04 23:39:45 +01:00
}
2015-03-18 17:31:49 +01:00
assert ( vvTried [ nKBucket ] [ nKBucketPos ] = = - 1 ) ;
2012-01-04 23:39:45 +01:00
2015-03-18 17:31:49 +01:00
vvTried [ nKBucket ] [ nKBucketPos ] = nId ;
nTried + + ;
2012-01-04 23:39:45 +01:00
info . fInTried = true ;
}
Merge #9037: net: Add test-before-evict discipline to addrman
e68172ed9 Add test-before-evict discipline to addrman (Ethan Heilman)
Pull request description:
This change implement countermeasures 3 (test-before-evict) suggested in our paper: ["Eclipse Attacks on Bitcoin’s Peer-to-Peer Network"](http://cs-people.bu.edu/heilman/eclipse/).
# Design:
A collision occurs when an address, addr1, is being moved to the tried table from the new table, but maps to a position in the tried table which already contains an address (addr2). The current behavior is that addr1 would evict addr2 from the tried table.
This change ensures that during a collision, addr1 is not inserted into tried but instead inserted into a buffer (setTriedCollisions). The to-be-evicted address, addr2, is then tested by [a feeler connection](https://github.com/bitcoin/bitcoin/pull/8282). If addr2 is found to be online, we remove addr1 from the buffer and addr2 is not evicted, on the other hand if addr2 is found be offline it is replaced by addr1.
An additional small advantage of this change is that, as no more than ten addresses can be in the test buffer at once, and addresses are only cleared one at a time from the test buffer (at 2 minute intervals), thus an attacker is forced to wait at least two minutes to insert a new address into tried after filling up the test buffer. This rate limits an attacker attempting to launch an eclipse attack.
# Risk mitigation:
- To prevent this functionality from being used as a DoS vector, we limit the number of addresses which are to be tested to ten. If we have more than ten addresses to test, we drop new addresses being added to tried if they would evict an address. Since the feeler thread only creates one new connection every 2 minutes the additional network overhead is limited.
- An address in tried gains immunity from tests for 4 hours after it has been tested or successfully connected to.
# Tests:
This change includes additional addrman unittests which test this behavior.
I ran an instance of this change with a much smaller tried table (2 buckets of 64 addresses) so that collisions were much more likely and observed evictions.
```
2016-10-27 07:20:26 Swapping 208.12.64.252:8333 for 68.62.95.247:8333 in tried table
2016-10-27 07:20:26 Moving 208.12.64.252:8333 to tried
```
I documented tests we ran against similar earlier versions of this change in #6355.
# Security Benefit
This is was originally posted in PR #8282 see [this comment for full details](https://github.com/bitcoin/bitcoin/pull/8282#issuecomment-237255215).
To determine the security benefit of these larger numbers of IPs in the tried table I modeled the attack presented in [Eclipse Attacks on Bitcoin’s Peer-to-Peer Network](https://eprint.iacr.org/2015/263).
![attackergraph40000-10-1000short-line](https://cloud.githubusercontent.com/assets/274814/17366828/372af458-595b-11e6-81e5-2c9f97282305.png)
**Default node:** 595 attacker IPs for ~50% attack success.
**Default node + test-before-evict:** 620 attacker IPs for ~50% attack success.
**Feeler node:** 5540 attacker IPs for ~50% attack success.
**Feeler node + test-before-evict:** 8600 attacker IPs for ~50% attack success.
The node running feeler connections has 10 times as many online IP addresses in its tried table making an attack 10 times harder (i.e. requiring the an attacker require 10 times as many IP addresses in different /16s). Adding test-before-evict increases resistance of the node by an additional 3000 attacker IP addresses.
Below I graph the attack over even greater attacker resources (i.e. more attacker controled IP addresses). Note that test-before-evict maintains some security far longer even against an attacker with 50,000 IPs. If this node had a larger tried table test-before-evict could greatly boost a nodes resistance to eclipse attacks.
![attacker graph long view](https://cloud.githubusercontent.com/assets/274814/17367108/96f46d64-595c-11e6-91cd-edba160598e7.png)
Tree-SHA512: fdad4d26aadeaad9bcdc71929b3eb4e1f855b3ee3541fbfbe25dca8d7d0a1667815402db0cb4319db6bd3fcd32d67b5bbc0e12045c4252d62d6239b7d77c4395
2018-03-06 21:36:48 +01:00
void CAddrMan : : Good_ ( const CService & addr , bool test_before_evict , int64_t nTime )
2012-01-04 23:39:45 +01:00
{
int nId ;
2016-06-08 12:58:21 +02:00
nLastGood = nTime ;
2014-09-19 19:21:46 +02:00
CAddrInfo * pinfo = Find ( addr , & nId ) ;
2012-01-04 23:39:45 +01:00
// if not found, bail out
if ( ! pinfo )
return ;
2014-09-19 19:21:46 +02:00
CAddrInfo & info = * pinfo ;
2012-01-04 23:39:45 +01:00
// check whether we are talking about the exact same CService (including same port)
if ( info ! = addr )
return ;
// update info
info . nLastSuccess = nTime ;
info . nLastTry = nTime ;
info . nAttempts = 0 ;
2015-03-05 13:01:22 +01:00
// nTime is not updated here, to avoid leaking information about
// currently-connected peers.
2012-01-04 23:39:45 +01:00
// if it is already in the tried set, don't do anything else
if ( info . fInTried )
return ;
// find a bucket it is in now
2017-06-30 20:29:29 +02:00
int nRnd = RandomInt ( ADDRMAN_NEW_BUCKET_COUNT ) ;
2012-01-04 23:39:45 +01:00
int nUBucket = - 1 ;
2015-03-18 17:31:49 +01:00
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 ) {
2012-01-04 23:39:45 +01:00
nUBucket = nB ;
break ;
}
}
// if no bucket is found, something bad happened;
// TODO: maybe re-add the node, but for now, just bail out
2014-09-19 19:21:46 +02:00
if ( nUBucket = = - 1 )
return ;
2012-01-04 23:39:45 +01:00
Merge #9037: net: Add test-before-evict discipline to addrman
e68172ed9 Add test-before-evict discipline to addrman (Ethan Heilman)
Pull request description:
This change implement countermeasures 3 (test-before-evict) suggested in our paper: ["Eclipse Attacks on Bitcoin’s Peer-to-Peer Network"](http://cs-people.bu.edu/heilman/eclipse/).
# Design:
A collision occurs when an address, addr1, is being moved to the tried table from the new table, but maps to a position in the tried table which already contains an address (addr2). The current behavior is that addr1 would evict addr2 from the tried table.
This change ensures that during a collision, addr1 is not inserted into tried but instead inserted into a buffer (setTriedCollisions). The to-be-evicted address, addr2, is then tested by [a feeler connection](https://github.com/bitcoin/bitcoin/pull/8282). If addr2 is found to be online, we remove addr1 from the buffer and addr2 is not evicted, on the other hand if addr2 is found be offline it is replaced by addr1.
An additional small advantage of this change is that, as no more than ten addresses can be in the test buffer at once, and addresses are only cleared one at a time from the test buffer (at 2 minute intervals), thus an attacker is forced to wait at least two minutes to insert a new address into tried after filling up the test buffer. This rate limits an attacker attempting to launch an eclipse attack.
# Risk mitigation:
- To prevent this functionality from being used as a DoS vector, we limit the number of addresses which are to be tested to ten. If we have more than ten addresses to test, we drop new addresses being added to tried if they would evict an address. Since the feeler thread only creates one new connection every 2 minutes the additional network overhead is limited.
- An address in tried gains immunity from tests for 4 hours after it has been tested or successfully connected to.
# Tests:
This change includes additional addrman unittests which test this behavior.
I ran an instance of this change with a much smaller tried table (2 buckets of 64 addresses) so that collisions were much more likely and observed evictions.
```
2016-10-27 07:20:26 Swapping 208.12.64.252:8333 for 68.62.95.247:8333 in tried table
2016-10-27 07:20:26 Moving 208.12.64.252:8333 to tried
```
I documented tests we ran against similar earlier versions of this change in #6355.
# Security Benefit
This is was originally posted in PR #8282 see [this comment for full details](https://github.com/bitcoin/bitcoin/pull/8282#issuecomment-237255215).
To determine the security benefit of these larger numbers of IPs in the tried table I modeled the attack presented in [Eclipse Attacks on Bitcoin’s Peer-to-Peer Network](https://eprint.iacr.org/2015/263).
![attackergraph40000-10-1000short-line](https://cloud.githubusercontent.com/assets/274814/17366828/372af458-595b-11e6-81e5-2c9f97282305.png)
**Default node:** 595 attacker IPs for ~50% attack success.
**Default node + test-before-evict:** 620 attacker IPs for ~50% attack success.
**Feeler node:** 5540 attacker IPs for ~50% attack success.
**Feeler node + test-before-evict:** 8600 attacker IPs for ~50% attack success.
The node running feeler connections has 10 times as many online IP addresses in its tried table making an attack 10 times harder (i.e. requiring the an attacker require 10 times as many IP addresses in different /16s). Adding test-before-evict increases resistance of the node by an additional 3000 attacker IP addresses.
Below I graph the attack over even greater attacker resources (i.e. more attacker controled IP addresses). Note that test-before-evict maintains some security far longer even against an attacker with 50,000 IPs. If this node had a larger tried table test-before-evict could greatly boost a nodes resistance to eclipse attacks.
![attacker graph long view](https://cloud.githubusercontent.com/assets/274814/17367108/96f46d64-595c-11e6-91cd-edba160598e7.png)
Tree-SHA512: fdad4d26aadeaad9bcdc71929b3eb4e1f855b3ee3541fbfbe25dca8d7d0a1667815402db0cb4319db6bd3fcd32d67b5bbc0e12045c4252d62d6239b7d77c4395
2018-03-06 21:36:48 +01:00
// which tried bucket to move the entry to
int tried_bucket = info . GetTriedBucket ( nKey ) ;
int tried_bucket_pos = info . GetBucketPosition ( nKey , false , tried_bucket ) ;
// Will moving this address into tried evict another entry?
if ( test_before_evict & & ( vvTried [ tried_bucket ] [ tried_bucket_pos ] ! = - 1 ) ) {
2018-03-06 22:15:43 +01:00
if ( fLogIPs ) LogPrint ( BCLog : : ADDRMAN , " Collision inserting element into tried table, moving %s to m_tried_collisions=%d \n " , addr . ToString ( ) , m_tried_collisions . size ( ) ) ;
Merge #9037: net: Add test-before-evict discipline to addrman
e68172ed9 Add test-before-evict discipline to addrman (Ethan Heilman)
Pull request description:
This change implement countermeasures 3 (test-before-evict) suggested in our paper: ["Eclipse Attacks on Bitcoin’s Peer-to-Peer Network"](http://cs-people.bu.edu/heilman/eclipse/).
# Design:
A collision occurs when an address, addr1, is being moved to the tried table from the new table, but maps to a position in the tried table which already contains an address (addr2). The current behavior is that addr1 would evict addr2 from the tried table.
This change ensures that during a collision, addr1 is not inserted into tried but instead inserted into a buffer (setTriedCollisions). The to-be-evicted address, addr2, is then tested by [a feeler connection](https://github.com/bitcoin/bitcoin/pull/8282). If addr2 is found to be online, we remove addr1 from the buffer and addr2 is not evicted, on the other hand if addr2 is found be offline it is replaced by addr1.
An additional small advantage of this change is that, as no more than ten addresses can be in the test buffer at once, and addresses are only cleared one at a time from the test buffer (at 2 minute intervals), thus an attacker is forced to wait at least two minutes to insert a new address into tried after filling up the test buffer. This rate limits an attacker attempting to launch an eclipse attack.
# Risk mitigation:
- To prevent this functionality from being used as a DoS vector, we limit the number of addresses which are to be tested to ten. If we have more than ten addresses to test, we drop new addresses being added to tried if they would evict an address. Since the feeler thread only creates one new connection every 2 minutes the additional network overhead is limited.
- An address in tried gains immunity from tests for 4 hours after it has been tested or successfully connected to.
# Tests:
This change includes additional addrman unittests which test this behavior.
I ran an instance of this change with a much smaller tried table (2 buckets of 64 addresses) so that collisions were much more likely and observed evictions.
```
2016-10-27 07:20:26 Swapping 208.12.64.252:8333 for 68.62.95.247:8333 in tried table
2016-10-27 07:20:26 Moving 208.12.64.252:8333 to tried
```
I documented tests we ran against similar earlier versions of this change in #6355.
# Security Benefit
This is was originally posted in PR #8282 see [this comment for full details](https://github.com/bitcoin/bitcoin/pull/8282#issuecomment-237255215).
To determine the security benefit of these larger numbers of IPs in the tried table I modeled the attack presented in [Eclipse Attacks on Bitcoin’s Peer-to-Peer Network](https://eprint.iacr.org/2015/263).
![attackergraph40000-10-1000short-line](https://cloud.githubusercontent.com/assets/274814/17366828/372af458-595b-11e6-81e5-2c9f97282305.png)
**Default node:** 595 attacker IPs for ~50% attack success.
**Default node + test-before-evict:** 620 attacker IPs for ~50% attack success.
**Feeler node:** 5540 attacker IPs for ~50% attack success.
**Feeler node + test-before-evict:** 8600 attacker IPs for ~50% attack success.
The node running feeler connections has 10 times as many online IP addresses in its tried table making an attack 10 times harder (i.e. requiring the an attacker require 10 times as many IP addresses in different /16s). Adding test-before-evict increases resistance of the node by an additional 3000 attacker IP addresses.
Below I graph the attack over even greater attacker resources (i.e. more attacker controled IP addresses). Note that test-before-evict maintains some security far longer even against an attacker with 50,000 IPs. If this node had a larger tried table test-before-evict could greatly boost a nodes resistance to eclipse attacks.
![attacker graph long view](https://cloud.githubusercontent.com/assets/274814/17367108/96f46d64-595c-11e6-91cd-edba160598e7.png)
Tree-SHA512: fdad4d26aadeaad9bcdc71929b3eb4e1f855b3ee3541fbfbe25dca8d7d0a1667815402db0cb4319db6bd3fcd32d67b5bbc0e12045c4252d62d6239b7d77c4395
2018-03-06 21:36:48 +01:00
if ( m_tried_collisions . size ( ) < ADDRMAN_SET_TRIED_COLLISION_SIZE ) {
m_tried_collisions . insert ( nId ) ;
}
} else {
if ( fLogIPs ) LogPrint ( BCLog : : ADDRMAN , " Moving %s to tried \n " , addr . ToString ( ) ) ;
2012-01-04 23:39:45 +01:00
Merge #9037: net: Add test-before-evict discipline to addrman
e68172ed9 Add test-before-evict discipline to addrman (Ethan Heilman)
Pull request description:
This change implement countermeasures 3 (test-before-evict) suggested in our paper: ["Eclipse Attacks on Bitcoin’s Peer-to-Peer Network"](http://cs-people.bu.edu/heilman/eclipse/).
# Design:
A collision occurs when an address, addr1, is being moved to the tried table from the new table, but maps to a position in the tried table which already contains an address (addr2). The current behavior is that addr1 would evict addr2 from the tried table.
This change ensures that during a collision, addr1 is not inserted into tried but instead inserted into a buffer (setTriedCollisions). The to-be-evicted address, addr2, is then tested by [a feeler connection](https://github.com/bitcoin/bitcoin/pull/8282). If addr2 is found to be online, we remove addr1 from the buffer and addr2 is not evicted, on the other hand if addr2 is found be offline it is replaced by addr1.
An additional small advantage of this change is that, as no more than ten addresses can be in the test buffer at once, and addresses are only cleared one at a time from the test buffer (at 2 minute intervals), thus an attacker is forced to wait at least two minutes to insert a new address into tried after filling up the test buffer. This rate limits an attacker attempting to launch an eclipse attack.
# Risk mitigation:
- To prevent this functionality from being used as a DoS vector, we limit the number of addresses which are to be tested to ten. If we have more than ten addresses to test, we drop new addresses being added to tried if they would evict an address. Since the feeler thread only creates one new connection every 2 minutes the additional network overhead is limited.
- An address in tried gains immunity from tests for 4 hours after it has been tested or successfully connected to.
# Tests:
This change includes additional addrman unittests which test this behavior.
I ran an instance of this change with a much smaller tried table (2 buckets of 64 addresses) so that collisions were much more likely and observed evictions.
```
2016-10-27 07:20:26 Swapping 208.12.64.252:8333 for 68.62.95.247:8333 in tried table
2016-10-27 07:20:26 Moving 208.12.64.252:8333 to tried
```
I documented tests we ran against similar earlier versions of this change in #6355.
# Security Benefit
This is was originally posted in PR #8282 see [this comment for full details](https://github.com/bitcoin/bitcoin/pull/8282#issuecomment-237255215).
To determine the security benefit of these larger numbers of IPs in the tried table I modeled the attack presented in [Eclipse Attacks on Bitcoin’s Peer-to-Peer Network](https://eprint.iacr.org/2015/263).
![attackergraph40000-10-1000short-line](https://cloud.githubusercontent.com/assets/274814/17366828/372af458-595b-11e6-81e5-2c9f97282305.png)
**Default node:** 595 attacker IPs for ~50% attack success.
**Default node + test-before-evict:** 620 attacker IPs for ~50% attack success.
**Feeler node:** 5540 attacker IPs for ~50% attack success.
**Feeler node + test-before-evict:** 8600 attacker IPs for ~50% attack success.
The node running feeler connections has 10 times as many online IP addresses in its tried table making an attack 10 times harder (i.e. requiring the an attacker require 10 times as many IP addresses in different /16s). Adding test-before-evict increases resistance of the node by an additional 3000 attacker IP addresses.
Below I graph the attack over even greater attacker resources (i.e. more attacker controled IP addresses). Note that test-before-evict maintains some security far longer even against an attacker with 50,000 IPs. If this node had a larger tried table test-before-evict could greatly boost a nodes resistance to eclipse attacks.
![attacker graph long view](https://cloud.githubusercontent.com/assets/274814/17367108/96f46d64-595c-11e6-91cd-edba160598e7.png)
Tree-SHA512: fdad4d26aadeaad9bcdc71929b3eb4e1f855b3ee3541fbfbe25dca8d7d0a1667815402db0cb4319db6bd3fcd32d67b5bbc0e12045c4252d62d6239b7d77c4395
2018-03-06 21:36:48 +01:00
// move nId to the tried tables
MakeTried ( info , nId ) ;
}
2012-01-04 23:39:45 +01:00
}
2014-09-19 19:21:46 +02:00
bool CAddrMan : : Add_ ( const CAddress & addr , const CNetAddr & source , int64_t nTimePenalty )
2012-01-04 23:39:45 +01:00
{
if ( ! addr . IsRoutable ( ) )
return false ;
bool fNew = false ;
int nId ;
2014-09-19 19:21:46 +02:00
CAddrInfo * pinfo = Find ( addr , & nId ) ;
2012-01-04 23:39:45 +01:00
2017-01-29 16:05:06 +01:00
// Do not set a penalty for a source's self-announcement
2016-09-23 13:55:43 +02:00
if ( addr = = source ) {
nTimePenalty = 0 ;
}
2014-09-19 19:21:46 +02:00
if ( pinfo ) {
2012-01-04 23:39:45 +01:00
// periodically update nTime
bool fCurrentlyOnline = ( GetAdjustedTime ( ) - addr . nTime < 24 * 60 * 60 ) ;
2013-04-13 07:13:08 +02:00
int64_t nUpdateInterval = ( fCurrentlyOnline ? 60 * 60 : 24 * 60 * 60 ) ;
2012-01-04 23:39:45 +01:00
if ( addr . nTime & & ( ! pinfo - > nTime | | pinfo - > nTime < addr . nTime - nUpdateInterval - nTimePenalty ) )
2015-06-15 14:45:19 +02:00
pinfo - > nTime = std : : max ( ( int64_t ) 0 , addr . nTime - nTimePenalty ) ;
2012-01-04 23:39:45 +01:00
// add services
2017-07-05 05:45:23 +02:00
pinfo - > nServices = ServiceFlags ( pinfo - > nServices | addr . nServices ) ;
2012-01-04 23:39:45 +01:00
// do not update if no new information is present
2012-04-15 13:03:28 +02:00
if ( ! addr . nTime | | ( pinfo - > nTime & & addr . nTime < = pinfo - > nTime ) )
2012-01-04 23:39:45 +01:00
return false ;
// do not update if the entry was already in the "tried" table
if ( pinfo - > fInTried )
return false ;
// do not update if the max reference count is reached
if ( pinfo - > nRefCount = = ADDRMAN_NEW_BUCKETS_PER_ADDRESS )
return false ;
// stochastic test: previous nRefCount == N: 2^N times harder to increase it
int nFactor = 1 ;
2014-09-19 19:21:46 +02:00
for ( int n = 0 ; n < pinfo - > nRefCount ; n + + )
2012-01-04 23:39:45 +01:00
nFactor * = 2 ;
2017-06-30 20:29:29 +02:00
if ( nFactor > 1 & & ( RandomInt ( nFactor ) ! = 0 ) )
2012-01-04 23:39:45 +01:00
return false ;
} else {
pinfo = Create ( addr , source , & nId ) ;
2015-06-15 14:45:19 +02:00
pinfo - > nTime = std : : max ( ( int64_t ) 0 , ( int64_t ) pinfo - > nTime - nTimePenalty ) ;
2012-01-04 23:39:45 +01:00
nNew + + ;
fNew = true ;
}
int nUBucket = pinfo - > GetNewBucket ( nKey , source ) ;
2015-03-18 17:31:49 +01:00
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 ) ;
}
}
2012-01-04 23:39:45 +01:00
}
return fNew ;
}
2016-06-08 12:58:21 +02:00
void CAddrMan : : Attempt_ ( const CService & addr , bool fCountFailure , int64_t nTime )
2012-01-04 23:39:45 +01:00
{
2014-09-19 19:21:46 +02:00
CAddrInfo * pinfo = Find ( addr ) ;
2012-01-04 23:39:45 +01:00
// if not found, bail out
if ( ! pinfo )
return ;
2014-09-19 19:21:46 +02:00
CAddrInfo & info = * pinfo ;
2012-01-04 23:39:45 +01:00
// check whether we are talking about the exact same CService (including same port)
if ( info ! = addr )
return ;
// update info
info . nLastTry = nTime ;
2016-06-08 12:58:21 +02:00
if ( fCountFailure & & info . nLastCountAttempt < nLastGood ) {
info . nLastCountAttempt = nTime ;
info . nAttempts + + ;
}
2012-01-04 23:39:45 +01:00
}
2015-09-22 21:24:16 +02:00
CAddrInfo CAddrMan : : Select_ ( bool newOnly )
2012-01-04 23:39:45 +01:00
{
if ( size ( ) = = 0 )
2015-04-19 20:10:13 +02:00
return CAddrInfo ( ) ;
2012-01-04 23:39:45 +01:00
2015-09-22 21:24:16 +02:00
if ( newOnly & & nNew = = 0 )
return CAddrInfo ( ) ;
2015-03-19 17:51:59 +01:00
// Use a 50% chance for choosing between tried and new table entries.
2015-09-22 21:24:16 +02:00
if ( ! newOnly & &
2020-07-29 03:23:12 +02:00
( nTried > 0 & & ( nNew = = 0 | | RandomInt ( 2 ) = = 0 ) ) ) {
2012-01-04 23:39:45 +01:00
// use a tried node
double fChanceFactor = 1.0 ;
2014-09-19 19:21:46 +02:00
while ( 1 ) {
2017-06-30 20:29:29 +02:00
int nKBucket = RandomInt ( ADDRMAN_TRIED_BUCKET_COUNT ) ;
int nKBucketPos = RandomInt ( ADDRMAN_BUCKET_SIZE ) ;
2015-08-06 19:49:19 +02:00
while ( vvTried [ nKBucket ] [ nKBucketPos ] = = - 1 ) {
2017-04-24 14:02:12 +02:00
nKBucket = ( nKBucket + insecure_rand . randbits ( ADDRMAN_TRIED_BUCKET_COUNT_LOG2 ) ) % ADDRMAN_TRIED_BUCKET_COUNT ;
nKBucketPos = ( nKBucketPos + insecure_rand . randbits ( ADDRMAN_BUCKET_SIZE_LOG2 ) ) % ADDRMAN_BUCKET_SIZE ;
2015-08-06 19:49:19 +02:00
}
2015-03-18 17:31:49 +01:00
int nId = vvTried [ nKBucket ] [ nKBucketPos ] ;
assert ( mapInfo . count ( nId ) = = 1 ) ;
CAddrInfo & info = mapInfo [ nId ] ;
2017-06-30 20:29:29 +02:00
if ( RandomInt ( 1 < < 30 ) < fChanceFactor * info . GetChance ( ) * ( 1 < < 30 ) )
2012-01-04 23:39:45 +01:00
return info ;
fChanceFactor * = 1.2 ;
}
} else {
2012-08-18 16:45:24 +02:00
// use a new node
2012-01-04 23:39:45 +01:00
double fChanceFactor = 1.0 ;
2014-09-19 19:21:46 +02:00
while ( 1 ) {
2017-06-30 20:29:29 +02:00
int nUBucket = RandomInt ( ADDRMAN_NEW_BUCKET_COUNT ) ;
int nUBucketPos = RandomInt ( ADDRMAN_BUCKET_SIZE ) ;
2015-08-06 19:49:19 +02:00
while ( vvNew [ nUBucket ] [ nUBucketPos ] = = - 1 ) {
2017-04-24 14:02:12 +02:00
nUBucket = ( nUBucket + insecure_rand . randbits ( ADDRMAN_NEW_BUCKET_COUNT_LOG2 ) ) % ADDRMAN_NEW_BUCKET_COUNT ;
nUBucketPos = ( nUBucketPos + insecure_rand . randbits ( ADDRMAN_BUCKET_SIZE_LOG2 ) ) % ADDRMAN_BUCKET_SIZE ;
2015-08-06 19:49:19 +02:00
}
2015-03-18 17:31:49 +01:00
int nId = vvNew [ nUBucket ] [ nUBucketPos ] ;
assert ( mapInfo . count ( nId ) = = 1 ) ;
CAddrInfo & info = mapInfo [ nId ] ;
2017-06-30 20:29:29 +02:00
if ( RandomInt ( 1 < < 30 ) < fChanceFactor * info . GetChance ( ) * ( 1 < < 30 ) )
2012-01-04 23:39:45 +01:00
return info ;
fChanceFactor * = 1.2 ;
}
}
}
# ifdef DEBUG_ADDRMAN
int CAddrMan : : Check_ ( )
{
std : : set < int > setTried ;
std : : map < int , int > mapNew ;
2018-01-29 14:26:20 +01:00
if ( vRandom . size ( ) ! = ( size_t ) ( nTried + nNew ) )
2014-09-19 19:21:46 +02:00
return - 7 ;
2012-01-04 23:39:45 +01:00
2017-11-30 23:09:44 +01:00
for ( const auto & entry : mapInfo ) {
int n = entry . first ;
const CAddrInfo & info = entry . second ;
2014-09-19 19:21:46 +02:00
if ( info . fInTried ) {
if ( ! info . nLastSuccess )
return - 1 ;
if ( info . nRefCount )
return - 2 ;
2012-01-04 23:39:45 +01:00
setTried . insert ( n ) ;
} else {
2014-09-19 19:21:46 +02:00
if ( info . nRefCount < 0 | | info . nRefCount > ADDRMAN_NEW_BUCKETS_PER_ADDRESS )
return - 3 ;
if ( ! info . nRefCount )
return - 4 ;
2012-01-04 23:39:45 +01:00
mapNew [ n ] = info . nRefCount ;
}
2014-09-19 19:21:46 +02:00
if ( mapAddr [ info ] ! = n )
return - 5 ;
2018-01-29 14:26:20 +01:00
if ( info . nRandomPos < 0 | | ( size_t ) info . nRandomPos > = vRandom . size ( ) | | vRandom [ info . nRandomPos ] ! = n )
2014-09-19 19:21:46 +02:00
return - 14 ;
if ( info . nLastTry < 0 )
return - 6 ;
if ( info . nLastSuccess < 0 )
return - 8 ;
2012-01-04 23:39:45 +01:00
}
2018-01-29 14:26:20 +01:00
if ( setTried . size ( ) ! = ( size_t ) nTried )
2014-09-19 19:21:46 +02:00
return - 9 ;
2018-01-29 14:26:20 +01:00
if ( mapNew . size ( ) ! = ( size_t ) nNew )
2014-09-19 19:21:46 +02:00
return - 10 ;
2012-01-04 23:39:45 +01:00
2015-03-18 17:31:49 +01:00
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 ] ) ;
}
2012-01-04 23:39:45 +01:00
}
}
2015-03-18 17:31:49 +01:00
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 ] ) ;
}
2012-01-04 23:39:45 +01:00
}
}
2014-09-19 19:21:46 +02:00
if ( setTried . size ( ) )
return - 13 ;
if ( mapNew . size ( ) )
return - 15 ;
2015-03-08 14:30:05 +01:00
if ( nKey . IsNull ( ) )
return - 16 ;
2012-01-04 23:39:45 +01:00
return 0 ;
}
# endif
2014-09-19 19:21:46 +02:00
void CAddrMan : : GetAddr_ ( std : : vector < CAddress > & vAddr )
2012-01-04 23:39:45 +01:00
{
2014-08-18 22:50:39 +02:00
unsigned int nNodes = ADDRMAN_GETADDR_MAX_PCT * vRandom . size ( ) / 100 ;
2012-01-04 23:39:45 +01:00
if ( nNodes > ADDRMAN_GETADDR_MAX )
nNodes = ADDRMAN_GETADDR_MAX ;
2014-08-18 22:50:39 +02:00
// gather a list of random nodes, skipping those of low quality
2014-09-19 19:21:46 +02:00
for ( unsigned int n = 0 ; n < vRandom . size ( ) ; n + + ) {
2014-08-18 22:50:39 +02:00
if ( vAddr . size ( ) > = nNodes )
break ;
2017-06-30 20:29:29 +02:00
int nRndPos = RandomInt ( vRandom . size ( ) - n ) + n ;
2012-01-04 23:39:45 +01:00
SwapRandom ( n , nRndPos ) ;
2012-05-05 21:30:38 +02:00
assert ( mapInfo . count ( vRandom [ n ] ) = = 1 ) ;
2014-08-18 22:50:39 +02:00
const CAddrInfo & ai = mapInfo [ vRandom [ n ] ] ;
if ( ! ai . IsTerrible ( ) )
vAddr . push_back ( ai ) ;
2012-01-04 23:39:45 +01:00
}
}
2014-09-19 19:21:46 +02:00
void CAddrMan : : Connected_ ( const CService & addr , int64_t nTime )
2012-01-04 23:39:45 +01:00
{
2014-09-19 19:21:46 +02:00
CAddrInfo * pinfo = Find ( addr ) ;
2012-01-04 23:39:45 +01:00
// if not found, bail out
if ( ! pinfo )
return ;
2014-09-19 19:21:46 +02:00
CAddrInfo & info = * pinfo ;
2012-01-04 23:39:45 +01:00
// check whether we are talking about the exact same CService (including same port)
if ( info ! = addr )
return ;
// update info
2013-04-13 07:13:08 +02:00
int64_t nUpdateInterval = 20 * 60 ;
2012-01-04 23:39:45 +01:00
if ( nTime - info . nTime > nUpdateInterval )
info . nTime = nTime ;
}
2017-06-30 20:29:29 +02:00
2017-07-05 05:45:23 +02:00
void CAddrMan : : SetServices_ ( const CService & addr , ServiceFlags nServices )
{
CAddrInfo * pinfo = Find ( addr ) ;
// if not found, bail out
if ( ! pinfo )
return ;
CAddrInfo & info = * pinfo ;
// check whether we are talking about the exact same CService (including same port)
if ( info ! = addr )
return ;
// update info
info . nServices = nServices ;
}
2019-06-14 13:04:19 +02:00
CAddrInfo CAddrMan : : GetAddressInfo_ ( const CService & addr )
{
CAddrInfo * pinfo = Find ( addr ) ;
// if not found, bail out
if ( ! pinfo )
return CAddrInfo ( ) ;
CAddrInfo & info = * pinfo ;
// check whether we are talking about the exact same CService (including same port)
if ( info ! = addr )
return CAddrInfo ( ) ;
return * pinfo ;
}
2017-06-30 20:29:29 +02:00
int CAddrMan : : RandomInt ( int nMax ) {
return GetRandInt ( nMax ) ;
Merge #9037: net: Add test-before-evict discipline to addrman
e68172ed9 Add test-before-evict discipline to addrman (Ethan Heilman)
Pull request description:
This change implement countermeasures 3 (test-before-evict) suggested in our paper: ["Eclipse Attacks on Bitcoin’s Peer-to-Peer Network"](http://cs-people.bu.edu/heilman/eclipse/).
# Design:
A collision occurs when an address, addr1, is being moved to the tried table from the new table, but maps to a position in the tried table which already contains an address (addr2). The current behavior is that addr1 would evict addr2 from the tried table.
This change ensures that during a collision, addr1 is not inserted into tried but instead inserted into a buffer (setTriedCollisions). The to-be-evicted address, addr2, is then tested by [a feeler connection](https://github.com/bitcoin/bitcoin/pull/8282). If addr2 is found to be online, we remove addr1 from the buffer and addr2 is not evicted, on the other hand if addr2 is found be offline it is replaced by addr1.
An additional small advantage of this change is that, as no more than ten addresses can be in the test buffer at once, and addresses are only cleared one at a time from the test buffer (at 2 minute intervals), thus an attacker is forced to wait at least two minutes to insert a new address into tried after filling up the test buffer. This rate limits an attacker attempting to launch an eclipse attack.
# Risk mitigation:
- To prevent this functionality from being used as a DoS vector, we limit the number of addresses which are to be tested to ten. If we have more than ten addresses to test, we drop new addresses being added to tried if they would evict an address. Since the feeler thread only creates one new connection every 2 minutes the additional network overhead is limited.
- An address in tried gains immunity from tests for 4 hours after it has been tested or successfully connected to.
# Tests:
This change includes additional addrman unittests which test this behavior.
I ran an instance of this change with a much smaller tried table (2 buckets of 64 addresses) so that collisions were much more likely and observed evictions.
```
2016-10-27 07:20:26 Swapping 208.12.64.252:8333 for 68.62.95.247:8333 in tried table
2016-10-27 07:20:26 Moving 208.12.64.252:8333 to tried
```
I documented tests we ran against similar earlier versions of this change in #6355.
# Security Benefit
This is was originally posted in PR #8282 see [this comment for full details](https://github.com/bitcoin/bitcoin/pull/8282#issuecomment-237255215).
To determine the security benefit of these larger numbers of IPs in the tried table I modeled the attack presented in [Eclipse Attacks on Bitcoin’s Peer-to-Peer Network](https://eprint.iacr.org/2015/263).
![attackergraph40000-10-1000short-line](https://cloud.githubusercontent.com/assets/274814/17366828/372af458-595b-11e6-81e5-2c9f97282305.png)
**Default node:** 595 attacker IPs for ~50% attack success.
**Default node + test-before-evict:** 620 attacker IPs for ~50% attack success.
**Feeler node:** 5540 attacker IPs for ~50% attack success.
**Feeler node + test-before-evict:** 8600 attacker IPs for ~50% attack success.
The node running feeler connections has 10 times as many online IP addresses in its tried table making an attack 10 times harder (i.e. requiring the an attacker require 10 times as many IP addresses in different /16s). Adding test-before-evict increases resistance of the node by an additional 3000 attacker IP addresses.
Below I graph the attack over even greater attacker resources (i.e. more attacker controled IP addresses). Note that test-before-evict maintains some security far longer even against an attacker with 50,000 IPs. If this node had a larger tried table test-before-evict could greatly boost a nodes resistance to eclipse attacks.
![attacker graph long view](https://cloud.githubusercontent.com/assets/274814/17367108/96f46d64-595c-11e6-91cd-edba160598e7.png)
Tree-SHA512: fdad4d26aadeaad9bcdc71929b3eb4e1f855b3ee3541fbfbe25dca8d7d0a1667815402db0cb4319db6bd3fcd32d67b5bbc0e12045c4252d62d6239b7d77c4395
2018-03-06 21:36:48 +01:00
}
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 ) {
2018-03-06 22:15:43 +01:00
LogPrint ( BCLog : : ADDRMAN , " Swapping %s for %s in tried table \n " , info_new . ToString ( ) , info_old . ToString ( ) ) ;
Merge #9037: net: Add test-before-evict discipline to addrman
e68172ed9 Add test-before-evict discipline to addrman (Ethan Heilman)
Pull request description:
This change implement countermeasures 3 (test-before-evict) suggested in our paper: ["Eclipse Attacks on Bitcoin’s Peer-to-Peer Network"](http://cs-people.bu.edu/heilman/eclipse/).
# Design:
A collision occurs when an address, addr1, is being moved to the tried table from the new table, but maps to a position in the tried table which already contains an address (addr2). The current behavior is that addr1 would evict addr2 from the tried table.
This change ensures that during a collision, addr1 is not inserted into tried but instead inserted into a buffer (setTriedCollisions). The to-be-evicted address, addr2, is then tested by [a feeler connection](https://github.com/bitcoin/bitcoin/pull/8282). If addr2 is found to be online, we remove addr1 from the buffer and addr2 is not evicted, on the other hand if addr2 is found be offline it is replaced by addr1.
An additional small advantage of this change is that, as no more than ten addresses can be in the test buffer at once, and addresses are only cleared one at a time from the test buffer (at 2 minute intervals), thus an attacker is forced to wait at least two minutes to insert a new address into tried after filling up the test buffer. This rate limits an attacker attempting to launch an eclipse attack.
# Risk mitigation:
- To prevent this functionality from being used as a DoS vector, we limit the number of addresses which are to be tested to ten. If we have more than ten addresses to test, we drop new addresses being added to tried if they would evict an address. Since the feeler thread only creates one new connection every 2 minutes the additional network overhead is limited.
- An address in tried gains immunity from tests for 4 hours after it has been tested or successfully connected to.
# Tests:
This change includes additional addrman unittests which test this behavior.
I ran an instance of this change with a much smaller tried table (2 buckets of 64 addresses) so that collisions were much more likely and observed evictions.
```
2016-10-27 07:20:26 Swapping 208.12.64.252:8333 for 68.62.95.247:8333 in tried table
2016-10-27 07:20:26 Moving 208.12.64.252:8333 to tried
```
I documented tests we ran against similar earlier versions of this change in #6355.
# Security Benefit
This is was originally posted in PR #8282 see [this comment for full details](https://github.com/bitcoin/bitcoin/pull/8282#issuecomment-237255215).
To determine the security benefit of these larger numbers of IPs in the tried table I modeled the attack presented in [Eclipse Attacks on Bitcoin’s Peer-to-Peer Network](https://eprint.iacr.org/2015/263).
![attackergraph40000-10-1000short-line](https://cloud.githubusercontent.com/assets/274814/17366828/372af458-595b-11e6-81e5-2c9f97282305.png)
**Default node:** 595 attacker IPs for ~50% attack success.
**Default node + test-before-evict:** 620 attacker IPs for ~50% attack success.
**Feeler node:** 5540 attacker IPs for ~50% attack success.
**Feeler node + test-before-evict:** 8600 attacker IPs for ~50% attack success.
The node running feeler connections has 10 times as many online IP addresses in its tried table making an attack 10 times harder (i.e. requiring the an attacker require 10 times as many IP addresses in different /16s). Adding test-before-evict increases resistance of the node by an additional 3000 attacker IP addresses.
Below I graph the attack over even greater attacker resources (i.e. more attacker controled IP addresses). Note that test-before-evict maintains some security far longer even against an attacker with 50,000 IPs. If this node had a larger tried table test-before-evict could greatly boost a nodes resistance to eclipse attacks.
![attacker graph long view](https://cloud.githubusercontent.com/assets/274814/17367108/96f46d64-595c-11e6-91cd-edba160598e7.png)
Tree-SHA512: fdad4d26aadeaad9bcdc71929b3eb4e1f855b3ee3541fbfbe25dca8d7d0a1667815402db0cb4319db6bd3fcd32d67b5bbc0e12045c4252d62d6239b7d77c4395
2018-03-06 21:36:48 +01:00
// 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 ] ;
}