Skip to content

Commit

Permalink
net: recognize TORv3/I2P/CJDNS networks
Browse files Browse the repository at this point in the history
Recognizing addresses from those networks allows us to accept and gossip
them, even though we don't know how to connect to them (yet).

Co-authored-by: eriknylund <erik@daychanged.com>
  • Loading branch information
2 people authored and furszy committed Aug 10, 2021
1 parent 8e50853 commit a237ba4
Show file tree
Hide file tree
Showing 7 changed files with 367 additions and 58 deletions.
7 changes: 7 additions & 0 deletions src/crypto/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ void static inline WriteLE64(unsigned char* ptr, uint64_t x)
memcpy(ptr, (char*)&v, 8);
}

uint16_t static inline ReadBE16(const unsigned char* ptr)
{
uint16_t x;
memcpy((char*)&x, ptr, 2);
return be16toh(x);
}

uint32_t static inline ReadBE32(const unsigned char* ptr)
{
uint32_t x;
Expand Down
243 changes: 198 additions & 45 deletions src/netaddress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "crypto/common.h"
#include "crypto/sha3.h"
#include "netaddress.h"
#include "hash.h"
#include "prevector.h"
#include "tinyformat.h"
#include "util/asmap.h"
#include "util/string.h"
Expand All @@ -29,7 +32,18 @@ CNetAddr::BIP155Network CNetAddr::GetBIP155Network() const
case NET_IPV6:
return BIP155Network::IPV6;
case NET_ONION:
return BIP155Network::TORV2;
switch (m_addr.size()) {
case ADDR_TORV2_SIZE:
return BIP155Network::TORV2;
case ADDR_TORV3_SIZE:
return BIP155Network::TORV3;
default:
assert(false);
}
case NET_I2P:
return BIP155Network::I2P;
case NET_CJDNS:
return BIP155Network::CJDNS;
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
Expand Down Expand Up @@ -66,6 +80,30 @@ bool CNetAddr::SetNetFromBIP155Network(uint8_t possible_bip155_net, size_t addre
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 addresses with unknown network ids (maybe from the future).
Expand All @@ -92,7 +130,13 @@ void CNetAddr::SetIP(const CNetAddr& ipIn)
assert(ipIn.m_addr.size() == ADDR_IPV6_SIZE);
break;
case NET_ONION:
assert(ipIn.m_addr.size() == ADDR_TORV2_SIZE);
assert(ipIn.m_addr.size() == ADDR_TORV2_SIZE || ipIn.m_addr.size() == ADDR_TORV3_SIZE);
break;
case NET_I2P:
assert(ipIn.m_addr.size() == ADDR_I2P_SIZE);
break;
case NET_CJDNS:
assert(ipIn.m_addr.size() == ADDR_CJDNS_SIZE);
break;
case NET_INTERNAL:
assert(ipIn.m_addr.size() == ADDR_INTERNAL_SIZE);
Expand Down Expand Up @@ -150,24 +194,80 @@ bool CNetAddr::SetInternal(const std::string &name)
return true;
}

namespace torv3 {
// https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt#n2135
static constexpr size_t CHECKSUM_LEN = 2;
static const unsigned char VERSION[] = {3};
static constexpr size_t TOTAL_LEN = ADDR_TORV3_SIZE + CHECKSUM_LEN + sizeof(VERSION);

static void Checksum(Span<const uint8_t> addr_pubkey, uint8_t (&checksum)[CHECKSUM_LEN])
{
// TORv3 CHECKSUM = H(".onion checksum" | PUBKEY | VERSION)[:2]
static const unsigned char prefix[] = ".onion checksum";
static constexpr size_t prefix_len = 15;

SHA3_256 hasher;

hasher.Write(MakeSpan(prefix).first(prefix_len));
hasher.Write(addr_pubkey);
hasher.Write(VERSION);

uint8_t checksum_full[SHA3_256::OUTPUT_SIZE];

hasher.Finalize(checksum_full);

memcpy(checksum, checksum_full, sizeof(checksum));
}

}; // namespace torv3

/**
* Parse a TORv2 address and set this object to it.
* Parse a TOR address and set this object to it.
*
* @returns Whether or not the operation was successful.
*
* @see CNetAddr::IsTor()
*/
bool CNetAddr::SetSpecial(const std::string& strName)
bool CNetAddr::SetSpecial(const std::string& str)
{
if (strName.size()>6 && strName.substr(strName.size() - 6, 6) == ".onion") {
std::vector<unsigned char> vchAddr = DecodeBase32(strName.substr(0, strName.size() - 6).c_str());
if (vchAddr.size() != ADDR_TORV2_SIZE) {
static const char* suffix{".onion"};
static constexpr size_t suffix_len{6};

if (!ValidAsCString(str) || str.size() <= suffix_len ||
str.substr(str.size() - suffix_len) != suffix) {
return false;
}

bool invalid;
const auto& input = DecodeBase32(str.substr(0, str.size() - suffix_len).c_str(), &invalid);

if (invalid) {
return false;
}

switch (input.size()) {
case ADDR_TORV2_SIZE:
m_net = NET_ONION;
m_addr.assign(input.begin(), input.end());
return true;
case torv3::TOTAL_LEN: {
Span<const uint8_t> input_pubkey{input.data(), ADDR_TORV3_SIZE};
Span<const uint8_t> input_checksum{input.data() + ADDR_TORV3_SIZE, torv3::CHECKSUM_LEN};
Span<const uint8_t> input_version{input.data() + ADDR_TORV3_SIZE + torv3::CHECKSUM_LEN, sizeof(torv3::VERSION)};

uint8_t calculated_checksum[torv3::CHECKSUM_LEN];
torv3::Checksum(input_pubkey, calculated_checksum);

if (input_checksum != calculated_checksum || input_version != torv3::VERSION) {
return false;
}

m_net = NET_ONION;
m_addr.assign(vchAddr.begin(), vchAddr.end());
m_addr.assign(input_pubkey.begin(), input_pubkey.end());
return true;
}
}

return false;
}

Expand Down Expand Up @@ -284,13 +384,21 @@ bool CNetAddr::IsHeNet() const
}

/**
* @returns Whether or not this is a dummy address that maps an onion address
* into IPv6.
*
* Check whether this object represents a TOR address.
* @see CNetAddr::SetSpecial(const std::string &)
*/
bool CNetAddr::IsTor() const { return m_net == NET_ONION; }

/**
* Check whether this object represents an I2P address.
*/
bool CNetAddr::IsI2P() const { return m_net == NET_I2P; }

/**
* Check whether this object represents a CJDNS address.
*/
bool CNetAddr::IsCJDNS() const { return m_net == NET_CJDNS; }

bool CNetAddr::IsLocal() const
{
// IPv4 loopback (127.0.0.0/8 or 0.0.0.0/8)
Expand Down Expand Up @@ -369,28 +477,72 @@ enum Network CNetAddr::GetNetwork() const
return m_net;
}

static std::string IPv6ToString(Span<const uint8_t> a)
{
assert(a.size() == ADDR_IPV6_SIZE);
// clang-format off
return strprintf("%x:%x:%x:%x:%x:%x:%x:%x",
ReadBE16(&a[0]),
ReadBE16(&a[2]),
ReadBE16(&a[4]),
ReadBE16(&a[6]),
ReadBE16(&a[8]),
ReadBE16(&a[10]),
ReadBE16(&a[12]),
ReadBE16(&a[14]));
// clang-format on
}

std::string CNetAddr::ToStringIP() const
{
if (IsTor())
return EncodeBase32(m_addr) + ".onion";
if (IsInternal())
switch (m_net) {
case NET_IPV4:
case NET_IPV6: {
CService serv(*this, 0);
struct sockaddr_storage sockaddr;
socklen_t socklen = sizeof(sockaddr);
if (serv.GetSockAddr((struct sockaddr*)&sockaddr, &socklen)) {
char name[1025] = "";
if (!getnameinfo((const struct sockaddr*)&sockaddr, socklen, name,
sizeof(name), nullptr, 0, NI_NUMERICHOST))
return std::string(name);
}
if (m_net == NET_IPV4) {
return strprintf("%u.%u.%u.%u", m_addr[0], m_addr[1], m_addr[2], m_addr[3]);
}
return IPv6ToString(m_addr);
}
case NET_ONION:
switch (m_addr.size()) {
case ADDR_TORV2_SIZE:
return EncodeBase32(m_addr) + ".onion";
case ADDR_TORV3_SIZE: {

uint8_t checksum[torv3::CHECKSUM_LEN];
torv3::Checksum(m_addr, checksum);

// TORv3 onion_address = base32(PUBKEY | CHECKSUM | VERSION) + ".onion"
prevector<torv3::TOTAL_LEN, uint8_t> address{m_addr.begin(), m_addr.end()};
address.insert(address.end(), checksum, checksum + torv3::CHECKSUM_LEN);
address.insert(address.end(), torv3::VERSION, torv3::VERSION + sizeof(torv3::VERSION));

return EncodeBase32(address) + ".onion";
}
default:
assert(false);
}
case NET_I2P:
return EncodeBase32(m_addr, false /* don't pad with = */) + ".b32.i2p";
case NET_CJDNS:
return IPv6ToString(m_addr);
case NET_INTERNAL:
return EncodeBase32(m_addr) + ".internal";
CService serv(*this, 0);
struct sockaddr_storage sockaddr;
socklen_t socklen = sizeof(sockaddr);
if (serv.GetSockAddr((struct sockaddr*)&sockaddr, &socklen)) {
char name[1025] = "";
if (!getnameinfo((const struct sockaddr*)&sockaddr, socklen, name, sizeof(name), nullptr, 0, NI_NUMERICHOST))
return std::string(name);
}
if (IsIPv4())
return strprintf("%u.%u.%u.%u", m_addr[0], m_addr[1], m_addr[2], m_addr[3]);
assert(IsIPv6());
return strprintf("%x:%x:%x:%x:%x:%x:%x:%x",
m_addr[0] << 8 | m_addr[1], m_addr[2] << 8 | m_addr[3],
m_addr[4] << 8 | m_addr[5], m_addr[6] << 8 | m_addr[7],
m_addr[8] << 8 | m_addr[9], m_addr[10] << 8 | m_addr[11],
m_addr[12] << 8 | m_addr[13], m_addr[14] << 8 | m_addr[15]);
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);
}

std::string CNetAddr::ToString() const
Expand Down Expand Up @@ -464,21 +616,22 @@ uint32_t CNetAddr::GetLinkedIPv4() const
assert(false);
}

uint32_t CNetAddr::GetNetClass() const {
uint32_t net_class = NET_IPV6;
if (IsLocal()) {
net_class = 255;
}
uint32_t CNetAddr::GetNetClass() const
{
// Make sure that if we return NET_IPV6, then IsIPv6() is true. The callers expect that.

// Check for "internal" first because such addresses are also !IsRoutable()
// and we don't want to return NET_UNROUTABLE in that case.
if (IsInternal()) {
net_class = NET_INTERNAL;
} else if (!IsRoutable()) {
net_class = NET_UNROUTABLE;
} else if (HasLinkedIPv4()) {
net_class = NET_IPV4;
} else if (IsTor()) {
net_class = NET_ONION;
return NET_INTERNAL;
}
return net_class;
if (!IsRoutable()) {
return NET_UNROUTABLE;
}
if (HasLinkedIPv4()) {
return NET_IPV4;
}
return m_net;
}

uint32_t CNetAddr::GetMappedAS(const std::vector<bool> &asmap) const {
Expand Down Expand Up @@ -553,7 +706,7 @@ std::vector<unsigned char> CNetAddr::GetGroup(const std::vector<bool> &asmap) co
vchRet.push_back((ipv4 >> 24) & 0xFF);
vchRet.push_back((ipv4 >> 16) & 0xFF);
return vchRet;
} else if (IsTor()) {
} else if (IsTor() || IsI2P() || IsCJDNS()) {
nBits = 4;
} else if (IsHeNet()) {
// for he.net, use /36 groups
Expand Down Expand Up @@ -768,7 +921,7 @@ std::string CService::ToStringPort() const

std::string CService::ToStringIPPort() const
{
if (IsIPv4() || IsTor() || IsInternal()) {
if (IsIPv4() || IsTor() || IsI2P() || IsInternal()) {
return ToStringIP() + ":" + ToStringPort();
} else {
return "[" + ToStringIP() + "]:" + ToStringPort();
Expand Down
Loading

0 comments on commit a237ba4

Please sign in to comment.