]> git.ipfire.org Git - people/ms/suricata.git/blob - rust/src/dns/parser.rs
a3164e3286923495c4a5af5633466936db43c174
[people/ms/suricata.git] / rust / src / dns / parser.rs
1 /* Copyright (C) 2017 Open Information Security Foundation
2 *
3 * You can copy, redistribute or modify this Program under the terms of
4 * the GNU General Public License version 2 as published by the Free
5 * Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * version 2 along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15 * 02110-1301, USA.
16 */
17
18 //! Nom parsers for DNS.
19
20 use nom::IResult;
21 use nom::combinator::rest;
22 use nom::error::ErrorKind;
23 use nom::multi::length_data;
24 use nom::number::streaming::{be_u8, be_u16, be_u32};
25 use nom;
26 use crate::dns::dns::*;
27
28 // Parse a DNS header.
29 named!(pub dns_parse_header<DNSHeader>,
30 do_parse!(
31 tx_id: be_u16 >>
32 flags: be_u16 >>
33 questions: be_u16 >>
34 answer_rr: be_u16 >>
35 authority_rr: be_u16 >>
36 additional_rr: be_u16 >>
37 (
38 DNSHeader{
39 tx_id: tx_id,
40 flags: flags,
41 questions: questions,
42 answer_rr: answer_rr,
43 authority_rr: authority_rr,
44 additional_rr: additional_rr,
45 }
46 )
47 )
48 );
49
50 /// Parse a DNS name.
51 ///
52 /// Parameters:
53 /// start: the start of the name
54 /// message: the complete message that start is a part of
55 pub fn dns_parse_name<'a, 'b>(start: &'b [u8],
56 message: &'b [u8])
57 -> IResult<&'b [u8], Vec<u8>> {
58 let mut pos = start;
59 let mut pivot = start;
60 let mut name: Vec<u8> = Vec::with_capacity(32);
61 let mut count = 0;
62
63 loop {
64 if pos.len() == 0 {
65 break;
66 }
67
68 let len = pos[0];
69
70 if len == 0x00 {
71 pos = &pos[1..];
72 break;
73 } else if len & 0b1100_0000 == 0 {
74 match length_data(be_u8)(pos) as IResult<&[u8],_> {
75 Ok((rem, label)) => {
76 if name.len() > 0 {
77 name.push('.' as u8);
78 }
79 name.extend(label);
80 pos = rem;
81 }
82 _ => {
83 return Err(nom::Err::Error(
84 error_position!(pos, ErrorKind::OctDigit)));
85 }
86 }
87 } else if len & 0b1100_0000 == 0b1100_0000 {
88 match be_u16(pos) as IResult<&[u8],_> {
89 Ok((rem, leader)) => {
90 let offset = usize::from(leader) & 0x3fff;
91 if offset > message.len() {
92 return Err(nom::Err::Error(
93 error_position!(pos, ErrorKind::OctDigit)));
94 }
95 pos = &message[offset..];
96 if pivot == start {
97 pivot = rem;
98 }
99 }
100 _ => {
101 return Err(nom::Err::Error(
102 error_position!(pos, ErrorKind::OctDigit)));
103 }
104 }
105 } else {
106 return Err(nom::Err::Error(
107 error_position!(pos, ErrorKind::OctDigit)));
108 }
109
110 // Return error if we've looped a certain number of times.
111 count += 1;
112 if count > 255 {
113 return Err(nom::Err::Error(
114 error_position!(pos, ErrorKind::OctDigit)));
115 }
116
117 }
118
119 // If we followed a pointer we return the position after the first
120 // pointer followed. Is there a better way to see if these slices
121 // diverged from each other? A straight up comparison would
122 // actually check the contents.
123 if pivot.len() != start.len() {
124 return Ok((pivot, name));
125 }
126 return Ok((pos, name));
127
128 }
129
130 /// Parse answer entries.
131 ///
132 /// In keeping with the C implementation, answer values that can
133 /// contain multiple answers get expanded into their own answer
134 /// records. An example of this is a TXT record with multiple strings
135 /// in it - each string will be expanded to its own answer record.
136 ///
137 /// This function could be a made a whole lot simpler if we logged a
138 /// multi-string TXT entry as a single quote string, similar to the
139 /// output of dig. Something to consider for a future version.
140 fn dns_parse_answer<'a>(slice: &'a [u8], message: &'a [u8], count: usize)
141 -> IResult<&'a [u8], Vec<DNSAnswerEntry>> {
142
143 let mut answers = Vec::new();
144 let mut input = slice;
145
146 for _ in 0..count {
147 match do_parse!(
148 input,
149 name: call!(dns_parse_name, message) >>
150 rrtype: be_u16 >>
151 rrclass: be_u16 >>
152 ttl: be_u32 >>
153 data_len: be_u16 >>
154 data: take!(data_len) >>
155 (
156 name,
157 rrtype,
158 rrclass,
159 ttl,
160 data
161 )
162 ) {
163 Ok((rem, val)) => {
164 let name = val.0;
165 let rrtype = val.1;
166 let rrclass = val.2;
167 let ttl = val.3;
168 let data = val.4;
169 let n = match rrtype {
170 DNS_RECORD_TYPE_TXT => {
171 // For TXT records we need to run the parser
172 // multiple times. Set n high, to the maximum
173 // value based on a max txt side of 65535, but
174 // taking into considering that strings need
175 // to be quoted, so half that.
176 32767
177 }
178 _ => {
179 // For all other types we only want to run the
180 // parser once, so set n to 1.
181 1
182 }
183 };
184 let result: IResult<&'a [u8], Vec<DNSRData>> =
185 do_parse!(
186 data,
187 rdata: many_m_n!(1, n,
188 complete!(call!(dns_parse_rdata, message, rrtype)))
189 >> (rdata)
190 );
191 match result {
192 Ok((_, rdatas)) => {
193 for rdata in rdatas {
194 answers.push(DNSAnswerEntry{
195 name: name.clone(),
196 rrtype: rrtype,
197 rrclass: rrclass,
198 ttl: ttl,
199 data: rdata,
200 });
201 }
202 }
203 Err(e) => { return Err(e); }
204 }
205 input = rem;
206 }
207 Err(e) => { return Err(e); }
208 }
209 }
210
211 return Ok((input, answers));
212 }
213
214
215 /// Parse a DNS response.
216 pub fn dns_parse_response<'a>(slice: &'a [u8])
217 -> IResult<&[u8], DNSResponse> {
218 do_parse!(
219 slice,
220 header: dns_parse_header
221 >> queries: count!(
222 call!(dns_parse_query, slice), header.questions as usize)
223 >> answers: call!(
224 dns_parse_answer, slice, header.answer_rr as usize)
225 >> authorities: call!(
226 dns_parse_answer, slice, header.authority_rr as usize)
227 >> (
228 DNSResponse{
229 header: header,
230 queries: queries,
231 answers: answers,
232 authorities: authorities,
233 }
234 )
235 )
236 }
237
238 /// Parse a single DNS query.
239 ///
240 /// Arguments are suitable for using with call!:
241 ///
242 /// call!(complete_dns_message_buffer)
243 pub fn dns_parse_query<'a>(input: &'a [u8],
244 message: &'a [u8])
245 -> IResult<&'a [u8], DNSQueryEntry> {
246 do_parse!(
247 input,
248 name: call!(dns_parse_name, message) >>
249 rrtype: be_u16 >>
250 rrclass: be_u16 >>
251 (
252 DNSQueryEntry{
253 name: name,
254 rrtype: rrtype,
255 rrclass: rrclass,
256 }
257 )
258 )
259 }
260
261 fn dns_parse_rdata_a<'a>(input: &'a [u8]) -> IResult<&'a [u8], DNSRData> {
262 rest(input).map(|(input, data)| (input, DNSRData::A(data.to_vec())))
263 }
264
265 fn dns_parse_rdata_aaaa<'a>(input: &'a [u8]) -> IResult<&'a [u8], DNSRData> {
266 rest(input).map(|(input, data)| (input, DNSRData::AAAA(data.to_vec())))
267 }
268
269 fn dns_parse_rdata_cname<'a>(input: &'a [u8], message: &'a [u8])
270 -> IResult<&'a [u8], DNSRData> {
271 dns_parse_name(input, message).map(|(input, name)|
272 (input, DNSRData::CNAME(name)))
273 }
274
275 fn dns_parse_rdata_ns<'a>(input: &'a [u8], message: &'a [u8])
276 -> IResult<&'a [u8], DNSRData> {
277 dns_parse_name(input, message).map(|(input, name)|
278 (input, DNSRData::NS(name)))
279 }
280
281 fn dns_parse_rdata_ptr<'a>(input: &'a [u8], message: &'a [u8])
282 -> IResult<&'a [u8], DNSRData> {
283 dns_parse_name(input, message).map(|(input, name)|
284 (input, DNSRData::PTR(name)))
285 }
286
287 fn dns_parse_rdata_soa<'a>(input: &'a [u8], message: &'a [u8])
288 -> IResult<&'a [u8], DNSRData> {
289 do_parse!(
290 input,
291 mname: call!(dns_parse_name, message) >>
292 rname: call!(dns_parse_name, message) >>
293 serial: be_u32 >>
294 refresh: be_u32 >>
295 retry: be_u32 >>
296 expire: be_u32 >>
297 minimum: be_u32 >>
298 (DNSRData::SOA(DNSRDataSOA{
299 mname,
300 rname,
301 serial,
302 refresh,
303 retry,
304 expire,
305 minimum,
306 }))
307 )
308 }
309
310 fn dns_parse_rdata_mx<'a>(input: &'a [u8], message: &'a [u8])
311 -> IResult<&'a [u8], DNSRData> {
312 // For MX we skip over the preference field before
313 // parsing out the name.
314 do_parse!(
315 input,
316 be_u16 >>
317 name: call!(dns_parse_name, message) >>
318 (DNSRData::MX(name))
319 )
320 }
321
322 fn dns_parse_rdata_srv<'a>(input: &'a [u8], message: &'a [u8])
323 -> IResult<&'a [u8], DNSRData> {
324 do_parse!(
325 input,
326 priority: be_u16 >>
327 weight: be_u16 >>
328 port: be_u16 >>
329 target: call!(dns_parse_name, message) >>
330 (DNSRData::SRV(DNSRDataSRV{
331 priority,
332 weight,
333 port,
334 target,
335 }))
336 )
337 }
338
339 fn dns_parse_rdata_txt<'a>(input: &'a [u8])
340 -> IResult<&'a [u8], DNSRData> {
341 do_parse!(
342 input,
343 len: be_u8 >>
344 txt: take!(len) >>
345 (DNSRData::TXT(txt.to_vec()))
346 )
347 }
348
349 fn dns_parse_rdata_null<'a>(input: &'a [u8])
350 -> IResult<&'a [u8], DNSRData> {
351 do_parse!(
352 input,
353 data: take!(input.len()) >>
354 (DNSRData::NULL(data.to_vec()))
355 )
356 }
357
358 fn dns_parse_rdata_sshfp<'a>(input: &'a [u8])
359 -> IResult<&'a [u8], DNSRData> {
360 do_parse!(
361 input,
362 algo: be_u8 >>
363 fp_type: be_u8 >>
364 fingerprint: call!(rest) >>
365 (DNSRData::SSHFP(DNSRDataSSHFP{
366 algo,
367 fp_type,
368 fingerprint: fingerprint.to_vec()
369 }))
370 )
371 }
372
373 fn dns_parse_rdata_unknown<'a>(input: &'a [u8])
374 -> IResult<&'a [u8], DNSRData> {
375 rest(input).map(|(input, data)| (input, DNSRData::Unknown(data.to_vec())))
376 }
377
378 pub fn dns_parse_rdata<'a>(input: &'a [u8], message: &'a [u8], rrtype: u16)
379 -> IResult<&'a [u8], DNSRData>
380 {
381 match rrtype {
382 DNS_RECORD_TYPE_A => dns_parse_rdata_a(input),
383 DNS_RECORD_TYPE_AAAA => dns_parse_rdata_aaaa(input),
384 DNS_RECORD_TYPE_CNAME => dns_parse_rdata_cname(input, message),
385 DNS_RECORD_TYPE_PTR => dns_parse_rdata_ptr(input, message),
386 DNS_RECORD_TYPE_SOA => dns_parse_rdata_soa(input, message),
387 DNS_RECORD_TYPE_MX => dns_parse_rdata_mx(input, message),
388 DNS_RECORD_TYPE_NS => dns_parse_rdata_ns(input, message),
389 DNS_RECORD_TYPE_TXT => dns_parse_rdata_txt(input),
390 DNS_RECORD_TYPE_NULL => dns_parse_rdata_null(input),
391 DNS_RECORD_TYPE_SSHFP => dns_parse_rdata_sshfp(input),
392 DNS_RECORD_TYPE_SRV => dns_parse_rdata_srv(input, message),
393 _ => dns_parse_rdata_unknown(input),
394 }
395 }
396
397 /// Parse a DNS request.
398 pub fn dns_parse_request<'a>(input: &'a [u8]) -> IResult<&[u8], DNSRequest> {
399 do_parse!(
400 input,
401 header: dns_parse_header >>
402 queries: count!(call!(dns_parse_query, input),
403 header.questions as usize) >>
404 (
405 DNSRequest{
406 header: header,
407 queries: queries,
408 }
409 )
410 )
411 }
412
413 #[cfg(test)]
414 mod tests {
415
416 use crate::dns::dns::{DNSHeader,DNSAnswerEntry};
417 use crate::dns::parser::*;
418
419 /// Parse a simple name with no pointers.
420 #[test]
421 fn test_dns_parse_name() {
422 let buf: &[u8] = &[
423 0x09, 0x63, /* .......c */
424 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2d, 0x63, 0x66, /* lient-cf */
425 0x07, 0x64, 0x72, 0x6f, 0x70, 0x62, 0x6f, 0x78, /* .dropbox */
426 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, /* .com.... */
427 ];
428 let expected_remainder: &[u8] = &[0x00, 0x01, 0x00];
429 let (remainder,name) = dns_parse_name(buf, buf).unwrap();
430 assert_eq!("client-cf.dropbox.com".as_bytes(), &name[..]);
431 assert_eq!(remainder, expected_remainder);
432 }
433
434 /// Test parsing a name with pointers.
435 #[test]
436 fn test_dns_parse_name_with_pointer() {
437 let buf: &[u8] = &[
438 0xd8, 0xcb, 0x8a, 0xed, 0xa1, 0x46, 0x00, 0x15 /* 0 - .....F.. */,
439 0x17, 0x0d, 0x06, 0xf7, 0x08, 0x00, 0x45, 0x00 /* 8 - ......E. */,
440 0x00, 0x7b, 0x71, 0x6e, 0x00, 0x00, 0x39, 0x11 /* 16 - .{qn..9. */,
441 0xf4, 0xd9, 0x08, 0x08, 0x08, 0x08, 0x0a, 0x10 /* 24 - ........ */,
442 0x01, 0x0b, 0x00, 0x35, 0xe1, 0x8e, 0x00, 0x67 /* 32 - ...5...g */,
443 0x60, 0x00, 0xef, 0x08, 0x81, 0x80, 0x00, 0x01 /* 40 - `....... */,
444 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x03, 0x77 /* 48 - .......w */,
445 0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63 /* 56 - ww.suric */,
446 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03 /* 64 - ata-ids. */,
447 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01 /* 72 - org..... */,
448 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00 /* 80 - ........ */,
449 0x0e, 0x0f, 0x00, 0x02, 0xc0, 0x10, 0xc0, 0x10 /* 88 - ........ */,
450 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x2b /* 96 - .......+ */,
451 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x19, 0xc0, 0x10 /* 104 - ....N... */,
452 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x2b /* 112 - .......+ */,
453 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x18, 0x00, 0x00 /* 120 - ....N... */,
454 0x29, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 /* 128 - )....... */,
455 0x00, /* 136 - . */
456 ];
457
458 // The DNS payload starts at offset 42.
459 let message = &buf[42..];
460
461 // The name at offset 54 is the complete name.
462 let start1 = &buf[54..];
463 let res1 = dns_parse_name(start1, message);
464 assert_eq!(res1,
465 Ok((&start1[22..],
466 "www.suricata-ids.org".as_bytes().to_vec())));
467
468 // The second name starts at offset 80, but is just a pointer
469 // to the first.
470 let start2 = &buf[80..];
471 let res2 = dns_parse_name(start2, message);
472 assert_eq!(res2,
473 Ok((&start2[2..],
474 "www.suricata-ids.org".as_bytes().to_vec())));
475
476 // The third name starts at offset 94, but is a pointer to a
477 // portion of the first.
478 let start3 = &buf[94..];
479 let res3 = dns_parse_name(start3, message);
480 assert_eq!(res3,
481 Ok((&start3[2..],
482 "suricata-ids.org".as_bytes().to_vec())));
483
484 // The fourth name starts at offset 110, but is a pointer to a
485 // portion of the first.
486 let start4 = &buf[110..];
487 let res4 = dns_parse_name(start4, message);
488 assert_eq!(res4,
489 Ok((&start4[2..],
490 "suricata-ids.org".as_bytes().to_vec())));
491 }
492
493 #[test]
494 fn test_dns_parse_name_double_pointer() {
495 let buf: &[u8] = &[
496 0xd8, 0xcb, 0x8a, 0xed, 0xa1, 0x46, 0x00, 0x15 /* 0: .....F.. */,
497 0x17, 0x0d, 0x06, 0xf7, 0x08, 0x00, 0x45, 0x00 /* 8: ......E. */,
498 0x00, 0x66, 0x5e, 0x20, 0x40, 0x00, 0x40, 0x11 /* 16: .f^ @.@. */,
499 0xc6, 0x3b, 0x0a, 0x10, 0x01, 0x01, 0x0a, 0x10 /* 24: .;...... */,
500 0x01, 0x0b, 0x00, 0x35, 0xc2, 0x21, 0x00, 0x52 /* 32: ...5.!.R */,
501 0x35, 0xc5, 0x0d, 0x4f, 0x81, 0x80, 0x00, 0x01 /* 40: 5..O.... */,
502 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x05, 0x62 /* 48: .......b */,
503 0x6c, 0x6f, 0x63, 0x6b, 0x07, 0x64, 0x72, 0x6f /* 56: lock.dro */,
504 0x70, 0x62, 0x6f, 0x78, 0x03, 0x63, 0x6f, 0x6d /* 64: pbox.com */,
505 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00 /* 72: ........ */,
506 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00 /* 80: ........ */,
507 0x0b, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x02 /* 88: ..block. */,
508 0x67, 0x31, 0xc0, 0x12, 0xc0, 0x2f, 0x00, 0x01 /* 96: g1.../.. */,
509 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04 /* 104: ........ */,
510 0x2d, 0x3a, 0x46, 0x21 /* 112: -:F! */
511 ];
512
513 // The start of the DNS message in the above packet.
514 let message: &[u8] = &buf[42..];
515
516 // The start of the name we want to parse, 0xc0 0x2f, a
517 // pointer to offset 47 in the message (or 89 in the full
518 // packet).
519 let start: &[u8] = &buf[100..];
520
521 let res = dns_parse_name(start, message);
522 assert_eq!(res,
523 Ok((&start[2..],
524 "block.g1.dropbox.com".as_bytes().to_vec())));
525 }
526
527 #[test]
528 fn test_dns_parse_request() {
529 // DNS request from dig-a-www.suricata-ids.org.pcap.
530 let pkt: &[u8] = &[
531 0x8d, 0x32, 0x01, 0x20, 0x00, 0x01, /* ...2. .. */
532 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x77, /* .......w */
533 0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63, /* ww.suric */
534 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03, /* ata-ids. */
535 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, /* org..... */
536 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x00, /* ..)..... */
537 0x00, 0x00, 0x00 /* ... */
538 ];
539
540 let res = dns_parse_request(pkt);
541 match res {
542 Ok((rem, request)) => {
543
544 // For now we have some remainder data as there is an
545 // additional record type we don't parse yet.
546 assert!(rem.len() > 0);
547
548 assert_eq!(request.header, DNSHeader {
549 tx_id: 0x8d32,
550 flags: 0x0120,
551 questions: 1,
552 answer_rr: 0,
553 authority_rr: 0,
554 additional_rr: 1,
555 });
556
557 assert_eq!(request.queries.len(), 1);
558
559 let query = &request.queries[0];
560 assert_eq!(query.name,
561 "www.suricata-ids.org".as_bytes().to_vec());
562 assert_eq!(query.rrtype, 1);
563 assert_eq!(query.rrclass, 1);
564 }
565 _ => {
566 assert!(false);
567 }
568 }
569 }
570
571 #[test]
572 fn test_dns_parse_response() {
573 // DNS response from dig-a-www.suricata-ids.org.pcap.
574 let pkt: &[u8] = &[
575 0x8d, 0x32, 0x81, 0xa0, 0x00, 0x01, /* ...2.... */
576 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, /* .......w */
577 0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63, /* ww.suric */
578 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03, /* ata-ids. */
579 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, /* org..... */
580 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, /* ........ */
581 0x0d, 0xd8, 0x00, 0x12, 0x0c, 0x73, 0x75, 0x72, /* .....sur */
582 0x69, 0x63, 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, /* icata-id */
583 0x73, 0x03, 0x6f, 0x72, 0x67, 0x00, 0xc0, 0x32, /* s.org..2 */
584 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf4, /* ........ */
585 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x18, 0xc0, 0x32, /* ....N..2 */
586 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf4, /* ........ */
587 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x19 /* ....N. */
588 ];
589
590 let res = dns_parse_response(pkt);
591 match res {
592 Ok((rem, response)) => {
593
594 // The response should be full parsed.
595 assert_eq!(rem.len(), 0);
596
597 assert_eq!(response.header, DNSHeader{
598 tx_id: 0x8d32,
599 flags: 0x81a0,
600 questions: 1,
601 answer_rr: 3,
602 authority_rr: 0,
603 additional_rr: 0,
604 });
605
606 assert_eq!(response.answers.len(), 3);
607
608 let answer1 = &response.answers[0];
609 assert_eq!(answer1.name,
610 "www.suricata-ids.org".as_bytes().to_vec());
611 assert_eq!(answer1.rrtype, 5);
612 assert_eq!(answer1.rrclass, 1);
613 assert_eq!(answer1.ttl, 3544);
614 assert_eq!(answer1.data,
615 DNSRData::CNAME("suricata-ids.org".as_bytes().to_vec()));
616
617 let answer2 = &response.answers[1];
618 assert_eq!(answer2, &DNSAnswerEntry{
619 name: "suricata-ids.org".as_bytes().to_vec(),
620 rrtype: 1,
621 rrclass: 1,
622 ttl: 244,
623 data: DNSRData::A([192, 0, 78, 24].to_vec()),
624 });
625
626 let answer3 = &response.answers[2];
627 assert_eq!(answer3, &DNSAnswerEntry{
628 name: "suricata-ids.org".as_bytes().to_vec(),
629 rrtype: 1,
630 rrclass: 1,
631 ttl: 244,
632 data: DNSRData::A([192, 0, 78, 25].to_vec()),
633 })
634
635 },
636 _ => {
637 assert!(false);
638 }
639 }
640 }
641
642 #[test]
643 fn test_dns_parse_response_nxdomain_soa() {
644 // DNS response with an SOA authority record from
645 // dns-udp-nxdomain-soa.pcap.
646 let pkt: &[u8] = &[
647 0x82, 0x95, 0x81, 0x83, 0x00, 0x01, /* j....... */
648 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x03, 0x64, /* .......d */
649 0x6e, 0x65, 0x04, 0x6f, 0x69, 0x73, 0x66, 0x03, /* ne.oisf. */
650 0x6e, 0x65, 0x74, 0x00, 0x00, 0x01, 0x00, 0x01, /* net..... */
651 0xc0, 0x10, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, /* ........ */
652 0x03, 0x83, 0x00, 0x45, 0x06, 0x6e, 0x73, 0x2d, /* ...E.ns- */
653 0x31, 0x31, 0x30, 0x09, 0x61, 0x77, 0x73, 0x64, /* 110.awsd */
654 0x6e, 0x73, 0x2d, 0x31, 0x33, 0x03, 0x63, 0x6f, /* ns-13.co */
655 0x6d, 0x00, 0x11, 0x61, 0x77, 0x73, 0x64, 0x6e, /* m..awsdn */
656 0x73, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x6d, 0x61, /* s-hostma */
657 0x73, 0x74, 0x65, 0x72, 0x06, 0x61, 0x6d, 0x61, /* ster.ama */
658 0x7a, 0x6f, 0x6e, 0xc0, 0x3b, 0x00, 0x00, 0x00, /* zon.;... */
659 0x01, 0x00, 0x00, 0x1c, 0x20, 0x00, 0x00, 0x03, /* .... ... */
660 0x84, 0x00, 0x12, 0x75, 0x00, 0x00, 0x01, 0x51, /* ...u...Q */
661 0x80, 0x00, 0x00, 0x29, 0x02, 0x00, 0x00, 0x00, /* ...).... */
662 0x00, 0x00, 0x00, 0x00 /* .... */
663 ];
664
665 let res = dns_parse_response(pkt);
666 match res {
667 Ok((rem, response)) => {
668
669 // For now we have some remainder data as there is an
670 // additional record type we don't parse yet.
671 assert!(rem.len() > 0);
672
673 assert_eq!(response.header, DNSHeader{
674 tx_id: 0x8295,
675 flags: 0x8183,
676 questions: 1,
677 answer_rr: 0,
678 authority_rr: 1,
679 additional_rr: 1,
680 });
681
682 assert_eq!(response.authorities.len(), 1);
683
684 let authority = &response.authorities[0];
685 assert_eq!(authority.name,
686 "oisf.net".as_bytes().to_vec());
687 assert_eq!(authority.rrtype, 6);
688 assert_eq!(authority.rrclass, 1);
689 assert_eq!(authority.ttl, 899);
690 assert_eq!(authority.data,
691 DNSRData::SOA(DNSRDataSOA{
692 mname: "ns-110.awsdns-13.com".as_bytes().to_vec(),
693 rname: "awsdns-hostmaster.amazon.com".as_bytes().to_vec(),
694 serial: 1,
695 refresh: 7200,
696 retry: 900,
697 expire: 1209600,
698 minimum: 86400,
699 }));
700 },
701 _ => {
702 assert!(false);
703 }
704 }
705 }
706
707
708 #[test]
709 fn test_dns_parse_response_null() {
710 // DNS response with a NULL record from
711 // https://redmine.openinfosecfoundation.org/attachments/2062
712
713 let pkt: &[u8] = &[
714 0x12, 0xb0, 0x84, 0x00, 0x00, 0x01, 0x00, 0x01, /* ........ */
715 0x00, 0x00, 0x00, 0x00, 0x0b, 0x76, 0x61, 0x61, /* .....vaa */
716 0x61, 0x61, 0x6b, 0x61, 0x72, 0x64, 0x6c, 0x69, /* aakardli */
717 0x06, 0x70, 0x69, 0x72, 0x61, 0x74, 0x65, 0x03, /* .pirate. */
718 0x73, 0x65, 0x61, 0x00, 0x00, 0x0a, 0x00, 0x01, /* sea..... */
719 0xc0, 0x0c, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x00, /* ........ */
720 0x00, 0x00, 0x00, 0x09, 0x56, 0x41, 0x43, 0x4b, /* ....VACK */
721 0x44, 0x03, 0xc5, 0xe9, 0x01, /* D.... */
722 ];
723
724 let res = dns_parse_response(pkt);
725 match res {
726 Ok((rem, response)) => {
727 // The response should be fully parsed.
728 assert_eq!(rem.len(), 0);
729
730 assert_eq!(response.header, DNSHeader {
731 tx_id: 0x12b0,
732 flags: 0x8400,
733 questions: 1,
734 answer_rr: 1,
735 authority_rr: 0,
736 additional_rr: 0,
737 });
738
739 assert_eq!(response.queries.len(), 1);
740 let query = &response.queries[0];
741 assert_eq!(query.name,
742 "vaaaakardli.pirate.sea".as_bytes().to_vec());
743 assert_eq!(query.rrtype, DNS_RECORD_TYPE_NULL);
744 assert_eq!(query.rrclass, 1);
745
746 assert_eq!(response.answers.len(), 1);
747
748 let answer = &response.answers[0];
749 assert_eq!(answer.name,
750 "vaaaakardli.pirate.sea".as_bytes().to_vec());
751 assert_eq!(answer.rrtype, DNS_RECORD_TYPE_NULL);
752 assert_eq!(answer.rrclass, 1);
753 assert_eq!(answer.ttl, 0);
754 assert_eq!(answer.data, DNSRData::NULL(vec![
755 0x56, 0x41, 0x43, 0x4b, /* VACK */
756 0x44, 0x03, 0xc5, 0xe9, 0x01, /* D.... */
757 ]));
758 },
759 _ => {
760 assert!(false);
761 }
762 }
763 }
764
765
766 #[test]
767 fn test_dns_parse_rdata_sshfp() {
768 // Dummy data since we don't have a pcap sample.
769 let data: &[u8] = &[
770 // algo: DSS
771 0x02,
772 // fp_type: SHA-1
773 0x01,
774 // fingerprint: 123456789abcdef67890123456789abcdef67890
775 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf6, 0x78, 0x90,
776 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf6, 0x78, 0x90
777 ];
778
779 let res = dns_parse_rdata_sshfp(data);
780 match res {
781 Ok((rem, rdata)) => {
782
783 // The data should be fully parsed.
784 assert_eq!(rem.len(), 0);
785
786 match rdata {
787 DNSRData::SSHFP(sshfp) => {
788 assert_eq!(sshfp.algo, 2);
789 assert_eq!(sshfp.fp_type, 1);
790 assert_eq!(sshfp.fingerprint, &data[2..]);
791 },
792 _ => {
793 assert!(false);
794 }
795 }
796 },
797 _ => {
798 assert!(false);
799 }
800 }
801 }
802
803 }