dns: truncate names larger than 1025 characters

Once a name has gone over 1025 chars it will be truncated to 1025
chars and no more labels will be added to it, however the name will
continue to be parsed up to the label limit in attempt to find the end
so parsing can continue.

This introduces a new struct, DNSName which contains the name and any
flags which indicate any name parsing errors which should not error
out parsing the complete message, for example, infinite recursion
after some labels are parsed can continue, or truncation of name where
compression was used so we know the start of the next data to be
parsed.

This limits the logged DNS messages from being over our maximum size
of 10Mb in the case of really long names.

Ticket: #7280
pull/12272/head
Jason Ish 2 years ago committed by Victor Julien
parent 9a53ec43b1
commit 3a5671739f

@ -144,7 +144,7 @@ pub struct DNSHeader {
#[derive(Debug)] #[derive(Debug)]
pub struct DNSQueryEntry { pub struct DNSQueryEntry {
pub name: Vec<u8>, pub name: DNSName,
pub rrtype: u16, pub rrtype: u16,
pub rrclass: u16, pub rrclass: u16,
} }
@ -160,9 +160,9 @@ pub struct DNSRDataOPT {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct DNSRDataSOA { pub struct DNSRDataSOA {
/// Primary name server for this zone /// Primary name server for this zone
pub mname: Vec<u8>, pub mname: DNSName,
/// Authority's mailbox /// Authority's mailbox
pub rname: Vec<u8>, pub rname: DNSName,
/// Serial version number /// Serial version number
pub serial: u32, pub serial: u32,
/// Refresh interval (seconds) /// Refresh interval (seconds)
@ -194,7 +194,22 @@ pub struct DNSRDataSRV {
/// Port /// Port
pub port: u16, pub port: u16,
/// Target /// Target
pub target: Vec<u8>, 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<u8>,
pub flags: DNSNameFlags,
} }
/// Represents RData of various formats /// Represents RData of various formats
@ -204,10 +219,10 @@ pub enum DNSRData {
A(Vec<u8>), A(Vec<u8>),
AAAA(Vec<u8>), AAAA(Vec<u8>),
// RData is a domain name // RData is a domain name
CNAME(Vec<u8>), CNAME(DNSName),
PTR(Vec<u8>), PTR(DNSName),
MX(Vec<u8>), MX(DNSName),
NS(Vec<u8>), NS(DNSName),
// RData is text // RData is text
TXT(Vec<u8>), TXT(Vec<u8>),
NULL(Vec<u8>), NULL(Vec<u8>),
@ -222,7 +237,7 @@ pub enum DNSRData {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct DNSAnswerEntry { pub struct DNSAnswerEntry {
pub name: Vec<u8>, pub name: DNSName,
pub rrtype: u16, pub rrtype: u16,
pub rrclass: u16, pub rrclass: u16,
pub ttl: u32, pub ttl: u32,
@ -485,7 +500,9 @@ impl DNSState {
tx.tx_data.set_event(event as u8); tx.tx_data.set_event(event as u8);
} }
fn parse_request(&mut self, input: &[u8], is_tcp: bool, frame: Option<Frame>, flow: *const core::Flow,) -> bool { fn parse_request(
&mut self, input: &[u8], is_tcp: bool, frame: Option<Frame>, flow: *const core::Flow,
) -> bool {
match dns_parse_request(input) { match dns_parse_request(input) {
Ok(mut tx) => { Ok(mut tx) => {
self.tx_id += 1; self.tx_id += 1;
@ -542,7 +559,9 @@ impl DNSState {
self.parse_response(input, false, frame, flow) self.parse_response(input, false, frame, flow)
} }
fn parse_response(&mut self, input: &[u8], is_tcp: bool, frame: Option<Frame>, flow: *const core::Flow) -> bool { fn parse_response(
&mut self, input: &[u8], is_tcp: bool, frame: Option<Frame>, flow: *const core::Flow,
) -> bool {
match dns_parse_response(input) { match dns_parse_response(input) {
Ok(mut tx) => { Ok(mut tx) => {
self.tx_id += 1; self.tx_id += 1;
@ -908,9 +927,9 @@ pub unsafe extern "C" fn SCDnsTxGetQueryName(
if let Some(queries) = queries { if let Some(queries) = queries {
if let Some(query) = queries.get(index) { if let Some(query) = queries.get(index) {
if !query.name.is_empty() { if !query.name.value.is_empty() {
*buf = query.name.as_ptr(); *buf = query.name.value.as_ptr();
*len = query.name.len() as u32; *len = query.name.value.len() as u32;
return true; return true;
} }
} }
@ -933,9 +952,9 @@ pub unsafe extern "C" fn SCDnsTxGetAnswerName(
if let Some(answers) = answers { if let Some(answers) = answers {
if let Some(answer) = answers.get(index) { if let Some(answer) = answers.get(index) {
if !answer.name.is_empty() { if !answer.name.value.is_empty() {
*buf = answer.name.as_ptr(); *buf = answer.name.value.as_ptr();
*len = answer.name.len() as u32; *len = answer.name.value.len() as u32;
return true; return true;
} }
} }

@ -414,8 +414,8 @@ fn dns_log_opt(opt: &DNSRDataOPT) -> Result<JsonBuilder, JsonError> {
fn dns_log_soa(soa: &DNSRDataSOA) -> Result<JsonBuilder, JsonError> { fn dns_log_soa(soa: &DNSRDataSOA) -> Result<JsonBuilder, JsonError> {
let mut js = JsonBuilder::try_new_object()?; let mut js = JsonBuilder::try_new_object()?;
js.set_string_from_bytes("mname", &soa.mname)?; js.set_string_from_bytes("mname", &soa.mname.value)?;
js.set_string_from_bytes("rname", &soa.rname)?; js.set_string_from_bytes("rname", &soa.rname.value)?;
js.set_uint("serial", soa.serial as u64)?; js.set_uint("serial", soa.serial as u64)?;
js.set_uint("refresh", soa.refresh as u64)?; js.set_uint("refresh", soa.refresh as u64)?;
js.set_uint("retry", soa.retry as u64)?; js.set_uint("retry", soa.retry as u64)?;
@ -450,7 +450,7 @@ fn dns_log_srv(srv: &DNSRDataSRV) -> Result<JsonBuilder, JsonError> {
js.set_uint("priority", srv.priority as u64)?; js.set_uint("priority", srv.priority as u64)?;
js.set_uint("weight", srv.weight as u64)?; js.set_uint("weight", srv.weight as u64)?;
js.set_uint("port", srv.port 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()?; js.close()?;
return Ok(js); return Ok(js);
@ -459,7 +459,7 @@ fn dns_log_srv(srv: &DNSRDataSRV) -> Result<JsonBuilder, JsonError> {
fn dns_log_json_answer_detail(answer: &DNSAnswerEntry) -> Result<JsonBuilder, JsonError> { fn dns_log_json_answer_detail(answer: &DNSAnswerEntry) -> Result<JsonBuilder, JsonError> {
let mut jsa = JsonBuilder::try_new_object()?; 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_string("rrtype", &dns_rrtype_string(answer.rrtype))?;
jsa.set_uint("ttl", answer.ttl as u64)?; jsa.set_uint("ttl", answer.ttl as u64)?;
@ -467,12 +467,10 @@ fn dns_log_json_answer_detail(answer: &DNSAnswerEntry) -> Result<JsonBuilder, Js
DNSRData::A(addr) | DNSRData::AAAA(addr) => { DNSRData::A(addr) | DNSRData::AAAA(addr) => {
jsa.set_string("rdata", &dns_print_addr(addr))?; jsa.set_string("rdata", &dns_print_addr(addr))?;
} }
DNSRData::CNAME(bytes) DNSRData::CNAME(name) | DNSRData::MX(name) | DNSRData::NS(name) | DNSRData::PTR(name) => {
| DNSRData::MX(bytes) jsa.set_string_from_bytes("rdata", &name.value)?;
| DNSRData::NS(bytes) }
| DNSRData::TXT(bytes) DNSRData::TXT(bytes) | DNSRData::NULL(bytes) => {
| DNSRData::NULL(bytes)
| DNSRData::PTR(bytes) => {
jsa.set_string_from_bytes("rdata", bytes)?; jsa.set_string_from_bytes("rdata", bytes)?;
} }
DNSRData::SOA(soa) => { DNSRData::SOA(soa) => {
@ -529,7 +527,7 @@ fn dns_log_json_answer(
js.set_uint("opcode", opcode as u64)?; js.set_uint("opcode", opcode as u64)?;
if let Some(query) = response.queries.first() { 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("rrtype", &dns_rrtype_string(query.rrtype))?;
} }
js.set_string("rcode", &dns_rcode_string(header.flags))?; 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))?; a.append_string(&dns_print_addr(addr))?;
} }
} }
DNSRData::CNAME(bytes) DNSRData::CNAME(name)
| DNSRData::MX(bytes) | DNSRData::MX(name)
| DNSRData::NS(bytes) | DNSRData::NS(name)
| DNSRData::TXT(bytes) | DNSRData::PTR(name) => {
| DNSRData::NULL(bytes) if !answer_types.contains_key(&type_string) {
| DNSRData::PTR(bytes) => { 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) { if !answer_types.contains_key(&type_string) {
answer_types answer_types
.insert(type_string.to_string(), JsonBuilder::try_new_array()?); .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))?; a.append_string(&dns_print_addr(addr))?;
} }
} }
DNSRData::CNAME(bytes) DNSRData::CNAME(name)
| DNSRData::MX(bytes) | DNSRData::MX(name)
| DNSRData::NS(bytes) | DNSRData::NS(name)
| DNSRData::TXT(bytes) | DNSRData::PTR(name) => {
| DNSRData::NULL(bytes) if !answer_types.contains_key(&type_string) {
| DNSRData::PTR(bytes) => { 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) { if !answer_types.contains_key(&type_string) {
answer_types answer_types
.insert(type_string.to_string(), JsonBuilder::try_new_array()?); .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) { if dns_log_rrtype_enabled(query.rrtype, flags) {
jb.set_string("type", "query")?; jb.set_string("type", "query")?;
jb.set_uint("id", request.header.tx_id as u64)?; 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_string("rrtype", &dns_rrtype_string(query.rrtype))?;
jb.set_uint("tx_id", tx.id - 1)?; jb.set_uint("tx_id", tx.id - 1)?;
if request.header.flags & 0x0040 != 0 { 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 { for query in &message.queries {
if dns_log_rrtype_enabled(query.rrtype, flags) { if dns_log_rrtype_enabled(query.rrtype, flags) {
jb.start_object()? jb.start_object()?
.set_string_from_bytes("rrname", &query.name)? .set_string_from_bytes("rrname", &query.name.value)?
.set_string("rrtype", &dns_rrtype_string(query.rrtype))? .set_string("rrtype", &dns_rrtype_string(query.rrtype))?;
.close()?; jb.close()?;
} }
} }
jb.close()?; jb.close()?;
@ -903,7 +915,7 @@ pub extern "C" fn SCDnsLogEnabled(tx: &DNSTransaction, flags: u64) -> bool {
// Should be unreachable... // Should be unreachable...
return false; return false;
}; };
for query in &message.queries { for query in &message.queries {
if dns_log_rrtype_enabled(query.rrtype, flags) { if dns_log_rrtype_enabled(query.rrtype, flags) {
return true; return true;

@ -34,12 +34,12 @@ pub extern "C" fn SCDnsLuaGetRrname(clua: &mut CLuaState, tx: &mut DNSTransactio
if let Some(request) = &tx.request { if let Some(request) = &tx.request {
if let Some(query) = request.queries.first() { 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; return 1;
} }
} else if let Some(response) = &tx.response { } else if let Some(response) = &tx.response {
if let Some(query) = response.queries.first() { 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; return 1;
} }
} }
@ -84,7 +84,7 @@ pub extern "C" fn SCDnsLuaGetQueryTable(clua: &mut CLuaState, tx: &mut DNSTransa
lua.settable(-3); lua.settable(-3);
lua.pushstring("rrname"); 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);
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.settable(-3);
lua.pushstring("rrname"); 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);
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.settable(-3);
lua.pushstring("rrname"); 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);
// All rdata types are pushed to "addr" for backwards compatibility // All rdata types are pushed to "addr" for backwards compatibility
match answer.data { match &answer.data {
DNSRData::A(ref bytes) | DNSRData::AAAA(ref bytes) => { DNSRData::A(ref bytes) | DNSRData::AAAA(ref bytes) => {
if !bytes.is_empty() { if !bytes.is_empty() {
lua.pushstring("addr"); lua.pushstring("addr");
@ -150,12 +150,18 @@ pub extern "C" fn SCDnsLuaGetAnswerTable(clua: &mut CLuaState, tx: &mut DNSTrans
lua.settable(-3); lua.settable(-3);
} }
} }
DNSRData::CNAME(ref bytes) DNSRData::CNAME(name)
| DNSRData::MX(ref bytes) | DNSRData::MX(name)
| DNSRData::NS(ref bytes) | DNSRData::NS(name)
| DNSRData::TXT(ref bytes) | 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::NULL(ref bytes)
| DNSRData::PTR(ref bytes)
| DNSRData::Unknown(ref bytes) => { | DNSRData::Unknown(ref bytes) => {
if !bytes.is_empty() { if !bytes.is_empty() {
lua.pushstring("addr"); lua.pushstring("addr");
@ -164,9 +170,9 @@ pub extern "C" fn SCDnsLuaGetAnswerTable(clua: &mut CLuaState, tx: &mut DNSTrans
} }
} }
DNSRData::SOA(ref soa) => { DNSRData::SOA(ref soa) => {
if !soa.mname.is_empty() { if !soa.mname.value.is_empty() {
lua.pushstring("addr"); lua.pushstring("addr");
lua.pushstring(&String::from_utf8_lossy(&soa.mname)); lua.pushstring(&String::from_utf8_lossy(&soa.mname.value));
lua.settable(-3); lua.settable(-3);
} }
} }
@ -177,7 +183,7 @@ pub extern "C" fn SCDnsLuaGetAnswerTable(clua: &mut CLuaState, tx: &mut DNSTrans
} }
DNSRData::SRV(ref srv) => { DNSRData::SRV(ref srv) => {
lua.pushstring("addr"); lua.pushstring("addr");
lua.pushstring(&String::from_utf8_lossy(&srv.target)); lua.pushstring(&String::from_utf8_lossy(&srv.target.value));
lua.settable(-3); lua.settable(-3);
} }
DNSRData::OPT(ref opt) => { DNSRData::OPT(ref opt) => {
@ -227,7 +233,7 @@ pub extern "C" fn SCDnsLuaGetAuthorityTable(
lua.settable(-3); lua.settable(-3);
lua.pushstring("rrname"); 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);
lua.settable(-3); lua.settable(-3);

@ -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::number::streaming::{be_u16, be_u32, be_u8};
use nom7::{error_position, Err, IResult}; 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. /// 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: /// Parameters:
/// start: the start of the name /// start: the start of the name
/// message: the complete message that start is a part of with the DNS header /// 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<u8>> { fn dns_parse_name<'b>(start: &'b [u8], message: &'b [u8]) -> IResult<&'b [u8], DNSName> {
let mut pos = start; let mut pos = start;
let mut pivot = start; let mut pivot = start;
let mut name: Vec<u8> = Vec::with_capacity(32); let mut name: Vec<u8> = Vec::with_capacity(32);
let mut count = 0; let mut count = 0;
let mut flags = DNSNameFlags::default();
loop { loop {
if pos.is_empty() { if pos.is_empty() {
@ -47,10 +79,12 @@ fn dns_parse_name<'b>(start: &'b [u8], message: &'b [u8]) -> IResult<&'b [u8], V
break; break;
} else if len & 0b1100_0000 == 0 { } else if len & 0b1100_0000 == 0 {
let (rem, label) = length_data(be_u8)(pos)?; let (rem, label) = length_data(be_u8)(pos)?;
if !name.is_empty() { if !flags.contains(DNSNameFlags::TRUNCATED) {
name.push(b'.'); if !name.is_empty() {
name.push(b'.');
}
name.extend(label);
} }
name.extend(label);
pos = rem; pos = rem;
} else if len & 0b1100_0000 == 0b1100_0000 { } else if len & 0b1100_0000 == 0b1100_0000 {
let (rem, leader) = be_u16(pos)?; 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() { if offset > message.len() {
return Err(Err::Error(error_position!(pos, ErrorKind::OctDigit))); 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..]; pos = &message[offset..];
if pivot == start { if pivot == start {
pivot = rem; 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. // Return error if we've looped a certain number of times.
count += 1; count += 1;
if count > 255 { 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))); 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 // If we followed a pointer we return the position after the first
// pointer followed. Is there a better way to see if these slices // pointer followed. Is there a better way to see if these slices
// diverged from each other? A straight up comparison would // diverged from each other? A straight up comparison would
// actually check the contents. // actually check the contents.
if pivot.len() != start.len() { if pivot != start {
return Ok((pivot, name)); Ok((pivot, DNSName { value: name, flags }))
} else {
Ok((pos, DNSName { value: name, flags }))
} }
return Ok((pos, name));
} }
/// Parse answer entries. /// Parse answer entries.
@ -100,7 +173,7 @@ fn dns_parse_answer<'a>(
let mut input = slice; let mut input = slice;
struct Answer<'a> { struct Answer<'a> {
name: Vec<u8>, name: DNSName,
rrtype: u16, rrtype: u16,
rrclass: u16, rrclass: u16,
ttl: u32, ttl: u32,
@ -392,7 +465,7 @@ mod tests {
]; ];
let expected_remainder: &[u8] = &[0x00, 0x01, 0x00]; let expected_remainder: &[u8] = &[0x00, 0x01, 0x00];
let (remainder, name) = dns_parse_name(buf, buf).unwrap(); 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); assert_eq!(remainder, expected_remainder);
} }
@ -428,7 +501,13 @@ mod tests {
let res1 = dns_parse_name(start1, message); let res1 = dns_parse_name(start1, message);
assert_eq!( assert_eq!(
res1, 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 // 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); let res2 = dns_parse_name(start2, message);
assert_eq!( assert_eq!(
res2, 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 // 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); let res3 = dns_parse_name(start3, message);
assert_eq!( assert_eq!(
res3, 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 // 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); let res4 = dns_parse_name(start4, message);
assert_eq!( assert_eq!(
res4, 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); let res = dns_parse_name(start, message);
assert_eq!( assert_eq!(
res, 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); assert_eq!(request.queries.len(), 1);
let query = &request.queries[0]; 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.rrtype, 1);
assert_eq!(query.rrclass, 1); assert_eq!(query.rrclass, 1);
@ -539,7 +642,10 @@ mod tests {
assert_eq!( assert_eq!(
additional, additional,
&DNSAnswerEntry { &DNSAnswerEntry {
name: vec![], name: DNSName {
value: vec![],
flags: DNSNameFlags::default()
},
rrtype: DNS_RECORD_TYPE_OPT, rrtype: DNS_RECORD_TYPE_OPT,
rrclass: 0x1000, // for OPT this is UDP payload size rrclass: 0x1000, // for OPT this is UDP payload size
ttl: 0, // for OPT this is extended RCODE and flags ttl: 0, // for OPT this is extended RCODE and flags
@ -586,7 +692,7 @@ mod tests {
assert_eq!(request.queries.len(), 1); assert_eq!(request.queries.len(), 1);
let query = &request.queries[0]; 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.rrtype, 1);
assert_eq!(query.rrclass, 1); assert_eq!(query.rrclass, 1);
@ -597,7 +703,10 @@ mod tests {
assert_eq!( assert_eq!(
additional, additional,
&DNSAnswerEntry { &DNSAnswerEntry {
name: vec![], name: DNSName {
value: vec![],
flags: DNSNameFlags::default()
},
rrtype: DNS_RECORD_TYPE_OPT, rrtype: DNS_RECORD_TYPE_OPT,
rrclass: 0x1000, // for OPT this is requestor's UDP payload size rrclass: 0x1000, // for OPT this is requestor's UDP payload size
ttl: 0, // for OPT this is extended RCODE and flags ttl: 0, // for OPT this is extended RCODE and flags
@ -661,20 +770,29 @@ mod tests {
assert_eq!(response.answers.len(), 3); assert_eq!(response.answers.len(), 3);
let answer1 = &response.answers[0]; 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.rrtype, 5);
assert_eq!(answer1.rrclass, 1); assert_eq!(answer1.rrclass, 1);
assert_eq!(answer1.ttl, 3544); assert_eq!(answer1.ttl, 3544);
assert_eq!( assert_eq!(
answer1.data, 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]; let answer2 = &response.answers[1];
assert_eq!( assert_eq!(
answer2, answer2,
&DNSAnswerEntry { &DNSAnswerEntry {
name: "suricata-ids.org".as_bytes().to_vec(), name: DNSName {
value: "suricata-ids.org".as_bytes().to_vec(),
flags: DNSNameFlags::default()
},
rrtype: 1, rrtype: 1,
rrclass: 1, rrclass: 1,
ttl: 244, ttl: 244,
@ -686,7 +804,10 @@ mod tests {
assert_eq!( assert_eq!(
answer3, answer3,
&DNSAnswerEntry { &DNSAnswerEntry {
name: "suricata-ids.org".as_bytes().to_vec(), name: DNSName {
value: "suricata-ids.org".as_bytes().to_vec(),
flags: DNSNameFlags::default()
},
rrtype: 1, rrtype: 1,
rrclass: 1, rrclass: 1,
ttl: 244, ttl: 244,
@ -737,15 +858,21 @@ mod tests {
assert_eq!(response.authorities.len(), 1); assert_eq!(response.authorities.len(), 1);
let authority = &response.authorities[0]; 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.rrtype, 6);
assert_eq!(authority.rrclass, 1); assert_eq!(authority.rrclass, 1);
assert_eq!(authority.ttl, 899); assert_eq!(authority.ttl, 899);
assert_eq!( assert_eq!(
authority.data, authority.data,
DNSRData::SOA(DNSRDataSOA { DNSRData::SOA(DNSRDataSOA {
mname: "ns-110.awsdns-13.com".as_bytes().to_vec(), mname: DNSName {
rname: "awsdns-hostmaster.amazon.com".as_bytes().to_vec(), 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, serial: 1,
refresh: 7200, refresh: 7200,
retry: 900, retry: 900,
@ -761,7 +888,10 @@ mod tests {
assert_eq!( assert_eq!(
additional, additional,
&DNSAnswerEntry { &DNSAnswerEntry {
name: vec![], name: DNSName {
value: vec![],
flags: DNSNameFlags::default()
},
rrtype: DNS_RECORD_TYPE_OPT, rrtype: DNS_RECORD_TYPE_OPT,
rrclass: 0x0200, // for OPT this is UDP payload size rrclass: 0x0200, // for OPT this is UDP payload size
ttl: 0, // for OPT this is extended RCODE and flags ttl: 0, // for OPT this is extended RCODE and flags
@ -804,14 +934,20 @@ mod tests {
assert_eq!(response.queries.len(), 1); assert_eq!(response.queries.len(), 1);
let query = &response.queries[0]; 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.rrtype, DNS_RECORD_TYPE_NULL);
assert_eq!(query.rrclass, 1); assert_eq!(query.rrclass, 1);
assert_eq!(response.answers.len(), 1); assert_eq!(response.answers.len(), 1);
let answer = &response.answers[0]; 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.rrtype, DNS_RECORD_TYPE_NULL);
assert_eq!(answer.rrclass, 1); assert_eq!(answer.rrclass, 1);
assert_eq!(answer.ttl, 0); assert_eq!(answer.ttl, 0);
@ -893,7 +1029,7 @@ mod tests {
assert_eq!(srv.weight, 1); assert_eq!(srv.weight, 1);
assert_eq!(srv.port, 5060); assert_eq!(srv.port, 5060);
assert_eq!( assert_eq!(
srv.target, srv.target.value,
"sip-anycast-2.voice.google.com".as_bytes().to_vec() "sip-anycast-2.voice.google.com".as_bytes().to_vec()
); );
} else { } else {
@ -905,11 +1041,72 @@ mod tests {
assert_eq!(srv.weight, 1); assert_eq!(srv.weight, 1);
assert_eq!(srv.port, 5060); assert_eq!(srv.port, 5060);
assert_eq!( assert_eq!(
srv.target, srv.target.value,
"sip-anycast-1.voice.google.com".as_bytes().to_vec() "sip-anycast-1.voice.google.com".as_bytes().to_vec()
); );
} else { } else {
panic!("Expected DNSRData::SRV"); 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<u8> = 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<u8> = 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<u8> = 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());
}
} }

Loading…
Cancel
Save