dns_server/
dns_record.rs

1use crate::{
2    read_exact, read_u16_be, read_u32_be, write_bytes, write_u16_be, write_u32_be, DnsClass,
3    DnsError, DnsName, DnsType,
4};
5use core::fmt::{Debug, Formatter};
6use fixed_buffer::FixedBuf;
7use std::convert::TryFrom;
8use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
9
10/// > 4.1.3. Resource record format
11/// >
12/// > The answer, authority, and additional sections all share the same format: a variable number
13/// > of resource records, where the number of records is specified in the corresponding count
14/// > field in the header.  Each resource record has the following format:
15/// >
16/// > ```text
17/// >                                 1  1  1  1  1  1
18/// >   0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
19/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
20/// > |                                               |
21/// > /                                               /
22/// > /                      NAME                     /
23/// > |                                               |
24/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
25/// > |                      TYPE                     |
26/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
27/// > |                     CLASS                     |
28/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
29/// > |                      TTL                      |
30/// > |                                               |
31/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
32/// > |                   RDLENGTH                    |
33/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
34/// > /                     RDATA                     /
35/// > /                                               /
36/// > +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
37/// > ```
38/// > where:
39/// > - NAME: a domain name to which this resource record pertains.
40/// > - TYPE: two octets containing one of the RR type codes.  This field specifies the meaning of
41/// >   the data in the RDATA field.
42/// > - CLASS: two octets which specify the class of the data in the RDATA field.
43/// > - TTL:  a 32 bit unsigned integer that specifies the time interval (in seconds) that the
44/// >   resource record may be cached before it should be discarded.  Zero values are interpreted
45/// >   to mean that the RR can only be used for the transaction in progress, and should not be
46/// >   cached.
47/// > - RDLENGTH: an unsigned 16 bit integer that specifies the length in octets of the RDATA field.
48/// > - RDATA:  a variable length string of octets that describes the resource.  The format of this
49/// >   information varies according to the TYPE and CLASS of the resource record.  For example,
50/// >   the if the TYPE is A and the CLASS is IN, the RDATA field is a 4 octet ARPA Internet
51/// >   address.
52///
53/// <https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.3>
54#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
55pub enum DnsRecord {
56    A(DnsName, std::net::Ipv4Addr),
57    AAAA(DnsName, std::net::Ipv6Addr),
58    CNAME(DnsName, DnsName),
59    Unknown(DnsName, DnsType),
60}
61impl DnsRecord {
62    /// # Errors
63    /// Returns an error when `buf` does not contain a valid resource record.
64    pub fn read_rdata<const N: usize>(buf: &mut FixedBuf<N>) -> Result<FixedBuf<65535>, DnsError> {
65        let len = read_u16_be(buf)?;
66        if buf.len() < (len as usize) {
67            return Err(DnsError::Truncated);
68        }
69        let borrowed_rdata = buf.read_bytes(len as usize);
70        let mut rdata: FixedBuf<65535> = FixedBuf::new();
71        rdata
72            .write_bytes(borrowed_rdata)
73            .map_err(|_| DnsError::Unreachable(file!(), line!()))?;
74        Ok(rdata)
75    }
76
77    /// # Errors
78    /// Returns an error when `buf` is full or `bytes` is longer than 65,535 bytes.
79    pub fn write_rdata<const N: usize>(
80        bytes: &[u8],
81        out: &mut FixedBuf<N>,
82    ) -> Result<(), DnsError> {
83        let len =
84            u16::try_from(bytes.len()).map_err(|_| DnsError::Unreachable(file!(), line!()))?;
85        write_u16_be(out, len)?;
86        write_bytes(out, bytes)?;
87        Ok(())
88    }
89
90    /// # Errors
91    /// Returns an error when `name` is not a valid DNS name
92    /// or `ipv4_addr` is not a valid IPv4 address.
93    pub fn new_a(name: &str, ipv4_addr: &str) -> Result<Self, String> {
94        let dns_name = DnsName::new(name)?;
95        let ip_addr: IpAddr = ipv4_addr
96            .parse()
97            .map_err(|e| format!("failed parsing {ipv4_addr:?} as an IP address: {e}"))?;
98        match ip_addr {
99            IpAddr::V4(addr) => Ok(Self::A(dns_name, addr)),
100            IpAddr::V6(addr) => Err(format!(
101                "cannot create an A record with ipv6 address {addr:?}"
102            )),
103        }
104    }
105
106    /// # Errors
107    /// Returns an error when `name` is not a valid DNS name
108    /// or `ipv6_addr` is not a valid IPv6 address.
109    pub fn new_aaaa(name: &str, ipv6_addr: &str) -> Result<Self, String> {
110        let dns_name = DnsName::new(name)?;
111        let ip_addr: IpAddr = ipv6_addr
112            .parse()
113            .map_err(|e| format!("failed parsing {ipv6_addr:?} as an IP address: {e}"))?;
114        match ip_addr {
115            IpAddr::V4(addr) => Err(format!(
116                "cannot create an AAAA record with ipv4 address {addr:?}"
117            )),
118            IpAddr::V6(addr) => Ok(Self::AAAA(dns_name, addr)),
119        }
120    }
121
122    /// # Errors
123    /// Returns an error when `name` or `target` are not both valid DNS names.
124    pub fn new_cname(name: &str, target: &str) -> Result<Self, String> {
125        let dns_name = DnsName::new(name)?;
126        let dns_name_target = DnsName::new(target)?;
127        Ok(Self::CNAME(dns_name, dns_name_target))
128    }
129
130    #[must_use]
131    pub fn name(&self) -> &DnsName {
132        match self {
133            DnsRecord::A(dns_name, _)
134            | DnsRecord::AAAA(dns_name, _)
135            | DnsRecord::CNAME(dns_name, _)
136            | DnsRecord::Unknown(dns_name, _) => dns_name,
137        }
138    }
139
140    #[must_use]
141    pub fn typ(&self) -> DnsType {
142        match self {
143            DnsRecord::A(_, _) => DnsType::A,
144            DnsRecord::AAAA(_, _) => DnsType::AAAA,
145            DnsRecord::CNAME(_, _) => DnsType::CNAME,
146            DnsRecord::Unknown(_, typ) => DnsType::Unknown(typ.num()),
147        }
148    }
149
150    /// # Errors
151    /// Returns an error when `buf` does not contain a valid resource record.
152    pub fn read<const N: usize>(buf: &mut FixedBuf<N>) -> Result<Self, DnsError> {
153        let name = DnsName::read(buf)?;
154        let typ = DnsType::read(buf)?;
155        let class = DnsClass::read(buf)?;
156        if class != DnsClass::Internet && class != DnsClass::Any {
157            return Err(DnsError::InvalidClass);
158        }
159        let _ttl_seconds = read_u32_be(buf)?;
160        let mut rdata = Self::read_rdata(buf)?;
161        match typ {
162            DnsType::A => {
163                let octets: [u8; 4] = read_exact(&mut rdata)?;
164                Ok(DnsRecord::A(name, Ipv4Addr::from(octets)))
165            }
166            DnsType::AAAA => {
167                let octets: [u8; 16] = read_exact(&mut rdata)?;
168                Ok(DnsRecord::AAAA(name, Ipv6Addr::from(octets)))
169            }
170            DnsType::CNAME => Ok(DnsRecord::CNAME(name, DnsName::read(&mut rdata)?)),
171            DnsType::MX
172            | DnsType::NS
173            | DnsType::PTR
174            | DnsType::SOA
175            | DnsType::TXT
176            | DnsType::ANY
177            | DnsType::Unknown(_) => Ok(DnsRecord::Unknown(name, typ)),
178        }
179    }
180
181    /// # Errors
182    /// Returns an error when `buf` is full.
183    pub fn write<const N: usize>(&self, out: &mut FixedBuf<N>) -> Result<(), DnsError> {
184        self.name().write(out)?;
185        self.typ().write(out)?;
186        DnsClass::Internet.write(out)?;
187        write_u32_be(out, 300)?; // TTL in seconds.
188        match self {
189            DnsRecord::A(_, ipv4_addr) => Self::write_rdata(&ipv4_addr.octets(), out),
190            DnsRecord::AAAA(_, ipv6_addr) => Self::write_rdata(&ipv6_addr.octets(), out),
191            DnsRecord::CNAME(_, target_name) => {
192                Self::write_rdata(target_name.as_bytes()?.readable(), out)
193            }
194            DnsRecord::Unknown(_, _) => {
195                Err(DnsError::Internal(format!("cannot write record {self:?}")))
196            }
197        }
198    }
199}
200impl Debug for DnsRecord {
201    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
202        match self {
203            DnsRecord::A(name, addr) => write!(f, "DnsRecord::A({name},{addr})"),
204            DnsRecord::AAAA(name, addr) => write!(f, "DnsRecord::AAAA({name},{addr})"),
205            DnsRecord::CNAME(name, target) => write!(f, "DnsRecord::CNAME({name},{target})"),
206            DnsRecord::Unknown(name, typ) => write!(f, "DnsRecord::Unknown({name},{typ})"),
207        }
208    }
209}
210
211#[cfg(test)]
212#[test]
213fn test_dns_record() {
214    use std::net::{Ipv4Addr, Ipv6Addr};
215    // Constructors
216    assert_eq!(
217        DnsRecord::A(DnsName::new("a.b").unwrap(), Ipv4Addr::new(1, 2, 3, 4)),
218        DnsRecord::new_a("a.b", "1.2.3.4").unwrap()
219    );
220    assert_eq!(
221        DnsRecord::AAAA(
222            DnsName::new("a.b").unwrap(),
223            Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)
224        ),
225        DnsRecord::new_aaaa("a.b", "2001:db8::").unwrap()
226    );
227    assert_eq!(
228        DnsRecord::CNAME(DnsName::new("a.b").unwrap(), DnsName::new("c.d").unwrap()),
229        DnsRecord::new_cname("a.b", "c.d").unwrap()
230    );
231    // Debug
232    assert_eq!(
233        "DnsRecord::A(a.b,1.2.3.4)",
234        format!(
235            "{:?}",
236            DnsRecord::A(DnsName::new("a.b").unwrap(), Ipv4Addr::new(1, 2, 3, 4))
237        )
238    );
239    assert_eq!(
240        "DnsRecord::AAAA(a.b,2001:db8::)",
241        format!(
242            "{:?}",
243            DnsRecord::AAAA(
244                DnsName::new("a.b").unwrap(),
245                Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)
246            )
247        )
248    );
249    assert_eq!(
250        "DnsRecord::CNAME(a.b,c.d)",
251        format!(
252            "{:?}",
253            DnsRecord::CNAME(DnsName::new("a.b").unwrap(), DnsName::new("c.d").unwrap())
254        )
255    );
256}