Skip to content

Commit

Permalink
net: CNetAddr: add support to (un)serialize as ADDRv2
Browse files Browse the repository at this point in the history
Co-authored-by: Carl Dong <contact@carldong.me>
  • Loading branch information
vasild and dongcarl committed Aug 14, 2020
1 parent 8b7f2c1 commit e885d61
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 4 deletions.
90 changes: 90 additions & 0 deletions src/netaddress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include <algorithm>
#include <cstdint>
#include <ios>
#include <tuple>

#ifdef WIN32
Expand All @@ -20,6 +21,95 @@
#endif

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:
switch (m_addr.size()) {
case ADDR_TORV2_SIZE:
return Bip155Network::TORV2;
case ADDR_TORV3_SIZE:
return Bip155Network::TORV3;
default:
assert(!"Unexpected TOR address size");
}
case NET_I2P:
return Bip155Network::I2P;
case NET_CJDNS:
return Bip155Network::CJDNS;
case NET_UNROUTABLE:
case NET_INTERNAL:
case NET_MAX:
assert(!"NET_UNROUTABLE, NET_INTERNAL and NET_MAX cannot be represented as "
"BIP155 network id");
} // no default case, so the compiler can warn about missing cases

assert(false);
}

bool CNetAddr::RecognizeBIP155Network(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));
case Bip155Network::TORV3:
if (address_size == ADDR_TORV3_SIZE) {
m_net = NET_ONION;
return true;
}
throw std::ios_base::failure(
strprintf("BIP155 TORv3 address with length %u (should be %u)", address_size,
ADDR_TORV3_SIZE));
case Bip155Network::I2P:
if (address_size == ADDR_I2P_SIZE) {
m_net = NET_I2P;
return true;
}
throw std::ios_base::failure(
strprintf("BIP155 I2P address with length %u (should be %u)", address_size,
ADDR_I2P_SIZE));
case Bip155Network::CJDNS:
if (address_size == ADDR_CJDNS_SIZE) {
m_net = NET_CJDNS;
return true;
}
throw std::ios_base::failure(
strprintf("BIP155 CJDNS address with length %u (should be %u)", address_size,
ADDR_CJDNS_SIZE));
}

// Don't throw on unknown network ids (from the future). Instead silently drop
// them and consume subsequent ones which may be known to us.
return false;
}

/**
* Construct an unspecified IPv6 network address (::/128).
Expand Down
131 changes: 127 additions & 4 deletions src/netaddress.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,21 @@
#include <compat.h>
#include <prevector.h>
#include <serialize.h>
#include <tinyformat.h>

#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`
Expand All @@ -38,9 +48,15 @@ enum Network
/// IPv6
NET_IPV6,

/// TORv2
/// TOR (v2 or v3)
NET_ONION,

/// I2P
NET_I2P,

/// CJDNS
NET_CJDNS,

/// 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
Expand Down Expand Up @@ -75,6 +91,15 @@ static constexpr size_t ADDR_IPV6_SIZE = 16;
/// Size of TORv2 address (in bytes).
static constexpr size_t ADDR_TORV2_SIZE = 10;

/// Size of TORv3 address (in bytes).
static constexpr size_t ADDR_TORV3_SIZE = 32;

/// Size of I2P address (in bytes).
static constexpr size_t ADDR_I2P_SIZE = 32;

/// Size of CJDNS address (in bytes).
static constexpr size_t ADDR_CJDNS_SIZE = 16;

/// Size of "internal" (NET_INTERNAL) address (in bytes).
static constexpr size_t ADDR_INTERNAL_SIZE = 10;

Expand Down Expand Up @@ -171,26 +196,67 @@ class CNetAddr
template <typename Stream>
void Serialize(Stream& s) const
{
SerializeV1Stream(s);
if (s.GetVersion() & ADDRv2_FORMAT) {
SerializeV2Stream(s);
} else {
SerializeV1Stream(s);
}
}

/**
* Unserialize from a stream.
* @throws std::ios_base::failure if the data is invalid and cannot be unserialized
*/
template <typename Stream>
void Unserialize(Stream& s)
{
UnserializeV1Stream(s);
if (s.GetVersion() & ADDRv2_FORMAT) {
UnserializeV2Stream(s);
} else {
UnserializeV1Stream(s);
}
}

