/* Copyright (C) 2017 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free * Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * version 2 along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ //! Nom parsers for DNS. use crate::dns::dns::*; use nom7::combinator::{complete, rest}; use nom7::error::ErrorKind; use nom7::multi::{count, length_data, many_m_n}; use nom7::number::streaming::{be_u16, be_u32, be_u8}; use nom7::{error_position, Err, IResult}; // Parse a DNS header. pub fn dns_parse_header(i: &[u8]) -> IResult<&[u8], DNSHeader> { let (i, tx_id) = be_u16(i)?; let (i, flags) = be_u16(i)?; let (i, questions) = be_u16(i)?; let (i, answer_rr) = be_u16(i)?; let (i, authority_rr) = be_u16(i)?; let (i, additional_rr) = be_u16(i)?; Ok(( i, DNSHeader { tx_id, flags, questions, answer_rr, authority_rr, additional_rr, }, )) } /// Parse a DNS name. /// /// Parameters: /// start: the start of the name /// message: the complete message that start is a part of with the DNS header pub fn dns_parse_name<'b>(start: &'b [u8], message: &'b [u8]) -> IResult<&'b [u8], Vec> { let mut pos = start; let mut pivot = start; let mut name: Vec = Vec::with_capacity(32); let mut count = 0; loop { if pos.is_empty() { break; } let len = pos[0]; if len == 0x00 { pos = &pos[1..]; break; } else if len & 0b1100_0000 == 0 { let (rem, label) = length_data(be_u8)(pos)?; if !name.is_empty() { name.push(b'.'); } name.extend(label); pos = rem; } else if len & 0b1100_0000 == 0b1100_0000 { let (rem, leader) = be_u16(pos)?; let offset = usize::from(leader) & 0x3fff; if offset > message.len() { return Err(Err::Error(error_position!(pos, ErrorKind::OctDigit))); } pos = &message[offset..]; if pivot == start { pivot = rem; } } else { return Err(Err::Error(error_position!(pos, ErrorKind::OctDigit))); } // Return error if we've looped a certain number of times. count += 1; if count > 255 { return Err(Err::Error(error_position!(pos, ErrorKind::OctDigit))); } } // If we followed a pointer we return the position after the first // pointer followed. Is there a better way to see if these slices // diverged from each other? A straight up comparison would // actually check the contents. if pivot.len() != start.len() { return Ok((pivot, name)); } return Ok((pos, name)); } /// Parse answer entries. /// /// In keeping with the C implementation, answer values that can /// contain multiple answers get expanded into their own answer /// records. An example of this is a TXT record with multiple strings /// in it - each string will be expanded to its own answer record. /// /// This function could be a made a whole lot simpler if we logged a /// multi-string TXT entry as a single quote string, similar to the /// output of dig. Something to consider for a future version. fn dns_parse_answer<'a>( slice: &'a [u8], message: &'a [u8], count: usize, ) -> IResult<&'a [u8], Vec> { let mut answers = Vec::new(); let mut input = slice; struct Answer<'a> { name: Vec, rrtype: u16, rrclass: u16, ttl: u32, data: &'a [u8], } fn subparser<'a>(i: &'a [u8], message: &'a [u8]) -> IResult<&'a [u8], Answer<'a>> { let (i, name) = dns_parse_name(i, message)?; let (i, rrtype) = be_u16(i)?; let (i, rrclass) = be_u16(i)?; let (i, ttl) = be_u32(i)?; let (i, data) = length_data(be_u16)(i)?; let answer = Answer { name, rrtype, rrclass, ttl, data, }; Ok((i, answer)) } for _ in 0..count { match subparser(input, message) { Ok((rem, val)) => { let n = match val.rrtype { DNS_RECORD_TYPE_TXT => { // For TXT records we need to run the parser // multiple times. Set n high, to the maximum // value based on a max txt side of 65535, but // taking into considering that strings need // to be quoted, so half that. 32767 } _ => { // For all other types we only want to run the // parser once, so set n to 1. 1 } }; let result: IResult<&'a [u8], Vec> = many_m_n(1, n, complete(|b| dns_parse_rdata(b, message, val.rrtype)))(val.data); match result { Ok((_, rdatas)) => { for rdata in rdatas { answers.push(DNSAnswerEntry { name: val.name.clone(), rrtype: val.rrtype, rrclass: val.rrclass, ttl: val.ttl, data: rdata, }); } } Err(e) => { return Err(e); } } input = rem; } Err(e) => { return Err(e); } } } return Ok((input, answers)); } pub fn dns_parse_response_body<'a>( i: &'a [u8], message: &'a [u8], header: DNSHeader, ) -> IResult<&'a [u8], DNSResponse> { let (i, queries) = count(|b| dns_parse_query(b, message), header.questions as usize)(i)?; let (i, answers) = dns_parse_answer(i, message, header.answer_rr as usize)?; let (i, authorities) = dns_parse_answer(i, message, header.authority_rr as usize)?; Ok(( i, DNSResponse { header, queries, answers, authorities, }, )) } /// Parse a single DNS query. /// /// Arguments are suitable for using with call!: /// /// call!(complete_dns_message_buffer) pub fn dns_parse_query<'a>(input: &'a [u8], message: &'a [u8]) -> IResult<&'a [u8], DNSQueryEntry> { let i = input; let (i, name) = dns_parse_name(i, message)?; let (i, rrtype) = be_u16(i)?; let (i, rrclass) = be_u16(i)?; Ok(( i, DNSQueryEntry { name, rrtype, rrclass, }, )) } fn dns_parse_rdata_a(input: &[u8]) -> IResult<&[u8], DNSRData> { rest(input).map(|(input, data)| (input, DNSRData::A(data.to_vec()))) } fn dns_parse_rdata_aaaa(input: &[u8]) -> IResult<&[u8], DNSRData> { rest(input).map(|(input, data)| (input, DNSRData::AAAA(data.to_vec()))) } fn dns_parse_rdata_cname<'a>(input: &'a [u8], message: &'a [u8]) -> IResult<&'a [u8], DNSRData> { dns_parse_name(input, message).map(|(input, name)| (input, DNSRData::CNAME(name))) } fn dns_parse_rdata_ns<'a>(input: &'a [u8], message: &'a [u8]) -> IResult<&'a [u8], DNSRData> { dns_parse_name(input, message).map(|(input, name)| (input, DNSRData::NS(name))) } fn dns_parse_rdata_ptr<'a>(input: &'a [u8], message: &'a [u8]) -> IResult<&'a [u8], DNSRData> { dns_parse_name(input, message).map(|(input, name)| (input, DNSRData::PTR(name))) } fn dns_parse_rdata_soa<'a>(input: &'a [u8], message: &'a [u8]) -> IResult<&'a [u8], DNSRData> { let i = input; let (i, mname) = dns_parse_name(i, message)?; let (i, rname) = dns_parse_name(i, message)?; let (i, serial) = be_u32(i)?; let (i, refresh) = be_u32(i)?; let (i, retry) = be_u32(i)?; let (i, expire) = be_u32(i)?; let (i, minimum) = be_u32(i)?; Ok(( i, DNSRData::SOA(DNSRDataSOA { mname, rname, serial, refresh, retry, expire, minimum, }), )) } fn dns_parse_rdata_mx<'a>(input: &'a [u8], message: &'a [u8]) -> IResult<&'a [u8], DNSRData> { // For MX we skip over the preference field before // parsing out the name. let (i, _) = be_u16(input)?; let (i, name) = dns_parse_name(i, message)?; Ok((i, DNSRData::MX(name))) } fn dns_parse_rdata_srv<'a>(input: &'a [u8], message: &'a [u8]) -> IResult<&'a [u8], DNSRData> { let i = input; let (i, priority) = be_u16(i)?; let (i, weight) = be_u16(i)?; let (i, port) = be_u16(i)?; let (i, target) = dns_parse_name(i, message)?; Ok(( i, DNSRData::SRV(DNSRDataSRV { priority, weight, port, target, }), )) } fn dns_parse_rdata_txt(input: &[u8]) -> IResult<&[u8], DNSRData> { let (i, txt) = length_data(be_u8)(input)?; Ok((i, DNSRData::TXT(txt.to_vec()))) } fn dns_parse_rdata_null(input: &[u8]) -> IResult<&[u8], DNSRData> { rest(input).map(|(input, data)| (input, DNSRData::NULL(data.to_vec()))) } fn dns_parse_rdata_sshfp(input: &[u8]) -> IResult<&[u8], DNSRData> { let i = input; let (i, algo) = be_u8(i)?; let (i, fp_type) = be_u8(i)?; let fingerprint = i; Ok(( &[], DNSRData::SSHFP(DNSRDataSSHFP { algo, fp_type, fingerprint: fingerprint.to_vec(), }), )) } fn dns_parse_rdata_unknown(input: &[u8]) -> IResult<&[u8], DNSRData> { rest(input).map(|(input, data)| (input, DNSRData::Unknown(data.to_vec()))) } pub fn dns_parse_rdata<'a>( input: &'a [u8], message: &'a [u8], rrtype: u16, ) -> IResult<&'a [u8], DNSRData> { match rrtype { DNS_RECORD_TYPE_A => dns_parse_rdata_a(input), DNS_RECORD_TYPE_AAAA => dns_parse_rdata_aaaa(input), DNS_RECORD_TYPE_CNAME => dns_parse_rdata_cname(input, message), DNS_RECORD_TYPE_PTR => dns_parse_rdata_ptr(input, message), DNS_RECORD_TYPE_SOA => dns_parse_rdata_soa(input, message), DNS_RECORD_TYPE_MX => dns_parse_rdata_mx(input, message), DNS_RECORD_TYPE_NS => dns_parse_rdata_ns(input, message), DNS_RECORD_TYPE_TXT => dns_parse_rdata_txt(input), DNS_RECORD_TYPE_NULL => dns_parse_rdata_null(input), DNS_RECORD_TYPE_SSHFP => dns_parse_rdata_sshfp(input), DNS_RECORD_TYPE_SRV => dns_parse_rdata_srv(input, message), _ => dns_parse_rdata_unknown(input), } } /// Parse a DNS request. pub fn dns_parse_request(input: &[u8]) -> IResult<&[u8], DNSRequest> { let i = input; let (i, header) = dns_parse_header(i)?; dns_parse_request_body(i, input, header) } pub fn dns_parse_request_body<'a>( input: &'a [u8], message: &'a [u8], header: DNSHeader, ) -> IResult<&'a [u8], DNSRequest> { let i = input; let (i, queries) = count(|b| dns_parse_query(b, message), header.questions as usize)(i)?; Ok((i, DNSRequest { header, queries })) } #[cfg(test)] mod tests { use crate::dns::dns::{DNSAnswerEntry, DNSHeader}; use crate::dns::parser::*; /// Parse a simple name with no pointers. #[test] fn test_dns_parse_name() { let buf: &[u8] = &[ 0x09, 0x63, /* .......c */ 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2d, 0x63, 0x66, /* lient-cf */ 0x07, 0x64, 0x72, 0x6f, 0x70, 0x62, 0x6f, 0x78, /* .dropbox */ 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, /* .com.... */ ]; let expected_remainder: &[u8] = &[0x00, 0x01, 0x00]; let (remainder, name) = dns_parse_name(buf, buf).unwrap(); assert_eq!("client-cf.dropbox.com".as_bytes(), &name[..]); assert_eq!(remainder, expected_remainder); } /// Test parsing a name with pointers. #[test] fn test_dns_parse_name_with_pointer() { let buf: &[u8] = &[ 0xd8, 0xcb, 0x8a, 0xed, 0xa1, 0x46, 0x00, 0x15, /* 0 - .....F.. */ 0x17, 0x0d, 0x06, 0xf7, 0x08, 0x00, 0x45, 0x00, /* 8 - ......E. */ 0x00, 0x7b, 0x71, 0x6e, 0x00, 0x00, 0x39, 0x11, /* 16 - .{qn..9. */ 0xf4, 0xd9, 0x08, 0x08, 0x08, 0x08, 0x0a, 0x10, /* 24 - ........ */ 0x01, 0x0b, 0x00, 0x35, 0xe1, 0x8e, 0x00, 0x67, /* 32 - ...5...g */ 0x60, 0x00, 0xef, 0x08, 0x81, 0x80, 0x00, 0x01, /* 40 - `....... */ 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x03, 0x77, /* 48 - .......w */ 0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63, /* 56 - ww.suric */ 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03, /* 64 - ata-ids. */ 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, /* 72 - org..... */ 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, /* 80 - ........ */ 0x0e, 0x0f, 0x00, 0x02, 0xc0, 0x10, 0xc0, 0x10, /* 88 - ........ */ 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x2b, /* 96 - .......+ */ 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x19, 0xc0, 0x10, /* 104 - ....N... */ 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x2b, /* 112 - .......+ */ 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x18, 0x00, 0x00, /* 120 - ....N... */ 0x29, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 128 - )....... */ 0x00, /* 136 - . */ ]; // The DNS payload starts at offset 42. let message = &buf[42..]; // The name at offset 54 is the complete name. let start1 = &buf[54..]; let res1 = dns_parse_name(start1, message); assert_eq!( res1, Ok((&start1[22..], "www.suricata-ids.org".as_bytes().to_vec())) ); // The second name starts at offset 80, but is just a pointer // to the first. let start2 = &buf[80..]; let res2 = dns_parse_name(start2, message); assert_eq!( res2, Ok((&start2[2..], "www.suricata-ids.org".as_bytes().to_vec())) ); // The third name starts at offset 94, but is a pointer to a // portion of the first. let start3 = &buf[94..]; let res3 = dns_parse_name(start3, message); assert_eq!( res3, Ok((&start3[2..], "suricata-ids.org".as_bytes().to_vec())) ); // The fourth name starts at offset 110, but is a pointer to a // portion of the first. let start4 = &buf[110..]; let res4 = dns_parse_name(start4, message); assert_eq!( res4, Ok((&start4[2..], "suricata-ids.org".as_bytes().to_vec())) ); } #[test] fn test_dns_parse_name_double_pointer() { let buf: &[u8] = &[ 0xd8, 0xcb, 0x8a, 0xed, 0xa1, 0x46, 0x00, 0x15, /* 0: .....F.. */ 0x17, 0x0d, 0x06, 0xf7, 0x08, 0x00, 0x45, 0x00, /* 8: ......E. */ 0x00, 0x66, 0x5e, 0x20, 0x40, 0x00, 0x40, 0x11, /* 16: .f^ @.@. */ 0xc6, 0x3b, 0x0a, 0x10, 0x01, 0x01, 0x0a, 0x10, /* 24: .;...... */ 0x01, 0x0b, 0x00, 0x35, 0xc2, 0x21, 0x00, 0x52, /* 32: ...5.!.R */ 0x35, 0xc5, 0x0d, 0x4f, 0x81, 0x80, 0x00, 0x01, /* 40: 5..O.... */ 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x05, 0x62, /* 48: .......b */ 0x6c, 0x6f, 0x63, 0x6b, 0x07, 0x64, 0x72, 0x6f, /* 56: lock.dro */ 0x70, 0x62, 0x6f, 0x78, 0x03, 0x63, 0x6f, 0x6d, /* 64: pbox.com */ 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, /* 72: ........ */ 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, /* 80: ........ */ 0x0b, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x02, /* 88: ..block. */ 0x67, 0x31, 0xc0, 0x12, 0xc0, 0x2f, 0x00, 0x01, /* 96: g1.../.. */ 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, /* 104: ........ */ 0x2d, 0x3a, 0x46, 0x21, /* 112: -:F! */ ]; // The start of the DNS message in the above packet. let message: &[u8] = &buf[42..]; // The start of the name we want to parse, 0xc0 0x2f, a // pointer to offset 47 in the message (or 89 in the full // packet). let start: &[u8] = &buf[100..]; let res = dns_parse_name(start, message); assert_eq!( res, Ok((&start[2..], "block.g1.dropbox.com".as_bytes().to_vec())) ); } #[test] fn test_dns_parse_request() { // DNS request from dig-a-www.suricata-ids.org.pcap. let pkt: &[u8] = &[ 0x8d, 0x32, 0x01, 0x20, 0x00, 0x01, /* ...2. .. */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x77, /* .......w */ 0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63, /* ww.suric */ 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03, /* ata-ids. */ 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, /* org..... */ 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x00, /* ..)..... */ 0x00, 0x00, 0x00, /* ... */ ]; let res = dns_parse_request(pkt); match res { Ok((rem, request)) => { // For now we have some remainder data as there is an // additional record type we don't parse yet. assert!(!rem.is_empty()); assert_eq!( request.header, DNSHeader { tx_id: 0x8d32, flags: 0x0120, questions: 1, answer_rr: 0, authority_rr: 0, additional_rr: 1, } ); assert_eq!(request.queries.len(), 1); let query = &request.queries[0]; assert_eq!(query.name, "www.suricata-ids.org".as_bytes().to_vec()); assert_eq!(query.rrtype, 1); assert_eq!(query.rrclass, 1); } _ => { assert!(false); } } } /// Parse a DNS response. fn dns_parse_response(message: &[u8]) -> IResult<&[u8], DNSResponse> { let i = message; let (i, header) = dns_parse_header(i)?; dns_parse_response_body(i, message, header) } #[test] fn test_dns_parse_response() { // DNS response from dig-a-www.suricata-ids.org.pcap. let pkt: &[u8] = &[ 0x8d, 0x32, 0x81, 0xa0, 0x00, 0x01, /* ...2.... */ 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, /* .......w */ 0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63, /* ww.suric */ 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03, /* ata-ids. */ 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, /* org..... */ 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, /* ........ */ 0x0d, 0xd8, 0x00, 0x12, 0x0c, 0x73, 0x75, 0x72, /* .....sur */ 0x69, 0x63, 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, /* icata-id */ 0x73, 0x03, 0x6f, 0x72, 0x67, 0x00, 0xc0, 0x32, /* s.org..2 */ 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf4, /* ........ */ 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x18, 0xc0, 0x32, /* ....N..2 */ 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf4, /* ........ */ 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x19, /* ....N. */ ]; let res = dns_parse_response(pkt); match res { Ok((rem, response)) => { // The response should be full parsed. assert_eq!(rem.len(), 0); assert_eq!( response.header, DNSHeader { tx_id: 0x8d32, flags: 0x81a0, questions: 1, answer_rr: 3, authority_rr: 0, additional_rr: 0, } ); assert_eq!(response.answers.len(), 3); let answer1 = &response.answers[0]; assert_eq!(answer1.name, "www.suricata-ids.org".as_bytes().to_vec()); assert_eq!(answer1.rrtype, 5); assert_eq!(answer1.rrclass, 1); assert_eq!(answer1.ttl, 3544); assert_eq!( answer1.data, DNSRData::CNAME("suricata-ids.org".as_bytes().to_vec()) ); let answer2 = &response.answers[1]; assert_eq!( answer2, &DNSAnswerEntry { name: "suricata-ids.org".as_bytes().to_vec(), rrtype: 1, rrclass: 1, ttl: 244, data: DNSRData::A([192, 0, 78, 24].to_vec()), } ); let answer3 = &response.answers[2]; assert_eq!( answer3, &DNSAnswerEntry { name: "suricata-ids.org".as_bytes().to_vec(), rrtype: 1, rrclass: 1, ttl: 244, data: DNSRData::A([192, 0, 78, 25].to_vec()), } ) } _ => { assert!(false); } } } #[test] fn test_dns_parse_response_nxdomain_soa() { // DNS response with an SOA authority record from // dns-udp-nxdomain-soa.pcap. let pkt: &[u8] = &[ 0x82, 0x95, 0x81, 0x83, 0x00, 0x01, /* j....... */ 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x03, 0x64, /* .......d */ 0x6e, 0x65, 0x04, 0x6f, 0x69, 0x73, 0x66, 0x03, /* ne.oisf. */ 0x6e, 0x65, 0x74, 0x00, 0x00, 0x01, 0x00, 0x01, /* net..... */ 0xc0, 0x10, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, /* ........ */ 0x03, 0x83, 0x00, 0x45, 0x06, 0x6e, 0x73, 0x2d, /* ...E.ns- */ 0x31, 0x31, 0x30, 0x09, 0x61, 0x77, 0x73, 0x64, /* 110.awsd */ 0x6e, 0x73, 0x2d, 0x31, 0x33, 0x03, 0x63, 0x6f, /* ns-13.co */ 0x6d, 0x00, 0x11, 0x61, 0x77, 0x73, 0x64, 0x6e, /* m..awsdn */ 0x73, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x6d, 0x61, /* s-hostma */ 0x73, 0x74, 0x65, 0x72, 0x06, 0x61, 0x6d, 0x61, /* ster.ama */ 0x7a, 0x6f, 0x6e, 0xc0, 0x3b, 0x00, 0x00, 0x00, /* zon.;... */ 0x01, 0x00, 0x00, 0x1c, 0x20, 0x00, 0x00, 0x03, /* .... ... */ 0x84, 0x00, 0x12, 0x75, 0x00, 0x00, 0x01, 0x51, /* ...u...Q */ 0x80, 0x00, 0x00, 0x29, 0x02, 0x00, 0x00, 0x00, /* ...).... */ 0x00, 0x00, 0x00, 0x00, /* .... */ ]; let res = dns_parse_response(pkt); match res { Ok((rem, response)) => { // For now we have some remainder data as there is an // additional record type we don't parse yet. assert!(!rem.is_empty()); assert_eq!( response.header, DNSHeader { tx_id: 0x8295, flags: 0x8183, questions: 1, answer_rr: 0, authority_rr: 1, additional_rr: 1, } ); assert_eq!(response.authorities.len(), 1); let authority = &response.authorities[0]; assert_eq!(authority.name, "oisf.net".as_bytes().to_vec()); assert_eq!(authority.rrtype, 6); assert_eq!(authority.rrclass, 1); assert_eq!(authority.ttl, 899); assert_eq!( authority.data, DNSRData::SOA(DNSRDataSOA { mname: "ns-110.awsdns-13.com".as_bytes().to_vec(), rname: "awsdns-hostmaster.amazon.com".as_bytes().to_vec(), serial: 1, refresh: 7200, retry: 900, expire: 1209600, minimum: 86400, }) ); } _ => { assert!(false); } } } #[test] fn test_dns_parse_response_null() { // DNS response with a NULL record from // https://redmine.openinfosecfoundation.org/attachments/2062 let pkt: &[u8] = &[ 0x12, 0xb0, 0x84, 0x00, 0x00, 0x01, 0x00, 0x01, /* ........ */ 0x00, 0x00, 0x00, 0x00, 0x0b, 0x76, 0x61, 0x61, /* .....vaa */ 0x61, 0x61, 0x6b, 0x61, 0x72, 0x64, 0x6c, 0x69, /* aakardli */ 0x06, 0x70, 0x69, 0x72, 0x61, 0x74, 0x65, 0x03, /* .pirate. */ 0x73, 0x65, 0x61, 0x00, 0x00, 0x0a, 0x00, 0x01, /* sea..... */ 0xc0, 0x0c, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x00, /* ........ */ 0x00, 0x00, 0x00, 0x09, 0x56, 0x41, 0x43, 0x4b, /* ....VACK */ 0x44, 0x03, 0xc5, 0xe9, 0x01, /* D.... */ ]; let res = dns_parse_response(pkt); match res { Ok((rem, response)) => { // The response should be fully parsed. assert_eq!(rem.len(), 0); assert_eq!( response.header, DNSHeader { tx_id: 0x12b0, flags: 0x8400, questions: 1, answer_rr: 1, authority_rr: 0, additional_rr: 0, } ); assert_eq!(response.queries.len(), 1); let query = &response.queries[0]; assert_eq!(query.name, "vaaaakardli.pirate.sea".as_bytes().to_vec()); assert_eq!(query.rrtype, DNS_RECORD_TYPE_NULL); assert_eq!(query.rrclass, 1); assert_eq!(response.answers.len(), 1); let answer = &response.answers[0]; assert_eq!(answer.name, "vaaaakardli.pirate.sea".as_bytes().to_vec()); assert_eq!(answer.rrtype, DNS_RECORD_TYPE_NULL); assert_eq!(answer.rrclass, 1); assert_eq!(answer.ttl, 0); assert_eq!( answer.data, DNSRData::NULL(vec![ 0x56, 0x41, 0x43, 0x4b, /* VACK */ 0x44, 0x03, 0xc5, 0xe9, 0x01, /* D.... */ ]) ); } _ => { assert!(false); } } } #[test] fn test_dns_parse_rdata_sshfp() { // Dummy data since we don't have a pcap sample. let data: &[u8] = &[ // algo: DSS 0x02, // fp_type: SHA-1 0x01, // fingerprint: 123456789abcdef67890123456789abcdef67890 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf6, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf6, 0x78, 0x90, ]; let res = dns_parse_rdata_sshfp(data); match res { Ok((rem, rdata)) => { // The data should be fully parsed. assert_eq!(rem.len(), 0); match rdata { DNSRData::SSHFP(sshfp) => { assert_eq!(sshfp.algo, 2); assert_eq!(sshfp.fp_type, 1); assert_eq!(sshfp.fingerprint, &data[2..]); } _ => { assert!(false); } } } _ => { assert!(false); } } } #[test] fn test_dns_parse_rdata_srv() { /* ; <<>> DiG 9.11.5-P4-5.1+deb10u2-Debian <<>> _sip._udp.sip.voice.google.com SRV ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1524 ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 3 [...] ;; ANSWER SECTION: _sip._udp.sip.voice.google.com. 300 IN SRV 10 1 5060 sip-anycast-1.voice.google.com. _sip._udp.sip.voice.google.com. 300 IN SRV 20 1 5060 sip-anycast-2.voice.google.com. [...] ;; Query time: 72 msec ;; MSG SIZE rcvd: 191 */ let pkt: &[u8] = &[ 0xeb, 0x56, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x04, 0x5f, 0x73, 0x69, 0x70, 0x04, 0x5f, 0x75, 0x64, 0x70, 0x03, 0x73, 0x69, 0x70, 0x05, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x21, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x01, 0x13, 0x00, 0x26, 0x00, 0x14, 0x00, 0x01, 0x13, 0xc4, 0x0d, 0x73, 0x69, 0x70, 0x2d, 0x61, 0x6e, 0x79, 0x63, 0x61, 0x73, 0x74, 0x2d, 0x32, 0x05, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0xc0, 0x0c, 0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x01, 0x13, 0x00, 0x26, 0x00, 0x0a, 0x00, 0x01, 0x13, 0xc4, 0x0d, 0x73, 0x69, 0x70, 0x2d, 0x61, 0x6e, 0x79, 0x63, 0x61, 0x73, 0x74, 0x2d, 0x31, 0x05, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, ]; let res = dns_parse_response(pkt); match res { Ok((rem, response)) => { // The data should be fully parsed. assert_eq!(rem.len(), 0); assert_eq!(response.answers.len(), 2); let answer1 = &response.answers[0]; match &answer1.data { DNSRData::SRV(srv) => { assert_eq!(srv.priority, 20); assert_eq!(srv.weight, 1); assert_eq!(srv.port, 5060); assert_eq!( srv.target, "sip-anycast-2.voice.google.com".as_bytes().to_vec() ); } _ => { assert!(false); } } let answer2 = &response.answers[1]; match &answer2.data { DNSRData::SRV(srv) => { assert_eq!(srv.priority, 10); assert_eq!(srv.weight, 1); assert_eq!(srv.port, 5060); assert_eq!( srv.target, "sip-anycast-1.voice.google.com".as_bytes().to_vec() ); } _ => { assert!(false); } } } _ => { assert!(false); } } } }