edge_captive/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![warn(clippy::large_futures)]
3#![allow(clippy::uninlined_format_args)]
4#![allow(unknown_lints)]
5
6use core::fmt::Display;
7use core::time::Duration;
8
9use domain::base::wire::Composer;
10use domain::dep::octseq::{OctetsBuilder, Truncate};
11
12use domain::{
13    base::{
14        iana::{Class, Opcode, Rcode},
15        message::ShortMessage,
16        message_builder::PushError,
17        record::Ttl,
18        wire::ParseError,
19        Record, Rtype,
20    },
21    dep::octseq::ShortBuf,
22    rdata::A,
23};
24
25// This mod MUST go first, so that the others see its macros.
26pub(crate) mod fmt;
27
28#[cfg(feature = "io")]
29pub mod io;
30
31#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
32pub enum DnsError {
33    ShortBuf,
34    InvalidMessage,
35}
36
37impl Display for DnsError {
38    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
39        match self {
40            Self::ShortBuf => write!(f, "ShortBuf"),
41            Self::InvalidMessage => write!(f, "InvalidMessage"),
42        }
43    }
44}
45
46#[cfg(feature = "defmt")]
47impl defmt::Format for DnsError {
48    fn format(&self, f: defmt::Formatter<'_>) {
49        match self {
50            Self::ShortBuf => defmt::write!(f, "ShortBuf"),
51            Self::InvalidMessage => defmt::write!(f, "InvalidMessage"),
52        }
53    }
54}
55
56impl core::error::Error for DnsError {}
57
58impl From<ShortBuf> for DnsError {
59    fn from(_: ShortBuf) -> Self {
60        Self::ShortBuf
61    }
62}
63
64impl From<PushError> for DnsError {
65    fn from(_: PushError) -> Self {
66        Self::ShortBuf
67    }
68}
69
70impl From<ShortMessage> for DnsError {
71    fn from(_: ShortMessage) -> Self {
72        Self::InvalidMessage
73    }
74}
75
76impl From<ParseError> for DnsError {
77    fn from(_: ParseError) -> Self {
78        Self::InvalidMessage
79    }
80}
81
82pub fn reply(
83    request: &[u8],
84    ip: &[u8; 4],
85    ttl: Duration,
86    buf: &mut [u8],
87) -> Result<usize, DnsError> {
88    let buf = Buf(buf, 0);
89
90    let message = domain::base::Message::from_octets(request)?;
91    debug!(
92        "Processing message with header: {:?}",
93        debug2format!(message.header())
94    );
95
96    let mut responseb = domain::base::MessageBuilder::from_target(buf)?;
97
98    let buf = if matches!(message.header().opcode(), Opcode::QUERY) {
99        debug!("Message is of type Query, processing all questions");
100
101        let mut answerb = responseb.start_answer(&message, Rcode::NOERROR)?;
102
103        for question in message.question() {
104            let question = question?;
105
106            if matches!(question.qtype(), Rtype::A) && matches!(question.qclass(), Class::IN) {
107                let record = Record::new(
108                    question.qname(),
109                    Class::IN,
110                    Ttl::from_duration_lossy(ttl),
111                    A::from_octets(ip[0], ip[1], ip[2], ip[3]),
112                );
113                debug!(
114                    "Answering {:?} with {:?}",
115                    debug2format!(question),
116                    debug2format!(record)
117                );
118                answerb.push(record)?;
119            } else {
120                debug!(
121                    "Question {:?} is not of type A, not answering",
122                    debug2format!(question)
123                );
124            }
125        }
126
127        answerb.finish()
128    } else {
129        debug!("Message is not of type Query, replying with NotImp");
130
131        let headerb = responseb.header_mut();
132
133        headerb.set_id(message.header().id());
134        headerb.set_opcode(message.header().opcode());
135        headerb.set_rd(message.header().rd());
136        headerb.set_rcode(domain::base::iana::Rcode::NOTIMP);
137
138        responseb.finish()
139    };
140
141    Ok(buf.1)
142}
143
144struct Buf<'a>(pub &'a mut [u8], pub usize);
145
146impl Composer for Buf<'_> {}
147
148impl OctetsBuilder for Buf<'_> {
149    type AppendError = ShortBuf;
150
151    fn append_slice(&mut self, slice: &[u8]) -> Result<(), Self::AppendError> {
152        if self.1 + slice.len() <= self.0.len() {
153            let end = self.1 + slice.len();
154            self.0[self.1..end].copy_from_slice(slice);
155            self.1 = end;
156
157            Ok(())
158        } else {
159            Err(ShortBuf)
160        }
161    }
162}
163
164impl Truncate for Buf<'_> {
165    fn truncate(&mut self, len: usize) {
166        self.1 = len;
167    }
168}
169
170impl AsMut<[u8]> for Buf<'_> {
171    fn as_mut(&mut self) -> &mut [u8] {
172        &mut self.0[..self.1]
173    }
174}
175
176impl AsRef<[u8]> for Buf<'_> {
177    fn as_ref(&self) -> &[u8] {
178        &self.0[..self.1]
179    }
180}