diff --git a/src/netaddress.h b/src/netaddress.h --- a/src/netaddress.h +++ b/src/netaddress.h @@ -16,19 +16,49 @@ #include #include +/** + * 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 @@ -39,6 +69,14 @@ 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. @@ -127,7 +165,21 @@ } friend bool operator<(const CNetAddr &a, const CNetAddr &b); - SERIALIZE_METHODS(CNetAddr, obj) { READWRITE(obj.ip); } + /** + * Serialize to a stream. + */ + template void Serialize(Stream &s) const { s << ip; } + + /** + * Unserialize from a stream. + */ + template 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; }; diff --git a/src/netaddress.cpp b/src/netaddress.cpp --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -26,17 +26,33 @@ } 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"); @@ -61,6 +77,7 @@ 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)); @@ -87,6 +104,7 @@ 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]; @@ -121,11 +139,11 @@ } 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 { @@ -156,48 +174,48 @@ } 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 { @@ -212,7 +230,7 @@ * @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 { @@ -224,7 +242,7 @@ // 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; } @@ -248,13 +266,13 @@ // 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; } @@ -306,7 +324,7 @@ * @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 { @@ -318,15 +336,7 @@ return NET_UNROUTABLE; } - if (IsIPv4()) { - return NET_IPV4; - } - - if (IsTor()) { - return NET_ONION; - } - - return NET_IPV6; + return m_net; } std::string CNetAddr::ToStringIP() const { @@ -366,11 +376,12 @@ } 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); } /** @@ -841,7 +852,7 @@ * 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) { diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -132,6 +132,13 @@ 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")); @@ -158,12 +165,14 @@ 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")));