2012-01-04 23:39:45 +01:00
|
|
|
// Copyright (c) 2012 Pieter Wuille
|
2023-08-16 19:27:31 +02:00
|
|
|
// Copyright (c) 2012-2020 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>
|
2023-07-14 17:22:44 +02:00
|
|
|
#include <i2p.h>
|
2021-05-11 17:11:07 +02:00
|
|
|
#include <logging.h>
|
2021-05-02 18:44:17 +02:00
|
|
|
#include <netaddress.h>
|
2021-05-18 18:30:48 +02:00
|
|
|
#include <serialize.h>
|
2012-01-04 23:39:45 +01:00
|
|
|
|
2022-12-25 09:25:19 +01:00
|
|
|
#include <cmath>
|
2021-05-02 18:44:17 +02:00
|
|
|
#include <optional>
|
2022-12-25 09:25:19 +01:00
|
|
|
|
2021-05-11 17:11:07 +02:00
|
|
|
int CAddrInfo::GetTriedBucket(const uint256& nKey, const std::vector<bool> &asmap) const
|
2012-01-04 23:39:45 +01:00
|
|
|
{
|
2018-11-30 18:21:03 +01:00
|
|
|
uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetKey()).GetCheapHash();
|
|
|
|
uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup(asmap) << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP)).GetCheapHash();
|
2021-05-11 17:11:07 +02:00
|
|
|
int tried_bucket = hash2 % ADDRMAN_TRIED_BUCKET_COUNT;
|
|
|
|
uint32_t mapped_as = GetMappedAS(asmap);
|
2021-05-18 18:30:48 +02:00
|
|
|
LogPrint(BCLog::NET, "IP %s mapped to AS%i belongs to tried bucket %i\n", ToStringIP(), mapped_as, tried_bucket);
|
2021-05-11 17:11:07 +02:00
|
|
|
return tried_bucket;
|
2012-01-04 23:39:45 +01:00
|
|
|
}
|
|
|
|
|
2021-05-11 17:11:07 +02:00
|
|
|
int CAddrInfo::GetNewBucket(const uint256& nKey, const CNetAddr& src, const std::vector<bool> &asmap) const
|
2012-01-04 23:39:45 +01:00
|
|
|
{
|
2021-05-11 17:11:07 +02:00
|
|
|
std::vector<unsigned char> vchSourceGroupKey = src.GetGroup(asmap);
|
2018-11-30 18:21:03 +01:00
|
|
|
uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup(asmap) << vchSourceGroupKey).GetCheapHash();
|
|
|
|
uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << vchSourceGroupKey << (hash1 % ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP)).GetCheapHash();
|
2021-05-11 17:11:07 +02:00
|
|
|
int new_bucket = hash2 % ADDRMAN_NEW_BUCKET_COUNT;
|
|
|
|
uint32_t mapped_as = GetMappedAS(asmap);
|
2021-05-18 18:30:48 +02:00
|
|
|
LogPrint(BCLog::NET, "IP %s mapped to AS%i belongs to new bucket %i\n", ToStringIP(), mapped_as, new_bucket);
|
2021-05-11 17:11:07 +02:00
|
|
|
return new_bucket;
|
2012-01-04 23:39:45 +01:00
|
|
|
}
|
|
|
|
|
2015-03-18 17:31:49 +01:00
|
|
|
int CAddrInfo::GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const
|
|
|
|
{
|
2018-11-30 18:21:03 +01:00
|
|
|
uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << (fNew ? 'N' : 'K') << nBucket << GetKey()).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;
|
|
|
|
}
|
|
|
|
|
2021-06-04 16:09:25 +02:00
|
|
|
void CAddrMan::RemoveInvalid()
|
|
|
|
{
|
|
|
|
for (size_t bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; ++bucket) {
|
|
|
|
for (size_t i = 0; i < ADDRMAN_BUCKET_SIZE; ++i) {
|
|
|
|
const auto id = vvNew[bucket][i];
|
|
|
|
if (id != -1 && !mapInfo[id].IsValid()) {
|
|
|
|
ClearNew(bucket, i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t bucket = 0; bucket < ADDRMAN_TRIED_BUCKET_COUNT; ++bucket) {
|
|
|
|
for (size_t i = 0; i < ADDRMAN_BUCKET_SIZE; ++i) {
|
|
|
|
const auto id = vvTried[bucket][i];
|
|
|
|
if (id == -1) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const auto& addr_info = mapInfo[id];
|
|
|
|
if (addr_info.IsValid()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
vvTried[bucket][i] = -1;
|
|
|
|
--nTried;
|
|
|
|
SwapRandom(addr_info.nRandomPos, vRandom.size() - 1);
|
|
|
|
vRandom.pop_back();
|
|
|
|
mapAddr.erase(addr_info);
|
|
|
|
mapInfo.erase(id);
|
|
|
|
m_tried_collisions.erase(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
}
|
2021-06-14 16:41:08 +02:00
|
|
|
AssertLockHeld(cs);
|
2018-07-07 23:19:33 +02:00
|
|
|
|
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
|
|
|
{
|
2021-06-14 16:41:08 +02:00
|
|
|
AssertLockHeld(cs);
|
|
|
|
|
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
|
|
|
{
|
2021-06-14 16:41:08 +02:00
|
|
|
AssertLockHeld(cs);
|
|
|
|
|
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
|
|
|
{
|
2021-06-14 16:41:08 +02:00
|
|
|
AssertLockHeld(cs);
|
|
|
|
|
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
|
|
|
{
|
2021-06-14 16:41:08 +02:00
|
|
|
AssertLockHeld(cs);
|
|
|
|
|
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
|
2021-05-11 17:11:07 +02:00
|
|
|
int nKBucket = info.GetTriedBucket(nKey, m_asmap);
|
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
|
2021-05-11 17:11:07 +02:00
|
|
|
int nUBucket = infoOld.GetNewBucket(nKey, m_asmap);
|
2015-03-18 17:31:49 +01:00
|
|
|
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
|
|
|
{
|
2021-06-14 16:41:08 +02:00
|
|
|
AssertLockHeld(cs);
|
|
|
|
|
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
|
2018-12-13 13:43:12 +01:00
|
|
|
int nRnd = insecure_rand.randrange(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
|
2021-05-11 17:11:07 +02:00
|
|
|
int tried_bucket = info.GetTriedBucket(nKey, m_asmap);
|
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
|
|
|
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)) {
|
2019-03-09 07:08:51 +01:00
|
|
|
// Output the entry we'd be colliding with, for debugging purposes
|
|
|
|
auto colliding_entry = mapInfo.find(vvTried[tried_bucket][tried_bucket_pos]);
|
|
|
|
if (fLogIPs) {
|
|
|
|
LogPrint(BCLog::ADDRMAN, "Collision inserting element into tried table (%s), moving %s to m_tried_collisions=%d\n",
|
|
|
|
colliding_entry != mapInfo.end() ? colliding_entry->second.ToString() : "",
|
|
|
|
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
|
|
|
{
|
2021-06-14 16:41:08 +02:00
|
|
|
AssertLockHeld(cs);
|
|
|
|
|
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;
|
2018-12-13 13:43:12 +01:00
|
|
|
if (nFactor > 1 && (insecure_rand.randrange(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;
|
|
|
|
}
|
|
|
|
|
2021-05-11 17:11:07 +02:00
|
|
|
int nUBucket = pinfo->GetNewBucket(nKey, source, m_asmap);
|
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
|
|
|
{
|
2021-06-14 16:41:08 +02:00
|
|
|
AssertLockHeld(cs);
|
|
|
|
|
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
|
|
|
{
|
2021-06-14 16:41:08 +02:00
|
|
|
AssertLockHeld(cs);
|
|
|
|
|
|
|
|
if (vRandom.empty())
|
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 &&
|
2018-12-13 13:43:12 +01:00
|
|
|
(nTried > 0 && (nNew == 0 || insecure_rand.randbool() == 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) {
|
2018-12-13 13:43:12 +01:00
|
|
|
int nKBucket = insecure_rand.randrange(ADDRMAN_TRIED_BUCKET_COUNT);
|
|
|
|
int nKBucketPos = insecure_rand.randrange(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];
|
2018-12-13 13:43:12 +01:00
|
|
|
if (insecure_rand.randbits(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) {
|
2018-12-13 13:43:12 +01:00
|
|
|
int nUBucket = insecure_rand.randrange(ADDRMAN_NEW_BUCKET_COUNT);
|
|
|
|
int nUBucketPos = insecure_rand.randrange(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];
|
2018-12-13 13:43:12 +01:00
|
|
|
if (insecure_rand.randbits(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_()
|
|
|
|
{
|
2021-06-14 16:41:08 +02:00
|
|
|
AssertLockHeld(cs);
|
2012-01-04 23:39:45 +01:00
|
|
|
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;
|
2021-05-11 17:11:07 +02:00
|
|
|
if (mapInfo[vvTried[n][i]].GetTriedBucket(nKey, m_asmap) != n)
|
2015-03-18 17:31:49 +01:00
|
|
|
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
|
|
|
|
|
2021-05-02 18:44:17 +02:00
|
|
|
void CAddrMan::GetAddr_(std::vector<CAddress>& vAddr, size_t max_addresses, size_t max_pct, std::optional<Network> network)
|
2012-01-04 23:39:45 +01:00
|
|
|
{
|
2021-06-14 16:41:08 +02:00
|
|
|
AssertLockHeld(cs);
|
|
|
|
|
2023-06-01 18:59:59 +02:00
|
|
|
size_t nNodes = vRandom.size();
|
|
|
|
if (max_pct != 0) {
|
|
|
|
nNodes = max_pct * nNodes / 100;
|
|
|
|
}
|
|
|
|
if (max_addresses != 0) {
|
|
|
|
nNodes = std::min(nNodes, max_addresses);
|
|
|
|
}
|
2012-01-04 23:39:45 +01:00
|
|
|
|
2014-08-18 22:50:39 +02:00
|
|
|
// gather a list of random nodes, skipping those of low quality
|
2021-05-02 18:44:17 +02:00
|
|
|
const int64_t now{GetAdjustedTime()};
|
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;
|
|
|
|
|
2018-12-13 13:43:12 +01:00
|
|
|
int nRndPos = insecure_rand.randrange(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]];
|
2021-05-02 18:44:17 +02:00
|
|
|
|
|
|
|
// Filter by network (optional)
|
|
|
|
if (network != std::nullopt && ai.GetNetClass() != network) continue;
|
|
|
|
|
|
|
|
// Filter for quality
|
|
|
|
if (ai.IsTerrible(now)) continue;
|
|
|
|
|
|
|
|
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
|
|
|
{
|
2021-06-14 16:41:08 +02:00
|
|
|
AssertLockHeld(cs);
|
|
|
|
|
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)
|
|
|
|
{
|
2021-06-14 16:41:08 +02:00
|
|
|
AssertLockHeld(cs);
|
|
|
|
|
2017-07-05 05:45:23 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
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_()
|
|
|
|
{
|
2021-06-14 16:41:08 +02:00
|
|
|
AssertLockHeld(cs);
|
|
|
|
|
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
|
|
|
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.
|
2021-05-11 17:11:07 +02:00
|
|
|
int tried_bucket = info_new.GetTriedBucket(nKey, m_asmap);
|
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
|
|
|
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) {
|
2019-03-09 07:08:51 +01:00
|
|
|
LogPrint(BCLog::ADDRMAN, "Replacing %s with %s in tried table\n", info_old.ToString(), info_new.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;
|
|
|
|
}
|
2019-03-09 07:08:51 +01:00
|
|
|
} else if (GetAdjustedTime() - info_new.nLastSuccess > ADDRMAN_TEST_WINDOW) {
|
|
|
|
// If the collision hasn't resolved in some reasonable amount of time,
|
|
|
|
// just evict the old entry -- we must not be able to
|
|
|
|
// connect to it for some reason.
|
|
|
|
LogPrint(BCLog::ADDRMAN, "Unable to test; replacing %s with %s in tried table anyway\n", info_old.ToString(), info_new.ToString());
|
|
|
|
Good_(info_new, false, GetAdjustedTime());
|
|
|
|
erase_collision = 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
|
|
|
}
|
|
|
|
} 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_()
|
|
|
|
{
|
2021-06-14 16:41:08 +02:00
|
|
|
AssertLockHeld(cs);
|
|
|
|
|
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() == 0) return CAddrInfo();
|
|
|
|
|
|
|
|
std::set<int>::iterator it = m_tried_collisions.begin();
|
|
|
|
|
|
|
|
// Selects a random element from m_tried_collisions
|
2018-12-13 13:43:12 +01:00
|
|
|
std::advance(it, insecure_rand.randrange(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
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2021-01-07 09:03:35 +01:00
|
|
|
const CAddrInfo& newInfo = mapInfo[id_new];
|
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
|
2021-05-11 17:11:07 +02:00
|
|
|
int tried_bucket = newInfo.GetTriedBucket(nKey, m_asmap);
|
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
|
|
|
int tried_bucket_pos = newInfo.GetBucketPosition(nKey, false, tried_bucket);
|
|
|
|
|
|
|
|
int id_old = vvTried[tried_bucket][tried_bucket_pos];
|
|
|
|
|
|
|
|
return mapInfo[id_old];
|
|
|
|
}
|
2021-05-11 17:11:07 +02:00
|
|
|
|
|
|
|
std::vector<bool> CAddrMan::DecodeAsmap(fs::path path)
|
|
|
|
{
|
|
|
|
std::vector<bool> bits;
|
|
|
|
FILE *filestr = fsbridge::fopen(path, "rb");
|
|
|
|
CAutoFile file(filestr, SER_DISK, CLIENT_VERSION);
|
|
|
|
if (file.IsNull()) {
|
2021-05-18 18:30:48 +02:00
|
|
|
LogPrintf("Failed to open asmap file from disk\n");
|
2021-05-11 17:11:07 +02:00
|
|
|
return bits;
|
|
|
|
}
|
|
|
|
fseek(filestr, 0, SEEK_END);
|
|
|
|
int length = ftell(filestr);
|
2021-05-18 18:30:48 +02:00
|
|
|
LogPrintf("Opened asmap file %s (%d bytes) from disk\n", path, length);
|
2021-05-11 17:11:07 +02:00
|
|
|
fseek(filestr, 0, SEEK_SET);
|
|
|
|
char cur_byte;
|
|
|
|
for (int i = 0; i < length; ++i) {
|
|
|
|
file >> cur_byte;
|
|
|
|
for (int bit = 0; bit < 8; ++bit) {
|
|
|
|
bits.push_back((cur_byte >> bit) & 1);
|
|
|
|
}
|
|
|
|
}
|
2022-03-24 20:28:20 +01:00
|
|
|
if (!SanityCheckASMap(bits)) {
|
|
|
|
LogPrintf("Sanity check of asmap file %s failed\n", path);
|
|
|
|
return {};
|
|
|
|
}
|
2021-05-11 17:11:07 +02:00
|
|
|
return bits;
|
|
|
|
}
|
2023-07-14 17:22:44 +02:00
|
|
|
|
|
|
|
void CAddrMan::ResetI2PPorts()
|
|
|
|
{
|
|
|
|
for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; ++bucket) {
|
|
|
|
for (int i = 0; i < ADDRMAN_BUCKET_SIZE; ++i) {
|
|
|
|
const auto id = vvNew[bucket][i];
|
|
|
|
if (id == -1) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
auto it = mapInfo.find(id);
|
|
|
|
if (it == mapInfo.end()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto& addr_info = it->second;
|
|
|
|
if (!addr_info.IsI2P() || addr_info.GetPort() == I2P_SAM31_PORT) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto addr_info_newport = addr_info;
|
|
|
|
// The below changes addr_info_newport.GetKey(), which is used in finding a
|
|
|
|
// bucket and a position within that bucket. So a re-bucketing may be necessary.
|
|
|
|
addr_info_newport.port = I2P_SAM31_PORT;
|
|
|
|
|
|
|
|
// Reposition entries of vvNew within the same bucket because we don't know the source
|
|
|
|
// address which led to the decision to store the entry in vvNew[bucket] so we can't
|
|
|
|
// re-evaluate that decision, but even if we could, CAddrInfo::GetNewBucket() does not
|
|
|
|
// use CAddrInfo::GetKey() so it would end up in the same bucket as before the port
|
|
|
|
// change.
|
|
|
|
const auto i_target = addr_info_newport.GetBucketPosition(nKey, true, bucket);
|
|
|
|
|
|
|
|
if (i_target == i) { // No need to re-position.
|
|
|
|
addr_info = addr_info_newport;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reposition from i to i_target, removing the entry from i_target (if any).
|
|
|
|
ClearNew(bucket, i_target);
|
|
|
|
vvNew[bucket][i_target] = id;
|
|
|
|
vvNew[bucket][i] = -1;
|
|
|
|
addr_info = addr_info_newport;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int bucket = 0; bucket < ADDRMAN_TRIED_BUCKET_COUNT; ++bucket) {
|
|
|
|
for (int i = 0; i < ADDRMAN_BUCKET_SIZE; ++i) {
|
|
|
|
const auto id = vvTried[bucket][i];
|
|
|
|
if (id == -1) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
auto it = mapInfo.find(id);
|
|
|
|
if (it == mapInfo.end()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto& addr_info = it->second;
|
|
|
|
if (!addr_info.IsI2P() || addr_info.GetPort() == I2P_SAM31_PORT) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto addr_info_newport = addr_info;
|
|
|
|
// The below changes addr_info_newport.GetKey(), which is used in finding a
|
|
|
|
// bucket and a position within that bucket. So a re-bucketing may be necessary.
|
|
|
|
addr_info_newport.port = I2P_SAM31_PORT;
|
|
|
|
|
|
|
|
const auto bucket_target = addr_info_newport.GetTriedBucket(nKey, m_asmap);
|
|
|
|
const auto i_target = addr_info_newport.GetBucketPosition(nKey, false, bucket_target);
|
|
|
|
|
|
|
|
if (bucket_target == bucket && i_target == i) { // No need to re-position.
|
|
|
|
addr_info = addr_info_newport;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reposition from (bucket, i) to (bucket_target, i_target). If the latter is
|
|
|
|
// occupied, then move the entry from there to vvNew.
|
|
|
|
|
|
|
|
const auto old_target_id = vvTried[bucket_target][i_target];
|
|
|
|
if (old_target_id != -1) {
|
|
|
|
CAddrInfo& old_target_info = mapInfo[old_target_id];
|
|
|
|
|
|
|
|
old_target_info.fInTried = false;
|
|
|
|
vvTried[bucket_target][i_target] = -1;
|
|
|
|
--nTried;
|
|
|
|
|
|
|
|
const auto new_bucket = old_target_info.GetNewBucket(nKey, m_asmap);
|
|
|
|
const auto new_bucket_i = old_target_info.GetBucketPosition(nKey, true, new_bucket);
|
|
|
|
ClearNew(new_bucket, new_bucket_i);
|
|
|
|
|
|
|
|
old_target_info.nRefCount = 1;
|
|
|
|
vvNew[new_bucket][new_bucket_i] = old_target_id;
|
|
|
|
++nNew;
|
|
|
|
}
|
|
|
|
|
|
|
|
vvTried[bucket_target][i_target] = id;
|
|
|
|
vvTried[bucket][i] = -1;
|
|
|
|
addr_info = addr_info_newport;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|