Skip to content

Commit b9c46e0

Browse files
committed
net: recognize TORv3/I2P/CJDNS networks
Recognizing addresses from those networks allows us to accept and gossip them, even though we don't know how to connect to them (yet).
1 parent e0d7357 commit b9c46e0

File tree

6 files changed

+364
-57
lines changed

6 files changed

+364
-57
lines changed

src/crypto/common.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ void static inline WriteLE64(unsigned char* ptr, uint64_t x)
5353
memcpy(ptr, (char*)&v, 8);
5454
}
5555

56+
uint16_t static inline ReadBE16(const unsigned char* ptr)
57+
{
58+
uint16_t x;
59+
memcpy((char*)&x, ptr, 2);
60+
return be16toh(x);
61+
}
62+
5663
uint32_t static inline ReadBE32(const unsigned char* ptr)
5764
{
5865
uint32_t x;

src/netaddress.cpp

Lines changed: 200 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55

66
#include <netaddress.h>
77

8+
#include <crypto/common.h>
9+
#include <crypto/sha3.h>
810
#include <hash.h>
11+
#include <prevector.h>
912
#include <tinyformat.h>
1013
#include <util/asmap.h>
1114
#include <util/strencodings.h>
@@ -29,7 +32,18 @@ CNetAddr::BIP155Network CNetAddr::GetBIP155Network() const
2932
case NET_IPV6:
3033
return BIP155Network::IPV6;
3134
case NET_ONION:
32-
return BIP155Network::TORV2;
35+
switch (m_addr.size()) {
36+
case ADDR_TORV2_SIZE:
37+
return BIP155Network::TORV2;
38+
case ADDR_TORV3_SIZE:
39+
return BIP155Network::TORV3;
40+
default:
41+
assert(false);
42+
}
43+
case NET_I2P:
44+
return BIP155Network::I2P;
45+
case NET_CJDNS:
46+
return BIP155Network::CJDNS;
3347
case NET_INTERNAL: // should have been handled before calling this function
3448
case NET_UNROUTABLE: // m_net is never and should not be set to NET_UNROUTABLE
3549
case NET_MAX: // m_net is never and should not be set to NET_MAX
@@ -66,6 +80,30 @@ bool CNetAddr::SetNetFromBIP155Network(uint8_t possible_bip155_net, size_t addre
6680
throw std::ios_base::failure(
6781
strprintf("BIP155 TORv2 address with length %u (should be %u)", address_size,
6882
ADDR_TORV2_SIZE));
83+
case BIP155Network::TORV3:
84+
if (address_size == ADDR_TORV3_SIZE) {
85+
m_net = NET_ONION;
86+
return true;
87+
}
88+
throw std::ios_base::failure(
89+
strprintf("BIP155 TORv3 address with length %u (should be %u)", address_size,
90+
ADDR_TORV3_SIZE));
91+
case BIP155Network::I2P:
92+
if (address_size == ADDR_I2P_SIZE) {
93+
m_net = NET_I2P;
94+
return true;
95+
}
96+
throw std::ios_base::failure(
97+
strprintf("BIP155 I2P address with length %u (should be %u)", address_size,
98+
ADDR_I2P_SIZE));
99+
case BIP155Network::CJDNS:
100+
if (address_size == ADDR_CJDNS_SIZE) {
101+
m_net = NET_CJDNS;
102+
return true;
103+
}
104+
throw std::ios_base::failure(
105+
strprintf("BIP155 CJDNS address with length %u (should be %u)", address_size,
106+
ADDR_CJDNS_SIZE));
69107
}
70108

71109
// Don't throw on addresses with unknown network ids (maybe from the future).
@@ -92,7 +130,13 @@ void CNetAddr::SetIP(const CNetAddr& ipIn)
92130
assert(ipIn.m_addr.size() == ADDR_IPV6_SIZE);
93131
break;
94132
case NET_ONION:
95-
assert(ipIn.m_addr.size() == ADDR_TORV2_SIZE);
133+
assert(ipIn.m_addr.size() == ADDR_TORV2_SIZE || ipIn.m_addr.size() == ADDR_TORV3_SIZE);
134+
break;
135+
case NET_I2P:
136+
assert(ipIn.m_addr.size() == ADDR_I2P_SIZE);
137+
break;
138+
case NET_CJDNS:
139+
assert(ipIn.m_addr.size() == ADDR_CJDNS_SIZE);
96140
break;
97141
case NET_INTERNAL:
98142
assert(ipIn.m_addr.size() == ADDR_INTERNAL_SIZE);
@@ -150,24 +194,82 @@ bool CNetAddr::SetInternal(const std::string &name)
150194
return true;
151195
}
152196

