Line data Source code
1 : // Copyright (c) 2025 The Dash Core developers
2 : // Distributed under the MIT software license, see the accompanying
3 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 :
5 : #include <evo/netinfo.h>
6 :
7 : #include <chainparams.h>
8 : #include <evo/providertx.h>
9 : #include <netbase.h>
10 : #include <span.h>
11 : #include <util/check.h>
12 : #include <util/string.h>
13 : #include <util/system.h>
14 :
15 : #include <univalue.h>
16 :
17 : namespace {
18 : static std::unique_ptr<const CChainParams> g_main_params{nullptr};
19 : static std::once_flag g_main_params_flag;
20 :
21 : /** Maximum length of a label in a domain per RFC 1035 */
22 : static constexpr uint8_t DOMAIN_LABEL_MAX_LEN{63};
23 : /** Maximum possible length of a ASCII FQDN */
24 : static constexpr uint8_t DOMAIN_MAX_LEN{253};
25 : /** Minimum length of a FQDN */
26 : static constexpr uint8_t DOMAIN_MIN_LEN{3};
27 :
28 : static constexpr std::string_view SAFE_CHARS_ALPHA{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"};
29 : static constexpr std::string_view SAFE_CHARS_IPV4{"1234567890."};
30 : static constexpr std::string_view SAFE_CHARS_IPV4_6{"abcdefABCDEF1234567890.:[]"};
31 : static constexpr std::string_view SAFE_CHARS_RFC1035{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-"};
32 : static constexpr std::array<std::string_view, 13> TLDS_BAD{
33 : // ICANN resolution 2018.02.04.12
34 : ".mail",
35 : // Infrastructure TLD
36 : ".arpa",
37 : // RFC 6761
38 : ".example", ".invalid", ".localhost", ".test",
39 : // RFC 6762
40 : ".local",
41 : // RFC 6762, Appendix G
42 : ".corp", ".home", ".internal", ".intranet", ".lan", ".private",
43 : };
44 : static constexpr std::array<std::string_view, 2> TLDS_PRIVACY{".i2p", ".onion"};
45 :
46 1826 : bool MatchCharsFilter(std::string_view input, std::string_view filter)
47 : {
48 24300 : return std::all_of(input.begin(), input.end(), [&filter](char c) { return filter.find(c) != std::string_view::npos; });
49 : }
50 :
51 194 : bool MatchSuffix(const std::string& str, Span<const std::string_view> list)
52 : {
53 194 : if (str.empty()) return false;
54 1216 : for (const auto& suffix : list) {
55 1066 : if (suffix.size() > str.size()) continue;
56 1066 : if (std::string_view{str}.ends_with(suffix)) return true;
57 : }
58 150 : return false;
59 194 : }
60 :
61 6 : bool IsAllowedPlatformHTTPPort(uint16_t port)
62 : {
63 6 : switch (port) {
64 : case 443:
65 5 : return true;
66 : }
67 1 : return false;
68 6 : }
69 : } // anonymous namespace
70 :
71 8889 : bool IsNodeOnMainnet() { return Params().NetworkIDString() == CBaseChainParams::MAIN; }
72 :
73 4764 : bool IsServiceDeprecatedRPCEnabled()
74 : {
75 4764 : const auto args = gArgs.GetArgs("-deprecatedrpc");
76 4764 : return std::find(args.begin(), args.end(), "service") != args.end();
77 4764 : }
78 :
79 9385 : const CChainParams& MainParams()
80 : {
81 10005 : std::call_once(g_main_params_flag, [&]() { g_main_params = CreateChainParams(::gArgs, CBaseChainParams::MAIN); });
82 9385 : return *Assert(g_main_params);
83 : }
84 :
85 16078 : UniValue ArrFromService(const CService& addr)
86 : {
87 16078 : UniValue obj(UniValue::VARR);
88 16078 : obj.push_back(addr.ToStringAddrPort());
89 16078 : return obj;
90 16078 : }
91 :
92 463 : DomainPort::Status DomainPort::ValidateDomain(const std::string& addr)
93 : {
94 463 : if (addr.length() > DOMAIN_MAX_LEN || addr.length() < DOMAIN_MIN_LEN) {
95 2 : return DomainPort::Status::BadLen;
96 : }
97 461 : if (!MatchCharsFilter(addr, SAFE_CHARS_RFC1035)) {
98 2 : return DomainPort::Status::BadChar;
99 : }
100 459 : if (addr.front() == '.' || addr.back() == '.') {
101 2 : return DomainPort::Status::BadCharPos;
102 : }
103 457 : std::vector<std::string> labels{SplitString(addr, '.')};
104 457 : if (labels.size() < 2) {
105 2 : return DomainPort::Status::BadDotless;
106 : }
107 1788 : for (const auto& label : labels) {
108 1336 : if (label.empty() || label.length() > DOMAIN_LABEL_MAX_LEN) {
109 2 : return DomainPort::Status::BadLabelLen;
110 : }
111 1334 : if (label.front() == '-' || label.back() == '-') {
112 1 : return DomainPort::Status::BadLabelCharPos;
113 : }
114 : }
115 452 : return DomainPort::Status::Success;
116 463 : }
117 :
118 39 : DomainPort::Status DomainPort::Set(const std::string& addr, const uint16_t port)
119 : {
120 39 : if (port == 0) {
121 1 : return DomainPort::Status::BadPort;
122 : }
123 38 : const auto ret{ValidateDomain(addr)};
124 38 : if (ret == DomainPort::Status::Success) {
125 : // Convert to lowercase to avoid duplication by changing case (domains are case-insensitive)
126 27 : m_addr = ToLower(addr);
127 27 : m_port = port;
128 27 : }
129 38 : return ret;
130 39 : }
131 :
132 436 : DomainPort::Status DomainPort::Validate() const
133 : {
134 436 : if (m_addr.empty() || m_addr != ToLower(m_addr)) {
135 11 : return DomainPort::Status::Malformed;
136 : }
137 425 : if (m_port == 0) {
138 0 : return DomainPort::Status::BadPort;
139 : }
140 425 : return ValidateDomain(m_addr);
141 436 : }
142 :
143 191428 : bool NetInfoEntry::operator==(const NetInfoEntry& rhs) const
144 : {
145 191428 : if (m_type != rhs.m_type) return false;
146 27932 : return std::visit(
147 27932 : [](auto&& lhs, auto&& rhs) -> bool {
148 : if constexpr (std::is_same_v<decltype(lhs), decltype(rhs)>) {
149 27932 : return lhs == rhs;
150 : }
151 0 : return false;
152 : },
153 27932 : m_data, rhs.m_data);
154 191428 : }
155 :
156 4689 : bool NetInfoEntry::operator<(const NetInfoEntry& rhs) const
157 : {
158 4689 : if (m_type != rhs.m_type) return m_type < rhs.m_type;
159 4412 : return std::visit(
160 4412 : [](auto&& lhs, auto&& rhs) -> bool {
161 : using T1 = std::decay_t<decltype(lhs)>;
162 : using T2 = std::decay_t<decltype(rhs)>;
163 : if constexpr (std::is_same_v<T1, T2>) {
164 : // Both the same type, compare as usual
165 4412 : return lhs < rhs;
166 : } else if constexpr ((std::is_same_v<T1, CService> || std::is_same_v<T1, DomainPort>) &&
167 : (std::is_same_v<T2, CService> || std::is_same_v<T2, DomainPort>)) {
168 : // Differing types but both implement ToStringAddrPort(), lexicographical compare strings
169 0 : return lhs.ToStringAddrPort() < rhs.ToStringAddrPort();
170 : }
171 : // If lhs is monostate, it less than rhs; otherwise rhs is greater
172 0 : return std::is_same_v<T1, std::monostate>;
173 0 : },
174 4412 : m_data, rhs.m_data);
175 4689 : }
176 :
177 356194 : std::optional<CService> NetInfoEntry::GetAddrPort() const
178 : {
179 356194 : if (const auto* data_ptr{std::get_if<CService>(&m_data)}; m_type == NetInfoType::Service && data_ptr) {
180 : ASSERT_IF_DEBUG(data_ptr->IsValid());
181 350953 : return *data_ptr;
182 : }
183 5241 : return std::nullopt;
184 356194 : }
185 :
186 385 : std::optional<DomainPort> NetInfoEntry::GetDomainPort() const
187 : {
188 385 : if (const auto* data_ptr{std::get_if<DomainPort>(&m_data)}; m_type == NetInfoType::Domain && data_ptr) {
189 : ASSERT_IF_DEBUG(data_ptr->IsValid());
190 136 : return *data_ptr;
191 : }
192 249 : return std::nullopt;
193 385 : }
194 :
195 34 : uint16_t NetInfoEntry::GetPort() const
196 : {
197 34 : return std::visit(
198 34 : [](auto&& input) -> uint16_t {
199 : using T1 = std::decay_t<decltype(input)>;
200 : if constexpr (std::is_same_v<T1, CService> || std::is_same_v<T1, DomainPort>) {
201 33 : return input.GetPort();
202 : }
203 1 : return 0;
204 : },
205 34 : m_data);
206 : }
207 :
208 : // NetInfoEntry is a dumb object that doesn't enforce validation rules, that is the responsibility of
209 : // types that utilize NetInfoEntry (MnNetInfo and others). IsTriviallyValid() is there to check if a
210 : // NetInfoEntry object is properly constructed.
211 16452 : bool NetInfoEntry::IsTriviallyValid() const
212 : {
213 16452 : if (m_type == NetInfoType::Invalid) return false;
214 16433 : return std::visit(
215 32866 : [this](auto&& input) -> bool {
216 : using T1 = std::decay_t<decltype(input)>;
217 : static_assert(std::is_same_v<T1, std::monostate> || std::is_same_v<T1, CService> || std::is_same_v<T1, DomainPort>,
218 : "Unexpected type");
219 : if constexpr (std::is_same_v<T1, std::monostate>) {
220 : // Empty underlying data isn't a valid entry
221 0 : return false;
222 : } else if constexpr (std::is_same_v<T1, CService>) {
223 : // Type code should be truthful as it decides what underlying type is used when (de)serializing
224 16329 : if (m_type != NetInfoType::Service) return false;
225 : // Underlying data must meet surface-level validity checks for its type
226 16329 : if (!input.IsValid()) return false;
227 : } else if constexpr (std::is_same_v<T1, DomainPort>) {
228 : // Type code should be truthful as it decides what underlying type is used when (de)serializing
229 104 : if (m_type != NetInfoType::Domain) return false;
230 : // Underlying data should at least meet surface-level validity checks
231 104 : if (!input.IsValid()) return false;
232 : }
233 16433 : return true;
234 16433 : },
235 16433 : m_data);
236 16452 : }
237 :
238 6736 : std::string NetInfoEntry::ToString() const
239 : {
240 6736 : return std::visit(
241 6736 : [](auto&& input) -> std::string {
242 : using T1 = std::decay_t<decltype(input)>;
243 : if constexpr (std::is_same_v<T1, CService>) {
244 6715 : return strprintf("CService(addr=%s, port=%u)", input.ToStringAddr(), input.GetPort());
245 : } else if constexpr (std::is_same_v<T1, DomainPort>) {
246 20 : return strprintf("DomainPort(addr=%s, port=%u)", input.ToStringAddr(), input.GetPort());
247 : }
248 1 : return "[invalid entry]";
249 0 : },
250 6736 : m_data);
251 : }
252 :
253 774 : std::string NetInfoEntry::ToStringAddr() const
254 : {
255 774 : return std::visit(
256 774 : [](auto&& input) -> std::string {
257 : using T1 = std::decay_t<decltype(input)>;
258 : if constexpr (std::is_same_v<T1, CService> || std::is_same_v<T1, DomainPort>) {
259 773 : return input.ToStringAddr();
260 : }
261 1 : return "[invalid entry]";
262 : },
263 774 : m_data);
264 : }
265 :
266 7959 : std::string NetInfoEntry::ToStringAddrPort() const
267 : {
268 7959 : return std::visit(
269 7959 : [](auto&& input) -> std::string {
270 : using T1 = std::decay_t<decltype(input)>;
271 : if constexpr (std::is_same_v<T1, CService> || std::is_same_v<T1, DomainPort>) {
272 7958 : return input.ToStringAddrPort();
273 : }
274 1 : return "[invalid entry]";
275 : },
276 7959 : m_data);
277 : }
278 :
279 1108 : std::shared_ptr<NetInfoInterface> NetInfoInterface::MakeNetInfo(const uint16_t nVersion)
280 : {
281 1108 : assert(nVersion > 0);
282 1108 : if (nVersion >= ProTxVersion::ExtAddr) {
283 90 : return std::make_shared<ExtNetInfo>();
284 : }
285 1018 : return std::make_shared<MnNetInfo>();
286 1108 : }
287 :
288 8395 : NetInfoStatus MnNetInfo::ValidateService(const CService& service)
289 : {
290 8395 : if (!service.IsValid()) {
291 1 : return NetInfoStatus::BadAddress;
292 : }
293 8394 : if (!service.IsIPv4()) {
294 0 : return NetInfoStatus::BadType;
295 : }
296 8394 : if (Params().RequireRoutableExternalIP() && !service.IsRoutable()) {
297 1 : return NetInfoStatus::NotRoutable;
298 : }
299 :
300 8393 : if (IsNodeOnMainnet() != (service.GetPort() == MainParams().GetDefaultPort())) {
301 : // Must use mainnet port on mainnet.
302 : // Must NOT use mainnet port on other networks.
303 4 : return NetInfoStatus::BadPort;
304 : }
305 :
306 8389 : return NetInfoStatus::Success;
307 8395 : }
308 :
309 945 : NetInfoStatus MnNetInfo::AddEntry(const NetInfoPurpose purpose, const std::string& input)
310 : {
311 945 : if (purpose != NetInfoPurpose::CORE_P2P || !IsEmpty()) {
312 11 : return NetInfoStatus::MaxLimit;
313 : }
314 :
315 934 : std::string addr;
316 934 : uint16_t port{Params().GetDefaultPort()};
317 934 : SplitHostPort(input, port, addr);
318 : // Contains invalid characters, unlikely to pass Lookup(), fast-fail
319 934 : if (!MatchCharsFilter(addr, SAFE_CHARS_IPV4)) {
320 6 : return NetInfoStatus::BadInput;
321 : }
322 :
323 1854 : if (auto service_opt{Lookup(addr, /*portDefault=*/port, /*fAllowLookup=*/false)}) {
324 926 : const auto ret{ValidateService(*service_opt)};
325 926 : if (ret == NetInfoStatus::Success) {
326 920 : m_addr = NetInfoEntry{*service_opt};
327 : ASSERT_IF_DEBUG(m_addr.GetAddrPort().has_value());
328 920 : }
329 926 : return ret;
330 : }
331 2 : return NetInfoStatus::BadInput;
332 945 : }
333 :
334 119971 : NetInfoList MnNetInfo::GetEntries(std::optional<NetInfoPurpose> purpose_opt) const
335 : {
336 119971 : if (!IsEmpty() && (!purpose_opt || *purpose_opt == NetInfoPurpose::CORE_P2P)) {
337 : ASSERT_IF_DEBUG(m_addr.GetAddrPort().has_value());
338 119496 : return {m_addr};
339 : }
340 : // If MnNetInfo is empty or we are given an unexpected purpose code, we don't
341 : // expect any entries to show up, so we return a blank set instead.
342 475 : return NetInfoList{};
343 119971 : }
344 :
345 200009 : CService MnNetInfo::GetPrimary() const
346 : {
347 395771 : if (const auto service_opt{m_addr.GetAddrPort()}) {
348 195762 : return *service_opt;
349 : }
350 4247 : return CService{};
351 200009 : }
352 :
353 7485 : NetInfoStatus MnNetInfo::Validate() const
354 : {
355 7485 : if (!m_addr.IsTriviallyValid()) {
356 16 : return NetInfoStatus::Malformed;
357 : }
358 7469 : return ValidateService(GetPrimary());
359 7485 : }
360 :
361 14793 : UniValue MnNetInfo::ToJson(std::optional<NetInfoPurpose> purpose_opt) const
362 : {
363 14793 : if (purpose_opt) {
364 0 : UniValue arr(UniValue::VARR);
365 0 : for (const auto& entry : GetEntries(purpose_opt)) {
366 0 : arr.push_back(entry.ToStringAddrPort());
367 : }
368 0 : return arr;
369 0 : }
370 :
371 14793 : UniValue ret{UniValue::VOBJ};
372 14793 : if (!IsEmpty()) {
373 14750 : ret.pushKV(PurposeToString(NetInfoPurpose::CORE_P2P).data(), ArrFromService(GetPrimary()));
374 14750 : }
375 14793 : return ret;
376 29586 : }
377 :
378 6514 : std::string MnNetInfo::ToString() const
379 : {
380 13018 : return IsEmpty() ? "MnNetInfo()"
381 13008 : : strprintf("MnNetInfo(NetInfo(purpose=%s, [%s]))", PurposeToString(NetInfoPurpose::CORE_P2P),
382 6504 : m_addr.ToString());
383 0 : }
384 :
385 124 : bool ExtNetInfo::HasAddrPortDuplicates() const
386 : {
387 124 : std::set<NetInfoEntry> known{};
388 715 : for (const auto& entry : m_all_entries) {
389 591 : if (auto [_, inserted] = known.insert(entry); !inserted) {
390 0 : return true;
391 : }
392 : }
393 : ASSERT_IF_DEBUG(known.size() == m_all_entries.size());
394 124 : return false;
395 124 : }
396 :
397 275 : bool ExtNetInfo::IsAddrPortDuplicate(const NetInfoEntry& candidate) const
398 : {
399 550 : return std::any_of(m_all_entries.begin(), m_all_entries.end(),
400 765 : [&candidate](const auto& entry) { return candidate == entry; });
401 : }
402 :
403 350 : bool ExtNetInfo::HasAddrDuplicates(const NetInfoList& entries) const
404 : {
405 350 : std::unordered_set<std::string> known{};
406 941 : for (const auto& entry : entries) {
407 591 : if (auto [_, inserted] = known.insert(entry.ToStringAddr()); !inserted) {
408 0 : return true;
409 : }
410 : }
411 : ASSERT_IF_DEBUG(known.size() == entries.size());
412 350 : return false;
413 350 : }
414 :
415 84 : bool ExtNetInfo::IsAddrDuplicate(const NetInfoEntry& candidate, const NetInfoList& entries) const
416 : {
417 84 : const std::string& candidate_str{candidate.ToStringAddr()};
418 168 : return std::any_of(entries.begin(), entries.end(),
419 181 : [&candidate_str](const auto& entry) { return candidate_str == entry.ToStringAddr(); });
420 84 : }
421 :
422 275 : NetInfoStatus ExtNetInfo::ProcessCandidate(const NetInfoPurpose purpose, const NetInfoEntry& candidate)
423 : {
424 275 : assert(candidate.IsTriviallyValid());
425 :
426 275 : if (IsAddrPortDuplicate(candidate)) {
427 6 : return NetInfoStatus::Duplicate;
428 : }
429 269 : if (candidate.GetDomainPort().has_value() && purpose != NetInfoPurpose::PLATFORM_HTTPS) {
430 : // Domains only allowed for Platform HTTPS API
431 2 : return NetInfoStatus::BadInput;
432 : }
433 267 : if (auto it{m_data.find(purpose)}; it != m_data.end()) {
434 : // Existing entries list found, check limit
435 85 : auto& [_, entries] = *it;
436 85 : if (entries.size() >= MAX_ENTRIES_EXTNETINFO) {
437 1 : return NetInfoStatus::MaxLimit;
438 : }
439 168 : if (IsAddrDuplicate(candidate, entries)) {
440 1 : return NetInfoStatus::Duplicate;
441 : }
442 83 : entries.push_back(candidate);
443 83 : } else {
444 : // First entry for purpose code, create new entries list
445 182 : auto [_, status] = m_data.try_emplace(purpose, std::vector<NetInfoEntry>({candidate}));
446 182 : assert(status); // We did just check to see if our value already existed, try_emplace shouldn't fail
447 : }
448 :
449 : // Candidate successfully added, update cache
450 265 : m_all_entries.push_back(candidate);
451 265 : return NetInfoStatus::Success;
452 275 : }
453 :
454 813 : NetInfoStatus ExtNetInfo::ValidateService(const CService& service)
455 : {
456 813 : if (!service.IsValid()) {
457 1 : return NetInfoStatus::BadAddress;
458 : }
459 812 : if (!service.IsCJDNS() && !service.IsI2P() && !service.IsIPv4() && !service.IsIPv6() && !service.IsTor()) {
460 0 : return NetInfoStatus::BadType;
461 : }
462 812 : if (Params().RequireRoutableExternalIP() && !service.IsRoutable()) {
463 1 : return NetInfoStatus::NotRoutable;
464 : }
465 811 : const uint16_t service_port{service.GetPort()};
466 811 : if (service.IsI2P()) {
467 68 : if (service_port != I2P_SAM31_PORT) {
468 : // I2P SAM 3.1 and earlier don't support arbitrary ports
469 1 : return NetInfoStatus::BadPort;
470 : }
471 67 : } else {
472 743 : if (service_port == 0 || IsBadPort(service_port)) {
473 6 : return NetInfoStatus::BadPort;
474 : }
475 : }
476 :
477 804 : return NetInfoStatus::Success;
478 813 : }
479 :
480 65 : NetInfoStatus ExtNetInfo::ValidateDomainPort(const DomainPort& domain)
481 : {
482 65 : if (!domain.IsValid()) {
483 0 : return NetInfoStatus::BadInput;
484 : }
485 65 : const uint16_t domain_port{domain.GetPort()};
486 65 : if (domain_port == 0 || (IsBadPort(domain_port) && !IsAllowedPlatformHTTPPort(domain_port))) {
487 1 : return NetInfoStatus::BadPort;
488 : }
489 64 : const std::string& addr{domain.ToStringAddr()};
490 64 : if (MatchSuffix(addr, TLDS_BAD) || MatchSuffix(addr, TLDS_PRIVACY)) {
491 1 : return NetInfoStatus::BadInput;
492 : }
493 64 : if (const auto labels{SplitString(addr, '.')}; !MatchCharsFilter(labels.at(labels.size() - 1), SAFE_CHARS_ALPHA)) {
494 1 : return NetInfoStatus::BadInput;
495 : }
496 :
497 62 : return NetInfoStatus::Success;
498 65 : }
499 :
500 302 : NetInfoStatus ExtNetInfo::AddEntry(const NetInfoPurpose purpose, const std::string& input)
501 : {
502 302 : if (!IsValidPurpose(purpose)) {
503 1 : return NetInfoStatus::MaxLimit;
504 : }
505 :
506 : // We don't allow assuming ports, so we set the default value to 0 so that if no port is specified
507 : // it uses a fallback value of 0, which will return a NetInfoStatus::BadPort
508 301 : std::string addr;
509 301 : uint16_t port{0};
510 301 : SplitHostPort(input, port, addr);
511 :
512 301 : if (!MatchCharsFilter(addr, SAFE_CHARS_IPV4_6)) {
513 67 : if (!MatchCharsFilter(addr, SAFE_CHARS_RFC1035)) {
514 : // Neither IP:port safe nor domain-safe, we can safely assume it's bad input
515 0 : return NetInfoStatus::BadInput;
516 : }
517 :
518 : // Not IP:port safe but domain safe
519 67 : if (MatchSuffix(addr, TLDS_PRIVACY)) {
520 : // Special domain, try storing it as CService
521 43 : CNetAddr netaddr;
522 43 : if (netaddr.SetSpecial(addr)) {
523 43 : const CService service{netaddr, port};
524 43 : const auto ret{ValidateService(service)};
525 43 : if (ret == NetInfoStatus::Success) {
526 41 : return ProcessCandidate(purpose, NetInfoEntry{service});
527 : }
528 2 : return ret; /* ValidateService() failed */
529 43 : }
530 90 : } else if (DomainPort domain; domain.Set(addr, port) == DomainPort::Status::Success) {
531 : // Regular domain
532 23 : const auto ret{ValidateDomainPort(domain)};
533 23 : if (ret == NetInfoStatus::Success) {
534 20 : return ProcessCandidate(purpose, NetInfoEntry{domain});
535 : }
536 3 : return ret; /* ValidateDomainPort() failed */
537 : }
538 1 : return NetInfoStatus::BadInput; /* CNetAddr::SetSpecial() or DomainPort::Set() failed */
539 : }
540 :
541 : // IP:port safe, try to parse it as IP:port
542 455 : if (auto service_opt{Lookup(addr, /*portDefault=*/port, /*fAllowLookup=*/false)}) {
543 221 : const auto service{MaybeFlipIPv6toCJDNS(*service_opt)};
544 221 : const auto ret{ValidateService(service)};
545 221 : if (ret == NetInfoStatus::Success) {
546 214 : return ProcessCandidate(purpose, NetInfoEntry{service});
547 : }
548 7 : return ret; /* ValidateService() failed */
549 221 : }
550 13 : return NetInfoStatus::BadInput; /* Lookup() failed */
551 302 : }
552 :
553 833 : NetInfoList ExtNetInfo::GetEntries(std::optional<NetInfoPurpose> purpose_opt) const
554 : {
555 833 : if (!purpose_opt) {
556 : // Without a purpose code specified, we're being requested for a flattened list of all
557 : // entries. ExtNetInfo is an append-only structure, so we can avoid re-calculating a
558 : // list of all entries by maintaining a small cache.
559 801 : return m_all_entries;
560 : }
561 32 : if (!IsValidPurpose(*purpose_opt)) return NetInfoList{};
562 32 : const auto& it{m_data.find(*purpose_opt)};
563 32 : return it != m_data.end() ? it->second : NetInfoList{};
564 833 : }
565 :
566 64 : CService ExtNetInfo::GetPrimary() const
567 : {
568 64 : if (const auto& it{m_data.find(NetInfoPurpose::CORE_P2P)}; it != m_data.end()) {
569 58 : const auto& [_, entries] = *it;
570 : // If a purpose code is in the map, there should be at least one entry
571 : ASSERT_IF_DEBUG(!entries.empty());
572 58 : if (entries.size() >= 1) {
573 116 : if (const auto& service_opt{entries[0].GetAddrPort()}) {
574 58 : return *service_opt;
575 : }
576 0 : }
577 0 : }
578 6 : return CService{};
579 64 : }
580 :
581 445 : bool ExtNetInfo::HasEntries(NetInfoPurpose purpose) const
582 : {
583 445 : if (!IsValidPurpose(purpose)) return false;
584 444 : const auto& it{m_data.find(purpose)};
585 444 : return it != m_data.end() && !it->second.empty();
586 445 : }
587 :
588 139 : NetInfoStatus ExtNetInfo::Validate() const
589 : {
590 139 : if (m_version == 0 || m_version > CURRENT_VERSION || m_data.empty()) {
591 15 : return NetInfoStatus::Malformed;
592 : }
593 124 : if (HasAddrPortDuplicates()) {
594 0 : return NetInfoStatus::Duplicate;
595 : }
596 516 : for (const auto& [purpose, entries] : m_data) {
597 350 : if (!IsValidPurpose(purpose)) {
598 0 : return NetInfoStatus::Malformed;
599 : }
600 350 : if (entries.empty()) {
601 : // Purpose if present in map must have at least one entry
602 0 : return NetInfoStatus::Malformed;
603 : }
604 350 : if (HasAddrDuplicates(entries)) {
605 0 : return NetInfoStatus::Duplicate;
606 : }
607 941 : for (const auto& entry : entries) {
608 591 : if (!entry.IsTriviallyValid()) {
609 : // Trivially invalid NetInfoEntry, no point checking against consensus rules
610 0 : return NetInfoStatus::Malformed;
611 : }
612 1182 : if (const auto& service_opt{entry.GetAddrPort()}) {
613 549 : if (auto ret{ValidateService(*service_opt)}; ret != NetInfoStatus::Success) {
614 : // Stores CService underneath but doesn't pass validation rules
615 0 : return ret;
616 : }
617 633 : } else if (const auto domain_opt{entry.GetDomainPort()}) {
618 42 : if (purpose != NetInfoPurpose::PLATFORM_HTTPS) {
619 : // Domains only allowed for Platform HTTPS API
620 0 : return NetInfoStatus::BadInput;
621 : }
622 42 : if (auto ret{ValidateDomainPort(*domain_opt)}; ret != NetInfoStatus::Success) {
623 : // Stores DomainPort underneath but doesn't pass validation rules
624 0 : return ret;
625 : }
626 42 : } else {
627 : // Doesn't store valid type underneath
628 0 : return NetInfoStatus::Malformed;
629 : }
630 : }
631 : }
632 124 : return NetInfoStatus::Success;
633 139 : }
634 :
635 54 : UniValue ExtNetInfo::ToJson(std::optional<NetInfoPurpose> purpose_opt) const
636 : {
637 : // Report only a subset of all addresses if supplied a purpose code
638 54 : if (purpose_opt) {
639 0 : UniValue arr(UniValue::VARR);
640 0 : for (const auto& entry : GetEntries(purpose_opt)) {
641 0 : arr.push_back(entry.ToStringAddrPort());
642 : }
643 0 : return arr;
644 0 : }
645 :
646 54 : UniValue ret(UniValue::VOBJ);
647 222 : for (const auto& [purpose, entries] : m_data) {
648 84 : UniValue arr(UniValue::VARR);
649 242 : for (const auto& entry : entries) {
650 158 : arr.push_back(entry.ToStringAddrPort());
651 : }
652 84 : ret.pushKV(PurposeToString(purpose).data(), arr);
653 84 : }
654 54 : return ret;
655 108 : }
656 :
657 70 : std::string ExtNetInfo::ToString() const
658 : {
659 110 : return IsEmpty() ? "ExtNetInfo()" : strprintf("ExtNetInfo(%s)", [&]() -> std::string {
660 40 : std::string ret{};
661 40 : bool first{true};
662 280 : for (const auto& [purpose, entries] : m_data) {
663 120 : if (!first) { ret += ", "; } else { first = false; }
664 240 : ret += strprintf("NetInfo(purpose=%s, [%s])", PurposeToString(purpose), [&]() -> std::string {
665 120 : if (entries.empty()) {
666 0 : return "invalid list";
667 : }
668 350 : return Join(entries, ", ", [](const auto& entry) { return entry.ToString(); });
669 120 : }());
670 : }
671 40 : return ret;
672 40 : }());
673 0 : }
|