Integrate ASN bucketing in Addrman and add tests

Instead of using /16 netgroups to bucket nodes in Addrman for connection
diversification, ASN, which better represents an actor in terms
of network-layer infrastructure, is used.
For testing, asmap.raw is used. It represents a minimal
asmap needed for testing purposes.
This commit is contained in:
Gleb Naumenko 2019-12-24 13:18:44 -05:00
parent 8feb4e4b66
commit ec45646de9
11 changed files with 483 additions and 88 deletions

View file

@ -53,7 +53,8 @@ JSON_TEST_FILES = \
test/data/tx_invalid.json \ test/data/tx_invalid.json \
test/data/tx_valid.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) GENERATED_TEST_FILES = $(JSON_TEST_FILES:.json=.json.h) $(RAW_TEST_FILES:.raw=.raw.h)
@ -429,3 +430,12 @@ endif
echo "};};"; \ echo "};};"; \
} > "$@.new" && mv -f "$@.new" "$@" } > "$@.new" && mv -f "$@.new" "$@"
@echo "Generated $@" @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 $@"

View file

@ -8,17 +8,17 @@
#include <hash.h> #include <hash.h>
#include <serialize.h> #include <serialize.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 hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetKey()).GetCheapHash();
uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup() << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP)).GetCheapHash(); uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup(asmap) << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP)).GetCheapHash();
return hash2 % ADDRMAN_TRIED_BUCKET_COUNT; return hash2 % ADDRMAN_TRIED_BUCKET_COUNT;
} }
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(); std::vector<unsigned char> vchSourceGroupKey = src.GetGroup(asmap);
uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup() << vchSourceGroupKey).GetCheapHash(); 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(); uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << vchSourceGroupKey << (hash1 % ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP)).GetCheapHash();
return hash2 % ADDRMAN_NEW_BUCKET_COUNT; return hash2 % ADDRMAN_NEW_BUCKET_COUNT;
} }
@ -153,7 +153,7 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId)
assert(info.nRefCount == 0); assert(info.nRefCount == 0);
// which tried bucket to move the entry to // 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); 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). // first make space to add it (the existing tried entry there is moved to new, deleting whatever is there).
@ -169,7 +169,7 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId)
nTried--; nTried--;
// find which new bucket it belongs to // 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); int nUBucketPos = infoOld.GetBucketPosition(nKey, true, nUBucket);
ClearNew(nUBucket, nUBucketPos); ClearNew(nUBucket, nUBucketPos);
assert(vvNew[nUBucket][nUBucketPos] == -1); assert(vvNew[nUBucket][nUBucketPos] == -1);
@ -233,7 +233,7 @@ void CAddrMan::Good_(const CService& addr, bool test_before_evict, int64_t nTime
return; return;
// which tried bucket to move the entry to // 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); int tried_bucket_pos = info.GetBucketPosition(nKey, false, tried_bucket);
// Will moving this address into tried evict another entry? // Will moving this address into tried evict another entry?
@ -301,7 +301,7 @@ bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimeP
fNew = true; fNew = true;
} }
int nUBucket = pinfo->GetNewBucket(nKey, source); int nUBucket = pinfo->GetNewBucket(nKey, source, m_asmap);
int nUBucketPos = pinfo->GetBucketPosition(nKey, true, nUBucket); int nUBucketPos = pinfo->GetBucketPosition(nKey, true, nUBucket);
if (vvNew[nUBucket][nUBucketPos] != nId) { if (vvNew[nUBucket][nUBucketPos] != nId) {
bool fInsert = vvNew[nUBucket][nUBucketPos] == -1; bool fInsert = vvNew[nUBucket][nUBucketPos] == -1;
@ -439,7 +439,7 @@ int CAddrMan::Check_()
if (vvTried[n][i] != -1) { if (vvTried[n][i] != -1) {
if (!setTried.count(vvTried[n][i])) if (!setTried.count(vvTried[n][i]))
return -11; return -11;
if (mapInfo[vvTried[n][i]].GetTriedBucket(nKey) != n) if (mapInfo[vvTried[n][i]].GetTriedBucket(nKey, m_asmap) != n)
return -17; return -17;
if (mapInfo[vvTried[n][i]].GetBucketPosition(nKey, false, n) != i) if (mapInfo[vvTried[n][i]].GetBucketPosition(nKey, false, n) != i)
return -18; return -18;
@ -545,7 +545,7 @@ void CAddrMan::ResolveCollisions_()
CAddrInfo& info_new = mapInfo[id_new]; CAddrInfo& info_new = mapInfo[id_new];
// Which tried bucket to move the entry to. // 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); 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 if (!info_new.IsValid()) { // id_new may no longer map to a valid address
erase_collision = true; erase_collision = true;
@ -609,10 +609,33 @@ CAddrInfo CAddrMan::SelectTriedCollision_()
CAddrInfo& newInfo = mapInfo[id_new]; CAddrInfo& newInfo = mapInfo[id_new];
// which tried bucket to move the entry to // 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 tried_bucket_pos = newInfo.GetBucketPosition(nKey, false, tried_bucket);
int id_old = vvTried[tried_bucket][tried_bucket_pos]; int id_old = vvTried[tried_bucket][tried_bucket_pos];
return mapInfo[id_old]; 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;
}

View file

@ -12,11 +12,17 @@
#include <sync.h> #include <sync.h>
#include <timedata.h> #include <timedata.h>
#include <util/system.h> #include <util/system.h>
#include <clientversion.h>
#include <map> #include <map>
#include <set> #include <set>
#include <stdint.h> #include <stdint.h>
#include <vector> #include <vector>
#include <iostream>
#include <streams.h>
#include <fs.h>
#include <hash.h>
/** /**
* Extended statistics about a CAddress * Extended statistics about a CAddress
@ -72,15 +78,15 @@ public:
} }
//! Calculate in which "tried" bucket this entry belongs //! 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 //! 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 //! 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. //! Calculate in which position of a bucket to store this entry.
@ -174,6 +180,7 @@ static const int64_t ADDRMAN_TEST_WINDOW = 40*60; // 40 minutes
*/ */
class CAddrMan class CAddrMan
{ {
friend class CAddrManTest;
protected: protected:
//! critical section to protect the inner data structures //! critical section to protect the inner data structures
mutable CCriticalSection cs; mutable CCriticalSection cs;
@ -268,9 +275,29 @@ protected:
void SetServices_(const CService &addr, ServiceFlags nServices) EXCLUSIVE_LOCKS_REQUIRED(cs); void SetServices_(const CService &addr, ServiceFlags nServices) EXCLUSIVE_LOCKS_REQUIRED(cs);
public: 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: * 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) * * 0x20 + nKey (serialized as if it were a vector, for backward compatibility)
* * nNew * * nNew
* * nTried * * nTried
@ -302,7 +329,7 @@ public:
{ {
LOCK(cs); LOCK(cs);
unsigned char nVersion = 1; unsigned char nVersion = 2;
s << nVersion; s << nVersion;
s << ((unsigned char)32); s << ((unsigned char)32);
s << nKey; s << nKey;
@ -345,6 +372,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> template<typename Stream>
@ -353,7 +387,6 @@ public:
LOCK(cs); LOCK(cs);
Clear(); Clear();
unsigned char nVersion; unsigned char nVersion;
s >> nVersion; s >> nVersion;
unsigned char nKeySize; unsigned char nKeySize;
@ -383,16 +416,6 @@ public:
mapAddr[info] = n; mapAddr[info] = n;
info.nRandomPos = vRandom.size(); info.nRandomPos = vRandom.size();
vRandom.push_back(n); 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; nIdCount = nNew;
@ -401,7 +424,7 @@ public:
for (int n = 0; n < nTried; n++) { for (int n = 0; n < nTried; n++) {
CAddrInfo info; CAddrInfo info;
s >> info; s >> info;
int nKBucket = info.GetTriedBucket(nKey); int nKBucket = info.GetTriedBucket(nKey, m_asmap);
int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket); int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket);
if (vvTried[nKBucket][nKBucketPos] == -1) { if (vvTried[nKBucket][nKBucketPos] == -1) {
info.nRandomPos = vRandom.size(); info.nRandomPos = vRandom.size();
@ -417,7 +440,9 @@ public:
} }
nTried -= nLost; 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++) { for (int bucket = 0; bucket < nUBuckets; bucket++) {
int nSize = 0; int nSize = 0;
s >> nSize; s >> nSize;
@ -425,12 +450,38 @@ public:
int nIndex = 0; int nIndex = 0;
s >> nIndex; s >> nIndex;
if (nIndex >= 0 && nIndex < nNew) { if (nIndex >= 0 && nIndex < nNew) {
CAddrInfo &info = mapInfo[nIndex]; entryToBucket[nIndex] = bucket;
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;
} 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++;
} }
} }
} }

View file

@ -52,7 +52,11 @@
#include <util/threadnames.h> #include <util/threadnames.h>
#include <util/translation.h> #include <util/translation.h>
#include <util/validation.h> #include <util/validation.h>
#include <util/asmap.h>
#include <validation.h> #include <validation.h>
#include <hash.h>
#include <validationinterface.h> #include <validationinterface.h>
#include <walletinitinterface.h> #include <walletinitinterface.h>
@ -97,6 +101,8 @@ static constexpr int DUMP_BANS_INTERVAL = 60 * 15;
static const char* FEE_ESTIMATES_FILENAME="fee_estimates.dat"; static const char* FEE_ESTIMATES_FILENAME="fee_estimates.dat";
static const char* DEFAULT_ASMAP_FILENAME="ip_asn.map";
/** /**
* The PID file facilities. * The PID file facilities.
*/ */
@ -426,6 +432,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("-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("-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("-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 #ifdef USE_UPNP
#if 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); gArgs.AddArg("-upnp", "Use UPnP to map the listening port (default: 1 when listening and no -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@ -1807,6 +1814,25 @@ bool AppInitMain(NodeContext& node)
return false; 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 // ********************************************************* Step 13: finished
SetRPCWarmupFinished(); SetRPCWarmupFinished();

View file

@ -1758,7 +1758,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
// but inbound and addnode peers do not use our outbound slots. Inbound peers // 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 // 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. // 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) { if (pnode->m_tx_relay == nullptr) {
nOutboundBlockRelay++; nOutboundBlockRelay++;
} else if (!pnode->fFeeler) { } else if (!pnode->fFeeler) {
@ -1806,7 +1806,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
} }
// Require outbound connections, other than feelers, to be to distinct network groups // 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; break;
} }
@ -2778,7 +2778,7 @@ CSipHasher CConnman::GetDeterministicRandomizer(uint64_t id) const
uint64_t CConnman::CalculateKeyedNetGroup(const CAddress& ad) 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(); return GetDeterministicRandomizer(RANDOMIZER_ID_NETGROUP).Write(vchNetGroup.data(), vchNetGroup.size()).Finalize();
} }

