]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
dns: parse and populate OPT rdata struct
authorNathan Scrivens <nathan.scrivens21@gmail.com>
Thu, 16 May 2024 19:51:51 +0000 (15:51 -0400)
committerVictor Julien <victor@inliniac.net>
Tue, 9 Jul 2024 10:15:24 +0000 (12:15 +0200)
Feature: 7017
Add DNSRDataOPT struct and DNSRData enum type OPT.
Add OPT parsing function and test function.
Add DNSRData OPT type to lua.rs match.
Log OPT rdata.

etc/schema.json
rust/src/dns/dns.rs
rust/src/dns/log.rs
rust/src/dns/lua.rs
rust/src/dns/parser.rs

index bb6a8238c32e0b1e6d6d369d8b100fd4579c4758..356e4fe9829a60d9a19934d3a3ba48815001a18d 100644 (file)
                     },
                     "ttl": {
                         "type": "integer"
+                    },
+                    "opt": {
+                        "type": "array",
+                        "minItems": 1,
+                        "items": {
+                            "type": "object",
+                            "properties": {
+                                "code": {
+                                    "type": "integer"
+                                },
+                                "data": {
+                                    "type": "string"
+                                }
+                            },
+                            "additionalProperties": false
+                        }
                     }
                 },
                 "additionalProperties": false
index 0b6f8039307530c02a6440a7e3c5dae5c6dbe542..ea12b43ac3aab4188fa5fec4c9882a48483095b4 100644 (file)
@@ -149,6 +149,14 @@ pub struct DNSQueryEntry {
     pub rrclass: u16,
 }
 
+#[derive(Debug, PartialEq, Eq)]
+pub struct DNSRDataOPT {
+    /// Option Code
+    pub code: u16,
+    /// Option Data
+    pub data: Vec<u8>,
+}
+
 #[derive(Debug, PartialEq, Eq)]
 pub struct DNSRDataSOA {
     /// Primary name server for this zone
@@ -207,6 +215,7 @@ pub enum DNSRData {
     SOA(DNSRDataSOA),
     SRV(DNSRDataSRV),
     SSHFP(DNSRDataSSHFP),
+    OPT(Vec<DNSRDataOPT>),
     // RData for remaining types is sometimes ignored
     Unknown(Vec<u8>),
 }
index c1043f89252ef0a783c9cf75527074a5b32ff9a0..e4bfd91976d755f58019cce9959b3f9a104e2b39 100644 (file)
@@ -394,6 +394,17 @@ pub fn dns_print_addr(addr: &[u8]) -> std::string::String {
     }
 }
 
+/// Log OPT section fields
+fn dns_log_opt(opt: &DNSRDataOPT) -> Result<JsonBuilder, JsonError> {
+    let mut js = JsonBuilder::try_new_object()?;
+
+    js.set_uint("code", opt.code as u64)?;
+    js.set_hex("data", &opt.data)?;
+
+    js.close()?;
+    Ok(js)
+} 
+
 /// Log SOA section fields.
 fn dns_log_soa(soa: &DNSRDataSOA) -> Result<JsonBuilder, JsonError> {
     let mut js = JsonBuilder::try_new_object()?;
@@ -468,6 +479,13 @@ fn dns_log_json_answer_detail(answer: &DNSAnswerEntry) -> Result<JsonBuilder, Js
         DNSRData::SRV(srv) => {
             jsa.set_object("srv", &dns_log_srv(srv)?)?;
         }
+        DNSRData::OPT(opt) => {
+            jsa.open_array("opt")?;
+            for val in opt {
+                jsa.append_object(&dns_log_opt(val)?)?;
+            }
+            jsa.close()?;
+        }
         _ => {}
     }
 
@@ -609,7 +627,7 @@ fn dns_log_json_answer(
     if !response.additionals.is_empty() {
         let mut is_js_open = false;
         for add in &response.additionals {
-            if let DNSRData::Unknown(rdata) = &add.data {
+            if let DNSRData::OPT(rdata) = &add.data {
                 if rdata.is_empty() {
                     continue;
                 }
index a7847ecaf3217da61629a0e30d1aa270ffd45b0c..436a75115a5ce9cf841d600a5de3ccbf98146f94 100644 (file)
@@ -180,6 +180,16 @@ pub extern "C" fn SCDnsLuaGetAnswerTable(clua: &mut CLuaState, tx: &mut DNSTrans
                     lua.pushstring(&String::from_utf8_lossy(&srv.target));
                     lua.settable(-3);
                 }
+                DNSRData::OPT(ref opt) => {
+                    if !opt.is_empty() {
+                        lua.pushstring("addr");
+                        for option in opt.iter() {
+                            lua.pushstring(&String::from_utf8_lossy(&option.code.to_be_bytes()));
+                            lua.pushstring(&String::from_utf8_lossy(&option.data));
+                        }
+                        lua.settable(-3);
+                    }
+                }
             }
             lua.settable(-3);
         }
index 238b83a10241e34f2f402c42fe4740eb6e4b5901..31332c165922a9bfea6f1ded72808c6807cf2a4a 100644 (file)
@@ -149,7 +149,7 @@ fn dns_parse_answer<'a>(
                         rrtype: val.rrtype,
                         rrclass: val.rrclass,
                         ttl: val.ttl,
-                        data: DNSRData::Unknown(Vec::new()),
+                        data: DNSRData::OPT(Vec::new()),
                     });
                     input = rem;
                     continue;
@@ -295,6 +295,22 @@ fn dns_parse_rdata_sshfp(input: &[u8]) -> IResult<&[u8], DNSRData> {
     ))
 }
 
