simple_dns/dns/rdata/
svcb.rs

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