Skip to content

Commit 0f230bf

Browse files
committed
evo: recognize and store I2P and onion domains in ExtNetInfo
1 parent 7cd7a98 commit 0f230bf

File tree

2 files changed

+65
-7
lines changed

2 files changed

+65
-7
lines changed

src/evo/netinfo.cpp

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ static const CService empty_service{CService()};
1717

1818
static constexpr std::string_view SAFE_CHARS_IPV4{"1234567890."};
1919
static constexpr std::string_view SAFE_CHARS_IPV4_6{"abcdefABCDEF1234567890.:[]"};
20+
static constexpr std::string_view SAFE_CHARS_RFC1035{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-"};
21+
static constexpr std::array<std::string_view, 2> TLDS_SPECIAL{".i2p", ".onion"};
2022

2123
bool IsNodeOnMainnet() { return Params().NetworkIDString() == CBaseChainParams::MAIN; }
2224
const CChainParams& MainParams()
@@ -31,6 +33,17 @@ bool MatchCharsFilter(std::string_view input, std::string_view filter)
3133
{
3234
return std::all_of(input.begin(), input.end(), [&filter](char c) { return filter.find(c) != std::string_view::npos; });
3335
}
36+
37+
template <typename T1>
38+
bool MatchSuffix(const std::string& str, const T1& list)
39+
{
40+
if (str.empty()) return false;
41+
for (const auto& suffix : list) {
42+
if (suffix.size() > str.size()) continue;
43+
if (std::equal(suffix.rbegin(), suffix.rend(), str.rbegin())) return true;
44+
}
45+
return false;
46+
}
3447
} // anonymous namespace
3548

