Skip to main content

c_ares/dns/
dns_opt.rs

1//! Utilities for parsing and formatting DNS option/parameter values.
2//!
3//! DNS record types such as OPT, SVCB, and HTTPS contain option/parameter
4//! extensions whose values are opaque byte slices.
5//! [`DnsRr::opt_datatype()`](c_ares::DnsRr::opt_datatype) indicates how to
6//! interpret these bytes.  This module provides helpers to parse them into
7//! meaningful Rust types and to produce human-readable representations.
8
9use itertools::Itertools;
10use std::fmt;
11use std::net::{Ipv4Addr, Ipv6Addr};
12
13use crate::{DnsOptDataType, DnsRr, DnsRrKey};
14
15/// Error returned when an option value cannot be parsed.
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct OptParseError(String);
18
19impl fmt::Display for OptParseError {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        write!(f, "{}", self.0)
22    }
23}
24
25impl std::error::Error for OptParseError {}
26
27/// A parsed DNS option value.
28#[derive(Clone, Debug, PartialEq, Eq)]
29pub enum OptValue {
30    /// No value.
31    None,
32    /// List of strings.
33    StrList(Vec<String>),
34    /// List of `u8` values.
35    U8List(Vec<u8>),
36    /// A single `u16`.
37    U16(u16),
38    /// List of `u16` values.
39    U16List(Vec<u16>),
40    /// A single `u32`.
41    U32(u32),
42    /// List of `u32` values.
43    U32List(Vec<u32>),
44    /// List of IPv4 addresses.
45    InAddr4List(Vec<Ipv4Addr>),
46    /// List of IPv6 addresses.
47    InAddr6List(Vec<Ipv6Addr>),
48    /// Opaque binary data.
49    Bin(Vec<u8>),
50    /// A DNS domain name.
51    Name(String),
52}
53
54impl fmt::Display for OptValue {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        match self {
57            OptValue::None => Ok(()),
58            OptValue::StrList(v) => {
59                let vals = v.iter().format(", ");
60                write!(f, "[{vals}]")
61            }
62            OptValue::U8List(v) => {
63                let vals = v.iter().format(", ");
64                write!(f, "[{vals}]")
65            }
66            OptValue::U16(v) => write!(f, "{v}"),
67            OptValue::U16List(v) => {
68                let vals = v.iter().format(", ");
69                write!(f, "[{vals}]")
70            }
71            OptValue::U32(v) => write!(f, "{v}"),
72            OptValue::U32List(v) => {
73                let vals = v.iter().format(", ");
74                write!(f, "[{vals}]")
75            }
76            OptValue::InAddr4List(v) => {
77                let vals = v.iter().format(", ");
78                write!(f, "[{vals}]")
79            }
80            OptValue::InAddr6List(v) => {
81                let vals = v.iter().format(", ");
82                write!(f, "[{vals}]")
83            }
84            OptValue::Bin(v) => {
85                for b in v {
86                    write!(f, "{b:02x}")?;
87                }
88                Ok(())
89            }
90            OptValue::Name(s) => write!(f, "{s}"),
91        }
92    }
93}
94
95/// Parse a raw option value according to its datatype.
96///
97/// Uses [`DnsRr::opt_datatype()`] to determine the wire format, then decodes
98/// the byte slice into the corresponding [`OptValue`].
99pub fn parse_opt_value(key: DnsRrKey, opt: u16, data: &[u8]) -> Result<OptValue, OptParseError> {
100    match DnsRr::opt_datatype(key, opt) {
101        DnsOptDataType::None => parse_none(data),
102        DnsOptDataType::StrList => parse_str_list(data).map(OptValue::StrList),
103        DnsOptDataType::U8List => Ok(OptValue::U8List(data.to_vec())),
104        DnsOptDataType::U16 => parse_u16(data).map(OptValue::U16),
105        DnsOptDataType::U16List => parse_u16_list(data).map(OptValue::U16List),
106        DnsOptDataType::U32 => parse_u32(data).map(OptValue::U32),
107        DnsOptDataType::U32List => parse_u32_list(data).map(OptValue::U32List),
108        DnsOptDataType::InAddr4List => parse_inaddr4_list(data).map(OptValue::InAddr4List),
109        DnsOptDataType::InAddr6List => parse_inaddr6_list(data).map(OptValue::InAddr6List),
110        DnsOptDataType::Bin => Ok(OptValue::Bin(data.to_vec())),
111        DnsOptDataType::Name => parse_name(data).map(OptValue::Name),
112    }
113}
114
115fn parse_none(data: &[u8]) -> Result<OptValue, OptParseError> {
116    if !data.is_empty() {
117        return Err(OptParseError(format!(
118            "expected no data, got {} bytes",
119            data.len()
120        )));
121    }
122    Ok(OptValue::None)
123}
124
125fn parse_str_list(data: &[u8]) -> Result<Vec<String>, OptParseError> {
126    let mut result = Vec::new();
127    let mut pos = 0;
128    while pos < data.len() {
129        let (buf, consumed) = crate::expand_string(data, pos)
130            .map_err(|e| OptParseError(format!("invalid string in list: {e}")))?;
131        let s = std::str::from_utf8(&buf)
132            .map_err(|e| OptParseError(format!("invalid UTF-8 in string list: {e}")))?;
133        result.push(s.to_owned());
134        pos += consumed;
135    }
136    Ok(result)
137}
138
139fn parse_u16(data: &[u8]) -> Result<u16, OptParseError> {
140    data.try_into()
141        .map_err(|_| OptParseError(format!("Expected 2 bytes, got {}", data.len())))
142        .map(u16::from_be_bytes)
143}
144
145fn parse_u16_list(data: &[u8]) -> Result<Vec<u16>, OptParseError> {
146    if !data.len().is_multiple_of(2) {
147        return Err(OptParseError(format!(
148            "u16 list length {} is not a multiple of 2",
149            data.len()
150        )));
151    }
152
153    Ok(data
154        .as_chunks::<2>()
155        .0
156        .iter()
157        .copied()
158        .map(u16::from_be_bytes)
159        .collect())
160}
161
162fn parse_u32(data: &[u8]) -> Result<u32, OptParseError> {
163    data.try_into()
164        .map_err(|_| OptParseError(format!("Expected 4 bytes, got {}", data.len())))
165        .map(u32::from_be_bytes)
166}
167
168fn parse_u32_list(data: &[u8]) -> Result<Vec<u32>, OptParseError> {
169    if !data.len().is_multiple_of(4) {
170        return Err(OptParseError(format!(
171            "u32 list length {} is not a multiple of 4",
172            data.len()
173        )));
174    }
175
176    Ok(data
177        .as_chunks::<4>()
178        .0
179        .iter()
180        .copied()
181        .map(u32::from_be_bytes)
182        .collect())
183}
184
185fn parse_inaddr4_list(data: &[u8]) -> Result<Vec<Ipv4Addr>, OptParseError> {
186    if !data.len().is_multiple_of(4) {
187        return Err(OptParseError(format!(
188            "IPv4 address list length {} is not a multiple of 4",
189            data.len()
190        )));
191    }
192
193    Ok(data
194        .as_chunks::<4>()
195        .0
196        .iter()
197        .copied()
198        .map(Ipv4Addr::from)
199        .collect())
200}
201
202fn parse_inaddr6_list(data: &[u8]) -> Result<Vec<Ipv6Addr>, OptParseError> {
203    if !data.len().is_multiple_of(16) {
204        return Err(OptParseError(format!(
205            "IPv6 address list length {} is not a multiple of 16",
206            data.len()
207        )));
208    }
209
210    Ok(data
211        .as_chunks::<16>()
212        .0
213        .iter()
214        .copied()
215        .map(Ipv6Addr::from)
216        .collect())
217}
218
219fn parse_name(data: &[u8]) -> Result<String, OptParseError> {
220    let (name, consumed) =
221        crate::expand_name(data, 0).map_err(|e| OptParseError(format!("invalid DNS name: {e}")))?;
222    if consumed != data.len() {
223        return Err(OptParseError(format!(
224            "DNS name consumed {consumed} bytes, but buffer is {} bytes",
225            data.len()
226        )));
227    }
228    Ok(name.to_owned())
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn parse_none_empty() {
237        assert_eq!(parse_none(&[]).unwrap(), OptValue::None);
238    }
239
240    #[test]
241    fn parse_none_nonempty() {
242        assert!(parse_none(&[0x01]).is_err());
243    }
244
245    #[test]
246    fn parse_str_list_basic() {
247        // Two strings: "h3" and "h2"
248        let data = [2, b'h', b'3', 2, b'h', b'2'];
249        assert_eq!(
250            parse_str_list(&data).unwrap(),
251            vec!["h3".to_string(), "h2".to_string()]
252        );
253    }
254
255    #[test]
256    fn parse_str_list_invalid_utf8() {
257        let data = [2, 0xff, 0xfe];
258        assert!(parse_str_list(&data).is_err());
259    }
260
261    #[test]
262    fn parse_str_list_truncated() {
263        // Length byte says 5, but only 2 bytes follow.
264        let data = [5, b'h', b'i'];
265        assert!(parse_str_list(&data).is_err());
266    }
267
268    #[test]
269    fn parse_str_list_trailing_data() {
270        // Valid string "hi" followed by extra byte.
271        let data = [2, b'h', b'i', 0xff];
272        assert!(parse_str_list(&data).is_err());
273    }
274
275    #[test]
276    fn parse_u16_basic() {
277        assert_eq!(parse_u16(&[0x01, 0x00]).unwrap(), 256);
278    }
279
280    #[test]
281    fn parse_u16_bad_length() {
282        assert!(parse_u16(&[0x01]).is_err());
283        assert!(parse_u16(&[0x01, 0x00, 0x00]).is_err());
284    }
285
286    #[test]
287    fn parse_u16_list_basic() {
288        assert_eq!(
289            parse_u16_list(&[0x00, 0x01, 0x00, 0x02]).unwrap(),
290            vec![1, 2]
291        );
292    }
293
294    #[test]
295    fn parse_u16_list_bad_length() {
296        assert!(parse_u16_list(&[0x00, 0x01, 0x00]).is_err());
297    }
298
299    #[test]
300    fn parse_u32_basic() {
301        assert_eq!(parse_u32(&[0x00, 0x00, 0x01, 0x00]).unwrap(), 256);
302    }
303
304    #[test]
305    fn parse_u32_bad_length() {
306        assert!(parse_u32(&[0x00, 0x00, 0x01]).is_err());
307        assert!(parse_u32(&[0x00, 0x00, 0x01, 0x00, 0x00]).is_err());
308    }
309
310    #[test]
311    fn parse_u32_list_bad_length() {
312        assert!(parse_u32_list(&[0x00, 0x00, 0x01]).is_err());
313    }
314
315    #[test]
316    fn parse_inaddr4_list_basic() {
317        let data = [127, 0, 0, 1, 10, 0, 0, 1];
318        assert_eq!(
319            parse_inaddr4_list(&data).unwrap(),
320            vec![Ipv4Addr::LOCALHOST, Ipv4Addr::new(10, 0, 0, 1),]
321        );
322    }
323
324    #[test]
325    fn parse_inaddr4_list_bad_length() {
326        assert!(parse_inaddr4_list(&[1, 2, 3]).is_err());
327    }
328
329    #[test]
330    fn parse_inaddr6_list_basic() {
331        let mut data = [0u8; 16];
332        data[0] = 0x20;
333        data[1] = 0x01;
334        let result = parse_inaddr6_list(&data).unwrap();
335        assert_eq!(result.len(), 1);
336        assert_eq!(result[0].octets()[0], 0x20);
337    }
338
339    #[test]
340    fn parse_inaddr6_list_bad_length() {
341        assert!(parse_inaddr6_list(&[0u8; 15]).is_err());
342    }
343
344    #[test]
345    fn parse_name_basic() {
346        // "www.example.com" in DNS wire format.
347        let data = [
348            3, b'w', b'w', b'w', 7, b'e', b'x', b'a', b'm', b'p', b'l', b'e', 3, b'c', b'o', b'm',
349            0,
350        ];
351        assert_eq!(parse_name(&data).unwrap(), "www.example.com");
352    }
353
354    #[test]
355    fn parse_name_truncated() {
356        // Label length says 5, but only 2 bytes follow before terminator.
357        let data = [5, b'a', b'b', 0];
358        assert!(parse_name(&data).is_err());
359    }
360
361    #[test]
362    fn parse_name_trailing_data() {
363        // Valid name "com." followed by extra bytes.
364        let data = [3, b'c', b'o', b'm', 0, 0xff];
365        assert!(parse_name(&data).is_err());
366    }
367
368    #[test]
369    fn format_opt_value_display() {
370        let val = OptValue::InAddr4List(vec![Ipv4Addr::new(1, 2, 3, 4)]);
371        assert_eq!(val.to_string(), "[1.2.3.4]");
372    }
373
374    #[test]
375    fn format_opt_value_bin() {
376        let val = OptValue::Bin(vec![0xca, 0xfe]);
377        assert_eq!(val.to_string(), "cafe");
378    }
379
380    #[test]
381    fn display_none() {
382        assert_eq!(OptValue::None.to_string(), "");
383    }
384
385    #[test]
386    fn display_str_list() {
387        let val = OptValue::StrList(vec!["h3".to_owned(), "h2".to_owned()]);
388        assert_eq!(val.to_string(), "[h3, h2]");
389    }
390
391    #[test]
392    fn display_u8_list() {
393        let val = OptValue::U8List(vec![1, 2, 3]);
394        assert_eq!(val.to_string(), "[1, 2, 3]");
395    }
396
397    #[test]
398    fn display_u16() {
399        assert_eq!(OptValue::U16(42).to_string(), "42");
400    }
401
402    #[test]
403    fn display_u16_list() {
404        let val = OptValue::U16List(vec![100, 200]);
405        assert_eq!(val.to_string(), "[100, 200]");
406    }
407
408    #[test]
409    fn display_u32() {
410        assert_eq!(OptValue::U32(123456).to_string(), "123456");
411    }
412
413    #[test]
414    fn display_u32_list() {
415        let val = OptValue::U32List(vec![1, 2]);
416        assert_eq!(val.to_string(), "[1, 2]");
417    }
418
419    #[test]
420    fn display_inaddr6_list() {
421        let val = OptValue::InAddr6List(vec![Ipv6Addr::LOCALHOST]);
422        assert_eq!(val.to_string(), "[::1]");
423    }
424
425    #[test]
426    fn display_name() {
427        let val = OptValue::Name("example.com".to_owned());
428        assert_eq!(val.to_string(), "example.com");
429    }
430
431    #[test]
432    fn display_bin_empty() {
433        assert_eq!(OptValue::Bin(vec![]).to_string(), "");
434    }
435
436    #[test]
437    fn opt_parse_error_display() {
438        let err = OptParseError("test error".to_owned());
439        assert_eq!(err.to_string(), "test error");
440    }
441
442    #[test]
443    fn opt_parse_error_is_error() {
444        let err = OptParseError("test".to_owned());
445        let _: &dyn std::error::Error = &err;
446    }
447
448    #[test]
449    fn parse_str_list_empty() {
450        assert_eq!(parse_str_list(&[]).unwrap(), Vec::<String>::new());
451    }
452
453    #[test]
454    fn parse_u16_list_empty() {
455        assert_eq!(parse_u16_list(&[]).unwrap(), Vec::<u16>::new());
456    }
457
458    #[test]
459    fn parse_u32_list_basic() {
460        assert_eq!(
461            parse_u32_list(&[0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02]).unwrap(),
462            vec![1, 2]
463        );
464    }
465
466    #[test]
467    fn parse_u32_list_empty() {
468        assert_eq!(parse_u32_list(&[]).unwrap(), Vec::<u32>::new());
469    }
470
471    #[test]
472    fn parse_inaddr4_list_empty() {
473        assert_eq!(parse_inaddr4_list(&[]).unwrap(), Vec::<Ipv4Addr>::new());
474    }
475
476    #[test]
477    fn parse_inaddr6_list_empty() {
478        assert_eq!(parse_inaddr6_list(&[]).unwrap(), Vec::<Ipv6Addr>::new());
479    }
480
481    #[test]
482    fn parse_inaddr6_list_multiple() {
483        let mut data = [0u8; 32];
484        // ::1
485        data[15] = 1;
486        // ::2
487        data[31] = 2;
488        let result = parse_inaddr6_list(&data).unwrap();
489        assert_eq!(
490            result,
491            vec![Ipv6Addr::LOCALHOST, Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 2)]
492        );
493    }
494
495    // --- parse_opt_value integration tests ---
496    // These use HTTPS_PARAMS opt codes with known datatype mappings.
497
498    #[test]
499    fn parse_opt_value_none() {
500        // HTTPS param 2 = no-default-alpn (None)
501        let result = parse_opt_value(DnsRrKey::HTTPS_PARAMS, 2, &[]).unwrap();
502        assert_eq!(result, OptValue::None);
503    }
504
505    #[test]
506    fn parse_opt_value_none_rejects_data() {
507        assert!(parse_opt_value(DnsRrKey::HTTPS_PARAMS, 2, &[0x01]).is_err());
508    }
509
510    #[test]
511    fn parse_opt_value_str_list() {
512        // HTTPS param 1 = alpn (StrList)
513        let data = [2, b'h', b'3'];
514        let result = parse_opt_value(DnsRrKey::HTTPS_PARAMS, 1, &data).unwrap();
515        assert_eq!(result, OptValue::StrList(vec!["h3".to_owned()]));
516    }
517
518    #[test]
519    fn parse_opt_value_u16() {
520        // HTTPS param 3 = port (U16)
521        let result = parse_opt_value(DnsRrKey::HTTPS_PARAMS, 3, &[0x01, 0xBB]).unwrap();
522        assert_eq!(result, OptValue::U16(443));
523    }
524
525    #[test]
526    fn parse_opt_value_u16_bad_length() {
527        assert!(parse_opt_value(DnsRrKey::HTTPS_PARAMS, 3, &[0x01]).is_err());
528    }
529
530    #[test]
531    fn parse_opt_value_u16_list() {
532        // HTTPS param 0 = mandatory (U16List)
533        let result = parse_opt_value(DnsRrKey::HTTPS_PARAMS, 0, &[0x00, 0x01, 0x00, 0x03]).unwrap();
534        assert_eq!(result, OptValue::U16List(vec![1, 3]));
535    }
536
537    #[test]
538    fn parse_opt_value_inaddr4_list() {
539        // HTTPS param 4 = ipv4hint (InAddr4List)
540        let result = parse_opt_value(DnsRrKey::HTTPS_PARAMS, 4, &[127, 0, 0, 1]).unwrap();
541        assert_eq!(result, OptValue::InAddr4List(vec![Ipv4Addr::LOCALHOST]));
542    }
543
544    #[test]
545    fn parse_opt_value_inaddr4_list_bad_length() {
546        assert!(parse_opt_value(DnsRrKey::HTTPS_PARAMS, 4, &[1, 2, 3]).is_err());
547    }
548
549    #[test]
550    fn parse_opt_value_bin() {
551        // HTTPS param 5 = ech (Bin)
552        let result = parse_opt_value(DnsRrKey::HTTPS_PARAMS, 5, &[0xca, 0xfe]).unwrap();
553        assert_eq!(result, OptValue::Bin(vec![0xca, 0xfe]));
554    }
555
556    #[test]
557    fn parse_opt_value_inaddr6_list() {
558        // HTTPS param 6 = ipv6hint (InAddr6List)
559        let mut data = [0u8; 16];
560        data[15] = 1;
561        let result = parse_opt_value(DnsRrKey::HTTPS_PARAMS, 6, &data).unwrap();
562        assert_eq!(result, OptValue::InAddr6List(vec![Ipv6Addr::LOCALHOST]));
563    }
564
565    #[test]
566    fn parse_opt_value_inaddr6_list_bad_length() {
567        assert!(parse_opt_value(DnsRrKey::HTTPS_PARAMS, 6, &[0u8; 15]).is_err());
568    }
569
570    #[test]
571    fn parse_opt_value_u32() {
572        // OPT option 2 = UL (U32)
573        let result = parse_opt_value(DnsRrKey::OPT_OPTIONS, 2, &[0x00, 0x01, 0x51, 0x80]).unwrap();
574        assert_eq!(result, OptValue::U32(86400));
575    }
576
577    #[test]
578    fn parse_opt_value_u32_bad_length() {
579        assert!(parse_opt_value(DnsRrKey::OPT_OPTIONS, 2, &[0x00, 0x01, 0x51]).is_err());
580    }
581
582    #[test]
583    fn parse_opt_value_u8_list() {
584        // OPT option 5 = DAU (U8List)
585        let result = parse_opt_value(DnsRrKey::OPT_OPTIONS, 5, &[8, 13, 14]).unwrap();
586        assert_eq!(result, OptValue::U8List(vec![8, 13, 14]));
587    }
588
589    #[test]
590    fn parse_opt_value_name() {
591        // OPT option 13 = CHAIN (Name)
592        let data = [
593            7, b'e', b'x', b'a', b'm', b'p', b'l', b'e', 3, b'c', b'o', b'm', 0,
594        ];
595        let result = parse_opt_value(DnsRrKey::OPT_OPTIONS, 13, &data).unwrap();
596        assert_eq!(result, OptValue::Name("example.com".to_owned()));
597    }
598}