From 66d7ecf34ef91eaf5d294a1243ec3f7b548b47b1 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Sun, 18 Oct 2026 16:00:55 -0400 Subject: [PATCH] fix(p2p): consume the published TXT seed records --- README.md | 7 ++++++ src/p2p/dns_seed.h | 37 ++++++++++++++++++++++++++++++++ src/p2p/net_node.h | 2 +- src/p2p/net_node.inl | 7 +++--- tests/unit_tests/node_server.cpp | 23 ++++++++++++++++++++ 5 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 src/p2p/dns_seed.h diff --git a/README.md b/README.md index 37bfafc8f..bc72ec7e0 100644 --- a/README.md +++ b/README.md @@ -156,3 +156,10 @@ Once the node has caught up, `--block-sync-size` can be removed. Copyright (c) 2014-2026 The Monero Project. Portions Copyright (c) 2012-2013 The Cryptonote developers. + +### DNS seed discovery + +Mainnet discovers peers from TXT records at `seeds.wownero.org`. Records contain +literal IPv4 endpoints (`IP:port`), either individually or separated by semicolons. +Invalid and local endpoints are ignored. The built-in seed list remains the +fallback when DNS is unavailable or returns too few usable endpoints. diff --git a/src/p2p/dns_seed.h b/src/p2p/dns_seed.h new file mode 100644 index 000000000..518b6dd95 --- /dev/null +++ b/src/p2p/dns_seed.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#pragma once + +#include +#include +#include +#include +#include +#include "net/parse.h" + +namespace nodetool +{ + // TXT seed records contain literal IPv4 endpoints, separated by semicolons. + inline std::set parse_dns_seed_records(const std::vector& records, uint16_t default_port) + { + std::set seeds; + for (const auto& record : records) + { + std::vector endpoints; + boost::split(endpoints, record, boost::is_any_of(";")); + for (auto endpoint : endpoints) + { + boost::trim(endpoint); + const auto address = net::get_network_address(endpoint, default_port); + if (!address || address->get_type_id() != epee::net_utils::address_type::ipv4) + continue; + const auto& ipv4 = address->as(); + if (ipv4.ip() == 0 || ipv4.port() == 0 || ipv4.is_loopback() || ipv4.is_local()) + continue; + seeds.insert(address->str()); + } + } + return seeds; + } + +} diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index ed59310e1..88be9f0d2 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -297,7 +297,7 @@ namespace nodetool private: const std::vector m_seed_nodes_list = { - "seeds.wownero.com" + "seeds.wownero.org" }; bool islimitup=false; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 82f19a14c..edb2a9d6b 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -59,6 +59,7 @@ #include "storages/levin_abstract_invoke2.h" #include "cryptonote_core/cryptonote_core.h" #include "net/parse.h" +#include "p2p/dns_seed.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -801,7 +802,7 @@ namespace nodetool MDEBUG("dns_threads[" << result_index << "] created for: " << addr_str); // TODO: care about dnssec avail/valid bool avail, valid; - std::vector addr_list = tools::DNSResolver::instance().get_ipv4(addr_str, avail, valid); + std::vector addr_list = tools::DNSResolver::instance().get_txt_record(addr_str, avail, valid); MINFO("dns_threads[" << result_index << "] addr_str: " << addr_str << " number of results: " << addr_list.size()); const auto frame = frame_weak.lock(); if (frame) @@ -836,8 +837,8 @@ namespace nodetool // if no results for node, thread's lookup likely timed out if (result.size()) { - for (const auto& addr_string : result) - full_addrs.insert(addr_string + ":" + std::to_string(cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT)); + const auto seeds = parse_dns_seed_records(result, cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT); + full_addrs.insert(seeds.begin(), seeds.end()); } ++i; } diff --git a/tests/unit_tests/node_server.cpp b/tests/unit_tests/node_server.cpp index e9195cb5e..a150c4401 100644 --- a/tests/unit_tests/node_server.cpp +++ b/tests/unit_tests/node_server.cpp @@ -1451,3 +1451,26 @@ TEST(regtest, isolates_p2p_state_from_mainnet_data_dir) namespace nodetool { template class node_server>; } namespace cryptonote { template class t_cryptonote_protocol_handler; } + +TEST(dns_seed_records, accepts_grouped_and_individual_endpoints) +{ + const auto seeds = nodetool::parse_dns_seed_records({ + "198.51.100.10:34567; 203.0.113.20:34567", "198.51.100.30", "198.51.100.10:34567"}, 34567); + const std::set expected{ + "198.51.100.10:34567", "203.0.113.20:34567", "198.51.100.30:34567"}; + EXPECT_EQ(expected, seeds); +} + +TEST(dns_seed_records, ignores_invalid_and_local_endpoints) +{ + const auto seeds = nodetool::parse_dns_seed_records({ + ";invalid;198.51.100.10:65536;127.0.0.1:34567;192.168.1.2:34567;0.0.0.0:34567;198.51.100.20:0;", + "203.0.113.20:34567"}, 34567); + EXPECT_EQ((std::set{"203.0.113.20:34567"}), seeds); +} + +TEST(dns_seed_records, empty_results_allow_hardcoded_fallback) +{ + EXPECT_TRUE(nodetool::parse_dns_seed_records({}, 34567).empty()); + EXPECT_TRUE(nodetool::parse_dns_seed_records({"", "; ;", "invalid"}, 34567).empty()); +}