]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
dns: parse and log fields for SOA record type
authorSimon Dugas <simdugas@gmail.com>
Fri, 24 Apr 2020 17:57:20 +0000 (17:57 +0000)
committerVictor Julien <victor@inliniac.net>
Fri, 4 Sep 2020 11:05:08 +0000 (13:05 +0200)
Added `dns_parse_rdata_soa` to parse SOA fields into an `DNSRDataSOA`
struct.

Added logging for answer and authority SOA records in both version
1 & 2, as well as grouped formats.

rust/src/dns/dns.rs
rust/src/dns/log.rs
rust/src/dns/lua.rs
rust/src/dns/parser.rs

index 2a1ef6dd0c7021a1bf21015e565438a123304a59..2a8f0cc126801276e01710cbb417e27ecb8a3c39 100644 (file)
@@ -233,7 +233,20 @@ pub struct DNSQueryEntry {
 
 #[derive(Debug,PartialEq)]
 pub struct DNSRDataSOA {
-    pub data: Vec<u8>,
+    /// Primary name server for this zone
+    pub mname: Vec<u8>,
+    /// Authority's mailbox
+    pub rname: Vec<u8>,
+    /// 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)]
index d2cb6d1fdd0ffb82684acab9285a2e62e72f971e..c27076be45ec2f3667912efd3ddb56d7ad6dc212 100644 (file)
@@ -395,7 +395,23 @@ pub fn dns_print_addr(addr: &Vec<u8>) -> std::string::String {
     }
 }
 
-///  Log the SSHPF in an DNSAnswerEntry.
+/// Log SOA section fields.
+fn dns_log_soa(soa: &DNSRDataSOA) -> Result<JsonBuilder, JsonError> {
+    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<JsonBuilder, JsonError>
 {
     let mut js = JsonBuilder::new_object();
@@ -431,6 +447,9 @@ fn dns_log_json_answer_detail(answer: &DNSAnswerEntry) -> Result<JsonBuilder, Js
         DNSRData::PTR(bytes) => {
             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)?)?;
         }
index 0165fa8e77e835b5fd9946c1db659cf9e6472b77..6a7ea26921371d84662b1b7c8488548af674b1c2 100644 (file)
@@ -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);
                     }
                 },
index 8fea18457e580a6371c4fee32619d7d00c3db993..0f7038dda61475ed7c31d65d9a97c5ad63856acc 100644 (file)
@@ -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.