simple_dns/dns/rdata/
opt.rs

1use crate::{
2    dns::{header::Header, WireFormat},
3    RCODE,
4};
5use std::borrow::Cow;
6
7use super::RR;
8
9pub mod masks {
10    pub const RCODE_MASK: u32 = 0b0000_0000_0000_0000_0000_0000_1111_1111;
11    pub const VERSION_MASK: u32 = 0b0000_0000_0000_0000_1111_1111_0000_0000;
12}
13
14/// OPT is a pseudo-rr used to carry control information  
15/// If an OPT record is present in a received request, responders MUST include an OPT record in their respective responses.  
16/// OPT RRs MUST NOT be cached, forwarded, or stored in or loaded from master files.  
17///
18/// There must be only one OPT record in the message.
19/// If a query message with more than one OPT RR is received, a FORMERR (RCODE=1) MUST be returned.
20#[derive(Debug, PartialEq, Eq, Hash, Clone)]
21pub struct OPT<'a> {
22    /// The variable part of this OPT RR
23    pub opt_codes: Vec<OPTCode<'a>>,
24    /// UDP packet size supported by the responder
25    pub udp_packet_size: u16,
26
27    /// EDNS version supported by the responder
28    pub version: u8,
29}
30
31impl<'a> RR for OPT<'a> {
32    const TYPE_CODE: u16 = 41;
33}
34
35impl<'a> WireFormat<'a> for OPT<'a> {
36    fn parse(data: &'a [u8], position: &mut usize) -> crate::Result<Self>
37    where
38        Self: Sized,
39    {
40        if *position + 10 > data.len() {
41            return Err(crate::SimpleDnsError::InsufficientData);
42        }
43
44        // udp packet size comes from CLASS
45        let udp_packet_size = u16::from_be_bytes(data[*position + 2..*position + 4].try_into()?);
46        // version comes from ttl
47        let ttl = u32::from_be_bytes(data[*position + 4..*position + 8].try_into()?);
48        let version = ((ttl & masks::VERSION_MASK) >> masks::VERSION_MASK.trailing_zeros()) as u8;
49
50        *position += 10;
51
52        let mut opt_codes = Vec::new();
53        while *position < data.len() {
54            if *position + 4 > data.len() {
55                return Err(crate::SimpleDnsError::InsufficientData);
56            }
57
58            let code = u16::from_be_bytes(data[*position..*position + 2].try_into()?);
59            let length =
60                u16::from_be_bytes(data[*position + 2..*position + 4].try_into()?) as usize;
61
62            if *position + 4 + length > data.len() {
63                return Err(crate::SimpleDnsError::InsufficientData);
64            }
65
66            let inner_data = Cow::Borrowed(&data[*position + 4..*position + 4 + length]);
67            opt_codes.push(OPTCode {
68                code,
69                data: inner_data,
70            });
71
72            *position += 4 + length;
73        }
74
75        Ok(Self {
76            opt_codes,
77            udp_packet_size,
78            version,
79        })
80    }
81
82    fn write_to<T: std::io::Write>(&self, out: &mut T) -> crate::Result<()> {
83        for code in self.opt_codes.iter() {
84            out.write_all(&code.code.to_be_bytes())?;
85            out.write_all(&(code.data.len() as u16).to_be_bytes())?;
86            out.write_all(&code.data)?;
87        }
88
89        Ok(())
90    }
91
92    fn len(&self) -> usize {
93        self.opt_codes.iter().map(|o| o.data.len() + 4).sum()
94    }
95}
96
97impl<'a> OPT<'a> {
98    pub(crate) fn extract_rcode_from_ttl(ttl: u32, header: &Header) -> RCODE {
99        let mut rcode = (ttl & masks::RCODE_MASK) << 4;
100        rcode |= header.response_code as u32;
101        RCODE::from(rcode as u16)
102    }
103
104    pub(crate) fn encode_ttl(&self, header: &Header) -> u32 {
105        let mut ttl: u32 = (header.response_code as u32 & masks::RCODE_MASK) >> 4;
106        ttl |= (self.version as u32) << masks::VERSION_MASK.trailing_zeros();
107        ttl
108    }
109    /// Transforms the inner data into its owned type
110    pub fn into_owned<'b>(self) -> OPT<'b> {
111        OPT {
112            // length: self.length,
113            udp_packet_size: self.udp_packet_size,
114            version: self.version,
115            opt_codes: self.opt_codes.into_iter().map(|o| o.into_owned()).collect(),
116        }
117    }
118}
119
120/// Represents the variable part of an OPT rr
121#[derive(Debug, PartialEq, Eq, Hash, Clone)]
122pub struct OPTCode<'a> {
123    // TODO: include an OPT_CODE enum???
124    /// Assigned by the Expert Review process as defined by the DNSEXT working group and the IESG.
125    pub code: u16,
126    /// Varies per OPTION-CODE.  MUST be treated as a bit field.
127    pub data: Cow<'a, [u8]>,
128}
129
130impl<'a> OPTCode<'a> {
131    /// Transforms the inner data into its owned type
132    pub fn into_owned<'b>(self) -> OPTCode<'b> {
133        OPTCode {
134            code: self.code,
135            data: self.data.into_owned().into(),
136        }
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use crate::{rdata::RData, Name, ResourceRecord};
143
144    use super::*;
145
146    #[test]
147    fn parse_and_write_opt_empty() {
148        let header = Header::new_reply(1, crate::OPCODE::StandardQuery);
149
150        let opt = OPT {
151            udp_packet_size: 500,
152            version: 2,
153            opt_codes: Vec::new(),
154        };
155        let opt_rr = ResourceRecord {
156            ttl: opt.encode_ttl(&header),
157            name: Name::new_unchecked("."),
158            class: crate::CLASS::IN,
159            cache_flush: false,
160            rdata: RData::OPT(opt),
161        };
162
163        let mut data = Vec::new();
164        assert!(opt_rr.write_to(&mut data).is_ok());
165
166        let opt = match ResourceRecord::parse(&data, &mut 0)
167            .expect("failed to parse")
168            .rdata
169        {
170            RData::OPT(rdata) => rdata,
171            _ => unreachable!(),
172        };
173
174        assert_eq!(data.len(), opt_rr.len());
175        assert_eq!(500, opt.udp_packet_size);
176        assert_eq!(2, opt.version);
177        assert!(opt.opt_codes.is_empty());
178    }
179
180    #[test]
181    fn parse_and_write_opt() {
182        let header = Header::new_reply(1, crate::OPCODE::StandardQuery);
183
184        let opt = OPT {
185            udp_packet_size: 500,
186            version: 2,
187            opt_codes: vec![
188                OPTCode {
189                    code: 1,
190                    data: Cow::Owned(vec![255, 255]),
191                },
192                OPTCode {
193                    code: 2,
194                    data: Cow::Owned(vec![255, 255, 255]),
195                },
196            ],
197        };
198
199        let opt_rr = ResourceRecord {
200            ttl: opt.encode_ttl(&header),
201            name: Name::new_unchecked("."),
202            class: crate::CLASS::IN,
203            cache_flush: false,
204            rdata: RData::OPT(opt),
205        };
206
207        let mut data = Vec::new();
208        assert!(opt_rr.write_to(&mut data).is_ok());
209
210        let mut opt = match ResourceRecord::parse(&data, &mut 0)
211            .expect("failed to parse")
212            .rdata
213        {
214            RData::OPT(rdata) => rdata,
215            _ => unreachable!(),
216        };
217
218        assert_eq!(data.len(), opt_rr.len());
219        assert_eq!(500, opt.udp_packet_size);
220        assert_eq!(2, opt.version);
221        assert_eq!(2, opt.opt_codes.len());
222
223        let opt_code = opt.opt_codes.pop().unwrap();
224        assert_eq!(2, opt_code.code);
225        assert_eq!(vec![255, 255, 255], *opt_code.data);
226
227        let opt_code = opt.opt_codes.pop().unwrap();
228        assert_eq!(1, opt_code.code);
229        assert_eq!(vec![255, 255], *opt_code.data);
230    }
231
232    // #[test]
233    // fn parse_sample() -> Result<(), Box<dyn std::error::Error>> {
234    //     let sample_file = std::fs::read("samples/zonefile/OPT.sample")?;
235    //
236    //     let sample_rdata = match ResourceRecord::parse(&sample_file, 0)?.rdata {
237    //         RData::OPT(rdata) => rdata,
238    //         _ => unreachable!(),
239    //     };
240    //
241    //     Ok(())
242    // }
243}