1use std::net::SocketAddr;
2use std::sync::Arc;
3use std::time::{Duration, Instant};
4
5use dashmap::DashMap;
6use parking_lot::RwLock;
7use tokio::net::lookup_host;
8use url::Url;
9
10#[derive(Debug, Clone)]
12struct DnsEntry {
13 addresses: Vec<SocketAddr>,
15 resolved_at: Instant,
17 ttl: Duration,
19}
20
21impl DnsEntry {
22 fn is_expired(&self) -> bool {
24 self.resolved_at.elapsed() > self.ttl
25 }
26}
27
28pub struct DnsCache {
45 cache: DashMap<String, DnsEntry>,
47 default_ttl: Duration,
49 max_entries: usize,
51 prefetch_queue: Arc<RwLock<Vec<String>>>,
53 memory_usage: std::sync::atomic::AtomicUsize,
55}
56
57impl DnsCache {
58 pub fn new(default_ttl: Duration, max_entries: usize) -> Self {
60 Self {
61 cache: DashMap::with_capacity(max_entries.min(1024)),
62 default_ttl,
63 max_entries,
64 prefetch_queue: Arc::new(RwLock::new(Vec::new())),
65 memory_usage: std::sync::atomic::AtomicUsize::new(0),
66 }
67 }
68
69 pub fn with_defaults() -> Self {
71 Self::new(Duration::from_secs(300), 10_000)
72 }
73
74 pub async fn resolve(&self, domain: &str) -> Result<Vec<SocketAddr>, DnsError> {
76 if let Some(entry) = self.cache.get(domain) {
78 if !entry.is_expired() {
79 return Ok(entry.addresses.clone());
80 }
81 }
82
83 let addr = format!("{domain}:443");
85 let addresses: Vec<SocketAddr> = lookup_host(&addr)
86 .await
87 .map_err(|e| DnsError::ResolutionFailed {
88 domain: domain.to_string(),
89 source: e,
90 })?
91 .collect();
92
93 if addresses.is_empty() {
94 return Err(DnsError::NoRecords {
95 domain: domain.to_string(),
96 });
97 }
98
99 self.insert(domain, addresses.clone());
100 Ok(addresses)
101 }
102
103 pub async fn resolve_url(&self, url: &Url) -> Result<Vec<SocketAddr>, DnsError> {
105 let domain = url.host_str().ok_or_else(|| DnsError::NoRecords {
106 domain: url.to_string(),
107 })?;
108 self.resolve(domain).await
109 }
110
111 pub fn insert(&self, domain: &str, addresses: Vec<SocketAddr>) {
113 if self.cache.len() >= self.max_entries {
115 self.evict_expired();
116 if self.cache.len() >= self.max_entries {
118 if let Some(oldest) = self.find_oldest_entry() {
119 self.cache.remove(&oldest);
120 }
121 }
122 }
123
124 let entry = DnsEntry {
125 addresses,
126 resolved_at: Instant::now(),
127 ttl: self.default_ttl,
128 };
129
130 let new_size = std::mem::size_of::<DnsEntry>() + domain.len();
132 let old_size = self
133 .cache
134 .get(domain)
135 .map(|e| {
136 std::mem::size_of::<DnsEntry>()
137 + domain.len()
138 + e.addresses.len() * std::mem::size_of::<SocketAddr>()
139 })
140 .unwrap_or(0);
141
142 self.memory_usage.fetch_add(
143 new_size.saturating_sub(old_size),
144 std::sync::atomic::Ordering::Relaxed,
145 );
146
147 self.cache.insert(domain.to_string(), entry);
148 }
149
150 pub fn get_cached(&self, domain: &str) -> Option<Vec<SocketAddr>> {
152 self.cache.get(domain).and_then(|entry| {
153 if entry.is_expired() {
154 None
155 } else {
156 Some(entry.addresses.clone())
157 }
158 })
159 }
160
161 pub fn enqueue_prefetch(&self, domains: Vec<String>) {
163 let mut queue = self.prefetch_queue.write();
164 for domain in domains {
165 if !self.cache.contains_key(&domain) {
166 queue.push(domain);
167 }
168 }
169 }
170
171 pub fn dequeue_prefetch(&self, batch_size: usize) -> Vec<String> {
173 let mut queue = self.prefetch_queue.write();
174 let drain_count = batch_size.min(queue.len());
175 queue.drain(..drain_count).collect()
176 }
177
178 pub fn len(&self) -> usize {
180 self.cache.len()
181 }
182
183 pub fn is_empty(&self) -> bool {
185 self.cache.is_empty()
186 }
187
188 pub fn memory_usage(&self) -> usize {
190 self.memory_usage.load(std::sync::atomic::Ordering::Relaxed)
191 }
192
193 fn evict_expired(&self) {
195 let mut evicted = Vec::new();
196 for entry in self.cache.iter() {
197 if entry.value().is_expired() {
198 evicted.push(entry.key().clone());
199 }
200 }
201 for key in evicted {
202 if let Some(entry) = self.cache.remove(&key) {
203 let size = std::mem::size_of::<DnsEntry>()
204 + key.len()
205 + entry.1.addresses.len() * std::mem::size_of::<SocketAddr>();
206 self.memory_usage
207 .fetch_sub(size, std::sync::atomic::Ordering::Relaxed);
208 }
209 }
210 }
211
212 fn find_oldest_entry(&self) -> Option<String> {
214 self.cache
215 .iter()
216 .min_by_key(|e| e.value().resolved_at)
217 .map(|e| e.key().clone())
218 }
219}
220
221impl Default for DnsCache {
222 fn default() -> Self {
223 Self::with_defaults()
224 }
225}
226
227pub struct DnsPrefetcher {
229 cache: Arc<DnsCache>,
230 prefetch_interval: Duration,
232 batch_size: usize,
234}
235
236impl DnsPrefetcher {
237 pub fn new(cache: Arc<DnsCache>, prefetch_interval: Duration, batch_size: usize) -> Self {
239 Self {
240 cache,
241 prefetch_interval,
242 batch_size,
243 }
244 }
245
246 pub fn with_defaults(cache: Arc<DnsCache>) -> Self {
248 Self::new(cache, Duration::from_millis(100), 10)
249 }
250
251 pub fn spawn(self) -> tokio::task::JoinHandle<()> {
255 tokio::spawn(async move {
256 loop {
257 let domains = self.cache.dequeue_prefetch(self.batch_size);
258 if !domains.is_empty() {
259 for domain in &domains {
260 if let Err(e) = self.cache.resolve(domain).await {
261 tracing::debug!(
262 domain = domain,
263 error = %e,
264 "DNS prefetch failed"
265 );
266 }
267 }
268 }
269 tokio::time::sleep(self.prefetch_interval).await;
270 }
271 })
272 }
273}
274
275#[derive(Debug, thiserror::Error)]
277pub enum DnsError {
278 #[error("DNS resolution failed for {domain}: {source}")]
280 ResolutionFailed {
281 domain: String,
283 source: std::io::Error,
285 },
286 #[error("no DNS records found for {domain}")]
288 NoRecords {
289 domain: String,
291 },
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297
298 #[test]
299 fn test_dns_cache_insert_and_get() {
300 let cache = DnsCache::new(Duration::from_secs(60), 100);
301 let addrs = vec!["127.0.0.1:443".parse().unwrap()];
302 cache.insert("example.com", addrs.clone());
303
304 let cached = cache.get_cached("example.com");
305 assert_eq!(cached, Some(addrs));
306 }
307
308 #[test]
309 fn test_dns_cache_expiry() {
310 let cache = DnsCache::new(Duration::from_millis(1), 100);
311 let addrs = vec!["127.0.0.1:443".parse().unwrap()];
312 cache.insert("example.com", addrs);
313
314 std::thread::sleep(Duration::from_millis(5));
316 assert!(cache.get_cached("example.com").is_none());
317 }
318
319 #[test]
320 fn test_dns_cache_capacity_eviction() {
321 let cache = DnsCache::new(Duration::from_secs(60), 2);
322
323 cache.insert("a.com", vec!["1.1.1.1:443".parse().unwrap()]);
324 cache.insert("b.com", vec!["2.2.2.2:443".parse().unwrap()]);
325 assert_eq!(cache.len(), 2);
326
327 cache.insert("c.com", vec!["3.3.3.3:443".parse().unwrap()]);
329 assert_eq!(cache.len(), 2);
330 }
331
332 #[test]
333 fn test_prefetch_queue() {
334 let cache = DnsCache::new(Duration::from_secs(60), 100);
335 cache.enqueue_prefetch(vec!["a.com".to_string(), "b.com".to_string()]);
336
337 let batch = cache.dequeue_prefetch(10);
338 assert_eq!(batch.len(), 2);
339 assert!(cache.dequeue_prefetch(10).is_empty());
340 }
341
342 #[test]
343 fn test_memory_tracking() {
344 let cache = DnsCache::new(Duration::from_secs(60), 100);
345 let initial = cache.memory_usage();
346 cache.insert("example.com", vec!["1.1.1.1:443".parse().unwrap()]);
347 assert!(cache.memory_usage() > initial);
348 }
349
350 #[tokio::test]
351 async fn test_dns_cache_resolve() {
352 let cache = DnsCache::new(Duration::from_secs(30), 100);
353 let result = cache.resolve("localhost").await;
355 assert!(result.is_ok());
356 let addrs = result.unwrap();
357 assert!(!addrs.is_empty());
358 }
359}