From 829d2b9d21d04b15aa81bce4d5c4a319debca3be Mon Sep 17 00:00:00 2001 From: Razvan Dimescu Date: Tue, 7 Apr 2026 20:10:51 +0300 Subject: [PATCH] refactor: simplify DoT cert/key match and extract send_response helper - Flatten 4-arm cert/key match in start_dot to 2 arms with the partial-config warning hoisted into a one-liner above the match. - Extract send_response() that serializes a DnsPacket and writes it framed, used by both the FORMERR-on-parse-error and SERVFAIL-on- resolve-error paths. Removes duplicated buffer/write/log boilerplate and unifies the rescode logging via {:?}. No behavior change; 126/126 tests still pass. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/dot.rs | 70 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/src/dot.rs b/src/dot.rs index 2178c26..291f6f0 100644 --- a/src/dot.rs +++ b/src/dot.rs @@ -60,6 +60,9 @@ fn fallback_tls(ctx: &ServerCtx) -> Option> { /// Start the DNS-over-TLS listener (RFC 7858). pub async fn start_dot(ctx: Arc, config: &DotConfig) { + if config.cert_path.is_some() != config.key_path.is_some() { + warn!("DoT: both cert_path and key_path must be set — ignoring partial config, using self-signed"); + } let tls_config = match (&config.cert_path, &config.key_path) { (Some(cert), Some(key)) => match load_tls_config(cert, key) { Ok(cfg) => cfg, @@ -68,14 +71,7 @@ pub async fn start_dot(ctx: Arc, config: &DotConfig) { return; } }, - (Some(_), None) | (None, Some(_)) => { - warn!("DoT: both cert_path and key_path must be set — ignoring partial config, using self-signed"); - match fallback_tls(&ctx) { - Some(cfg) => cfg, - None => return, - } - } - (None, None) => match fallback_tls(&ctx) { + _ => match fallback_tls(&ctx) { Some(cfg) => cfg, None => return, }, @@ -190,41 +186,55 @@ where resp.header.id = query_id; resp.header.response = true; resp.header.rescode = ResultCode::FORMERR; - let mut out_buf = BytePacketBuffer::new(); - if resp.write(&mut out_buf).is_err() { - debug!("DoT: failed to serialize FORMERR for {}", remote_addr); - break; - } - if write_framed(&mut stream, out_buf.filled()).await.is_err() { + if send_response(&mut stream, &resp, remote_addr).await.is_err() { break; } continue; } }; - let resp_buffer = match resolve_query(query.clone(), remote_addr, ctx).await { - Ok(buf) => buf, - Err(e) => { - warn!("{} | RESOLVE ERROR | {}", remote_addr, e); - // Build SERVFAIL that echoes the original question section. - let resp = DnsPacket::response_from(&query, ResultCode::SERVFAIL); - let mut out_buf = BytePacketBuffer::new(); - if resp.write(&mut out_buf).is_err() { - debug!("DoT: failed to serialize SERVFAIL for {}", remote_addr); + match resolve_query(query.clone(), remote_addr, ctx).await { + Ok(resp_buffer) => { + if write_framed(&mut stream, resp_buffer.filled()) + .await + .is_err() + { + break; + } + } + Err(e) => { + warn!("{} | RESOLVE ERROR | {}", remote_addr, e); + // SERVFAIL that echoes the original question section. + let resp = DnsPacket::response_from(&query, ResultCode::SERVFAIL); + if send_response(&mut stream, &resp, remote_addr).await.is_err() { break; } - out_buf } - }; - if write_framed(&mut stream, resp_buffer.filled()) - .await - .is_err() - { - break; } } } +/// Serialize a DNS response and send it framed. Logs serialization failures +/// and returns Err so the caller can tear down the connection. +async fn send_response( + stream: &mut S, + resp: &DnsPacket, + remote_addr: SocketAddr, +) -> std::io::Result<()> +where + S: AsyncWriteExt + Unpin, +{ + let mut out_buf = BytePacketBuffer::new(); + if resp.write(&mut out_buf).is_err() { + debug!( + "DoT: failed to serialize {:?} response for {}", + resp.header.rescode, remote_addr + ); + return Err(std::io::Error::other("serialize failed")); + } + write_framed(stream, out_buf.filled()).await +} + /// Write a DNS message with its 2-byte length prefix, coalesced into one syscall. async fn write_framed(stream: &mut S, msg: &[u8]) -> std::io::Result<()> where