diff --git a/rust/src/dns/dns.rs b/rust/src/dns/dns.rs index 2a1ef6dd0c..2a8f0cc126 100644 --- a/rust/src/dns/dns.rs +++ b/rust/src/dns/dns.rs @@ -233,7 +233,20 @@ pub struct DNSQueryEntry { #[derive(Debug,PartialEq)] pub struct DNSRDataSOA { - pub data: Vec, + /// Primary name server for this zone + pub mname: Vec, + /// Authority's mailbox + pub rname: Vec, + /// Serial version number + pub serial: u32, + /// Refresh interval (seconds) + pub refresh: u32, + /// Retry interval (seconds) + pub retry: u32, + /// Upper time limit until zone is no longer authoritative (seconds) + pub expire: u32, + /// Minimum ttl for records in this zone (seconds) + pub minimum: u32, } #[derive(Debug,PartialEq)] diff --git a/rust/src/dns/log.rs b/rust/src/dns/log.rs index d2cb6d1fdd..c27076be45 100644 --- a/rust/src/dns/log.rs +++ b/rust/src/dns/log.rs @@ -395,7 +395,23 @@ pub fn dns_print_addr(addr: &Vec) -> std::string::String { } } -/// Log the SSHPF in an DNSAnswerEntry. +/// Log SOA section fields. +fn dns_log_soa(soa: &DNSRDataSOA) -> Result { + let mut js = JsonBuilder::new_object(); + + js.set_string_from_bytes("mname", &soa.mname)?; + js.set_string_from_bytes("rname", &soa.rname)?; + js.set_uint("serial", soa.serial as u64)?; + js.set_uint("refresh", soa.refresh as u64)?; + js.set_uint("retry", soa.retry as u64)?; + js.set_uint("expire", soa.expire as u64)?; + js.set_uint("minimum", soa.minimum as u64)?; + + js.close()?; + return Ok(js); +} + +/// Log SSHFP section fields. fn dns_log_sshfp(sshfp: &DNSRDataSSHFP) -> Result { let mut js = JsonBuilder::new_object(); @@ -431,6 +447,9 @@ fn dns_log_json_answer_detail(answer: &DNSAnswerEntry) -> Result { jsa.set_string_from_bytes("rdata", &bytes)?; } + DNSRData::SOA(soa) => { + jsa.set_object("soa", &dns_log_soa(&soa)?)?; + } DNSRData::SSHFP(sshfp) => { jsa.set_object("sshfp", &dns_log_sshfp(&sshfp)?)?; } @@ -505,6 +524,15 @@ fn dns_log_json_answer(js: &mut JsonBuilder, response: &DNSResponse, flags: u64) a.append_string_from_bytes(&bytes)?; } }, + DNSRData::SOA(soa) => { + 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_object(&dns_log_soa(&soa)?)?; + } + }, DNSRData::SSHFP(sshfp) => { if !answer_types.contains_key(&type_string) { answer_types.insert(type_string.to_string(), @@ -662,6 +690,9 @@ fn dns_log_json_answer_v1(header: &DNSHeader, answer: &DNSAnswerEntry) DNSRData::PTR(bytes) => { js.set_string_from_bytes("rdata", &bytes)?; } + DNSRData::SOA(soa) => { + js.set_object("soa", &dns_log_soa(&soa)?)?; + } DNSRData::SSHFP(sshfp) => { js.set_object("sshfp", &dns_log_sshfp(&sshfp)?)?; } diff --git a/rust/src/dns/lua.rs b/rust/src/dns/lua.rs index 0165fa8e77..6a7ea26921 100644 --- a/rust/src/dns/lua.rs +++ b/rust/src/dns/lua.rs @@ -186,9 +186,9 @@ pub extern "C" fn rs_dns_lua_get_answer_table(clua: &mut CLuaState, } }, DNSRData::SOA(ref soa) => { - if soa.data.len() > 0 { + if soa.mname.len() > 0 { lua.pushstring("addr"); - lua.pushstring(&String::from_utf8_lossy(&soa.data)); + lua.pushstring(&String::from_utf8_lossy(&soa.mname)); lua.settable(-3); } }, diff --git a/rust/src/dns/parser.rs b/rust/src/dns/parser.rs index 8fea18457e..0f7038dda6 100644 --- a/rust/src/dns/parser.rs +++ b/rust/src/dns/parser.rs @@ -280,8 +280,25 @@ fn dns_parse_rdata_ptr<'a>(input: &'a [u8], message: &'a [u8]) 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}))) + do_parse!( + input, + mname: call!(dns_parse_name, message) >> + rname: call!(dns_parse_name, message) >> + serial: be_u32 >> + refresh: be_u32 >> + retry: be_u32 >> + expire: be_u32 >> + minimum: be_u32 >> + (DNSRData::SOA(DNSRDataSOA{ + mname, + rname, + serial, + refresh, + retry, + expire, + minimum, + })) + ) } fn dns_parse_rdata_mx<'a>(input: &'a [u8], message: &'a [u8]) @@ -587,6 +604,71 @@ mod tests { } } + #[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.len() > 0); + + 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_rdata_sshfp() { // Dummy data since we don't have a pcap sample.