rust/dns: handle multiple txt strings

Fix handling of TXT records when there are multiple strings
in a single TXT record. For now, conform to the C implementation
where an answer record is created for each string in a single
txt record.

Also removes the data_len field from the answer entry. In Rust,
the length is available from actual data, which after decoding
may actually be different than the encoded data length, so just
use the length from the actual data.
pull/2814/head
Jason Ish 8 years ago committed by Victor Julien
parent 9dab3ec71e
commit 40991cab82

@ -149,7 +149,6 @@ pub struct DNSAnswerEntry {
pub rrtype: u16, pub rrtype: u16,
pub rrclass: u16, pub rrclass: u16,
pub ttl: u32, pub ttl: u32,
pub data_len: u16,
pub data: Vec<u8>, pub data: Vec<u8>,
} }

@ -330,7 +330,7 @@ pub fn dns_print_addr(addr: &Vec<u8>) -> std::string::String {
fn dns_log_sshfp(js: &Json, answer: &DNSAnswerEntry) fn dns_log_sshfp(js: &Json, answer: &DNSAnswerEntry)
{ {
// Need at least 3 bytes - TODO: log something if we don't? // Need at least 3 bytes - TODO: log something if we don't?
if answer.data_len < 3 { if answer.data.len() < 3 {
return; return;
} }

@ -123,35 +123,110 @@ pub fn dns_parse_name<'a, 'b>(start: &'b [u8],
} }
/// Parse a DNS response. /// Parse answer entries.
pub fn dns_parse_response<'a>(slice: &'a [u8]) ///
-> nom::IResult<&[u8], DNSResponse> { /// In keeping with the C implementation, answer values that can
let answer_parser = closure!(&'a [u8], do_parse!( /// contain multiple answers get expanded into their own answer
name: apply!(dns_parse_name, slice) >> /// 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)
-> nom::IResult<&'a [u8], Vec<DNSAnswerEntry>> {
let mut answers = Vec::new();
let mut input = slice;
for _ in 0..count {
match closure!(&'a [u8], do_parse!(
name: apply!(dns_parse_name, message) >>
rrtype: be_u16 >> rrtype: be_u16 >>
rrclass: be_u16 >> rrclass: be_u16 >>
ttl: be_u32 >> ttl: be_u32 >>
data_len: be_u16 >> data_len: be_u16 >>
data: flat_map!(take!(data_len), data: take!(data_len) >>
apply!(dns_parse_rdata, slice, rrtype)) >>
( (
DNSAnswerEntry{ name,
name: name, rrtype,
rrclass,
ttl,
data
)
))(input) {
nom::IResult::Done(rem, val) => {
let name = val.0;
let rrtype = val.1;
let rrclass = val.2;
let ttl = val.3;
let data = val.4;
let n = match rrtype {
DNS_RTYPE_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: nom::IResult<&'a [u8], Vec<Vec<u8>>> =
closure!(&'a [u8], do_parse!(
rdata: many_m_n!(1, n,
apply!(dns_parse_rdata, message, rrtype))
>> (rdata)
))(data);
match result {
nom::IResult::Done(_, rdatas) => {
for rdata in rdatas {
answers.push(DNSAnswerEntry{
name: name.clone(),
rrtype: rrtype, rrtype: rrtype,
rrclass: rrclass, rrclass: rrclass,
ttl: ttl, ttl: ttl,
data_len: data_len, data: rdata,
data: data.to_vec(), });
}
}
nom::IResult::Error(err) => {
return nom::IResult::Error(err);
}
nom::IResult::Incomplete(needed) => {
return nom::IResult::Incomplete(needed);
}
}
input = rem;
}
nom::IResult::Error(err) => {
return nom::IResult::Error(err);
}
nom::IResult::Incomplete(needed) => {
return nom::IResult::Incomplete(needed);
}
}
} }
)
));
return nom::IResult::Done(input, answers);
}
/// Parse a DNS response.
pub fn dns_parse_response<'a>(slice: &'a [u8])
-> nom::IResult<&[u8], DNSResponse> {
let response = closure!(&'a [u8], do_parse!( let response = closure!(&'a [u8], do_parse!(
header: dns_parse_header header: dns_parse_header
>> queries: count!(apply!(dns_parse_query, slice), >> queries: count!(
header.questions as usize) apply!(dns_parse_query, slice), header.questions as usize)
>> answers: count!(answer_parser, header.answer_rr as usize) >> answers: apply!(
>> authorities: count!(answer_parser, header.authority_rr as usize) dns_parse_answer, slice, header.answer_rr as usize)
>> authorities: apply!(
dns_parse_answer, slice, header.authority_rr as usize)
>> ( >> (
DNSResponse{ DNSResponse{
header: header, header: header,
@ -187,14 +262,14 @@ pub fn dns_parse_query<'a>(input: &'a [u8],
))(input); ))(input);
} }
pub fn dns_parse_rdata<'a>(data: &'a [u8], message: &'a [u8], rrtype: u16) pub fn dns_parse_rdata<'a>(input: &'a [u8], message: &'a [u8], rrtype: u16)
-> nom::IResult<&'a [u8], Vec<u8>> -> nom::IResult<&'a [u8], Vec<u8>>
{ {
match rrtype { match rrtype {
DNS_RTYPE_CNAME | DNS_RTYPE_CNAME |
DNS_RTYPE_PTR | DNS_RTYPE_PTR |
DNS_RTYPE_SOA => { DNS_RTYPE_SOA => {
dns_parse_name(data, message) dns_parse_name(input, message)
}, },
DNS_RTYPE_MX => { DNS_RTYPE_MX => {
// For MX we we skip over the preference field before // For MX we we skip over the preference field before
@ -203,16 +278,21 @@ pub fn dns_parse_rdata<'a>(data: &'a [u8], message: &'a [u8], rrtype: u16)
be_u16 >> be_u16 >>
name: apply!(dns_parse_name, message) >> name: apply!(dns_parse_name, message) >>
(name) (name)
))(data) ))(input)
}, },
DNS_RTYPE_TXT => { DNS_RTYPE_TXT => {
closure!(&'a [u8], do_parse!( closure!(&'a [u8], do_parse!(
len: be_u8 >> len: be_u8 >>
txt: take!(len) >> txt: take!(len) >>
(txt.to_vec()) (txt.to_vec())
))(data) ))(input)
}, },
_ => nom::IResult::Done(data, data.to_vec()) _ => {
closure!(&'a [u8], do_parse!(
data: take!(input.len()) >>
(data.to_vec())
))(input)
}
} }
} }
@ -440,7 +520,6 @@ mod tests {
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!(answer1.data_len, 18);
assert_eq!(answer1.data, assert_eq!(answer1.data,
"suricata-ids.org".as_bytes().to_vec()); "suricata-ids.org".as_bytes().to_vec());
@ -450,7 +529,6 @@ mod tests {
rrtype: 1, rrtype: 1,
rrclass: 1, rrclass: 1,
ttl: 244, ttl: 244,
data_len: 4,
data: [192, 0, 78, 24].to_vec(), data: [192, 0, 78, 24].to_vec(),
}); });
@ -460,7 +538,6 @@ mod tests {
rrtype: 1, rrtype: 1,
rrclass: 1, rrclass: 1,
ttl: 244, ttl: 244,
data_len: 4,
data: [192, 0, 78, 25].to_vec(), data: [192, 0, 78, 25].to_vec(),
}) })

Loading…
Cancel
Save