From: Jason Ish Date: Wed, 26 Apr 2017 22:35:10 +0000 (-0600) Subject: rust: DNS app-layer. X-Git-Tag: suricata-4.0.0-beta1~29 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=73388042b21e2431639d7329cc9f00186567ef2b;p=thirdparty%2Fsuricata.git rust: DNS app-layer. A DNS application layer in Rust. This is different than the C based one, as it is partially stateless by not matching up responses to replies. --- diff --git a/rust/src/dns/dns.rs b/rust/src/dns/dns.rs index 967bfa1e6a..f08ad4f0e8 100644 --- a/rust/src/dns/dns.rs +++ b/rust/src/dns/dns.rs @@ -15,15 +15,55 @@ * 02110-1301, USA. */ +extern crate libc; +extern crate nom; + +use std; +use std::mem::transmute; + +use log::*; +use core; +use dns::parser; + /// DNS record types. pub const DNS_RTYPE_A: u16 = 1; pub const DNS_RTYPE_CNAME: u16 = 5; pub const DNS_RTYPE_SOA: u16 = 6; pub const DNS_RTYPE_PTR: u16 = 12; pub const DNS_RTYPE_MX: u16 = 15; +pub const DNS_RTYPE_AAAA: u16 = 28; pub const DNS_RTYPE_SSHFP: u16 = 44; pub const DNS_RTYPE_RRSIG: u16 = 46; +/// DNS error codes. +pub const DNS_RCODE_NOERROR: u16 = 0; +pub const DNS_RCODE_FORMERR: u16 = 1; +pub const DNS_RCODE_NXDOMAIN: u16 = 3; + +/// The maximum number of transactions to keep in the queue pending +/// processing before they are aggressively purged. Due to the +/// stateless nature of this parser this is rarely needed, especially +/// when one call to parse a request parses and a single request, and +/// likewise for responses. +/// +/// Where this matters is when one TCP buffer contains multiple +/// requests are responses and one call into the parser creates +/// multiple transactions. In this case we have to hold onto +/// transactions longer than until handling the next transaction so it +/// gets logged. +const MAX_TRANSACTIONS: usize = 32; + +#[repr(u32)] +pub enum DNSEvent { + UnsolicitedResponse = 0, + MalformedData, + NotRequest, + NotResponse, + ZFlagSet, + Flooded, + StateMemCapReached, +} + #[derive(Debug,PartialEq)] pub struct DNSHeader { pub tx_id: u16, @@ -41,6 +81,18 @@ pub struct DNSQueryEntry { pub rrclass: u16, } +impl DNSQueryEntry { + + pub fn name(&self) -> &str { + let r = std::str::from_utf8(&self.name); + if r.is_err() { + return ""; + } + return r.unwrap(); + } + +} + #[derive(Debug,PartialEq)] pub struct DNSAnswerEntry { pub name: Vec, @@ -51,6 +103,26 @@ pub struct DNSAnswerEntry { pub data: Vec, } +impl DNSAnswerEntry { + + pub fn name(&self) -> &str { + let r = std::str::from_utf8(&self.name); + if r.is_err() { + return ""; + } + return r.unwrap(); + } + + pub fn data_to_string(&self) -> &str { + let r = std::str::from_utf8(&self.data); + if r.is_err() { + return ""; + } + return r.unwrap(); + } + +} + #[derive(Debug)] pub struct DNSRequest { pub header: DNSHeader, @@ -64,3 +136,666 @@ pub struct DNSResponse { pub answers: Vec, pub authorities: Vec, } + +#[derive(Debug)] +pub struct DNSTransaction { + pub id: u64, + pub request: Option, + pub response: Option, + pub logged: u32, + pub de_state: Option<*mut core::DetectEngineState>, + pub events: *mut core::AppLayerDecoderEvents, +} + +impl DNSTransaction { + + pub fn new() -> DNSTransaction { + return DNSTransaction{ + id: 0, + request: None, + response: None, + logged: 0, + de_state: None, + events: std::ptr::null_mut(), + } + } + + pub fn free(&mut self) { + if self.events != std::ptr::null_mut() { + core::sc_app_layer_decoder_events_free_events(&mut self.events); + } + } + + /// Get the DNS transactions ID (not the internal tracking ID). + pub fn tx_id(&self) -> u16 { + for request in &self.request { + return request.header.tx_id; + } + for response in &self.response { + return response.header.tx_id; + } + + // Shouldn't happen. + return 0; + } + + /// Get the reply code of the transaction. Note that this will + /// also return 0 if there is no reply. + pub fn rcode(&self) -> u16 { + for response in &self.response { + return response.header.flags & 0x000f; + } + return 0; + } + +} + +impl Drop for DNSTransaction { + fn drop(&mut self) { + self.free(); + } +} + +pub struct DNSState { + // Internal transaction ID. + pub tx_id: u64, + + // Transactions. + pub transactions: Vec, + + pub de_state_count: u64, + + pub events: u16, + + pub request_buffer: Vec, + pub response_buffer: Vec, +} + +impl DNSState { + + pub fn new() -> DNSState { + return DNSState{ + tx_id: 0, + transactions: Vec::new(), + de_state_count: 0, + events: 0, + request_buffer: Vec::new(), + response_buffer: Vec::new(), + }; + } + + /// Allocate a new state with capacites in the buffers for + /// potentially buffering as might be needed in TCP. + pub fn new_tcp() -> DNSState { + return DNSState{ + tx_id: 0, + transactions: Vec::new(), + de_state_count: 0, + events: 0, + request_buffer: Vec::with_capacity(0xffff), + response_buffer: Vec::with_capacity(0xffff), + }; + } + + pub fn free(&mut self) { + SCLogDebug!("Freeing {} transactions left in state.", + self.transactions.len()); + while self.transactions.len() > 0 { + self.free_tx_at_index(0); + } + assert!(self.transactions.len() == 0); + } + + pub fn new_tx(&mut self) -> DNSTransaction { + let mut tx = DNSTransaction::new(); + self.tx_id += 1; + tx.id = self.tx_id; + return tx; + } + + pub fn free_tx(&mut self, tx_id: u64) { + SCLogDebug!("************** Freeing TX with ID {}", tx_id); + let len = self.transactions.len(); + let mut found = false; + let mut index = 0; + for i in 0..len { + let tx = &self.transactions[i]; + if tx.id == tx_id + 1 { + found = true; + index = i; + break; + } + } + if found { + self.free_tx_at_index(index); + } + } + + fn free_tx_at_index(&mut self, index: usize) { + let tx = self.transactions.remove(index); + match tx.de_state { + Some(state) => { + core::sc_detect_engine_state_free(state); + self.de_state_count -= 1; + } + _ => {} + } + } + + // Purges all transactions except one. This is a stateless parser + // so we don't need to hang onto old transactions. + // + // This is to actually handle an edge case where a DNS flood + // occurs in a single direction with no response packets. In such + // a case the functions to free a transaction are never called by + // the app-layer as they require bidirectional traffic. + pub fn purge(&mut self, tx_id: u64) { + while self.transactions.len() > MAX_TRANSACTIONS { + if self.transactions[0].id == tx_id + 1 { + return; + } + SCLogDebug!("Purging DNS TX with ID {}", self.transactions[0].id); + self.free_tx_at_index(0); + } + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&DNSTransaction> { + SCLogDebug!("get_tx: tx_id={}", tx_id); + self.purge(tx_id); + for tx in &mut self.transactions { + if tx.id == tx_id + 1 { + SCLogDebug!("Found DNS TX with ID {}", tx_id); + return Some(tx); + } + } + SCLogDebug!("Failed to find DNS TX with ID {}", tx_id); + return None; + } + + /// Set an event. The event is set on the most recent transaction. + pub fn set_event(&mut self, event: DNSEvent) { + let len = self.transactions.len(); + if len == 0 { + return; + } + + let mut tx = &mut self.transactions[len - 1]; + core::sc_app_layer_decoder_events_set_event_raw(&mut tx.events, + event as u8); + self.events += 1; + } + + pub fn parse_request(&mut self, input: &[u8]) -> bool { + match parser::dns_parse_request(input) { + nom::IResult::Done(_, request) => { + if request.header.flags & 0x8000 != 0 { + SCLogDebug!("DNS message is not a request"); + self.set_event(DNSEvent::NotRequest); + return false; + } + + if request.header.flags & 0x0040 != 0 { + SCLogDebug!("Z-flag set on DNS response"); + self.set_event(DNSEvent::ZFlagSet); + return false; + } + + let mut tx = self.new_tx(); + tx.request = Some(request); + self.transactions.push(tx); + return true; + } + nom::IResult::Incomplete(_) => { + // Insufficient data. + SCLogDebug!("Insufficient data while parsing DNS request"); + self.set_event(DNSEvent::MalformedData); + return false; + } + nom::IResult::Error(_) => { + // Error, probably malformed data. + SCLogDebug!("An error occurred while parsing DNS request"); + self.set_event(DNSEvent::MalformedData); + return false; + } + } + } + + pub fn parse_response(&mut self, input: &[u8]) -> bool { + match parser::dns_parse_response(input) { + nom::IResult::Done(_, response) => { + + SCLogDebug!("Response header flags: {}", response.header.flags); + + if response.header.flags & 0x8000 == 0 { + SCLogDebug!("DNS message is not a response"); + self.set_event(DNSEvent::NotResponse); + } + + if response.header.flags & 0x0040 != 0 { + SCLogDebug!("Z-flag set on DNS response"); + self.set_event(DNSEvent::ZFlagSet); + return false; + } + + let mut tx = self.new_tx(); + tx.response = Some(response); + self.transactions.push(tx); + return true; + } + nom::IResult::Incomplete(_) => { + // Insufficient data. + SCLogDebug!("Insufficient data while parsing DNS response"); + self.set_event(DNSEvent::MalformedData); + return false; + } + nom::IResult::Error(_) => { + // Error, probably malformed data. + SCLogDebug!("An error occurred while parsing DNS response"); + self.set_event(DNSEvent::MalformedData); + return false; + } + } + } + + /// TCP variation of response request parser to handle the length + /// prefix as well as buffering. + /// + /// Always buffer and read from the buffer. Should optimize to skip + /// the buffer if not needed. + pub fn parse_request_tcp(&mut self, input: &[u8]) -> i8 { + self.request_buffer.extend_from_slice(input); + + while self.request_buffer.len() > 0 { + let size = match nom::be_u16(&self.request_buffer) { + nom::IResult::Done(_, len) => { + len as usize + } + _ => 0 as usize + }; + SCLogDebug!("Have {} bytes, need {} to parse", + self.request_buffer.len(), size); + if size > 0 && self.request_buffer.len() >= size { + let msg: Vec = self.request_buffer.drain(0..(size + 2)) + .collect(); + if self.parse_request(&msg[2..]) { + continue; + } + return -1; + } + SCLogDebug!("Not enough DNS traffic to parse."); + return 0; + } + return 0; + } + + /// TCP variation of the response parser to handle the length + /// prefix as well as buffering. + /// + /// Always buffer and read from the buffer. Should optimize to skip + /// the buffer if not needed. + pub fn parse_response_tcp(&mut self, input: &[u8]) -> i8 { + self.response_buffer.extend_from_slice(input); + let size = match nom::be_u16(&self.response_buffer) { + nom::IResult::Done(_, len) => { + len as usize + } + _ => 0 as usize + }; + if size > 0 && self.response_buffer.len() + 2 >= size { + let msg: Vec = self.response_buffer.drain(0..(size + 2)) + .collect(); + if self.parse_response(&msg[2..]) { + return 1; + } + return -1; + } + 0 + } +} + +/// Implement Drop for DNSState as transactions need to do some +/// explicit cleanup. +impl Drop for DNSState { + fn drop(&mut self) { + self.free(); + } +} + +/// Returns *mut DNSState +#[no_mangle] +pub extern "C" fn rs_dns_state_new() -> *mut libc::c_void { + let state = DNSState::new(); + let boxed = Box::new(state); + return unsafe{transmute(boxed)}; +} + +/// Returns *mut DNSState +#[no_mangle] +pub extern "C" fn rs_dns_state_tcp_new() -> *mut libc::c_void { + let state = DNSState::new_tcp(); + let boxed = Box::new(state); + return unsafe{transmute(boxed)}; +} + +/// Params: +/// - state: *mut DNSState as void pointer +#[no_mangle] +pub extern "C" fn rs_dns_state_free(state: *mut libc::c_void) { + // Just unbox... + let _drop: Box = unsafe{transmute(state)}; +} + +#[no_mangle] +pub extern "C" fn rs_dns_state_tx_free(state: &mut DNSState, + tx_id: libc::uint64_t) +{ + state.free_tx(tx_id); +} + +/// C binding parse a DNS request. Returns 1 on success, -1 on failure. +#[no_mangle] +pub extern "C" fn rs_dns_parse_request(_flow: *mut core::Flow, + state: &mut DNSState, + _pstate: *mut libc::c_void, + input: *mut libc::uint8_t, + input_len: libc::uint32_t, + _data: *mut libc::c_void) + -> libc::int8_t { + let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)}; + if state.parse_request(buf) { + 1 + } else { + -1 + } +} + +#[no_mangle] +pub extern "C" fn rs_dns_parse_response(_flow: *mut core::Flow, + state: &mut DNSState, + _pstate: *mut libc::c_void, + input: *mut libc::uint8_t, + input_len: libc::uint32_t, + _data: *mut libc::c_void) + -> libc::int8_t { + let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)}; + if state.parse_response(buf) { + 1 + } else { + -1 + } +} + +/// C binding parse a DNS request. Returns 1 on success, -1 on failure. +#[no_mangle] +pub extern "C" fn rs_dns_parse_request_tcp(_flow: *mut core::Flow, + state: &mut DNSState, + _pstate: *mut libc::c_void, + input: *mut libc::uint8_t, + input_len: libc::uint32_t, + _data: *mut libc::c_void) + -> libc::int8_t { + let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)}; + return state.parse_request_tcp(buf); +} + +#[no_mangle] +pub extern "C" fn rs_dns_parse_response_tcp(_flow: *mut core::Flow, + state: &mut DNSState, + _pstate: *mut libc::c_void, + input: *mut libc::uint8_t, + input_len: libc::uint32_t, + _data: *mut libc::c_void) + -> libc::int8_t { + let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)}; + return state.parse_response_tcp(buf); +} + +#[no_mangle] +pub extern "C" fn rs_dns_state_progress_completion_status( + _direction: libc::uint8_t) + -> libc::c_int +{ + SCLogDebug!("rs_dns_state_progress_completion_status"); + return 1; +} + +#[no_mangle] +pub extern "C" fn rs_dns_tx_get_alstate_progress(_tx: &mut DNSTransaction, + _direction: libc::uint8_t) + -> libc::uint8_t +{ + // This is a stateless parser, just the existence of a transaction + // means its complete. + SCLogDebug!("rs_dns_tx_get_alstate_progress"); + return 1; +} + +#[no_mangle] +pub extern "C" fn rs_dns_tx_set_logged(_state: &mut DNSState, + tx: &mut DNSTransaction, + logger: libc::uint32_t) +{ + tx.logged |= logger; +} + +#[no_mangle] +pub extern "C" fn rs_dns_tx_get_logged(_state: &mut DNSState, + tx: &mut DNSTransaction, + logger: libc::uint32_t) + -> i8 +{ + if tx.logged & logger != 0 { + return 1; + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_dns_state_get_tx_count(state: &mut DNSState) + -> libc::uint64_t +{ + SCLogDebug!("rs_dns_state_get_tx_count: returning {}", state.tx_id); + return state.tx_id; +} + +#[no_mangle] +pub extern "C" fn rs_dns_state_get_tx(state: &mut DNSState, + tx_id: libc::uint64_t) + -> *mut DNSTransaction +{ + match state.get_tx(tx_id) { + Some(tx) => { + return unsafe{transmute(tx)}; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub extern "C" fn rs_dns_state_has_detect_state(state: &mut DNSState) -> u8 +{ + if state.de_state_count > 0 { + return 1; + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_dns_state_set_tx_detect_state( + state: &mut DNSState, + tx: &mut DNSTransaction, + de_state: &mut core::DetectEngineState) +{ + state.de_state_count += 1; + tx.de_state = Some(de_state); +} + +#[no_mangle] +pub extern "C" fn rs_dns_state_get_tx_detect_state( + tx: &mut DNSTransaction) + -> *mut core::DetectEngineState +{ + match tx.de_state { + Some(ds) => { + return ds; + }, + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub extern "C" fn rs_dns_state_has_events(state: &mut DNSState) -> u8 { + if state.events > 0 { + return 1; + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_dns_state_get_events(state: &mut DNSState, + tx_id: libc::uint64_t) + -> *mut core::AppLayerDecoderEvents +{ + match state.get_tx(tx_id) { + Some(tx) => { + return tx.events; + } + _ => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub extern "C" fn rs_dns_tx_get_query_name(tx: &mut DNSTransaction, + i: libc::uint16_t, + buf: *mut *const libc::uint8_t, + len: *mut libc::uint32_t) + -> libc::uint8_t +{ + for request in &tx.request { + if (i as usize) < request.queries.len() { + let query = &request.queries[i as usize]; + if query.name.len() > 0 { + unsafe { + *len = query.name.len() as libc::uint32_t; + *buf = query.name.as_ptr(); + } + return 1; + } + } + } + return 0; +} + +/// Get the DNS transaction ID of a transaction. +// +/// extern uint16_t rs_dns_tx_get_tx_id(RSDNSTransaction *); +#[no_mangle] +pub extern "C" fn rs_dns_tx_get_tx_id(tx: &mut DNSTransaction) -> libc::uint16_t +{ + return tx.tx_id() +} + +/// Get the DNS response flags for a transaction. +/// +/// extern uint16_t rs_dns_tx_get_response_flags(RSDNSTransaction *); +#[no_mangle] +pub extern "C" fn rs_dns_tx_get_response_flags(tx: &mut DNSTransaction) + -> libc::uint16_t +{ + return tx.rcode(); +} + +#[no_mangle] +pub extern "C" fn rs_dns_tx_get_query_rrtype(tx: &mut DNSTransaction, + i: libc::uint16_t, + rrtype: *mut libc::uint16_t) + -> libc::uint8_t +{ + for request in &tx.request { + if (i as usize) < request.queries.len() { + let query = &request.queries[i as usize]; + if query.name.len() > 0 { + unsafe { + *rrtype = query.rrtype; + } + return 1; + } + } + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_dns_probe(input: *const libc::uint8_t, len: libc::uint32_t) + -> libc::uint8_t +{ + let slice: &[u8] = unsafe { + std::slice::from_raw_parts(input as *mut u8, len as usize) + }; + match parser::dns_parse_request(slice) { + nom::IResult::Done(_, _) => { + return 1; + } + _ => { + return 0; + } + } +} + +#[no_mangle] +pub extern "C" fn rs_dns_probe_tcp(input: *const libc::uint8_t, + len: libc::uint32_t) + -> libc::uint8_t +{ + let slice: &[u8] = unsafe { + std::slice::from_raw_parts(input as *mut u8, len as usize) + }; + match nom::be_u16(slice) { + nom::IResult::Done(rem, len) => { + if rem.len() >= len as usize { + match parser::dns_parse_request(rem) { + nom::IResult::Done(_, _) => { + return 1; + } + _ => {} + } + } + } + _ => {} + } + return 0; +} + +#[cfg(test)] +mod tests { + + // Playing with vector draining... + #[test] + fn test_drain() { + let buf: &[u8] = &[ + 0x09, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2d, 0x63, 0x66, + 0x07, 0x64, 0x72, 0x6f, 0x70, 0x62, 0x6f, 0x78, + 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, + ]; + let mut v: Vec = Vec::new(); + v.extend_from_slice(buf); + assert_eq!(v.len(), buf.len()); + + // Drain one byte. + let drained: Vec = v.drain(0..1).collect(); + assert_eq!(drained.len(), 1); + assert_eq!(v.len(), buf.len() - 1); + assert_eq!(buf[0], drained[0]); + + // Drain some more. + v.drain(0..8); + assert_eq!(v.len(), buf.len() - 9); + } +} diff --git a/rust/src/dns/log.rs b/rust/src/dns/log.rs new file mode 100644 index 0000000000..3fe4133c84 --- /dev/null +++ b/rust/src/dns/log.rs @@ -0,0 +1,211 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +extern crate libc; + +use std; +use std::string::String; + +use json::*; +use dns::dns::*; +use log::*; + +fn dns_rrtype_string(rrtype: u16) -> String { + match rrtype { + DNS_RTYPE_A => "A", + DNS_RTYPE_CNAME => "CNAME", + DNS_RTYPE_SOA => "SOA", + DNS_RTYPE_PTR => "PTR", + DNS_RTYPE_MX => "MX", + DNS_RTYPE_SSHFP => "SSHFP", + DNS_RTYPE_RRSIG => "RRSIG", + _ => { + return rrtype.to_string(); + } + }.to_string() +} + +fn dns_rcode_string(flags: u16) -> String { + match flags & 0x000f { + DNS_RCODE_NOERROR => "NOERROR", + DNS_RCODE_FORMERR => "FORMERR", + DNS_RCODE_NXDOMAIN => "NXDOMAIN", + _ => { + return (flags & 0x000f).to_string(); + } + }.to_string() +} + +/// Format bytes as an IP address string. +fn dns_print_addr(addr: &Vec) -> std::string::String { + if addr.len() == 4 { + return format!("{}.{}.{}.{}", addr[0], addr[1], addr[2], addr[3]); + } + else if addr.len() == 16 { + return format!("{}{}:{}{}:{}{}:{}{}:{}{}:{}{}:{}{}:{}{}", + addr[0], + addr[1], + addr[2], + addr[3], + addr[4], + addr[5], + addr[6], + addr[7], + addr[8], + addr[9], + addr[10], + addr[11], + addr[12], + addr[13], + addr[14], + addr[15]); + } + else { + return "".to_string(); + } +} + +/// Log the SSHPF in an DNSAnswerEntry. +fn dns_log_sshfp(js: &Json, answer: &DNSAnswerEntry) +{ + // Need at least 3 bytes - TODO: log something if we don't? + if answer.data_len < 3 { + return; + } + + let sshfp = Json::object(); + + let mut hex = Vec::new(); + for byte in &answer.data[2..] { + hex.push(format!("{:02x}", byte)); + } + sshfp.set_string("fingerprint", &hex.join(":")); + sshfp.set_integer("algo", answer.data[0] as u64); + sshfp.set_integer("type", answer.data[1] as u64); + + js.set("sshfp", sshfp); +} + +#[no_mangle] +pub extern "C" fn rs_dns_log_json_query(tx: &mut DNSTransaction, + i: libc::uint16_t) + -> *mut JsonT +{ + SCLogDebug!("rs_dns_log_json_query: tx_id={}, i={}", tx.id, i); + let index = i as usize; + for request in &tx.request { + if index < request.queries.len() { + let query = &request.queries[index]; + let js = Json::object(); + js.set_string("type", "query"); + js.set_integer("id", request.header.tx_id as u64); + js.set_string("rrname", query.name()); + js.set_string("rrtype", &dns_rrtype_string(query.rrtype)); + js.set_integer("tx_id", tx.id - 1); + return js.unwrap(); + } + } + + return std::ptr::null_mut(); +} + +fn dns_log_json_answer(header: &DNSHeader, answer: &DNSAnswerEntry) + -> Json +{ + let js = Json::object(); + + js.set_string("type", "answer"); + js.set_integer("id", header.tx_id as u64); + js.set_string("rcode", &dns_rcode_string(header.flags)); + js.set_string("rrname", answer.name()); + js.set_string("rrtype", &dns_rrtype_string(answer.rrtype)); + js.set_integer("ttl", answer.ttl as u64); + + match answer.rrtype { + DNS_RTYPE_A | DNS_RTYPE_AAAA => { + js.set_string("rdata", &dns_print_addr(&answer.data)); + } + DNS_RTYPE_CNAME | + DNS_RTYPE_MX | + DNS_RTYPE_PTR => { + js.set_string("rdata", answer.data_to_string()); + }, + DNS_RTYPE_SSHFP => { + dns_log_sshfp(&js, &answer); + }, + _ => {} + } + + return js; +} + +fn dns_log_json_failure(r: &DNSResponse, index: usize) -> * mut JsonT { + if index >= r.queries.len() { + return std::ptr::null_mut(); + } + + let ref query = r.queries[index]; + + let js = Json::object(); + + js.set_string("type", "answer"); + js.set_integer("id", r.header.tx_id as u64); + js.set_string("rcode", &dns_rcode_string(r.header.flags)); + js.set_string("rrname", std::str::from_utf8(&query.name[..]).unwrap()); + + return js.unwrap(); +} + +#[no_mangle] +pub extern "C" fn rs_dns_log_json_answer(tx: &mut DNSTransaction, + i: libc::uint16_t) + -> *mut JsonT +{ + let index = i as usize; + for response in &tx.response { + if response.header.flags & 0x000f > 0 { + if index == 0 { + return dns_log_json_failure(response, index); + } + break; + } + if index >= response.answers.len() { + break; + } + let answer = &response.answers[index]; + let js = dns_log_json_answer(&response.header, answer); + return js.unwrap(); + } + return std::ptr::null_mut(); +} + +#[no_mangle] +pub extern "C" fn rs_dns_log_json_authority(tx: &mut DNSTransaction, + i: libc::uint16_t) + -> *mut JsonT +{ + let index = i as usize; + for response in &tx.response { + if index >= response.authorities.len() { + break; + } + let answer = &response.authorities[index]; + let js = dns_log_json_answer(&response.header, answer); + return js.unwrap(); + } + return std::ptr::null_mut(); +} diff --git a/rust/src/dns/mod.rs b/rust/src/dns/mod.rs index d1821c3c4f..d6693b0a9a 100644 --- a/rust/src/dns/mod.rs +++ b/rust/src/dns/mod.rs @@ -15,26 +15,6 @@ * 02110-1301, USA. */ -use log::*; -use conf; - pub mod parser; -pub use self::parser::*; - pub mod dns; -pub use self::dns::*; - -#[no_mangle] -pub extern "C" fn rs_dns_init() { - SCLogNotice!("Initializing DNS analyzer"); - - match conf::conf_get("app-layer.protocols.dns.tcp.enabled") { - Some(val) => SCLogNotice!("- TCP is enabled: {}", val), - None => SCLogNotice!("- TCP is not enabled."), - } - - match conf::conf_get("app-layer.protocols.dns.udp.enabled") { - Some(val) => SCLogNotice!("- UDP is enabled: {}", val), - None => SCLogNotice!("- UDP is not enabled."), - } -} +pub mod log; diff --git a/rust/src/dns/parser.rs b/rust/src/dns/parser.rs index c72ac97b40..8794b311db 100644 --- a/rust/src/dns/parser.rs +++ b/rust/src/dns/parser.rs @@ -19,7 +19,7 @@ use nom::{be_u8, be_u16, be_u32}; use nom; -use dns::*; +use dns::dns::*; /// Parse a DNS header. named!(pub dns_parse_header, @@ -147,19 +147,19 @@ pub fn dns_parse_response<'a>(slice: &'a [u8]) )); let response = closure!(&'a [u8], do_parse!( - header: dns_parse_header >> - queries: count!(apply!(dns_parse_query, slice), - header.questions as usize) >> - answers: count!(answer_parser, header.answer_rr as usize) >> - authorities: count!(answer_parser, header.authority_rr as usize) >> - ( - DNSResponse{ - header: header, - queries: queries, - answers: answers, - authorities: authorities, - } - ) + header: dns_parse_header + >> queries: count!(apply!(dns_parse_query, slice), + header.questions as usize) + >> answers: count!(answer_parser, header.answer_rr as usize) + >> authorities: count!(answer_parser, header.authority_rr as usize) + >> ( + DNSResponse{ + header: header, + queries: queries, + answers: answers, + authorities: authorities, + } + ) ))(slice); return response; diff --git a/src/Makefile.am b/src/Makefile.am index 09325831c0..a8bc12607f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,7 +20,9 @@ app-layer-dnp3.c app-layer-dnp3.h \ app-layer-dnp3-objects.c app-layer-dnp3-objects.h \ app-layer-dns-common.c app-layer-dns-common.h \ app-layer-dns-tcp.c app-layer-dns-tcp.h \ +app-layer-dns-tcp-rust.c app-layer-dns-tcp-rust.h \ app-layer-dns-udp.c app-layer-dns-udp.h \ +app-layer-dns-udp-rust.c app-layer-dns-udp-rust.h \ app-layer-enip.c app-layer-enip.h \ app-layer-enip-common.c app-layer-enip-common.h \ app-layer-events.c app-layer-events.h \ diff --git a/src/app-layer-dns-common.h b/src/app-layer-dns-common.h index 2e069d0f4b..51f3763de0 100644 --- a/src/app-layer-dns-common.h +++ b/src/app-layer-dns-common.h @@ -124,6 +124,10 @@ enum { DNS_DECODER_EVENT_STATE_MEMCAP_REACHED, }; +/** Opaque Rust types. */ +typedef struct RSDNSState_ RSDNSState; +typedef struct RSDNSTransaction_ RSDNSTransaction; + /** \brief DNS packet header */ typedef struct DNSHeader_ { uint16_t tx_id; diff --git a/src/app-layer-dns-tcp-rust.c b/src/app-layer-dns-tcp-rust.c new file mode 100644 index 0000000000..8c94f7d6bb --- /dev/null +++ b/src/app-layer-dns-tcp-rust.c @@ -0,0 +1,204 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "suricata-common.h" +#include "suricata.h" + +#include "app-layer-protos.h" +#include "app-layer-detect-proto.h" +#include "app-layer-parser.h" +#include "app-layer-dns-common.h" + +#ifdef HAVE_RUST + +#include "app-layer-dns-tcp-rust.h" +#include "rust-dns-dns-gen.h" + +static void RustDNSTCPParserRegisterTests(void); + +static int RustDNSTCPParseRequest(Flow *f, void *state, + AppLayerParserState *pstate, uint8_t *input, uint32_t input_len, + void *local_data) +{ + SCLogDebug("RustDNSTCPParseRequest"); + return rs_dns_parse_request_tcp(f, state, pstate, input, input_len, + local_data); +} + +static int RustDNSTCPParseResponse(Flow *f, void *state, + AppLayerParserState *pstate, uint8_t *input, uint32_t input_len, + void *local_data) +{ + SCLogDebug("RustDNSTCPParseResponse"); + return rs_dns_parse_response_tcp(f, state, pstate, input, input_len, + local_data); +} + +static uint16_t RustDNSTCPProbe(uint8_t *input, uint32_t len, uint32_t *offset) +{ + SCLogDebug("RustDNSTCPProbe"); + if (len == 0 || len < sizeof(DNSHeader)) { + return ALPROTO_UNKNOWN; + } + + // Validate and return ALPROTO_FAILED if needed. + if (!rs_dns_probe_tcp(input, len)) { + return ALPROTO_FAILED; + } + + return ALPROTO_DNS; +} + +static int RustDNSGetAlstateProgress(void *tx, uint8_t direction) +{ + return rs_dns_tx_get_alstate_progress(tx, direction); +} + +static uint64_t RustDNSGetTxCnt(void *alstate) +{ + return rs_dns_state_get_tx_count(alstate); +} + +static void *RustDNSGetTx(void *alstate, uint64_t tx_id) +{ + return rs_dns_state_get_tx(alstate, tx_id); +} + +static void RustDNSSetTxLogged(void *alstate, void *tx, uint32_t logger) +{ + rs_dns_tx_set_logged(alstate, tx, logger); +} + +static int RustDNSGetTxLogged(void *alstate, void *tx, uint32_t logger) +{ + return rs_dns_tx_get_logged(alstate, tx, logger); +} + +static void RustDNSStateTransactionFree(void *state, uint64_t tx_id) +{ + rs_dns_state_tx_free(state, tx_id); +} + +static int RustDNSStateHasTxDetectState(void *state) +{ + return rs_dns_state_has_detect_state(state); +} + +static DetectEngineState *RustDNSGetTxDetectState(void *tx) +{ + return rs_dns_state_get_tx_detect_state(tx); +} + +static int RustDNSSetTxDetectState(void *state, void *tx, + DetectEngineState *s) +{ + rs_dns_state_set_tx_detect_state(state, tx, s); + return 0; +} + +static int RustDNSHasEvents(void *state) +{ + return rs_dns_state_has_events(state); +} + +static AppLayerDecoderEvents *RustDNSGetEvents(void *state, uint64_t id) +{ + return rs_dns_state_get_events(state, id); +} + +void RegisterRustDNSTCPParsers(void) +{ + const char *proto_name = "dns"; + + /** DNS */ + if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) { + AppLayerProtoDetectRegisterProtocol(ALPROTO_DNS, proto_name); + + if (RunmodeIsUnittests()) { + AppLayerProtoDetectPPRegister(IPPROTO_TCP, "53", ALPROTO_DNS, 0, + sizeof(DNSHeader) + 2, STREAM_TOSERVER, RustDNSTCPProbe, + NULL); + } else { + int have_cfg = AppLayerProtoDetectPPParseConfPorts("tcp", + IPPROTO_TCP, proto_name, ALPROTO_DNS, 0, + sizeof(DNSHeader) + 2, RustDNSTCPProbe, RustDNSTCPProbe); + /* if we have no config, we enable the default port 53 */ + if (!have_cfg) { + SCLogWarning(SC_ERR_DNS_CONFIG, "no DNS TCP config found, " + "enabling DNS detection on " + "port 53."); + AppLayerProtoDetectPPRegister(IPPROTO_TCP, "53", ALPROTO_DNS, 0, + sizeof(DNSHeader) + 2, STREAM_TOSERVER, RustDNSTCPProbe, + RustDNSTCPProbe); + } + } + } else { + SCLogInfo("Protocol detection and parser disabled for %s protocol.", + proto_name); + return; + } + + if (AppLayerParserConfParserEnabled("tcp", proto_name)) { + AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_DNS, STREAM_TOSERVER, + RustDNSTCPParseRequest); + AppLayerParserRegisterParser(IPPROTO_TCP , ALPROTO_DNS, STREAM_TOCLIENT, + RustDNSTCPParseResponse); + AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_DNS, + rs_dns_state_tcp_new, rs_dns_state_free); + AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_DNS, + RustDNSStateTransactionFree); + AppLayerParserRegisterGetEventsFunc(IPPROTO_TCP, ALPROTO_DNS, + RustDNSGetEvents); + AppLayerParserRegisterHasEventsFunc(IPPROTO_TCP, ALPROTO_DNS, + RustDNSHasEvents); + AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_DNS, + RustDNSStateHasTxDetectState, RustDNSGetTxDetectState, + RustDNSSetTxDetectState); + AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_DNS, RustDNSGetTx); + AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_DNS, + RustDNSGetTxCnt); + AppLayerParserRegisterLoggerFuncs(IPPROTO_TCP, ALPROTO_DNS, + RustDNSGetTxLogged, RustDNSSetTxLogged); + AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_DNS, + RustDNSGetAlstateProgress); + AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_DNS, + rs_dns_state_progress_completion_status); + DNSAppLayerRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_DNS); + } else { + SCLogInfo("Parsed disabled for %s protocol. Protocol detection" + "still on.", proto_name); + } + +#ifdef UNITTESTS + AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_DNS, + RustDNSTCPParserRegisterTests); +#endif + + return; +} + +#ifdef UNITTESTS +#endif /* UNITTESTS */ + +void RustDNSTCPParserRegisterTests(void) +{ +#if 0 + UtRegisterTest("DNSTCPParserTestMultiRecord", DNSTCPParserTestMultiRecord); +#endif +} + +#endif /* HAVE_RUST */ diff --git a/src/app-layer-dns-tcp-rust.h b/src/app-layer-dns-tcp-rust.h new file mode 100644 index 0000000000..740949656f --- /dev/null +++ b/src/app-layer-dns-tcp-rust.h @@ -0,0 +1,23 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef __APP_LAYER_DNS_TCP_RUST_H__ +#define __APP_LAYER_DNS_TCP_RUST_H__ + +void RegisterRustDNSTCPParsers(void); + +#endif /* !__APP_LAYER_DNS_TCP_RUST_H__ */ diff --git a/src/app-layer-dns-tcp.c b/src/app-layer-dns-tcp.c index cb88232e20..785ef54150 100644 --- a/src/app-layer-dns-tcp.c +++ b/src/app-layer-dns-tcp.c @@ -47,6 +47,10 @@ #include "app-layer-dns-tcp.h" +#ifdef HAVE_RUST +#include "app-layer-dns-tcp-rust.h" +#endif + struct DNSTcpHeader_ { uint16_t len; uint16_t tx_id; @@ -693,7 +697,10 @@ static uint16_t DNSTcpProbeResponse(uint8_t *input, uint32_t len, void RegisterDNSTCPParsers(void) { const char *proto_name = "dns"; - +#ifdef HAVE_RUST + RegisterRustDNSTCPParsers(); + return; +#endif /** DNS */ if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) { AppLayerProtoDetectRegisterProtocol(ALPROTO_DNS, proto_name); diff --git a/src/app-layer-dns-udp-rust.c b/src/app-layer-dns-udp-rust.c new file mode 100644 index 0000000000..cc189a1e49 --- /dev/null +++ b/src/app-layer-dns-udp-rust.c @@ -0,0 +1,197 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "suricata-common.h" +#include "suricata.h" + +#include "app-layer-protos.h" +#include "app-layer-detect-proto.h" +#include "app-layer-parser.h" +#include "app-layer-dns-common.h" + +#ifdef HAVE_RUST + +#include "app-layer-dns-udp-rust.h" +#include "rust-dns-dns-gen.h" + +static int RustDNSUDPParseRequest(Flow *f, void *state, + AppLayerParserState *pstate, uint8_t *input, uint32_t input_len, + void *local_data) +{ + return rs_dns_parse_request(f, state, pstate, input, input_len, + local_data); +} + +static int RustDNSUDPParseResponse(Flow *f, void *state, + AppLayerParserState *pstate, uint8_t *input, uint32_t input_len, + void *local_data) +{ + return rs_dns_parse_response(f, state, pstate, input, input_len, + local_data); +} + +static uint16_t DNSUDPProbe(uint8_t *input, uint32_t len, uint32_t *offset) +{ + if (len == 0 || len < sizeof(DNSHeader)) { + return ALPROTO_UNKNOWN; + } + + // Validate and return ALPROTO_FAILED if needed. + if (!rs_dns_probe(input, len)) { + return ALPROTO_FAILED; + } + + return ALPROTO_DNS; +} + +static int RustDNSGetAlstateProgress(void *tx, uint8_t direction) +{ + return rs_dns_tx_get_alstate_progress(tx, direction); +} + +static uint64_t RustDNSGetTxCnt(void *alstate) +{ + return rs_dns_state_get_tx_count(alstate); +} + +static void *RustDNSGetTx(void *alstate, uint64_t tx_id) +{ + return rs_dns_state_get_tx(alstate, tx_id); +} + +static void RustDNSSetTxLogged(void *alstate, void *tx, uint32_t logger) +{ + rs_dns_tx_set_logged(alstate, tx, logger); +} + +static int RustDNSGetTxLogged(void *alstate, void *tx, uint32_t logger) +{ + return rs_dns_tx_get_logged(alstate, tx, logger); +} + +static void RustDNSStateTransactionFree(void *state, uint64_t tx_id) +{ + rs_dns_state_tx_free(state, tx_id); +} + +static int RustDNSStateHasTxDetectState(void *state) +{ + return rs_dns_state_has_detect_state(state); +} + +static DetectEngineState *RustDNSGetTxDetectState(void *tx) +{ + return rs_dns_state_get_tx_detect_state(tx); +} + +static int RustDNSSetTxDetectState(void *state, void *tx, + DetectEngineState *s) +{ + rs_dns_state_set_tx_detect_state(state, tx, s); + return 0; +} + +static int RustDNSHasEvents(void *state) +{ + return rs_dns_state_has_events(state); +} + +static AppLayerDecoderEvents *RustDNSGetEvents(void *state, uint64_t id) +{ + return rs_dns_state_get_events(state, id); +} + +void RegisterRustDNSUDPParsers(void) +{ + const char *proto_name = "dns"; + + /** DNS */ + if (AppLayerProtoDetectConfProtoDetectionEnabled("udp", proto_name)) { + AppLayerProtoDetectRegisterProtocol(ALPROTO_DNS, proto_name); + + if (RunmodeIsUnittests()) { + AppLayerProtoDetectPPRegister(IPPROTO_UDP, "53", ALPROTO_DNS, 0, + sizeof(DNSHeader), STREAM_TOSERVER, DNSUDPProbe, + NULL); + } else { + int have_cfg = AppLayerProtoDetectPPParseConfPorts("udp", + IPPROTO_UDP, proto_name, ALPROTO_DNS, 0, sizeof(DNSHeader), + DNSUDPProbe, NULL); + + /* If no config, enable on port 53. */ + if (!have_cfg) { +#ifndef AFLFUZZ_APPLAYER + SCLogWarning(SC_ERR_DNS_CONFIG, "no DNS UDP config found, " + "enabling DNS detection on port 53."); +#endif + AppLayerProtoDetectPPRegister(IPPROTO_UDP, "53", ALPROTO_DNS, + 0, sizeof(DNSHeader), STREAM_TOSERVER, + DNSUDPProbe, NULL); + } + } + } else { + SCLogInfo("Protocol detection and parser disabled for %s protocol.", + proto_name); + return; + } + + if (AppLayerParserConfParserEnabled("udp", proto_name)) { + AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_DNS, STREAM_TOSERVER, + RustDNSUDPParseRequest); + AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_DNS, STREAM_TOCLIENT, + RustDNSUDPParseResponse); + AppLayerParserRegisterStateFuncs(IPPROTO_UDP, ALPROTO_DNS, + rs_dns_state_new, rs_dns_state_free); + AppLayerParserRegisterTxFreeFunc(IPPROTO_UDP, ALPROTO_DNS, + RustDNSStateTransactionFree); + AppLayerParserRegisterGetEventsFunc(IPPROTO_UDP, ALPROTO_DNS, + RustDNSGetEvents); + AppLayerParserRegisterHasEventsFunc(IPPROTO_UDP, ALPROTO_DNS, + RustDNSHasEvents); + AppLayerParserRegisterDetectStateFuncs(IPPROTO_UDP, ALPROTO_DNS, + RustDNSStateHasTxDetectState, RustDNSGetTxDetectState, + RustDNSSetTxDetectState); + + AppLayerParserRegisterGetTx(IPPROTO_UDP, ALPROTO_DNS, RustDNSGetTx); + AppLayerParserRegisterGetTxCnt(IPPROTO_UDP, ALPROTO_DNS, + RustDNSGetTxCnt); + AppLayerParserRegisterLoggerFuncs(IPPROTO_UDP, ALPROTO_DNS, + RustDNSGetTxLogged, RustDNSSetTxLogged); + AppLayerParserRegisterGetStateProgressFunc(IPPROTO_UDP, ALPROTO_DNS, + RustDNSGetAlstateProgress); + + AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_DNS, + rs_dns_state_progress_completion_status); + + DNSAppLayerRegisterGetEventInfo(IPPROTO_UDP, ALPROTO_DNS); + +#if 0 + DNSUDPConfigure(); +#endif + } else { + SCLogInfo("Parsed disabled for %s protocol. Protocol detection" + "still on.", proto_name); + } +#if 0 +#ifdef UNITTESTS + AppLayerParserRegisterProtocolUnittests(IPPROTO_UDP, ALPROTO_DNS, + DNSUDPParserRegisterTests); +#endif +#endif +} + +#endif /* HAVE_RUST */ diff --git a/src/app-layer-dns-udp-rust.h b/src/app-layer-dns-udp-rust.h new file mode 100644 index 0000000000..e45737589e --- /dev/null +++ b/src/app-layer-dns-udp-rust.h @@ -0,0 +1,23 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef __APP_LAYER_DNS_UDP_RUST_H__ +#define __APP_LAYER_DNS_UDP_RUST_H__ + +void RegisterRustDNSUDPParsers(void); + +#endif /* !__APP_LAYER_DNS_UDP_RUST_H__ */ diff --git a/src/app-layer-dns-udp.c b/src/app-layer-dns-udp.c index 8d6ae4d2a6..29a89b4955 100644 --- a/src/app-layer-dns-udp.c +++ b/src/app-layer-dns-udp.c @@ -51,7 +51,7 @@ #include "app-layer-dns-udp.h" #ifdef HAVE_RUST -#include "rust.h" +#include "app-layer-dns-udp-rust.h" #endif /** \internal @@ -390,9 +390,7 @@ void RegisterDNSUDPParsers(void) const char *proto_name = "dns"; #ifdef HAVE_RUST - /* If DNS was implemented in Rust, we could call into the rust - * init function here. */ - rs_dns_init(); + return RegisterRustDNSUDPParsers(); #endif /** DNS */ diff --git a/src/detect-engine-dns.c b/src/detect-engine-dns.c index 2891d08831..8226920191 100644 --- a/src/detect-engine-dns.c +++ b/src/detect-engine-dns.c @@ -48,6 +48,10 @@ #include "util-unittest-helper.h" #include "util-validate.h" +#ifdef HAVE_RUST +#include "rust-dns-dns-gen.h" +#endif + /** \brief Do the content inspection & validation for a signature * * \param de_ctx Detection engine context @@ -67,14 +71,32 @@ int DetectEngineInspectDnsQueryName(ThreadVars *tv, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id) { - DNSTransaction *tx = (DNSTransaction *)txv; - DNSQueryEntry *query = NULL; uint8_t *buffer; - uint16_t buffer_len; + uint32_t buffer_len; int r = 0; SCLogDebug("start"); +#ifdef HAVE_RUST + for (uint16_t i = 0;; i++) { + det_ctx->discontinue_matching = 0; + det_ctx->buffer_offset = 0; + det_ctx->inspection_recursion_counter = 0; + + if (rs_dns_tx_get_query_name(txv, i, &buffer, &buffer_len)) { + r = DetectEngineContentInspection(de_ctx, det_ctx, + s, smd, f, buffer, buffer_len, 0, + DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE, NULL); + if (r == 1) { + break; + } + } else { + break; + } + } +#else + DNSTransaction *tx = (DNSTransaction *)txv; + DNSQueryEntry *query = NULL; TAILQ_FOREACH(query, &tx->query_list, next) { SCLogDebug("tx %p query %p", tx, query); det_ctx->discontinue_matching = 0; @@ -93,6 +115,7 @@ int DetectEngineInspectDnsQueryName(ThreadVars *tv, if (r == 1) break; } +#endif return r; } @@ -110,8 +133,23 @@ static void PrefilterTxDnsQuery(DetectEngineThreadCtx *det_ctx, const uint64_t idx, const uint8_t flags) { SCEnter(); - const MpmCtx *mpm_ctx = (MpmCtx *)pectx; + +#ifdef HAVE_RUST + uint8_t *buffer; + uint32_t buffer_len; + for (uint16_t i = 0; i < 0xffff; i++) { + if (rs_dns_tx_get_query_name(txv, i, &buffer, &buffer_len)) { + if (buffer_len >= mpm_ctx->minlen) { + (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx, + &det_ctx->mtcu, &det_ctx->pmq, + buffer, buffer_len); + } + } else { + break; + } + } +#else DNSTransaction *tx = (DNSTransaction *)txv; DNSQueryEntry *query = NULL; @@ -128,6 +166,7 @@ static void PrefilterTxDnsQuery(DetectEngineThreadCtx *det_ctx, buffer, buffer_len); } } +#endif } int PrefilterTxDnsQueryRegister(SigGroupHead *sgh, MpmCtx *mpm_ctx) diff --git a/src/log-dnslog.c b/src/log-dnslog.c index df3e19c39d..7b1ce7bc44 100644 --- a/src/log-dnslog.c +++ b/src/log-dnslog.c @@ -164,6 +164,10 @@ static void LogAnswer(LogDnsLogThread *aft, char *timebuf, char *srcip, char *ds static int LogDnsLogger(ThreadVars *tv, void *data, const Packet *p, Flow *f, void *state, void *tx, uint64_t tx_id, uint8_t direction) { +#ifdef HAVE_RUST + SCLogNotice("LogDnsLogger not implemented for Rust DNS."); + return 0; +#endif LogDnsLogThread *aft = (LogDnsLogThread *)data; DNSTransaction *dns_tx = (DNSTransaction *)tx; SCLogDebug("pcap_cnt %"PRIu64, p->pcap_cnt); diff --git a/src/output-json-dns.c b/src/output-json-dns.c index a438fb0164..98b10129ca 100644 --- a/src/output-json-dns.c +++ b/src/output-json-dns.c @@ -53,6 +53,10 @@ #ifdef HAVE_LIBJANSSON +#ifdef HAVE_RUST +#include "rust-dns-log-gen.h" +#endif + /* we can do query logging as well, but it's disabled for now as the * TX id handling doesn't expect it */ #define QUERY 0 @@ -260,6 +264,7 @@ typedef struct LogDnsLogThread_ { MemBuffer *buffer; } LogDnsLogThread; +#ifndef HAVE_RUST static int DNSRRTypeEnabled(uint16_t type, uint64_t flags) { if (likely(flags == ~0UL)) { @@ -387,7 +392,9 @@ static int DNSRRTypeEnabled(uint16_t type, uint64_t flags) return 0; } } +#endif +#ifndef HAVE_RUST static void LogQuery(LogDnsLogThread *aft, json_t *js, DNSTransaction *tx, uint64_t tx_id, DNSQueryEntry *entry) __attribute__((nonnull)); @@ -435,7 +442,9 @@ static void LogQuery(LogDnsLogThread *aft, json_t *js, DNSTransaction *tx, OutputJSONBuffer(js, aft->dnslog_ctx->file_ctx, &aft->buffer); json_object_del(js, "dns"); } +#endif +#ifndef HAVE_RUST static void OutputAnswer(LogDnsLogThread *aft, json_t *djs, DNSTransaction *tx, DNSAnswerEntry *entry) __attribute__((nonnull)); @@ -546,7 +555,9 @@ static void OutputAnswer(LogDnsLogThread *aft, json_t *djs, return; } +#endif +#ifndef HAVE_RUST static void OutputFailure(LogDnsLogThread *aft, json_t *djs, DNSTransaction *tx, DNSQueryEntry *entry) __attribute__((nonnull)); @@ -588,7 +599,9 @@ static void OutputFailure(LogDnsLogThread *aft, json_t *djs, return; } +#endif +#ifndef HAVE_RUST static void LogAnswers(LogDnsLogThread *aft, json_t *js, DNSTransaction *tx, uint64_t tx_id) { @@ -616,6 +629,7 @@ static void LogAnswers(LogDnsLogThread *aft, json_t *js, DNSTransaction *tx, uin } } +#endif static int JsonDnsLoggerToServer(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f, void *alstate, void *txptr, uint64_t tx_id) @@ -624,9 +638,30 @@ static int JsonDnsLoggerToServer(ThreadVars *tv, void *thread_data, LogDnsLogThread *td = (LogDnsLogThread *)thread_data; LogDnsFileCtx *dnslog_ctx = td->dnslog_ctx; - DNSTransaction *tx = txptr; json_t *js; + if (unlikely(dnslog_ctx->flags & LOG_QUERIES) == 0) { + return TM_ECODE_OK; + } + +#ifdef HAVE_RUST + for (uint16_t i = 0; i < 0xffff; i++) { + js = CreateJSONHeader((Packet *)p, 1, "dns"); + if (unlikely(js == NULL)) { + return TM_ECODE_OK; + } + json_t *dns = rs_dns_log_json_query(txptr, i); + if (unlikely(dns == NULL)) { + json_decref(js); + break; + } + json_object_set_new(js, "dns", dns); + MemBufferReset(td->buffer); + OutputJSONBuffer(js, td->dnslog_ctx->file_ctx, &td->buffer); + json_decref(js); + } +#else + DNSTransaction *tx = txptr; if (likely(dnslog_ctx->flags & LOG_QUERIES) != 0) { DNSQueryEntry *query = NULL; TAILQ_FOREACH(query, &tx->query_list, next) { @@ -639,6 +674,7 @@ static int JsonDnsLoggerToServer(ThreadVars *tv, void *thread_data, json_decref(js); } } +#endif SCReturnInt(TM_ECODE_OK); } @@ -650,19 +686,48 @@ static int JsonDnsLoggerToClient(ThreadVars *tv, void *thread_data, LogDnsLogThread *td = (LogDnsLogThread *)thread_data; LogDnsFileCtx *dnslog_ctx = td->dnslog_ctx; - DNSTransaction *tx = txptr; json_t *js; - if (likely(dnslog_ctx->flags & LOG_ANSWERS) != 0) { - js = CreateJSONHeader((Packet *)p, 0, "dns"); - if (unlikely(js == NULL)) - return TM_ECODE_OK; + if (unlikely(dnslog_ctx->flags & LOG_ANSWERS) == 0) { + return TM_ECODE_OK; + } - LogAnswers(td, js, tx, tx_id); + js = CreateJSONHeader((Packet *)p, 0, "dns"); - json_decref(js); +#if HAVE_RUST + /* Log answers. */ + for (uint16_t i = 0; i < 0xffff; i++) { + json_t *answer = rs_dns_log_json_answer(txptr, i); + if (answer == NULL) { + break; + } + json_object_set_new(js, "dns", answer); + MemBufferReset(td->buffer); + OutputJSONBuffer(js, td->dnslog_ctx->file_ctx, &td->buffer); + json_object_del(js, "dns"); } + /* Log authorities. */ + for (uint16_t i = 0; i < 0xffff; i++) { + json_t *answer = rs_dns_log_json_authority(txptr, i); + if (answer == NULL) { + break; + } + json_object_set_new(js, "dns", answer); + MemBufferReset(td->buffer); + OutputJSONBuffer(js, td->dnslog_ctx->file_ctx, &td->buffer); + json_object_del(js, "dns"); + } +#else + DNSTransaction *tx = txptr; + if (unlikely(js == NULL)) + return TM_ECODE_OK; + + LogAnswers(td, js, tx, tx_id); +#endif + + json_decref(js); + SCReturnInt(TM_ECODE_OK); } diff --git a/src/rust.h b/src/rust.h index 68314cbd77..b5ee1b5038 100644 --- a/src/rust.h +++ b/src/rust.h @@ -27,6 +27,4 @@ typedef struct SuricataContext_ { void (*AppLayerDecoderEventsFreeEvents)(AppLayerDecoderEvents **); } SuricataContext; -void rs_dns_init(void); - #endif /* !__RUST_H__ */ diff --git a/src/suricata.c b/src/suricata.c index 7ab3ec3af7..afda25d67e 100644 --- a/src/suricata.c +++ b/src/suricata.c @@ -175,7 +175,7 @@ #ifdef HAVE_RUST #include "rust.h" -#include "rust-core.h" +#include "rust-core-gen.h" #endif /* diff --git a/src/util-debug.c b/src/util-debug.c index 027538a1f1..97b1626b2b 100644 --- a/src/util-debug.c +++ b/src/util-debug.c @@ -44,7 +44,7 @@ #include "util-syslog.h" #ifdef HAVE_RUST -#include "rust-log.h" +#include "rust-log-gen.h" #endif #include "conf.h" diff --git a/src/util-lua-dns.c b/src/util-lua-dns.c index da5c99deb3..761b1f7c9a 100644 --- a/src/util-lua-dns.c +++ b/src/util-lua-dns.c @@ -58,11 +58,42 @@ #include "util-lua-common.h" #include "util-lua-dns.h" +#ifdef HAVE_RUST +#include "rust-dns-dns-gen.h" +#endif + static int DnsGetDnsRrname(lua_State *luastate) { if (!(LuaStateNeedProto(luastate, ALPROTO_DNS))) return LuaCallbackError(luastate, "error: protocol not dns"); +#ifdef HAVE_RUST + RSDNSTransaction *tx = LuaStateGetTX(luastate); + if (tx == NULL) + return LuaCallbackError(luastate, "internal error: no tx"); + + for (uint16_t i = 0;; i++) { + uint32_t buf_len; + uint8_t *buf; + if (!rs_dns_tx_get_query_name(tx, i, &buf, &buf_len)) { + break; + } + + char *rrname = BytesToString(buf, buf_len); + if (rrname != NULL) { + size_t input_len = strlen(rrname); + /* sanity check */ + if (input_len > (size_t)(2 * buf_len)) { + SCFree(rrname); + return LuaCallbackError(luastate, "invalid length"); + } + int ret = LuaPushStringBuffer(luastate, (uint8_t *)rrname, + input_len); + SCFree(rrname); + return ret; + } + } +#else DNSTransaction *tx = LuaStateGetTX(luastate); if (tx == NULL) return LuaCallbackError(luastate, "internal error: no tx"); @@ -85,7 +116,7 @@ static int DnsGetDnsRrname(lua_State *luastate) return ret; } } - +#endif return LuaCallbackError(luastate, "no query"); } @@ -93,12 +124,19 @@ static int DnsGetTxid(lua_State *luastate) { if (!(LuaStateNeedProto(luastate, ALPROTO_DNS))) return LuaCallbackError(luastate, "error: protocol not dns"); - +#ifdef HAVE_RUST + RSDNSTransaction *tx = LuaStateGetTX(luastate); + if (tx == NULL) + return LuaCallbackError(luastate, "internal error: no tx"); + uint16_t tx_id = rs_dns_tx_get_tx_id(tx); + lua_pushinteger(luastate, tx_id); +#else DNSTransaction *tx = LuaStateGetTX(luastate); if (tx == NULL) return LuaCallbackError(luastate, "internal error: no tx"); lua_pushinteger(luastate, tx->tx_id); +#endif return 1; } @@ -106,15 +144,24 @@ static int DnsGetRcode(lua_State *luastate) { if (!(LuaStateNeedProto(luastate, ALPROTO_DNS))) return LuaCallbackError(luastate, "error: protocol not dns"); - + uint16_t rcode = 0; +#ifdef HAVE_RUST + RSDNSTransaction *tx = LuaStateGetTX(luastate); + if (tx == NULL) { + return LuaCallbackError(luastate, "internal error: no tx"); + } + uint16_t flags = rs_dns_tx_get_response_flags(tx); + rcode = flags & 0x000f; +#else DNSTransaction *tx = LuaStateGetTX(luastate); if (tx == NULL) return LuaCallbackError(luastate, "internal error: no tx"); - - if (tx->rcode) { - char rcode[16] = ""; - DNSCreateRcodeString(tx->rcode, rcode, sizeof(rcode)); - return LuaPushStringBuffer(luastate, (const uint8_t *)rcode, strlen(rcode)); + rcode = tx->rcode; +#endif + if (rcode) { + char rcode_str[16] = ""; + DNSCreateRcodeString(rcode, rcode_str, sizeof(rcode_str)); + return LuaPushStringBuffer(luastate, (const uint8_t *)rcode_str, strlen(rcode_str)); } else { return 0; } @@ -124,17 +171,77 @@ static int DnsGetRecursionDesired(lua_State *luastate) { if (!(LuaStateNeedProto(luastate, ALPROTO_DNS))) return LuaCallbackError(luastate, "error: protocol not dns"); +#ifdef HAVE_RUST + RSDNSTransaction *tx = LuaStateGetTX(luastate); + if (tx == NULL) + return LuaCallbackError(luastate, "internal error: no tx"); + + uint16_t flags = rs_dns_tx_get_response_flags(tx); + int recursion_desired = flags & 0x0080 ? 1 : 0; + lua_pushboolean(luastate, recursion_desired); +#else DNSTransaction *tx = LuaStateGetTX(luastate); if (tx == NULL) return LuaCallbackError(luastate, "internal error: no tx"); lua_pushboolean(luastate, tx->recursion_desired); +#endif return 1; } static int DnsGetQueryTable(lua_State *luastate) { +#ifdef HAVE_RUST + if (!(LuaStateNeedProto(luastate, ALPROTO_DNS))) + return LuaCallbackError(luastate, "error: protocol not dns"); + + RSDNSTransaction *tx = LuaStateGetTX(luastate); + if (tx == NULL) + return LuaCallbackError(luastate, "internal error: no tx"); + + lua_newtable(luastate); + + uint8_t *name; + uint32_t name_len; + + for (uint16_t i = 0;; i++) { + + if (!rs_dns_tx_get_query_name(tx, i, &name, &name_len)) { + break; + } + + lua_pushinteger(luastate, i); + lua_newtable(luastate); + + uint16_t rrtype; + if (rs_dns_tx_get_query_rrtype(tx, i, &rrtype)) { + char s_rrtype[16] = ""; + DNSCreateTypeString(rrtype, s_rrtype, sizeof(s_rrtype)); + lua_pushstring(luastate, "type"); + lua_pushstring(luastate, s_rrtype); + lua_settable(luastate, -3); + } + + char *s = BytesToString(name, name_len); + if (s != NULL) { + size_t slen = strlen(s); + if (slen > name_len * 2) { + SCFree(s); + return LuaCallbackError(luastate, "invalid length"); + } + lua_pushstring(luastate, "rrname"); + LuaPushStringBuffer(luastate, (uint8_t *)s, slen); + lua_settable(luastate, -3); + SCFree(s); + } + + lua_pushinteger(luastate, i++); + + } + + return 1; +#else if (!(LuaStateNeedProto(luastate, ALPROTO_DNS))) return LuaCallbackError(luastate, "error: protocol not dns"); @@ -178,10 +285,15 @@ static int DnsGetQueryTable(lua_State *luastate) } return 1; +#endif } static int DnsGetAnswerTable(lua_State *luastate) { +#ifdef HAVE_RUST + SCLogNotice("DnsGetAnswerTable not implemented for Rust DNS."); + return 1; +#else if (!(LuaStateNeedProto(luastate, ALPROTO_DNS))) return LuaCallbackError(luastate, "error: protocol not dns"); @@ -242,10 +354,15 @@ static int DnsGetAnswerTable(lua_State *luastate) } return 1; +#endif } static int DnsGetAuthorityTable(lua_State *luastate) { +#ifdef HAVE_RUST + SCLogNotice("DnsGetAuthorityTable not implemented for Rust DNS"); + return 1; +#else if (!(LuaStateNeedProto(luastate, ALPROTO_DNS))) return LuaCallbackError(luastate, "error: protocol not dns"); @@ -293,6 +410,7 @@ static int DnsGetAuthorityTable(lua_State *luastate) } return 1; +#endif }