Skip to content

Commit

Permalink
net: save the network type explicitly in CNetAddr
Browse files Browse the repository at this point in the history
Summary:
```
Before this change, we would analyze the contents of CNetAddr::ip[16]
in order to tell which type is an address. Change this by introducing a
new member CNetAddr::m_net that explicitly tells the type of the
address.

This is necessary because in BIP155 we will not be able to tell the
address type by just looking at its raw representation (e.g. both TORv3
and I2P are "seemingly random" 32 bytes).

As a side effect of this change we no longer need to store IPv4
addresses encoded as IPv6 addresses - we can store them in proper 4
bytes (will be done in a separate commit). Also the code gets
somewhat simplified - instead of
memcmp(ip, pchIPv4, sizeof(pchIPv4)) == 0 we can use
m_net == NET_IPV4.
```

Backport of core [[bitcoin/bitcoin#19534 | PR19534]].

Depends on D9170 and D9171.

Test Plan:
  ninja all check-all

Reviewers: #bitcoin_abc, majcosta

Reviewed By: #bitcoin_abc, majcosta

Differential Revision: https://reviews.bitcoinabc.org/D9172
  • Loading branch information
vasild authored and Fabcien committed Feb 5, 2021
1 parent 2184cc6 commit b4c94da
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 37 deletions.
77 changes: 44 additions & 33 deletions src/netaddress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,33 @@ CNetAddr::CNetAddr() {
}

void CNetAddr::SetIP(const CNetAddr &ipIn) {
m_net = ipIn.m_net;
memcpy(ip, ipIn.ip, sizeof(ip));
}

void CNetAddr::SetLegacyIPv6(const uint8_t ipv6[16]) {
if (memcmp(ipv6, pchIPv4, sizeof(pchIPv4)) == 0) {
m_net = NET_IPV4;
} else if (memcmp(ipv6, pchOnionCat, sizeof(pchOnionCat)) == 0) {
m_net = NET_ONION;
} else if (memcmp(ipv6, g_internal_prefix, sizeof(g_internal_prefix)) ==
0) {
m_net = NET_INTERNAL;
} else {
m_net = NET_IPV6;
}
memcpy(ip, ipv6, 16);
}

void CNetAddr::SetRaw(Network network, const uint8_t *ip_in) {
switch (network) {
case NET_IPV4:
m_net = NET_IPV4;
memcpy(ip, pchIPv4, 12);
memcpy(ip + 12, ip_in, 4);
break;
case NET_IPV6:
memcpy(ip, ip_in, 16);
SetLegacyIPv6(ip_in);
break;
default:
assert(!"invalid network");
Expand All @@ -61,6 +77,7 @@ bool CNetAddr::SetInternal(const std::string &name) {
if (name.empty()) {
return false;
}
m_net = NET_INTERNAL;
uint8_t hash[32] = {};
CSHA256().Write((const uint8_t *)name.data(), name.size()).Finalize(hash);
memcpy(ip, g_internal_prefix, sizeof(g_internal_prefix));
Expand All @@ -87,6 +104,7 @@ bool CNetAddr::SetSpecial(const std::string &strName) {
if (vchAddr.size() != 16 - sizeof(pchOnionCat)) {
return false;
}
m_net = NET_ONION;
memcpy(ip, pchOnionCat, sizeof(pchOnionCat));
for (unsigned int i = 0; i < 16 - sizeof(pchOnionCat); i++) {
ip[i + sizeof(pchOnionCat)] = vchAddr[i];
Expand Down Expand Up @@ -121,11 +139,11 @@ bool CNetAddr::IsBindAny() const {
}

bool CNetAddr::IsIPv4() const {
return (memcmp(ip, pchIPv4, sizeof(pchIPv4)) == 0);
return m_net == NET_IPV4;
}

bool CNetAddr::IsIPv6() const {
return !IsIPv4() && !IsTor() && !IsInternal();
return m_net == NET_IPV6;
}

bool CNetAddr::IsRFC1918() const {
Expand Down Expand Up @@ -156,48 +174,48 @@ bool CNetAddr::IsRFC5737() const {
}

bool CNetAddr::IsRFC3849() const {
return GetByte(15) == 0x20 && GetByte(14) == 0x01 && GetByte(13) == 0x0D &&
GetByte(12) == 0xB8;
return IsIPv6() && GetByte(15) == 0x20 && GetByte(14) == 0x01 &&
GetByte(13) == 0x0D && GetByte(12) == 0xB8;
}

bool CNetAddr::IsRFC3964() const {
return (GetByte(15) == 0x20 && GetByte(14) == 0x02);
return IsIPv6() && GetByte(15) == 0x20 && GetByte(14) == 0x02;
}

bool CNetAddr::IsRFC6052() const {
static const uint8_t pchRFC6052[] = {0, 0x64, 0xFF, 0x9B, 0, 0,
0, 0, 0, 0, 0, 0};
return (memcmp(ip, pchRFC6052, sizeof(pchRFC6052)) == 0);
return IsIPv6() && memcmp(ip, pchRFC6052, sizeof(pchRFC6052)) == 0;
}

bool CNetAddr::IsRFC4380() const {
return (GetByte(15) == 0x20 && GetByte(14) == 0x01 && GetByte(13) == 0 &&
GetByte(12) == 0);
return IsIPv6() && GetByte(15) == 0x20 && GetByte(14) == 0x01 &&
GetByte(13) == 0 && GetByte(12) == 0;
}

bool CNetAddr::IsRFC4862() const {
static const uint8_t pchRFC4862[] = {0xFE, 0x80, 0, 0, 0, 0, 0, 0};
return (memcmp(ip, pchRFC4862, sizeof(pchRFC4862)) == 0);
return IsIPv6() && memcmp(ip, pchRFC4862, sizeof(pchRFC4862)) == 0;
}

bool CNetAddr::IsRFC4193() const {
return ((GetByte(15) & 0xFE) == 0xFC);
return IsIPv6() && (GetByte(15) & 0xFE) == 0xFC;
}

bool CNetAddr::IsRFC6145() const {
static const uint8_t pchRFC6145[] = {0, 0, 0, 0, 0, 0,
0, 0, 0xFF, 0xFF, 0, 0};
return (memcmp(ip, pchRFC6145, sizeof(pchRFC6145)) == 0);
return IsIPv6() && memcmp(ip, pchRFC6145, sizeof(pchRFC6145)) == 0;
}

bool CNetAddr::IsRFC4843() const {
return (GetByte(15) == 0x20 && GetByte(14) == 0x01 && GetByte(13) == 0x00 &&
(GetByte(12) & 0xF0) == 0x10);
return IsIPv6() && GetByte(15) == 0x20 && GetByte(14) == 0x01 &&
GetByte(13) == 0x00 && (GetByte(12) & 0xF0) == 0x10;
}

bool CNetAddr::IsRFC7343() const {
return (GetByte(15) == 0x20 && GetByte(14) == 0x01 && GetByte(13) == 0x00 &&
(GetByte(12) & 0xF0) == 0x20);
return IsIPv6() && GetByte(15) == 0x20 && GetByte(14) == 0x01 &&
GetByte(13) == 0x00 && (GetByte(12) & 0xF0) == 0x20;
}

bool CNetAddr::IsHeNet() const {
Expand All @@ -212,7 +230,7 @@ bool CNetAddr::IsHeNet() const {
* @see CNetAddr::SetSpecial(const std::string &)
*/
bool CNetAddr::IsTor() const {
return (memcmp(ip, pchOnionCat, sizeof(pchOnionCat)) == 0);
return m_net == NET_ONION;
}

bool CNetAddr::IsLocal() const {
Expand All @@ -224,7 +242,7 @@ bool CNetAddr::IsLocal() const {
// IPv6 loopback (::1/128)
static const uint8_t pchLocal[16] = {0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1};
if (memcmp(ip, pchLocal, 16) == 0) {
if (IsIPv6() && memcmp(ip, pchLocal, 16) == 0) {
return true;
}

Expand All @@ -248,13 +266,13 @@ bool CNetAddr::IsValid() const {
// header20 vectorlen3 addr26 addr26 addr26 header20 vectorlen3 addr26
// addr26 addr26... so if the first length field is garbled, it reads the
// second batch of addr misaligned by 3 bytes.
if (memcmp(ip, pchIPv4 + 3, sizeof(pchIPv4) - 3) == 0) {
if (IsIPv6() && memcmp(ip, pchIPv4 + 3, sizeof(pchIPv4) - 3) == 0) {
return false;
}

// unspecified IPv6 address (::/128)
uint8_t ipNone6[16] = {};
if (memcmp(ip, ipNone6, 16) == 0) {
if (IsIPv6() && memcmp(ip, ipNone6, 16) == 0) {
return false;
}

Expand Down Expand Up @@ -306,7 +324,7 @@ bool CNetAddr::IsRoutable() const {
* @see CNetAddr::SetInternal(const std::string &)
*/
bool CNetAddr::IsInternal() const {
return memcmp(ip, g_internal_prefix, sizeof(g_internal_prefix)) == 0;
return m_net == NET_INTERNAL;
}

enum Network CNetAddr::GetNetwork() const {
Expand All @@ -318,15 +336,7 @@ enum Network CNetAddr::GetNetwork() const {
return NET_UNROUTABLE;
}

if (IsIPv4()) {
return NET_IPV4;
}

if (IsTor()) {
return NET_ONION;
}

return NET_IPV6;
return m_net;
}

std::string CNetAddr::ToStringIP() const {
Expand Down Expand Up @@ -366,11 +376,12 @@ std::string CNetAddr::ToString() const {
}

bool operator==(const CNetAddr &a, const CNetAddr &b) {
return (memcmp(a.ip, b.ip, 16) == 0);
return a.m_net == b.m_net && memcmp(a.ip, b.ip, 16) == 0;
}

bool operator<(const CNetAddr &a, const CNetAddr &b) {
return (memcmp(a.ip, b.ip, 16) < 0);
return a.m_net < b.m_net ||
(a.m_net == b.m_net && memcmp(a.ip, b.ip, 16) < 0);
}

/**
Expand Down Expand Up @@ -841,7 +852,7 @@ CSubNet::CSubNet(const CNetAddr &addr) : valid(addr.IsValid()) {
* the specified address belongs in this subnet.
*/
bool CSubNet::Match(const CNetAddr &addr) const {
if (!valid || !addr.IsValid()) {
if (!valid || !addr.IsValid() || network.m_net != addr.m_net) {
return false;
}
for (int x = 0; x < 16; ++x) {
Expand Down
56 changes: 54 additions & 2 deletions src/netaddress.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,49 @@
#include <string>
#include <vector>

/**
* A network type.
* @note An address may belong to more than one network, for example `10.0.0.1`
* belongs to both `NET_UNROUTABLE` and `NET_IPV4`.
* Keep these sequential starting from 0 and `NET_MAX` as the last entry.
* We have loops like `for (int i = 0; i < NET_MAX; i++)` that expect to iterate
* over all enum values and also `GetExtNetwork()` "extends" this enum by
* introducing standalone constants starting from `NET_MAX`.
*/
enum Network {
/// Addresses from these networks are not publicly routable on the global
/// Internet.
NET_UNROUTABLE = 0,

/// IPv4
NET_IPV4,

/// IPv6
NET_IPV6,

/// TORv2
NET_ONION,

/// A set of dummy addresses that map a name to an IPv6 address. These
/// addresses belong to RFC4193's fc00::/7 subnet (unique-local addresses).
/// We use them to map a string or FQDN to an IPv6 address in CAddrMan to
/// keep track of which DNS seeds were used.
NET_INTERNAL,

/// Dummy value to indicate the number of NET_* constants.
NET_MAX,
};

/** IP address (IPv6, or IPv4 using mapped IPv6 range (::FFFF:0:0/96)) */
/**
* Network address.
*/
class CNetAddr {
protected:
/**
* Network to which this address belongs.
*/
Network m_net{NET_IPV6};

// in network byte order
uint8_t ip[16];
// for scoped/link-local ipv6 addresses
Expand All @@ -39,6 +69,14 @@ class CNetAddr {
explicit CNetAddr(const struct in_addr &ipv4Addr);
void SetIP(const CNetAddr &ip);

/**
* Set from a legacy IPv6 address.
* Legacy IPv6 address may be a normal IPv6 address, or another address
* (e.g. IPv4) disguised as IPv6. This encoding is used in the legacy
* `addr` encoding.
*/
void SetLegacyIPv6(const uint8_t ipv6[16]);

/**
* Set raw IPv4 or IPv6 address (in network byte order)
* @note Only NET_IPV4 and NET_IPV6 are allowed for network.
Expand Down Expand Up @@ -127,7 +165,21 @@ class CNetAddr {
}
friend bool operator<(const CNetAddr &a, const CNetAddr &b);

SERIALIZE_METHODS(CNetAddr, obj) { READWRITE(obj.ip); }
/**
* Serialize to a stream.
*/
template <typename Stream> void Serialize(Stream &s) const { s << ip; }

/**
* Unserialize from a stream.
*/
template <typename Stream> void Unserialize(Stream &s) {
uint8_t ip_temp[sizeof(ip)];
s >> ip_temp;
// Use SetLegacyIPv6() so that m_net is set correctly. For example
// ::FFFF:0102:0304 should be set as m_net=NET_IPV4 (1.2.3.4).
SetLegacyIPv6(ip_temp);
}

friend class CSubNet;
};
Expand Down
13 changes: 11 additions & 2 deletions src/test/netbase_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ BOOST_AUTO_TEST_CASE(onioncat_test) {
BOOST_CHECK(addr1.IsRoutable());
}

BOOST_AUTO_TEST_CASE(embedded_test) {
CNetAddr addr1(ResolveIP("1.2.3.4"));
CNetAddr addr2(ResolveIP("::FFFF:0102:0304"));
BOOST_CHECK(addr2.IsIPv4());
BOOST_CHECK_EQUAL(addr1.ToString(), addr2.ToString());
}

BOOST_AUTO_TEST_CASE(subnet_test) {
BOOST_CHECK(ResolveSubNet("1.2.3.0/24") ==
ResolveSubNet("1.2.3.0/255.255.255.0"));
Expand All @@ -158,12 +165,14 @@ BOOST_AUTO_TEST_CASE(subnet_test) {
BOOST_CHECK(ResolveSubNet("1.2.2.1/24").Match(ResolveIP("1.2.2.4")));
BOOST_CHECK(ResolveSubNet("1.2.2.110/31").Match(ResolveIP("1.2.2.111")));
BOOST_CHECK(ResolveSubNet("1.2.2.20/26").Match(ResolveIP("1.2.2.63")));
// All-Matching IPv6 Matches arbitrary IPv4 and IPv6
// All-Matching IPv6 Matches arbitrary IPv6
BOOST_CHECK(ResolveSubNet("::/0").Match(ResolveIP("1:2:3:4:5:6:7:1234")));
// But not `::` or `0.0.0.0` because they are considered invalid addresses
BOOST_CHECK(!ResolveSubNet("::/0").Match(ResolveIP("::")));
BOOST_CHECK(!ResolveSubNet("::/0").Match(ResolveIP("0.0.0.0")));
BOOST_CHECK(ResolveSubNet("::/0").Match(ResolveIP("1.2.3.4")));
// Addresses from one network (IPv4) don't belong to subnets of another
// network (IPv6)
BOOST_CHECK(!ResolveSubNet("::/0").Match(ResolveIP("1.2.3.4")));
// All-Matching IPv4 does not Match IPv6
BOOST_CHECK(
!ResolveSubNet("0.0.0.0/0").Match(ResolveIP("1:2:3:4:5:6:7:1234")));
Expand Down

0 comments on commit b4c94da

Please sign in to comment.