dns_server/
dns_record.rs

1use crate::dns_string::DnsString;
2use crate::{
3    read_exact, read_u16_be, read_u32_be, write_bytes, write_u16_be, write_u32_be, DnsClass,
4    DnsError, DnsName, DnsType,
5};
6use core::fmt::{Debug, Formatter};
7use fixed_buffer::FixedBuf;
8use std::convert::TryFrom;
9use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
10
11fn check_empty<const SIZE: usize>(f: &FixedBuf<SIZE>) -> Result<(), DnsError> {
12    if f.is_empty() {
13        Ok(())
14    } else {
15        Err(DnsError::RecordHasAdditionalBytes)
16    }
17}
18
19/// > 4.1.3. Resource record format
20/// >
21/// > The answer, authority, and additional sections all share the same format: a variable number
22/// > of resource records, where the number of records is specified in the corresponding count
23/// > field in the header.  Each resource record has the following format:
24/// >
25/// > ```text
26/// >                                 1  1  1  1  1  1
27/// >   0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
28/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
29/// > |                                               |
30/// > /                                               /
31/// > /                      NAME                     /
32/// > |                                               |
33/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
34/// > |                      TYPE                     |
35/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
36/// > |                     CLASS                     |
37/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
38/// > |                      TTL                      |
39/// > |                                               |
40/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
41/// > |                   RDLENGTH                    |
42/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
43/// > /                     RDATA                     /
44/// > /                                               /
45/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
46/// > ```
47/// > where:
48/// > - NAME: a domain name to which this resource record pertains.
49/// > - TYPE: two octets containing one of the RR type codes.  This field specifies the meaning of
50/// >   the data in the RDATA field.
51/// > - CLASS: two octets which specify the class of the data in the RDATA field.
52/// > - TTL:  a 32 bit unsigned integer that specifies the time interval (in seconds) that the
53/// >   resource record may be cached before it should be discarded.  Zero values are interpreted
54/// >   to mean that the RR can only be used for the transaction in progress, and should not be
55/// >   cached.
56/// > - RDLENGTH: an unsigned 16 bit integer that specifies the length in octets of the RDATA field.
57/// > - RDATA:  a variable length string of octets that describes the resource.  The format of this
58/// >   information varies according to the TYPE and CLASS of the resource record.  For example,
59/// >   the if the TYPE is A and the CLASS is IN, the RDATA field is a 4 octet ARPA Internet
60/// >   address.
61///
62/// <https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.3>
63#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
64pub enum DnsRecord {
65    A(DnsName, std::net::Ipv4Addr),
66    AAAA(DnsName, std::net::Ipv6Addr),
67    CNAME(DnsName, DnsName),
68    NS(DnsName, DnsName),
69    /// > 3.3.14. TXT RDATA format
70    /// > TXT-DATA = One or more <character-string>s.
71    /// >
72    /// > TXT RRs are used to hold descriptive text.  The semantics of the text depends on the domain
73    /// > where it is found.
74    ///
75    /// <https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.14>
76    TXT(DnsName, Vec<DnsString>),
77    Unknown(DnsName, DnsType),
78}
79impl DnsRecord {
80    /// # Errors
81    /// Returns an error when `buf` does not contain a valid resource record.
82    pub fn read_rdata<const N: usize>(buf: &mut FixedBuf<N>) -> Result<FixedBuf<65535>, DnsError> {
83        let len = read_u16_be(buf)?;
84        if buf.len() < (len as usize) {
85            return Err(DnsError::Truncated);
86        }
87        let borrowed_rdata = buf.read_bytes(len as usize);
88        let mut rdata: FixedBuf<65535> = FixedBuf::new();
89        rdata
90            .write_bytes(borrowed_rdata)
91            .map_err(|_| DnsError::Unreachable(file!(), line!()))?;
92        Ok(rdata)
93    }
94
95    /// # Errors
96    /// Returns an error when `buf` is full or `bytes` is longer than 65,535 bytes.
97    pub fn write_rdata<const N: usize>(
98        bytes: &[u8],
99        out: &mut FixedBuf<N>,
100    ) -> Result<(), DnsError> {
101        let len =
102            u16::try_from(bytes.len()).map_err(|_| DnsError::Unreachable(file!(), line!()))?;
103        write_u16_be(out, len)?;
104        write_bytes(out, bytes)?;
105        Ok(())
106    }
107
108    /// # Errors
109    /// Returns an error when `name` is not a valid DNS name
110    /// or `ipv4_addr` is not a valid IPv4 address.
111    pub fn new_a(name: &str, ipv4_addr: &str) -> Result<Self, String> {
112        let dns_name = DnsName::new(name)?;
113        let ip_addr: IpAddr = ipv4_addr
114            .parse()
115            .map_err(|e| format!("failed parsing {ipv4_addr:?} as an IP address: {e}"))?;
116        match ip_addr {
117            IpAddr::V4(addr) => Ok(Self::A(dns_name, addr)),
118            IpAddr::V6(addr) => Err(format!(
119                "cannot create an A record with ipv6 address {addr:?}"
120            )),
121        }
122    }
123
124    /// # Errors
125    /// Returns an error when `name` is not a valid DNS name
126    /// or `ipv6_addr` is not a valid IPv6 address.
127    pub fn new_aaaa(name: &str, ipv6_addr: &str) -> Result<Self, String> {
128        let dns_name = DnsName::new(name)?;
129        let ip_addr: IpAddr = ipv6_addr
130            .parse()
131            .map_err(|e| format!("failed parsing {ipv6_addr:?} as an IP address: {e}"))?;
132        match ip_addr {
133            IpAddr::V4(addr) => Err(format!(
134                "cannot create an AAAA record with ipv4 address {addr:?}"
135            )),
136            IpAddr::V6(addr) => Ok(Self::AAAA(dns_name, addr)),
137        }
138    }
139
140    /// # Errors
141    /// Returns an error when `name` or `target` are not both valid DNS names.
142    pub fn new_cname(name: &str, target: &str) -> Result<Self, String> {
143        let dns_name = DnsName::new(name)?;
144        let dns_name_target = DnsName::new(target)?;
145        Ok(Self::CNAME(dns_name, dns_name_target))
146    }
147
148    /// # Errors
149    /// Returns an error when `name` or `target` are not both valid DNS names.
150    pub fn new_ns(name: &str, target: &str) -> Result<Self, String> {
151        let dns_name = DnsName::new(name)?;
152        let dns_name_target = DnsName::new(target)?;
153        Ok(Self::NS(dns_name, dns_name_target))
154    }
155
156    /// # Errors
157    /// Returns an error when `name` or `target` are not both valid DNS names.
158    pub fn new_txt(name: &str, content: &str) -> Result<Self, String> {
159        let dns_name = DnsName::new(name)?;
160        let dns_string = DnsString::new(content)?;
161        Ok(Self::TXT(dns_name, vec![dns_string]))
162    }
163
164    /// # Errors
165    /// Returns an error when `name` or `target` are not both valid DNS names.
166    pub fn new_txt_multi(name: &str, lines: &[&str]) -> Result<Self, String> {
167        let dns_name = DnsName::new(name)?;
168        let mut dns_strings = Vec::new();
169        for line in lines {
170            dns_strings.push(DnsString::new(line)?);
171        }
172        Ok(Self::TXT(dns_name, dns_strings))
173    }
174
175    #[must_use]
176    pub fn name(&self) -> &DnsName {
177        match self {
178            DnsRecord::A(dns_name, _)
179            | DnsRecord::AAAA(dns_name, _)
180            | DnsRecord::CNAME(dns_name, _)
181            | DnsRecord::NS(dns_name, _)
182            | DnsRecord::TXT(dns_name, _)
183            | DnsRecord::Unknown(dns_name, _) => dns_name,
184        }
185    }
186
187    #[must_use]
188    pub fn typ(&self) -> DnsType {
189        match self {
190            DnsRecord::A(..) => DnsType::A,
191            DnsRecord::AAAA(..) => DnsType::AAAA,
192            DnsRecord::CNAME(..) => DnsType::CNAME,
193            DnsRecord::NS(..) => DnsType::NS,
194            DnsRecord::TXT(..) => DnsType::TXT,
195            DnsRecord::Unknown(_, typ) => DnsType::Unknown(typ.num()),
196        }
197    }
198
199    /// # Errors
200    /// Returns an error when `buf` does not contain a valid resource record.
201    pub fn read<const N: usize>(buf: &mut FixedBuf<N>) -> Result<Self, DnsError> {
202        let name = DnsName::read(buf)?;
203        let typ = DnsType::read(buf)?;
204        let class = DnsClass::read(buf)?;
205        if class != DnsClass::Internet && class != DnsClass::Any {
206            return Err(DnsError::InvalidClass);
207        }
208        let _ttl_seconds = read_u32_be(buf)?;
209        let mut rdata = Self::read_rdata(buf)?;
210        let record = match typ {
211            DnsType::A => {
212                let octets: [u8; 4] = read_exact(&mut rdata)?;
213                check_empty(&rdata)?;
214                DnsRecord::A(name, Ipv4Addr::from(octets))
215            }
216            DnsType::AAAA => {
217                let octets: [u8; 16] = read_exact(&mut rdata)?;
218                check_empty(&rdata)?;
219                DnsRecord::AAAA(name, Ipv6Addr::from(octets))
220            }
221            DnsType::CNAME => {
222                let target = DnsName::read(&mut rdata)?;
223                check_empty(&rdata)?;
224                DnsRecord::CNAME(name, target)
225            }
226            DnsType::NS => {
227                let target = DnsName::read(&mut rdata)?;
228                check_empty(&rdata)?;
229                DnsRecord::NS(name, target)
230            }
231            DnsType::TXT => {
232                let strings = DnsString::read_multiple(&mut rdata)?;
233                DnsRecord::TXT(name, strings)
234            }
235            DnsType::MX | DnsType::PTR | DnsType::SOA | DnsType::ANY | DnsType::Unknown(_) => {
236                DnsRecord::Unknown(name, typ)
237            }
238        };
239        Ok(record)
240    }
241
242    /// # Errors
243    /// Returns an error when `buf` is full.
244    pub fn write<const N: usize>(&self, out: &mut FixedBuf<N>) -> Result<(), DnsError> {
245        self.name().write(out)?;
246        self.typ().write(out)?;
247        DnsClass::Internet.write(out)?;
248        write_u32_be(out, 300)?; // TTL in seconds.
249        #[allow(clippy::match_same_arms)]
250        match self {
251            DnsRecord::A(_, ipv4_addr) => Self::write_rdata(&ipv4_addr.octets(), out)?,
252            DnsRecord::AAAA(_, ipv6_addr) => Self::write_rdata(&ipv6_addr.octets(), out)?,
253            DnsRecord::CNAME(_, target_name) => {
254                Self::write_rdata(target_name.as_bytes()?.readable(), out)?;
255            }
256            DnsRecord::NS(_, target_name) => {
257                Self::write_rdata(target_name.as_bytes()?.readable(), out)?;
258            }
259            DnsRecord::TXT(_, strings) => {
260                let mut buf = Vec::new();
261                for string in strings {
262                    let bytes = string.as_bytes()?;
263                    buf.extend(bytes.readable());
264                }
265                Self::write_rdata(&buf, out)?;
266            }
267            DnsRecord::Unknown(_, _) => {
268                return Err(DnsError::Internal(format!("cannot write record {self:?}")))
269            }
270        }
271        Ok(())
272    }
273}
274impl Debug for DnsRecord {
275    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
276        match self {
277            DnsRecord::A(name, addr) => write!(f, "DnsRecord::A({name},{addr})"),
278            DnsRecord::AAAA(name, addr) => write!(f, "DnsRecord::AAAA({name},{addr})"),
279            DnsRecord::CNAME(name, target) => write!(f, "DnsRecord::CNAME({name},{target})"),
280            DnsRecord::NS(name, target) => write!(f, "DnsRecord::NS({name},{target})"),
281            DnsRecord::TXT(name, strings) => {
282                write!(f, "DnsRecord::TXT({name},['")?;
283                let mut first = true;
284                for string in strings {
285                    if first {
286                        first = false;
287                        write!(f, "{string}")?;
288                    } else {
289                        write!(f, "', '{string}")?;
290                    }
291                }
292                write!(f, "'])")
293            }
294            DnsRecord::Unknown(name, typ) => write!(f, "DnsRecord::Unknown({name},{typ})"),
295        }
296    }
297}
298
299#[cfg(test)]
300#[test]
301fn test_dns_record() {
302    use std::net::{Ipv4Addr, Ipv6Addr};
303    // Constructors
304    assert_eq!(
305        DnsRecord::A(DnsName::new("a.b").unwrap(), Ipv4Addr::new(1, 2, 3, 4)),
306        DnsRecord::new_a("a.b", "1.2.3.4").unwrap()
307    );
308    assert_eq!(
309        DnsRecord::AAAA(
310            DnsName::new("a.b").unwrap(),
311            Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)
312        ),
313        DnsRecord::new_aaaa("a.b", "2001:db8::").unwrap()
314    );
315    assert_eq!(
316        DnsRecord::CNAME(DnsName::new("a.b").unwrap(), DnsName::new("c.d").unwrap()),
317        DnsRecord::new_cname("a.b", "c.d").unwrap()
318    );
319    assert_eq!(
320        DnsRecord::NS(DnsName::new("a.b").unwrap(), DnsName::new("c.d").unwrap()),
321        DnsRecord::new_ns("a.b", "c.d").unwrap()
322    );
323    assert_eq!(
324        DnsRecord::TXT(
325            DnsName::new("a.b").unwrap(),
326            vec![DnsString::new("s1").unwrap()]
327        ),
328        DnsRecord::new_txt("a.b", "s1").unwrap()
329    );
330    // Debug
331    assert_eq!(
332        "DnsRecord::A(a.b,1.2.3.4)",
333        format!(
334            "{:?}",
335            DnsRecord::A(DnsName::new("a.b").unwrap(), Ipv4Addr::new(1, 2, 3, 4))
336        )
337    );
338    assert_eq!(
339        "DnsRecord::AAAA(a.b,2001:db8::)",
340        format!(
341            "{:?}",
342            DnsRecord::AAAA(
343                DnsName::new("a.b").unwrap(),
344                Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)
345            )
346        )
347    );
348    assert_eq!(
349        "DnsRecord::CNAME(a.b,c.d)",
350        format!(
351            "{:?}",
352            DnsRecord::CNAME(DnsName::new("a.b").unwrap(), DnsName::new("c.d").unwrap())
353        )
354    );
355    assert_eq!(
356        "DnsRecord::NS(a.b,c.d)",
357        format!(
358            "{:?}",
359            DnsRecord::NS(DnsName::new("a.b").unwrap(), DnsName::new("c.d").unwrap())
360        )
361    );
362    assert_eq!(
363        "DnsRecord::TXT(a.b,['s1'])",
364        format!("{:?}", DnsRecord::new_txt("a.b", "s1").unwrap())
365    );
366    assert_eq!(
367        "DnsRecord::TXT(a.b,['s1', 's2'])",
368        format!(
369            "{:?}",
370            DnsRecord::new_txt_multi("a.b", &["s1", "s2"]).unwrap()
371        )
372    );
373}