rust_mc_status/core/
dns.rs1use std::net::SocketAddr;
14use std::num::NonZeroUsize;
15use std::sync::Arc;
16use std::time::{Duration, SystemTime};
17
18use hickory_resolver::{
19 config::{ResolverConfig, ResolverOpts},
20 net::runtime::TokioRuntimeProvider,
21 Resolver,
22};
23use lru::LruCache;
24use tokio::sync::{OnceCell, RwLock};
25use tokio::time::timeout;
26
27use crate::error::McError;
28use crate::models::DnsInfo;
29
30const DNS_CACHE_TTL: Duration = Duration::from_secs(300);
32const DEFAULT_CACHE_SIZE: usize = 1024;
34
35static RESOLVER: OnceCell<Arc<Resolver<TokioRuntimeProvider>>> = OnceCell::const_new();
37
38async fn global_resolver() -> Arc<Resolver<TokioRuntimeProvider>> {
39 RESOLVER
40 .get_or_init(|| async {
41 let mut opts = ResolverOpts::default();
42 opts.cache_size = 1000;
44 opts.positive_min_ttl = Some(Duration::from_secs(60));
45 opts.negative_min_ttl = Some(Duration::from_secs(10));
47 Arc::new(
48 Resolver::builder_with_config(
49 ResolverConfig::default(),
50 TokioRuntimeProvider::default(),
51 )
52 .with_options(opts)
53 .build()
54 .expect("failed to build DNS resolver"),
55 )
56 })
57 .await
58 .clone()
59}
60
61#[derive(Clone)]
69pub struct DnsResolver {
70 dns_cache: Arc<RwLock<LruCache<String, (SocketAddr, SystemTime)>>>,
72 srv_cache: Arc<RwLock<LruCache<String, ((String, u16), SystemTime)>>>,
74}
75
76impl DnsResolver {
77 pub fn new() -> Self {
79 Self::with_cache_size(DEFAULT_CACHE_SIZE)
80 }
81
82 pub fn with_cache_size(size: usize) -> Self {
90 let cap = NonZeroUsize::new(size).expect("DNS cache size must be > 0");
91 Self {
92 dns_cache: Arc::new(RwLock::new(LruCache::new(cap))),
93 srv_cache: Arc::new(RwLock::new(LruCache::new(cap))),
94 }
95 }
96
97 pub async fn clear(&self) {
99 self.dns_cache.write().await.clear();
100 self.srv_cache.write().await.clear();
101 }
102
103 pub async fn dns_len(&self) -> usize { self.dns_cache.read().await.len() }
105
106 pub async fn srv_len(&self) -> usize { self.srv_cache.read().await.len() }
108
109 pub async fn lookup_srv(
117 &self,
118 host: &str,
119 tout: Duration,
120 ) -> Option<(String, u16)> {
121 {
123 let cache = self.srv_cache.read().await;
124 if let Some(((h, p), ts)) = cache.peek(host)
125 && ts.elapsed().unwrap_or(Duration::MAX) < DNS_CACHE_TTL {
126 return Some((h.clone(), *p));
127 }
128 }
129
130 let name = format!("_minecraft._tcp.{}", host);
131 let resolver = global_resolver().await;
132 let res = timeout(tout, resolver.srv_lookup(&name))
133 .await.ok()?.ok()?;
134
135 let record = res.answers().iter().find_map(|r| {
136 if let hickory_resolver::proto::rr::RData::SRV(s) = &r.data {
137 Some((
138 s.target.to_string().trim_end_matches('.').to_string(),
139 s.port,
140 ))
141 } else {
142 None
143 }
144 })?;
145
146 let mut cache = self.srv_cache.write().await;
148 if !cache.contains(host) {
149 cache.put(host.to_string(), (record.clone(), SystemTime::now()));
150 }
151 Some(record)
152 }
153
154 pub async fn resolve(
167 &self,
168 host: &str,
169 port: u16,
170 ) -> Result<(SocketAddr, DnsInfo), McError> {
171 let key = format!("{}:{}", host, port);
172
173 {
175 let cache = self.dns_cache.read().await;
176 if let Some((addr, ts)) = cache.peek(&key)
177 && ts.elapsed().unwrap_or(Duration::MAX) < DNS_CACHE_TTL {
178 return Ok((*addr, make_dns_info(&[*addr])));
179 }
180 }
181
182 let host_port = format!("{}:{}", host, port);
184 let addrs: Vec<SocketAddr> = tokio::task::spawn_blocking(move || {
185 std::net::ToSocketAddrs::to_socket_addrs(&host_port).map(|i| i.collect())
186 })
187 .await
188 .map_err(|e| McError::dns(e.to_string()))?
189 .map_err(|e| McError::dns(e.to_string()))?;
190
191 let addr = addrs
192 .iter()
193 .find(|a| a.is_ipv4()) .or_else(|| addrs.first()) .copied()
196 .ok_or_else(|| McError::dns("no addresses resolved"))?;
197
198 let info = make_dns_info(&addrs);
199
200 let mut cache = self.dns_cache.write().await;
202 if !cache.contains(&key) {
203 cache.put(key, (addr, SystemTime::now()));
204 }
205 Ok((addr, info))
206 }
207}
208
209impl Default for DnsResolver {
210 fn default() -> Self { Self::new() }
211}
212
213fn make_dns_info(addrs: &[SocketAddr]) -> DnsInfo {
214 DnsInfo {
215 a_records: addrs.iter().map(|a| a.ip().to_string()).collect(),
216 cname: None,
217 ttl: DNS_CACHE_TTL.as_secs() as u32,
218 }
219}