dns: refactor to handle more rdata formats

Represent rdata as `DNSRData` enum variants instead of `Vec<u8>`.
This will allow parsing/logging of more complex formats like SOA.
pull/5361/head
Simon Dugas 6 years ago committed by Victor Julien
parent 77bafe13a3
commit 8005f50647

@ -231,13 +231,47 @@ pub struct DNSQueryEntry {
pub rrclass: u16,
}
#[derive(Debug,PartialEq)]
pub struct DNSRDataSOA {
pub data: Vec<u8>,
}
#[derive(Debug,PartialEq)]
pub struct DNSRDataSSHFP {
/// Algorithm number
pub algo: u8,
/// Fingerprint type
pub fp_type: u8,
/// Fingerprint
pub fingerprint: Vec<u8>,
}
/// Represents RData of various formats
#[derive(Debug,PartialEq)]
pub enum DNSRData {
// RData is an address
A(Vec<u8>),
AAAA(Vec<u8>),
// RData is a domain name
CNAME(Vec<u8>),
PTR(Vec<u8>),
MX(Vec<u8>),
// RData is text
TXT(Vec<u8>),
// RData has several fields
SOA(DNSRDataSOA),
SSHFP(DNSRDataSSHFP),
// RData for remaining types is sometimes ignored
Unknown(Vec<u8>),
}
#[derive(Debug,PartialEq)]
pub struct DNSAnswerEntry {
pub name: Vec<u8>,
pub rrtype: u16,
pub rrclass: u16,
pub ttl: u32,
pub data: Vec<u8>,
pub data: DNSRData,
}
#[derive(Debug)]

