1pub mod actions;
8pub mod dnssec;
9pub mod records;
10pub mod transfer;
11
12use crate::llm::action_helper::call_llm;
13use crate::llm::ollama_client::OllamaClient;
14use crate::llm::ActionResult;
15use crate::logging::emit::Log;
16use crate::protocol::Event;
17use crate::server::connection::ConnectionId;
18use crate::server::DnsProtocol;
19use crate::state::app_state::AppState;
20use actions::{DNS_QUERY_EVENT, DNS_ZONE_TRANSFER_EVENT, RESULT_IXFR, RESULT_ZONE};
21use anyhow::Result;
22use dnssec::ZoneSigner;
23use hickory_proto::op::{Edns, Message as DnsMessage, Query, ResponseCode};
24use hickory_proto::rr::{Name, Record, RecordType};
25use std::collections::HashMap;
26use std::net::{IpAddr, Ipv6Addr, SocketAddr};
27use std::sync::Arc;
28use std::time::Duration;
29use tokio::io::{AsyncReadExt, AsyncWriteExt};
30use tokio::net::{TcpListener, TcpStream, UdpSocket};
31use tokio::sync::mpsc;
32
33use crate::server::accept_bounded::{accept_bounded, ConnectionLimiter};
34
35pub const DEFAULT_TCP_IDLE_TIMEOUT: Duration = Duration::from_secs(10);
38
39pub const MAX_TCP_CONNECTIONS: usize = 64;
41
42pub const MAX_TCP_CONNECTIONS_PER_IP: usize = 8;
48
49pub const CONNECTION_CAP_REFUSAL: &[u8] = b"";
52
53pub const EDNS_UDP_SIZE: u16 = 1232;
56
57pub const PLAIN_UDP_LIMIT: usize = 512;
59
60pub const MAX_UDP_RESPONSE: usize = 4096;
63
64pub struct DnsSettings {
66 pub dnssec_zone: Option<String>,
67 pub tcp_idle_timeout: Duration,
68 pub tcp_connections_per_ip: usize,
70}
71
72struct PerSourceLimiter {
74 max: usize,
75 open: Arc<std::sync::Mutex<HashMap<IpAddr, usize>>>,
76}
77
78struct SourceSlot {
81 key: IpAddr,
82 open: Arc<std::sync::Mutex<HashMap<IpAddr, usize>>>,
83}
84
85impl PerSourceLimiter {
86 fn new(max: usize) -> Self {
87 Self {
88 max: max.max(1),
89 open: Arc::default(),
90 }
91 }
92
93 fn try_acquire(&self, ip: IpAddr) -> Option<SourceSlot> {
95 let key = source_key(ip);
96 let mut open = self.open.lock().unwrap_or_else(|e| e.into_inner());
97 let count = open.entry(key).or_insert(0);
98 if *count >= self.max {
99 return None;
100 }
101 *count += 1;
102 Some(SourceSlot {
103 key,
104 open: self.open.clone(),
105 })
106 }
107}
108
109impl Drop for SourceSlot {
110 fn drop(&mut self) {
111 let mut open = self.open.lock().unwrap_or_else(|e| e.into_inner());
112 if let Some(count) = open.get_mut(&self.key) {
113 *count = count.saturating_sub(1);
114 if *count == 0 {
115 open.remove(&self.key);
116 }
117 }
118 }
119}
120
121fn source_key(ip: IpAddr) -> IpAddr {
124 match ip {
125 IpAddr::V4(_) => ip,
126 IpAddr::V6(v6) => match v6.to_ipv4_mapped() {
127 Some(v4) => IpAddr::V4(v4),
128 None => {
129 let s = v6.segments();
130 IpAddr::V6(Ipv6Addr::new(s[0], s[1], s[2], s[3], 0, 0, 0, 0))
131 }
132 },
133 }
134}
135
136pub struct DnsServer;
138
139#[derive(Clone, Copy, PartialEq, Eq, Debug)]
140enum Transport {
141 Udp,
142 Tcp,
143}
144
145impl Transport {
146 fn as_str(self) -> &'static str {
147 match self {
148 Transport::Udp => "udp",
149 Transport::Tcp => "tcp",
150 }
151 }
152}
153
154struct Shared {
156 llm_client: OllamaClient,
157 app_state: Arc<AppState>,
158 status_tx: mpsc::UnboundedSender<String>,
159 server_id: crate::state::ServerId,
160 protocol: Arc<DnsProtocol>,
161 signer: Option<Arc<ZoneSigner>>,
162}
163
164fn now_secs() -> u32 {
165 crate::utils::clock::SystemTime::now()
166 .duration_since(crate::utils::clock::UNIX_EPOCH)
167 .map(|d| d.as_secs() as u32)
168 .unwrap_or_default()
169}
170
171impl DnsServer {
172 pub async fn spawn_with_llm_actions(
174 listen_addr: SocketAddr,
175 llm_client: OllamaClient,
176 app_state: Arc<AppState>,
177 status_tx: mpsc::UnboundedSender<String>,
178 server_id: crate::state::ServerId,
179 settings: DnsSettings,
180 ) -> Result<SocketAddr> {
181 const PORT_PAIR_ATTEMPTS: usize = 16;
186 let mut attempt = 0;
187 let (socket, local_addr, listener) = loop {
188 attempt += 1;
189 let socket = Arc::new(UdpSocket::bind(listen_addr).await?);
190 let local_addr = socket.local_addr()?;
191 match TcpListener::bind(local_addr).await {
192 Ok(listener) => break (socket, local_addr, listener),
193 Err(e)
194 if listen_addr.port() == 0
195 && e.kind() == std::io::ErrorKind::AddrInUse
196 && attempt < PORT_PAIR_ATTEMPTS =>
197 {
198 continue
199 }
200 Err(e) => {
201 return Err(anyhow::Error::new(e)
202 .context(format!("DNS over TCP cannot bind {local_addr}")))
203 }
204 }
205 };
206 let log = Log::new(Some(&status_tx));
207 log.info(format!(
208 "DNS server listening on {local_addr} (UDP and TCP)"
209 ));
210
211 let signer = match settings.dnssec_zone.as_deref() {
212 None => None,
213 Some(zone) => {
214 let name = records::parse_name(zone, None)?;
215 let signer = ZoneSigner::generate(name, now_secs())?;
216 log.info(format!(
217 "DNS signing {} with DNSSEC (ECDSA P-256, key tag {})",
218 signer.zone(),
219 signer.key_tag()
220 ));
221 Some(Arc::new(signer))
222 }
223 };
224
225 let shared = Arc::new(Shared {
226 llm_client,
227 app_state: app_state.clone(),
228 status_tx: status_tx.clone(),
229 server_id,
230 protocol: Arc::new(DnsProtocol::new()),
231 signer,
232 });
233
234 let udp_shared = shared.clone();
235 let udp_handle = tokio::spawn(async move {
236 Self::udp_loop(socket, local_addr, udp_shared).await;
237 });
238 app_state.register_server_task(server_id, udp_handle).await;
239
240 let tcp_shared = shared.clone();
241 let idle = settings.tcp_idle_timeout;
242 let per_source = PerSourceLimiter::new(settings.tcp_connections_per_ip);
243 let tcp_handle = tokio::spawn(async move {
244 Self::tcp_loop(listener, local_addr, tcp_shared, idle, per_source).await;
245 });
246 app_state.register_server_task(server_id, tcp_handle).await;
247
248 Ok(local_addr)
249 }
250
251 async fn register_connection(
252 shared: &Shared,
253 peer_addr: SocketAddr,
254 local_addr: SocketAddr,
255 received: u64,
256 ) -> ConnectionId {
257 use crate::state::server::{
258 ConnectionState as ServerConnectionState, ConnectionStatus, ProtocolConnectionInfo,
259 };
260 let connection_id = ConnectionId::new(shared.app_state.get_next_unified_id().await);
261 let now = crate::utils::clock::Instant::now();
262 shared
263 .app_state
264 .add_connection_to_server(
265 shared.server_id,
266 ServerConnectionState {
267 id: connection_id,
268 remote_addr: peer_addr,
269 local_addr,
270 bytes_sent: 0,
271 bytes_received: received,
272 packets_sent: 0,
273 packets_received: u64::from(received > 0),
274 last_activity: now,
275 status: ConnectionStatus::Active,
276 status_changed_at: now,
277 protocol_info: ProtocolConnectionInfo::empty(),
278 },
279 )
280 .await;
281 let _ = shared.status_tx.send("__UPDATE_UI__".to_string());
282 connection_id
283 }
284
285 async fn udp_loop(socket: Arc<UdpSocket>, local_addr: SocketAddr, shared: Arc<Shared>) {
286 let mut buffer = vec![0u8; 4096];
290 loop {
291 let (n, peer_addr) = match socket.recv_from(&mut buffer).await {
292 Ok(r) => r,
293 Err(e) => {
294 Log::new(Some(&shared.status_tx)).error(format!("DNS receive error: {e}"));
295 break;
296 }
297 };
298 let data = buffer[..n].to_vec();
299 let connection_id =
300 Self::register_connection(&shared, peer_addr, local_addr, n as u64).await;
301 {
302 let log = Log::new(Some(&shared.status_tx));
303 log.debug(format!("DNS received {n} bytes from {peer_addr} over UDP"));
304 log.trace(format!("DNS data (hex): {}", hex::encode(&data)));
305 }
306 let task_shared = shared.clone();
307 let task_socket = socket.clone();
308 shared
310 .app_state
311 .spawn_server_task(shared.server_id, async move {
312 let replies = Self::answer(
313 &task_shared,
314 &data,
315 peer_addr,
316 connection_id,
317 Transport::Udp,
318 )
319 .await;
320 if let Some(reply) = replies.first() {
322 let _ = task_socket.send_to(reply, peer_addr).await;
323 Self::count_sent(&task_shared, connection_id, reply.len()).await;
324 }
325 })
326 .await;
327 }
328 }
329
330 async fn tcp_loop(
331 listener: TcpListener,
332 local_addr: SocketAddr,
333 shared: Arc<Shared>,
334 idle: Duration,
335 per_source: PerSourceLimiter,
336 ) {
337 let limiter = ConnectionLimiter::new(MAX_TCP_CONNECTIONS);
340 loop {
341 let (stream, peer_addr, permit) = match accept_bounded(
342 &listener,
343 &limiter,
344 CONNECTION_CAP_REFUSAL,
345 "DNS",
346 Some(&shared.status_tx),
347 )
348 .await
349 {
350 Ok(r) => r,
351 Err(e) => {
352 Log::new(Some(&shared.status_tx)).error(format!("DNS TCP accept error: {e}"));
353 continue;
354 }
355 };
356 let Some(source_slot) = per_source.try_acquire(peer_addr.ip()) else {
357 Log::new(Some(&shared.status_tx)).warn(format!(
358 "DNS connection from {peer_addr} refused decision=fail_closed_per_source_cap \
359 (limit {} concurrent per address)",
360 per_source.max
361 ));
362 drop(permit);
365 let mut stream = stream;
366 let _ = stream.shutdown().await;
367 continue;
368 };
369 let connection_id = Self::register_connection(&shared, peer_addr, local_addr, 0).await;
370 let task_shared = shared.clone();
371 shared
372 .app_state
373 .spawn_server_task(shared.server_id, async move {
374 let _permit = permit;
375 let _source_slot = source_slot;
376 Self::tcp_connection(stream, peer_addr, connection_id, task_shared, idle).await;
377 })
378 .await;
379 }
380 }
381
382 async fn tcp_connection(
386 mut stream: TcpStream,
387 peer_addr: SocketAddr,
388 connection_id: ConnectionId,
389 shared: Arc<Shared>,
390 idle: Duration,
391 ) {
392 let log = Log::new(Some(&shared.status_tx));
393 loop {
394 let len = match tokio::time::timeout(idle, stream.read_u16()).await {
395 Err(_) => {
396 log.debug(format!(
397 "DNS TCP {peer_addr} idle for {}s; closing decision=tcp_idle",
398 idle.as_secs()
399 ));
400 break;
401 }
402 Ok(Err(_)) => break, Ok(Ok(0)) => break,
404 Ok(Ok(n)) => n as usize,
405 };
406 let mut data = vec![0u8; len];
407 match tokio::time::timeout(idle, stream.read_exact(&mut data)).await {
408 Ok(Ok(_)) => {}
409 _ => {
410 log.debug(format!(
411 "DNS TCP {peer_addr} stopped part way through a {len}-byte message"
412 ));
413 break;
414 }
415 }
416 shared
417 .app_state
418 .update_connection_stats(
419 shared.server_id,
420 connection_id,
421 Some(len as u64 + 2),
422 None,
423 Some(1),
424 None,
425 )
426 .await;
427 log.debug(format!(
428 "DNS received {len} bytes from {peer_addr} over TCP"
429 ));
430 let replies =
431 Self::answer(&shared, &data, peer_addr, connection_id, Transport::Tcp).await;
432 if replies.is_empty() && DnsMessage::from_vec(&data).is_err() {
433 break; }
435 for reply in &replies {
436 let Ok(reply_len) = u16::try_from(reply.len()) else {
440 log.error(format!(
441 "DNS reply of {} bytes to {peer_addr} does not fit a TCP length prefix; \
442 closing decision=fail_closed_oversize_answer",
443 reply.len()
444 ));
445 let _ = stream.shutdown().await;
446 return;
447 };
448 let mut framed = Vec::with_capacity(reply.len() + 2);
449 framed.extend_from_slice(&reply_len.to_be_bytes());
450 framed.extend_from_slice(reply);
451 if stream.write_all(&framed).await.is_err() {
452 return;
453 }
454 Self::count_sent(&shared, connection_id, framed.len()).await;
455 }
456 }
457 let _ = stream.shutdown().await;
458 }
459
460 async fn count_sent(shared: &Shared, connection_id: ConnectionId, bytes: usize) {
461 shared
462 .app_state
463 .update_connection_stats(
464 shared.server_id,
465 connection_id,
466 None,
467 Some(bytes as u64),
468 None,
469 Some(1),
470 )
471 .await;
472 }
473
474 async fn answer(
477 shared: &Shared,
478 data: &[u8],
479 peer_addr: SocketAddr,
480 connection_id: ConnectionId,
481 transport: Transport,
482 ) -> Vec<Vec<u8>> {
483 let log = Log::new(Some(&shared.status_tx));
484 let query = match DnsMessage::from_vec(data) {
485 Ok(q) => q,
486 Err(e) => {
487 log.warn(format!("Failed to parse DNS query: {e}"));
488 log.debug(format!(
489 "DNS malformed query from {peer_addr} ({} bytes, hex: {})",
490 data.len(),
491 hex::encode(data)
492 ));
493 return Vec::new();
494 }
495 };
496 let edns = query.extensions().clone();
497 let dnssec_ok = edns.as_ref().is_some_and(|e| e.dnssec_ok());
498 let udp_limit = match (&edns, transport) {
499 (_, Transport::Tcp) => u16::MAX as usize,
500 (Some(e), Transport::Udp) => (e.max_payload() as usize)
501 .max(PLAIN_UDP_LIMIT)
502 .min(MAX_UDP_RESPONSE),
503 (None, Transport::Udp) => PLAIN_UDP_LIMIT,
504 };
505 let reply = Reply {
506 query: &query,
507 edns: edns.is_some(),
508 dnssec_ok,
509 udp_limit,
510 transport,
511 };
512
513 let Some(question) = query.queries().first().cloned() else {
514 log.warn(format!(
515 "DNS query from {peer_addr} has no question; FORMERR decision=spec_reply"
516 ));
517 return reply.rcode_only(ResponseCode::FormErr);
518 };
519 let qname = question.name().clone();
520 let qtype = question.query_type();
521
522 if let Some(signer) = &shared.signer {
524 if qtype == RecordType::DNSKEY && qname.to_lowercase() == signer.zone().to_lowercase() {
525 log.debug(format!(
526 "DNS DNSKEY {qname} from {peer_addr} answered by the server decision=spec_reply"
527 ));
528 let mut message = transfer::response_shell(&query, ResponseCode::NoError);
529 message.add_query(question.clone());
530 message.add_answer(signer.dnskey_record());
531 return reply.finish(message, shared.signer.as_deref(), &log);
532 }
533 }
534
535 match qtype {
536 RecordType::AXFR if transport == Transport::Udp => {
537 log.info(format!(
539 "DNS AXFR {qname} from {peer_addr} over UDP; NOTIMP decision=spec_reply"
540 ));
541 reply.rcode_only(ResponseCode::NotImp)
542 }
543 RecordType::AXFR | RecordType::IXFR => {
544 Self::transfer(shared, &reply, &question, peer_addr, connection_id, &log).await
545 }
546 _ => Self::query(shared, &reply, &question, peer_addr, connection_id, &log).await,
547 }
548 }
549
550 async fn query(
551 shared: &Shared,
552 reply: &Reply<'_>,
553 question: &Query,
554 peer_addr: SocketAddr,
555 connection_id: ConnectionId,
556 log: &Log<'_>,
557 ) -> Vec<Vec<u8>> {
558 let query = reply.query;
559 log.debug(format!(
560 "DNS query: {} {} {} over {}",
561 question.name(),
562 question.query_type(),
563 question.query_class(),
564 reply.transport.as_str()
565 ));
566 let event = Event::new(
567 &DNS_QUERY_EVENT,
568 serde_json::json!({
569 "query_id": query.id(),
570 "domain": question.name().to_string(),
571 "query_type": question.query_type().to_string(),
572 "transport": reply.transport.as_str(),
573 "dnssec_ok": reply.dnssec_ok,
574 "client_ip": peer_addr.ip().to_string(),
575 "client_port": peer_addr.port(),
576 }),
577 );
578 let result = call_llm(
582 &shared.llm_client,
583 &shared.app_state,
584 shared.server_id,
585 Some(connection_id),
586 &event,
587 shared.protocol.as_ref(),
588 )
589 .await;
590 let execution = match result {
591 Ok(r) => r,
592 Err(e) => return reply.servfail_for_llm_error(&e, peer_addr, connection_id, log),
593 };
594 for message in &execution.messages {
595 log.info(message);
596 }
597 let mut model_chose_silence = false;
602 for result in execution.protocol_results {
603 match result {
604 ActionResult::NoAction => model_chose_silence = true,
605 ActionResult::Output(bytes) => {
606 let Ok(message) = DnsMessage::from_vec(&bytes) else {
607 log.error(format!(
611 "DNS answer to the query from {peer_addr} is not a DNS message \
612 ({} bytes); answering SERVFAIL \
613 decision=fail_closed_unparseable_answer",
614 bytes.len()
615 ));
616 return reply.servfail();
617 };
618 log.debug(format!("DNS query from {peer_addr} decision=model_answer"));
619 log.info(format!("DNS response to {peer_addr}"));
620 return reply.finish(message, shared.signer.as_deref(), log);
621 }
622 _ => {}
623 }
624 }
625 if model_chose_silence {
626 log.debug(format!(
627 "DNS deliberately sending nothing to {peer_addr} decision=model_silent"
628 ));
629 return Vec::new();
630 }
631 log.error(format!(
632 "DNS no usable action for the query from {peer_addr} ({connection_id}); answering \
633 SERVFAIL decision=fail_closed_no_action (failures: {:?})",
634 execution.failures
635 ));
636 reply.servfail()
637 }
638
639 async fn transfer(
640 shared: &Shared,
641 reply: &Reply<'_>,
642 question: &Query,
643 peer_addr: SocketAddr,
644 connection_id: ConnectionId,
645 log: &Log<'_>,
646 ) -> Vec<Vec<u8>> {
647 let query = reply.query;
648 let zone = question.name().clone();
649 let kind = question.query_type();
650 let client_serial = query
652 .name_servers()
653 .iter()
654 .find(|r| r.record_type() == RecordType::SOA)
655 .and_then(transfer::serial_of);
656 let signed = shared
657 .signer
658 .as_ref()
659 .is_some_and(|s| s.zone().to_lowercase() == zone.to_lowercase());
660 let mut data = serde_json::json!({
661 "query_id": query.id(),
662 "zone": zone.to_string(),
663 "transfer_type": kind.to_string(),
664 "transport": reply.transport.as_str(),
665 "signed": signed,
666 "client_ip": peer_addr.ip().to_string(),
667 "client_port": peer_addr.port(),
668 });
669 if let Some(serial) = client_serial {
670 data["client_serial"] = serde_json::json!(serial);
671 }
672 log.info(format!(
673 "DNS {kind} {zone} from {peer_addr} over {}",
674 reply.transport.as_str()
675 ));
676 let result = call_llm(
677 &shared.llm_client,
678 &shared.app_state,
679 shared.server_id,
680 Some(connection_id),
681 &Event::new(&DNS_ZONE_TRANSFER_EVENT, data),
682 shared.protocol.as_ref(),
683 )
684 .await;
685 let execution = match result {
686 Ok(r) => r,
687 Err(e) => return reply.servfail_for_llm_error(&e, peer_addr, connection_id, log),
688 };
689 for message in &execution.messages {
690 log.info(message);
691 }
692 for result in execution.protocol_results {
693 let built = match &result {
694 ActionResult::NoAction => {
695 log.debug(format!(
696 "DNS {kind} {zone} from {peer_addr}: sending nothing decision=model_silent"
697 ));
698 return Vec::new();
699 }
700 ActionResult::Output(bytes) => {
701 let Ok(message) = DnsMessage::from_vec(bytes) else {
703 log.error(format!(
704 "DNS answer to the {kind} of {zone} from {peer_addr} is not a DNS \
705 message ({} bytes); answering SERVFAIL \
706 decision=fail_closed_unparseable_answer",
707 bytes.len()
708 ));
709 return reply.servfail();
710 };
711 let decision = if message.response_code() == ResponseCode::Refused {
712 "model_reject"
713 } else {
714 "model_answer"
715 };
716 log.info(format!(
717 "DNS {kind} {zone} from {peer_addr} decision={decision}"
718 ));
719 return reply.finish(message, None, log);
720 }
721 ActionResult::Custom { name, data } if name == RESULT_ZONE => {
722 if reply.transport == Transport::Udp {
723 log.info(format!(
724 "DNS {kind} {zone} from {peer_addr}: a full zone is never sent over \
725 UDP; answering with the SOA alone decision=udp_soa_only"
726 ));
727 }
728 Self::build_axfr(shared, reply, question, data)
729 }
730 ActionResult::Custom { name, data } if name == RESULT_IXFR => {
731 let has_changes = data
732 .get("changes")
733 .and_then(|c| c.as_array())
734 .is_some_and(|c| !c.is_empty());
735 if signed && has_changes {
738 Err(anyhow::anyhow!(
739 "{zone} is signed and its IXFR must be answered with the full zone \
740 (send_dns_zone); a diff would carry no signatures"
741 ))
742 } else {
743 Self::build_ixfr(reply, question, data, client_serial.unwrap_or(0))
744 }
745 }
746 _ => continue,
747 };
748 return match built {
749 Ok(messages) => {
750 log.info(format!(
751 "DNS {kind} {zone} to {peer_addr}: {} message(s) decision=model_answer",
752 messages.len()
753 ));
754 messages
755 }
756 Err(e) => {
757 log.error(format!(
758 "DNS {kind} {zone} from {peer_addr}: cannot build the transfer ({e:#}); \
759 answering SERVFAIL decision=fail_closed_no_action"
760 ));
761 reply.servfail()
762 }
763 };
764 }
765 log.error(format!(
766 "DNS no usable action for the {kind} of {zone} from {peer_addr}; answering SERVFAIL \
767 decision=fail_closed_no_action (failures: {:?})",
768 execution.failures
769 ));
770 reply.servfail()
771 }
772
773 fn build_axfr(
778 shared: &Shared,
779 reply: &Reply<'_>,
780 question: &Query,
781 action: &serde_json::Value,
782 ) -> Result<Vec<Vec<u8>>> {
783 let zone = transfer::zone_from_action(action)?;
784 if zone.origin.to_lowercase() != question.name().to_lowercase() {
785 anyhow::bail!(
786 "the answer transfers {} but the client asked for {}",
787 zone.origin,
788 question.name()
789 );
790 }
791 if reply.transport == Transport::Udp {
792 return Self::soa_only(reply, question, Some(zone.soa.clone()));
793 }
794 let mut sequence = transfer::axfr_sequence(&zone);
795 if let Some(signer) = shared
796 .signer
797 .as_deref()
798 .filter(|s| s.zone().to_lowercase() == zone.origin.to_lowercase())
799 {
800 sequence = Self::sign_zone(signer, &zone)?;
801 }
802 Self::transfer_messages(reply, question, sequence)
803 }
804
805 fn sign_zone(signer: &ZoneSigner, zone: &transfer::Zone) -> Result<Vec<Record>> {
806 let now = now_secs();
807 let mut body: Vec<Record> = vec![zone.soa.clone(), signer.dnskey_record()];
808 for r in &zone.records {
809 if r.record_type() == RecordType::NS && r.name() != &zone.origin {
810 anyhow::bail!(
811 "{} NS is a delegation; a signed zone here holds no delegations",
812 r.name()
813 );
814 }
815 body.push(r.clone());
816 }
817 let nsec = signer.nsec_chain(&body, dnssec::negative_ttl(&zone.soa))?;
818 body.extend(nsec);
819 let sigs = signer.signatures_for(&mut body, now)?;
820 let soa = body[0].clone();
823 let mut out = body;
824 out.extend(sigs);
825 out.push(soa);
826 Ok(out)
827 }
828
829 fn build_ixfr(
830 reply: &Reply<'_>,
831 question: &Query,
832 action: &serde_json::Value,
833 client_serial: u32,
834 ) -> Result<Vec<Vec<u8>>> {
835 let (origin, sequence) = transfer::ixfr_from_action(action, client_serial)?;
836 if origin.to_lowercase() != question.name().to_lowercase() {
837 anyhow::bail!(
838 "the answer transfers {origin} but the client asked for {}",
839 question.name()
840 );
841 }
842 Self::transfer_messages(reply, question, sequence)
843 }
844
845 fn soa_only(reply: &Reply<'_>, question: &Query, soa: Option<Record>) -> Result<Vec<Vec<u8>>> {
848 let mut only_soa = transfer::response_shell(reply.query, ResponseCode::NoError);
849 only_soa.add_query(question.clone());
850 only_soa.add_answers(soa);
851 if let Some(e) = reply.response_edns() {
852 only_soa.set_edns(e);
853 }
854 Ok(vec![only_soa.to_vec()?])
855 }
856
857 fn transfer_messages(
858 reply: &Reply<'_>,
859 question: &Query,
860 sequence: Vec<Record>,
861 ) -> Result<Vec<Vec<u8>>> {
862 let edns = reply.response_edns();
863 let add_edns = |m: &mut DnsMessage| {
864 if let Some(e) = &edns {
865 m.set_edns(e.clone());
866 }
867 };
868 if reply.transport == Transport::Udp {
869 let mut whole = transfer::response_shell(reply.query, ResponseCode::NoError);
872 whole.add_query(question.clone());
873 let soa = sequence.first().cloned();
874 whole.add_answers(sequence);
875 add_edns(&mut whole);
876 let bytes = whole.to_vec().ok();
877 if let Some(bytes) = bytes.filter(|b| b.len() <= reply.udp_limit) {
878 return Ok(vec![bytes]);
879 }
880 return Self::soa_only(reply, question, soa);
881 }
882 transfer::split_into_messages(
883 reply.query,
884 Some(question),
885 sequence,
886 transfer::TRANSFER_MESSAGE_BYTES,
887 add_edns,
888 )
889 }
890}
891
892struct Reply<'a> {
894 query: &'a DnsMessage,
895 edns: bool,
896 dnssec_ok: bool,
897 udp_limit: usize,
898 transport: Transport,
899}
900
901impl Reply<'_> {
902 fn response_edns(&self) -> Option<Edns> {
905 self.edns.then(|| {
906 let mut e = Edns::new();
907 e.set_max_payload(EDNS_UDP_SIZE);
908 e.set_version(0);
909 e.set_dnssec_ok(self.dnssec_ok);
910 e
911 })
912 }
913
914 fn rcode_only(&self, rcode: ResponseCode) -> Vec<Vec<u8>> {
916 let mut message = transfer::response_shell(self.query, rcode);
917 message.set_authoritative(false);
918 for q in self.query.queries() {
919 message.add_query(q.clone());
920 }
921 if let Some(e) = self.response_edns() {
922 message.set_edns(e);
923 }
924 message.to_vec().map(|b| vec![b]).unwrap_or_default()
925 }
926
927 fn servfail(&self) -> Vec<Vec<u8>> {
929 match actions::build_servfail(self.query) {
930 Ok(packet) => vec![packet],
931 Err(_) => Vec::new(),
932 }
933 }
934
935 fn servfail_for_llm_error(
936 &self,
937 e: &anyhow::Error,
938 peer_addr: SocketAddr,
939 connection_id: ConnectionId,
940 log: &Log<'_>,
941 ) -> Vec<Vec<u8>> {
942 let decision = if crate::llm::is_overload_error(e) {
945 "fail_closed_llm_overload"
946 } else {
947 "fail_closed_llm_error"
948 };
949 log.warn(format!(
950 "DNS LLM call failed for query from {peer_addr} ({connection_id}) decision={decision}: {e}"
951 ));
952 let out = self.servfail();
953 if let Some(packet) = out.first() {
954 log.info(format!(
955 "DNS SERVFAIL to {peer_addr} ({} bytes)",
956 packet.len()
957 ));
958 }
959 out
960 }
961
962 fn finish(
965 &self,
966 mut message: DnsMessage,
967 signer: Option<&ZoneSigner>,
968 log: &Log<'_>,
969 ) -> Vec<Vec<u8>> {
970 let qname = self.query.queries().first().map(|q| q.name().clone());
971 if let (Some(signer), Some(qname)) = (signer, qname.as_ref()) {
972 if self.dnssec_ok && signer.covers(qname) {
973 if let Err(e) = Self::sign_answer(&mut message, signer, qname) {
974 log.error(format!(
975 "DNS cannot sign the answer for {qname} ({e:#}); answering SERVFAIL \
976 decision=fail_closed_dnssec"
977 ));
978 return self.servfail();
979 }
980 }
981 }
982 if let Some(e) = self.response_edns() {
983 message.set_edns(e);
984 }
985 let bytes = match message.to_vec() {
986 Ok(b) => b,
987 Err(e) => {
988 log.error(format!(
989 "DNS cannot encode the answer ({e}); answering SERVFAIL \
990 decision=fail_closed_no_action"
991 ));
992 return self.servfail();
993 }
994 };
995 if self.transport == Transport::Udp && bytes.len() > self.udp_limit {
996 log.debug(format!(
999 "DNS answer of {} bytes is over the client's {} over UDP; TC decision=truncated",
1000 bytes.len(),
1001 self.udp_limit
1002 ));
1003 let mut truncated = transfer::response_shell(self.query, message.response_code());
1004 truncated.set_truncated(true);
1005 for q in self.query.queries() {
1006 truncated.add_query(q.clone());
1007 }
1008 if let Some(e) = self.response_edns() {
1009 truncated.set_edns(e);
1010 }
1011 return truncated.to_vec().map(|b| vec![b]).unwrap_or_default();
1012 }
1013 vec![bytes]
1014 }
1015
1016 fn sign_answer(message: &mut DnsMessage, signer: &ZoneSigner, qname: &Name) -> Result<()> {
1018 let now = now_secs();
1019 let negative = message.response_code() == ResponseCode::NXDomain
1020 || (message.response_code() == ResponseCode::NoError && message.answers().is_empty());
1021 let mut answers = message.take_answers();
1022 let mut authority = message.take_name_servers();
1023 let mut additional = message.take_additionals();
1024 if negative {
1025 let soa = match authority
1026 .iter()
1027 .find(|r| r.record_type() == RecordType::SOA && signer.covers(r.name()))
1028 {
1029 Some(soa) => soa.clone(),
1030 None => {
1031 let soa = signer.soa_record();
1032 authority.push(soa.clone());
1033 soa
1034 }
1035 };
1036 authority.push(signer.compact_denial(qname, dnssec::negative_ttl(&soa))?);
1037 message.set_response_code(ResponseCode::NoError);
1039 }
1040 let answer_sigs = signer.signatures_for(&mut answers, now)?;
1041 let authority_sigs = signer.signatures_for(&mut authority, now)?;
1042 let additional_sigs = signer.signatures_for(&mut additional, now)?;
1043 answers.extend(answer_sigs);
1044 authority.extend(authority_sigs);
1045 additional.extend(additional_sigs);
1046 message.insert_answers(answers);
1047 message.insert_name_servers(authority);
1048 message.add_additionals(additional);
1049 message.set_authoritative(true);
1050 Ok(())
1051 }
1052}