mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-11-12 06:58:57 +01:00
net: CNetAddr: add support to (un)serialize as ADDRv2
Co-authored-by: Carl Dong <contact@carldong.me>
This commit is contained in:
@@ -14,10 +14,65 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <ios>
|
||||
#include <iterator>
|
||||
#include <tuple>
|
||||
|
||||
constexpr size_t CNetAddr::V1_SERIALIZATION_SIZE;
|
||||
constexpr size_t CNetAddr::MAX_ADDRV2_SIZE;
|
||||
|
||||
CNetAddr::BIP155Network CNetAddr::GetBIP155Network() const
|
||||
{
|
||||
switch (m_net) {
|
||||
case NET_IPV4:
|
||||
return BIP155Network::IPV4;
|
||||
case NET_IPV6:
|
||||
return BIP155Network::IPV6;
|
||||
case NET_ONION:
|
||||
return BIP155Network::TORV2;
|
||||
case NET_INTERNAL: // should have been handled before calling this function
|
||||
case NET_UNROUTABLE: // m_net is never and should not be set to NET_UNROUTABLE
|
||||
case NET_MAX: // m_net is never and should not be set to NET_MAX
|
||||
assert(false);
|
||||
} // no default case, so the compiler can warn about missing cases
|
||||
|
||||
assert(false);
|
||||
}
|
||||
|
||||
bool CNetAddr::SetNetFromBIP155Network(uint8_t possible_bip155_net, size_t address_size)
|
||||
{
|
||||
switch (possible_bip155_net) {
|
||||
case BIP155Network::IPV4:
|
||||
if (address_size == ADDR_IPV4_SIZE) {
|
||||
m_net = NET_IPV4;
|
||||
return true;
|
||||
}
|
||||
throw std::ios_base::failure(
|
||||
strprintf("BIP155 IPv4 address with length %u (should be %u)", address_size,
|
||||
ADDR_IPV4_SIZE));
|
||||
case BIP155Network::IPV6:
|
||||
if (address_size == ADDR_IPV6_SIZE) {
|
||||
m_net = NET_IPV6;
|
||||
return true;
|
||||
}
|
||||
throw std::ios_base::failure(
|
||||
strprintf("BIP155 IPv6 address with length %u (should be %u)", address_size,
|
||||
ADDR_IPV6_SIZE));
|
||||
case BIP155Network::TORV2:
|
||||
if (address_size == ADDR_TORV2_SIZE) {
|
||||
m_net = NET_ONION;
|
||||
return true;
|
||||
}
|
||||
throw std::ios_base::failure(
|
||||
strprintf("BIP155 TORv2 address with length %u (should be %u)", address_size,
|
||||
ADDR_TORV2_SIZE));
|
||||
}
|
||||
|
||||
// Don't throw on addresses with unknown network ids (maybe from the future).
|
||||
// Instead silently drop them and have the unserialization code consume
|
||||
// subsequent ones which may be known to us.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an unspecified IPv6 network address (::/128).
|
||||
|
||||
130
src/netaddress.h
130
src/netaddress.h
@@ -13,12 +13,24 @@
|
||||
#include <compat.h>
|
||||
#include <prevector.h>
|
||||
#include <serialize.h>
|
||||
#include <tinyformat.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <util/string.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <ios>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* A flag that is ORed into the protocol version to designate that addresses
|
||||
* should be serialized in (unserialized from) v2 format (BIP155).
|
||||
* Make sure that this does not collide with any of the values in `version.h`
|
||||
* or with `SERIALIZE_TRANSACTION_NO_WITNESS`.
|
||||
*/
|
||||
static const int ADDRV2_FORMAT = 0x20000000;
|
||||
|
||||
/**
|
||||
* A network type.
|
||||
* @note An address may belong to more than one network, for example `10.0.0.1`
|
||||
@@ -177,8 +189,12 @@ class CNetAddr
|
||||
template <typename Stream>
|
||||
void Serialize(Stream& s) const
|
||||
{
|
||||
if (s.GetVersion() & ADDRV2_FORMAT) {
|
||||
SerializeV2Stream(s);
|
||||
} else {
|
||||
SerializeV1Stream(s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize from a stream.
|
||||
@@ -186,17 +202,53 @@ class CNetAddr
|
||||
template <typename Stream>
|
||||
void Unserialize(Stream& s)
|
||||
{
|
||||
if (s.GetVersion() & ADDRV2_FORMAT) {
|
||||
UnserializeV2Stream(s);
|
||||
} else {
|
||||
UnserializeV1Stream(s);
|
||||
}
|
||||
}
|
||||
|
||||
friend class CSubNet;
|
||||
|
||||
private:
|
||||
/**
|
||||
* BIP155 network ids recognized by this software.
|
||||
*/
|
||||
enum BIP155Network : uint8_t {
|
||||
IPV4 = 1,
|
||||
IPV6 = 2,
|
||||
TORV2 = 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* Size of CNetAddr when serialized as ADDRv1 (pre-BIP155) (in bytes).
|
||||
*/
|
||||
static constexpr size_t V1_SERIALIZATION_SIZE = ADDR_IPV6_SIZE;
|
||||
|
||||
/**
|
||||
* Maximum size of an address as defined in BIP155 (in bytes).
|
||||
* This is only the size of the address, not the entire CNetAddr object
|
||||
* when serialized.
|
||||
*/
|
||||
static constexpr size_t MAX_ADDRV2_SIZE = 512;
|
||||
|
||||
/**
|
||||
* Get the BIP155 network id of this address.
|
||||
* Must not be called for IsInternal() objects.
|
||||
* @returns BIP155 network id
|
||||
*/
|
||||
BIP155Network GetBIP155Network() const;
|
||||
|
||||
/**
|
||||
* Set `m_net` from the provided BIP155 network id and size after validation.
|
||||
* @retval true the network was recognized, is valid and `m_net` was set
|
||||
* @retval false not recognised (from future?) and should be silently ignored
|
||||
* @throws std::ios_base::failure if the network is one of the BIP155 founding
|
||||
* networks recognized by this software (id 1..3) and has wrong address size.
|
||||
*/
|
||||
bool SetNetFromBIP155Network(uint8_t possible_bip155_net, size_t address_size);
|
||||
|
||||
/**
|
||||
* Serialize in pre-ADDRv2/BIP155 format to an array.
|
||||
* Some addresses (e.g. TORv3) cannot be serialized in pre-BIP155 format.
|
||||
@@ -250,6 +302,25 @@ class CNetAddr
|
||||
s << serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize as ADDRv2 / BIP155.
|
||||
*/
|
||||
template <typename Stream>
|
||||
void SerializeV2Stream(Stream& s) const
|
||||
{
|
||||
if (IsInternal()) {
|
||||
// Serialize NET_INTERNAL as embedded in IPv6. We need to
|
||||
// serialize such addresses from addrman.
|
||||
s << static_cast<uint8_t>(BIP155Network::IPV6);
|
||||
s << COMPACTSIZE(ADDR_IPV6_SIZE);
|
||||
SerializeV1Stream(s);
|
||||
return;
|
||||
}
|
||||
|
||||
s << static_cast<uint8_t>(GetBIP155Network());
|
||||
s << m_addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize from a pre-ADDRv2/BIP155 format from an array.
|
||||
*/
|
||||
@@ -272,6 +343,65 @@ class CNetAddr
|
||||
|
||||
UnserializeV1Array(serialized);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize from a ADDRv2 / BIP155 format.
|
||||
*/
|
||||
template <typename Stream>
|
||||
void UnserializeV2Stream(Stream& s)
|
||||
{
|
||||
uint8_t bip155_net;
|
||||
s >> bip155_net;
|
||||
|
||||
size_t address_size;
|
||||
s >> COMPACTSIZE(address_size);
|
||||
|
||||
if (address_size > MAX_ADDRV2_SIZE) {
|
||||
throw std::ios_base::failure(strprintf(
|
||||
"Address too long: %u > %u", address_size, MAX_ADDRV2_SIZE));
|
||||
}
|
||||
|
||||
scopeId = 0;
|
||||
|
||||
if (SetNetFromBIP155Network(bip155_net, address_size)) {
|
||||
m_addr.resize(address_size);
|
||||
s >> MakeSpan(m_addr);
|
||||
|
||||
if (m_net != NET_IPV6) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do some special checks on IPv6 addresses.
|
||||
|
||||
// Recognize NET_INTERNAL embedded in IPv6, such addresses are not
|
||||
// gossiped but could be coming from addrman, when unserializing from
|
||||
// disk.
|
||||
if (HasPrefix(m_addr, INTERNAL_IN_IPV6_PREFIX)) {
|
||||
m_net = NET_INTERNAL;
|
||||
memmove(m_addr.data(), m_addr.data() + INTERNAL_IN_IPV6_PREFIX.size(),
|
||||
ADDR_INTERNAL_SIZE);
|
||||
m_addr.resize(ADDR_INTERNAL_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HasPrefix(m_addr, IPV4_IN_IPV6_PREFIX) &&
|
||||
!HasPrefix(m_addr, TORV2_IN_IPV6_PREFIX)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// IPv4 and TORv2 are not supposed to be embedded in IPv6 (like in V1
|
||||
// encoding). Unserialize as !IsValid(), thus ignoring them.
|
||||
} else {
|
||||
// If we receive an unknown BIP155 network id (from the future?) then
|
||||
// ignore the address - unserialize as !IsValid().
|
||||
s.ignore(address_size);
|
||||
}
|
||||
|
||||
// Mimic a default-constructed CNetAddr object which is !IsValid() and thus
|
||||
// will not be gossiped, but continue reading next addresses from the stream.
|
||||
m_net = NET_IPV6;
|
||||
m_addr.assign(ADDR_IPV6_SIZE, 0x0);
|
||||
}
|
||||
};
|
||||
|
||||
class CSubNet
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
|
||||
#include <tuple>
|
||||
|
||||
/**
|
||||
* A flag that is ORed into the protocol version to designate that a transaction
|
||||
* should be (un)serialized without witness data.
|
||||
* Make sure that this does not collide with any of the values in `version.h`
|
||||
* or with `ADDRV2_FORMAT`.
|
||||
*/
|
||||
static const int SERIALIZE_TRANSACTION_NO_WITNESS = 0x40000000;
|
||||
|
||||
/** An outpoint - a combination of a transaction hash and an index n into its vout */
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <net.h>
|
||||
#include <netbase.h>
|
||||
#include <serialize.h>
|
||||
#include <span.h>
|
||||
#include <streams.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <util/memory.h>
|
||||
@@ -20,6 +21,7 @@
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <ios>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@@ -261,17 +263,207 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic)
|
||||
BOOST_CHECK_EQUAL(addr.ToString(), "esffpvrt3wpeaygy.internal");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cnetaddr_serialize)
|
||||
BOOST_AUTO_TEST_CASE(cnetaddr_serialize_v1)
|
||||
{
|
||||
CNetAddr addr;
|
||||
CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
|
||||
|
||||
s << addr;
|
||||
BOOST_CHECK_EQUAL(HexStr(s), "00000000000000000000000000000000");
|
||||
s.clear();
|
||||
|
||||
BOOST_REQUIRE(LookupHost("1.2.3.4", addr, false));
|
||||
s << addr;
|
||||
BOOST_CHECK_EQUAL(HexStr(s), "00000000000000000000ffff01020304");
|
||||
s.clear();
|
||||
|
||||
BOOST_REQUIRE(LookupHost("1a1b:2a2b:3a3b:4a4b:5a5b:6a6b:7a7b:8a8b", addr, false));
|
||||
s << addr;
|
||||
BOOST_CHECK_EQUAL(HexStr(s), "1a1b2a2b3a3b4a4b5a5b6a6b7a7b8a8b");
|
||||
s.clear();
|
||||
|
||||
BOOST_REQUIRE(addr.SetSpecial("6hzph5hv6337r6p2.onion"));
|
||||
s << addr;
|
||||
BOOST_CHECK_EQUAL(HexStr(s), "fd87d87eeb43f1f2f3f4f5f6f7f8f9fa");
|
||||
s.clear();
|
||||
|
||||
addr.SetInternal("a");
|
||||
s << addr;
|
||||
BOOST_CHECK_EQUAL(HexStr(s), "fd6b88c08724ca978112ca1bbdcafac2");
|
||||
s.clear();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cnetaddr_serialize_v2)
|
||||
{
|
||||
CNetAddr addr;
|
||||
CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
|
||||
// Add ADDRV2_FORMAT to the version so that the CNetAddr
|
||||
// serialize method produces an address in v2 format.
|
||||
s.SetVersion(s.GetVersion() | ADDRV2_FORMAT);
|
||||
|
||||
s << addr;
|
||||
BOOST_CHECK_EQUAL(HexStr(s), "021000000000000000000000000000000000");
|
||||
s.clear();
|
||||
|
||||
BOOST_REQUIRE(LookupHost("1.2.3.4", addr, false));
|
||||
s << addr;
|
||||
BOOST_CHECK_EQUAL(HexStr(s), "010401020304");
|
||||
s.clear();
|
||||
|
||||
BOOST_REQUIRE(LookupHost("1a1b:2a2b:3a3b:4a4b:5a5b:6a6b:7a7b:8a8b", addr, false));
|
||||
s << addr;
|
||||
BOOST_CHECK_EQUAL(HexStr(s), "02101a1b2a2b3a3b4a4b5a5b6a6b7a7b8a8b");
|
||||
s.clear();
|
||||
|
||||
BOOST_REQUIRE(addr.SetSpecial("6hzph5hv6337r6p2.onion"));
|
||||
s << addr;
|
||||
BOOST_CHECK_EQUAL(HexStr(s), "030af1f2f3f4f5f6f7f8f9fa");
|
||||
s.clear();
|
||||
|
||||
BOOST_REQUIRE(addr.SetInternal("a"));
|
||||
s << addr;
|
||||
BOOST_CHECK_EQUAL(HexStr(s), "0210fd6b88c08724ca978112ca1bbdcafac2");
|
||||
s.clear();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2)
|
||||
{
|
||||
CNetAddr addr;
|
||||
CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
|
||||
// Add ADDRV2_FORMAT to the version so that the CNetAddr
|
||||
// unserialize method expects an address in v2 format.
|
||||
s.SetVersion(s.GetVersion() | ADDRV2_FORMAT);
|
||||
|
||||
// Valid IPv4.
|
||||
s << MakeSpan(ParseHex("01" // network type (IPv4)
|
||||
"04" // address length
|
||||
"01020304")); // address
|
||||
s >> addr;
|
||||
BOOST_CHECK(addr.IsValid());
|
||||
BOOST_CHECK(addr.IsIPv4());
|
||||
BOOST_CHECK_EQUAL(addr.ToString(), "1.2.3.4");
|
||||
BOOST_REQUIRE(s.empty());
|
||||
|
||||
// Invalid IPv4, valid length but address itself is shorter.
|
||||
s << MakeSpan(ParseHex("01" // network type (IPv4)
|
||||
"04" // address length
|
||||
"0102")); // address
|
||||
BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure, HasReason("end of data"));
|
||||
BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input.
|
||||
s.clear();
|
||||
|
||||
// Invalid IPv4, with bogus length.
|
||||
s << MakeSpan(ParseHex("01" // network type (IPv4)
|
||||
"05" // address length
|
||||
"01020304")); // address
|
||||
BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure,
|
||||
HasReason("BIP155 IPv4 address with length 5 (should be 4)"));
|
||||
BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input.
|
||||
s.clear();
|
||||
|
||||
// Invalid IPv4, with extreme length.
|
||||
s << MakeSpan(ParseHex("01" // network type (IPv4)
|
||||
"fd0102" // address length (513 as CompactSize)
|
||||
"01020304")); // address
|
||||
BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure,
|
||||
HasReason("Address too long: 513 > 512"));
|
||||
BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input.
|
||||
s.clear();
|
||||
|
||||
// Valid IPv6.
|
||||
s << MakeSpan(ParseHex("02" // network type (IPv6)
|
||||
"10" // address length
|
||||
"0102030405060708090a0b0c0d0e0f10")); // address
|
||||
s >> addr;
|
||||
BOOST_CHECK(addr.IsValid());
|
||||
BOOST_CHECK(addr.IsIPv6());
|
||||
BOOST_CHECK_EQUAL(addr.ToString(), "102:304:506:708:90a:b0c:d0e:f10");
|
||||
BOOST_REQUIRE(s.empty());
|
||||
|
||||
// Valid IPv6, contains embedded "internal".
|
||||
s << MakeSpan(ParseHex(
|
||||
"02" // network type (IPv6)
|
||||
"10" // address length
|
||||
"fd6b88c08724ca978112ca1bbdcafac2")); // address: 0xfd + sha256("bitcoin")[0:5] +
|
||||
// sha256(name)[0:10]
|
||||
s >> addr;
|
||||
BOOST_CHECK(addr.IsInternal());
|
||||
BOOST_CHECK_EQUAL(addr.ToString(), "zklycewkdo64v6wc.internal");
|
||||
BOOST_REQUIRE(s.empty());
|
||||
|
||||
// Invalid IPv6, with bogus length.
|
||||
s << MakeSpan(ParseHex("02" // network type (IPv6)
|
||||
"04" // address length
|
||||
"00")); // address
|
||||
BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure,
|
||||
HasReason("BIP155 IPv6 address with length 4 (should be 16)"));
|
||||
BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input.
|
||||
s.clear();
|
||||
|
||||
// Invalid IPv6, contains embedded IPv4.
|
||||
s << MakeSpan(ParseHex("02" // network type (IPv6)
|
||||
"10" // address length
|
||||
"00000000000000000000ffff01020304")); // address
|
||||
s >> addr;
|
||||
BOOST_CHECK(!addr.IsValid());
|
||||
BOOST_REQUIRE(s.empty());
|
||||
|
||||
// Invalid IPv6, contains embedded TORv2.
|
||||
s << MakeSpan(ParseHex("02" // network type (IPv6)
|
||||
"10" // address length
|
||||
"fd87d87eeb430102030405060708090a")); // address
|
||||
s >> addr;
|
||||
BOOST_CHECK(!addr.IsValid());
|
||||
BOOST_REQUIRE(s.empty());
|
||||
|
||||
// Valid TORv2.
|
||||
s << MakeSpan(ParseHex("03" // network type (TORv2)
|
||||
"0a" // address length
|
||||
"f1f2f3f4f5f6f7f8f9fa")); // address
|
||||
s >> addr;
|
||||
BOOST_CHECK(addr.IsValid());
|
||||
BOOST_CHECK(addr.IsTor());
|
||||
BOOST_CHECK_EQUAL(addr.ToString(), "6hzph5hv6337r6p2.onion");
|
||||
BOOST_REQUIRE(s.empty());
|
||||
|
||||
// Invalid TORv2, with bogus length.
|
||||
s << MakeSpan(ParseHex("03" // network type (TORv2)
|
||||
"07" // address length
|
||||
"00")); // address
|
||||
BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure,
|
||||
HasReason("BIP155 TORv2 address with length 7 (should be 10)"));
|
||||
BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input.
|
||||
s.clear();
|
||||
|
||||
// Unknown, with extreme length.
|
||||
s << MakeSpan(ParseHex("aa" // network type (unknown)
|
||||
"fe00000002" // address length (CompactSize's MAX_SIZE)
|
||||
"01020304050607" // address
|
||||
));
|
||||
BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure,
|
||||
HasReason("Address too long: 33554432 > 512"));
|
||||
BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input.
|
||||
s.clear();
|
||||
|
||||
// Unknown, with reasonable length.
|
||||
s << MakeSpan(ParseHex("aa" // network type (unknown)
|
||||
"04" // address length
|
||||
"01020304" // address
|
||||
));
|
||||
s >> addr;
|
||||
BOOST_CHECK(!addr.IsValid());
|
||||
BOOST_REQUIRE(s.empty());
|
||||
|
||||
// Unknown, with zero length.
|
||||
s << MakeSpan(ParseHex("aa" // network type (unknown)
|
||||
"00" // address length
|
||||
"" // address
|
||||
));
|
||||
s >> addr;
|
||||
BOOST_CHECK(!addr.IsValid());
|
||||
BOOST_REQUIRE(s.empty());
|
||||
}
|
||||
|
||||
// prior to PR #14728, this test triggers an undefined behavior
|
||||
BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test)
|
||||
{
|
||||
|
||||
@@ -38,4 +38,7 @@ static const int INVALID_CB_NO_BAN_VERSION = 70015;
|
||||
//! "wtxidrelay" command for wtxid-based relay starts with this version
|
||||
static const int WTXID_RELAY_VERSION = 70016;
|
||||
|
||||
// Make sure that none of the values above collide with
|
||||
// `SERIALIZE_TRANSACTION_NO_WITNESS` or `ADDRV2_FORMAT`.
|
||||
|
||||
#endif // BITCOIN_VERSION_H
|
||||
|
||||
Reference in New Issue
Block a user