simple_dns/dns/rdata/
svcb.rs

1use std::collections::BTreeSet;
2use std::{borrow::Cow, collections::BTreeMap};
3
4use crate::bytes_buffer::BytesBuffer;
5use crate::dns::WireFormat;
6use crate::{CharacterString, Name};
7
8use super::RR;
9
10/// The SVCB DNS RR type is used to locate alternative endpoints for a service.
11/// [RFC 9460](https://datatracker.ietf.org/doc/html/rfc9460).
12#[derive(Debug, PartialEq, Eq, Hash, Clone)]
13pub struct SVCB<'a> {
14    /// The priority of this record (relative to others, with lower values preferred).
15    ///
16    /// A value of 0 indicates AliasMode.
17    pub priority: u16,
18
19    /// The domain name of either the alias target (for AliasMode)
20    /// or the alternative endpoint (for ServiceMode).
21    pub target: Name<'a>,
22
23    /// A list of key=value pairs describing the alternative endpoint at `target`.
24    params: BTreeMap<u16, SVCParam<'a>>,
25}
26
27impl RR for SVCB<'_> {
28    const TYPE_CODE: u16 = 64;
29}
30
31impl<'a> SVCB<'a> {
32    /// Creates a new `SVCB` instance with no parameters.
33    pub fn new(priority: u16, target: Name<'a>) -> Self {
34        Self {
35            priority,
36            target,
37            params: BTreeMap::new(),
38        }
39    }
40
41    /// Sets a parameter, replacing any previous value.
42    pub fn set_param(&mut self, param: SVCParam<'a>) {
43        self.params.insert(param.key_code(), param);
44    }
45
46    /// Same as [`Self::set_param`], but returns `self` for chaining.
47    pub fn with_param(mut self, param: SVCParam<'a>) -> Self {
48        self.set_param(param);
49        self
50    }
51
52    /// Sets the "mandatory" parameter.
53    ///
54    /// If `keys` is empty, this method does nothing.
55    pub fn set_mandatory(&mut self, keys: impl Iterator<Item = u16>) {
56        let keys: BTreeSet<_> = keys.collect();
57        if keys.is_empty() {
58            return;
59        }
60
61        self.set_param(SVCParam::Mandatory(keys));
62    }
63
64    /// Sets the "alpn" parameter.
65    ///
66    /// if `alpn_ids` is empty, this method does nothing.
67    pub fn set_alpn(&mut self, alpn_ids: &[CharacterString<'a>]) {
68        if alpn_ids.is_empty() {
69            return;
70        }
71
72        self.set_param(SVCParam::Alpn(alpn_ids.into()));
73    }
74
75    /// Sets the "no-default-alpn" parameter.
76    pub fn set_no_default_alpn(&mut self) {
77        self.set_param(SVCParam::NoDefaultAlpn);
78    }
79
80    /// Sets the "port" parameter.
81    pub fn set_port(&mut self, port: u16) {
82        self.set_param(SVCParam::Port(port));
83    }
84
85    /// Sets the "ipv4hint" parameter.
86    ///
87    /// if `ips` is empty, this method does nothing.
88    pub fn set_ipv4hint(&mut self, ips: &[u32]) {
89        if ips.is_empty() {
90            return;
91        }
92
93        self.set_param(SVCParam::Ipv4Hint(ips.into()));
94    }
95
96    /// Sets the "ipv6hint" parameter.
97    ///
98    /// if `ips` is empty, this method does nothing
99    pub fn set_ipv6hint(&mut self, ips: &[u128]) {
100        if ips.is_empty() {
101            return;
102        }
103
104        self.set_param(SVCParam::Ipv6Hint(ips.into()))
105    }
106
107    /// Gets a read-only reference to the [`SVCParam`]
108    ///
109    /// Returns `None` if the key does not exist.
110    pub fn get_param(&'a self, key: u16) -> Option<&'a SVCParam<'a>> {
111        self.params.get(&key)
112    }
113
114    /// Iterates over all parameters.
115    pub fn iter_params(&self) -> impl Iterator<Item = &SVCParam> {
116        self.params.values()
117    }
118
119    /// Transforms the inner data into its owned type
120    pub fn into_owned<'b>(self) -> SVCB<'b> {
121        SVCB {
122            priority: self.priority,
123            target: self.target.into_owned(),
124            params: self
125                .params
126                .into_iter()
127                .map(|(k, v)| (k, v.into_owned()))
128                .collect(),
129        }
130    }
131}
132
133impl<'a> WireFormat<'a> for SVCB<'a> {
134    const MINIMUM_LEN: usize = 2;
135
136    fn parse(data: &mut BytesBuffer<'a>) -> crate::Result<Self>
137    where
138        Self: Sized,
139    {
140        let priority = data.get_u16()?;
141
142        let target = Name::parse(data)?;
143        let mut params = BTreeMap::new();
144
145        let mut previous_key: Option<u16> = None;
146        while data.has_remaining() {
147            let param = SVCParam::parse(data)?;
148            let key = param.key_code();
149
150            if let Some(p_key) = previous_key {
151                if key <= p_key {
152                    return Err(crate::SimpleDnsError::InvalidDnsPacket);
153                }
154            }
155
156            previous_key = Some(key);
157            params.insert(key, param);
158        }
159        Ok(Self {
160            priority,
161            target,
162            params,
163        })
164    }
165
166    fn write_to<T: std::io::Write>(&self, out: &mut T) -> crate::Result<()> {
167        out.write_all(&self.priority.to_be_bytes())?;
168        self.target.write_to(out)?;
169        for param in self.params.values() {
170            param.write_to(out)?;
171        }
172        Ok(())
173    }
174
175    // NOT implementing `write_compressed_to`,
176    // RFC9460 ยง2.2 specifically mentioned the TargetName is *uncompressed*.
177
178    fn len(&self) -> usize {
179        self.target.len() + self.params.values().map(|p| p.len()).sum::<usize>() + Self::MINIMUM_LEN
180    }
181}
182
183/// The SVC Param section of the SVCB DNS RR type.
184/// [RFC 9460](https://datatracker.ietf.org/doc/html/rfc9460).
185///
186/// Known parameters are defined as variants of this enum and properly parsed.
187/// Unknown parameters are stored as [Self::Unknown] variant.
188#[derive(Debug, Clone, Eq, PartialEq, Hash)]
189pub enum SVCParam<'a> {
190    /// Mandatory keys in this RR. Key Code 0.
191    Mandatory(BTreeSet<u16>),
192
193    /// Additional supported protocols. Key Code 1.
194    Alpn(Vec<CharacterString<'a>>),
195
196    /// No support for default protocol. Key Code 2.
197    NoDefaultAlpn,
198
199    /// Port for alternative endpoint. Key Code 3.
200    Port(u16),
201
202    /// IPv4 address hints. Key Code 4.
203    Ipv4Hint(Vec<u32>),
204
205    /// Encrypted ClientHello (ECH) configuration. Key Code 5.
206    Ech(Cow<'a, [u8]>),
207
208    /// IPv6 address hints. Key Code 6.
209    Ipv6Hint(Vec<u128>),
210
211    /// Reserved for invalid keys. Key Code 65535.
212    InvalidKey,
213
214    /// Unknown key format.
215    Unknown(u16, Cow<'a, [u8]>),
216}
217
218impl SVCParam<'_> {
219    /// Returns the key code of the parameter
220    pub fn key_code(&self) -> u16 {
221        match self {
222            SVCParam::Mandatory(_) => 0,
223            SVCParam::Alpn(_) => 1,
224            SVCParam::NoDefaultAlpn => 2,
225            SVCParam::Port(_) => 3,
226            SVCParam::Ipv4Hint(_) => 4,
227            SVCParam::Ech(_) => 5,
228            SVCParam::Ipv6Hint(_) => 6,
229            SVCParam::InvalidKey => 65535,
230            SVCParam::Unknown(key, _) => *key,
231        }
232    }
233
234    /// Transforms the inner data into its owned
235    pub fn into_owned<'b>(self) -> SVCParam<'b> {
236        match self {
237            SVCParam::Mandatory(keys) => SVCParam::Mandatory(keys),
238            SVCParam::Alpn(alpns) => {
239                SVCParam::Alpn(alpns.into_iter().map(|a| a.into_owned()).collect())
240            }
241            SVCParam::NoDefaultAlpn => SVCParam::NoDefaultAlpn,
242            SVCParam::Port(port) => SVCParam::Port(port),
243            SVCParam::Ipv4Hint(ips) => SVCParam::Ipv4Hint(ips),
244            SVCParam::Ech(ech) => SVCParam::Ech(ech.into_owned().into()),
245            SVCParam::Ipv6Hint(ips) => SVCParam::Ipv6Hint(ips),
246            SVCParam::InvalidKey => SVCParam::InvalidKey,
247            SVCParam::Unknown(key, value) => SVCParam::Unknown(key, value.into_owned().into()),
248        }
249    }
250}
251
252impl<'a> WireFormat<'a> for SVCParam<'a> {
253    const MINIMUM_LEN: usize = 4;
254
255    fn parse(data: &mut BytesBuffer<'a>) -> crate::Result<Self>
256    where
257        Self: Sized,
258    {
259        let key = data.get_u16()?;
260        let len = data.get_u16()? as usize;
261
262        let mut data = data.new_limited_to(len)?;
263        match key {
264            0 => {
265                let mut keys = BTreeSet::new();
266                while data.has_remaining() {
267                    keys.insert(data.get_u16()?);
268                }
269                Ok(SVCParam::Mandatory(keys))
270            }
271            1 => {
272                let mut alpns = Vec::new();
273                while data.has_remaining() {
274                    alpns.push(CharacterString::parse(&mut data)?);
275                }
276                Ok(SVCParam::Alpn(alpns))
277            }
278            2 => Ok(SVCParam::NoDefaultAlpn),
279            3 => Ok(SVCParam::Port(data.get_u16()?)),
280            4 => {
281                let mut ips = Vec::new();
282                while data.has_remaining() {
283                    ips.push(data.get_u32()?);
284                }
285                Ok(SVCParam::Ipv4Hint(ips))
286            }
287            5 => {
288                let len = data.get_u16()? as usize;
289                let data = data.get_remaining();
290                if data.len() != len {
291                    Err(crate::SimpleDnsError::InvalidDnsPacket)
292                } else {
293                    Ok(SVCParam::Ech(Cow::Borrowed(data)))
294                }
295            }
296            6 => {
297                let mut ips = Vec::new();
298                while data.has_remaining() {
299                    ips.push(data.get_u128()?);
300                }
301                Ok(SVCParam::Ipv6Hint(ips))
302            }
303            _ => {
304                let value = Cow::Borrowed(data.get_remaining());
305                Ok(SVCParam::Unknown(key, value))
306            }
307        }
308    }
309
310    fn write_to<T: std::io::Write>(&self, out: &mut T) -> crate::Result<()> {
311        out.write_all(&self.key_code().to_be_bytes())?;
312        out.write_all(&(self.len() as u16 - 4).to_be_bytes())?;
313
314        match self {
315            SVCParam::Mandatory(keys) => {
316                for key in keys {
317                    out.write_all(&key.to_be_bytes())?;
318                }
319            }
320            SVCParam::Alpn(alpns) => {
321                for alpn in alpns.iter() {
322                    alpn.write_to(out)?;
323                }
324            }
325            SVCParam::NoDefaultAlpn => {}
326            SVCParam::Port(port) => {
327                out.write_all(&port.to_be_bytes())?;
328            }
329            SVCParam::Ipv4Hint(ips) => {
330                for ip in ips.iter() {
331                    out.write_all(&ip.to_be_bytes())?;
332                }
333            }
334            SVCParam::Ech(ech) => {
335                out.write_all(&(ech.len() as u16).to_be_bytes())?;
336                out.write_all(ech)?;
337            }
338            SVCParam::Ipv6Hint(ips) => {
339                for ip in ips.iter() {
340                    out.write_all(&ip.to_be_bytes())?;
341                }
342            }
343            SVCParam::Unknown(_, value) => {
344                out.write_all(value)?;
345            }
346            _ => return Err(crate::SimpleDnsError::InvalidDnsPacket),
347        };
348
349        Ok(())
350    }
351
352    fn len(&self) -> usize {
353        // key + param len + param value len
354        Self::MINIMUM_LEN
355            + match self {
356                SVCParam::Mandatory(keys) => keys.len() * 2,
357                SVCParam::Alpn(alpns) => alpns.iter().map(|a| a.len()).sum(),
358                SVCParam::NoDefaultAlpn => 0,
359                SVCParam::Port(_) => 2,
360                SVCParam::Ipv4Hint(ips) => ips.len() * 4,
361                SVCParam::Ech(ech) => 2 + ech.len(),
362                SVCParam::Ipv6Hint(ips) => ips.len() * 16,
363                SVCParam::Unknown(_, value) => value.len(),
364                _ => 0,
365            }
366    }
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372    use crate::{rdata::RData, ResourceRecord};
373
374    #[test]
375    fn parse_sample() -> Result<(), Box<dyn std::error::Error>> {
376        // Copy of the answer from `dig crypto.cloudflare.com -t HTTPS`.
377        let sample_file = std::fs::read("samples/zonefile/HTTPS.sample")?;
378
379        let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata {
380            RData::HTTPS(rdata) => rdata,
381            _ => unreachable!(),
382        };
383
384        let mut expected_rdata = SVCB::new(1, Name::new_unchecked(""));
385        expected_rdata.set_alpn(&["http/1.1".try_into()?, "h2".try_into()?]);
386        expected_rdata.set_ipv4hint(&[0xa2_9f_89_55, 0xa2_9f_8a_55]);
387        expected_rdata.set_param(SVCParam::Ech(
388            b"\xfe\x0d\x00\x41\x44\x00\x20\x00\x20\x1a\xd1\x4d\x5c\xa9\x52\xda\
389                \x88\x18\xae\xaf\xd7\xc6\xc8\x7d\x47\xb4\xb3\x45\x7f\x8e\x58\xbc\
390                \x87\xb8\x95\xfc\xb3\xde\x1b\x34\x33\x00\x04\x00\x01\x00\x01\x00\
391                \x12cloudflare-ech.com\x00\x00"
392                .into(),
393        ));
394        expected_rdata.set_ipv6hint(&[
395            0x2606_4700_0007_0000_0000_0000_a29f_8955,
396            0x2606_4700_0007_0000_0000_0000_a29f_8a55,
397        ]);
398
399        assert_eq!(*sample_rdata, expected_rdata);
400
401        assert_eq!(
402            sample_rdata.get_param(1),
403            Some(&SVCParam::Alpn(vec![
404                "http/1.1".try_into().unwrap(),
405                "h2".try_into().unwrap()
406            ]))
407        );
408        assert_eq!(sample_rdata.get_param(3), None);
409
410        Ok(())
411    }
412
413    #[test]
414    fn parse_and_write_svcb() {
415        // Test vectors are taken from Appendix D.
416        // <https://www.rfc-editor.org/rfc/rfc9460.html#name-test-vectors>
417        let tests: &[(&str, &[u8], SVCB<'_>)] = &[
418            (
419                "D.1. AliasMode",
420                b"\x00\x00\x03foo\x07example\x03com\x00",
421                SVCB::new(0, Name::new_unchecked("foo.example.com")),
422            ),
423            (
424                "D.2.3. TargetName Is '.'",
425                b"\x00\x01\x00",
426                SVCB::new(1, Name::new_unchecked("")),
427            ),
428            (
429                "D.2.4. Specified a Port",
430                b"\x00\x10\x03foo\x07example\x03com\x00\x00\x03\x00\x02\x00\x35",
431                {
432                    let mut svcb = SVCB::new(16, Name::new_unchecked("foo.example.com"));
433                    svcb.set_port(53);
434                    svcb
435                }
436            ),
437            (
438                "D.2.6. A Generic Key and Quoted Value with a Decimal Escape",
439                b"\x00\x01\x03foo\x07example\x03com\x00\x02\x9b\x00\x09hello\xd2qoo",
440                {
441                    let svcb = SVCB::new(1, Name::new_unchecked("foo.example.com")).with_param(SVCParam::Unknown(667, b"hello\xd2qoo"[..].into()));
442                    svcb
443                }
444            ),
445            (
446                "D.2.7. Two Quoted IPv6 Hints",
447                b"\x00\x01\x03foo\x07example\x03com\x00\x00\x06\x00\x20\
448                    \x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\
449                    \x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x53\x00\x01",
450                {
451                    let mut svcb = SVCB::new(1, Name::new_unchecked("foo.example.com"));
452                    svcb.set_ipv6hint(&[
453                        0x2001_0db8_0000_0000_0000_0000_0000_0001,
454                        0x2001_0db8_0000_0000_0000_0000_0053_0001,
455                    ]);
456                    svcb
457                },
458            ),
459            (
460                "D.2.10. SvcParamKey Ordering Is Arbitrary in Presentation Format but Sorted in Wire Format",
461                b"\x00\x10\x03foo\x07example\x03org\x00\
462                    \x00\x00\x00\x04\x00\x01\x00\x04\
463                    \x00\x01\x00\x09\x02h2\x05h3-19\
464                    \x00\x04\x00\x04\xc0\x00\x02\x01",
465                {
466                    let mut svcb = SVCB::new(16, Name::new_unchecked("foo.example.org"));
467                    svcb.set_alpn(&["h2".try_into().unwrap(), "h3-19".try_into().unwrap()]);
468                    svcb.set_mandatory([1, 4].into_iter());
469                    svcb.set_ipv4hint(&[0xc0_00_02_01]);
470                    svcb
471                },
472            ),
473        ];
474
475        for (name, expected_bytes, svcb) in tests {
476            let mut data = Vec::new();
477            svcb.write_to(&mut data).unwrap();
478            assert_eq!(expected_bytes, &data, "Test {name}");
479
480            let svcb2 = SVCB::parse(&mut data[..].into()).unwrap();
481            assert_eq!(svcb, &svcb2, "Test {name}");
482        }
483    }
484}