@ -1,4 +1,4 @@
/* Copyright (C) 2017 Open Information Security Foundation
/* Copyright (C) 2017 -2024 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
@ -403,7 +403,7 @@ fn dns_log_opt(opt: &DNSRDataOPT) -> Result<JsonBuilder, JsonError> {
js . close ( ) ? ;
Ok ( js )
}
}
/// Log SOA section fields.
fn dns_log_soa ( soa : & DNSRDataSOA ) -> Result < JsonBuilder , JsonError > {
@ -647,6 +647,97 @@ fn dns_log_json_answer(
Ok ( ( ) )
}
/// V3 style answer logging.
fn dns_log_json_answers (
jb : & mut JsonBuilder , response : & DNSMessage , flags : u64 ,
) -> Result < ( ) , JsonError > {
if ! response . answers . is_empty ( ) {
let mut js_answers = JsonBuilder ::try_new_array ( ) ? ;
// For grouped answers we use a HashMap keyed by the rrtype.
let mut answer_types = HashMap ::new ( ) ;
for answer in & response . answers {
if flags & LOG_FORMAT_GROUPED ! = 0 {
let type_string = dns_rrtype_string ( answer . rrtype ) ;
match & answer . data {
DNSRData ::A ( addr ) | DNSRData ::AAAA ( addr ) = > {
if ! answer_types . contains_key ( & type_string ) {
answer_types
. insert ( type_string . to_string ( ) , JsonBuilder ::try_new_array ( ) ? ) ;
}
if let Some ( a ) = answer_types . get_mut ( & type_string ) {
a . append_string ( & dns_print_addr ( addr ) ) ? ;
}
}
DNSRData ::CNAME ( bytes )
| DNSRData ::MX ( bytes )
| DNSRData ::NS ( bytes )
| DNSRData ::TXT ( bytes )
| DNSRData ::NULL ( bytes )
| DNSRData ::PTR ( bytes ) = > {
if ! answer_types . contains_key ( & type_string ) {
answer_types
. insert ( type_string . to_string ( ) , JsonBuilder ::try_new_array ( ) ? ) ;
}
if let Some ( a ) = answer_types . get_mut ( & type_string ) {
a . append_string_from_bytes ( bytes ) ? ;
}
}
DNSRData ::SOA ( soa ) = > {
if ! answer_types . contains_key ( & type_string ) {
answer_types
. insert ( type_string . to_string ( ) , JsonBuilder ::try_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 ( ) , JsonBuilder ::try_new_array ( ) ? ) ;
}
if let Some ( a ) = answer_types . get_mut ( & type_string ) {
a . append_object ( & dns_log_sshfp ( sshfp ) ? ) ? ;
}
}
DNSRData ::SRV ( srv ) = > {
if ! answer_types . contains_key ( & type_string ) {
answer_types
. insert ( type_string . to_string ( ) , JsonBuilder ::try_new_array ( ) ? ) ;
}
if let Some ( a ) = answer_types . get_mut ( & type_string ) {
a . append_object ( & dns_log_srv ( srv ) ? ) ? ;
}
}
_ = > { }
}
}
if flags & LOG_FORMAT_DETAILED ! = 0 {
js_answers . append_object ( & dns_log_json_answer_detail ( answer ) ? ) ? ;
}
}
js_answers . close ( ) ? ;
if flags & LOG_FORMAT_DETAILED ! = 0 {
jb . set_object ( "answers" , & js_answers ) ? ;
}
if flags & LOG_FORMAT_GROUPED ! = 0 {
jb . open_object ( "grouped" ) ? ;
for ( k , mut v ) in answer_types . drain ( ) {
v . close ( ) ? ;
jb . set_object ( & k , & v ) ? ;
}
jb . close ( ) ? ;
}
}
Ok ( ( ) )
}
fn dns_log_query (
tx : & DNSTransaction , i : u16 , flags : u64 , jb : & mut JsonBuilder ,
) -> Result < bool , JsonError > {
@ -687,6 +778,115 @@ pub extern "C" fn SCDnsLogJsonQuery(
}
}
/// Common logger for DNS requests and responses.
///
/// It is expected that the JsonBuilder is an open object that the DNS
/// transaction will be logged into. This function will not create the
/// "dns" object.
///
/// This logger implements V3 style DNS logging.
fn log_json ( tx : & mut DNSTransaction , flags : u64 , jb : & mut JsonBuilder ) -> Result < ( ) , JsonError > {
jb . open_object ( "dns" ) ? ;
jb . set_int ( "version" , 3 ) ? ;
let message = if let Some ( request ) = & tx . request {
jb . set_string ( "type" , "request" ) ? ;
request
} else if let Some ( response ) = & tx . response {
jb . set_string ( "type" , "response" ) ? ;
response
} else {
debug_validate_fail ! ( "unreachable" ) ;
return Ok ( ( ) ) ;
} ;
// The internal Suricata transaction ID.
jb . set_uint ( "tx_id" , tx . id - 1 ) ? ;
// The on the wire DNS transaction ID.
jb . set_uint ( "id" , tx . tx_id ( ) as u64 ) ? ;
// Log header fields. Should this be a sub-object?
let header = & message . header ;
jb . set_string ( "flags" , format! ( "{:x}" , header . flags ) . as_str ( ) ) ? ;
if header . flags & 0x8000 ! = 0 {
jb . set_bool ( "qr" , true ) ? ;
}
if header . flags & 0x0400 ! = 0 {
jb . set_bool ( "aa" , true ) ? ;
}
if header . flags & 0x0200 ! = 0 {
jb . set_bool ( "tc" , true ) ? ;
}
if header . flags & 0x0100 ! = 0 {
jb . set_bool ( "rd" , true ) ? ;
}
if header . flags & 0x0080 ! = 0 {
jb . set_bool ( "ra" , true ) ? ;
}
if header . flags & 0x0040 ! = 0 {
jb . set_bool ( "z" , true ) ? ;
}
let opcode = ( ( header . flags > > 11 ) & 0xf ) as u8 ;
jb . set_uint ( "opcode" , opcode as u64 ) ? ;
jb . set_string ( "rcode" , & dns_rcode_string ( header . flags ) ) ? ;
if ! message . queries . is_empty ( ) {
jb . open_array ( "queries" ) ? ;
for query in & message . queries {
if dns_log_rrtype_enabled ( query . rrtype , flags ) {
jb . start_object ( ) ?
. set_string_from_bytes ( "rrname" , & query . name ) ?
. set_string ( "rrtype" , & dns_rrtype_string ( query . rrtype ) ) ?
. close ( ) ? ;
}
}
jb . close ( ) ? ;
}
if ! message . answers . is_empty ( ) {
dns_log_json_answers ( jb , message , flags ) ? ;
}
if ! message . authorities . is_empty ( ) {
jb . open_array ( "authorities" ) ? ;
for auth in & message . authorities {
let auth_detail = dns_log_json_answer_detail ( auth ) ? ;
jb . append_object ( & auth_detail ) ? ;
}
jb . close ( ) ? ;
}
if ! message . additionals . is_empty ( ) {
let mut is_jb_open = false ;
for add in & message . additionals {
if let DNSRData ::OPT ( rdata ) = & add . data {
if rdata . is_empty ( ) {
continue ;
}
}
if ! is_jb_open {
jb . open_array ( "additionals" ) ? ;
is_jb_open = true ;
}
let add_detail = dns_log_json_answer_detail ( add ) ? ;
jb . append_object ( & add_detail ) ? ;
}
if is_jb_open {
jb . close ( ) ? ;
}
}
jb . close ( ) ? ;
Ok ( ( ) )
}
/// FFI wrapper around the common V3 style DNS logger.
#[ no_mangle ]
pub extern "C" fn SCDnsLogJson ( tx : & mut DNSTransaction , flags : u64 , jb : & mut JsonBuilder ) -> bool {
log_json ( tx , flags , jb ) . is_ok ( )
}
#[ no_mangle ]
pub extern "C" fn SCDnsLogJsonAnswer (
tx : & DNSTransaction , flags : u64 , js : & mut JsonBuilder ,