aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Makefile.am2
-rw-r--r--src/Makefile.test.include12
-rw-r--r--src/addrman.cpp58
-rw-r--r--src/addrman.h101
-rw-r--r--src/init.cpp26
-rw-r--r--src/net.cpp11
-rw-r--r--src/net.h6
-rw-r--r--src/netaddress.cpp59
-rw-r--r--src/netaddress.h9
-rw-r--r--src/rpc/net.cpp4
-rw-r--r--src/test/addrman_tests.cpp301
-rw-r--r--src/test/data/asmap.rawbin0 -> 59 bytes
-rw-r--r--src/test/netbase_tests.cpp28
-rw-r--r--src/util/asmap.cpp97
-rw-r--r--src/util/asmap.h10
15 files changed, 631 insertions, 93 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 821553579a..e58a89ca03 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -210,6 +210,7 @@ BITCOIN_CORE_H = \
txmempool.h \
ui_interface.h \
undo.h \
+ util/asmap.h \
util/bip32.h \
util/bytevectorhash.h \
util/check.h \
@@ -510,6 +511,7 @@ libbitcoin_util_a_SOURCES = \
support/cleanse.cpp \
sync.cpp \
threadinterrupt.cpp \
+ util/asmap.cpp \
util/bip32.cpp \
util/bytevectorhash.cpp \
util/error.cpp \
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index 0975551995..ed81622717 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -82,7 +82,8 @@ JSON_TEST_FILES = \
test/data/tx_invalid.json \
test/data/tx_valid.json
-RAW_TEST_FILES =
+RAW_TEST_FILES = \
+ test/data/asmap.raw
GENERATED_TEST_FILES = $(JSON_TEST_FILES:.json=.json.h) $(RAW_TEST_FILES:.raw=.raw.h)
@@ -635,3 +636,12 @@ endif
echo "};};"; \
} > "$@.new" && mv -f "$@.new" "$@"
@echo "Generated $@"
+
+%.raw.h: %.raw
+ @$(MKDIR_P) $(@D)
+ @{ \
+ echo "static unsigned const char $(*F)_raw[] = {" && \
+ $(HEXDUMP) -v -e '8/1 "0x%02x, "' -e '"\n"' $< | $(SED) -e 's/0x ,//g' && \
+ echo "};"; \
+ } > "$@.new" && mv -f "$@.new" "$@"
+ @echo "Generated $@"
diff --git a/src/addrman.cpp b/src/addrman.cpp
index 065fdbe4c6..121ae4bf7e 100644
--- a/src/addrman.cpp
+++ b/src/addrman.cpp
@@ -7,20 +7,27 @@
#include <hash.h>
#include <serialize.h>
+#include <logging.h>
-int CAddrInfo::GetTriedBucket(const uint256& nKey) const
+int CAddrInfo::GetTriedBucket(const uint256& nKey, const std::vector<bool> &asmap) const
{
uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetKey()).GetCheapHash();
- uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup() << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP)).GetCheapHash();
- return hash2 % ADDRMAN_TRIED_BUCKET_COUNT;
+ uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup(asmap) << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP)).GetCheapHash();
+ int tried_bucket = hash2 % ADDRMAN_TRIED_BUCKET_COUNT;
+ uint32_t mapped_as = GetMappedAS(asmap);
+ LogPrint(BCLog::NET, "IP %s mapped to AS%i belongs to tried bucket %i.\n", ToStringIP(), mapped_as, tried_bucket);
+ return tried_bucket;
}
-int CAddrInfo::GetNewBucket(const uint256& nKey, const CNetAddr& src) const
+int CAddrInfo::GetNewBucket(const uint256& nKey, const CNetAddr& src, const std::vector<bool> &asmap) const
{
- std::vector<unsigned char> vchSourceGroupKey = src.GetGroup();
- uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup() << vchSourceGroupKey).GetCheapHash();
+ std::vector<unsigned char> vchSourceGroupKey = src.GetGroup(asmap);
+ 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();
- return hash2 % ADDRMAN_NEW_BUCKET_COUNT;
+ int new_bucket = hash2 % ADDRMAN_NEW_BUCKET_COUNT;
+ uint32_t mapped_as = GetMappedAS(asmap);
+ LogPrint(BCLog::NET, "IP %s mapped to AS%i belongs to new bucket %i.\n", ToStringIP(), mapped_as, new_bucket);
+ return new_bucket;
}
int CAddrInfo::GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const
@@ -153,7 +160,7 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId)
assert(info.nRefCount == 0);
// which tried bucket to move the entry to
- int nKBucket = info.GetTriedBucket(nKey);
+ int nKBucket = info.GetTriedBucket(nKey, m_asmap);
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).
@@ -169,7 +176,7 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId)
nTried--;
// find which new bucket it belongs to
- int nUBucket = infoOld.GetNewBucket(nKey);
+ int nUBucket = infoOld.GetNewBucket(nKey, m_asmap);
int nUBucketPos = infoOld.GetBucketPosition(nKey, true, nUBucket);
ClearNew(nUBucket, nUBucketPos);
assert(vvNew[nUBucket][nUBucketPos] == -1);
@@ -233,7 +240,7 @@ void CAddrMan::Good_(const CService& addr, bool test_before_evict, int64_t nTime
return;
// which tried bucket to move the entry to
- int tried_bucket = info.GetTriedBucket(nKey);
+ int tried_bucket = info.GetTriedBucket(nKey, m_asmap);
int tried_bucket_pos = info.GetBucketPosition(nKey, false, tried_bucket);
// Will moving this address into tried evict another entry?
@@ -301,7 +308,7 @@ bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimeP
fNew = true;
}
- int nUBucket = pinfo->GetNewBucket(nKey, source);
+ int nUBucket = pinfo->GetNewBucket(nKey, source, m_asmap);
int nUBucketPos = pinfo->GetBucketPosition(nKey, true, nUBucket);
if (vvNew[nUBucket][nUBucketPos] != nId) {
bool fInsert = vvNew[nUBucket][nUBucketPos] == -1;
@@ -439,7 +446,7 @@ int CAddrMan::Check_()
if (vvTried[n][i] != -1) {
if (!setTried.count(vvTried[n][i]))
return -11;
- if (mapInfo[vvTried[n][i]].GetTriedBucket(nKey) != n)
+ if (mapInfo[vvTried[n][i]].GetTriedBucket(nKey, m_asmap) != n)
return -17;
if (mapInfo[vvTried[n][i]].GetBucketPosition(nKey, false, n) != i)
return -18;
@@ -545,7 +552,7 @@ void CAddrMan::ResolveCollisions_()
CAddrInfo& info_new = mapInfo[id_new];
// Which tried bucket to move the entry to.
- int tried_bucket = info_new.GetTriedBucket(nKey);
+ int tried_bucket = info_new.GetTriedBucket(nKey, m_asmap);
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;
@@ -609,10 +616,33 @@ CAddrInfo CAddrMan::SelectTriedCollision_()
CAddrInfo& newInfo = mapInfo[id_new];
// which tried bucket to move the entry to
- int tried_bucket = newInfo.GetTriedBucket(nKey);
+ int tried_bucket = newInfo.GetTriedBucket(nKey, m_asmap);
int tried_bucket_pos = newInfo.GetBucketPosition(nKey, false, tried_bucket);
int id_old = vvTried[tried_bucket][tried_bucket_pos];
return mapInfo[id_old];
}
+
+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()) {
+ LogPrintf("Failed to open asmap file from disk.\n");
+ return bits;
+ }
+ fseek(filestr, 0, SEEK_END);
+ int length = ftell(filestr);
+ LogPrintf("Opened asmap file %s (%d bytes) from disk.\n", path, length);
+ 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);
+ }
+ }
+ return bits;
+}
diff --git a/src/addrman.h b/src/addrman.h
index 8685ea5049..5901611bee 100644
--- a/src/addrman.h
+++ b/src/addrman.h
@@ -12,11 +12,17 @@
#include <sync.h>
#include <timedata.h>
#include <util/system.h>
+#include <clientversion.h>
#include <map>
#include <set>
#include <stdint.h>
#include <vector>
+#include <iostream>
+#include <streams.h>
+#include <fs.h>
+#include <hash.h>
+
/**
* Extended statistics about a CAddress
@@ -68,15 +74,15 @@ public:
}
//! Calculate in which "tried" bucket this entry belongs
- int GetTriedBucket(const uint256 &nKey) const;
+ int GetTriedBucket(const uint256 &nKey, const std::vector<bool> &asmap) const;
//! Calculate in which "new" bucket this entry belongs, given a certain source
- int GetNewBucket(const uint256 &nKey, const CNetAddr& src) const;
+ int GetNewBucket(const uint256 &nKey, const CNetAddr& src, const std::vector<bool> &asmap) const;
//! Calculate in which "new" bucket this entry belongs, using its default source
- int GetNewBucket(const uint256 &nKey) const
+ int GetNewBucket(const uint256 &nKey, const std::vector<bool> &asmap) const
{
- return GetNewBucket(nKey, source);
+ return GetNewBucket(nKey, source, asmap);
}
//! Calculate in which position of a bucket to store this entry.
@@ -170,6 +176,7 @@ static const int64_t ADDRMAN_TEST_WINDOW = 40*60; // 40 minutes
*/
class CAddrMan
{
+friend class CAddrManTest;
protected:
//! critical section to protect the inner data structures
mutable RecursiveMutex cs;
@@ -264,9 +271,29 @@ protected:
void SetServices_(const CService &addr, ServiceFlags nServices) EXCLUSIVE_LOCKS_REQUIRED(cs);
public:
+ // Compressed IP->ASN mapping, loaded from a file when a node starts.
+ // Should be always empty if no file was provided.
+ // This mapping is then used for bucketing nodes in Addrman.
+ //
+ // If asmap is provided, nodes will be bucketed by
+ // AS they belong to, in order to make impossible for a node
+ // to connect to several nodes hosted in a single AS.
+ // This is done in response to Erebus attack, but also to generally
+ // diversify the connections every node creates,
+ // especially useful when a large fraction of nodes
+ // operate under a couple of cloud providers.
+ //
+ // If a new asmap was provided, the existing records
+ // would be re-bucketed accordingly.
+ std::vector<bool> m_asmap;
+
+ // Read asmap from provided binary file
+ static std::vector<bool> DecodeAsmap(fs::path path);
+
+
/**
* serialized format:
- * * version byte (currently 1)
+ * * version byte (1 for pre-asmap files, 2 for files including asmap version)
* * 0x20 + nKey (serialized as if it were a vector, for backward compatibility)
* * nNew
* * nTried
@@ -298,7 +325,7 @@ public:
{
LOCK(cs);
- unsigned char nVersion = 1;
+ unsigned char nVersion = 2;
s << nVersion;
s << ((unsigned char)32);
s << nKey;
@@ -341,6 +368,13 @@ public:
}
}
}
+ // Store asmap version after bucket entries so that it
+ // can be ignored by older clients for backward compatibility.
+ uint256 asmap_version;
+ if (m_asmap.size() != 0) {
+ asmap_version = SerializeHash(m_asmap);
+ }
+ s << asmap_version;
}
template<typename Stream>
@@ -349,7 +383,6 @@ public:
LOCK(cs);
Clear();
-
unsigned char nVersion;
s >> nVersion;
unsigned char nKeySize;
@@ -379,16 +412,6 @@ public:
mapAddr[info] = n;
info.nRandomPos = vRandom.size();
vRandom.push_back(n);
- if (nVersion != 1 || nUBuckets != ADDRMAN_NEW_BUCKET_COUNT) {
- // In case the new table data cannot be used (nVersion unknown, or bucket count wrong),
- // immediately try to give them a reference based on their primary source address.
- int nUBucket = info.GetNewBucket(nKey);
- int nUBucketPos = info.GetBucketPosition(nKey, true, nUBucket);
- if (vvNew[nUBucket][nUBucketPos] == -1) {
- vvNew[nUBucket][nUBucketPos] = n;
- info.nRefCount++;
- }
- }
}
nIdCount = nNew;
@@ -397,7 +420,7 @@ public:
for (int n = 0; n < nTried; n++) {
CAddrInfo info;
s >> info;
- int nKBucket = info.GetTriedBucket(nKey);
+ int nKBucket = info.GetTriedBucket(nKey, m_asmap);
int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket);
if (vvTried[nKBucket][nKBucketPos] == -1) {
info.nRandomPos = vRandom.size();
@@ -413,7 +436,9 @@ public:
}
nTried -= nLost;
- // Deserialize positions in the new table (if possible).
+ // Store positions in the new table buckets to apply later (if possible).
+ std::map<int, int> entryToBucket; // Represents which entry belonged to which bucket when serializing
+
for (int bucket = 0; bucket < nUBuckets; bucket++) {
int nSize = 0;
s >> nSize;
@@ -421,12 +446,38 @@ public:
int nIndex = 0;
s >> nIndex;
if (nIndex >= 0 && nIndex < nNew) {
- CAddrInfo &info = mapInfo[nIndex];
- int nUBucketPos = info.GetBucketPosition(nKey, true, bucket);
- if (nVersion == 1 && nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && vvNew[bucket][nUBucketPos] == -1 && info.nRefCount < ADDRMAN_NEW_BUCKETS_PER_ADDRESS) {
- info.nRefCount++;
- vvNew[bucket][nUBucketPos] = nIndex;
- }
+ entryToBucket[nIndex] = bucket;
+ }
+ }
+ }
+
+ uint256 supplied_asmap_version;
+ if (m_asmap.size() != 0) {
+ supplied_asmap_version = SerializeHash(m_asmap);
+ }
+ uint256 serialized_asmap_version;
+ if (nVersion > 1) {
+ s >> serialized_asmap_version;
+ }
+
+ for (int n = 0; n < nNew; n++) {
+ CAddrInfo &info = mapInfo[n];
+ int bucket = entryToBucket[n];
+ int nUBucketPos = info.GetBucketPosition(nKey, true, bucket);
+ if (nVersion == 2 && nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && vvNew[bucket][nUBucketPos] == -1 &&
+ info.nRefCount < ADDRMAN_NEW_BUCKETS_PER_ADDRESS && serialized_asmap_version == supplied_asmap_version) {
+ // Bucketing has not changed, using existing bucket positions for the new table
+ vvNew[bucket][nUBucketPos] = n;
+ info.nRefCount++;
+ } else {
+ // In case the new table data cannot be used (nVersion unknown, bucket count wrong or new asmap),
+ // try to give them a reference based on their primary source address.
+ LogPrint(BCLog::ADDRMAN, "Bucketing method was updated, re-bucketing addrman entries from disk\n");
+ bucket = info.GetNewBucket(nKey, m_asmap);
+ nUBucketPos = info.GetBucketPosition(nKey, true, bucket);
+ if (vvNew[bucket][nUBucketPos] == -1) {
+ vvNew[bucket][nUBucketPos] = n;
+ info.nRefCount++;
}
}
}
diff --git a/src/init.cpp b/src/init.cpp
index 1bc1d767ca..49f4727169 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -52,7 +52,11 @@
#include <util/threadnames.h>
#include <util/translation.h>
#include <util/validation.h>
+#include <util/asmap.h>
#include <validation.h>
+#include <hash.h>
+
+
#include <validationinterface.h>
#include <walletinitinterface.h>
@@ -98,6 +102,8 @@ static constexpr int DUMP_BANS_INTERVAL = 60 * 15;
static const char* FEE_ESTIMATES_FILENAME="fee_estimates.dat";
+static const char* DEFAULT_ASMAP_FILENAME="ip_asn.map";
+
/**
* The PID file facilities.
*/
@@ -431,6 +437,7 @@ void SetupServerArgs()
gArgs.AddArg("-peertimeout=<n>", strprintf("Specify p2p connection timeout in seconds. This option determines the amount of time a peer may be inactive before the connection to it is dropped. (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION);
gArgs.AddArg("-torcontrol=<ip>:<port>", strprintf("Tor control port to use if onion listening enabled (default: %s)", DEFAULT_TOR_CONTROL), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-torpassword=<pass>", "Tor control port password (default: empty)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
+ gArgs.AddArg("-asmap=<file>", "Specify asn mapping used for bucketing of the peers. Path should be relative to the -datadir path.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
#ifdef USE_UPNP
#if USE_UPNP
gArgs.AddArg("-upnp", "Use UPnP to map the listening port (default: 1 when listening and no -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@@ -1813,6 +1820,25 @@ bool AppInitMain(NodeContext& node)
return false;
}
+ // Read asmap file if configured
+ if (gArgs.IsArgSet("-asmap")) {
+ std::string asmap_file = gArgs.GetArg("-asmap", "");
+ if (asmap_file.empty()) {
+ asmap_file = DEFAULT_ASMAP_FILENAME;
+ }
+ const fs::path asmap_path = GetDataDir() / asmap_file;
+ std::vector<bool> asmap = CAddrMan::DecodeAsmap(asmap_path);
+ if (asmap.size() == 0) {
+ InitError(strprintf(_("Could not find or parse specified asmap: '%s'").translated, asmap_path));
+ return false;
+ }
+ node.connman->SetAsmap(asmap);
+ const uint256 asmap_version = SerializeHash(asmap);
+ LogPrintf("Using asmap version %s for IP bucketing.\n", asmap_version.ToString());
+ } else {
+ LogPrintf("Using /16 prefix for IP bucketing.\n");
+ }
+
// ********************************************************* Step 13: finished
SetRPCWarmupFinished();
diff --git a/src/net.cpp b/src/net.cpp
index 68764bf5cb..9cd2d30d9d 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -498,12 +498,13 @@ void CNode::SetAddrLocal(const CService& addrLocalIn) {
#undef X
#define X(name) stats.name = name
-void CNode::copyStats(CNodeStats &stats)
+void CNode::copyStats(CNodeStats &stats, std::vector<bool> &m_asmap)
{
stats.nodeid = this->GetId();
X(nServices);
X(addr);
X(addrBind);
+ stats.m_mapped_as = addr.GetMappedAS(m_asmap);
if (m_tx_relay != nullptr) {
LOCK(m_tx_relay->cs_filter);
stats.fRelayTxes = m_tx_relay->fRelayTxes;
@@ -1769,7 +1770,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
// but inbound and addnode peers do not use our outbound slots. Inbound peers
// also have the added issue that they're attacker controlled and could be used
// to prevent us from connecting to particular hosts if we used them here.
- setConnected.insert(pnode->addr.GetGroup());
+ setConnected.insert(pnode->addr.GetGroup(addrman.m_asmap));
if (pnode->m_tx_relay == nullptr) {
nOutboundBlockRelay++;
} else if (!pnode->fFeeler) {
@@ -1817,7 +1818,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
}
// Require outbound connections, other than feelers, to be to distinct network groups
- if (!fFeeler && setConnected.count(addr.GetGroup())) {
+ if (!fFeeler && setConnected.count(addr.GetGroup(addrman.m_asmap))) {
break;
}
@@ -2502,7 +2503,7 @@ void CConnman::GetNodeStats(std::vector<CNodeStats>& vstats)
vstats.reserve(vNodes.size());
for (CNode* pnode : vNodes) {
vstats.emplace_back();
- pnode->copyStats(vstats.back());
+ pnode->copyStats(vstats.back(), addrman.m_asmap);
}
}
@@ -2789,7 +2790,7 @@ CSipHasher CConnman::GetDeterministicRandomizer(uint64_t id) const
uint64_t CConnman::CalculateKeyedNetGroup(const CAddress& ad) const
{
- std::vector<unsigned char> vchNetGroup(ad.GetGroup());
+ std::vector<unsigned char> vchNetGroup(ad.GetGroup(addrman.m_asmap));
return GetDeterministicRandomizer(RANDOMIZER_ID_NETGROUP).Write(vchNetGroup.data(), vchNetGroup.size()).Finalize();
}
diff --git a/src/net.h b/src/net.h
index 2e7f29a8d7..f23cae323e 100644
--- a/src/net.h
+++ b/src/net.h
@@ -153,6 +153,7 @@ public:
bool m_use_addrman_outgoing = true;
std::vector<std::string> m_specified_outgoing;
std::vector<std::string> m_added_nodes;
+ std::vector<bool> m_asmap;
};
void Init(const Options& connOptions) {
@@ -330,6 +331,8 @@ public:
*/
int64_t PoissonNextSendInbound(int64_t now, int average_interval_seconds);
+ void SetAsmap(std::vector<bool> asmap) { addrman.m_asmap = asmap; }
+
private:
struct ListenSocket {
public:
@@ -603,6 +606,7 @@ public:
CAddress addr;
// Bind address of our side of the connection
CAddress addrBind;
+ uint32_t m_mapped_as;
};
@@ -979,7 +983,7 @@ public:
void CloseSocketDisconnect();
- void copyStats(CNodeStats &stats);
+ void copyStats(CNodeStats &stats, std::vector<bool> &m_asmap);
ServiceFlags GetLocalServices() const
{
diff --git a/src/netaddress.cpp b/src/netaddress.cpp
index 2ee4ae3ee3..ce3e17197e 100644
--- a/src/netaddress.cpp
+++ b/src/netaddress.cpp
@@ -6,6 +6,7 @@
#include <netaddress.h>
#include <hash.h>
#include <util/strencodings.h>
+#include <util/asmap.h>
#include <tinyformat.h>
static const unsigned char pchIPv4[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff };
@@ -400,6 +401,39 @@ bool CNetAddr::GetIn6Addr(struct in6_addr* pipv6Addr) const
return true;
}
+uint32_t CNetAddr::GetNetClass() const {
+ uint32_t net_class = NET_IPV6;
+ if (IsLocal()) {
+ net_class = 255;
+ }
+ if (IsInternal()) {
+ net_class = NET_INTERNAL;
+ } else if (!IsRoutable()) {
+ net_class = NET_UNROUTABLE;
+ } else if (IsIPv4() || IsRFC6145() || IsRFC6052() || IsRFC3964() || IsRFC4380()) {
+ net_class = NET_IPV4;
+ } else if (IsTor()) {
+ net_class = NET_ONION;
+ }
+ return net_class;
+}
+
+uint32_t CNetAddr::GetMappedAS(const std::vector<bool> &asmap) const {
+ uint32_t net_class = GetNetClass();
+ if (asmap.size() == 0 || (net_class != NET_IPV4 && net_class != NET_IPV6)) {
+ return 0; // Indicates not found, safe because AS0 is reserved per RFC7607.
+ }
+ std::vector<bool> ip_bits(128);
+ for (int8_t byte_i = 0; byte_i < 16; ++byte_i) {
+ uint8_t cur_byte = GetByte(15 - byte_i);
+ for (uint8_t bit_i = 0; bit_i < 8; ++bit_i) {
+ ip_bits[byte_i * 8 + bit_i] = (cur_byte >> (7 - bit_i)) & 1;
+ }
+ }
+ uint32_t mapped_as = Interpret(asmap, ip_bits);
+ return mapped_as;
+}
+
/**
* Get the canonical identifier of our network group
*
@@ -410,56 +444,61 @@ bool CNetAddr::GetIn6Addr(struct in6_addr* pipv6Addr) const
* @note No two connections will be attempted to addresses with the same network
* group.
*/
-std::vector<unsigned char> CNetAddr::GetGroup() const
+std::vector<unsigned char> CNetAddr::GetGroup(const std::vector<bool> &asmap) const
{
std::vector<unsigned char> vchRet;
- int nClass = NET_IPV6;
+ uint32_t net_class = GetNetClass();
+ // If non-empty asmap is supplied and the address is IPv4/IPv6,
+ // return ASN to be used for bucketing.
+ uint32_t asn = GetMappedAS(asmap);
+ if (asn != 0) { // Either asmap was empty, or address has non-asmappable net class (e.g. TOR).
+ vchRet.push_back(NET_IPV6); // IPv4 and IPv6 with same ASN should be in the same bucket
+ for (int i = 0; i < 4; i++) {
+ vchRet.push_back((asn >> (8 * i)) & 0xFF);
+ }
+ return vchRet;
+ }
+
+ vchRet.push_back(net_class);
int nStartByte = 0;
int nBits = 16;
// all local addresses belong to the same group
if (IsLocal())
{
- nClass = 255;
nBits = 0;
}
// all internal-usage addresses get their own group
if (IsInternal())
{
- nClass = NET_INTERNAL;
nStartByte = sizeof(g_internal_prefix);
nBits = (sizeof(ip) - sizeof(g_internal_prefix)) * 8;
}
// all other unroutable addresses belong to the same group
else if (!IsRoutable())
{
- nClass = NET_UNROUTABLE;
nBits = 0;
}
// for IPv4 addresses, '1' + the 16 higher-order bits of the IP
// includes mapped IPv4, SIIT translated IPv4, and the well-known prefix
else if (IsIPv4() || IsRFC6145() || IsRFC6052())
{
- nClass = NET_IPV4;
nStartByte = 12;
}
// for 6to4 tunnelled addresses, use the encapsulated IPv4 address
else if (IsRFC3964())
{
- nClass = NET_IPV4;
nStartByte = 2;
}
// for Teredo-tunnelled IPv6 addresses, use the encapsulated IPv4 address
else if (IsRFC4380())
{
- vchRet.push_back(NET_IPV4);
vchRet.push_back(GetByte(3) ^ 0xFF);
vchRet.push_back(GetByte(2) ^ 0xFF);
return vchRet;
}
else if (IsTor())
{
- nClass = NET_ONION;
nStartByte = 6;
nBits = 4;
}
@@ -470,8 +509,6 @@ std::vector<unsigned char> CNetAddr::GetGroup() const
else
nBits = 32;
- vchRet.push_back(nClass);
-
// push our ip onto vchRet byte by byte...
while (nBits >= 8)
{
diff --git a/src/netaddress.h b/src/netaddress.h
index dcb492da0d..078234595c 100644
--- a/src/netaddress.h
+++ b/src/netaddress.h
@@ -78,7 +78,14 @@ class CNetAddr
unsigned int GetByte(int n) const;
uint64_t GetHash() const;
bool GetInAddr(struct in_addr* pipv4Addr) const;
- std::vector<unsigned char> GetGroup() const;
+ uint32_t GetNetClass() const;
+
+ // The AS on the BGP path to the node we use to diversify
+ // peers in AddrMan bucketing based on the AS infrastructure.
+ // The ip->AS mapping depends on how asmap is constructed.
+ uint32_t GetMappedAS(const std::vector<bool> &asmap) const;
+
+ std::vector<unsigned char> GetGroup(const std::vector<bool> &asmap) const;
int GetReachabilityFrom(const CNetAddr *paddrPartner = nullptr) const;
explicit CNetAddr(const struct in6_addr& pipv6Addr, const uint32_t scope = 0);
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index e0c1976f1a..1ce49709b2 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -83,6 +83,7 @@ static UniValue getpeerinfo(const JSONRPCRequest& request)
" \"addr\":\"host:port\", (string) The IP address and port of the peer\n"
" \"addrbind\":\"ip:port\", (string) Bind address of the connection to the peer\n"
" \"addrlocal\":\"ip:port\", (string) Local address as reported by the peer\n"
+ " \"mapped_as\":\"mapped_as\", (string) The AS in the BGP route to the peer used for diversifying peer selection\n"
" \"services\":\"xxxxxxxxxxxxxxxx\", (string) The services offered\n"
" \"servicesnames\":[ (array) the services offered, in human-readable form\n"
" \"SERVICE_NAME\", (string) the service name if it is recognised\n"
@@ -152,6 +153,9 @@ static UniValue getpeerinfo(const JSONRPCRequest& request)
obj.pushKV("addrlocal", stats.addrLocal);
if (stats.addrBind.IsValid())
obj.pushKV("addrbind", stats.addrBind.ToString());
+ if (stats.m_mapped_as != 0) {
+ obj.pushKV("mapped_as", uint64_t(stats.m_mapped_as));
+ }
obj.pushKV("services", strprintf("%016x", stats.nServices));
obj.pushKV("servicesnames", GetServicesNames(stats.nServices));
obj.pushKV("relaytxes", stats.fRelayTxes);
diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp
index f44ed712d9..07cebeb35a 100644
--- a/src/test/addrman_tests.cpp
+++ b/src/test/addrman_tests.cpp
@@ -5,6 +5,8 @@
#include <test/util/setup_common.h>
#include <string>
#include <boost/test/unit_test.hpp>
+#include <util/asmap.h>
+#include <test/data/asmap.raw.h>
#include <hash.h>
#include <netbase.h>
@@ -12,13 +14,18 @@
class CAddrManTest : public CAddrMan
{
+private:
+ bool deterministic;
public:
- explicit CAddrManTest(bool makeDeterministic = true)
+ explicit CAddrManTest(bool makeDeterministic = true,
+ std::vector<bool> asmap = std::vector<bool>())
{
if (makeDeterministic) {
// Set addrman addr placement to be deterministic.
MakeDeterministic();
}
+ deterministic = makeDeterministic;
+ m_asmap = asmap;
}
//! Ensure that bucket placement is always the same for testing purposes.
@@ -46,6 +53,21 @@ public:
CAddrMan::Delete(nId);
}
+ // Used to test deserialization
+ std::pair<int, int> GetBucketAndEntry(const CAddress& addr)
+ {
+ LOCK(cs);
+ int nId = mapAddr[addr];
+ for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; ++bucket) {
+ for (int entry = 0; entry < ADDRMAN_BUCKET_SIZE; ++entry) {
+ if (nId == vvNew[bucket][entry]) {
+ return std::pair<int, int>(bucket, entry);
+ }
+ }
+ }
+ return std::pair<int, int>(-1, -1);
+ }
+
// Simulates connection failure so that we can test eviction of offline nodes
void SimConnFail(CService& addr)
{
@@ -57,6 +79,16 @@ public:
int64_t nLastTry = GetAdjustedTime()-61;
Attempt(addr, count_failure, nLastTry);
}
+
+ void Clear()
+ {
+ CAddrMan::Clear();
+ if (deterministic) {
+ nKey.SetNull();
+ insecure_rand = FastRandomContext(true);
+ }
+ }
+
};
static CNetAddr ResolveIP(const std::string& ip)
@@ -74,6 +106,18 @@ static CService ResolveService(const std::string& ip, const int port = 0)
}
+static std::vector<bool> FromBytes(const unsigned char* source, int vector_size) {
+ std::vector<bool> result(vector_size);
+ for (int byte_i = 0; byte_i < vector_size / 8; ++byte_i) {
+ unsigned char cur_byte = source[byte_i];
+ for (int bit_i = 0; bit_i < 8; ++bit_i) {
+ result[byte_i * 8 + bit_i] = (cur_byte >> bit_i) & 1;
+ }
+ }
+ return result;
+}
+
+
BOOST_FIXTURE_TEST_SUITE(addrman_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(addrman_simple)
@@ -400,7 +444,7 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr)
}
-BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket)
+BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket_legacy)
{
CAddrManTest addrman;
@@ -415,30 +459,31 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket)
uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash();
uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash();
+ std::vector<bool> asmap; // use /16
- BOOST_CHECK_EQUAL(info1.GetTriedBucket(nKey1), 40);
+ BOOST_CHECK_EQUAL(info1.GetTriedBucket(nKey1, asmap), 40);
// Test: Make sure key actually randomizes bucket placement. A fail on
// this test could be a security issue.
- BOOST_CHECK(info1.GetTriedBucket(nKey1) != info1.GetTriedBucket(nKey2));
+ BOOST_CHECK(info1.GetTriedBucket(nKey1, asmap) != info1.GetTriedBucket(nKey2, asmap));
// Test: Two addresses with same IP but different ports can map to
// different buckets because they have different keys.
CAddrInfo info2 = CAddrInfo(addr2, source1);
BOOST_CHECK(info1.GetKey() != info2.GetKey());
- BOOST_CHECK(info1.GetTriedBucket(nKey1) != info2.GetTriedBucket(nKey1));
+ BOOST_CHECK(info1.GetTriedBucket(nKey1, asmap) != info2.GetTriedBucket(nKey1, asmap));
std::set<int> buckets;
for (int i = 0; i < 255; i++) {
CAddrInfo infoi = CAddrInfo(
CAddress(ResolveService("250.1.1." + std::to_string(i)), NODE_NONE),
ResolveIP("250.1.1." + std::to_string(i)));
- int bucket = infoi.GetTriedBucket(nKey1);
+ int bucket = infoi.GetTriedBucket(nKey1, asmap);
buckets.insert(bucket);
}
- // Test: IP addresses in the same group (\16 prefix for IPv4) should
- // never get more than 8 buckets
+ // Test: IP addresses in the same /16 prefix should
+ // never get more than 8 buckets with legacy grouping
BOOST_CHECK_EQUAL(buckets.size(), 8U);
buckets.clear();
@@ -446,15 +491,15 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket)
CAddrInfo infoj = CAddrInfo(
CAddress(ResolveService("250." + std::to_string(j) + ".1.1"), NODE_NONE),
ResolveIP("250." + std::to_string(j) + ".1.1"));
- int bucket = infoj.GetTriedBucket(nKey1);
+ int bucket = infoj.GetTriedBucket(nKey1, asmap);
buckets.insert(bucket);
}
- // Test: IP addresses in the different groups should map to more than
- // 8 buckets.
+ // Test: IP addresses in the different /16 prefix should map to more than
+ // 8 buckets with legacy grouping
BOOST_CHECK_EQUAL(buckets.size(), 160U);
}
-BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket)
+BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket_legacy)
{
CAddrManTest addrman;
@@ -468,25 +513,27 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket)
uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash();
uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash();
+ std::vector<bool> asmap; // use /16
+
// Test: Make sure the buckets are what we expect
- BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1), 786);
- BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, source1), 786);
+ BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, asmap), 786);
+ BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, source1, asmap), 786);
// Test: Make sure key actually randomizes bucket placement. A fail on
// this test could be a security issue.
- BOOST_CHECK(info1.GetNewBucket(nKey1) != info1.GetNewBucket(nKey2));
+ BOOST_CHECK(info1.GetNewBucket(nKey1, asmap) != info1.GetNewBucket(nKey2, asmap));
// Test: Ports should not affect bucket placement in the addr
CAddrInfo info2 = CAddrInfo(addr2, source1);
BOOST_CHECK(info1.GetKey() != info2.GetKey());
- BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1), info2.GetNewBucket(nKey1));
+ BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, asmap), info2.GetNewBucket(nKey1, asmap));
std::set<int> buckets;
for (int i = 0; i < 255; i++) {
CAddrInfo infoi = CAddrInfo(
CAddress(ResolveService("250.1.1." + std::to_string(i)), NODE_NONE),
ResolveIP("250.1.1." + std::to_string(i)));
- int bucket = infoi.GetNewBucket(nKey1);
+ int bucket = infoi.GetNewBucket(nKey1, asmap);
buckets.insert(bucket);
}
// Test: IP addresses in the same group (\16 prefix for IPv4) should
@@ -499,10 +546,10 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket)
ResolveService(
std::to_string(250 + (j / 255)) + "." + std::to_string(j % 256) + ".1.1"), NODE_NONE),
ResolveIP("251.4.1.1"));
- int bucket = infoj.GetNewBucket(nKey1);
+ int bucket = infoj.GetNewBucket(nKey1, asmap);
buckets.insert(bucket);
}
- // Test: IP addresses in the same source groups should map to no more
+ // Test: IP addresses in the same source groups should map to NO MORE
// than 64 buckets.
BOOST_CHECK(buckets.size() <= 64);
@@ -511,14 +558,226 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket)
CAddrInfo infoj = CAddrInfo(
CAddress(ResolveService("250.1.1.1"), NODE_NONE),
ResolveIP("250." + std::to_string(p) + ".1.1"));
- int bucket = infoj.GetNewBucket(nKey1);
+ int bucket = infoj.GetNewBucket(nKey1, asmap);
buckets.insert(bucket);
}
- // Test: IP addresses in the different source groups should map to more
+ // Test: IP addresses in the different source groups should map to MORE
// than 64 buckets.
BOOST_CHECK(buckets.size() > 64);
}
+// The following three test cases use asmap.raw
+// We use an artificial minimal mock mapping
+// 250.0.0.0/8 AS1000
+// 101.1.0.0/16 AS1
+// 101.2.0.0/16 AS2
+// 101.3.0.0/16 AS3
+// 101.4.0.0/16 AS4
+// 101.5.0.0/16 AS5
+// 101.6.0.0/16 AS6
+// 101.7.0.0/16 AS7
+// 101.8.0.0/16 AS8
+BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket)
+{
+ CAddrManTest addrman;
+
+ CAddress addr1 = CAddress(ResolveService("250.1.1.1", 8333), NODE_NONE);
+ CAddress addr2 = CAddress(ResolveService("250.1.1.1", 9999), NODE_NONE);
+
+ CNetAddr source1 = ResolveIP("250.1.1.1");
+
+
+ CAddrInfo info1 = CAddrInfo(addr1, source1);
+
+ uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash();
+ uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash();
+
+ std::vector<bool> asmap = FromBytes(asmap_raw, sizeof(asmap_raw) * 8);
+
+ BOOST_CHECK_EQUAL(info1.GetTriedBucket(nKey1, asmap), 236);
+
+ // Test: Make sure key actually randomizes bucket placement. A fail on
+ // this test could be a security issue.
+ BOOST_CHECK(info1.GetTriedBucket(nKey1, asmap) != info1.GetTriedBucket(nKey2, asmap));
+
+ // Test: Two addresses with same IP but different ports can map to
+ // different buckets because they have different keys.
+ CAddrInfo info2 = CAddrInfo(addr2, source1);
+
+ BOOST_CHECK(info1.GetKey() != info2.GetKey());
+ BOOST_CHECK(info1.GetTriedBucket(nKey1, asmap) != info2.GetTriedBucket(nKey1, asmap));
+
+ std::set<int> buckets;
+ for (int j = 0; j < 255; j++) {
+ CAddrInfo infoj = CAddrInfo(
+ CAddress(ResolveService("101." + std::to_string(j) + ".1.1"), NODE_NONE),
+ ResolveIP("101." + std::to_string(j) + ".1.1"));
+ int bucket = infoj.GetTriedBucket(nKey1, asmap);
+ buckets.insert(bucket);
+ }
+ // Test: IP addresses in the different /16 prefix MAY map to more than
+ // 8 buckets.
+ BOOST_CHECK(buckets.size() > 8);
+
+ buckets.clear();
+ for (int j = 0; j < 255; j++) {
+ CAddrInfo infoj = CAddrInfo(
+ CAddress(ResolveService("250." + std::to_string(j) + ".1.1"), NODE_NONE),
+ ResolveIP("250." + std::to_string(j) + ".1.1"));
+ int bucket = infoj.GetTriedBucket(nKey1, asmap);
+ buckets.insert(bucket);
+ }
+ // Test: IP addresses in the different /16 prefix MAY NOT map to more than
+ // 8 buckets.
+ BOOST_CHECK(buckets.size() == 8);
+}
+
+BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket)
+{
+ CAddrManTest addrman;
+
+ CAddress addr1 = CAddress(ResolveService("250.1.2.1", 8333), NODE_NONE);
+ CAddress addr2 = CAddress(ResolveService("250.1.2.1", 9999), NODE_NONE);
+
+ CNetAddr source1 = ResolveIP("250.1.2.1");
+
+ CAddrInfo info1 = CAddrInfo(addr1, source1);
+
+ uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash();
+ uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash();
+
+ std::vector<bool> asmap = FromBytes(asmap_raw, sizeof(asmap_raw) * 8);
+
+ // Test: Make sure the buckets are what we expect
+ BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, asmap), 795);
+ BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, source1, asmap), 795);
+
+ // Test: Make sure key actually randomizes bucket placement. A fail on
+ // this test could be a security issue.
+ BOOST_CHECK(info1.GetNewBucket(nKey1, asmap) != info1.GetNewBucket(nKey2, asmap));
+
+ // Test: Ports should not affect bucket placement in the addr
+ CAddrInfo info2 = CAddrInfo(addr2, source1);
+ BOOST_CHECK(info1.GetKey() != info2.GetKey());
+ BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, asmap), info2.GetNewBucket(nKey1, asmap));
+
+ std::set<int> buckets;
+ for (int i = 0; i < 255; i++) {
+ CAddrInfo infoi = CAddrInfo(
+ CAddress(ResolveService("250.1.1." + std::to_string(i)), NODE_NONE),
+ ResolveIP("250.1.1." + std::to_string(i)));
+ int bucket = infoi.GetNewBucket(nKey1, asmap);
+ buckets.insert(bucket);
+ }
+ // Test: IP addresses in the same /16 prefix
+ // usually map to the same bucket.
+ BOOST_CHECK_EQUAL(buckets.size(), 1U);
+
+ buckets.clear();
+ for (int j = 0; j < 4 * 255; j++) {
+ CAddrInfo infoj = CAddrInfo(CAddress(
+ ResolveService(
+ std::to_string(250 + (j / 255)) + "." + std::to_string(j % 256) + ".1.1"), NODE_NONE),
+ ResolveIP("251.4.1.1"));
+ int bucket = infoj.GetNewBucket(nKey1, asmap);
+ buckets.insert(bucket);
+ }
+ // Test: IP addresses in the same source /16 prefix should not map to more
+ // than 64 buckets.
+ BOOST_CHECK(buckets.size() <= 64);
+
+ buckets.clear();
+ for (int p = 0; p < 255; p++) {
+ CAddrInfo infoj = CAddrInfo(
+ CAddress(ResolveService("250.1.1.1"), NODE_NONE),
+ ResolveIP("101." + std::to_string(p) + ".1.1"));
+ int bucket = infoj.GetNewBucket(nKey1, asmap);
+ buckets.insert(bucket);
+ }
+ // Test: IP addresses in the different source /16 prefixes usually map to MORE
+ // than 1 bucket.
+ BOOST_CHECK(buckets.size() > 1);
+
+ buckets.clear();
+ for (int p = 0; p < 255; p++) {
+ CAddrInfo infoj = CAddrInfo(
+ CAddress(ResolveService("250.1.1.1"), NODE_NONE),
+ ResolveIP("250." + std::to_string(p) + ".1.1"));
+ int bucket = infoj.GetNewBucket(nKey1, asmap);
+ buckets.insert(bucket);
+ }
+ // Test: IP addresses in the different source /16 prefixes sometimes map to NO MORE
+ // than 1 bucket.
+ BOOST_CHECK(buckets.size() == 1);
+
+}
+
+BOOST_AUTO_TEST_CASE(addrman_serialization)
+{
+ std::vector<bool> asmap1 = FromBytes(asmap_raw, sizeof(asmap_raw) * 8);
+
+ CAddrManTest addrman_asmap1(true, asmap1);
+ CAddrManTest addrman_asmap1_dup(true, asmap1);
+ CAddrManTest addrman_noasmap;
+ CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
+
+ CAddress addr = CAddress(ResolveService("250.1.1.1"), NODE_NONE);
+ CNetAddr default_source;
+
+
+ addrman_asmap1.Add(addr, default_source);
+
+ stream << addrman_asmap1;
+ // serizalizing/deserializing addrman with the same asmap
+ stream >> addrman_asmap1_dup;
+
+ std::pair<int, int> bucketAndEntry_asmap1 = addrman_asmap1.GetBucketAndEntry(addr);
+ std::pair<int, int> bucketAndEntry_asmap1_dup = addrman_asmap1_dup.GetBucketAndEntry(addr);
+ BOOST_CHECK(bucketAndEntry_asmap1.second != -1);
+ BOOST_CHECK(bucketAndEntry_asmap1_dup.second != -1);
+
+ BOOST_CHECK(bucketAndEntry_asmap1.first == bucketAndEntry_asmap1_dup.first);
+ BOOST_CHECK(bucketAndEntry_asmap1.second == bucketAndEntry_asmap1_dup.second);
+
+ // deserializing asmaped peers.dat to non-asmaped addrman
+ stream << addrman_asmap1;
+ stream >> addrman_noasmap;
+ std::pair<int, int> bucketAndEntry_noasmap = addrman_noasmap.GetBucketAndEntry(addr);
+ BOOST_CHECK(bucketAndEntry_noasmap.second != -1);
+ BOOST_CHECK(bucketAndEntry_asmap1.first != bucketAndEntry_noasmap.first);
+ BOOST_CHECK(bucketAndEntry_asmap1.second != bucketAndEntry_noasmap.second);
+
+ // deserializing non-asmaped peers.dat to asmaped addrman
+ addrman_asmap1.Clear();
+ addrman_noasmap.Clear();
+ addrman_noasmap.Add(addr, default_source);
+ stream << addrman_noasmap;
+ stream >> addrman_asmap1;
+ std::pair<int, int> bucketAndEntry_asmap1_deser = addrman_asmap1.GetBucketAndEntry(addr);
+ BOOST_CHECK(bucketAndEntry_asmap1_deser.second != -1);
+ BOOST_CHECK(bucketAndEntry_asmap1_deser.first != bucketAndEntry_noasmap.first);
+ BOOST_CHECK(bucketAndEntry_asmap1_deser.first == bucketAndEntry_asmap1_dup.first);
+ BOOST_CHECK(bucketAndEntry_asmap1_deser.second == bucketAndEntry_asmap1_dup.second);
+
+ // used to map to different buckets, now maps to the same bucket.
+ addrman_asmap1.Clear();
+ addrman_noasmap.Clear();
+ CAddress addr1 = CAddress(ResolveService("250.1.1.1"), NODE_NONE);
+ CAddress addr2 = CAddress(ResolveService("250.2.1.1"), NODE_NONE);
+ addrman_noasmap.Add(addr, default_source);
+ addrman_noasmap.Add(addr2, default_source);
+ std::pair<int, int> bucketAndEntry_noasmap_addr1 = addrman_noasmap.GetBucketAndEntry(addr1);
+ std::pair<int, int> bucketAndEntry_noasmap_addr2 = addrman_noasmap.GetBucketAndEntry(addr2);
+ BOOST_CHECK(bucketAndEntry_noasmap_addr1.first != bucketAndEntry_noasmap_addr2.first);
+ BOOST_CHECK(bucketAndEntry_noasmap_addr1.second != bucketAndEntry_noasmap_addr2.second);
+ stream << addrman_noasmap;
+ stream >> addrman_asmap1;
+ std::pair<int, int> bucketAndEntry_asmap1_deser_addr1 = addrman_asmap1.GetBucketAndEntry(addr1);
+ std::pair<int, int> bucketAndEntry_asmap1_deser_addr2 = addrman_asmap1.GetBucketAndEntry(addr2);
+ BOOST_CHECK(bucketAndEntry_asmap1_deser_addr1.first == bucketAndEntry_asmap1_deser_addr2.first);
+ BOOST_CHECK(bucketAndEntry_asmap1_deser_addr1.second != bucketAndEntry_asmap1_deser_addr2.second);
+}
+
BOOST_AUTO_TEST_CASE(addrman_selecttriedcollision)
{
diff --git a/src/test/data/asmap.raw b/src/test/data/asmap.raw
new file mode 100644
index 0000000000..3dcf1f3940
--- /dev/null
+++ b/src/test/data/asmap.raw
Binary files differ
diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp
index 58e0565bda..9730b40580 100644
--- a/src/test/netbase_tests.cpp
+++ b/src/test/netbase_tests.cpp
@@ -286,23 +286,23 @@ BOOST_AUTO_TEST_CASE(subnet_test)
BOOST_AUTO_TEST_CASE(netbase_getgroup)
{
-
- BOOST_CHECK(ResolveIP("127.0.0.1").GetGroup() == std::vector<unsigned char>({0})); // Local -> !Routable()
- BOOST_CHECK(ResolveIP("257.0.0.1").GetGroup() == std::vector<unsigned char>({0})); // !Valid -> !Routable()
- BOOST_CHECK(ResolveIP("10.0.0.1").GetGroup() == std::vector<unsigned char>({0})); // RFC1918 -> !Routable()
- BOOST_CHECK(ResolveIP("169.254.1.1").GetGroup() == std::vector<unsigned char>({0})); // RFC3927 -> !Routable()
- BOOST_CHECK(ResolveIP("1.2.3.4").GetGroup() == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // IPv4
- BOOST_CHECK(ResolveIP("::FFFF:0:102:304").GetGroup() == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6145
- BOOST_CHECK(ResolveIP("64:FF9B::102:304").GetGroup() == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6052
- BOOST_CHECK(ResolveIP("2002:102:304:9999:9999:9999:9999:9999").GetGroup() == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC3964
- BOOST_CHECK(ResolveIP("2001:0:9999:9999:9999:9999:FEFD:FCFB").GetGroup() == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC4380
- BOOST_CHECK(ResolveIP("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetGroup() == std::vector<unsigned char>({(unsigned char)NET_ONION, 239})); // Tor
- BOOST_CHECK(ResolveIP("2001:470:abcd:9999:9999:9999:9999:9999").GetGroup() == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 4, 112, 175})); //he.net
- BOOST_CHECK(ResolveIP("2001:2001:9999:9999:9999:9999:9999:9999").GetGroup() == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 32, 1})); //IPv6
+ std::vector<bool> asmap; // use /16
+ BOOST_CHECK(ResolveIP("127.0.0.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // Local -> !Routable()
+ BOOST_CHECK(ResolveIP("257.0.0.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // !Valid -> !Routable()
+ BOOST_CHECK(ResolveIP("10.0.0.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // RFC1918 -> !Routable()
+ BOOST_CHECK(ResolveIP("169.254.1.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // RFC3927 -> !Routable()
+ BOOST_CHECK(ResolveIP("1.2.3.4").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // IPv4
+ BOOST_CHECK(ResolveIP("::FFFF:0:102:304").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6145
+ BOOST_CHECK(ResolveIP("64:FF9B::102:304").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6052
+ BOOST_CHECK(ResolveIP("2002:102:304:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC3964
+ BOOST_CHECK(ResolveIP("2001:0:9999:9999:9999:9999:FEFD:FCFB").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC4380
+ BOOST_CHECK(ResolveIP("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_ONION, 239})); // Tor
+ BOOST_CHECK(ResolveIP("2001:470:abcd:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 4, 112, 175})); //he.net
+ BOOST_CHECK(ResolveIP("2001:2001:9999:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 32, 1})); //IPv6
// baz.net sha256 hash: 12929400eb4607c4ac075f087167e75286b179c693eb059a01774b864e8fe505
std::vector<unsigned char> internal_group = {NET_INTERNAL, 0x12, 0x92, 0x94, 0x00, 0xeb, 0x46, 0x07, 0xc4, 0xac, 0x07};
- BOOST_CHECK(CreateInternal("baz.net").GetGroup() == internal_group);
+ BOOST_CHECK(CreateInternal("baz.net").GetGroup(asmap) == internal_group);
}
BOOST_AUTO_TEST_CASE(netbase_parsenetwork)
diff --git a/src/util/asmap.cpp b/src/util/asmap.cpp
new file mode 100644
index 0000000000..ac230e9ee5
--- /dev/null
+++ b/src/util/asmap.cpp
@@ -0,0 +1,97 @@
+// Copyright (c) 2019 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <vector>
+#include <assert.h>
+#include <crypto/common.h>
+
+namespace {
+
+uint32_t DecodeBits(std::vector<bool>::const_iterator& bitpos, uint8_t minval, const std::vector<uint8_t> &bit_sizes)
+{
+ uint32_t val = minval;
+ bool bit;
+ for (std::vector<uint8_t>::const_iterator bit_sizes_it = bit_sizes.begin();
+ bit_sizes_it != bit_sizes.end(); ++bit_sizes_it) {
+ if (bit_sizes_it + 1 != bit_sizes.end()) {
+ bit = *bitpos;
+ bitpos++;
+ } else {
+ bit = 0;
+ }
+ if (bit) {
+ val += (1 << *bit_sizes_it);
+ } else {
+ for (int b = 0; b < *bit_sizes_it; b++) {
+ bit = *bitpos;
+ bitpos++;
+ val += bit << (*bit_sizes_it - 1 - b);
+ }
+ return val;
+ }
+ }
+ return -1;
+}
+
+const std::vector<uint8_t> TYPE_BIT_SIZES{0, 0, 1};
+uint32_t DecodeType(std::vector<bool>::const_iterator& bitpos)
+{
+ return DecodeBits(bitpos, 0, TYPE_BIT_SIZES);
+}
+
+const std::vector<uint8_t> ASN_BIT_SIZES{15, 16, 17, 18, 19, 20, 21, 22, 23, 24};
+uint32_t DecodeASN(std::vector<bool>::const_iterator& bitpos)
+{
+ return DecodeBits(bitpos, 1, ASN_BIT_SIZES);
+}
+
+
+const std::vector<uint8_t> MATCH_BIT_SIZES{1, 2, 3, 4, 5, 6, 7, 8};
+uint32_t DecodeMatch(std::vector<bool>::const_iterator& bitpos)
+{
+ return DecodeBits(bitpos, 2, MATCH_BIT_SIZES);
+}
+
+
+const std::vector<uint8_t> JUMP_BIT_SIZES{5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30};
+uint32_t DecodeJump(std::vector<bool>::const_iterator& bitpos)
+{
+ return DecodeBits(bitpos, 17, JUMP_BIT_SIZES);
+}
+
+}
+
+uint32_t Interpret(const std::vector<bool> &asmap, const std::vector<bool> &ip)
+{
+ std::vector<bool>::const_iterator pos = asmap.begin();
+ uint8_t bits = ip.size();
+ uint8_t default_asn = 0;
+ uint32_t opcode, jump, match, matchlen;
+ while (1) {
+ assert(pos != asmap.end());
+ opcode = DecodeType(pos);
+ if (opcode == 0) {
+ return DecodeASN(pos);
+ } else if (opcode == 1) {
+ jump = DecodeJump(pos);
+ if (ip[ip.size() - bits]) {
+ pos += jump;
+ }
+ bits--;
+ } else if (opcode == 2) {
+ match = DecodeMatch(pos);
+ matchlen = CountBits(match) - 1;
+ for (uint32_t bit = 0; bit < matchlen; bit++) {
+ if ((ip[ip.size() - bits]) != ((match >> (matchlen - 1 - bit)) & 1)) {
+ return default_asn;
+ }
+ bits--;
+ }
+ } else if (opcode == 3) {
+ default_asn = DecodeASN(pos);
+ } else {
+ assert(0);
+ }
+ }
+}
diff --git a/src/util/asmap.h b/src/util/asmap.h
new file mode 100644
index 0000000000..a0e14013c5
--- /dev/null
+++ b/src/util/asmap.h
@@ -0,0 +1,10 @@
+// Copyright (c) 2019 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_UTIL_ASMAP_H
+#define BITCOIN_UTIL_ASMAP_H
+
+uint32_t Interpret(const std::vector<bool> &asmap, const std::vector<bool> &ip);
+
+#endif // BITCOIN_UTIL_ASMAP_H