Skip to content

Commit 7be6ff6

Browse files
vasilderiknylund
andcommitted
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). Co-authored-by: eriknylund <[email protected]>
1 parent e0d7357 commit 7be6ff6

File tree

7 files changed

+365
-57
lines changed

7 files changed

+365
-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: 198 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,80 @@ 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 const unsigned char VERSION[] = {3};
201+
static constexpr size_t TOTAL_LEN = ADDR_TORV3_SIZE + CHECKSUM_LEN + sizeof(VERSION);
202+
203+
static void Checksum(Span<const uint8_t> addr_pubkey, uint8_t (&checksum)[CHECKSUM_LEN])
204+
{
205+
// TORv3 CHECKSUM = H(".onion checksum" | PUBKEY | VERSION)[:2]
206+
static const unsigned char prefix[] = ".onion checksum";
207+
static constexpr size_t prefix_len = 15;
208+
209+
SHA3_256 hasher;
210+
211+
hasher.Write(MakeSpan(prefix).first(prefix_len));
212+
hasher.Write(addr_pubkey);
213+
hasher.Write(VERSION);
214+
215+
uint8_t checksum_full[SHA3_256::OUTPUT_SIZE];
216+
217+
hasher.Finalize(checksum_full);
218+
219+
memcpy(checksum, checksum_full, sizeof(checksum));
220+
}
221+
222+
}; // namespace torv3
223+
153224
/**
154-
* Parse a TORv2 address and set this object to it.
225+
* Parse a TOR address and set this object to it.
155226
*
156227
* @returns Whether or not the operation was successful.
157228
*
158229
* @see CNetAddr::IsTor()
159230
*/
160-
bool CNetAddr::SetSpecial(const std::string &strName)
231+
bool CNetAddr::SetSpecial(const std::string& str)
161232
{
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) {
233+
static const char* suffix{".onion"};
234+
static constexpr size_t suffix_len{6};
235+
236+
if (!ValidAsCString(str) || str.size() <= suffix_len ||
237+
str.substr(str.size() - suffix_len) != suffix) {
238+
return false;
239+
}
240+
241+
bool invalid;
242+
const auto& input = DecodeBase32(str.substr(0, str.size() - suffix_len).c_str(), &invalid);
243+
244+
if (invalid) {
245+
return false;
246+
}
247+
248+
switch (input.size()) {
249+
case ADDR_TORV2_SIZE:
250+
m_net = NET_ONION;
251+
m_addr.assign(input.begin(), input.end());
252+
return true;
253+
case torv3::TOTAL_LEN: {
254+
Span<const uint8_t> input_pubkey{input.data(), ADDR_TORV3_SIZE};
255+
Span<const uint8_t> input_checksum{input.data() + ADDR_TORV3_SIZE, torv3::CHECKSUM_LEN};
256+
Span<const uint8_t> input_version{input.data() + ADDR_TORV3_SIZE + torv3::CHECKSUM_LEN, sizeof(torv3::VERSION)};
257+
258+
uint8_t calculated_checksum[torv3::CHECKSUM_LEN];
259+
torv3::Checksum(input_pubkey, calculated_checksum);
260+
261+
if (input_checksum != calculated_checksum || input_version != torv3::VERSION) {
165262
return false;
166263
}
264+
167265
m_net = NET_ONION;
168-
m_addr.assign(vchAddr.begin(), vchAddr.end());
266+
m_addr.assign(input_pubkey.begin(), input_pubkey.end());
169267
return true;
170268
}
269+
}
270+
171271
return false;
172272
}
173273

@@ -284,13 +384,21 @@ bool CNetAddr::IsHeNet() const
284384
}
285385

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

392+
/**
393+
* Check whether this object represents an I2P address.
394+
*/
395+
bool CNetAddr::IsI2P() const { return m_net == NET_I2P; }
396+
397+
/**
398+
* Check whether this object represents a CJDNS address.
399+
*/
400+
bool CNetAddr::IsCJDNS() const { return m_net == NET_CJDNS; }
401+
294402
bool CNetAddr::IsLocal() const
295403
{
296404
// IPv4 loopback (127.0.0.0/8 or 0.0.0.0/8)
@@ -377,28 +485,72 @@ enum Network CNetAddr::GetNetwork() const
377485
return m_net;
378486
}
379487

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

404556
std::string CNetAddr::ToString() const
@@ -477,21 +629,22 @@ uint32_t CNetAddr::GetLinkedIPv4() const
477629
assert(false);
478630
}
479631

480-
uint32_t CNetAddr::GetNetClass() const {
481-
uint32_t net_class = NET_IPV6;
482-
if (IsLocal()) {
483-
net_class = 255;
484-
}
632+
uint32_t CNetAddr::GetNetClass() const
633+
{
634+
// Make sure that if we return NET_IPV6, then IsIPv6() is true. The callers expect that.
635+
636+
// Check for "internal" first because such addresses are also !IsRoutable()
637+
// and we don't want to return NET_UNROUTABLE in that case.
485638
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;
639+
return NET_INTERNAL;
493640
}
494-
return net_class;
641+
if (!IsRoutable()) {
642+
return NET_UNROUTABLE;
643+
}
644+
if (HasLinkedIPv4()) {
645+
return NET_IPV4;
646+
}
647+
return m_net;
495648
}
496649

497650
uint32_t CNetAddr::GetMappedAS(const std::vector<bool> &asmap) const {
@@ -566,7 +719,7 @@ std::vector<unsigned char> CNetAddr::GetGroup(const std::vector<bool> &asmap) co
566719
vchRet.push_back((ipv4 >> 24) & 0xFF);
567720
vchRet.push_back((ipv4 >> 16) & 0xFF);
568721
return vchRet;
569-
} else if (IsTor()) {
722+
} else if (IsTor() || IsI2P() || IsCJDNS()) {
570723
nBits = 4;
571724
} else if (IsHeNet()) {
572725
// for he.net, use /36 groups
@@ -791,7 +944,7 @@ std::string CService::ToStringPort() const
791944

792945
std::string CService::ToStringIPPort() const
793946
{
794-
if (IsIPv4() || IsTor() || IsInternal()) {
947+
if (IsIPv4() || IsTor() || IsI2P() || IsInternal()) {
795948
return ToStringIP() + ":" + ToStringPort();
796949
} else {
797950
return "[" + ToStringIP() + "]:" + ToStringPort();

0 commit comments

Comments
 (0)