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 Sep 17, 2020
1 parent fe42411 commit e0d7357
Show file tree
Hide file tree
Showing 5 changed files with 389 additions and 3 deletions.
55 changes: 55 additions & 0 deletions src/netaddress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
134 changes: 132 additions & 2 deletions src/netaddress.h
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -177,7 +189,11 @@ class CNetAddr
template <typename Stream>
void Serialize(Stream& s) const
{
SerializeV1Stream(s);
if (s.GetVersion() & ADDRV2_FORMAT) {
SerializeV2Stream(s);
} else {
SerializeV1Stream(s);
}
}

/**
Expand All @@ -186,17 +202,53 @@ class CNetAddr
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 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.
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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
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
Loading

0 comments on commit e0d7357

Please sign in to comment.