sawp_dns/
header.rs

1use nom::number::streaming::be_u16;
2
3use sawp::error::Result;
4
5use sawp_flags::{BitFlags, Flag, Flags};
6
7use crate::enums::{OpCode, QueryResponse, ResponseCode};
8
9use crate::ErrorFlags;
10#[cfg(feature = "ffi")]
11use sawp_ffi::GenerateFFI;
12
13/// Masks for extracting DNS header flags
14#[allow(non_camel_case_types)]
15#[derive(Debug, Clone, Copy, PartialEq, Eq, BitFlags)]
16#[repr(u16)]
17pub enum header_masks {
18    QUERY_RESPONSE = 0b1000_0000_0000_0000,
19    OPCODE = 0b0111_1000_0000_0000,
20    AUTH = 0b0000_0100_0000_0000,
21    TRUNC = 0b0000_0010_0000_0000,
22    RECUR_DESIRED = 0b0000_0001_0000_0000,
23    RECUR_AVAIL = 0b0000_0000_1000_0000,
24    Z = 0b0000_0000_0100_0000,
25    AUTH_DATA = 0b0000_0000_0010_0000,
26    CHECK_DISABLED = 0b0000_0000_0001_0000,
27    RCODE = 0b0000_0000_0000_1111,
28}
29
30/// A parsed DNS header
31#[cfg_attr(feature = "ffi", derive(GenerateFFI))]
32#[cfg_attr(feature = "ffi", sawp_ffi(prefix = "sawp_dns"))]
33#[derive(Debug, PartialEq, Eq)]
34pub struct Header {
35    /// Transaction ID
36    pub transaction_id: u16,
37    /// Raw header flags
38    pub flags: u16,
39    #[cfg_attr(feature = "ffi", sawp_ffi(copy))]
40    /// QueryResponse::Query or QueryResponse::Response
41    pub query_response: QueryResponse,
42    #[cfg_attr(feature = "ffi", sawp_ffi(copy))]
43    /// Type of query
44    pub opcode: OpCode,
45    /// Is the name server an authority for this domain name?
46    pub authoritative: bool,
47    /// Was this msg truncated?
48    pub truncated: bool,
49    /// Should the name server pursue the query recursively?
50    pub recursion_desired: bool,
51    /// Can the name server pursue the query recursively?
52    pub recursion_available: bool,
53    /// Z flag is set?
54    pub zflag: bool,
55
56    /// All data authenticated by the server
57    pub authenticated_data: bool,
58
59    pub check_disabled: bool,
60    #[cfg_attr(feature = "ffi", sawp_ffi(copy))]
61    /// Name server success/error state
62    pub rcode: ResponseCode,
63    /// Number of questions provided
64    pub qdcount: u16,
65    /// Number of answers provided
66    pub ancount: u16,
67    /// Number of name server resource records in the auth records
68    pub nscount: u16,
69    /// Number of resource records in the additional records section
70    pub arcount: u16,
71}
72
73impl Header {
74    #[allow(clippy::type_complexity)]
75    pub fn parse(input: &[u8]) -> Result<(&[u8], (Header, Flags<ErrorFlags>))> {
76        let mut error_flags = ErrorFlags::none();
77
78        let (input, txid) = be_u16(input)?;
79        let (input, flags) = be_u16(input)?;
80        let wrapped_flags = Flags::<header_masks>::from_bits(flags);
81        let query = if wrapped_flags.intersects(header_masks::QUERY_RESPONSE) {
82            QueryResponse::Response
83        } else {
84            QueryResponse::Query
85        };
86        let opcode: OpCode = OpCode::from_raw((wrapped_flags & header_masks::OPCODE).bits() >> 10);
87        if opcode == OpCode::UNKNOWN {
88            error_flags |= ErrorFlags::UnknownOpcode;
89        }
90        let rcode: ResponseCode =
91            ResponseCode::from_raw((wrapped_flags & header_masks::RCODE).bits());
92        if rcode == ResponseCode::UNKNOWN {
93            error_flags |= ErrorFlags::UnknownRcode;
94        }
95        let (input, qcnt) = be_u16(input)?;
96        let (input, acnt) = be_u16(input)?;
97        let (input, nscnt) = be_u16(input)?;
98        let (input, arcnt) = be_u16(input)?;
99
100        Ok((
101            input,
102            (
103                Header {
104                    transaction_id: txid,
105                    flags,
106                    query_response: query,
107                    opcode,
108                    authoritative: wrapped_flags.intersects(header_masks::AUTH),
109                    truncated: wrapped_flags.intersects(header_masks::TRUNC),
110                    recursion_desired: wrapped_flags.intersects(header_masks::RECUR_DESIRED),
111                    recursion_available: wrapped_flags.intersects(header_masks::RECUR_AVAIL),
112                    zflag: wrapped_flags.intersects(header_masks::Z),
113                    authenticated_data: wrapped_flags.intersects(header_masks::AUTH_DATA),
114                    check_disabled: wrapped_flags.intersects(header_masks::CHECK_DISABLED),
115                    rcode,
116                    qdcount: qcnt,
117                    ancount: acnt,
118                    nscount: nscnt,
119                    arcount: arcnt,
120                },
121                error_flags,
122            ),
123        ))
124    }
125}
126
127#[cfg(test)]
128mod test {
129    #![allow(clippy::type_complexity)]
130
131    use crate::{ErrorFlags, Header, OpCode, QueryResponse, ResponseCode};
132    use rstest::rstest;
133    use sawp::error::{Error, Result};
134    use sawp_flags::{Flag, Flags};
135
136    #[rstest(
137        input,
138        expected,
139        case::parse_simple_header(
140            & [
141                0x31, 0x21, // Transaction ID: 0x3121
142                0x81, 0x00, // Flags: response, recursion desired
143                0x00, 0x01, // QDCOUNT: 1
144                0x00, 0x01, // ANCOUNT: 1
145                0x00, 0x00, // NSCOUNT: 0
146                0x00, 0x00, // ARCOUNT: 0
147            ],
148            Ok((
149                b"".as_ref(),
150                (Header {
151                    transaction_id: 0x3121,
152                    flags: 0b1000_0001_0000_0000,
153                    query_response: QueryResponse::Response,
154                    opcode: OpCode::QUERY,
155                    authoritative: false,
156                    truncated: false,
157                    recursion_desired: true,
158                    recursion_available: false,
159                    zflag: false,
160                    authenticated_data: false,
161                    check_disabled: false,
162                    rcode: ResponseCode::NOERROR,
163                    qdcount: 1,
164                    ancount: 1,
165                    nscount: 0,
166                    arcount: 0,
167                },
168                ErrorFlags::none())
169            ))
170        ),
171        case::parse_too_short_header(
172            & [
173                0x31, 0x21, // Transaction ID: 0x3121
174                0x81, 0x00, // Flags: response, recursion desired
175                0x00, 0x01, // QDCOUNT: 1
176                0x00, 0x01, // ANCOUNT: 1
177                0x00, 0x00, // NSCOUNT: 0
178            ],
179            Err(Error::incomplete_needed(2))
180        ),
181        case::parse_header_bad_opcode(
182            & [
183                0x31, 0x21, // Transaction ID: 0x3121
184                0xb1, 0x00, // Flags: invalid opcode, recursion desired, authenticated data, format error
185                0x00, 0x01, // QDCOUNT: 1
186                0x00, 0x01, // ANCOUNT: 1
187                0x00, 0x00, // NSCOUNT: 0
188                0x00, 0x00, // ARCOUNT: 0
189            ],
190            Ok((
191                b"".as_ref(),
192                (Header {
193                    transaction_id: 0x3121,
194                    flags: 0b1011_0001_0000_0000,
195                    query_response: QueryResponse::Response,
196                    opcode: OpCode::UNKNOWN,
197                    authoritative: false,
198                    truncated: false,
199                    recursion_desired: true,
200                    recursion_available: false,
201                    zflag: false,
202                    authenticated_data: false,
203                    check_disabled: false,
204                    rcode: ResponseCode::NOERROR,
205                    qdcount: 1,
206                    ancount: 1,
207                    nscount: 0,
208                    arcount: 0,
209                },
210                ErrorFlags::UnknownOpcode.into())
211            ))
212        ),
213        case::parse_header_bad_rcode(
214            & [
215                0x31, 0x21, // Transaction ID: 0x3121
216                0x81, 0x0c, // Flags: response, recursion desired, invalid rcode
217                0x00, 0x01, // QDCOUNT: 1
218                0x00, 0x01, // ANCOUNT: 1
219                0x00, 0x00, // NSCOUNT: 0
220                0x00, 0x00, // ARCOUNT: 0
221            ],
222            Ok((
223            b"".as_ref(),
224            (Header {
225                transaction_id: 0x3121,
226                flags: 0b1000_0001_0000_1100,
227                query_response: QueryResponse::Response,
228                opcode: OpCode::QUERY,
229                authoritative: false,
230                truncated: false,
231                recursion_desired: true,
232                recursion_available: false,
233                zflag: false,
234                authenticated_data: false,
235                check_disabled: false,
236                rcode: ResponseCode::UNKNOWN,
237                qdcount: 1,
238                ancount: 1,
239                nscount: 0,
240                arcount: 0,
241            },
242            ErrorFlags::UnknownRcode.into())
243            ))
244        ),
245    )]
246    fn header(input: &[u8], expected: Result<(&[u8], (Header, Flags<ErrorFlags>))>) {
247        assert_eq!(Header::parse(input), expected);
248    }
249}