1use crate::queues::WakePipe;
41use crate::virtio_net_log;
42use polling::{Event, Events};
43use std::io::{Read, Write};
44use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream, UdpSocket as HostUdpSocket};
45use std::sync::atomic::{AtomicUsize, Ordering};
46use std::sync::mpsc::{self, Receiver, SyncSender, TryRecvError, TrySendError};
47use std::sync::Arc;
48use std::thread;
49use std::time::{Duration, Instant};
50
51const DNS_PORT: u16 = 53;
53const UPSTREAM_TIMEOUT: Duration = Duration::from_secs(2);
55const DNS_MAX_MSG: usize = 4096;
57const CHANNEL_CAPACITY: usize = 256;
59const MAX_INFLIGHT_UDP: usize = 256;
61const MAX_INFLIGHT_TCP: usize = 64;
63const RELAY_POLL_MAX_MS: u64 = 250;
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub enum DnsTransport {
69 Udp,
70 Tcp,
71}
72
73pub struct DnsQuery {
75 pub id: u64,
77 pub transport: DnsTransport,
79 pub upstream: Ipv4Addr,
81 pub query: Vec<u8>,
83}
84
85pub struct DnsResponse {
87 pub id: u64,
89 pub answer: Option<Vec<u8>>,
92}
93
94pub struct DnsRelayChannels {
96 pub to_relay: SyncSender<DnsQuery>,
98 pub from_relay: Receiver<DnsResponse>,
100 pub relay_thread_wake: WakePipe,
102}
103
104pub fn start_dns_relay(
111 reply_wake: Arc<WakePipe>,
112 shutdown: Arc<dyn Fn() -> bool + Send + Sync>,
113) -> DnsRelayChannels {
114 let (to_relay_tx, to_relay_rx) = mpsc::sync_channel(CHANNEL_CAPACITY);
115 let (from_relay_tx, from_relay_rx) = mpsc::sync_channel(CHANNEL_CAPACITY);
116 let relay_thread_wake = WakePipe::new();
117 let thread_wake = relay_thread_wake.clone();
118
119 let _ = thread::Builder::new()
120 .name("smolvm-dns-relay".into())
121 .spawn(move || {
122 run_dns_relay(
123 to_relay_rx,
124 from_relay_tx,
125 thread_wake,
126 reply_wake,
127 shutdown,
128 );
129 });
130
131 DnsRelayChannels {
132 to_relay: to_relay_tx,
133 from_relay: from_relay_rx,
134 relay_thread_wake,
135 }
136}
137
138struct InflightUdp {
141 socket: HostUdpSocket,
142 deadline: Instant,
143}
144
145fn deliver_answer(inbound: &SyncSender<DnsResponse>, id: u64, answer: Option<Vec<u8>>) -> bool {
148 match inbound.try_send(DnsResponse { id, answer }) {
149 Ok(()) => true,
150 Err(TrySendError::Full(_)) => {
151 virtio_net_log!(
152 "virtio-net: dropping DNS answer id={} (inbound queue full)",
153 id
154 );
155 true
156 }
157 Err(TrySendError::Disconnected(_)) => false,
158 }
159}
160
161fn run_dns_relay(
162 outbound: Receiver<DnsQuery>,
163 inbound: SyncSender<DnsResponse>,
164 wake: WakePipe,
165 reply_wake: Arc<WakePipe>,
166 shutdown: Arc<dyn Fn() -> bool + Send + Sync>,
167) {
168 let mut inflight: std::collections::HashMap<u64, InflightUdp> =
169 std::collections::HashMap::new();
170 let mut recv_buf = vec![0u8; DNS_MAX_MSG];
171 let tcp_inflight = Arc::new(AtomicUsize::new(0));
172
173 loop {
174 if shutdown() {
175 return;
176 }
177
178 let mut woke_reply = false;
179
180 loop {
182 match outbound.try_recv() {
183 Ok(query) => match query.transport {
184 DnsTransport::Udp => {
185 if inflight.len() >= MAX_INFLIGHT_UDP {
186 virtio_net_log!(
187 "virtio-net: dropping DNS query id={} (in-flight UDP table full)",
188 query.id
189 );
190 woke_reply |= deliver_answer(&inbound, query.id, None);
191 continue;
192 }
193 let upstream = SocketAddr::new(IpAddr::V4(query.upstream), DNS_PORT);
194 match start_udp_query(upstream, &query.query) {
195 Ok(socket) => {
196 inflight.insert(
197 query.id,
198 InflightUdp {
199 socket,
200 deadline: Instant::now() + UPSTREAM_TIMEOUT,
201 },
202 );
203 }
204 Err(err) => {
205 virtio_net_log!(
206 "virtio-net: DNS/UDP upstream send failed id={} error={}",
207 query.id,
208 err
209 );
210 woke_reply |= deliver_answer(&inbound, query.id, None);
211 }
212 }
213 }
214 DnsTransport::Tcp => {
215 spawn_tcp_query(&inbound, &reply_wake, &tcp_inflight, query);
216 }
217 },
218 Err(TryRecvError::Empty) => break,
219 Err(TryRecvError::Disconnected) => return,
220 }
221 }
222
223 let poller = wake.poller();
226 let ids: Vec<u64> = inflight.keys().copied().collect();
227 for (slot, id) in ids.iter().enumerate() {
228 let _ = unsafe { poller.add(&inflight[id].socket, Event::readable(slot + 1)) };
231 }
232
233 let now = Instant::now();
235 let mut wait = Duration::from_millis(RELAY_POLL_MAX_MS);
236 for f in inflight.values() {
237 let remaining = f.deadline.saturating_duration_since(now);
238 if remaining < wait {
239 wait = remaining;
240 }
241 }
242
243 let mut events = Events::new();
244 let _ = poller.wait(&mut events, Some(wait));
245
246 let mut ready: Vec<bool> = vec![false; ids.len()];
247 for event in events.iter() {
248 if event.key >= 1 && event.key - 1 < ready.len() {
249 ready[event.key - 1] = true;
250 }
251 }
252
253 for id in &ids {
255 let _ = poller.delete(&inflight[id].socket);
256 }
257
258 let now = Instant::now();
260 for (slot, id) in ids.iter().enumerate() {
261 if ready[slot] {
262 let answer = inflight
263 .get(id)
264 .and_then(|f| match f.socket.recv(&mut recv_buf) {
265 Ok(len) => Some(recv_buf[..len].to_vec()),
266 Err(_) => None,
267 });
268 match answer {
269 Some(bytes) => {
270 inflight.remove(id);
271 woke_reply |= deliver_answer(&inbound, *id, Some(bytes));
272 }
273 None if inflight.get(id).is_some_and(|f| f.deadline <= now) => {
276 inflight.remove(id);
277 woke_reply |= deliver_answer(&inbound, *id, None);
278 }
279 None => {}
280 }
281 } else if inflight.get(id).is_some_and(|f| f.deadline <= now) {
282 inflight.remove(id);
283 woke_reply |= deliver_answer(&inbound, *id, None);
284 }
285 }
286
287 if woke_reply {
288 reply_wake.wake();
289 }
290 }
291}
292
293fn start_udp_query(upstream: SocketAddr, query: &[u8]) -> std::io::Result<HostUdpSocket> {
296 let bind: SocketAddr = if upstream.is_ipv4() {
297 (Ipv4Addr::UNSPECIFIED, 0).into()
298 } else {
299 (std::net::Ipv6Addr::UNSPECIFIED, 0).into()
300 };
301 let socket = HostUdpSocket::bind(bind)?;
302 socket.connect(upstream)?;
303 socket.set_nonblocking(true)?;
304 socket.send(query)?;
305 Ok(socket)
306}
307
308fn spawn_tcp_query(
316 inbound: &SyncSender<DnsResponse>,
317 reply_wake: &Arc<WakePipe>,
318 tcp_inflight: &Arc<AtomicUsize>,
319 query: DnsQuery,
320) {
321 if tcp_inflight.load(Ordering::Relaxed) >= MAX_INFLIGHT_TCP {
322 virtio_net_log!(
323 "virtio-net: dropping DNS/TCP query id={} (worker cap reached)",
324 query.id
325 );
326 if deliver_answer(inbound, query.id, None) {
327 reply_wake.wake();
328 }
329 return;
330 }
331 tcp_inflight.fetch_add(1, Ordering::Relaxed);
332 let worker_inbound = inbound.clone();
333 let worker_wake = reply_wake.clone();
334 let worker_inflight = tcp_inflight.clone();
335 let id = query.id;
336 let spawned = thread::Builder::new()
337 .name("smolvm-dns-tcp".into())
338 .spawn(move || {
339 let upstream = SocketAddr::new(IpAddr::V4(query.upstream), DNS_PORT);
340 let answer = forward_dns_query_tcp(upstream, &query.query).ok();
341 if deliver_answer(&worker_inbound, id, answer) {
342 worker_wake.wake();
343 }
344 worker_inflight.fetch_sub(1, Ordering::Relaxed);
345 });
346 if spawned.is_err() {
347 tcp_inflight.fetch_sub(1, Ordering::Relaxed);
349 if deliver_answer(inbound, id, None) {
350 reply_wake.wake();
351 }
352 }
353}
354
355fn forward_dns_query_tcp(upstream: SocketAddr, query: &[u8]) -> std::io::Result<Vec<u8>> {
360 use std::io::{Error, ErrorKind};
361 let len = u16::try_from(query.len())
362 .map_err(|_| Error::new(ErrorKind::InvalidInput, "DNS query too large for TCP"))?;
363 let mut stream = TcpStream::connect_timeout(&upstream, UPSTREAM_TIMEOUT)?;
364 stream.set_read_timeout(Some(UPSTREAM_TIMEOUT))?;
365 stream.set_write_timeout(Some(UPSTREAM_TIMEOUT))?;
366 stream.write_all(&len.to_be_bytes())?;
367 stream.write_all(query)?;
368 stream.flush()?;
369
370 let mut len_buf = [0u8; 2];
371 stream.read_exact(&mut len_buf)?;
372 let resp_len = u16::from_be_bytes(len_buf) as usize;
373 if resp_len == 0 || resp_len > DNS_MAX_MSG {
374 return Err(Error::new(
375 ErrorKind::InvalidData,
376 "upstream DNS/TCP response length out of range",
377 ));
378 }
379 let mut response = vec![0u8; resp_len];
380 stream.read_exact(&mut response)?;
381 Ok(response)
382}
383
384#[cfg(test)]
385mod tests {
386 use super::*;
387 use std::sync::atomic::AtomicBool;
388
389 #[test]
393 fn start_udp_query_sends_and_receives_nonblocking() {
394 let upstream = HostUdpSocket::bind("127.0.0.1:0").unwrap();
395 let upstream_addr = upstream.local_addr().unwrap();
396 let resolver = thread::spawn(move || {
397 let mut buf = [0u8; 512];
398 upstream
399 .set_read_timeout(Some(Duration::from_secs(2)))
400 .unwrap();
401 let (n, peer) = upstream.recv_from(&mut buf).unwrap();
402 upstream.send_to(&buf[..n], peer).unwrap();
403 });
404
405 let sock = start_udp_query(upstream_addr, b"\x12\x34hello-dns").unwrap();
406
407 let deadline = Instant::now() + Duration::from_secs(2);
408 let mut buf = [0u8; 512];
409 loop {
410 match sock.recv(&mut buf) {
411 Ok(n) => {
412 assert_eq!(&buf[..n], b"\x12\x34hello-dns");
413 break;
414 }
415 Err(_) if Instant::now() < deadline => {
416 thread::sleep(Duration::from_millis(5));
417 }
418 Err(e) => panic!("no DNS answer: {e}"),
419 }
420 }
421 resolver.join().unwrap();
422 }
423
424 #[test]
429 fn poll_thread_never_blocks_on_dead_resolver() {
430 let reply_wake = Arc::new(WakePipe::new());
431 let stop = Arc::new(AtomicBool::new(false));
432 let stop_flag = stop.clone();
433 let channels = start_dns_relay(
434 reply_wake.clone(),
435 Arc::new(move || stop_flag.load(Ordering::Relaxed)),
436 );
437
438 let started = Instant::now();
441 channels
442 .to_relay
443 .send(DnsQuery {
444 id: 7,
445 transport: DnsTransport::Udp,
446 upstream: Ipv4Addr::new(192, 0, 2, 1),
447 query: b"\x00\x00query".to_vec(),
448 })
449 .unwrap();
450 channels.relay_thread_wake.wake();
451
452 assert!(started.elapsed() < Duration::from_millis(50));
454
455 let resp = channels
458 .from_relay
459 .recv_timeout(Duration::from_secs(4))
460 .expect("relay must always answer, even on timeout");
461 assert_eq!(resp.id, 7);
462 assert!(resp.answer.is_none());
463
464 stop.store(true, Ordering::Relaxed);
465 channels.relay_thread_wake.wake();
466 }
467
468 #[test]
472 fn forward_dns_query_tcp_round_trips() {
473 use std::net::TcpListener;
474 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
475 let addr = listener.local_addr().unwrap();
476
477 let server = thread::spawn(move || {
480 let (mut sock, _) = listener.accept().unwrap();
481 let mut len_buf = [0u8; 2];
482 sock.read_exact(&mut len_buf).unwrap();
483 let qlen = u16::from_be_bytes(len_buf) as usize;
484 let mut q = vec![0u8; qlen];
485 sock.read_exact(&mut q).unwrap();
486 assert_eq!(&q, b"tcp-query");
487 let answer = b"answer-bytes";
488 sock.write_all(&(answer.len() as u16).to_be_bytes())
489 .unwrap();
490 sock.write_all(answer).unwrap();
491 });
492
493 let resp = forward_dns_query_tcp(addr, b"tcp-query").unwrap();
494 assert_eq!(resp, b"answer-bytes");
495 server.join().unwrap();
496 }
497}