View file

@ -153,6 +153,7 @@ public:
bool m_use_addrman_outgoing = true; bool m_use_addrman_outgoing = true;
std::vector<std::string> m_specified_outgoing; std::vector<std::string> m_specified_outgoing;
std::vector<std::string> m_added_nodes; std::vector<std::string> m_added_nodes;
std::vector<bool> m_asmap;
}; };
void Init(const Options& connOptions) { void Init(const Options& connOptions) {
@ -330,6 +331,8 @@ public:
*/ */
int64_t PoissonNextSendInbound(int64_t now, int average_interval_seconds); int64_t PoissonNextSendInbound(int64_t now, int average_interval_seconds);
void SetAsmap(std::vector<bool> asmap) { addrman.m_asmap = asmap; }
private: private:
struct ListenSocket { struct ListenSocket {
public: public:

View file

@ -6,6 +6,7 @@
#include <netaddress.h> #include <netaddress.h>
#include <hash.h> #include <hash.h>
#include <util/strencodings.h> #include <util/strencodings.h>
#include <util/asmap.h>
#include <tinyformat.h> #include <tinyformat.h>
static const unsigned char pchIPv4[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff }; static const unsigned char pchIPv4[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff };
@ -410,7 +411,7 @@ bool CNetAddr::GetIn6Addr(struct in6_addr* pipv6Addr) const
* @note No two connections will be attempted to addresses with the same network * @note No two connections will be attempted to addresses with the same network
* group. * 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; std::vector<unsigned char> vchRet;
int nClass = NET_IPV6; int nClass = NET_IPV6;
@ -470,6 +471,27 @@ std::vector<unsigned char> CNetAddr::GetGroup() const
else else
nBits = 32; nBits = 32;
// If asmap is supplied and the address is IPv4/IPv6,
// ignore nBits and use 32/128 bits to obtain ASN from asmap.
// ASN is then returned to be used for bucketing.
if (asmap.size() != 0 && (nClass == NET_IPV4 || nClass == NET_IPV6)) {
nClass = NET_IPV6;
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 asn = Interpret(asmap, ip_bits);
vchRet.push_back(nClass);
for (int i = 0; i < 4; i++) {
vchRet.push_back((asn >> (8 * i)) & 0xFF);
}
return vchRet;
}
vchRet.push_back(nClass); vchRet.push_back(nClass);
// push our ip onto vchRet byte by byte... // push our ip onto vchRet byte by byte...

View file

@ -78,7 +78,8 @@ class CNetAddr
unsigned int GetByte(int n) const; unsigned int GetByte(int n) const;
uint64_t GetHash() const; uint64_t GetHash() const;
bool GetInAddr(struct in_addr* pipv4Addr) const; bool GetInAddr(struct in_addr* pipv4Addr) const;
std::vector<unsigned char> GetGroup() const; std::vector<unsigned char> GetGroup(const std::vector<bool> &asmap) const;
int GetReachabilityFrom(const CNetAddr *paddrPartner = nullptr) const; int GetReachabilityFrom(const CNetAddr *paddrPartner = nullptr) const;
explicit CNetAddr(const struct in6_addr& pipv6Addr, const uint32_t scope = 0); explicit CNetAddr(const struct in6_addr& pipv6Addr, const uint32_t scope = 0);

View file

@ -5,6 +5,8 @@
#include <test/util/setup_common.h> #include <test/util/setup_common.h>
#include <string> #include <string>
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include <util/asmap.h>
#include <test/data/asmap.raw.h>
#include <hash.h> #include <hash.h>
#include <netbase.h> #include <netbase.h>
@ -12,13 +14,18 @@
class CAddrManTest : public CAddrMan class CAddrManTest : public CAddrMan
{ {
private:
bool deterministic;
public: public:
explicit CAddrManTest(bool makeDeterministic = true) explicit CAddrManTest(bool makeDeterministic = true,
std::vector<bool> asmap = std::vector<bool>())
{ {
if (makeDeterministic) { if (makeDeterministic) {
// Set addrman addr placement to be deterministic. // Set addrman addr placement to be deterministic.
MakeDeterministic(); MakeDeterministic();
} }
deterministic = makeDeterministic;
m_asmap = asmap;
} }
//! Ensure that bucket placement is always the same for testing purposes. //! Ensure that bucket placement is always the same for testing purposes.
@ -46,6 +53,21 @@ public:
CAddrMan::Delete(nId); 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 // Simulates connection failure so that we can test eviction of offline nodes
void SimConnFail(CService& addr) void SimConnFail(CService& addr)
{ {
@ -57,6 +79,16 @@ public:
int64_t nLastTry = GetAdjustedTime()-61; int64_t nLastTry = GetAdjustedTime()-61;
Attempt(addr, count_failure, nLastTry); Attempt(addr, count_failure, nLastTry);
} }
void Clear()
{
CAddrMan::Clear();
if (deterministic) {
nKey.SetNull();
insecure_rand = FastRandomContext(true);
}
}
}; };
static CNetAddr ResolveIP(const char* ip) static CNetAddr ResolveIP(const char* ip)
@ -83,6 +115,18 @@ static CService ResolveService(std::string ip, int port = 0)
return ResolveService(ip.c_str(), port); return ResolveService(ip.c_str(), port);
} }
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_FIXTURE_TEST_SUITE(addrman_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(addrman_simple) BOOST_AUTO_TEST_CASE(addrman_simple)
@ -409,6 +453,139 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr)
} }
BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket_legacy)
{
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; // use /16
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, 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 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, asmap);
buckets.insert(bucket);
}
// 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();
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 should map to more than
// 8 buckets with legacy grouping
BOOST_CHECK_EQUAL(buckets.size(), 160U);
}
BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket_legacy)
{
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; // use /16
// Test: Make sure the buckets are what we expect
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, 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 group (\16 prefix for IPv4) should
// always 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 groups should map to NO 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("250." + std::to_string(p) + ".1.1"));
int bucket = infoj.GetNewBucket(nKey1, asmap);
buckets.insert(bucket);
}
// 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) BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket)
{ {
CAddrManTest addrman; CAddrManTest addrman;
@ -424,43 +601,44 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket)
uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash(); uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash();
uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).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), 40); BOOST_CHECK_EQUAL(info1.GetTriedBucket(nKey1, asmap), 236);
// Test: Make sure key actually randomizes bucket placement. A fail on // Test: Make sure key actually randomizes bucket placement. A fail on
// this test could be a security issue. // 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 // Test: Two addresses with same IP but different ports can map to
// different buckets because they have different keys. // different buckets because they have different keys.
CAddrInfo info2 = CAddrInfo(addr2, source1); CAddrInfo info2 = CAddrInfo(addr2, source1);
BOOST_CHECK(info1.GetKey() != info2.GetKey()); 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; std::set<int> buckets;
for (int i = 0; i < 255; i++) { for (int j = 0; j < 255; j++) {
CAddrInfo infoi = CAddrInfo( CAddrInfo infoj = CAddrInfo(
CAddress(ResolveService("250.1.1." + std::to_string(i)), NODE_NONE), CAddress(ResolveService("101." + std::to_string(j) + ".1.1"), NODE_NONE),
ResolveIP("250.1.1." + std::to_string(i))); ResolveIP("101." + std::to_string(j) + ".1.1"));
int bucket = infoi.GetTriedBucket(nKey1); int bucket = infoj.GetTriedBucket(nKey1, asmap);
buckets.insert(bucket); buckets.insert(bucket);
} }
// Test: IP addresses in the same group (\16 prefix for IPv4) should // Test: IP addresses in the different /16 prefix MAY map to more than
// never get more than 8 buckets // 8 buckets.
BOOST_CHECK_EQUAL(buckets.size(), 8U); BOOST_CHECK(buckets.size() > 8);
buckets.clear(); buckets.clear();
for (int j = 0; j < 255; j++) { for (int j = 0; j < 255; j++) {
CAddrInfo infoj = CAddrInfo( CAddrInfo infoj = CAddrInfo(
CAddress(ResolveService("250." + std::to_string(j) + ".1.1"), NODE_NONE), CAddress(ResolveService("250." + std::to_string(j) + ".1.1"), NODE_NONE),
ResolveIP("250." + std::to_string(j) + ".1.1")); ResolveIP("250." + std::to_string(j) + ".1.1"));
int bucket = infoj.GetTriedBucket(nKey1); int bucket = infoj.GetTriedBucket(nKey1, asmap);
buckets.insert(bucket); buckets.insert(bucket);
} }
// Test: IP addresses in the different groups should map to more than // Test: IP addresses in the different /16 prefix MAY NOT map to more than
// 8 buckets. // 8 buckets.
BOOST_CHECK_EQUAL(buckets.size(), 160U); BOOST_CHECK(buckets.size() == 8);
} }
BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket) BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket)
@ -477,29 +655,31 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket)
uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash(); uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash();
uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).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 // Test: Make sure the buckets are what we expect
BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1), 786); BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, asmap), 795);
BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, source1), 786); BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, source1, asmap), 795);
// Test: Make sure key actually randomizes bucket placement. A fail on // Test: Make sure key actually randomizes bucket placement. A fail on
// this test could be a security issue. // 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 // Test: Ports should not affect bucket placement in the addr
CAddrInfo info2 = CAddrInfo(addr2, source1); CAddrInfo info2 = CAddrInfo(addr2, source1);
BOOST_CHECK(info1.GetKey() != info2.GetKey()); 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; std::set<int> buckets;
for (int i = 0; i < 255; i++) { for (int i = 0; i < 255; i++) {
CAddrInfo infoi = CAddrInfo( CAddrInfo infoi = CAddrInfo(
CAddress(ResolveService("250.1.1." + std::to_string(i)), NODE_NONE), CAddress(ResolveService("250.1.1." + std::to_string(i)), NODE_NONE),
ResolveIP("250.1.1." + std::to_string(i))); ResolveIP("250.1.1." + std::to_string(i)));
int bucket = infoi.GetNewBucket(nKey1); int bucket = infoi.GetNewBucket(nKey1, asmap);
buckets.insert(bucket); buckets.insert(bucket);
} }
// Test: IP addresses in the same group (\16 prefix for IPv4) should // Test: IP addresses in the same /16 prefix
// always map to the same bucket. // usually map to the same bucket.
BOOST_CHECK_EQUAL(buckets.size(), 1U); BOOST_CHECK_EQUAL(buckets.size(), 1U);
buckets.clear(); buckets.clear();
@ -508,24 +688,103 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket)
ResolveService( ResolveService(
std::to_string(250 + (j / 255)) + "." + std::to_string(j % 256) + ".1.1"), NODE_NONE), std::to_string(250 + (j / 255)) + "." + std::to_string(j % 256) + ".1.1"), NODE_NONE),
ResolveIP("251.4.1.1")); ResolveIP("251.4.1.1"));
int bucket = infoj.GetNewBucket(nKey1); int bucket = infoj.GetNewBucket(nKey1, asmap);
buckets.insert(bucket); buckets.insert(bucket);
} }
// Test: IP addresses in the same source groups should map to no more // Test: IP addresses in the same source /16 prefix should not map to more
// than 64 buckets. // than 64 buckets.
BOOST_CHECK(buckets.size() <= 64); BOOST_CHECK(buckets.size() <= 64);
buckets.clear(); buckets.clear();
for (int p = 0; p < 255; p++) { for (int p = 0; p < 255; p++) {
CAddrInfo infoj = CAddrInfo( CAddrInfo infoj = CAddrInfo(
CAddress(ResolveService("250.1.1.1"), NODE_NONE), CAddress(ResolveService("250.1.1.1"), NODE_NONE),
ResolveIP("250." + std::to_string(p) + ".1.1")); ResolveIP("101." + std::to_string(p) + ".1.1"));
int bucket = infoj.GetNewBucket(nKey1); int bucket = infoj.GetNewBucket(nKey1, asmap);
buckets.insert(bucket); buckets.insert(bucket);
} }
// Test: IP addresses in the different source groups should map to more // Test: IP addresses in the different source /16 prefixes usually map to MORE
// than 64 buckets. // than 1 bucket.
BOOST_CHECK(buckets.size() > 64); 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);
} }

