From e8c6989e9544426e91695a3d5d7b79b41b89b91a Mon Sep 17 00:00:00 2001
From: "Wladimir J. van der Laan" <laanwj@protonmail.com>
Date: Wed, 15 Jul 2020 20:59:00 +0200
Subject: [PATCH 1/6] Merge #19360: net: improve encapsulation of CNetAddr

bc74a40a56128f81f11151d5966f53b82f19038c net: improve encapsulation of CNetAddr (Vasil Dimov)

Pull request description:

  Do not access `CNetAddr::ip` directly from `CService` methods.

  This improvement will help later when we change the type of
  `CNetAddr::ip` (in the BIP155 implementation).

  (chopped off from https://github.com/bitcoin/bitcoin/pull/19031 to ease review)

ACKs for top commit:
  dongcarl:
    ACK bc74a40a56128f81f11151d5966f53b82f19038c
  naumenkogs:
    ACK bc74a40
  fjahr:
    Code review ACK bc74a40
  laanwj:
    code review ACK bc74a40a56128f81f11151d5966f53b82f19038c
  jonatack:
    ACK bc74a40a56128f81f11151d5966f53b82f19038c
  jnewbery:
    ACK bc74a40a5

Tree-SHA512: 29a203905538e8311e3249b78565abe69ce36dc4ec239bec85c726c30e1a7b55b0aaf5c6659b676935008e068cfa53d716f7a598469064108daf130f94329a5d
---
 src/netaddress.cpp | 10 ++++------
 src/netaddress.h   | 10 ++++------
 2 files changed, 8 insertions(+), 12 deletions(-)

diff --git a/src/netaddress.cpp b/src/netaddress.cpp
index 658002a49076..d03209f8d3dd 100644
--- a/src/netaddress.cpp
+++ b/src/netaddress.cpp
@@ -568,12 +568,10 @@ bool CService::GetSockAddr(struct sockaddr* paddr, socklen_t *addrlen) const
 
 std::vector<unsigned char> CService::GetKey() const
 {
-     std::vector<unsigned char> vKey;
-     vKey.resize(18);
-     memcpy(vKey.data(), ip, 16);
-     vKey[16] = port / 0x100;
-     vKey[17] = port & 0x0FF;
-     return vKey;
+    auto key = GetAddrBytes();
+    key.push_back(port / 0x100);
+    key.push_back(port & 0x0FF);
+    return key;
 }
 
 std::string CService::ToStringPort() const
diff --git a/src/netaddress.h b/src/netaddress.h
index ed7971d2a4a3..87172d02d142 100644
--- a/src/netaddress.h
+++ b/src/netaddress.h
@@ -167,12 +167,10 @@ class CService : public CNetAddr
         CService(const struct in6_addr& ipv6Addr, unsigned short port);
         explicit CService(const struct sockaddr_in6& addr);
 
-        ADD_SERIALIZE_METHODS;
-
-        template <typename Stream, typename Operation>
-        inline void SerializationOp(Stream& s, Operation ser_action) {
-            READWRITE(ip);
-            READWRITE(WrapBigEndian(port));
+        SERIALIZE_METHODS(CService, obj)
+        {
+            READWRITEAS(CNetAddr, obj);
+            READWRITE(Using<BigEndianFormatter<2>>(obj.port));
         }
 };
 

From 78328c1898e52565d7da2b2815e32c6a3cf98b76 Mon Sep 17 00:00:00 2001
From: UdjinM6 <UdjinM6@users.noreply.github.com>
Date: Mon, 18 Jan 2021 23:54:46 +0300
Subject: [PATCH 2/6] Fix CService (de)serialization

---
 src/netaddress.h | 24 +++++++++++++++++++++---
 1 file changed, 21 insertions(+), 3 deletions(-)

diff --git a/src/netaddress.h b/src/netaddress.h
index 87172d02d142..c065a8ab487f 100644
--- a/src/netaddress.h
+++ b/src/netaddress.h
@@ -167,10 +167,28 @@ class CService : public CNetAddr
         CService(const struct in6_addr& ipv6Addr, unsigned short port);
         explicit CService(const struct sockaddr_in6& addr);
 
-        SERIALIZE_METHODS(CService, obj)
+        /**
+         * Serialize to a stream.
+         */
+        template <typename Stream>
+        void Serialize(Stream& s) const
+        {
+            s << ip;
+            s << WrapBigEndian(port);
+        }
+
+        /**
+         * Unserialize from a stream.
+         */
+        template <typename Stream>
+        void Unserialize(Stream& s)
         {
-            READWRITEAS(CNetAddr, obj);
-            READWRITE(Using<BigEndianFormatter<2>>(obj.port));
+            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);
+            s >> WrapBigEndian(port);
         }
 };
 

