Skip to content

Commit 629877e

Browse files
committed
fix: udp recv buffer floor and tc bit on overflow
1 parent 7f9dc58 commit 629877e

1 file changed

Lines changed: 132 additions & 3 deletions

File tree

src/server/udp.rs

Lines changed: 132 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@
44
//! Buffer sizes and max concurrency are configurable via [`ServerConfig`].
55
66
use crate::dns::Message;
7+
use crate::dns::types::{RecordClass, RecordType};
78
use crate::server::{RequestHandler, Server, ServerConfig};
89
use crate::{Error, Result};
910
use std::sync::Arc;
1011
use tokio::net::UdpSocket;
1112
use tokio::sync::Semaphore;
1213
use tracing::{debug, error, info, trace, warn};
1314

15+
/// Floor for the read buffer: queries can legally exceed the 512-byte
16+
/// classic limit once EDNS is in play.
17+
const RECV_BUF_MIN: usize = 4096;
18+
1419
/// UDP DNS server.
1520
///
1621
/// Each instance binds one UDP socket and dispatches incoming queries to
@@ -199,7 +204,7 @@ impl UdpServer {
199204
///
200205
/// This method does not panic under normal circumstances.
201206
pub async fn run(&self) -> Result<()> {
202-
let mut buf = vec![0u8; self.config.max_udp_size];
207+
let mut buf = vec![0u8; self.config.max_udp_size.max(RECV_BUF_MIN)];
203208

204209
info!("UDP server started");
205210

@@ -266,6 +271,7 @@ impl UdpServer {
266271

267272
// Create request context
268273
let req_id = request.id();
274+
let max_response = client_udp_limit(&request);
269275
let ctx = crate::server::RequestContext::with_client(
270276
request,
271277
Some(peer_addr),
@@ -285,6 +291,7 @@ impl UdpServer {
285291

286292
// Serialize and send response
287293
let response_data = Self::serialize_response(&response)?;
294+
let response_data = fit_udp_response(&response, response_data, max_response);
288295

289296
socket
290297
.send_to(&response_data, peer_addr)
@@ -304,13 +311,56 @@ impl UdpServer {
304311

305312
/// Serialize DNS response to wire format.
306313
///
307-
/// Thin wrapper over [`crate::dns::wire::serialize_message`]; returns an error
308-
/// on invalid names/labels or messages exceeding the UDP size limit.
314+
/// Thin wrapper over [`crate::dns::wire::serialize_message`]; returns an
315+
/// error on invalid names/labels.
309316
fn serialize_response(message: &Message) -> Result<Vec<u8>> {
310317
crate::dns::wire::serialize_message(message)
311318
}
312319
}
313320

321+
/// Response size limit for `request`: the client's advertised EDNS payload
322+
/// size, or the classic 512-byte limit without EDNS. Sizes below 512 are
323+
/// ignored (RFC 6891).
324+
fn client_udp_limit(request: &Message) -> usize {
325+
request
326+
.additional()
327+
.iter()
328+
.find(|rr| rr.rtype() == RecordType::OPT)
329+
.and_then(|rr| match rr.rclass() {
330+
RecordClass::Unknown(size) if size >= 512 => Some(size as usize),
331+
_ => None,
332+
})
333+
.unwrap_or(512)
334+
}
335+
336+
/// Send `serialized` as-is while it fits; past the limit, fall back to a
337+
/// TC=1 header plus question section so the client retries over TCP.
338+
fn fit_udp_response(response: &Message, serialized: Vec<u8>, limit: usize) -> Vec<u8> {
339+
if serialized.len() <= limit {
340+
return serialized;
341+
}
342+
343+
let mut truncated = response.clone();
344+
truncated.set_truncated(true);
345+
truncated.answers_mut().clear();
346+
truncated.authority_mut().clear();
347+
truncated.additional_mut().clear();
348+
match crate::dns::wire::serialize_message(&truncated) {
349+
Ok(data) => {
350+
debug!(
351+
limit,
352+
full_len = serialized.len(),
353+
truncated_len = data.len(),
354+
"response exceeds UDP limit, sending TC=1"
355+
);
356+
data
357+
}
358+
// cannot happen for a message that just serialized, but never
359+
// fail to answer over a truncation fallback
360+
Err(_) => serialized,
361+
}
362+
}
363+
314364
#[async_trait::async_trait]
315365
impl Server for UdpServer {
316366
async fn from_config(config: ServerConfig) -> Result<Self> {
@@ -452,4 +502,83 @@ mod tests {
452502
assert!(parsed.is_response());
453503
assert!(parsed.recursion_available());
454504
}
505+
506+
#[test]
507+
fn test_client_udp_limit() {
508+
let mut req = Message::new();
509+
req.set_query(true);
510+
req.add_question(Question::new("example.com", RecordType::A, RecordClass::IN));
511+
512+
// no EDNS: classic 512-byte limit
513+
assert_eq!(client_udp_limit(&req), 512);
514+
515+
req.add_additional(crate::dns::ResourceRecord::new(
516+
".",
517+
RecordType::OPT,
518+
RecordClass::from_u16(1232),
519+
0,
520+
crate::dns::RData::OPT {
521+
extended_rcode: 0,
522+
version: 0,
523+
flags: 0,
524+
options: Vec::new(),
525+
},
526+
));
527+
assert_eq!(client_udp_limit(&req), 1232);
528+
529+
// sizes below 512 are invalid per RFC 6891 and must not shrink the limit
530+
let mut tiny = Message::new();
531+
tiny.add_question(Question::new("example.com", RecordType::A, RecordClass::IN));
532+
tiny.add_additional(crate::dns::ResourceRecord::new(
533+
".",
534+
RecordType::OPT,
535+
RecordClass::from_u16(100),
536+
0,
537+
crate::dns::RData::OPT {
538+
extended_rcode: 0,
539+
version: 0,
540+
flags: 0,
541+
options: Vec::new(),
542+
},
543+
));
544+
assert_eq!(client_udp_limit(&tiny), 512);
545+
}
546+
547+
#[test]
548+
fn test_fit_udp_response_truncates() {
549+
let mut resp = Message::new();
550+
resp.set_id(0x77);
551+
resp.set_response(true);
552+
resp.add_question(Question::new(
553+
"big.example.com",
554+
RecordType::A,
555+
RecordClass::IN,
556+
));
557+
for i in 0..40 {
558+
resp.add_answer(crate::dns::ResourceRecord::new(
559+
"big.example.com",
560+
RecordType::A,
561+
RecordClass::IN,
562+
300,
563+
crate::dns::RData::A(std::net::Ipv4Addr::new(192, 0, 2, i as u8)),
564+
));
565+
}
566+
567+
let full = UdpServer::serialize_response(&resp).unwrap();
568+
assert!(full.len() > 512);
569+
570+
let out = fit_udp_response(&resp, full.clone(), 512);
571+
assert!(out.len() <= 512, "truncated response must fit the limit");
572+
let parsed = wire::parse_message(&out).unwrap();
573+
assert!(parsed.is_truncated(), "TC bit must be set");
574+
assert_eq!(parsed.answer_count(), 0);
575+
576+
// a fitting message goes out untouched
577+
let mut small = Message::new();
578+
small.set_id(0x78);
579+
small.set_response(true);
580+
let small_data = UdpServer::serialize_response(&small).unwrap();
581+
let same = fit_udp_response(&small, small_data.clone(), 512);
582+
assert_eq!(same, small_data);
583+
}
455584
}

0 commit comments

Comments
 (0)