@ -396,24 +396,21 @@ pub fn dns_print_addr(addr: &Vec<u8>) -> std::string::String {
}
/// Log the SSHPF in an DNSAnswerEntry.
fn dns_log_sshfp(answer: &DNSAnswerEntry) -> Result<Option<JsonBuilder>, JsonError>
fn dns_log_sshfp(sshfp: &DNSRDataSSHFP) -> Result<JsonBuilder, JsonError>
{
// Need at least 3 bytes - TODO: log something if we don't?
if answer.data.len() < 3 {
return Ok(None)
}
let mut sshfp = JsonBuilder::new_object();
let mut js = JsonBuilder::new_object();
let mut hex = Vec::new();
for byte in &answer.data[2..] {
for byte in &sshfp.fingerprint {
hex.push(format!("{:02x}", byte));
}
sshfp.set_string("fingerprint", &hex.join(":"))?;
sshfp.set_uint("algo", answer.data[0] as u64)?;
sshfp.set_uint("type", answer.data[1] as u64)?;
return Ok(Some(sshfp));
js.set_string("fingerprint", &hex.join(":"))?;
js.set_uint("algo", sshfp.algo as u64)?;
js.set_uint("type", sshfp.fp_type as u64)?;
js.close()?;
return Ok(js);
}
fn dns_log_json_answer_detail(answer: &DNSAnswerEntry) -> Result<JsonBuilder, JsonError>
@ -424,21 +421,19 @@ fn dns_log_json_answer_detail(answer: &DNSAnswerEntry) -> Result<JsonBuilder, Js
jsa.set_string("rrtype", &dns_rrtype_string(answer.rrtype))?;
jsa.set_uint("ttl", answer.ttl as u64)?;
match answer.rrtype {
DNS_RECORD_TYPE_A | DNS_RECORD_TYPE_AAAA => {
jsa.set_string("rdata", &dns_print_addr(&answer.data))?;
match &answer.data {
DNSRData::A(addr) | DNSRData::AAAA(addr) => {
jsa.set_string("rdata", &dns_print_addr(&addr))?;
}
DNSRData::CNAME(bytes) |
DNSRData::MX(bytes) |
DNSRData::TXT(bytes) |
DNSRData::PTR(bytes) => {
jsa.set_string_from_bytes("rdata", &bytes)?;
}
DNSRData::SSHFP(sshfp) => {
jsa.set_object("sshfp", &dns_log_sshfp(&sshfp)?)?;
}
DNS_RECORD_TYPE_CNAME |
DNS_RECORD_TYPE_MX |
DNS_RECORD_TYPE_TXT |
DNS_RECORD_TYPE_PTR => {
jsa.set_string_from_bytes("rdata", &answer.data)?;
},
DNS_RECORD_TYPE_SSHFP => {
if let Some(sshfp) = dns_log_sshfp(answer)? {
jsa.set_object("sshfp", &sshfp)?;
}
},
_ => {}
}
@ -488,37 +483,35 @@ fn dns_log_json_answer(js: &mut JsonBuilder, response: &DNSResponse, flags: u64)
if flags & LOG_FORMAT_GROUPED != 0 {
let type_string = dns_rrtype_string(answer.rrtype);
match answer.rrtype {
DNS_RECORD_TYPE_A | DNS_RECORD_TYPE_AAAA => {
match &answer.data {
DNSRData::A(addr) | DNSRData::AAAA(addr) => {
if !answer_types.contains_key(&type_string) {
answer_types.insert(type_string.to_string(),
JsonBuilder::new_array());
}
if let Some(a) = answer_types.get_mut(&type_string) {
a.append_string(&dns_print_addr(&answer.data))?;
a.append_string(&dns_print_addr(&addr))?;
}
}
DNS_RECORD_TYPE_CNAME |
DNS_RECORD_TYPE_MX |
DNS_RECORD_TYPE_TXT |
DNS_RECORD_TYPE_PTR => {
DNSRData::CNAME(bytes) |
DNSRData::MX(bytes) |
DNSRData::TXT(bytes) |
DNSRData::PTR(bytes) => {
if !answer_types.contains_key(&type_string) {
answer_types.insert(type_string.to_string(),
JsonBuilder::new_array());
}
if let Some(a) = answer_types.get_mut(&type_string) {
a.append_string_from_bytes(&answer.data)?;
a.append_string_from_bytes(&bytes)?;
}
},
DNS_RECORD_TYPE_SSHFP => {
DNSRData::SSHFP(sshfp) => {
if !answer_types.contains_key(&type_string) {
answer_types.insert(type_string.to_string(),
JsonBuilder::new_array());
}
if let Some(a) = answer_types.get_mut(&type_string) {
if let Some(sshfp) = dns_log_sshfp(&answer)? {
a.append_object(&sshfp)?;
}
a.append_object(&dns_log_sshfp(&sshfp)?)?;
}
},
_ => {}
@ -659,21 +652,19 @@ fn dns_log_json_answer_v1(header: &DNSHeader, answer: &DNSAnswerEntry)
js.set_string("rrtype", &dns_rrtype_string(answer.rrtype))?;
js.set_uint("ttl", answer.ttl as u64)?;
match answer.rrtype {
DNS_RECORD_TYPE_A | DNS_RECORD_TYPE_AAAA => {
js.set_string("rdata", &dns_print_addr(&answer.data))?;
match &answer.data {
DNSRData::A(addr) | DNSRData::AAAA(addr) => {
js.set_string("rdata", &dns_print_addr(&addr))?;
}
DNSRData::CNAME(bytes) |
DNSRData::MX(bytes) |
DNSRData::TXT(bytes) |
DNSRData::PTR(bytes) => {
js.set_string_from_bytes("rdata", &bytes)?;
}
DNSRData::SSHFP(sshfp) => {
js.set_object("sshfp", &dns_log_sshfp(&sshfp)?)?;
}
DNS_RECORD_TYPE_CNAME |
DNS_RECORD_TYPE_MX |
DNS_RECORD_TYPE_TXT |
DNS_RECORD_TYPE_PTR => {
js.set_string_from_bytes("rdata", &answer.data)?;
},
DNS_RECORD_TYPE_SSHFP => {
if let Some(sshfp) = dns_log_sshfp(&answer)? {
js.set_object("sshfp", &sshfp)?;
}
},
_ => {}
}

@ -165,17 +165,38 @@ pub extern "C" fn rs_dns_lua_get_answer_table(clua: &mut CLuaState,
lua.pushstring(&String::from_utf8_lossy(&answer.name));
lua.settable(-3);
if answer.data.len() > 0 {
lua.pushstring("addr");
match answer.rrtype {
DNS_RECORD_TYPE_A | DNS_RECORD_TYPE_AAAA => {
lua.pushstring(&dns_print_addr(&answer.data));
// All rdata types are pushed to "addr" for backwards compatibility
match answer.data {
DNSRData::A(ref bytes) | DNSRData::AAAA(ref bytes) => {
if bytes.len() > 0 {
lua.pushstring("addr");
lua.pushstring(&dns_print_addr(&bytes));
lua.settable(-3);
}
_ => {
lua.pushstring(&String::from_utf8_lossy(&answer.data));
},
DNSRData::CNAME(ref bytes) |
DNSRData::MX(ref bytes) |
DNSRData::TXT(ref bytes) |
DNSRData::PTR(ref bytes) |
DNSRData::Unknown(ref bytes) => {
if bytes.len() > 0 {
lua.pushstring("addr");
lua.pushstring(&String::from_utf8_lossy(&bytes));
lua.settable(-3);
}
}
lua.settable(-3);
},
DNSRData::SOA(ref soa) => {
if soa.data.len() > 0 {
lua.pushstring("addr");
lua.pushstring(&String::from_utf8_lossy(&soa.data));
lua.settable(-3);
}
},
DNSRData::SSHFP(ref sshfp) => {
lua.pushstring("addr");
lua.pushstring(&String::from_utf8_lossy(&sshfp.fingerprint));
lua.settable(-3);
},
}
lua.settable(-3);
}

@ -18,6 +18,7 @@
//! Nom parsers for DNS.
use nom::IResult;
use nom::combinator::rest;
use nom::error::ErrorKind;
use nom::multi::length_data;
use nom::number::streaming::{be_u8, be_u16, be_u32};
@ -180,7 +181,7 @@ fn dns_parse_answer<'a>(slice: &'a [u8], message: &'a [u8], count: usize)
1
}
};
let result: IResult<&'a [u8], Vec<Vec<u8>>> =
let result: IResult<&'a [u8], Vec<DNSRData>> =
do_parse!(
data,
rdata: many_m_n!(1, n,
@ -257,40 +258,99 @@ pub fn dns_parse_query<'a>(input: &'a [u8],
)
}
fn dns_parse_rdata_a<'a>(input: &'a [u8]) -> IResult<&'a [u8], DNSRData> {
do_parse!(
input,
data: take!(input.len()) >>
(DNSRData::A(data.to_vec()))
)
}
fn dns_parse_rdata_aaaa<'a>(input: &'a [u8]) -> IResult<&'a [u8], DNSRData> {
do_parse!(
input,
data: take!(input.len()) >>
(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_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> {
dns_parse_name(input, message).map(|(input, name)|
(input, DNSRData::SOA(DNSRDataSOA{data: name})))
}
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.
do_parse!(
input,
be_u16 >>
name: call!(dns_parse_name, message) >>
(DNSRData::MX(name))
)
}
fn dns_parse_rdata_txt<'a>(input: &'a [u8])
-> IResult<&'a [u8], DNSRData> {
do_parse!(
input,
len: be_u8 >>
txt: take!(len) >>
(DNSRData::TXT(txt.to_vec()))
)
}
fn dns_parse_rdata_sshfp<'a>(input: &'a [u8])
-> IResult<&'a [u8], DNSRData> {
do_parse!(
input,
algo: be_u8 >>
fp_type: be_u8 >>
fingerprint: call!(rest) >>
(DNSRData::SSHFP(DNSRDataSSHFP{
algo,
fp_type,
fingerprint: fingerprint.to_vec()
}))
)
}
fn dns_parse_rdata_unknown<'a>(input: &'a [u8])
-> IResult<&'a [u8], DNSRData> {
do_parse!(
input,
data: take!(input.len()) >>
(DNSRData::Unknown(data.to_vec()))
)
}
pub fn dns_parse_rdata<'a>(input: &'a [u8], message: &'a [u8], rrtype: u16)
-> IResult<&'a [u8], Vec<u8>>
-> IResult<&'a [u8], DNSRData>
{
match rrtype {
DNS_RECORD_TYPE_CNAME |
DNS_RECORD_TYPE_PTR |
DNS_RECORD_TYPE_SOA => {
dns_parse_name(input, message)
},
DNS_RECORD_TYPE_MX => {
// For MX we we skip over the preference field before
// parsing out the name.
do_parse!(
input,
be_u16 >>
name: call!(dns_parse_name, message) >>
(name)
)
},
DNS_RECORD_TYPE_TXT => {
do_parse!(
input,
len: be_u8 >>
txt: take!(len) >>
(txt.to_vec())
)
},
_ => {
do_parse!(
input,
data: take!(input.len()) >>
(data.to_vec())
)
}
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_TXT => dns_parse_rdata_txt(input),
DNS_RECORD_TYPE_SSHFP => dns_parse_rdata_sshfp(input),
_ => dns_parse_rdata_unknown(input),
}
}
@ -512,7 +572,7 @@ mod tests {
assert_eq!(answer1.rrclass, 1);
assert_eq!(answer1.ttl, 3544);
assert_eq!(answer1.data,
"suricata-ids.org".as_bytes().to_vec());
DNSRData::CNAME("suricata-ids.org".as_bytes().to_vec()));
let answer2 = &response.answers[1];
assert_eq!(answer2, &DNSAnswerEntry{
@ -520,7 +580,7 @@ mod tests {
rrtype: 1,
rrclass: 1,
ttl: 244,
data: [192, 0, 78, 24].to_vec(),
data: DNSRData::A([192, 0, 78, 24].to_vec()),
});
let answer3 = &response.answers[2];
@ -529,7 +589,7 @@ mod tests {
rrtype: 1,
rrclass: 1,
ttl: 244,
data: [192, 0, 78, 25].to_vec(),
data: DNSRData::A([192, 0, 78, 25].to_vec()),
})
},
@ -539,4 +599,41 @@ mod tests {
}
}
#[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);
}
}
}
}

Loading…
Cancel
Save