From a9a9c3d86519e9ae5501511bd4754c2a4ccb90b0 Mon Sep 17 00:00:00 2001
From: pasta <pasta@dashboost.org>
Date: Sat, 16 Jan 2021 01:25:07 -0500
Subject: [PATCH 3/6] Very partial merge bitcoin#19219

Signed-off-by: pasta <pasta@dashboost.org>
---
 src/netaddress.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/netaddress.h b/src/netaddress.h
index c065a8ab487f..66e45c5654c8 100644
--- a/src/netaddress.h
+++ b/src/netaddress.h
@@ -85,6 +85,7 @@ class CNetAddr
         uint64_t GetHash() const;
         bool GetInAddr(struct in_addr* pipv4Addr) const;
         std::vector<unsigned char> GetGroup() const;
+        std::vector<unsigned char> GetAddrBytes() const { return {std::begin(ip), std::end(ip)}; }
         int GetReachabilityFrom(const CNetAddr *paddrPartner = nullptr) const;
 
         explicit CNetAddr(const struct in6_addr& pipv6Addr, const uint32_t scope = 0);

From 233232ce3eaea6020829db444090d536a157dedd Mon Sep 17 00:00:00 2001
From: "Wladimir J. van der Laan" <laanwj@gmail.com>
Date: Thu, 6 Jun 2019 12:52:10 +0200
Subject: [PATCH 4/6] Merge #15689: netaddress: Update CNetAddr for ORCHIDv2

8be3f3063 netaddress: Update CNetAddr for ORCHIDv2 (Carl Dong)

Pull request description:

  ```
  The original ORCHID prefix was deprecated as of 2014-03, the new
  ORCHIDv2 prefix was allocated by RFC7343 as of 2014-07. We did not
  consider the original ORCHID prefix routable, and I don't see any reason
  to consider the new one to be either.
  ```

  Would like to know if people think this kind of thing is even worth keeping the codebase updated for. Perhaps it'd be nice to write a devtool to pull the csv from [here](https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml) and generate the code.

ACKs for commit 8be3f3:
  laanwj:
    utACK 8be3f3063
  ryanofsky:
    utACK 8be3f306338e27b3fa3ad3bfb75f822d038909a5. Only change since last review is rebasing after #15718 merge.

Tree-SHA512: 7c93317f597b1a6c1443e12dd690010392edb9d72a479a8201970db7d3444fbb99a80b98026caad6fbfbebb455ab4035d2dde79bc9263bfd1d0398cd218392e1
---
 src/netaddress.cpp         | 7 ++++++-
 src/netaddress.h           | 3 ++-
 src/test/netbase_tests.cpp | 1 +
 3 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/netaddress.cpp b/src/netaddress.cpp
index d03209f8d3dd..43dd6a6f55b8 100644
--- a/src/netaddress.cpp
+++ b/src/netaddress.cpp
@@ -178,6 +178,11 @@ bool CNetAddr::IsRFC4843() const
     return (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);
+}
+
 bool CNetAddr::IsTor() const
 {
     return (memcmp(ip, pchOnionCat, sizeof(pchOnionCat)) == 0);
@@ -242,7 +247,7 @@ bool CNetAddr::IsRoutable() const
         return false;
     if (!fAllowPrivateNet && IsRFC1918())
         return false;
-    return !(IsRFC2544() || IsRFC3927() || IsRFC4862() || IsRFC6598() || IsRFC5737() || (IsRFC4193() && !IsTor()) || IsRFC4843() || IsLocal() || IsInternal());
+    return !(IsRFC2544() || IsRFC3927() || IsRFC4862() || IsRFC6598() || IsRFC5737() || (IsRFC4193() && !IsTor()) || IsRFC4843() || IsRFC7343() || IsLocal() || IsInternal());
 }
 
 bool CNetAddr::IsInternal() const
diff --git a/src/netaddress.h b/src/netaddress.h
index 66e45c5654c8..630a94c39629 100644
--- a/src/netaddress.h
+++ b/src/netaddress.h
@@ -69,7 +69,8 @@ class CNetAddr
         bool IsRFC3964() const; // IPv6 6to4 tunnelling (2002::/16)
         bool IsRFC4193() const; // IPv6 unique local (FC00::/7)
         bool IsRFC4380() const; // IPv6 Teredo tunnelling (2001::/32)
-        bool IsRFC4843() const; // IPv6 ORCHID (2001:10::/28)
+        bool IsRFC4843() const; // IPv6 ORCHID (deprecated) (2001:10::/28)
+        bool IsRFC7343() const; // IPv6 ORCHIDv2 (2001:20::/28)
         bool IsRFC4862() const; // IPv6 autoconfig (FE80::/64)
         bool IsRFC6052() const; // IPv6 well-known prefix (64:FF9B::/96)
         bool IsRFC6145() const; // IPv6 IPv4-translated address (::FFFF:0:0:0/96)
diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp
index 42339eb16524..dc7fb1e3fc8c 100644
--- a/src/test/netbase_tests.cpp
+++ b/src/test/netbase_tests.cpp
@@ -60,6 +60,7 @@ BOOST_AUTO_TEST_CASE(netbase_properties)
     BOOST_CHECK(ResolveIP("FC00::").IsRFC4193());
     BOOST_CHECK(ResolveIP("2001::2").IsRFC4380());
     BOOST_CHECK(ResolveIP("2001:10::").IsRFC4843());
+    BOOST_CHECK(ResolveIP("2001:20::").IsRFC7343());
     BOOST_CHECK(ResolveIP("FE80::").IsRFC4862());
     BOOST_CHECK(ResolveIP("64:FF9B::").IsRFC6052());
     BOOST_CHECK(ResolveIP("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").IsTor());

From 3af7c994db585fa85e81fabdd245fb557cc30589 Mon Sep 17 00:00:00 2001
From: "Wladimir J. van der Laan" <laanwj@protonmail.com>
Date: Mon, 22 Jun 2020 17:55:00 +0200
Subject: [PATCH 5/6] Merge #19351: test: add two edge case tests for CSubNet

ccef5d7bf0af8377c6c779295f7b41d5af435c47 test: add two edge case tests for CSubNet (Vasil Dimov)

Pull request description:

  This is chopped off from https://github.com/bitcoin/bitcoin/pull/19031. It is needed because later 19031 modifies the related code and the tests ensure that no surprising changes in behavior sneak in.

ACKs for top commit:
  practicalswift:
    ACK ccef5d7bf0af8377c6c779295f7b41d5af435c47 -- more test coverage is better than less test coverage :)
  laanwj:
    ACK ccef5d7bf0af8377c6c779295f7b41d5af435c47
  hebasto:
    ACK ccef5d7bf0af8377c6c779295f7b41d5af435c47, I have reviewed the code and it looks OK, I agree it can be merged.

Tree-SHA512: 6d386672b6598aeddd33dabe3512e816cf548d5c1af56c4c9e6f897d513b62ba4659cde73405811a0df286ffee3a3f084ab7caf8e3a2086fa9ddecd1bdcb3c67
---
 src/test/netbase_tests.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp
index dc7fb1e3fc8c..cad50fb30a53 100644
--- a/src/test/netbase_tests.cpp
+++ b/src/test/netbase_tests.cpp
@@ -158,6 +158,9 @@ BOOST_AUTO_TEST_CASE(subnet_test)
     BOOST_CHECK(ResolveSubNet("1.2.2.20/26").Match(ResolveIP("1.2.2.63")));
     // All-Matching IPv6 Matches arbitrary IPv4 and 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")));
     // 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")));

From 888f36abe7eded6bd78f614fa2b52f5bbfe1c066 Mon Sep 17 00:00:00 2001
From: "Wladimir J. van der Laan" <laanwj@protonmail.com>
Date: Wed, 29 Jul 2020 13:31:10 +0200
Subject: [PATCH 6/6] Merge #19534: net: save the network type explicitly in
 CNetAddr

bcfebb6d5511ad4c156868bc799831ace628a225 net: save the network type explicitly in CNetAddr (Vasil Dimov)
100c64a95b518a6a19241aec4058b866a8872d9b net: document `enum Network` (Vasil Dimov)