BIN
src/test/data/asmap.raw Normal file

Binary file not shown.

View file

@ -285,23 +285,23 @@ BOOST_AUTO_TEST_CASE(subnet_test)
BOOST_AUTO_TEST_CASE(netbase_getgroup) BOOST_AUTO_TEST_CASE(netbase_getgroup)
{ {
std::vector<bool> asmap; // use /16
BOOST_CHECK(ResolveIP("127.0.0.1").GetGroup() == std::vector<unsigned char>({0})); // Local -> !Routable() BOOST_CHECK(ResolveIP("127.0.0.1").GetGroup(asmap) == 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("257.0.0.1").GetGroup(asmap) == 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("10.0.0.1").GetGroup(asmap) == 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("169.254.1.1").GetGroup(asmap) == 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("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() == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6145 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() == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6052 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() == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC3964 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() == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC4380 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() == std::vector<unsigned char>({(unsigned char)NET_ONION, 239})); // Tor 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() == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 4, 112, 175})); //he.net 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() == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 32, 1})); //IPv6 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 // baz.net sha256 hash: 12929400eb4607c4ac075f087167e75286b179c693eb059a01774b864e8fe505
std::vector<unsigned char> internal_group = {NET_INTERNAL, 0x12, 0x92, 0x94, 0x00, 0xeb, 0x46, 0x07, 0xc4, 0xac, 0x07}; 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) BOOST_AUTO_TEST_CASE(netbase_parsenetwork)