+fn dns_parse_rdata_opt(input: &[u8]) -> IResult<&[u8], DNSRData> {
+    let mut dns_rdata_opt_vec = Vec::new();
+    let mut i = input;
+
+    while !i.is_empty() {
+        let (j, code) = be_u16(i)?;
+        let (j, data) = length_data(be_u16)(j)?;
+        i = j;
+        dns_rdata_opt_vec.push(DNSRDataOPT {
+            code,
+            data: data.to_vec(),
+        });
+    }
+    Ok((i, DNSRData::OPT(dns_rdata_opt_vec)))
+}
+
 fn dns_parse_rdata_unknown(input: &[u8]) -> IResult<&[u8], DNSRData> {
     rest(input).map(|(input, data)| (input, DNSRData::Unknown(data.to_vec())))
 }
@@ -314,6 +330,7 @@ fn dns_parse_rdata<'a>(
         DNS_RECORD_TYPE_NULL => dns_parse_rdata_null(input),
         DNS_RECORD_TYPE_SSHFP => dns_parse_rdata_sshfp(input),
         DNS_RECORD_TYPE_SRV => dns_parse_rdata_srv(input, message),
+        DNS_RECORD_TYPE_OPT => dns_parse_rdata_opt(input),
         _ => dns_parse_rdata_unknown(input),
     }
 }
@@ -493,7 +510,7 @@ mod tests {
         let (body, header) = dns_parse_header(pkt).unwrap();
         let res = dns_parse_body(body, pkt, header);
         let (rem, request) = res.unwrap();
-        // The response should be fully parsed.
+        // The request should be fully parsed.
         assert!(rem.is_empty());
 
         assert_eq!(
@@ -517,15 +534,84 @@ mod tests {
 
         // verify additional section
         assert_eq!(request.additionals.len(), 1);
+
         let additional = &request.additionals[0];
         assert_eq!(
             additional,
             &DNSAnswerEntry {
                 name: vec![],
                 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
+                data: DNSRData::OPT(vec![]), // empty rdata
+            }
+        );
+    }
+
+    #[test]
+    fn test_dns_parse_request_multi_opt() {
+        let pkt: &[u8] = &[
+            0x8d, 0x32, 0x01, 0x20, 0x00, 0x01, /* ...2. .. */
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x77, /* .......w */
+            0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63, /* ww.suric */
+            0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03, /* ata-ids. */
+            0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, /* org..... */
+            0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x00, /* ..)..... */
+            0x00, 0x00, 0x10, /* total rdata len: 16 */
+            /* Option Cookie */
+            0x00, 0x0a, /* Code: 10 */
+            0x00, 0x08, /* Option len: 8 */
+            0x7f, 0x86, 0xcf, 0x8b, 0x81, 0xf6, 0xf9, 0x55, /* Option NSID */
+            0x00, 0x03, /* Code: 3 */
+            0x00, 0x00, /* option len: 0 */
+        ];
+
+        let (body, header) = dns_parse_header(pkt).unwrap();
+        let res = dns_parse_body(body, pkt, header);
+        let (rem, request) = res.unwrap();
+
+        assert!(rem.is_empty());
+        assert_eq!(
+            request.header,
+            DNSHeader {
+                tx_id: 0x8d32,
+                flags: 0x0120,
+                questions: 1,
+                answer_rr: 0,
+                authority_rr: 0,
+                additional_rr: 1,
+            }
+        );
+
+        assert_eq!(request.queries.len(), 1);
+
+        let query = &request.queries[0];
+        assert_eq!(query.name, "www.suricata-ids.org".as_bytes().to_vec());
+        assert_eq!(query.rrtype, 1);
+        assert_eq!(query.rrclass, 1);
+
+        // verify additional section
+        assert_eq!(request.additionals.len(), 1);
+
+        let additional = &request.additionals[0];
+        assert_eq!(
+            additional,
+            &DNSAnswerEntry {
+                name: vec![],
+                rrtype: DNS_RECORD_TYPE_OPT,
+                rrclass: 0x1000, // for OPT this is requestor's UDP payload size
                 ttl: 0,          // for OPT this is extended RCODE and flags
-                data: DNSRData::Unknown(vec![]), // empty rdata
+                // verify two options
+                data: DNSRData::OPT(vec![
+                    DNSRDataOPT {
+                        code: 0x000a,
+                        data: vec![0x7f, 0x86, 0xcf, 0x8b, 0x81, 0xf6, 0xf9, 0x55]
+                    },
+                    DNSRDataOPT {
+                        code: 0x0003,
+                        data: vec![]
+                    },
+                ])
             }
         );
     }
@@ -677,9 +763,9 @@ mod tests {
             &DNSAnswerEntry {
                 name: vec![],
                 rrtype: DNS_RECORD_TYPE_OPT,
-                rrclass: 0x0200, // for OPT this is UDP payload size
-                ttl: 0,          // for OPT this is extended RCODE and flags
-                data: DNSRData::Unknown(vec![]), // no rdata
+                rrclass: 0x0200,             // for OPT this is UDP payload size
+                ttl: 0,                      // for OPT this is extended RCODE and flags
+                data: DNSRData::OPT(vec![]), // no rdata
             }
         );
     }