Pull request description:

  (chopped off from https://github.com/bitcoin/bitcoin/pull/19031 to ease review)

  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`.

ACKs for top commit:
  troygiorshev:
    reACK bcfebb6d5511ad4c156868bc799831ace628a225 via `git range-diff master 64897c5 bcfebb6`
  jonatack:
    re-ACK bcfebb6 per `git diff 662bb25 bcfebb6`, code review, debug build/tests clean, ran bitcoind.
  laanwj:
    Code review ACK bcfebb6d5511ad4c156868bc799831ace628a225

Tree-SHA512: 9347e2a50feac617a994bfb46a8f77e31c236bde882e4fd4f03eea4766cd5110216f5f3d24dee91d25218bab7f8bb6e1d2d6212a44db9e34594299fd6ff7606b
Signed-off-by: pasta <pasta@dashboost.org>

# Conflicts:
#	src/netaddress.cpp
#	src/netaddress.h
---
 src/netaddress.cpp         | 79 +++++++++++++++++++++-----------------
 src/netaddress.h           | 62 +++++++++++++++++++++++++++---
 src/test/netbase_tests.cpp | 13 ++++++-
 3 files changed, 111 insertions(+), 43 deletions(-)

diff --git a/src/netaddress.cpp b/src/netaddress.cpp
index 43dd6a6f55b8..11714e6b66c6 100644
--- a/src/netaddress.cpp
+++ b/src/netaddress.cpp
@@ -24,19 +24,35 @@ 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");
@@ -48,6 +64,7 @@ bool CNetAddr::SetInternal(const std::string &name)
     if (name.empty()) {
         return false;
     }
+    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));
@@ -61,6 +78,7 @@ bool CNetAddr::SetSpecial(const std::string &strName)
         std::vector<unsigned char> vchAddr = DecodeBase32(strName.substr(0, strName.size() - 6).c_str());
         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];
@@ -95,15 +113,9 @@ bool CNetAddr::IsBindAny() const
     return true;
 }
 
-bool CNetAddr::IsIPv4() const
-{
-    return (memcmp(ip, pchIPv4, sizeof(pchIPv4)) == 0);
-}
+bool CNetAddr::IsIPv4() const { return m_net == NET_IPV4; }
 
-bool CNetAddr::IsIPv6() const
-{
-    return (!IsIPv4() && !IsTor() && !IsInternal());
-}
+bool CNetAddr::IsIPv6() const { return m_net == NET_IPV6; }
 
 bool CNetAddr::IsRFC1918() const
 {
@@ -137,56 +149,57 @@ 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 unsigned char 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 unsigned char 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 unsigned char 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::IsTor() const
-{
-    return (memcmp(ip, pchOnionCat, sizeof(pchOnionCat)) == 0);
-}
+bool CNetAddr::IsTor() const { return m_net == NET_ONION; }
 
 bool CNetAddr::IsLocal() const
 {
@@ -196,7 +209,7 @@ bool CNetAddr::IsLocal() const
 
    // 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 (memcmp(ip, pchLocal, 16) == 0)
+   if (IsIPv6() && memcmp(ip, pchLocal, 16) == 0)
        return true;
 
    return false;
@@ -210,12 +223,12 @@ 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)
     unsigned char ipNone6[16] = {};
-    if (memcmp(ip, ipNone6, 16) == 0)
+    if (IsIPv6() && memcmp(ip, ipNone6, 16) == 0)
         return false;
 
     // documentation IPv6 address
@@ -252,7 +265,7 @@ bool CNetAddr::IsRoutable() const
 
 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
@@ -263,13 +276,7 @@ enum Network CNetAddr::GetNetwork() const
     if (!IsRoutable())
         return NET_UNROUTABLE;
 
-    if (IsIPv4())
-        return NET_IPV4;
-
-    if (IsTor())
-        return NET_ONION;
-
-    return NET_IPV6;
+    return m_net;
 }
 
 std::string CNetAddr::ToStringIP(bool fUseGetnameinfo) const
@@ -306,12 +313,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);
 }
 
 bool CNetAddr::GetInAddr(struct in_addr* pipv4Addr) const
@@ -661,7 +668,7 @@ CSubNet::CSubNet(const CNetAddr &addr):
 
 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)
         if ((addr.ip[x] & netmask[x]) != network.ip[x])
diff --git a/src/netaddress.h b/src/netaddress.h
index 630a94c39629..46a6ec33265f 100644
--- a/src/netaddress.h
+++ b/src/netaddress.h
@@ -19,21 +19,50 @@
 
 extern bool fAllowPrivateNet;
 
+/**
+ * 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};
+
         unsigned char ip[16]; // in network byte order
         uint32_t scopeId{0}; // for scoped/link-local ipv6 addresses
 
@@ -42,6 +71,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]);
+
     private:
         /**
          * Set raw IPv4 or IPv6 address (in network byte order)
@@ -96,11 +133,26 @@ class CNetAddr
         friend bool operator!=(const CNetAddr& a, const CNetAddr& b) { return !(a == b); }
         friend bool operator<(const CNetAddr& a, const CNetAddr& b);
 
-        ADD_SERIALIZE_METHODS;
+        /**
+         * Serialize to a stream.
+         */
+        template <typename Stream>
+        void Serialize(Stream& s) const
+        {
+            s << ip;
+        }
 
-        template <typename Stream, typename Operation>
-        inline void SerializationOp(Stream& s, Operation ser_action) {
-            READWRITE(ip);
+        /**
+         * Unserialize from a stream.
+         */
+        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);
         }
 
         friend class CSubNet;
diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp
index cad50fb30a53..71fd73bc9f25 100644
--- a/src/test/netbase_tests.cpp
+++ b/src/test/netbase_tests.cpp
@@ -136,6 +136,14 @@ BOOST_AUTO_TEST_CASE(onioncat_test)
 
 }
 
+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)
 {
 
@@ -156,12 +164,13 @@ 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")));
     // Invalid subnets Match nothing (not even invalid addresses)