197+
namespace torv3 {
198+
// https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt#n2135
199+
static constexpr size_t CHECKSUM_LEN = 2;
200+
static constexpr size_t VERSION_LEN = 1;
201+
static const unsigned char VERSION[] = "\x03";
202+
static constexpr size_t TOTAL_LEN = ADDR_TORV3_SIZE + CHECKSUM_LEN + VERSION_LEN;
203+
204+
static void Checksum(Span<const uint8_t> addr_pubkey, uint8_t (&checksum)[CHECKSUM_LEN])
205+
{
206+
// TORv3 CHECKSUM = H(".onion checksum" | PUBKEY | VERSION)[:2]
207+
static const unsigned char prefix[] = ".onion checksum";
208+
static constexpr size_t prefix_len = 15;
209+
210+
SHA3_256 hasher;
211+
212+
hasher.Write(MakeSpan(prefix).first(prefix_len));
213+
hasher.Write(addr_pubkey);
214+
hasher.Write(MakeSpan(torv3::VERSION).first(torv3::VERSION_LEN));
215+
216+
uint8_t checksum_full[SHA3_256::OUTPUT_SIZE];
217+
218+
hasher.Finalize(checksum_full);
219+
220+
memcpy(checksum, checksum_full, sizeof(checksum));
221+
}
222+
223+
}; // namespace torv3
224+
153225
/**
154-
* Parse a TORv2 address and set this object to it.
226+
* Parse a TOR address and set this object to it.
155227
*
156228
* @returns Whether or not the operation was successful.
157229
*
158230
* @see CNetAddr::IsTor()
159231
*/
160-
bool CNetAddr::SetSpecial(const std::string &strName)
232+
bool CNetAddr::SetSpecial(const std::string& str)
161233
{
162-
if (strName.size()>6 && strName.substr(strName.size() - 6, 6) == ".onion") {
163-
std::vector<unsigned char> vchAddr = DecodeBase32(strName.substr(0, strName.size() - 6).c_str());
164-
if (vchAddr.size() != ADDR_TORV2_SIZE) {
234+
static const char* suffix{".onion"};
235+
static constexpr size_t suffix_len{6};
236+
237+
if (!ValidAsCString(str) || str.size() <= suffix_len ||
238+
str.substr(str.size() - suffix_len) != suffix) {
239+
return false;
240+
}
241+
242+
bool invalid;
243+
const auto& input = DecodeBase32(str.substr(0, str.size() - suffix_len).c_str(), &invalid);
244+
245+
if (invalid) {
246+
return false;
247+
}
248+
249+
switch (input.size()) {
250+
case ADDR_TORV2_SIZE:
251+
m_net = NET_ONION;
252+
m_addr.assign(input.begin(), input.end());
253+
return true;
254+
case torv3::TOTAL_LEN: {
255+
Span<const uint8_t> input_pubkey{input.data(), ADDR_TORV3_SIZE};
256+
Span<const uint8_t> input_checksum{input.data() + ADDR_TORV3_SIZE, torv3::CHECKSUM_LEN};
257+
Span<const uint8_t> input_version{input.data() + ADDR_TORV3_SIZE + torv3::CHECKSUM_LEN, torv3::VERSION_LEN};
258+
259+
uint8_t calculated_checksum[torv3::CHECKSUM_LEN];
260+
torv3::Checksum(input_pubkey, calculated_checksum);
261+
262+
if (input_checksum != calculated_checksum ||
263+
input_version != MakeSpan(torv3::VERSION).first(torv3::VERSION_LEN)) {
165264
return false;
166265
}
266+
167267
m_net = NET_ONION;
168-
m_addr.assign(vchAddr.begin(), vchAddr.end());
268+
m_addr.assign(input_pubkey.begin(), input_pubkey.end());
169269
return true;
170270
}
271+
}
272+
171273
return false;
172274
}
173275

@@ -284,13 +386,21 @@ bool CNetAddr::IsHeNet() const
284386
}
285387

286388
/**
287-
* @returns Whether or not this is a dummy address that maps an onion address
288-
* into IPv6.
289-
*
389+
* Check whether this object represents a TOR address.
290390
* @see CNetAddr::SetSpecial(const std::string &)
291391
*/
292392
bool CNetAddr::IsTor() const { return m_net == NET_ONION; }
293393

394+
/**
395+
* Check whether this object represents an I2P address.
396+
*/
397+
bool CNetAddr::IsI2P() const { return m_net == NET_I2P; }
398+
399+
/**
400+
* Check whether this object represents a CJDNS address.
401+
*/
402+
bool CNetAddr::IsCJDNS() const { return m_net == NET_CJDNS; }
403+
294404
bool CNetAddr::IsLocal() const
295405
{
296406
// IPv4 loopback (127.0.0.0/8 or 0.0.0.0/8)
@@ -377,28 +487,72 @@ enum Network CNetAddr::GetNetwork() const
377487
return m_net;
378488
}
379489

490+
static std::string IPv6ToString(Span<const uint8_t> a)
491+
{
492+
assert(a.size() == ADDR_IPV6_SIZE);
493+
// clang-format off
494+
return strprintf("%x:%x:%x:%x:%x:%x:%x:%x",
495+
ReadBE16(&a[0]),
496+
ReadBE16(&a[2]),
497+
ReadBE16(&a[4]),
498+
ReadBE16(&a[6]),
499+
ReadBE16(&a[8]),
500+
ReadBE16(&a[10]),
501+
ReadBE16(&a[12]),
502+
ReadBE16(&a[14]));
503+
// clang-format on
504+
}
505+
380506
std::string CNetAddr::ToStringIP() const
381507
{
382-
if (IsTor())
383-
return EncodeBase32(m_addr) + ".onion";
384-
if (IsInternal())
508+
switch (m_net) {
509+
case NET_IPV4:
510+
case NET_IPV6: {
511+
CService serv(*this, 0);
512+
struct sockaddr_storage sockaddr;
513+
socklen_t socklen = sizeof(sockaddr);
514+
if (serv.GetSockAddr((struct sockaddr*)&sockaddr, &socklen)) {
515+
char name[1025] = "";
516+
if (!getnameinfo((const struct sockaddr*)&sockaddr, socklen, name,
517+
sizeof(name), nullptr, 0, NI_NUMERICHOST))
518+
return std::string(name);
519+
}
520+
if (m_net == NET_IPV4) {
521+
return strprintf("%u.%u.%u.%u", m_addr[0], m_addr[1], m_addr[2], m_addr[3]);
522+
}
523+
return IPv6ToString(m_addr);
524+
}
525+
case NET_ONION:
526+
switch (m_addr.size()) {
527+
case ADDR_TORV2_SIZE:
528+
return EncodeBase32(m_addr) + ".onion";
529+
case ADDR_TORV3_SIZE: {
530+
531+
uint8_t checksum[torv3::CHECKSUM_LEN];
532+
torv3::Checksum(m_addr, checksum);
533+
534+
// TORv3 onion_address = base32(PUBKEY | CHECKSUM | VERSION) + ".onion"
535+
prevector<torv3::TOTAL_LEN, uint8_t> address{m_addr.begin(), m_addr.end()};
536+
address.insert(address.end(), checksum, checksum + torv3::CHECKSUM_LEN);
537+
address.insert(address.end(), torv3::VERSION, torv3::VERSION + torv3::VERSION_LEN);
538+
539+
return EncodeBase32(address) + ".onion";
540+
}
541+
default:
542+
assert(false);
543+
}
544+
case NET_I2P:
545+
return EncodeBase32(m_addr, false /* don't pad with = */) + ".b32.i2p";
546+
case NET_CJDNS:
547+
return IPv6ToString(m_addr);
548+
case NET_INTERNAL:
385549
return EncodeBase32(m_addr) + ".internal";
386-
CService serv(*this, 0);
387-
struct sockaddr_storage sockaddr;
388-
socklen_t socklen = sizeof(sockaddr);
389-
if (serv.GetSockAddr((struct sockaddr*)&sockaddr, &socklen)) {
390-
char name[1025] = "";
391-
if (!getnameinfo((const struct sockaddr*)&sockaddr, socklen, name, sizeof(name), nullptr, 0, NI_NUMERICHOST))
392-
return std::string(name);
393-
}
394-
if (IsIPv4())
395-
return strprintf("%u.%u.%u.%u", m_addr[0], m_addr[1], m_addr[2], m_addr[3]);
396-
assert(IsIPv6());
397-
return strprintf("%x:%x:%x:%x:%x:%x:%x:%x",
398-
m_addr[0] << 8 | m_addr[1], m_addr[2] << 8 | m_addr[3],
399-
m_addr[4] << 8 | m_addr[5], m_addr[6] << 8 | m_addr[7],
400-
m_addr[8] << 8 | m_addr[9], m_addr[10] << 8 | m_addr[11],
401-
m_addr[12] << 8 | m_addr[13], m_addr[14] << 8 | m_addr[15]);
550+
case NET_UNROUTABLE: // m_net is never and should not be set to NET_UNROUTABLE
551+
case NET_MAX: // m_net is never and should not be set to NET_MAX
552+
assert(false);
553+
} // no default case, so the compiler can warn about missing cases
554+
555+
assert(false);
402556
}
403557

404558
std::string CNetAddr::ToString() const
@@ -477,21 +631,22 @@ uint32_t CNetAddr::GetLinkedIPv4() const
477631
assert(false);
478632
}
479633

