dnstap_utils/
util.rs

1// Copyright 2021 Fastly, Inc.
2
3use anyhow::{bail, Result};
4use bytes::{Buf, BufMut, Bytes, BytesMut};
5use domain::base::opt::AllOptData;
6use std::convert::TryFrom;
7use std::net::IpAddr;
8use thiserror::Error;
9
10/// Utility function that converts a slice of bytes into an [`IpAddr`]. Slices of length 4 are
11/// converted to IPv4 addresses and slices of length 16 are converted to IPv6 addresses. All other
12/// slice lengths are invalid. This is how IP addresses are encoded in dnstap protobuf messages.
13pub fn try_from_u8_slice_for_ipaddr(value: &[u8]) -> Result<IpAddr> {
14    match value.len() {
15        4 => Ok(IpAddr::from(<[u8; 4]>::try_from(value)?)),
16        16 => Ok(IpAddr::from(<[u8; 16]>::try_from(value)?)),
17        _ => bail!(
18            "Cannot decode an IP address from a {} byte field",
19            value.len()
20        ),
21    }
22}
23
24#[derive(Debug, Error, PartialEq)]
25pub enum DnstapHandlerError {
26    #[error("Mismatch between logged dnstap response and re-queried DNS response, expecting {1} but received {2}")]
27    Mismatch(Bytes, String, String),
28
29    #[error("Timeout sending DNS query")]
30    Timeout,
31
32    #[error("dnstap payload is missing a required field")]
33    MissingField,
34}
35
36const DNSTAP_HANDLER_ERROR_PREFIX: &[u8] = b"dnstap-replay/DnstapHandlerError\x00";
37
38impl DnstapHandlerError {
39    pub fn serialize(&self) -> Bytes {
40        match self {
41            DnstapHandlerError::Mismatch(mismatch, _, _) => {
42                let mut b =
43                    BytesMut::with_capacity(DNSTAP_HANDLER_ERROR_PREFIX.len() + 4 + mismatch.len());
44                b.extend_from_slice(DNSTAP_HANDLER_ERROR_PREFIX);
45                b.put_u32(1);
46                b.extend_from_slice(mismatch);
47                b
48            }
49            DnstapHandlerError::Timeout => {
50                let mut b = BytesMut::with_capacity(DNSTAP_HANDLER_ERROR_PREFIX.len() + 4);
51                b.extend_from_slice(DNSTAP_HANDLER_ERROR_PREFIX);
52                b.put_u32(2);
53                b
54            }
55            DnstapHandlerError::MissingField => {
56                let mut b = BytesMut::with_capacity(DNSTAP_HANDLER_ERROR_PREFIX.len() + 4);
57                b.extend_from_slice(DNSTAP_HANDLER_ERROR_PREFIX);
58                b.put_u32(3);
59                b
60            }
61        }
62        .freeze()
63    }
64}
65
66pub fn deserialize_dnstap_handler_error(input: &[u8]) -> Result<DnstapHandlerError> {
67    if input.len() < DNSTAP_HANDLER_ERROR_PREFIX.len() {
68        bail!("Input buffer is too small");
69    }
70
71    let mut buf = Bytes::copy_from_slice(input);
72
73    let prefix = buf.copy_to_bytes(DNSTAP_HANDLER_ERROR_PREFIX.len());
74    if prefix != DNSTAP_HANDLER_ERROR_PREFIX {
75        bail!("DnstapHandlerError prefix not present");
76    }
77
78    if buf.remaining() < 4 {
79        bail!("DnstapHandlerError type not present");
80    }
81
82    match buf.get_u32() {
83        1 => Ok(DnstapHandlerError::Mismatch(
84            buf.split_off(0),
85            String::from(""),
86            String::from(""),
87        )),
88        2 => Ok(DnstapHandlerError::Timeout),
89        3 => Ok(DnstapHandlerError::MissingField),
90        _ => {
91            bail!("Unknown DnstapHandlerError type");
92        }
93    }
94}
95
96#[test]
97fn test_deserialize_dnstap_handler_error() {
98    // Test the extreme boundary condition. This is a minimally sized serialized
99    // DnstapHandlerError::Mismatch.
100    assert_eq!(
101        deserialize_dnstap_handler_error(b"dnstap-replay/DnstapHandlerError\x00\x00\x00\x00\x01")
102            .unwrap(),
103        DnstapHandlerError::Mismatch(Bytes::from(&b""[..]), String::from(""), String::from(""))
104    );
105
106    // This is the smallest serialized DnstapHandlerError::Mismatch with a single byte payload.
107    assert_eq!(
108        deserialize_dnstap_handler_error(
109            b"dnstap-replay/DnstapHandlerError\x00\x00\x00\x00\x01\x42"
110        )
111        .unwrap(),
112        DnstapHandlerError::Mismatch(
113            Bytes::from(&b"\x42"[..]),
114            String::from(""),
115            String::from("")
116        )
117    );
118
119    // The only way to serialize a DnstapHandlerError::Timeout.
120    assert_eq!(
121        deserialize_dnstap_handler_error(b"dnstap-replay/DnstapHandlerError\x00\x00\x00\x00\x02")
122            .unwrap(),
123        DnstapHandlerError::Timeout
124    );
125
126    // The only way to serialize a DnstapHandlerError::MissingField.
127    assert_eq!(
128        deserialize_dnstap_handler_error(b"dnstap-replay/DnstapHandlerError\x00\x00\x00\x00\x03")
129            .unwrap(),
130        DnstapHandlerError::MissingField
131    );
132}
133
134pub fn fmt_dns_message(s: &mut String, prefix: &str, raw_msg_bytes: &[u8]) {
135    use domain::base::iana::rtype::Rtype;
136    use domain::base::Message;
137    use domain::rdata::AllRecordData;
138
139    let msg = match Message::from_octets(raw_msg_bytes) {
140        Ok(msg) => msg,
141        Err(err) => {
142            s.push_str(prefix);
143            s.push_str(";; PARSE ERROR: ");
144            s.push_str(&err.to_string());
145            s.push('\n');
146            return;
147        }
148    };
149
150    let hdr = msg.header();
151
152    // opcode
153    s.push_str(prefix);
154    s.push_str(";; ->>HEADER<<- opcode: ");
155    s.push_str(&hdr.opcode().to_string());
156
157    // rcode
158    s.push_str(", rcode: ");
159    s.push_str(&hdr.rcode().to_string());
160
161    // id
162    s.push_str(", id: ");
163    s.push_str(&hdr.id().to_string());
164    s.push('\n');
165
166    // flags
167    s.push_str(prefix);
168    s.push_str(";; flags: ");
169    if hdr.qr() {
170        s.push_str("qr ");
171    }
172    if hdr.aa() {
173        s.push_str("aa ");
174    }
175    if hdr.tc() {
176        s.push_str("tc ");
177    }
178    if hdr.rd() {
179        s.push_str("rd ");
180    }
181    if hdr.ra() {
182        s.push_str("ra ");
183    }
184    if hdr.ad() {
185        s.push_str("ad ");
186    }
187    if hdr.cd() {
188        s.push_str("cd ");
189    }
190
191    // header counts
192    let hdr_counts = msg.header_counts();
193    s.push_str("; QUERY: ");
194    s.push_str(&hdr_counts.qdcount().to_string());
195    s.push_str(", ANSWER: ");
196    s.push_str(&hdr_counts.ancount().to_string());
197    s.push_str(", AUTHORITY: ");
198    s.push_str(&hdr_counts.nscount().to_string());
199    s.push_str(", ADDITIONAL: ");
200    s.push_str(&hdr_counts.adcount().to_string());
201    s.push_str("\n\n");
202
203    if let Ok(sections) = msg.sections() {
204        s.push_str(prefix);
205        s.push_str(";; QUESTION SECTION:\n");
206        for question in sections.0.flatten() {
207            s.push_str(prefix);
208            s.push(';');
209            s.push_str(&question.qname().to_string());
210            s.push_str(". ");
211            s.push_str(&question.qclass().to_string());
212            s.push(' ');
213            s.push_str(&question.qtype().to_string());
214            s.push('\n')
215        }
216        s.push('\n');
217
218        s.push_str(prefix);
219        s.push_str(";; ANSWER SECTION:\n");
220        for record in sections.1.limit_to::<AllRecordData<_, _>>().flatten() {
221            s.push_str(prefix);
222            s.push_str(&record.to_string());
223            s.push('\n')
224        }
225        s.push('\n');
226
227        s.push_str(prefix);
228        s.push_str(";; AUTHORITY SECTION:\n");
229        for record in sections.2.limit_to::<AllRecordData<_, _>>().flatten() {
230            s.push_str(prefix);
231            s.push_str(&record.to_string());
232            s.push('\n')
233        }
234        s.push('\n');
235
236        s.push_str(prefix);
237        s.push_str(";; ADDITIONAL SECTION:\n");
238        for record in sections.3.limit_to::<AllRecordData<_, _>>().flatten() {
239            if record.rtype() == Rtype::Opt {
240                continue;
241            }
242            s.push_str(prefix);
243            s.push_str(&record.to_string());
244            s.push('\n')
245        }
246
247        if let Some(optrec) = msg.opt() {
248            s.push('\n');
249            s.push_str(prefix);
250            s.push_str(";; OPT PSEUDOSECTION:\n");
251            s.push_str(prefix);
252            s.push_str("; EDNS: version ");
253            s.push_str(&optrec.version().to_string());
254            s.push_str("; flags: ");
255            if optrec.dnssec_ok() {
256                s.push_str("do ");
257            }
258            s.push_str("; udp: ");
259            s.push_str(&optrec.udp_payload_size().to_string());
260            s.push('\n');
261
262            for opt in optrec.iter::<AllOptData<_>>().flatten() {
263                s.push_str(prefix);
264                match opt {
265                    AllOptData::Nsid(nsid) => {
266                        s.push_str("; NSID: ");
267                        s.push_str(&nsid.to_string());
268                        if let Ok(nsid_data) = hex::decode(&nsid.to_string()) {
269                            if let Ok(nsid_str) = std::str::from_utf8(&nsid_data) {
270                                s.push_str(" (\"");
271                                s.push_str(nsid_str);
272                                s.push_str("\")");
273                            }
274                        }
275                    }
276                    AllOptData::Dau(data) => {
277                        s.push_str("; DAU:");
278                        for alg in &data {
279                            s.push(' ');
280                            s.push_str(&alg.to_string());
281                        }
282                    }
283                    AllOptData::Dhu(data) => {
284                        s.push_str("; DHU:");
285                        for alg in &data {
286                            s.push(' ');
287                            s.push_str(&alg.to_string());
288                        }
289                    }
290                    AllOptData::N3u(data) => {
291                        s.push_str("; N3U:");
292                        for alg in &data {
293                            s.push(' ');
294                            s.push_str(&alg.to_string());
295                        }
296                    }
297                    AllOptData::Expire(expire) => {
298                        s.push_str("; EXPIRE: ");
299                        if let Some(expire_data) = expire.expire() {
300                            s.push_str(&expire_data.to_string());
301                            s.push_str(" seconds");
302                        }
303                    }
304                    AllOptData::TcpKeepalive(data) => {
305                        s.push_str("; TCP-KEEPALIVE: ");
306                        let iseconds = data.timeout() / 10;
307                        let fseconds = data.timeout() % 10;
308                        s.push_str(&iseconds.to_string());
309                        s.push('.');
310                        s.push_str(&fseconds.to_string());
311                        s.push_str(" seconds");
312                    }
313                    AllOptData::Padding(padding) => {
314                        s.push_str("; PADDING: [");
315                        s.push_str(&padding.len().to_string());
316                        s.push_str(" bytes]");
317                    }
318                    AllOptData::ClientSubnet(subnet) => {
319                        s.push_str("; CLIENT-SUBNET:\n");
320                        s.push_str(prefix);
321                        s.push_str(";  NETWORK ADDRESS: ");
322                        s.push_str(&subnet.addr().to_string());
323                        s.push('\n');
324                        s.push_str(prefix);
325                        s.push_str(";  SOURCE PREFIX-LENGTH: ");
326                        s.push_str(&subnet.source_prefix_len().to_string());
327                        s.push('\n');
328                        s.push_str(prefix);
329                        s.push_str(";  SCOPE PREFIX-LENGTH: ");
330                        s.push_str(&subnet.scope_prefix_len().to_string());
331                    }
332                    AllOptData::Cookie(data) => {
333                        s.push_str("; COOKIE: ");
334                        s.push_str(&hex::encode(data.cookie()));
335                    }
336                    AllOptData::Chain(data) => {
337                        s.push_str("; CHAIN: ");
338                        s.push_str(&data.start().to_string());
339                    }
340                    AllOptData::KeyTag(data) => {
341                        s.push_str("; KEY-TAG:");
342                        for keytag in &data {
343                            s.push(' ');
344                            s.push_str(&keytag.to_string());
345                        }
346                    }
347                    AllOptData::ExtendedError(data) => {
348                        s.push_str("; EXTENDED-DNS-ERROR:\n");
349                        s.push_str(prefix);
350                        s.push_str(";  INFO-CODE: (");
351                        s.push_str(&data.code().to_int().to_string());
352                        s.push_str(") ");
353                        s.push_str(&data.code().to_string());
354                        s.push('\n');
355                        if let Some(text) = data.text() {
356                            if let Ok(text_str) = std::str::from_utf8(text) {
357                                s.push_str(prefix);
358                                s.push_str(";  EXTRA-TEXT: \"");
359                                s.push_str(text_str);
360                                s.push('"');
361                            }
362                        }
363                    }
364                    AllOptData::Other(data) => {
365                        s.push_str("; OPT=");
366                        s.push_str(&data.code().to_int().to_string());
367                        s.push(':');
368                        s.push_str(&hex::encode(data.data()).to_uppercase());
369                    }
370                    _ => {
371                        s.push_str("; Other unknown EDNS option, giving up.");
372                    }
373                }
374                s.push('\n');
375            }
376        }
377    }
378}
379
380pub fn dns_message_is_truncated(raw_msg_bytes: &[u8]) -> bool {
381    // Only check a message if the complete header (12 octets) was received.
382    if raw_msg_bytes.len() >= 12 {
383        let msg = domain::base::Header::for_message_slice(raw_msg_bytes);
384        return msg.tc();
385    }
386    false
387}
388
389#[test]
390fn test_dns_message_is_truncated() {
391    // Too few bytes to be a complete DNS header.
392    assert!(!dns_message_is_truncated(&hex::decode("1234").unwrap()));
393
394    // Real DNS header with the TC bit set.
395    assert!(dns_message_is_truncated(
396        &hex::decode("b84587000001000000000001").unwrap()
397    ));
398
399    // Real DNS header with the TC bit unset.
400    assert!(!dns_message_is_truncated(
401        &hex::decode("b84585000001000000010001").unwrap()
402    ));
403}