dns_server/
dns_name.rs

1use crate::{read_u8, DnsError};
2use core::convert::TryFrom;
3use core::fmt::{Display, Formatter};
4use fixed_buffer::FixedBuf;
5
6/// > 2.3.1. Preferred name syntax
7/// >
8/// > The DNS specifications attempt to be as general as possible in the rules for constructing
9/// > domain names.  The idea is that the name of any existing object can be expressed as a domain
10/// > name with minimal changes.
11/// >
12/// > However, when assigning a domain name for an object, the prudent user will select a name
13/// > which satisfies both the rules of the domain system and any existing rules for the object,
14/// > whether these rules are published or implied by existing programs.
15/// >
16/// > For example, when naming a mail domain, the user should satisfy both the rules of this memo
17/// > and those in [RFC-822](https://datatracker.ietf.org/doc/html/rfc822).  When creating a new
18/// > host name, the old rules for HOSTS.TXT should be followed.  This avoids problems when old
19/// > software is converted to use domain names.
20/// >
21/// > The following syntax will result in fewer problems with many
22/// >
23/// > applications that use domain names (e.g., mail, TELNET).
24/// >
25/// > `<domain> ::= <subdomain> | " "`
26/// >
27/// > `<subdomain> ::= <label> | <subdomain> "." <label>`
28/// >
29/// > `<label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]`
30/// >
31/// > `<ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>`
32/// >
33/// > `<let-dig-hyp> ::= <let-dig> | "-"`
34/// >
35/// > `<let-dig> ::= <letter> | <digit>`
36/// >
37/// > `<letter> ::=` any one of the 52 alphabetic characters `A` through `Z` in upper case
38/// > and `a` through `z` in lower case
39/// >
40/// > `<digit> ::=` any one of the ten digits `0` through `9`
41/// >
42/// > Note that while upper and lower case letters are allowed in domain names, no significance is
43/// > attached to the case.  That is, two names with the same spelling but different case are to be
44/// > treated as if identical.
45/// >
46/// > The labels must follow the rules for ARPANET host names.  They must start with a letter, end
47/// > with a letter or digit, and have as interior characters only letters, digits, and hyphen.
48/// > There are also some restrictions on the length.  Labels must be 63 characters or less.
49/// >
50/// > For example, the following strings identify hosts in the Internet:
51/// >
52/// > `A.ISI.EDU XX.LCS.MIT.EDU SRI-NIC.ARPA`
53///
54/// <https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.1>
55///
56/// > Various objects and parameters in the DNS have size limits.  They are listed below.  Some
57/// > could be easily changed, others are more fundamental.
58/// >
59/// > - labels: 63 octets or less
60/// > - names: 255 octets or less
61/// > - TTL: positive values of a signed 32 bit number.
62/// > - UDP messages: 512 octets or less
63///
64/// <https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.4>
65#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
66pub struct DnsName(String);
67impl DnsName {
68    fn is_letter(b: u8) -> bool {
69        b.is_ascii_lowercase() || b.is_ascii_uppercase()
70    }
71
72    fn is_letter_digit(b: u8) -> bool {
73        Self::is_letter(b) || b.is_ascii_digit()
74    }
75
76    fn is_letter_digit_hyphen(b: u8) -> bool {
77        Self::is_letter_digit(b) || b == b'-'
78    }
79
80    fn is_valid_label(label: &str) -> bool {
81        if label.is_empty() || label.len() > 63 {
82            return false;
83        }
84        let bytes = label.as_bytes();
85        Self::is_letter(bytes[0])
86            && bytes.iter().copied().all(Self::is_letter_digit_hyphen)
87            && Self::is_letter_digit(*bytes.last().unwrap())
88    }
89
90    fn is_valid_name(value: &str) -> bool {
91        if !value.is_ascii() {
92            return false;
93        }
94        value.split('.').all(Self::is_valid_label)
95    }
96
97    /// # Errors
98    /// Returns an error when `value` is not a valid DNS name.
99    pub fn new(value: &str) -> Result<Self, String> {
100        let trimmed = value.strip_suffix('.').unwrap_or(value);
101        if trimmed.len() > 255 || !Self::is_valid_name(trimmed) {
102            return Err(format!("not a valid DNS name: {value:?}"));
103        }
104        Ok(Self(trimmed.to_string()))
105    }
106
107    /// # Errors
108    /// Returns an error when `buf` does not contain a valid name.
109    pub fn read<const N: usize>(buf: &mut FixedBuf<N>) -> Result<DnsName, DnsError> {
110        let mut value = String::new();
111        for _ in 0..63 {
112            let len = read_u8(buf)? as usize;
113            if len == 0 {
114                if value.len() > 255 {
115                    return Err(DnsError::NameTooLong);
116                }
117                return Ok(Self(value));
118            }
119            if buf.readable().len() < len {
120                return Err(DnsError::Truncated);
121            }
122            let label_bytes = buf.read_bytes(len);
123            let label = std::str::from_utf8(label_bytes).map_err(|_| DnsError::InvalidLabel)?;
124            if !Self::is_valid_label(label) {
125                return Err(DnsError::InvalidLabel);
126            }
127            if !value.is_empty() {
128                value.push('.');
129            }
130            value.push_str(label);
131        }
132        Err(DnsError::TooManyLabels)
133    }
134
135    /// # Errors
136    /// Returns an error when `buf` fills up.
137    pub fn write<const N: usize>(&self, out: &mut FixedBuf<N>) -> Result<(), DnsError> {
138        for label in self.0.split('.') {
139            if label.len() > 63 {
140                return Err(DnsError::Unreachable(file!(), line!()));
141            }
142            let len =
143                u8::try_from(label.len()).map_err(|_| DnsError::Unreachable(file!(), line!()))?;
144            out.write_bytes(&[len])
145                .map_err(|_| DnsError::ResponseBufferFull)?;
146            out.write_bytes(label.as_bytes())
147                .map_err(|_| DnsError::ResponseBufferFull)?;
148        }
149        out.write_bytes(&[0])
150            .map_err(|_| DnsError::ResponseBufferFull)?;
151        Ok(())
152    }
153
154    /// # Errors
155    /// Returns an error when the name is longer than 255 bytes.  This cannot happen.
156    pub fn as_bytes(&self) -> Result<FixedBuf<256>, DnsError> {
157        let mut buf: FixedBuf<256> = FixedBuf::new();
158        self.write(&mut buf)?;
159        Ok(buf)
160    }
161
162    #[must_use]
163    pub fn inner(&self) -> &str {
164        &self.0
165    }
166}
167impl Display for DnsName {
168    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
169        write!(f, "{}", self.0)
170    }
171}
172impl TryFrom<&'static str> for DnsName {
173    type Error = String;
174
175    fn try_from(value: &'static str) -> Result<Self, Self::Error> {
176        DnsName::new(value)
177    }
178}
179
180#[cfg(test)]
181#[test]
182fn test_err() {
183    assert_eq!(
184        <Result<DnsName, String>>::Err("not a valid DNS name: \"abc!\"".to_string()),
185        DnsName::new("abc!")
186    );
187}
188
189#[cfg(test)]
190#[test]
191fn test_new_label_separators() {
192    DnsName::new(".").unwrap_err();
193    assert_eq!("a", DnsName::new("a.").unwrap().inner());
194    DnsName::new("a..").unwrap_err();
195    DnsName::new(".a").unwrap_err();
196    DnsName::new("b..a").unwrap_err();
197    DnsName::new(".b.a").unwrap_err();
198}
199
200#[cfg(test)]
201#[test]
202fn test_new_label_charset() {
203    const ALLOWED: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.";
204    for c in ALLOWED.chars() {
205        let value = format!("a{c}a");
206        DnsName::new(&value).expect(&value);
207    }
208    for b in 0..=255_u8 {
209        let c = char::from(b);
210        if !ALLOWED.contains(c) {
211            let value = format!("a{c}a");
212            assert_eq!(
213                <Result<DnsName, String>>::Err(format!("not a valid DNS name: {value:?}")),
214                DnsName::new(&value)
215            );
216        }
217    }
218    assert_eq!(
219        <Result<DnsName, String>>::Err("not a valid DNS name: \"a\u{263A}\"".to_string()),
220        DnsName::new("a\u{263A}")
221    );
222}
223
224#[cfg(test)]
225#[test]
226fn test_new_label() {
227    assert_eq!(
228        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789",
229        DnsName::new("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789")
230            .unwrap()
231            .inner()
232    );
233    assert_eq!("aB-Cd.eFg", DnsName::new("aB-Cd.eFg").unwrap().inner());
234}
235
236#[cfg(test)]
237#[test]
238fn test_new_label_format() {
239    DnsName::new("a").unwrap();
240    DnsName::new("1").unwrap_err();
241    DnsName::new("1a").unwrap_err();
242    DnsName::new("a1").unwrap();
243    DnsName::new("a9876543210").unwrap();
244    DnsName::new("-").unwrap_err();
245    DnsName::new("a-").unwrap_err();
246    DnsName::new("-a").unwrap_err();
247    DnsName::new("a-.b").unwrap_err();
248    DnsName::new("a.-b").unwrap_err();
249    DnsName::new("a-b").unwrap();
250    DnsName::new("a-0").unwrap();
251    DnsName::new("a---b").unwrap();
252}
253
254#[cfg(test)]
255#[test]
256fn test_new_label_length() {
257    DnsName::new("").unwrap_err();
258    DnsName::new("a").unwrap();
259    DnsName::new("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap();
260    DnsName::new("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap_err();
261}
262
263#[cfg(test)]
264#[test]
265fn test_new_name_length() {
266    DnsName::new(concat!(
267        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
268        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
269        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
270        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
271    ))
272    .unwrap();
273    DnsName::new(concat!(
274        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
275        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
276        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
277        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
278    ))
279    .unwrap();
280    DnsName::new(concat!(
281        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
282        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
283        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
284        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.a"
285    ))
286    .unwrap_err();
287    DnsName::new(concat!(
288        "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
289        "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
290        "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
291        "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
292        "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
293        "a.a.a",
294    ))
295    .unwrap();
296    DnsName::new(concat!(
297        "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
298        "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
299        "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
300        "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
301        "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
302        "a.a.aa",
303    ))
304    .unwrap_err();
305}
306
307// TODO: Test read()
308// TODO: Test write()
309
310#[cfg(test)]
311#[test]
312fn test_inner() {
313    assert_eq!("abc", DnsName::new("abc").unwrap().inner());
314}
315
316#[cfg(test)]
317#[test]
318fn test_display() {
319    assert_eq!(
320        "example.com",
321        format!("{}", DnsName::new("example.com").unwrap())
322    );
323}
324
325// TODO: Test TryFrom