friend class CSubNet;

private:
/**
* BIP155 network id.
*/
enum Bip155Network : uint8_t {
IPV4 = 1,
IPV6 = 2,
TORV2 = 3,
TORV3 = 4,
I2P = 5,
CJDNS = 6,
};

/**
* Size of CNetAddr when serialized as ADDRv1 (pre-BIP155) (in bytes).
*/
static constexpr size_t V1_SERIALIZATION_SIZE = 16;

/**
* 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;

/**
* Deduce the BIP155 network id of this address.
*/
Bip155Network GetBip155Network() const;

/**
* Deduce the network type from BIP155 network id and size and set `m_net`.
* @returns true if the network was recognized and `m_net` was set
* @throws std::ios_base::failure if the network is one of the BIP155 founding
* networks with wrong address size.
*/
bool RecognizeBIP155Network(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.
Expand All @@ -212,6 +278,8 @@ class CNetAddr
return;
case NET_ONION:
prefix_size = sizeof(TORV2_IN_IPV6_PREFIX);
// This will assert if we try to serialize TORv3 address (NET_ONION) as
// ADDRv1 (pre-BIP155). We never do.
assert(prefix_size + m_addr.size() == sizeof(arr));
memcpy(arr, TORV2_IN_IPV6_PREFIX, prefix_size);
memcpy(arr + prefix_size, m_addr.data(), m_addr.size());
Expand All @@ -223,9 +291,12 @@ class CNetAddr
memcpy(arr + prefix_size, m_addr.data(), m_addr.size());
return;
case NET_UNROUTABLE:
case NET_MAX:
memset(arr, 0x0, sizeof(arr));
return;
case NET_I2P:
case NET_CJDNS:
case NET_MAX:
assert(!"Cannot serialize address in ADDR format");
} // no default case, so the compiler can warn about missing cases

assert(false);
Expand All @@ -245,6 +316,24 @@ class CNetAddr
s << serialized;
}

/**
* Serialize as ADDRv2 / BIP155.
*/
template <typename Stream>
void SerializeV2Stream(Stream& s) const
{
if (IsValid()) {
s << static_cast<uint8_t>(GetBip155Network());
s << m_addr;
return;
}

// Lots of tests serialize a default-constructed `CNetAddr`.
// Serialize as 0s, unserializing this will come back as !IsValid().
s << (uint8_t)0x00;
s << std::vector<uint8_t>();
}

/**
* Unserialize from a pre-ADDRv2/BIP155 format from an array.
*/
Expand All @@ -267,6 +356,40 @@ class CNetAddr

UnserializeV1Array(serialized);
}

/**
* Unserialize from a ADDRv2 / BIP155 format.
* @throws std::ios_base::failure if the data is invalid and cannot be unserialized
*/
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 > %zu", address_size, MAX_ADDRv2_SIZE));
}

if (RecognizeBIP155Network(bip155_net, address_size)) {
m_addr.resize(address_size);
s >> MakeSpan(m_addr);
} else {
// If we receive an unknown BIP155 network id (from the future?) then
// ignore the address and mimic a default-constructed object which is
// !IsValid() and thus will not be gossiped, but continue reading next
// addresses from the stream.
s.ignore(address_size);
m_net = NET_IPV6;
m_addr.assign(ADDR_IPV6_SIZE, 0x0);
}

scopeId = 0;
}
};

class CSubNet
Expand Down
6 changes: 6 additions & 0 deletions src/primitives/transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
3 changes: 3 additions & 0 deletions src/version.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit e885d61

Please sign in to comment.