1use serde::{Deserialize, Serialize};
2use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
3use thiserror::Error;
4use tracing::{debug, error, trace, warn};
5use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
6use trust_dns_resolver::TokioAsyncResolver;
7
8#[derive(Error, Debug)]
9pub enum DnsDiscoveryError {
10 #[error("DNS resolution failed: {0}")]
11 ResolutionFailed(String),
12 #[error("No peers found from any introducer")]
13 NoPeersFound,
14 #[error("Resolver creation failed: {0}")]
15 ResolverCreationFailed(String),
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct PeerAddress {
20 pub host: IpAddr,
21 pub port: u16,
22 pub is_ipv6: bool,
23}
24
25impl PeerAddress {
26 pub fn new(host: IpAddr, port: u16) -> Self {
27 let is_ipv6 = matches!(host, IpAddr::V6(_));
28 Self {
29 host,
30 port,
31 is_ipv6,
32 }
33 }
34
35 pub fn display_address(&self) -> String {
37 if self.is_ipv6 {
38 format!("[{}]:{}", self.host, self.port)
39 } else {
40 format!("{}:{}", self.host, self.port)
41 }
42 }
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct DiscoveryResult {
47 pub ipv4_peers: Vec<PeerAddress>,
48 pub ipv6_peers: Vec<PeerAddress>,
49 pub total_count: usize,
50}
51
52impl DiscoveryResult {
53 pub fn new() -> Self {
54 Self {
55 ipv4_peers: Vec::new(),
56 ipv6_peers: Vec::new(),
57 total_count: 0,
58 }
59 }
60
61 pub fn add_ipv4(&mut self, addr: Ipv4Addr, port: u16) {
62 self.ipv4_peers
63 .push(PeerAddress::new(IpAddr::V4(addr), port));
64 self.total_count += 1;
65 }
66
67 pub fn add_ipv6(&mut self, addr: Ipv6Addr, port: u16) {
68 self.ipv6_peers
69 .push(PeerAddress::new(IpAddr::V6(addr), port));
70 self.total_count += 1;
71 }
72
73 pub fn merge(&mut self, other: DiscoveryResult) {
74 self.ipv4_peers.extend(other.ipv4_peers);
75 self.ipv6_peers.extend(other.ipv6_peers);
76 self.total_count = self.ipv4_peers.len() + self.ipv6_peers.len();
77 }
78
79 pub fn shuffle(&mut self) {
80 use rand::seq::SliceRandom;
81 let mut rng = rand::thread_rng();
82 self.ipv4_peers.shuffle(&mut rng);
83 self.ipv6_peers.shuffle(&mut rng);
84 }
85}
86
87impl Default for DiscoveryResult {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93pub struct DnsDiscovery {
94 resolver: TokioAsyncResolver,
95}
96
97impl DnsDiscovery {
98 pub async fn new() -> Result<Self, DnsDiscoveryError> {
99 let resolver =
100 TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default());
101
102 Ok(Self { resolver })
103 }
104
105 pub async fn discover_mainnet_peers(&self) -> Result<DiscoveryResult, DnsDiscoveryError> {
107 let introducers = vec![
108 "dns-introducer.chia.net",
109 "seeder.dexie.space",
110 "chia.hoffmang.com",
111 ];
112
113 self.discover_peers(&introducers, 8444).await
114 }
115
116 pub async fn discover_testnet11_peers(&self) -> Result<DiscoveryResult, DnsDiscoveryError> {
118 let introducers = vec!["dns-introducer-testnet11.chia.net"];
119
120 self.discover_peers(&introducers, 58444).await
121 }
122
123 pub async fn discover_peers(
125 &self,
126 introducers: &[&str],
127 default_port: u16,
128 ) -> Result<DiscoveryResult, DnsDiscoveryError> {
129 debug!(
130 "Starting DNS discovery for {} introducers",
131 introducers.len()
132 );
133
134 let mut result = DiscoveryResult::new();
135
136 for introducer in introducers {
137 debug!("Resolving introducer: {}", introducer);
138
139 match self.resolve_ipv4(introducer).await {
141 Ok(ipv4_addrs) => {
142 trace!(
143 "Found {} IPv4 addresses for {}",
144 ipv4_addrs.len(),
145 introducer
146 );
147 for addr in ipv4_addrs {
148 result.add_ipv4(addr, default_port);
149 }
150 }
151 Err(e) => {
152 warn!("Failed to resolve IPv4 for {}: {}", introducer, e);
153 }
154 }
155
156 match self.resolve_ipv6(introducer).await {
158 Ok(ipv6_addrs) => {
159 trace!(
160 "Found {} IPv6 addresses for {}",
161 ipv6_addrs.len(),
162 introducer
163 );
164 for addr in ipv6_addrs {
165 result.add_ipv6(addr, default_port);
166 }
167 }
168 Err(e) => {
169 warn!("Failed to resolve IPv6 for {}: {}", introducer, e);
170 }
171 }
172 }
173
174 if result.total_count == 0 {
175 return Err(DnsDiscoveryError::NoPeersFound);
176 }
177
178 result.shuffle();
180
181 debug!(
182 "DNS discovery completed: {} IPv4 peers, {} IPv6 peers, {} total",
183 result.ipv4_peers.len(),
184 result.ipv6_peers.len(),
185 result.total_count
186 );
187
188 Ok(result)
189 }
190
191 pub async fn resolve_ipv4(&self, hostname: &str) -> Result<Vec<Ipv4Addr>, DnsDiscoveryError> {
193 trace!("Performing A record lookup for {}", hostname);
194
195 match self.resolver.ipv4_lookup(hostname).await {
196 Ok(lookup) => {
197 let addrs: Vec<Ipv4Addr> = lookup.iter().map(|record| record.0).collect();
198 trace!(
199 "A record lookup for {} returned {} addresses",
200 hostname,
201 addrs.len()
202 );
203 Ok(addrs)
204 }
205 Err(e) => {
206 error!("A record lookup failed for {}: {}", hostname, e);
207 Err(DnsDiscoveryError::ResolutionFailed(format!(
208 "IPv4 lookup for {}: {}",
209 hostname, e
210 )))
211 }
212 }
213 }
214
215 pub async fn resolve_ipv6(&self, hostname: &str) -> Result<Vec<Ipv6Addr>, DnsDiscoveryError> {
217 trace!("Performing AAAA record lookup for {}", hostname);
218
219 match self.resolver.ipv6_lookup(hostname).await {
220 Ok(lookup) => {
221 let addrs: Vec<Ipv6Addr> = lookup.iter().map(|record| record.0).collect();
222 trace!(
223 "AAAA record lookup for {} returned {} addresses",
224 hostname,
225 addrs.len()
226 );
227 Ok(addrs)
228 }
229 Err(e) => {
230 error!("AAAA record lookup failed for {}: {}", hostname, e);
231 Err(DnsDiscoveryError::ResolutionFailed(format!(
232 "IPv6 lookup for {}: {}",
233 hostname, e
234 )))
235 }
236 }
237 }
238
239 pub async fn resolve_both(
241 &self,
242 hostname: &str,
243 port: u16,
244 ) -> Result<DiscoveryResult, DnsDiscoveryError> {
245 let mut result = DiscoveryResult::new();
246
247 if let Ok(ipv4_addrs) = self.resolve_ipv4(hostname).await {
249 for addr in ipv4_addrs {
250 result.add_ipv4(addr, port);
251 }
252 }
253
254 if let Ok(ipv6_addrs) = self.resolve_ipv6(hostname).await {
256 for addr in ipv6_addrs {
257 result.add_ipv6(addr, port);
258 }
259 }
260
261 if result.total_count == 0 {
262 return Err(DnsDiscoveryError::ResolutionFailed(format!(
263 "No addresses found for {}",
264 hostname
265 )));
266 }
267
268 Ok(result)
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275
276 #[tokio::test]
277 async fn test_dns_discovery_creation() {
278 let discovery = DnsDiscovery::new().await;
279 assert!(discovery.is_ok());
280 }
281
282 #[tokio::test]
283 async fn test_ipv4_resolution() {
284 let discovery = DnsDiscovery::new().await.unwrap();
285
286 let result = discovery.resolve_ipv4("dns.google").await;
288 assert!(result.is_ok());
289 let addrs = result.unwrap();
290 assert!(!addrs.is_empty());
291 }
292
293 #[tokio::test]
294 async fn test_ipv6_resolution() {
295 let discovery = DnsDiscovery::new().await.unwrap();
296
297 let result = discovery.resolve_ipv6("dns.google").await;
299 println!("IPv6 resolution result: {:?}", result);
301 }
302
303 #[tokio::test]
304 async fn test_peer_address_formatting() {
305 let ipv4_peer = PeerAddress::new(IpAddr::V4("192.168.1.1".parse().unwrap()), 8444);
306 assert_eq!(ipv4_peer.display_address(), "192.168.1.1:8444");
307 assert!(!ipv4_peer.is_ipv6);
308
309 let ipv6_peer = PeerAddress::new(IpAddr::V6("2001:db8::1".parse().unwrap()), 8444);
310 assert_eq!(ipv6_peer.display_address(), "[2001:db8::1]:8444");
311 assert!(ipv6_peer.is_ipv6);
312 }
313
314 #[tokio::test]
315 async fn test_discovery_result_operations() {
316 let mut result = DiscoveryResult::new();
317
318 result.add_ipv4("192.168.1.1".parse().unwrap(), 8444);
319 result.add_ipv6("2001:db8::1".parse().unwrap(), 8444);
320
321 assert_eq!(result.ipv4_peers.len(), 1);
322 assert_eq!(result.ipv6_peers.len(), 1);
323 assert_eq!(result.total_count, 2);
324 }
325}