-/* 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
js.close()?;
Ok(js)
-}
+}
/// Log SOA section fields.
fn dns_log_soa(soa: &DNSRDataSOA) -> Result<JsonBuilder, JsonError> {
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> {
}
}
+/// 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,
OutputJsonThreadCtx *ctx;
} LogDnsLogThread;
-static JsonBuilder *JsonDNSLogQuery(void *txptr)
-{
- JsonBuilder *queryjb = jb_new_array();
- if (queryjb == NULL) {
- return NULL;
- }
- bool has_query = false;
-
- for (uint16_t i = 0; i < UINT16_MAX; i++) {
- JsonBuilder *js = jb_new_object();
- if (!SCDnsLogJsonQuery((void *)txptr, i, LOG_ALL_RRTYPES, js)) {
- jb_free(js);
- break;
- }
- jb_close(js);
- has_query = true;
- jb_append_object(queryjb, js);
- jb_free(js);
- }
-
- if (!has_query) {
- jb_free(queryjb);
- return NULL;
- }
-
- jb_close(queryjb);
- return queryjb;
-}
-
-static JsonBuilder *JsonDNSLogAnswer(void *txptr)
-{
- if (!SCDnsLogAnswerEnabled(txptr, LOG_ALL_RRTYPES)) {
- return NULL;
- } else {
- JsonBuilder *js = jb_new_object();
- SCDnsLogJsonAnswer(txptr, LOG_ALL_RRTYPES, js);
- jb_close(js);
- return js;
- }
-}
-
bool AlertJsonDns(void *txptr, JsonBuilder *js)
{
- bool r = false;
- jb_open_object(js, "dns");
- JsonBuilder *qjs = JsonDNSLogQuery(txptr);
- if (qjs != NULL) {
- jb_set_object(js, "query", qjs);
- jb_free(qjs);
- r = true;
- }
- JsonBuilder *ajs = JsonDNSLogAnswer(txptr);
- if (ajs != NULL) {
- jb_set_object(js, "answer", ajs);
- jb_free(ajs);
- r = true;
- }
- jb_close(js);
- return r;
+ return SCDnsLogJson(
+ txptr, LOG_FORMAT_DETAILED | LOG_QUERIES | LOG_ANSWERS | LOG_ALL_RRTYPES, js);
}
static int JsonDnsLoggerToServer(ThreadVars *tv, void *thread_data,