3649
bool NetInfoEntry::operator==(const NetInfoEntry& rhs) const
@@ -351,13 +364,16 @@ NetInfoStatus ExtNetInfo::ValidateService(const CService& service, bool is_prima
351364
if (!service.IsValid()) {
352365
return NetInfoStatus::BadAddress;
353366
}
354-
if (!service.IsCJDNS() && !service.IsIPv4() && !service.IsIPv6()) {
367+
if (!service.IsCJDNS() && !service.IsI2P() && !service.IsIPv4() && !service.IsIPv6() && !service.IsTor()) {
355368
return NetInfoStatus::BadType;
356369
}
357370
if (Params().RequireRoutableExternalIP() && !service.IsRoutable()) {
358371
return NetInfoStatus::NotRoutable;
359372
}
360-
if (IsBadPort(service.GetPort()) || service.GetPort() == 0) {
373+
if (service.IsI2P() && service.GetPort() != I2P_SAM31_PORT) {
374+
// I2P SAM 3.1 and earlier don't support arbitrary ports
375+
return NetInfoStatus::BadPort;
376+
} else if (!service.IsI2P() && (IsBadPort(service.GetPort()) || service.GetPort() == 0)) {
361377
return NetInfoStatus::BadPort;
362378
}
363379
if (is_primary) {
@@ -380,14 +396,39 @@ NetInfoStatus ExtNetInfo::AddEntry(const uint8_t purpose, const std::string& inp
380396
std::string addr;
381397
uint16_t port{0};
382398
SplitHostPort(input, port, addr);
383-
// Contains invalid characters, unlikely to pass Lookup(), fast-fail
399+
400+
// Primary addresses are subject to stricter validation rules
401+
const bool is_primary{m_data.find(purpose) == m_data.end()};
402+
384403
if (!MatchCharsFilter(addr, SAFE_CHARS_IPV4_6)) {
385-
return NetInfoStatus::BadInput;
404+
if (!MatchCharsFilter(addr, SAFE_CHARS_RFC1035)) {
405+
// Neither IP:port safe nor domain-safe, we can safely assume it's bad input
406+
return NetInfoStatus::BadInput;
407+
}
408+
409+
// Not IP:port safe but domain safe
410+
if (is_primary) {
411+
// Domains are not allowed as primary addresses
412+
return NetInfoStatus::BadType;
413+
} else if (MatchSuffix(addr, TLDS_SPECIAL)) {
414+
// Special domain, try storing it as CService
415+
CNetAddr netaddr;
416+
if (netaddr.SetSpecial(addr)) {
417+
const CService service{netaddr, port};
418+
const auto ret{ValidateService(service, /*is_primary=*/false)};
419+
if (ret == NetInfoStatus::Success) {
420+
return ProcessCandidate(purpose, NetInfoEntry{service});
421+
}
422+
return ret; /* ValidateService() failed */
423+
}
424+
}
425+
return NetInfoStatus::BadInput; /* CService::SetSpecial() failed */
386426
}
387427

428+
// IP:port safe, try to parse it as IP:port
388429
if (auto service_opt{Lookup(addr, /*portDefault=*/port, /*fAllowLookup=*/false)}) {
389430
const auto service{MaybeFlipIPv6toCJDNS(*service_opt)};
390-
const auto ret{ValidateService(service, /*is_primary=*/m_data.find(purpose) == m_data.end())};
431+
const auto ret{ValidateService(service, is_primary)};
391432
if (ret == NetInfoStatus::Success) {
392433
return ProcessCandidate(purpose, NetInfoEntry{service});
393434
}

src/test/evo_netinfo_tests.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ static const TestVectors vals_main{
3535
// - Non-IPv4 addresses are prohibited in MnNetInfo
3636
// - The first address must be IPv4 and therefore is not allowed in ExtNetInfo
3737
{{Purpose::CORE_P2P, "[2606:4700:4700::1111]:9999"}, NetInfoStatus::BadInput, NetInfoStatus::BadType},
38-
// Domains are not allowed
39-
{{Purpose::CORE_P2P, "example.com:9999"}, NetInfoStatus::BadInput, NetInfoStatus::BadInput},
38+
// - Domains are not allowed in MnNetInfo
39+
// - The first address must be IPv4 and therefore is not allowed in ExtNetInfo
40+
{{Purpose::CORE_P2P, "example.com:9999"}, NetInfoStatus::BadInput, NetInfoStatus::BadType},
4041
// Incorrect IPv4 address
4142
{{Purpose::CORE_P2P, "1.1.1.256:9999"}, NetInfoStatus::BadInput, NetInfoStatus::BadInput},
4243
// Missing address
@@ -186,11 +187,21 @@ static const TestVectors vals_reg{
186187

187188
enum class ExpectedType : uint8_t {
188189
CJDNS,
190+
I2P,
191+
Tor,
189192
};
190193

191194
static const std::vector<std::tuple</*type=*/ExpectedType, /*input=*/std::string, /*expected_ret=*/NetInfoStatus>> nonprimary_vals{
192195
// CJDNS is supported in ExtNetInfo
193196
{ExpectedType::CJDNS, "[fc00:3344:5566:7788:9900:aabb:ccdd:eeff]:9998", NetInfoStatus::Success},
197+
// ExtNetInfo can store I2P addresses as long as it uses port 0
198+
{ExpectedType::I2P, "udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p:0", NetInfoStatus::Success},
199+
// ExtNetInfo can store onion addresses
200+
{ExpectedType::Tor, "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:9998", NetInfoStatus::Success},
201+
// ExtNetInfo can store I2P addresses but non-zero ports are not allowed
202+
{ExpectedType::I2P, "udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p:9998", NetInfoStatus::BadPort},
203+
// ExtNetInfo can store onion addresses but zero ports are not allowed
204+
{ExpectedType::Tor, "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:0", NetInfoStatus::BadPort},
194205
};
195206

196207
BOOST_FIXTURE_TEST_CASE(mnnetinfo_rules_reg, RegTestingSetup) { TestMnNetInfo(vals_reg); }
@@ -245,6 +256,12 @@ BOOST_FIXTURE_TEST_CASE(extnetinfo_rules_reg, RegTestingSetup)
245256
case ExpectedType::CJDNS:
246257
BOOST_CHECK(service.IsCJDNS());
247258
break;
259+
case ExpectedType::I2P:
260+
BOOST_CHECK(service.IsI2P());
261+
break;
262+
case ExpectedType::Tor:
263+
BOOST_CHECK(service.IsTor());
264+
break;
248265
}
249266
}
250267
}

0 commit comments

Comments
 (0)