Skip to content

Commit

Permalink
dns: parse and populate OPT rdata struct
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
scrivs86 authored and jasonish committed Jul 4, 2024
1 parent 724aa4e commit 354af42
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 8 deletions.
16 changes: 16 additions & 0 deletions etc/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -6368,6 +6368,22 @@
},
"ttl": {
"type": "integer"
},
"opt": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"code": {
"type": "integer"
},
"data": {
"type": "string"
}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
Expand Down
9 changes: 9 additions & 0 deletions rust/src/dns/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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>),
}
Expand Down
20 changes: 19 additions & 1 deletion rust/src/dns/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?;
Expand Down Expand Up @@ -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()?;
}
_ => {}
}

Expand Down Expand Up @@ -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;
}
Expand Down
10 changes: 10 additions & 0 deletions rust/src/dns/lua.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
100 changes: 93 additions & 7 deletions rust/src/dns/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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())))
}
Expand All @@ -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),
}
}
Expand Down Expand Up @@ -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!(
Expand All @@ -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![]
},
])
}
);
}
Expand Down Expand Up @@ -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
}
);
}
Expand Down

0 comments on commit 354af42

Please sign in to comment.