480-
uint32_t CNetAddr::GetNetClass() const {
481-
uint32_t net_class = NET_IPV6;
482-
if (IsLocal()) {
483-
net_class = 255;
484-
}
634+
uint32_t CNetAddr::GetNetClass() const
635+
{
636+
// Make sure that if we return NET_IPV6, then IsIPv6() is true. The callers expect that.
637+
638+
// Check for "internal" first because such addresses are also !IsRoutable()
639+
// and we don't want to return NET_UNROUTABLE in that case.
485640
if (IsInternal()) {
486-
net_class = NET_INTERNAL;
487-
} else if (!IsRoutable()) {
488-
net_class = NET_UNROUTABLE;
489-
} else if (HasLinkedIPv4()) {
490-
net_class = NET_IPV4;
491-
} else if (IsTor()) {
492-
net_class = NET_ONION;
641+
return NET_INTERNAL;
493642
}
494-
return net_class;
643+
if (!IsRoutable()) {
644+
return NET_UNROUTABLE;
645+
}
646+
if (HasLinkedIPv4()) {
647+
return NET_IPV4;
648+
}
649+
return m_net;
495650
}
496651

497652
uint32_t CNetAddr::GetMappedAS(const std::vector<bool> &asmap) const {
@@ -566,7 +721,7 @@ std::vector<unsigned char> CNetAddr::GetGroup(const std::vector<bool> &asmap) co
566721
vchRet.push_back((ipv4 >> 24) & 0xFF);
567722
vchRet.push_back((ipv4 >> 16) & 0xFF);
568723
return vchRet;
569-
} else if (IsTor()) {
724+
} else if (IsTor() || IsI2P() || IsCJDNS()) {
570725
nBits = 4;
571726
} else if (IsHeNet()) {
572727
// for he.net, use /36 groups
@@ -791,7 +946,7 @@ std::string CService::ToStringPort() const
791946

792947
std::string CService::ToStringIPPort() const
793948
{
794-
if (IsIPv4() || IsTor() || IsInternal()) {
949+
if (IsIPv4() || IsTor() || IsI2P() || IsInternal()) {
795950
return ToStringIP() + ":" + ToStringPort();
796951
} else {
797952
return "[" + ToStringIP() + "]:" + ToStringPort();

0 commit comments

Comments
 (0)