Merge #19628: net: change CNetAddr::ip to have flexible size

102867c587 net: change CNetAddr::ip to have flexible size (Vasil Dimov)
1ea57ad674 net: don't accept non-left-contiguous netmasks (Vasil Dimov)

Pull request description:

  (chopped off from #19031 to ease review)

  Before this change `CNetAddr::ip` was a fixed-size array of 16 bytes,
  not being able to store larger addresses (e.g. TORv3) and encoded
  smaller ones as 16-byte IPv6 addresses.

  Change its type to `prevector`, so that it can hold larger addresses and
  do not disguise non-IPv6 addresses as IPv6. So the IPv4 address
  `1.2.3.4` is now encoded as `01020304` instead of
  `00000000000000000000FFFF01020304`.

  Rename `CNetAddr::ip` to `CNetAddr::m_addr` because it is not an "IP" or
  "IP address" (TOR addresses are not IP addresses).

  In order to preserve backward compatibility with serialization (where
  e.g. `1.2.3.4` is serialized as `00000000000000000000FFFF01020304`)
  introduce `CNetAddr` dedicated legacy serialize/unserialize methods.

  Adjust `CSubNet` accordingly. Still use `CSubNet::netmask[]` of fixed 16
  bytes, but use the first 4 for IPv4 (not the last 4). Do not accept
  invalid netmasks that have 0-bits followed by 1-bits and only allow
  subnetting for IPv4 and IPv6.

  Co-authored-by: Carl Dong <contact@carldong.me>

ACKs for top commit:
  sipa:
    utACK 102867c587
  MarcoFalke:
    Concept ACK 102867c587
  ryanofsky:
    Code review ACK 102867c587. Just many suggested updates since last review. Thanks for following up on everything!
  jonatack:
    re-ACK 102867c587 diff review, code review, build/tests/running bitcoind with ipv4/ipv6/onion peers
  kallewoof:
    ACK 102867c587

Tree-SHA512: d60bf716cecf8d3e8146d2f90f897ebe956befb16f711a24cfe680024c5afc758fb9e4a0a22066b42f7630d52cf916318bedbcbc069ae07092d5250a11e8f762
This commit is contained in:
MarcoFalke 2020-08-25 18:10:17 +02:00
commit 8d6224fefe
No known key found for this signature in database
GPG key ID: CE2B75697E69A548
11 changed files with 499 additions and 270 deletions

View file

@ -139,6 +139,10 @@ Updated settings
in future releases. Refer to the help of the affected settings `-whitebind`
and `-whitelist` for more details. (#19191)
- Netmasks that contain 1-bits after 0-bits (the 1-bits are not contiguous on
the left side, e.g. 255.0.255.255) are no longer accepted. They are invalid
according to RFC 4632.
Changes to Wallet or GUI related settings can be found in the GUI or Wallet section below.
Tools and Utilities

View file

@ -3,79 +3,90 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <cstdint>
#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 };
static const unsigned char pchOnionCat[] = {0xFD,0x87,0xD8,0x7E,0xEB,0x43};
#include <algorithm>
#include <array>
#include <cstdint>
#include <iterator>
#include <tuple>
// 0xFD + sha256("bitcoin")[0:5]
static const unsigned char g_internal_prefix[] = { 0xFD, 0x6B, 0x88, 0xC0, 0x87, 0x24 };
constexpr size_t CNetAddr::V1_SERIALIZATION_SIZE;
/**
* Construct an unspecified IPv6 network address (::/128).
*
* @note This address is considered invalid by CNetAddr::IsValid()
*/
CNetAddr::CNetAddr()
{
memset(ip, 0, sizeof(ip));
}
CNetAddr::CNetAddr() {}
void CNetAddr::SetIP(const CNetAddr& ipIn)
{
// Size check.
switch (ipIn.m_net) {
case NET_IPV4:
assert(ipIn.m_addr.size() == ADDR_IPV4_SIZE);
break;
case NET_IPV6:
assert(ipIn.m_addr.size() == ADDR_IPV6_SIZE);
break;
case NET_ONION:
assert(ipIn.m_addr.size() == ADDR_TORV2_SIZE);
break;
case NET_INTERNAL:
assert(ipIn.m_addr.size() == ADDR_INTERNAL_SIZE);
break;
case NET_UNROUTABLE:
case NET_MAX:
assert(false);
} // no default case, so the compiler can warn about missing cases
m_net = ipIn.m_net;
memcpy(ip, ipIn.ip, sizeof(ip));
m_addr = ipIn.m_addr;
}
void CNetAddr::SetLegacyIPv6(const uint8_t ipv6[16])
template <typename T1, size_t PREFIX_LEN>
inline bool HasPrefix(const T1& obj, const std::array<uint8_t, PREFIX_LEN>& prefix)
{
if (memcmp(ipv6, pchIPv4, sizeof(pchIPv4)) == 0) {
return obj.size() >= PREFIX_LEN &&
std::equal(std::begin(prefix), std::end(prefix), std::begin(obj));
}
void CNetAddr::SetLegacyIPv6(Span<const uint8_t> ipv6)
{
assert(ipv6.size() == ADDR_IPV6_SIZE);
size_t skip{0};
if (HasPrefix(ipv6, IPV4_IN_IPV6_PREFIX)) {
// IPv4-in-IPv6
m_net = NET_IPV4;
} else if (memcmp(ipv6, pchOnionCat, sizeof(pchOnionCat)) == 0) {
skip = sizeof(IPV4_IN_IPV6_PREFIX);
} else if (HasPrefix(ipv6, TORV2_IN_IPV6_PREFIX)) {
// TORv2-in-IPv6
m_net = NET_ONION;
} else if (memcmp(ipv6, g_internal_prefix, sizeof(g_internal_prefix)) == 0) {
skip = sizeof(TORV2_IN_IPV6_PREFIX);
} else if (HasPrefix(ipv6, INTERNAL_IN_IPV6_PREFIX)) {
// Internal-in-IPv6
m_net = NET_INTERNAL;
skip = sizeof(INTERNAL_IN_IPV6_PREFIX);
} else {
// IPv6
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:
SetLegacyIPv6(ip_in);
break;
default:
assert(!"invalid network");
}
m_addr.assign(ipv6.begin() + skip, ipv6.end());
}
/**
* Try to make this a dummy address that maps the specified name into IPv6 like
* so: (0xFD + %sha256("bitcoin")[0:5]) + %sha256(name)[0:10]. Such dummy
* addresses have a prefix of fd6b:88c0:8724::/48 and are guaranteed to not be
* publicly routable as it falls under RFC4193's fc00::/7 subnet allocated to
* unique-local addresses.
*
* CAddrMan uses these fake addresses to keep track of which DNS seeds were
* used.
*
* Create an "internal" address that represents a name or FQDN. CAddrMan uses
* these fake addresses to keep track of which DNS seeds were used.
* @returns Whether or not the operation was successful.
*
* @see CNetAddr::IsInternal(), CNetAddr::IsRFC4193()
* @see NET_INTERNAL, INTERNAL_IN_IPV6_PREFIX, CNetAddr::IsInternal(), CNetAddr::IsRFC4193()
*/
bool CNetAddr::SetInternal(const std::string &name)
{
@ -85,31 +96,26 @@ bool CNetAddr::SetInternal(const std::string &name)
m_net = NET_INTERNAL;
unsigned char hash[32] = {};
CSHA256().Write((const unsigned char*)name.data(), name.size()).Finalize(hash);
memcpy(ip, g_internal_prefix, sizeof(g_internal_prefix));
memcpy(ip + sizeof(g_internal_prefix), hash, sizeof(ip) - sizeof(g_internal_prefix));
m_addr.assign(hash, hash + ADDR_INTERNAL_SIZE);
return true;
}
/**
* Try to make this a dummy address that maps the specified onion address into
* IPv6 using OnionCat's range and encoding. Such dummy addresses have a prefix
* of fd87:d87e:eb43::/48 and are guaranteed to not be publicly routable as they
* fall under RFC4193's fc00::/7 subnet allocated to unique-local addresses.
* Parse a TORv2 address and set this object to it.
*
* @returns Whether or not the operation was successful.
*
* @see CNetAddr::IsTor(), CNetAddr::IsRFC4193()
* @see CNetAddr::IsTor()
*/
bool CNetAddr::SetSpecial(const std::string &strName)
{
if (strName.size()>6 && strName.substr(strName.size() - 6, 6) == ".onion") {
std::vector<unsigned char> vchAddr = DecodeBase32(strName.substr(0, strName.size() - 6).c_str());
if (vchAddr.size() != 16-sizeof(pchOnionCat))
if (vchAddr.size() != ADDR_TORV2_SIZE) {
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];
m_addr.assign(vchAddr.begin(), vchAddr.end());
return true;
}
return false;
@ -117,28 +123,23 @@ bool CNetAddr::SetSpecial(const std::string &strName)
CNetAddr::CNetAddr(const struct in_addr& ipv4Addr)
{
SetRaw(NET_IPV4, (const uint8_t*)&ipv4Addr);
m_net = NET_IPV4;
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&ipv4Addr);
m_addr.assign(ptr, ptr + ADDR_IPV4_SIZE);
}
CNetAddr::CNetAddr(const struct in6_addr& ipv6Addr, const uint32_t scope)
{
SetRaw(NET_IPV6, (const uint8_t*)&ipv6Addr);
SetLegacyIPv6(Span<const uint8_t>(reinterpret_cast<const uint8_t*>(&ipv6Addr), sizeof(ipv6Addr)));
scopeId = scope;
}
unsigned int CNetAddr::GetByte(int n) const
{
return ip[15-n];
}
bool CNetAddr::IsBindAny() const
{
const int cmplen = IsIPv4() ? 4 : 16;
for (int i = 0; i < cmplen; ++i) {
if (GetByte(i)) return false;
if (!IsIPv4() && !IsIPv6()) {
return false;
}
return true;
return std::all_of(m_addr.begin(), m_addr.end(), [](uint8_t b) { return b == 0; });
}
bool CNetAddr::IsIPv4() const { return m_net == NET_IPV4; }
@ -148,88 +149,88 @@ bool CNetAddr::IsIPv6() const { return m_net == NET_IPV6; }
bool CNetAddr::IsRFC1918() const
{
return IsIPv4() && (
GetByte(3) == 10 ||
(GetByte(3) == 192 && GetByte(2) == 168) ||
(GetByte(3) == 172 && (GetByte(2) >= 16 && GetByte(2) <= 31)));
m_addr[0] == 10 ||
(m_addr[0] == 192 && m_addr[1] == 168) ||
(m_addr[0] == 172 && m_addr[1] >= 16 && m_addr[1] <= 31));
}
bool CNetAddr::IsRFC2544() const
{
return IsIPv4() && GetByte(3) == 198 && (GetByte(2) == 18 || GetByte(2) == 19);
return IsIPv4() && m_addr[0] == 198 && (m_addr[1] == 18 || m_addr[1] == 19);
}
bool CNetAddr::IsRFC3927() const
{
return IsIPv4() && (GetByte(3) == 169 && GetByte(2) == 254);
return IsIPv4() && HasPrefix(m_addr, std::array<uint8_t, 2>{169, 254});
}
bool CNetAddr::IsRFC6598() const
{
return IsIPv4() && GetByte(3) == 100 && GetByte(2) >= 64 && GetByte(2) <= 127;
return IsIPv4() && m_addr[0] == 100 && m_addr[1] >= 64 && m_addr[1] <= 127;
}
bool CNetAddr::IsRFC5737() const
{
return IsIPv4() && ((GetByte(3) == 192 && GetByte(2) == 0 && GetByte(1) == 2) ||
(GetByte(3) == 198 && GetByte(2) == 51 && GetByte(1) == 100) ||
(GetByte(3) == 203 && GetByte(2) == 0 && GetByte(1) == 113));
return IsIPv4() && (HasPrefix(m_addr, std::array<uint8_t, 3>{192, 0, 2}) ||
HasPrefix(m_addr, std::array<uint8_t, 3>{198, 51, 100}) ||
HasPrefix(m_addr, std::array<uint8_t, 3>{203, 0, 113}));
}
bool CNetAddr::IsRFC3849() const
{
return IsIPv6() && GetByte(15) == 0x20 && GetByte(14) == 0x01 &&
GetByte(13) == 0x0D && GetByte(12) == 0xB8;
return IsIPv6() && HasPrefix(m_addr, std::array<uint8_t, 4>{0x20, 0x01, 0x0D, 0xB8});
}
bool CNetAddr::IsRFC3964() const
{
return IsIPv6() && GetByte(15) == 0x20 && GetByte(14) == 0x02;
return IsIPv6() && HasPrefix(m_addr, std::array<uint8_t, 2>{0x20, 0x02});
}
bool CNetAddr::IsRFC6052() const
{
static const unsigned char pchRFC6052[] = {0,0x64,0xFF,0x9B,0,0,0,0,0,0,0,0};
return IsIPv6() && memcmp(ip, pchRFC6052, sizeof(pchRFC6052)) == 0;
return IsIPv6() &&
HasPrefix(m_addr, std::array<uint8_t, 12>{0x00, 0x64, 0xFF, 0x9B, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
}
bool CNetAddr::IsRFC4380() const
{
return IsIPv6() && GetByte(15) == 0x20 && GetByte(14) == 0x01 && GetByte(13) == 0 &&
GetByte(12) == 0;
return IsIPv6() && HasPrefix(m_addr, std::array<uint8_t, 4>{0x20, 0x01, 0x00, 0x00});
}
bool CNetAddr::IsRFC4862() const
{
static const unsigned char pchRFC4862[] = {0xFE,0x80,0,0,0,0,0,0};
return IsIPv6() && memcmp(ip, pchRFC4862, sizeof(pchRFC4862)) == 0;
return IsIPv6() && HasPrefix(m_addr, std::array<uint8_t, 8>{0xFE, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00});
}
bool CNetAddr::IsRFC4193() const
{
return IsIPv6() && (GetByte(15) & 0xFE) == 0xFC;
return IsIPv6() && (m_addr[0] & 0xFE) == 0xFC;
}
bool CNetAddr::IsRFC6145() const
{
static const unsigned char pchRFC6145[] = {0,0,0,0,0,0,0,0,0xFF,0xFF,0,0};
return IsIPv6() && memcmp(ip, pchRFC6145, sizeof(pchRFC6145)) == 0;
return IsIPv6() &&
HasPrefix(m_addr, std::array<uint8_t, 12>{0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00});
}
bool CNetAddr::IsRFC4843() const
{
return IsIPv6() && GetByte(15) == 0x20 && GetByte(14) == 0x01 &&
GetByte(13) == 0x00 && (GetByte(12) & 0xF0) == 0x10;
return IsIPv6() && HasPrefix(m_addr, std::array<uint8_t, 3>{0x20, 0x01, 0x00}) &&
(m_addr[3] & 0xF0) == 0x10;
}
bool CNetAddr::IsRFC7343() const
{
return IsIPv6() && GetByte(15) == 0x20 && GetByte(14) == 0x01 &&
GetByte(13) == 0x00 && (GetByte(12) & 0xF0) == 0x20;
return IsIPv6() && HasPrefix(m_addr, std::array<uint8_t, 3>{0x20, 0x01, 0x00}) &&
(m_addr[3] & 0xF0) == 0x20;
}
bool CNetAddr::IsHeNet() const
{
return (GetByte(15) == 0x20 && GetByte(14) == 0x01 && GetByte(13) == 0x04 && GetByte(12) == 0x70);
return IsIPv6() && HasPrefix(m_addr, std::array<uint8_t, 4>{0x20, 0x01, 0x04, 0x70});
}
/**
@ -243,13 +244,15 @@ bool CNetAddr::IsTor() const { return m_net == NET_ONION; }
bool CNetAddr::IsLocal() const
{
// IPv4 loopback (127.0.0.0/8 or 0.0.0.0/8)
if (IsIPv4() && (GetByte(3) == 127 || GetByte(3) == 0))
if (IsIPv4() && (m_addr[0] == 127 || m_addr[0] == 0)) {
return true;
}
// IPv6 loopback (::1/128)
static const unsigned char pchLocal[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
if (IsIPv6() && memcmp(ip, pchLocal, 16) == 0)
if (IsIPv6() && memcmp(m_addr.data(), pchLocal, sizeof(pchLocal)) == 0) {
return true;
}
return false;
}
@ -272,13 +275,16 @@ 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 (IsIPv6() && memcmp(ip, pchIPv4+3, sizeof(pchIPv4)-3) == 0)
if (IsIPv6() && memcmp(m_addr.data(), IPV4_IN_IPV6_PREFIX.data() + 3,
sizeof(IPV4_IN_IPV6_PREFIX) - 3) == 0) {
return false;
}
// unspecified IPv6 address (::/128)
unsigned char ipNone6[16] = {};
if (IsIPv6() && memcmp(ip, ipNone6, 16) == 0)
if (IsIPv6() && memcmp(m_addr.data(), ipNone6, sizeof(ipNone6)) == 0) {
return false;
}
// documentation IPv6 address
if (IsRFC3849())
@ -287,17 +293,11 @@ bool CNetAddr::IsValid() const
if (IsInternal())
return false;
if (IsIPv4())
{
// INADDR_NONE
uint32_t ipNone = INADDR_NONE;
if (memcmp(ip+12, &ipNone, 4) == 0)
return false;
// 0
ipNone = 0;
if (memcmp(ip+12, &ipNone, 4) == 0)
if (IsIPv4()) {
const uint32_t addr = ReadBE32(m_addr.data());
if (addr == INADDR_ANY || addr == INADDR_NONE) {
return false;
}
}
return true;
@ -318,7 +318,7 @@ bool CNetAddr::IsRoutable() const
}
/**
* @returns Whether or not this is a dummy address that maps a name into IPv6.
* @returns Whether or not this is a dummy address that represents a name.
*
* @see CNetAddr::SetInternal(const std::string &)
*/
@ -341,9 +341,9 @@ enum Network CNetAddr::GetNetwork() const
std::string CNetAddr::ToStringIP() const
{
if (IsTor())
return EncodeBase32(&ip[6], 10) + ".onion";
return EncodeBase32(m_addr.data(), m_addr.size()) + ".onion";
if (IsInternal())
return EncodeBase32(ip + sizeof(g_internal_prefix), sizeof(ip) - sizeof(g_internal_prefix)) + ".internal";
return EncodeBase32(m_addr.data(), m_addr.size()) + ".internal";
CService serv(*this, 0);
struct sockaddr_storage sockaddr;
socklen_t socklen = sizeof(sockaddr);
@ -353,13 +353,13 @@ std::string CNetAddr::ToStringIP() const
return std::string(name);
}
if (IsIPv4())
return strprintf("%u.%u.%u.%u", GetByte(3), GetByte(2), GetByte(1), GetByte(0));
else
return strprintf("%x:%x:%x:%x:%x:%x:%x:%x",
GetByte(15) << 8 | GetByte(14), GetByte(13) << 8 | GetByte(12),
GetByte(11) << 8 | GetByte(10), GetByte(9) << 8 | GetByte(8),
GetByte(7) << 8 | GetByte(6), GetByte(5) << 8 | GetByte(4),
GetByte(3) << 8 | GetByte(2), GetByte(1) << 8 | GetByte(0));
return strprintf("%u.%u.%u.%u", m_addr[0], m_addr[1], m_addr[2], m_addr[3]);
assert(IsIPv6());
return strprintf("%x:%x:%x:%x:%x:%x:%x:%x",
m_addr[0] << 8 | m_addr[1], m_addr[2] << 8 | m_addr[3],
m_addr[4] << 8 | m_addr[5], m_addr[6] << 8 | m_addr[7],
m_addr[8] << 8 | m_addr[9], m_addr[10] << 8 | m_addr[11],
m_addr[12] << 8 | m_addr[13], m_addr[14] << 8 | m_addr[15]);
}
std::string CNetAddr::ToString() const
@ -369,12 +369,12 @@ std::string CNetAddr::ToString() const
bool operator==(const CNetAddr& a, const CNetAddr& b)
{
return a.m_net == b.m_net && memcmp(a.ip, b.ip, 16) == 0;
return a.m_net == b.m_net && a.m_addr == b.m_addr;
}
bool operator<(const CNetAddr& a, const CNetAddr& b)
{
return a.m_net < b.m_net || (a.m_net == b.m_net && memcmp(a.ip, b.ip, 16) < 0);
return std::tie(a.m_net, a.m_addr) < std::tie(b.m_net, b.m_addr);
}
/**
@ -391,7 +391,8 @@ bool CNetAddr::GetInAddr(struct in_addr* pipv4Addr) const
{
if (!IsIPv4())
return false;
memcpy(pipv4Addr, ip+12, 4);
assert(sizeof(*pipv4Addr) == m_addr.size());
memcpy(pipv4Addr, m_addr.data(), m_addr.size());
return true;
}
@ -410,7 +411,8 @@ bool CNetAddr::GetIn6Addr(struct in6_addr* pipv6Addr) const
if (!IsIPv6()) {
return false;
}
memcpy(pipv6Addr, ip, 16);
assert(sizeof(*pipv6Addr) == m_addr.size());
memcpy(pipv6Addr, m_addr.data(), m_addr.size());
return true;
}
@ -421,15 +423,17 @@ bool CNetAddr::HasLinkedIPv4() const
uint32_t CNetAddr::GetLinkedIPv4() const
{
if (IsIPv4() || IsRFC6145() || IsRFC6052()) {
// IPv4, mapped IPv4, SIIT translated IPv4: the IPv4 address is the last 4 bytes of the address
return ReadBE32(ip + 12);
if (IsIPv4()) {
return ReadBE32(m_addr.data());
} else if (IsRFC6052() || IsRFC6145()) {
// mapped IPv4, SIIT translated IPv4: the IPv4 address is the last 4 bytes of the address
return ReadBE32(MakeSpan(m_addr).last(ADDR_IPV4_SIZE).data());
} else if (IsRFC3964()) {
// 6to4 tunneled IPv4: the IPv4 address is in bytes 2-6
return ReadBE32(ip + 2);
return ReadBE32(MakeSpan(m_addr).subspan(2, ADDR_IPV4_SIZE).data());
} else if (IsRFC4380()) {
// Teredo tunneled IPv4: the IPv4 address is in the last 4 bytes of the address, but bitflipped
return ~ReadBE32(ip + 12);
return ~ReadBE32(MakeSpan(m_addr).last(ADDR_IPV4_SIZE).data());
}
assert(false);
}
@ -458,10 +462,10 @@ uint32_t CNetAddr::GetMappedAS(const std::vector<bool> &asmap) const {
}
std::vector<bool> ip_bits(128);
if (HasLinkedIPv4()) {
// For lookup, treat as if it was just an IPv4 address (pchIPv4 prefix + IPv4 bits)
// For lookup, treat as if it was just an IPv4 address (IPV4_IN_IPV6_PREFIX + IPv4 bits)
for (int8_t byte_i = 0; byte_i < 12; ++byte_i) {
for (uint8_t bit_i = 0; bit_i < 8; ++bit_i) {
ip_bits[byte_i * 8 + bit_i] = (pchIPv4[byte_i] >> (7 - bit_i)) & 1;
ip_bits[byte_i * 8 + bit_i] = (IPV4_IN_IPV6_PREFIX[byte_i] >> (7 - bit_i)) & 1;
}
}
uint32_t ipv4 = GetLinkedIPv4();
@ -470,8 +474,9 @@ uint32_t CNetAddr::GetMappedAS(const std::vector<bool> &asmap) const {
}
} else {
// Use all 128 bits of the IPv6 address otherwise
assert(IsIPv6());
for (int8_t byte_i = 0; byte_i < 16; ++byte_i) {
uint8_t cur_byte = GetByte(15 - byte_i);
uint8_t cur_byte = m_addr[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;
}
@ -507,19 +512,15 @@ std::vector<unsigned char> CNetAddr::GetGroup(const std::vector<bool> &asmap) co
}
vchRet.push_back(net_class);
int nStartByte = 0;
int nBits = 16;
int nBits{0};
if (IsLocal()) {
// all local addresses belong to the same group
nBits = 0;
} else if (IsInternal()) {
// all internal-usage addresses get their own group
nStartByte = sizeof(g_internal_prefix);
nBits = (sizeof(ip) - sizeof(g_internal_prefix)) * 8;
nBits = ADDR_INTERNAL_SIZE * 8;
} else if (!IsRoutable()) {
// all other unroutable addresses belong to the same group
nBits = 0;
} else if (HasLinkedIPv4()) {
// IPv4 addresses (and mapped IPv4 addresses) use /16 groups
uint32_t ipv4 = GetLinkedIPv4();
@ -527,7 +528,6 @@ std::vector<unsigned char> CNetAddr::GetGroup(const std::vector<bool> &asmap) co
vchRet.push_back((ipv4 >> 16) & 0xFF);
return vchRet;
} else if (IsTor()) {
nStartByte = 6;
nBits = 4;
} else if (IsHeNet()) {
// for he.net, use /36 groups
@ -537,23 +537,29 @@ std::vector<unsigned char> CNetAddr::GetGroup(const std::vector<bool> &asmap) co
nBits = 32;
}
// push our ip onto vchRet byte by byte...
while (nBits >= 8)
{
vchRet.push_back(GetByte(15 - nStartByte));
nStartByte++;
nBits -= 8;
}
// Push our address onto vchRet.
const size_t num_bytes = nBits / 8;
vchRet.insert(vchRet.end(), m_addr.begin(), m_addr.begin() + num_bytes);
nBits %= 8;
// ...for the last byte, push nBits and for the rest of the byte push 1's
if (nBits > 0)
vchRet.push_back(GetByte(15 - nStartByte) | ((1 << (8 - nBits)) - 1));
if (nBits > 0) {
assert(num_bytes < m_addr.size());
vchRet.push_back(m_addr[num_bytes] | ((1 << (8 - nBits)) - 1));
}
return vchRet;
}
std::vector<unsigned char> CNetAddr::GetAddrBytes() const
{
uint8_t serialized[V1_SERIALIZATION_SIZE];
SerializeV1Array(serialized);
return {std::begin(serialized), std::end(serialized)};
}
uint64_t CNetAddr::GetHash() const
{
uint256 hash = Hash(ip);
uint256 hash = Hash(m_addr);
uint64_t nRet;
memcpy(&nRet, &hash, sizeof(nRet));
return nRet;
@ -764,68 +770,25 @@ CSubNet::CSubNet():
memset(netmask, 0, sizeof(netmask));
}
CSubNet::CSubNet(const CNetAddr &addr, int32_t mask)
CSubNet::CSubNet(const CNetAddr& addr, uint8_t mask) : CSubNet()
{
valid = true;
valid = (addr.IsIPv4() && mask <= ADDR_IPV4_SIZE * 8) ||
(addr.IsIPv6() && mask <= ADDR_IPV6_SIZE * 8);
if (!valid) {
return;
}
assert(mask <= sizeof(netmask) * 8);
network = addr;
// Default to /32 (IPv4) or /128 (IPv6), i.e. match single address
memset(netmask, 255, sizeof(netmask));
// IPv4 addresses start at offset 12, and first 12 bytes must match, so just offset n
const int astartofs = network.IsIPv4() ? 12 : 0;
int32_t n = mask;
if(n >= 0 && n <= (128 - astartofs*8)) // Only valid if in range of bits of address
{
n += astartofs*8;
// Clear bits [n..127]
for (; n < 128; ++n)
netmask[n>>3] &= ~(1<<(7-(n&7)));
} else
valid = false;
// Normalize network according to netmask
for(int x=0; x<16; ++x)
network.ip[x] &= netmask[x];
}
CSubNet::CSubNet(const CNetAddr &addr, const CNetAddr &mask)
{
valid = true;
network = addr;
// Default to /32 (IPv4) or /128 (IPv6), i.e. match single address
memset(netmask, 255, sizeof(netmask));
// IPv4 addresses start at offset 12, and first 12 bytes must match, so just offset n
const int astartofs = network.IsIPv4() ? 12 : 0;
for(int x=astartofs; x<16; ++x)
netmask[x] = mask.ip[x];
// Normalize network according to netmask
for(int x=0; x<16; ++x)
network.ip[x] &= netmask[x];
}
CSubNet::CSubNet(const CNetAddr &addr):
valid(addr.IsValid())
{
memset(netmask, 255, sizeof(netmask));
network = addr;
}
/**
* @returns True if this subnet is valid, the specified address is valid, and
* the specified address belongs in this subnet.
*/
bool CSubNet::Match(const CNetAddr &addr) const
{
if (!valid || !addr.IsValid() || network.m_net != addr.m_net)
return false;
for(int x=0; x<16; ++x)
if ((addr.ip[x] & netmask[x]) != network.ip[x])
return false;
return true;
uint8_t n = mask;
for (size_t i = 0; i < network.m_addr.size(); ++i) {
const uint8_t bits = n < 8 ? n : 8;
netmask[i] = (uint8_t)((uint8_t)0xFF << (8 - bits)); // Set first bits.
network.m_addr[i] &= netmask[i]; // Normalize network according to netmask.
n -= bits;
}
}
/**
@ -848,42 +811,82 @@ static inline int NetmaskBits(uint8_t x)
}
}
CSubNet::CSubNet(const CNetAddr& addr, const CNetAddr& mask) : CSubNet()
{
valid = (addr.IsIPv4() || addr.IsIPv6()) && addr.m_net == mask.m_net;
if (!valid) {
return;
}
// Check if `mask` contains 1-bits after 0-bits (which is an invalid netmask).
bool zeros_found = false;
for (auto b : mask.m_addr) {
const int num_bits = NetmaskBits(b);
if (num_bits == -1 || (zeros_found && num_bits != 0)) {
valid = false;
return;
}
if (num_bits < 8) {
zeros_found = true;
}
}
assert(mask.m_addr.size() <= sizeof(netmask));
memcpy(netmask, mask.m_addr.data(), mask.m_addr.size());
network = addr;
// Normalize network according to netmask
for (size_t x = 0; x < network.m_addr.size(); ++x) {
network.m_addr[x] &= netmask[x];
}
}
CSubNet::CSubNet(const CNetAddr& addr) : CSubNet()
{
valid = addr.IsIPv4() || addr.IsIPv6();
if (!valid) {
return;
}
assert(addr.m_addr.size() <= sizeof(netmask));
memset(netmask, 0xFF, addr.m_addr.size());
network = addr;
}
/**
* @returns True if this subnet is valid, the specified address is valid, and
* the specified address belongs in this subnet.
*/
bool CSubNet::Match(const CNetAddr &addr) const
{
if (!valid || !addr.IsValid() || network.m_net != addr.m_net)
return false;
assert(network.m_addr.size() == addr.m_addr.size());
for (size_t x = 0; x < addr.m_addr.size(); ++x) {
if ((addr.m_addr[x] & netmask[x]) != network.m_addr[x]) {
return false;
}
}
return true;
}
std::string CSubNet::ToString() const
{
/* Parse binary 1{n}0{N-n} to see if mask can be represented as /n */
int cidr = 0;
bool valid_cidr = true;
int n = network.IsIPv4() ? 12 : 0;
for (; n < 16 && netmask[n] == 0xff; ++n)
cidr += 8;
if (n < 16) {
int bits = NetmaskBits(netmask[n]);
if (bits < 0)
valid_cidr = false;
else
cidr += bits;
++n;
}
for (; n < 16 && valid_cidr; ++n)
if (netmask[n] != 0x00)
valid_cidr = false;
assert(network.m_addr.size() <= sizeof(netmask));
/* Format output */
std::string strNetmask;
if (valid_cidr) {
strNetmask = strprintf("%u", cidr);
} else {
if (network.IsIPv4())
strNetmask = strprintf("%u.%u.%u.%u", netmask[12], netmask[13], netmask[14], netmask[15]);
else
strNetmask = strprintf("%x:%x:%x:%x:%x:%x:%x:%x",
netmask[0] << 8 | netmask[1], netmask[2] << 8 | netmask[3],
netmask[4] << 8 | netmask[5], netmask[6] << 8 | netmask[7],
netmask[8] << 8 | netmask[9], netmask[10] << 8 | netmask[11],
netmask[12] << 8 | netmask[13], netmask[14] << 8 | netmask[15]);
uint8_t cidr = 0;
for (size_t i = 0; i < network.m_addr.size(); ++i) {
if (netmask[i] == 0x00) {
break;
}
cidr += NetmaskBits(netmask[i]);
}
return network.ToString() + "/" + strNetmask;
return network.ToString() + strprintf("/%u", cidr);
}
bool CSubNet::IsValid() const

View file

@ -9,9 +9,12 @@
#include <config/bitcoin-config.h>
#endif
#include <attributes.h>
#include <compat.h>
#include <prevector.h>
#include <serialize.h>
#include <array>
#include <cstdint>
#include <string>
#include <vector>
@ -39,28 +42,66 @@ enum Network
/// 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.
/// A set of addresses that represent the hash of a string or FQDN. We use
/// them in CAddrMan to keep track of which DNS seeds were used.
NET_INTERNAL,
/// Dummy value to indicate the number of NET_* constants.
NET_MAX,
};
/// Prefix of an IPv6 address when it contains an embedded IPv4 address.
/// Used when (un)serializing addresses in ADDRv1 format (pre-BIP155).
static const std::array<uint8_t, 12> IPV4_IN_IPV6_PREFIX{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF
};
/// Prefix of an IPv6 address when it contains an embedded TORv2 address.
/// Used when (un)serializing addresses in ADDRv1 format (pre-BIP155).
/// Such dummy IPv6 addresses are guaranteed to not be publicly routable as they
/// fall under RFC4193's fc00::/7 subnet allocated to unique-local addresses.
static const std::array<uint8_t, 6> TORV2_IN_IPV6_PREFIX{
0xFD, 0x87, 0xD8, 0x7E, 0xEB, 0x43
};
/// Prefix of an IPv6 address when it contains an embedded "internal" address.
/// Used when (un)serializing addresses in ADDRv1 format (pre-BIP155).
/// The prefix comes from 0xFD + SHA256("bitcoin")[0:5].
/// Such dummy IPv6 addresses are guaranteed to not be publicly routable as they
/// fall under RFC4193's fc00::/7 subnet allocated to unique-local addresses.
static const std::array<uint8_t, 6> INTERNAL_IN_IPV6_PREFIX{
0xFD, 0x6B, 0x88, 0xC0, 0x87, 0x24 // 0xFD + sha256("bitcoin")[0:5].
};
/// Size of IPv4 address (in bytes).
static constexpr size_t ADDR_IPV4_SIZE = 4;
/// Size of IPv6 address (in bytes).
static constexpr size_t ADDR_IPV6_SIZE = 16;
/// Size of TORv2 address (in bytes).
static constexpr size_t ADDR_TORV2_SIZE = 10;
/// Size of "internal" (NET_INTERNAL) address (in bytes).
static constexpr size_t ADDR_INTERNAL_SIZE = 10;
/**
* Network address.
*/
class CNetAddr
{
protected:
/**
* Raw representation of the network address.
* In network byte order (big endian) for IPv4 and IPv6.
*/
prevector<ADDR_IPV6_SIZE, uint8_t> m_addr{ADDR_IPV6_SIZE, 0x0};
/**
* Network to which this address belongs.
*/
Network m_net{NET_IPV6};
unsigned char ip[16]; // in network byte order
uint32_t scopeId{0}; // for scoped/link-local ipv6 addresses
public:
@ -74,13 +115,7 @@ class CNetAddr
* (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.
*/
void SetRaw(Network network, const uint8_t *data);
void SetLegacyIPv6(Span<const uint8_t> ipv6);
bool SetInternal(const std::string& name);
@ -111,7 +146,6 @@ class CNetAddr
enum Network GetNetwork() const;
std::string ToString() const;
std::string ToStringIP() const;
unsigned int GetByte(int n) const;
uint64_t GetHash() const;
bool GetInAddr(struct in_addr* pipv4Addr) const;
uint32_t GetNetClass() const;
@ -127,7 +161,7 @@ class CNetAddr
uint32_t GetMappedAS(const std::vector<bool> &asmap) const;
std::vector<unsigned char> GetGroup(const std::vector<bool> &asmap) const;
std::vector<unsigned char> GetAddrBytes() const { return {std::begin(ip), std::end(ip)}; }
std::vector<unsigned char> GetAddrBytes() const;
int GetReachabilityFrom(const CNetAddr *paddrPartner = nullptr) const;
explicit CNetAddr(const struct in6_addr& pipv6Addr, const uint32_t scope = 0);
@ -143,7 +177,7 @@ class CNetAddr
template <typename Stream>
void Serialize(Stream& s) const
{
s << ip;
SerializeV1Stream(s);
}
/**
@ -152,14 +186,92 @@ class CNetAddr
template <typename Stream>
void Unserialize(Stream& s)
{
unsigned char 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);
UnserializeV1Stream(s);
}
friend class CSubNet;
private:
/**
* Size of CNetAddr when serialized as ADDRv1 (pre-BIP155) (in bytes).
*/
static constexpr size_t V1_SERIALIZATION_SIZE = ADDR_IPV6_SIZE;
/**
* Serialize in pre-ADDRv2/BIP155 format to an array.
* Some addresses (e.g. TORv3) cannot be serialized in pre-BIP155 format.
*/
void SerializeV1Array(uint8_t (&arr)[V1_SERIALIZATION_SIZE]) const
{
size_t prefix_size;
switch (m_net) {
case NET_IPV6:
assert(m_addr.size() == sizeof(arr));
memcpy(arr, m_addr.data(), m_addr.size());
return;
case NET_IPV4:
prefix_size = sizeof(IPV4_IN_IPV6_PREFIX);
assert(prefix_size + m_addr.size() == sizeof(arr));
memcpy(arr, IPV4_IN_IPV6_PREFIX.data(), prefix_size);
memcpy(arr + prefix_size, m_addr.data(), m_addr.size());
return;
case NET_ONION:
prefix_size = sizeof(TORV2_IN_IPV6_PREFIX);
assert(prefix_size + m_addr.size() == sizeof(arr));
memcpy(arr, TORV2_IN_IPV6_PREFIX.data(), prefix_size);
memcpy(arr + prefix_size, m_addr.data(), m_addr.size());
return;
case NET_INTERNAL:
prefix_size = sizeof(INTERNAL_IN_IPV6_PREFIX);
assert(prefix_size + m_addr.size() == sizeof(arr));
memcpy(arr, INTERNAL_IN_IPV6_PREFIX.data(), prefix_size);
memcpy(arr + prefix_size, m_addr.data(), m_addr.size());
return;
case NET_UNROUTABLE:
case NET_MAX:
assert(false);
} // no default case, so the compiler can warn about missing cases
assert(false);
}
/**
* Serialize in pre-ADDRv2/BIP155 format to a stream.
* Some addresses (e.g. TORv3) cannot be serialized in pre-BIP155 format.
*/
template <typename Stream>
void SerializeV1Stream(Stream& s) const
{
uint8_t serialized[V1_SERIALIZATION_SIZE];
SerializeV1Array(serialized);
s << serialized;
}
/**
* Unserialize from a pre-ADDRv2/BIP155 format from an array.
*/
void UnserializeV1Array(uint8_t (&arr)[V1_SERIALIZATION_SIZE])
{
// 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(arr);
}
/**
* Unserialize from a pre-ADDRv2/BIP155 format from a stream.
*/
template <typename Stream>
void UnserializeV1Stream(Stream& s)
{
uint8_t serialized[V1_SERIALIZATION_SIZE];
s >> serialized;
UnserializeV1Array(serialized);
}
};
class CSubNet
@ -174,11 +286,11 @@ class CSubNet
public:
CSubNet();
CSubNet(const CNetAddr &addr, int32_t mask);
CSubNet(const CNetAddr &addr, const CNetAddr &mask);
CSubNet(const CNetAddr& addr, uint8_t mask);
CSubNet(const CNetAddr& addr, const CNetAddr& mask);
//constructor for single ip subnet (<ipv4>/32 or <ipv6>/128)
explicit CSubNet(const CNetAddr &addr);
explicit CSubNet(const CNetAddr& addr);
bool Match(const CNetAddr &addr) const;

View file

@ -13,6 +13,7 @@
#include <atomic>
#include <cstdint>
#include <limits>
#ifndef WIN32
#include <fcntl.h>
@ -838,8 +839,8 @@ bool LookupSubNet(const std::string& strSubnet, CSubNet& ret)
if (slash != strSubnet.npos)
{
std::string strNetmask = strSubnet.substr(slash + 1);
int32_t n;
if (ParseInt32(strNetmask, &n)) {
uint8_t n;
if (ParseUInt8(strNetmask, &n)) {
// If valid number, assume CIDR variable-length subnet masking
ret = CSubNet(network, n);
return ret.IsValid();

View file

@ -33,7 +33,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
if (buffer.size() < 1 + 3 + 4) return;
int asmap_size = 3 + (buffer[0] & 127);
bool ipv6 = buffer[0] & 128;
int addr_size = ipv6 ? 16 : 4;
const size_t addr_size = ipv6 ? ADDR_IPV6_SIZE : ADDR_IPV4_SIZE;
if (buffer.size() < size_t(1 + asmap_size + addr_size)) return;
std::vector<bool> asmap = ipv6 ? IPV6_PREFIX_ASMAP : IPV4_PREFIX_ASMAP;
asmap.reserve(asmap.size() + 8 * asmap_size);
@ -43,7 +43,17 @@ void test_one_input(const std::vector<uint8_t>& buffer)
}
}
if (!SanityCheckASMap(asmap)) return;
const uint8_t* addr_data = buffer.data() + 1 + asmap_size;
CNetAddr net_addr;
net_addr.SetRaw(ipv6 ? NET_IPV6 : NET_IPV4, buffer.data() + 1 + asmap_size);
if (ipv6) {
assert(addr_size == ADDR_IPV6_SIZE);
net_addr.SetLegacyIPv6(Span<const uint8_t>(addr_data, addr_size));
} else {
assert(addr_size == ADDR_IPV4_SIZE);
in_addr ipv4;
memcpy(&ipv4, addr_data, addr_size);
net_addr.SetIP(CNetAddr{ipv4});
}
(void)net_addr.GetMappedAS(asmap);
}

View file

@ -17,9 +17,6 @@ void test_one_input(const std::vector<uint8_t>& buffer)
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
const CNetAddr net_addr = ConsumeNetAddr(fuzzed_data_provider);
for (int i = 0; i < 15; ++i) {
(void)net_addr.GetByte(i);
}
(void)net_addr.GetHash();
(void)net_addr.GetNetClass();
if (net_addr.GetNetwork() == Network::NET_IPV4) {
@ -78,7 +75,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
(void)net_addr.ToString();
(void)net_addr.ToStringIP();
const CSubNet sub_net{net_addr, fuzzed_data_provider.ConsumeIntegral<int32_t>()};
const CSubNet sub_net{net_addr, fuzzed_data_provider.ConsumeIntegral<uint8_t>()};
(void)sub_net.IsValid();
(void)sub_net.ToString();

View file

@ -257,7 +257,7 @@ CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider) noexcept
CSubNet ConsumeSubNet(FuzzedDataProvider& fuzzed_data_provider) noexcept
{
return {ConsumeNetAddr(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral<int32_t>()};
return {ConsumeNetAddr(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral<uint8_t>()};
}
void InitializeFuzzingContext(const std::string& chain_name = CBaseChainParams::REGTEST)

View file

@ -13,8 +13,10 @@
#include <streams.h>
#include <test/util/setup_common.h>
#include <util/memory.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/system.h>
#include <version.h>
#include <boost/test/unit_test.hpp>
@ -188,6 +190,78 @@ BOOST_AUTO_TEST_CASE(cnode_simple_test)
BOOST_CHECK(pnode2->IsInboundConn() == true);
}
BOOST_AUTO_TEST_CASE(cnetaddr_basic)
{
CNetAddr addr;
// IPv4, INADDR_ANY
BOOST_REQUIRE(LookupHost("0.0.0.0", addr, false));
BOOST_REQUIRE(!addr.IsValid());
BOOST_REQUIRE(addr.IsIPv4());
BOOST_CHECK(addr.IsBindAny());
BOOST_CHECK_EQUAL(addr.ToString(), "0.0.0.0");
// IPv4, INADDR_NONE
BOOST_REQUIRE(LookupHost("255.255.255.255", addr, false));
BOOST_REQUIRE(!addr.IsValid());
BOOST_REQUIRE(addr.IsIPv4());
BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK_EQUAL(addr.ToString(), "255.255.255.255");
// IPv4, casual
BOOST_REQUIRE(LookupHost("12.34.56.78", addr, false));
BOOST_REQUIRE(addr.IsValid());
BOOST_REQUIRE(addr.IsIPv4());
BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK_EQUAL(addr.ToString(), "12.34.56.78");
// IPv6, in6addr_any
BOOST_REQUIRE(LookupHost("::", addr, false));
BOOST_REQUIRE(!addr.IsValid());
BOOST_REQUIRE(addr.IsIPv6());
BOOST_CHECK(addr.IsBindAny());
BOOST_CHECK_EQUAL(addr.ToString(), "::");
// IPv6, casual
BOOST_REQUIRE(LookupHost("1122:3344:5566:7788:9900:aabb:ccdd:eeff", addr, false));
BOOST_REQUIRE(addr.IsValid());
BOOST_REQUIRE(addr.IsIPv6());
BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK_EQUAL(addr.ToString(), "1122:3344:5566:7788:9900:aabb:ccdd:eeff");
// TORv2
addr.SetSpecial("6hzph5hv6337r6p2.onion");
BOOST_REQUIRE(addr.IsValid());
BOOST_REQUIRE(addr.IsTor());
BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK_EQUAL(addr.ToString(), "6hzph5hv6337r6p2.onion");
// Internal
addr.SetInternal("esffpp");
BOOST_REQUIRE(!addr.IsValid()); // "internal" is considered invalid
BOOST_REQUIRE(addr.IsInternal());
BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK_EQUAL(addr.ToString(), "esffpvrt3wpeaygy.internal");
}
BOOST_AUTO_TEST_CASE(cnetaddr_serialize)
{
CNetAddr addr;
CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
addr.SetInternal("a");
s << addr;
BOOST_CHECK_EQUAL(HexStr(s), "fd6b88c08724ca978112ca1bbdcafac2");
s.clear();
}
// prior to PR #14728, this test triggers an undefined behavior
BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test)
{

View file

@ -185,6 +185,7 @@ BOOST_AUTO_TEST_CASE(subnet_test)
BOOST_CHECK(!ResolveSubNet("1.2.3.0/-1").IsValid());
BOOST_CHECK(ResolveSubNet("1.2.3.0/32").IsValid());
BOOST_CHECK(!ResolveSubNet("1.2.3.0/33").IsValid());
BOOST_CHECK(!ResolveSubNet("1.2.3.0/300").IsValid());
BOOST_CHECK(ResolveSubNet("1:2:3:4:5:6:7:8/0").IsValid());
BOOST_CHECK(ResolveSubNet("1:2:3:4:5:6:7:8/33").IsValid());
BOOST_CHECK(!ResolveSubNet("1:2:3:4:5:6:7:8/-1").IsValid());
@ -216,6 +217,11 @@ BOOST_AUTO_TEST_CASE(subnet_test)
BOOST_CHECK(CSubNet(ResolveIP("1:2:3:4:5:6:7:8")).Match(ResolveIP("1:2:3:4:5:6:7:8")));
BOOST_CHECK(!CSubNet(ResolveIP("1:2:3:4:5:6:7:8")).Match(ResolveIP("1:2:3:4:5:6:7:9")));
BOOST_CHECK(CSubNet(ResolveIP("1:2:3:4:5:6:7:8")).ToString() == "1:2:3:4:5:6:7:8/128");
// IPv4 address with IPv6 netmask or the other way around.
BOOST_CHECK(!CSubNet(ResolveIP("1.1.1.1"), ResolveIP("ffff::")).IsValid());
BOOST_CHECK(!CSubNet(ResolveIP("::1"), ResolveIP("255.0.0.0")).IsValid());
// Can't subnet TOR (or any other non-IPv4 and non-IPv6 network).
BOOST_CHECK(!CSubNet(ResolveIP("5wyqrzbvrdsumnok.onion"), ResolveIP("255.0.0.0")).IsValid());
subnet = ResolveSubNet("1.2.3.4/255.255.255.255");
BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.3.4/32");
@ -290,11 +296,13 @@ BOOST_AUTO_TEST_CASE(subnet_test)
BOOST_CHECK_EQUAL(subnet.ToString(), "1::/16");
subnet = ResolveSubNet("1:2:3:4:5:6:7:8/0000:0000:0000:0000:0000:0000:0000:0000");
BOOST_CHECK_EQUAL(subnet.ToString(), "::/0");
// Invalid netmasks (with 1-bits after 0-bits)
subnet = ResolveSubNet("1.2.3.4/255.255.232.0");
BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.0.0/255.255.232.0");
BOOST_CHECK(!subnet.IsValid());
subnet = ResolveSubNet("1.2.3.4/255.0.255.255");
BOOST_CHECK(!subnet.IsValid());
subnet = ResolveSubNet("1:2:3:4:5:6:7:8/ffff:ffff:ffff:fffe:ffff:ffff:ffff:ff0f");
BOOST_CHECK_EQUAL(subnet.ToString(), "1:2:3:4:5:6:7:8/ffff:ffff:ffff:fffe:ffff:ffff:ffff:ff0f");
BOOST_CHECK(!subnet.IsValid());
}
BOOST_AUTO_TEST_CASE(netbase_getgroup)
@ -428,7 +436,8 @@ BOOST_AUTO_TEST_CASE(netbase_dont_resolve_strings_with_embedded_nul_characters)
BOOST_CHECK(!LookupSubNet(std::string("1.2.3.0/24\0", 11), ret));
BOOST_CHECK(!LookupSubNet(std::string("1.2.3.0/24\0example.com", 22), ret));
BOOST_CHECK(!LookupSubNet(std::string("1.2.3.0/24\0example.com\0", 23), ret));
BOOST_CHECK(LookupSubNet(std::string("5wyqrzbvrdsumnok.onion", 22), ret));
// We only do subnetting for IPv4 and IPv6
BOOST_CHECK(!LookupSubNet(std::string("5wyqrzbvrdsumnok.onion", 22), ret));
BOOST_CHECK(!LookupSubNet(std::string("5wyqrzbvrdsumnok.onion\0", 23), ret));
BOOST_CHECK(!LookupSubNet(std::string("5wyqrzbvrdsumnok.onion\0example.com", 34), ret));
BOOST_CHECK(!LookupSubNet(std::string("5wyqrzbvrdsumnok.onion\0example.com\0", 35), ret));

View file

@ -318,6 +318,18 @@ bool ParseInt64(const std::string& str, int64_t *out)
n <= std::numeric_limits<int64_t>::max();
}
bool ParseUInt8(const std::string& str, uint8_t *out)
{
uint32_t u32;
if (!ParseUInt32(str, &u32) || u32 > std::numeric_limits<uint8_t>::max()) {
return false;
}
if (out != nullptr) {
*out = static_cast<uint8_t>(u32);
}
return true;
}
bool ParseUInt32(const std::string& str, uint32_t *out)
{
if (!ParsePrechecks(str))

View file

@ -98,6 +98,13 @@ NODISCARD bool ParseInt32(const std::string& str, int32_t *out);
*/
NODISCARD bool ParseInt64(const std::string& str, int64_t *out);
/**
* Convert decimal string to unsigned 8-bit integer with strict parse error feedback.
* @returns true if the entire string could be parsed as valid integer,
* false if not the entire string could be parsed or when overflow or underflow occurred.
*/
NODISCARD bool ParseUInt8(const std::string& str, uint8_t *out);
/**
* Convert decimal string to unsigned 32-bit integer with strict parse error feedback.
* @returns true if the entire string could be parsed as valid integer,