diff --git a/rust/src/dns/dns.rs b/rust/src/dns/dns.rs index 906f2a0288..b16516b657 100644 --- a/rust/src/dns/dns.rs +++ b/rust/src/dns/dns.rs @@ -144,7 +144,7 @@ pub struct DNSHeader { #[derive(Debug)] pub struct DNSQueryEntry { - pub name: Vec, + pub name: DNSName, pub rrtype: u16, pub rrclass: u16, } @@ -160,9 +160,9 @@ pub struct DNSRDataOPT { #[derive(Debug, PartialEq, Eq)] pub struct DNSRDataSOA { /// Primary name server for this zone - pub mname: Vec, + pub mname: DNSName, /// Authority's mailbox - pub rname: Vec, + pub rname: DNSName, /// Serial version number pub serial: u32, /// Refresh interval (seconds) @@ -194,7 +194,22 @@ pub struct DNSRDataSRV { /// Port pub port: u16, /// Target - pub target: Vec, + pub target: DNSName, +} + +bitflags! { + #[derive(Default)] + pub struct DNSNameFlags: u8 { + const INFINITE_LOOP = 0b0000_0001; + const TRUNCATED = 0b0000_0010; + const LABEL_LIMIT = 0b0000_0100; + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DNSName { + pub value: Vec, + pub flags: DNSNameFlags, } /// Represents RData of various formats @@ -204,10 +219,10 @@ pub enum DNSRData { A(Vec), AAAA(Vec), // RData is a domain name - CNAME(Vec), - PTR(Vec), - MX(Vec), - NS(Vec), + CNAME(DNSName), + PTR(DNSName), + MX(DNSName), + NS(DNSName), // RData is text TXT(Vec), NULL(Vec), @@ -222,7 +237,7 @@ pub enum DNSRData { #[derive(Debug, PartialEq, Eq)] pub struct DNSAnswerEntry { - pub name: Vec, + pub name: DNSName, pub rrtype: u16, pub rrclass: u16, pub ttl: u32, @@ -485,7 +500,9 @@ impl DNSState { tx.tx_data.set_event(event as u8); } - fn parse_request(&mut self, input: &[u8], is_tcp: bool, frame: Option, flow: *const core::Flow,) -> bool { + fn parse_request( + &mut self, input: &[u8], is_tcp: bool, frame: Option, flow: *const core::Flow, + ) -> bool { match dns_parse_request(input) { Ok(mut tx) => { self.tx_id += 1; @@ -542,7 +559,9 @@ impl DNSState { self.parse_response(input, false, frame, flow) } - fn parse_response(&mut self, input: &[u8], is_tcp: bool, frame: Option, flow: *const core::Flow) -> bool { + fn parse_response( + &mut self, input: &[u8], is_tcp: bool, frame: Option, flow: *const core::Flow, + ) -> bool { match dns_parse_response(input) { Ok(mut tx) => { self.tx_id += 1; @@ -908,9 +927,9 @@ pub unsafe extern "C" fn SCDnsTxGetQueryName( if let Some(queries) = queries { if let Some(query) = queries.get(index) { - if !query.name.is_empty() { - *buf = query.name.as_ptr(); - *len = query.name.len() as u32; + if !query.name.value.is_empty() { + *buf = query.name.value.as_ptr(); + *len = query.name.value.len() as u32; return true; } } @@ -933,9 +952,9 @@ pub unsafe extern "C" fn SCDnsTxGetAnswerName( if let Some(answers) = answers { if let Some(answer) = answers.get(index) { - if !answer.name.is_empty() { - *buf = answer.name.as_ptr(); - *len = answer.name.len() as u32; + if !answer.name.value.is_empty() { + *buf = answer.name.value.as_ptr(); + *len = answer.name.value.len() as u32; return true; } } diff --git a/rust/src/dns/log.rs b/rust/src/dns/log.rs index 6b9a70b55a..c22c1082c5 100644 --- a/rust/src/dns/log.rs +++ b/rust/src/dns/log.rs @@ -414,8 +414,8 @@ fn dns_log_opt(opt: &DNSRDataOPT) -> Result { fn dns_log_soa(soa: &DNSRDataSOA) -> Result { let mut js = JsonBuilder::try_new_object()?; - js.set_string_from_bytes("mname", &soa.mname)?; - js.set_string_from_bytes("rname", &soa.rname)?; + js.set_string_from_bytes("mname", &soa.mname.value)?; + js.set_string_from_bytes("rname", &soa.rname.value)?; js.set_uint("serial", soa.serial as u64)?; js.set_uint("refresh", soa.refresh as u64)?; js.set_uint("retry", soa.retry as u64)?; @@ -450,7 +450,7 @@ fn dns_log_srv(srv: &DNSRDataSRV) -> Result { js.set_uint("priority", srv.priority as u64)?; js.set_uint("weight", srv.weight as u64)?; js.set_uint("port", srv.port as u64)?; - js.set_string_from_bytes("name", &srv.target)?; + js.set_string_from_bytes("name", &srv.target.value)?; js.close()?; return Ok(js); @@ -459,7 +459,7 @@ fn dns_log_srv(srv: &DNSRDataSRV) -> Result { fn dns_log_json_answer_detail(answer: &DNSAnswerEntry) -> Result { let mut jsa = JsonBuilder::try_new_object()?; - jsa.set_string_from_bytes("rrname", &answer.name)?; + jsa.set_string_from_bytes("rrname", &answer.name.value)?; jsa.set_string("rrtype", &dns_rrtype_string(answer.rrtype))?; jsa.set_uint("ttl", answer.ttl as u64)?; @@ -467,12 +467,10 @@ fn dns_log_json_answer_detail(answer: &DNSAnswerEntry) -> Result { jsa.set_string("rdata", &dns_print_addr(addr))?; } - DNSRData::CNAME(bytes) - | DNSRData::MX(bytes) - | DNSRData::NS(bytes) - | DNSRData::TXT(bytes) - | DNSRData::NULL(bytes) - | DNSRData::PTR(bytes) => { + DNSRData::CNAME(name) | DNSRData::MX(name) | DNSRData::NS(name) | DNSRData::PTR(name) => { + jsa.set_string_from_bytes("rdata", &name.value)?; + } + DNSRData::TXT(bytes) | DNSRData::NULL(bytes) => { jsa.set_string_from_bytes("rdata", bytes)?; } DNSRData::SOA(soa) => { @@ -529,7 +527,7 @@ fn dns_log_json_answer( js.set_uint("opcode", opcode as u64)?; if let Some(query) = response.queries.first() { - js.set_string_from_bytes("rrname", &query.name)?; + js.set_string_from_bytes("rrname", &query.name.value)?; js.set_string("rrtype", &dns_rrtype_string(query.rrtype))?; } js.set_string("rcode", &dns_rcode_string(header.flags))?; @@ -553,12 +551,19 @@ fn dns_log_json_answer( a.append_string(&dns_print_addr(addr))?; } } - DNSRData::CNAME(bytes) - | DNSRData::MX(bytes) - | DNSRData::NS(bytes) - | DNSRData::TXT(bytes) - | DNSRData::NULL(bytes) - | DNSRData::PTR(bytes) => { + DNSRData::CNAME(name) + | DNSRData::MX(name) + | DNSRData::NS(name) + | DNSRData::PTR(name) => { + if !answer_types.contains_key(&type_string) { + answer_types + .insert(type_string.to_string(), JsonBuilder::try_new_array()?); + } + if let Some(a) = answer_types.get_mut(&type_string) { + a.append_string_from_bytes(&name.value)?; + } + } + DNSRData::TXT(bytes) | DNSRData::NULL(bytes) => { if !answer_types.contains_key(&type_string) { answer_types .insert(type_string.to_string(), JsonBuilder::try_new_array()?); @@ -674,12 +679,19 @@ fn dns_log_json_answers( a.append_string(&dns_print_addr(addr))?; } } - DNSRData::CNAME(bytes) - | DNSRData::MX(bytes) - | DNSRData::NS(bytes) - | DNSRData::TXT(bytes) - | DNSRData::NULL(bytes) - | DNSRData::PTR(bytes) => { + DNSRData::CNAME(name) + | DNSRData::MX(name) + | DNSRData::NS(name) + | DNSRData::PTR(name) => { + if !answer_types.contains_key(&type_string) { + answer_types + .insert(type_string.to_string(), JsonBuilder::try_new_array()?); + } + if let Some(a) = answer_types.get_mut(&type_string) { + a.append_string_from_bytes(&name.value)?; + } + } + DNSRData::TXT(bytes) | DNSRData::NULL(bytes) => { if !answer_types.contains_key(&type_string) { answer_types .insert(type_string.to_string(), JsonBuilder::try_new_array()?); @@ -752,7 +764,7 @@ fn dns_log_query( if dns_log_rrtype_enabled(query.rrtype, flags) { jb.set_string("type", "query")?; jb.set_uint("id", request.header.tx_id as u64)?; - jb.set_string_from_bytes("rrname", &query.name)?; + jb.set_string_from_bytes("rrname", &query.name.value)?; jb.set_string("rrtype", &dns_rrtype_string(query.rrtype))?; jb.set_uint("tx_id", tx.id - 1)?; if request.header.flags & 0x0040 != 0 { @@ -840,9 +852,9 @@ fn log_json(tx: &mut DNSTransaction, flags: u64, jb: &mut JsonBuilder) -> Result for query in &message.queries { if dns_log_rrtype_enabled(query.rrtype, flags) { jb.start_object()? - .set_string_from_bytes("rrname", &query.name)? - .set_string("rrtype", &dns_rrtype_string(query.rrtype))? - .close()?; + .set_string_from_bytes("rrname", &query.name.value)? + .set_string("rrtype", &dns_rrtype_string(query.rrtype))?; + jb.close()?; } } jb.close()?; @@ -903,7 +915,7 @@ pub extern "C" fn SCDnsLogEnabled(tx: &DNSTransaction, flags: u64) -> bool { // Should be unreachable... return false; }; - + for query in &message.queries { if dns_log_rrtype_enabled(query.rrtype, flags) { return true; diff --git a/rust/src/dns/lua.rs b/rust/src/dns/lua.rs index 436a75115a..64c4f32e8b 100644 --- a/rust/src/dns/lua.rs +++ b/rust/src/dns/lua.rs @@ -34,12 +34,12 @@ pub extern "C" fn SCDnsLuaGetRrname(clua: &mut CLuaState, tx: &mut DNSTransactio if let Some(request) = &tx.request { if let Some(query) = request.queries.first() { - lua.pushstring(&String::from_utf8_lossy(&query.name)); + lua.pushstring(&String::from_utf8_lossy(&query.name.value)); return 1; } } else if let Some(response) = &tx.response { if let Some(query) = response.queries.first() { - lua.pushstring(&String::from_utf8_lossy(&query.name)); + lua.pushstring(&String::from_utf8_lossy(&query.name.value)); return 1; } } @@ -84,7 +84,7 @@ pub extern "C" fn SCDnsLuaGetQueryTable(clua: &mut CLuaState, tx: &mut DNSTransa lua.settable(-3); lua.pushstring("rrname"); - lua.pushstring(&String::from_utf8_lossy(&query.name)); + lua.pushstring(&String::from_utf8_lossy(&query.name.value)); lua.settable(-3); lua.settable(-3); @@ -101,7 +101,7 @@ pub extern "C" fn SCDnsLuaGetQueryTable(clua: &mut CLuaState, tx: &mut DNSTransa lua.settable(-3); lua.pushstring("rrname"); - lua.pushstring(&String::from_utf8_lossy(&query.name)); + lua.pushstring(&String::from_utf8_lossy(&query.name.value)); lua.settable(-3); lua.settable(-3); @@ -138,11 +138,11 @@ pub extern "C" fn SCDnsLuaGetAnswerTable(clua: &mut CLuaState, tx: &mut DNSTrans lua.settable(-3); lua.pushstring("rrname"); - lua.pushstring(&String::from_utf8_lossy(&answer.name)); + lua.pushstring(&String::from_utf8_lossy(&answer.name.value)); lua.settable(-3); // All rdata types are pushed to "addr" for backwards compatibility - match answer.data { + match &answer.data { DNSRData::A(ref bytes) | DNSRData::AAAA(ref bytes) => { if !bytes.is_empty() { lua.pushstring("addr"); @@ -150,12 +150,18 @@ pub extern "C" fn SCDnsLuaGetAnswerTable(clua: &mut CLuaState, tx: &mut DNSTrans lua.settable(-3); } } - DNSRData::CNAME(ref bytes) - | DNSRData::MX(ref bytes) - | DNSRData::NS(ref bytes) - | DNSRData::TXT(ref bytes) + DNSRData::CNAME(name) + | DNSRData::MX(name) + | DNSRData::NS(name) + | DNSRData::PTR(name) => { + if !name.value.is_empty() { + lua.pushstring("addr"); + lua.pushstring(&String::from_utf8_lossy(&name.value)); + lua.settable(-3); + } + } + DNSRData::TXT(ref bytes) | DNSRData::NULL(ref bytes) - | DNSRData::PTR(ref bytes) | DNSRData::Unknown(ref bytes) => { if !bytes.is_empty() { lua.pushstring("addr"); @@ -164,9 +170,9 @@ pub extern "C" fn SCDnsLuaGetAnswerTable(clua: &mut CLuaState, tx: &mut DNSTrans } } DNSRData::SOA(ref soa) => { - if !soa.mname.is_empty() { + if !soa.mname.value.is_empty() { lua.pushstring("addr"); - lua.pushstring(&String::from_utf8_lossy(&soa.mname)); + lua.pushstring(&String::from_utf8_lossy(&soa.mname.value)); lua.settable(-3); } } @@ -177,7 +183,7 @@ pub extern "C" fn SCDnsLuaGetAnswerTable(clua: &mut CLuaState, tx: &mut DNSTrans } DNSRData::SRV(ref srv) => { lua.pushstring("addr"); - lua.pushstring(&String::from_utf8_lossy(&srv.target)); + lua.pushstring(&String::from_utf8_lossy(&srv.target.value)); lua.settable(-3); } DNSRData::OPT(ref opt) => { @@ -227,7 +233,7 @@ pub extern "C" fn SCDnsLuaGetAuthorityTable( lua.settable(-3); lua.pushstring("rrname"); - lua.pushstring(&String::from_utf8_lossy(&answer.name)); + lua.pushstring(&String::from_utf8_lossy(&answer.name.value)); lua.settable(-3); lua.settable(-3); diff --git a/rust/src/dns/parser.rs b/rust/src/dns/parser.rs index 31332c1659..e705d8b825 100644 --- a/rust/src/dns/parser.rs +++ b/rust/src/dns/parser.rs @@ -24,16 +24,48 @@ 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}; +// Set a maximum assembled hostname length of 1025, this value was +// chosen as its what DNSMasq uses, a popular DNS server, even if most +// tooling limits names to 256 chars without special options. +static MAX_NAME_LEN: usize = 1025; + /// Parse a DNS name. /// +/// Names are parsed with the following restrictions: +/// +/// - Only 255 segments will be processed, if more the parser may +/// error out. This is also our safeguard against an infinite loop. If +/// a pointer had been followed a truncated name will be +/// returned. However if pointer has been processed we error out as we +/// don't know where the next data point starts without more +/// iterations. +/// +/// - The maximum name parsed in representation format is MAX_NAME_LEN +/// characters. Once larger, the truncated name will be returned with +/// a flag specifying the name was truncated. Note that parsing +/// continues if no pointer has been used as we still need to find the +/// start of the next protocol unit. +/// +/// As some error in parsing the name are recoverable, a DNSName +/// object is returned with flags signifying a recoverable +/// error. These errors include: +/// +/// - infinite loop: as we know the end of the name in the input +/// stream, we can return what we've parsed with the remain data. +/// +/// - maximum number of segments/labels parsed +/// +/// - truncation of name when too long +/// /// Parameters: /// start: the start of the name /// message: the complete message that start is a part of with the DNS header -fn dns_parse_name<'b>(start: &'b [u8], message: &'b [u8]) -> IResult<&'b [u8], Vec> { +fn dns_parse_name<'b>(start: &'b [u8], message: &'b [u8]) -> IResult<&'b [u8], DNSName> { let mut pos = start; let mut pivot = start; let mut name: Vec = Vec::with_capacity(32); let mut count = 0; + let mut flags = DNSNameFlags::default(); loop { if pos.is_empty() { @@ -47,10 +79,12 @@ fn dns_parse_name<'b>(start: &'b [u8], message: &'b [u8]) -> IResult<&'b [u8], V break; } else if len & 0b1100_0000 == 0 { let (rem, label) = length_data(be_u8)(pos)?; - if !name.is_empty() { - name.push(b'.'); + if !flags.contains(DNSNameFlags::TRUNCATED) { + if !name.is_empty() { + name.push(b'.'); + } + name.extend(label); } - name.extend(label); pos = rem; } else if len & 0b1100_0000 == 0b1100_0000 { let (rem, leader) = be_u16(pos)?; @@ -58,6 +92,21 @@ fn dns_parse_name<'b>(start: &'b [u8], message: &'b [u8]) -> IResult<&'b [u8], V if offset > message.len() { return Err(Err::Error(error_position!(pos, ErrorKind::OctDigit))); } + + if &message[offset..] == pos { + // Self reference, immedate infinite loop. + flags.insert(DNSNameFlags::INFINITE_LOOP); + + // If we have followed a pointer, we can just break as + // we've already found the end of the input. But if we + // have not followed a pointer yet return a parse + // error. + if pivot != start { + break; + } + return Err(Err::Error(error_position!(pos, ErrorKind::OctDigit))); + } + pos = &message[offset..]; if pivot == start { pivot = rem; @@ -68,19 +117,43 @@ fn dns_parse_name<'b>(start: &'b [u8], message: &'b [u8]) -> IResult<&'b [u8], V // Return error if we've looped a certain number of times. count += 1; + if count > 255 { + flags.insert(DNSNameFlags::LABEL_LIMIT); + + // Our segment limit has been reached, if we have hit a + // pointer we can just return the truncated name. If we + // have not hit a pointer, we need to bail with an error. + if pivot != start { + flags.insert(DNSNameFlags::TRUNCATED); + break; + } return Err(Err::Error(error_position!(pos, ErrorKind::OctDigit))); } + + if name.len() > MAX_NAME_LEN { + name.truncate(MAX_NAME_LEN); + flags.insert(DNSNameFlags::TRUNCATED); + + // If we have pivoted due to a pointer we know where the + // end of the data is, so we can break early. Otherwise + // we'll keep parsing in hopes to find the end of the name + // so parsing can continue. + if pivot != start { + break; + } + } } // 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)); + if pivot != start { + Ok((pivot, DNSName { value: name, flags })) + } else { + Ok((pos, DNSName { value: name, flags })) } - return Ok((pos, name)); } /// Parse answer entries. @@ -100,7 +173,7 @@ fn dns_parse_answer<'a>( let mut input = slice; struct Answer<'a> { - name: Vec, + name: DNSName, rrtype: u16, rrclass: u16, ttl: u32, @@ -392,7 +465,7 @@ mod tests { ]; 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!("client-cf.dropbox.com".as_bytes(), &name.value[..]); assert_eq!(remainder, expected_remainder); } @@ -428,7 +501,13 @@ mod tests { let res1 = dns_parse_name(start1, message); assert_eq!( res1, - Ok((&start1[22..], "www.suricata-ids.org".as_bytes().to_vec())) + Ok(( + &start1[22..], + DNSName { + value: "www.suricata-ids.org".as_bytes().to_vec(), + flags: DNSNameFlags::default(), + } + )) ); // The second name starts at offset 80, but is just a pointer @@ -437,7 +516,13 @@ mod tests { let res2 = dns_parse_name(start2, message); assert_eq!( res2, - Ok((&start2[2..], "www.suricata-ids.org".as_bytes().to_vec())) + Ok(( + &start2[2..], + DNSName { + value: "www.suricata-ids.org".as_bytes().to_vec(), + flags: DNSNameFlags::default() + } + )) ); // The third name starts at offset 94, but is a pointer to a @@ -446,7 +531,13 @@ mod tests { let res3 = dns_parse_name(start3, message); assert_eq!( res3, - Ok((&start3[2..], "suricata-ids.org".as_bytes().to_vec())) + Ok(( + &start3[2..], + DNSName { + value: "suricata-ids.org".as_bytes().to_vec(), + flags: DNSNameFlags::default() + } + )) ); // The fourth name starts at offset 110, but is a pointer to a @@ -455,7 +546,13 @@ mod tests { let res4 = dns_parse_name(start4, message); assert_eq!( res4, - Ok((&start4[2..], "suricata-ids.org".as_bytes().to_vec())) + Ok(( + &start4[2..], + DNSName { + value: "suricata-ids.org".as_bytes().to_vec(), + flags: DNSNameFlags::default() + } + )) ); } @@ -490,7 +587,13 @@ mod tests { let res = dns_parse_name(start, message); assert_eq!( res, - Ok((&start[2..], "block.g1.dropbox.com".as_bytes().to_vec())) + Ok(( + &start[2..], + DNSName { + value: "block.g1.dropbox.com".as_bytes().to_vec(), + flags: DNSNameFlags::default() + } + )) ); } @@ -528,7 +631,7 @@ mod tests { 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.name.value, "www.suricata-ids.org".as_bytes().to_vec()); assert_eq!(query.rrtype, 1); assert_eq!(query.rrclass, 1); @@ -539,7 +642,10 @@ mod tests { assert_eq!( additional, &DNSAnswerEntry { - name: vec![], + name: DNSName { + value: vec![], + flags: DNSNameFlags::default() + }, rrtype: DNS_RECORD_TYPE_OPT, rrclass: 0x1000, // for OPT this is UDP payload size ttl: 0, // for OPT this is extended RCODE and flags @@ -586,7 +692,7 @@ mod tests { 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.name.value, "www.suricata-ids.org".as_bytes().to_vec()); assert_eq!(query.rrtype, 1); assert_eq!(query.rrclass, 1); @@ -597,7 +703,10 @@ mod tests { assert_eq!( additional, &DNSAnswerEntry { - name: vec![], + name: DNSName { + value: vec![], + flags: DNSNameFlags::default() + }, rrtype: DNS_RECORD_TYPE_OPT, rrclass: 0x1000, // for OPT this is requestor's UDP payload size ttl: 0, // for OPT this is extended RCODE and flags @@ -661,20 +770,29 @@ mod tests { 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.name.value, + "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()) + DNSRData::CNAME(DNSName { + value: "suricata-ids.org".as_bytes().to_vec(), + flags: DNSNameFlags::default() + }) ); let answer2 = &response.answers[1]; assert_eq!( answer2, &DNSAnswerEntry { - name: "suricata-ids.org".as_bytes().to_vec(), + name: DNSName { + value: "suricata-ids.org".as_bytes().to_vec(), + flags: DNSNameFlags::default() + }, rrtype: 1, rrclass: 1, ttl: 244, @@ -686,7 +804,10 @@ mod tests { assert_eq!( answer3, &DNSAnswerEntry { - name: "suricata-ids.org".as_bytes().to_vec(), + name: DNSName { + value: "suricata-ids.org".as_bytes().to_vec(), + flags: DNSNameFlags::default() + }, rrtype: 1, rrclass: 1, ttl: 244, @@ -737,15 +858,21 @@ mod tests { assert_eq!(response.authorities.len(), 1); let authority = &response.authorities[0]; - assert_eq!(authority.name, "oisf.net".as_bytes().to_vec()); + assert_eq!(authority.name.value, "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(), + mname: DNSName { + value: "ns-110.awsdns-13.com".as_bytes().to_vec(), + flags: DNSNameFlags::default() + }, + rname: DNSName { + value: "awsdns-hostmaster.amazon.com".as_bytes().to_vec(), + flags: DNSNameFlags::default() + }, serial: 1, refresh: 7200, retry: 900, @@ -761,7 +888,10 @@ mod tests { assert_eq!( additional, &DNSAnswerEntry { - name: vec![], + name: DNSName { + value: vec![], + flags: DNSNameFlags::default() + }, rrtype: DNS_RECORD_TYPE_OPT, rrclass: 0x0200, // for OPT this is UDP payload size ttl: 0, // for OPT this is extended RCODE and flags @@ -804,14 +934,20 @@ mod tests { 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.name.value, + "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.name.value, + "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); @@ -893,7 +1029,7 @@ mod tests { assert_eq!(srv.weight, 1); assert_eq!(srv.port, 5060); assert_eq!( - srv.target, + srv.target.value, "sip-anycast-2.voice.google.com".as_bytes().to_vec() ); } else { @@ -905,11 +1041,72 @@ mod tests { assert_eq!(srv.weight, 1); assert_eq!(srv.port, 5060); assert_eq!( - srv.target, + srv.target.value, "sip-anycast-1.voice.google.com".as_bytes().to_vec() ); } else { panic!("Expected DNSRData::SRV"); } } + + #[test] + fn test_dns_parse_name_truncated() { + // Generate a non-compressed hostname over our maximum of 1024. + let mut buf: Vec = vec![]; + for _ in 0..17 { + buf.push(0b0011_1111); + for _ in 0..63 { + buf.push(b'a'); + } + } + + let (rem, name) = dns_parse_name(&buf, &buf).unwrap(); + assert_eq!(name.value.len(), MAX_NAME_LEN); + assert!(name.flags.contains(DNSNameFlags::TRUNCATED)); + assert!(rem.is_empty()); + } + + #[test] + fn test_dns_parse_name_truncated_max_segments_no_pointer() { + let mut buf: Vec = vec![]; + for _ in 0..256 { + buf.push(0b0000_0001); + buf.push(b'a'); + } + + // This should fail as we've hit the segment limit without a + // pointer, we'd need to keep parsing more segments to figure + // out where the next data point lies. + assert!(dns_parse_name(&buf, &buf).is_err()); + } + + #[test] + fn test_dns_parse_name_truncated_max_segments_with_pointer() { + let mut buf: Vec = vec![]; + + // "a" at the beginning of the buffer. + buf.push(0b0000_0001); + buf.push(b'a'); + + // Followed by a pointer back to the beginning. + buf.push(0b1100_0000); + buf.push(0b0000_0000); + + // The start of the name, which is pointer to the beginning of + // the buffer. + buf.push(0b1100_0000); + buf.push(0b000_0000); + + let (_rem, name) = dns_parse_name(&buf[4..], &buf).unwrap(); + assert_eq!(name.value.len(), 255); + assert!(name.flags.contains(DNSNameFlags::TRUNCATED)); + } + + #[test] + fn test_dns_parse_name_self_reference() { + let mut buf = vec![]; + buf.push(0b1100_0000); + buf.push(0b0000_0000); + assert!(dns_parse_name(&buf, &buf).is_err()); + } }