mdns: add mdns parser, logger and detection

The mDNS support is based heavily on the DNS support, reusing the
existing DNS parser where possible. This meant adding variations on
DNS, as mDNS is a little different. Mainly being that *all* mDNS
traffic is to_server, yet there is still the concept of request and
responses.

Keywords added are:
- mdns.queries.rrname
- mdns.answers.rrname
- mdns.additionals.rrname
- mdns.authorities.rrname
- mdns.response.rrname

They are mostly in-line with the DNS keywords, except
mdns.answers.rdata which is a better than that mdns.response.rrname,
as its actually looking at the rdata, and not rrnames.

mDNS has its own logger that differs from the DNS logger:

- No grouped logging

- In answers/additionals/authorities, the rdata is logged in a field
  that is named after the rdata type. For example, "txt" data is no
  longer logged in the "rdata" field, but instead a "txt" field. We
  currently already did this in DNS for fields that were not a single
  buffer, like SOA, SRV, etc. So this makes things more consistent. And
  gives query like semantics that the "grouped" object was trying to
  provide.

- Types are logged in lower case ("txt" instead of "TXT")

- Flags are logged as an array: "flags": ["aa", "z"]

Ticket: #3952
pull/13334/head
Jason Ish 11 months ago committed by Victor Julien
parent de88d8ec48
commit 4a655053e8

@ -15,6 +15,7 @@ Suricata Rules
http-keywords
file-keywords
dns-keywords
mdns-keywords
tls-keywords
ssh-keywords
ja-keywords

@ -0,0 +1,93 @@
mDNS Keywords
=============
Suricata supports sticky buffers for efficiently matching on specific
fields in mDNS (Multicast DNS) messages.
Note that sticky buffers are expected to be followed by one or more
:doc:`payload-keywords`.
mdns.queries.rrname
-------------------
``mdns.queries.rrname`` is a sticky buffer that is used to look at the
name field in mDNS query resource records.
The buffer being matched on contains the complete re-assembled
resource name, for example "host.local".
``mdns.queries.rrname`` supports :doc:`multi-buffer-matching`.
Example::
alert udp any any -> any 5353 (msg:"mDNS query for .local domain"; \
mdns.queries.rrname; content:".local"; sid:1;)
mdns.answers.rrname
-------------------
``mdns.answers.rrname`` is a sticky buffer that is used to look at the
name field in mDNS answer resource records.
The buffer being matched on contains the complete re-assembled
resource name, for example "printer.local".
``mdns.answers.rrname`` supports :doc:`multi-buffer-matching`.
Example::
alert udp any 5353 -> any any (msg:"mDNS answer for printer.local"; \
mdns.answers.rrname; content:"printer.local"; sid:2;)
mdns.authorities.rrname
-----------------------
``mdns.authorities.rrname`` is a sticky buffer that is used to look at the
rrname field in mDNS authority resource records.
The buffer being matched on contains the complete re-assembled
resource name, for example "device.local".
``mdns.authorities.rrname`` supports :doc:`multi-buffer-matching`.
Example::
alert udp any 5353 -> any any (msg:"mDNS authority record check"; \
mdns.authorities.rrname; content:"auth.local"; sid:3;)
mdns.additionals.rrname
-----------------------
``mdns.additionals.rrname`` is a sticky buffer that is used to look at
the rrname field in mDNS additional resource records.
The buffer being matched on contains the complete re-assembled
resource name, for example "service.local".
``mdns.additionals.rrname`` supports :doc:`multi-buffer-matching`.
Example::
alert udp any any -> any 5353 (msg:"mDNS additional record check"; \
mdns.additionals.rrname; content:"_companion-link._tcp.local"; nocase; sid:4;)
mdns.response.rrname
--------------------
``mdns.response.rrname`` is a sticky buffer that is used to inspect
all the rrname fields in a response, in the queries, answers,
additionals and authorities. Additionally it will also inspect rdata
fields that have the same format as an rrname (hostname).
``rdata`` types that will be inspected are:
* CNAME
* PTR
* MX
* NS
* SOA
Example::
alert udp any 5353 -> any any (msg:"mDNS answer data match"; \
mdns.response.rrname; content:"Apple TV"; sid:5;)

@ -2806,6 +2806,117 @@
"log_level": {
"type": "string"
},
"mdns": {
"description": "mDNS requests and responses",
"type": "object",
"additionalProperties": false,
"properties": {
"additionals": {
"description": "mDNS additional records",
"type": "array",
"minItems": 1
},
"answers": {
"description": "mDNS answer records",
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"ptr": {
"type": "string"
},
"rrname": {
"type": "string"
},
"txt": {
"type": "array",
"minItems": 1
}
}
}
},
"authorities": {
"description": "mDNS authority records",
"type": "array",
"minItems": 1
},
"flags": {
"description": "mDNS message flags",
"type": "array",
"items": {
"oneOf": [
{
"const": "aa",
"title": "Authoritative Answer"
},
{
"const": "tc",
"title": "Truncated"
},
{
"const": "rd",
"title": "Recursion Desired"
},
{
"const": "ra",
"title": "Recursion Available"
},
{
"const": "z",
"title": "Z (reserved)"
},
{
"const": "ad",
"title": "Authentic Data"
},
{
"const": "cd",
"title": "Checking Disabled"
}
]
}
},
"id": {
"description": "mDNS transaction ID",
"type": "integer"
},
"opcode": {
"description": "mDNS opcode value",
"type": "integer"
},
"queries": {
"description": "mDNS query records",
"type": "array",
"additionalProperties": false,
"minItems": 1,
"items": {
"type": "object",
"properties": {
"rrname": {
"type": "string"
},
"rrtype": {
"type": "string"
}
}
}
},
"rcode": {
"description": "mDNS reply (error) code",
"type": "integer"
},
"type": {
"description": "Type of message, either a request or response",
"type": "string",
"enum": [
"request",
"response"
]
}
}
},
"metadata": {
"type": "object",
"additionalProperties": false,
@ -4801,6 +4912,10 @@
"description": "Errors encountered parsing LDAP/UDP protocol",
"$ref": "#/$defs/stats_applayer_error"
},
"mdns": {
"description": "Errors encountered parsing mDNS",
"$ref": "#/$defs/stats_applayer_error"
},
"modbus": {
"description": "Errors encountered parsing Modbus protocol",
"$ref": "#/$defs/stats_applayer_error"
@ -4980,6 +5095,10 @@
"type": "integer",
"description": "Number of flows LDAP/UDP protocol"
},
"mdns": {
"description": "Number of flows for mDNS",
"type": "integer"
},
"modbus": {
"type": "integer",
"description": "Number of flows for Modbus protocol"
@ -5147,6 +5266,10 @@
"type": "integer",
"description": "Number of transactions for LDAP/UDP protocol"
},
"mdns": {
"description": "Number of transactions for mDNS",
"type": "integer"
},
"modbus": {
"type": "integer",
"description": "Number of transactions for Modbus protocol"

@ -264,7 +264,7 @@ unsafe extern "C" fn dns_detect_answer_name_setup(
}
/// Get the DNS response answer name and index i.
unsafe extern "C" fn dns_tx_get_answer_name(
pub(crate) unsafe extern "C" fn dns_tx_get_answer_name(
_de: *mut DetectEngineThreadCtx, tx: *const c_void, flags: u8, i: u32, buf: *mut *const u8,
len: *mut u32,
) -> bool {
@ -302,7 +302,7 @@ unsafe extern "C" fn dns_detect_query_name_setup(
}
/// Get the DNS response answer name and index i.
unsafe extern "C" fn dns_tx_get_query_name(
pub(crate) unsafe extern "C" fn dns_tx_get_query_name(
_de: *mut DetectEngineThreadCtx, tx: *const c_void, flags: u8, i: u32, buf: *mut *const u8,
len: *mut u32,
) -> bool {

@ -127,7 +127,7 @@ pub enum DNSRcode {
pub(super) static mut ALPROTO_DNS: AppProto = ALPROTO_UNKNOWN;
#[derive(AppLayerFrameType)]
enum DnsFrameType {
pub(crate) enum DnsFrameType {
/// DNS PDU frame. For UDP DNS this is the complete UDP payload, for TCP
/// this is the DNS payload not including the leading length field allowing
/// this frame to be used for UDP and TCP DNS.
@ -290,7 +290,7 @@ impl Transaction for DNSTransaction {
}
impl DNSTransaction {
fn new(direction: Direction) -> Self {
pub(crate) fn new(direction: Direction) -> Self {
Self {
tx_data: AppLayerTxData::for_direction(direction),
..Default::default()
@ -355,8 +355,24 @@ impl ConfigTracker {
}
}
#[derive(Default)]
pub(crate) enum DnsVariant {
Dns,
MulticastDns,
}
impl DnsVariant {
pub fn is_dns(&self) -> bool {
matches!(self, DnsVariant::Dns)
}
pub fn is_mdns(&self) -> bool {
matches!(self, DnsVariant::MulticastDns)
}
}
//#[derive(Default)]
pub struct DNSState {
variant: DnsVariant,
state_data: AppLayerStateData,
// Internal transaction ID.
@ -397,7 +413,7 @@ pub(crate) enum DNSParseError {
OtherError,
}
pub(crate) fn dns_parse_request(input: &[u8]) -> Result<DNSTransaction, DNSParseError> {
pub(crate) fn dns_parse_request(input: &[u8], variant: &DnsVariant) -> Result<DNSTransaction, DNSParseError> {
let (body, header) = if let Some((body, header)) = dns_validate_header(input) {
(body, header)
} else {
@ -406,7 +422,7 @@ pub(crate) fn dns_parse_request(input: &[u8]) -> Result<DNSTransaction, DNSParse
match parser::dns_parse_body(body, input, header) {
Ok((_, (request, parse_flags))) => {
if request.header.flags & 0x8000 != 0 {
if variant.is_dns() && request.header.flags & 0x8000 != 0 {
SCLogDebug!("DNS message is not a request");
return Err(DNSParseError::NotRequest);
}
@ -421,7 +437,12 @@ pub(crate) fn dns_parse_request(input: &[u8]) -> Result<DNSTransaction, DNSParse
if request.invalid_authorities {
tx.set_event(DNSEvent::InvalidAuthorities);
}
tx.request = Some(request);
if variant.is_mdns() && request.header.flags & 0x8000 != 0 {
tx.response = Some(request);
} else {
tx.request = Some(request);
}
if z_flag {
SCLogDebug!("Z-flag set on DNS request");
@ -525,7 +546,25 @@ pub(crate) fn dns_parse_response(input: &[u8]) -> Result<DNSTransaction, DNSPars
impl DNSState {
fn new() -> Self {
Default::default()
Self {
variant: DnsVariant::Dns,
state_data: AppLayerStateData::default(),
tx_id: 0,
transactions: VecDeque::default(),
config: None,
gap: false,
}
}
pub(crate) fn new_variant(variant: DnsVariant) -> Self {
Self {
variant,
state_data: AppLayerStateData::default(),
tx_id: 0,
transactions: VecDeque::default(),
config: None,
gap: false,
}
}
fn free_tx(&mut self, tx_id: u64) {
@ -563,7 +602,7 @@ impl DNSState {
fn parse_request(
&mut self, input: &[u8], is_tcp: bool, frame: Option<Frame>, flow: *const Flow,
) -> bool {
match dns_parse_request(input) {
match dns_parse_request(input, &self.variant) {
Ok(mut tx) => {
self.tx_id += 1;
tx.id = self.tx_id;
@ -593,7 +632,7 @@ impl DNSState {
}
}
fn parse_request_udp(&mut self, flow: *const Flow, stream_slice: StreamSlice) -> bool {
pub(crate) fn parse_request_udp(&mut self, flow: *const Flow, stream_slice: StreamSlice) -> bool {
let input = stream_slice.as_slice();
let frame = Frame::new(
flow,
@ -797,7 +836,7 @@ impl DNSState {
const DNS_HEADER_SIZE: usize = 12;
fn probe_header_validity(header: &DNSHeader, rlen: usize) -> (bool, bool, bool) {
pub(crate) fn probe_header_validity(header: &DNSHeader, rlen: usize) -> (bool, bool, bool) {
let nb_records = header.additional_rr as usize
+ header.answer_rr as usize
+ header.authority_rr as usize
@ -869,7 +908,7 @@ fn probe_tcp(input: &[u8]) -> (bool, bool, bool) {
}
/// Returns *mut DNSState
extern "C" fn state_new(
pub(crate) extern "C" fn state_new(
_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto,
) -> *mut std::os::raw::c_void {
let state = DNSState::new();
@ -879,18 +918,18 @@ extern "C" fn state_new(
/// Params:
/// - state: *mut DNSState as void pointer
extern "C" fn state_free(state: *mut std::os::raw::c_void) {
pub(crate) extern "C" fn state_free(state: *mut std::os::raw::c_void) {
// Just unbox...
std::mem::drop(unsafe { Box::from_raw(state as *mut DNSState) });
}
unsafe extern "C" fn state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) {
pub(crate) unsafe extern "C" fn state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) {
let state = cast_pointer!(state, DNSState);
state.free_tx(tx_id);
}
/// C binding parse a DNS request. Returns 1 on success, -1 on failure.
unsafe extern "C" fn parse_request(
pub(crate) unsafe extern "C" fn parse_request(
flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void,
stream_slice: StreamSlice, _data: *const std::os::raw::c_void,
) -> AppLayerResult {
@ -935,7 +974,7 @@ unsafe extern "C" fn parse_response_tcp(
AppLayerResult::ok()
}
extern "C" fn tx_get_alstate_progress(
pub(crate) extern "C" fn tx_get_alstate_progress(
_tx: *mut std::os::raw::c_void, _direction: u8,
) -> std::os::raw::c_int {
// This is a stateless parser, just the existence of a transaction
@ -944,13 +983,13 @@ extern "C" fn tx_get_alstate_progress(
return 1;
}
unsafe extern "C" fn state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 {
pub(crate) unsafe extern "C" fn state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 {
let state = cast_pointer!(state, DNSState);
SCLogDebug!("state_get_tx_count: returning {}", state.tx_id);
return state.tx_id;
}
unsafe extern "C" fn state_get_tx(
pub(crate) unsafe extern "C" fn state_get_tx(
state: *mut std::os::raw::c_void, tx_id: u64,
) -> *mut std::os::raw::c_void {
let state = cast_pointer!(state, DNSState);
@ -974,12 +1013,17 @@ pub extern "C" fn SCDnsTxIsResponse(tx: &mut DNSTransaction) -> bool {
tx.response.is_some()
}
unsafe extern "C" fn state_get_tx_data(tx: *mut std::os::raw::c_void) -> *mut AppLayerTxData {
pub(crate) unsafe extern "C" fn state_get_tx_data(tx: *mut std::os::raw::c_void) -> *mut AppLayerTxData {
let tx = cast_pointer!(tx, DNSTransaction);
return &mut tx.tx_data;
}
export_state_data_get!(dns_get_state_data, DNSState);
pub(crate) unsafe extern "C" fn dns_get_state_data(
state: *mut std::os::raw::c_void,
) -> *mut AppLayerStateData {
let state = cast_pointer!(state, DNSState);
return &mut state.state_data;
}
/// Get the DNS query name at index i.
#[no_mangle]
@ -1161,7 +1205,7 @@ pub extern "C" fn SCDnsTxGetResponseFlags(tx: &mut DNSTransaction) -> u16 {
return tx.rcode();
}
unsafe extern "C" fn probe_udp(
pub(crate) unsafe extern "C" fn probe_udp(
_flow: *const Flow, _dir: u8, input: *const u8, len: u32, rdir: *mut u8,
) -> AppProto {
if input.is_null() || len < std::mem::size_of::<DNSHeader>() as u32 {

@ -323,7 +323,7 @@ pub fn dns_print_addr(addr: &[u8]) -> std::string::String {
}
/// Log OPT section fields
fn dns_log_opt(opt: &DNSRDataOPT) -> Result<JsonBuilder, JsonError> {
pub(crate) fn dns_log_opt(opt: &DNSRDataOPT) -> Result<JsonBuilder, JsonError> {
let mut js = JsonBuilder::try_new_object()?;
js.set_uint("code", opt.code as u64)?;
@ -334,7 +334,7 @@ fn dns_log_opt(opt: &DNSRDataOPT) -> Result<JsonBuilder, JsonError> {
}
/// Log SOA section fields.
fn dns_log_soa(soa: &DNSRDataSOA) -> Result<JsonBuilder, JsonError> {
pub(crate) fn dns_log_soa(soa: &DNSRDataSOA) -> Result<JsonBuilder, JsonError> {
let mut js = JsonBuilder::try_new_object()?;
js.set_string_from_bytes("mname", &soa.mname.value)?;
@ -356,7 +356,7 @@ fn dns_log_soa(soa: &DNSRDataSOA) -> Result<JsonBuilder, JsonError> {
}
/// Log SSHFP section fields.
fn dns_log_sshfp(sshfp: &DNSRDataSSHFP) -> Result<JsonBuilder, JsonError> {
pub(crate) fn dns_log_sshfp(sshfp: &DNSRDataSSHFP) -> Result<JsonBuilder, JsonError> {
let mut js = JsonBuilder::try_new_object()?;
let mut hex = Vec::new();
@ -373,7 +373,7 @@ fn dns_log_sshfp(sshfp: &DNSRDataSSHFP) -> Result<JsonBuilder, JsonError> {
}
/// Log SRV section fields.
fn dns_log_srv(srv: &DNSRDataSRV) -> Result<JsonBuilder, JsonError> {
pub(crate) fn dns_log_srv(srv: &DNSRDataSRV) -> Result<JsonBuilder, JsonError> {
let mut js = JsonBuilder::try_new_object()?;
js.set_uint("priority", srv.priority as u64)?;

@ -24,6 +24,7 @@ use crate::applayer::{self, *};
use crate::conf::conf_get;
use crate::core::*;
use crate::direction::Direction;
use crate::dns::dns::DnsVariant;
use crate::filecontainer::*;
use crate::filetracker::*;
use crate::flow::Flow;
@ -479,7 +480,7 @@ impl HTTP2Transaction {
AppLayerForceProtocolChange(flow, ALPROTO_DOH2);
}
}
} else if let Ok(mut dtx) = dns_parse_request(&doh.data_buf[dir.index()]) {
} else if let Ok(mut dtx) = dns_parse_request(&doh.data_buf[dir.index()], &DnsVariant::Dns) {
dtx.id = 1;
doh.dns_request_tx = Some(dtx);
unsafe {
@ -1206,7 +1207,7 @@ impl HTTP2State {
frame.set_tx(flow, tx.tx_id);
}
if let Some(doh_req_buf) = tx.handle_frame(&head, &txdata, dir) {
if let Ok(mut dtx) = dns_parse_request(&doh_req_buf) {
if let Ok(mut dtx) = dns_parse_request(&doh_req_buf, &DnsVariant::Dns) {
dtx.id = 1;
unsafe {
AppLayerForceProtocolChange(flow, ALPROTO_DOH2);

@ -101,6 +101,7 @@ pub mod handshake;
pub mod lua;
pub mod dns;
pub mod mdns;
pub mod nfs;
pub mod ftp;
pub mod smb;

@ -0,0 +1,180 @@
/* Copyright (C) 2025 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.
*/
use crate::dns::dns::*;
use crate::dns::log::{
dns_log_opt, dns_log_soa, dns_log_srv, dns_log_sshfp, dns_print_addr, dns_rrtype_string,
};
use crate::jsonbuilder::{JsonBuilder, JsonError};
fn mdns_log_json_answer_detail(answer: &DNSAnswerEntry) -> Result<JsonBuilder, JsonError> {
let mut jsa = JsonBuilder::try_new_object()?;
jsa.set_string_from_bytes("rrname", &answer.name.value)?;
if answer.name.flags.contains(DNSNameFlags::TRUNCATED) {
jsa.set_bool("rrname_truncated", true)?;
}
let rrtype = dns_rrtype_string(answer.rrtype).to_lowercase();
match &answer.data {
DNSRData::A(addr) | DNSRData::AAAA(addr) => {
jsa.set_string(&rrtype, &dns_print_addr(addr))?;
}
DNSRData::CNAME(name) | DNSRData::MX(name) | DNSRData::NS(name) | DNSRData::PTR(name) => {
jsa.set_string_from_bytes(&rrtype, &name.value)?;
if name.flags.contains(DNSNameFlags::TRUNCATED) {
jsa.set_bool("rdata_truncated", true)?;
}
}
DNSRData::TXT(txt) => {
jsa.open_array(&rrtype)?;
for txt in txt {
jsa.append_string_from_bytes(txt)?;
}
jsa.close()?;
}
DNSRData::NULL(bytes) | DNSRData::Unknown(bytes) => {
jsa.set_string_from_bytes(&rrtype, bytes)?;
}
DNSRData::SOA(soa) => {
jsa.set_object(&rrtype, &dns_log_soa(soa)?)?;
}
DNSRData::SSHFP(sshfp) => {
jsa.set_object(&rrtype, &dns_log_sshfp(sshfp)?)?;
}
DNSRData::SRV(srv) => {
jsa.set_object(&rrtype, &dns_log_srv(srv)?)?;
}
DNSRData::OPT(opt) => {
jsa.open_array(&rrtype)?;
for val in opt {
jsa.append_object(&dns_log_opt(val)?)?;
}
jsa.close()?;
}
}
jsa.close()?;
return Ok(jsa);
}
fn log_json(tx: &DNSTransaction, jb: &mut JsonBuilder) -> Result<(), JsonError> {
jb.open_object("mdns")?;
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 on the wire mDNS transaction ID.
jb.set_uint("id", tx.tx_id() as u64)?;
let header = &message.header;
if header.flags & (0x0400 | 0x0200 | 0x0100 | 0x0080 | 0x0040 | 0x0020 | 0x0010) != 0 {
jb.open_array("flags")?;
if header.flags & 0x0400 != 0 {
jb.append_string("aa")?;
}
if header.flags & 0x0200 != 0 {
jb.append_string("tc")?;
}
if header.flags & 0x0100 != 0 {
jb.append_string("rd")?;
}
if header.flags & 0x0080 != 0 {
jb.append_string("ra")?;
}
if header.flags & 0x0040 != 0 {
jb.append_string("z")?;
}
if header.flags & 0x0020 != 0 {
jb.append_string("ad")?;
}
if header.flags & 0x0010 != 0 {
jb.append_string("cd")?;
}
jb.close()?;
}
let opcode = ((header.flags >> 11) & 0xf) as u8;
jb.set_uint("opcode", opcode as u64)?;
jb.set_uint("rcode", header.flags & 0x000f)?;
if !message.queries.is_empty() {
jb.open_array("queries")?;
for query in &message.queries {
jb.start_object()?
.set_string_from_bytes("rrname", &query.name.value)?
.set_string("rrtype", &dns_rrtype_string(query.rrtype).to_lowercase())?;
if query.name.flags.contains(DNSNameFlags::TRUNCATED) {
jb.set_bool("rrname_truncated", true)?;
}
jb.close()?;
}
jb.close()?;
}
if !message.answers.is_empty() {
jb.open_array("answers")?;
for entry in &message.answers {
jb.append_object(&mdns_log_json_answer_detail(entry)?)?;
}
jb.close()?;
}
if !message.authorities.is_empty() {
jb.open_array("authorities")?;
for entry in &message.authorities {
jb.append_object(&mdns_log_json_answer_detail(entry)?)?;
}
jb.close()?;
}
if !message.additionals.is_empty() {
let mut is_jb_open = false;
for entry in &message.additionals {
if let DNSRData::OPT(rdata) = &entry.data {
if rdata.is_empty() {
continue;
}
}
if !is_jb_open {
jb.open_array("additionals")?;
is_jb_open = true;
}
jb.append_object(&mdns_log_json_answer_detail(entry)?)?;
}
if is_jb_open {
jb.close()?;
}
}
jb.close()?;
Ok(())
}
/// FFI wrapper around the common V3 style mDNS logger.
#[no_mangle]
pub extern "C" fn SCMdnsLogJson(tx: &DNSTransaction, jb: &mut JsonBuilder) -> bool {
log_json(tx, jb).is_ok()
}

@ -0,0 +1,131 @@
/* Copyright (C) 2025 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.
*/
use std;
use std::ffi::CString;
use std::os::raw::c_void;
use crate::applayer::*;
use crate::core::*;
use crate::direction::Direction;
use crate::dns::dns;
use crate::flow::Flow;
use suricata_sys::sys::DetectEngineThreadCtx;
use suricata_sys::sys::{AppProto, AppProtoEnum};
pub(super) static mut ALPROTO_MDNS: AppProto = ALPROTO_UNKNOWN;
unsafe extern "C" fn probe(
_flow: *const Flow, _dir: u8, input: *const u8, len: u32, _rdir: *mut u8,
) -> AppProto {
if crate::dns::dns::probe_udp(_flow, _dir, input, len, _rdir)
== AppProtoEnum::ALPROTO_DNS as u16
{
let dir = Direction::ToServer;
*_rdir = dir as u8;
return ALPROTO_MDNS;
}
return 0;
}
/// Returns *mut DNSState
pub(crate) extern "C" fn state_new(
_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto,
) -> *mut std::os::raw::c_void {
let state = dns::DNSState::new_variant(dns::DnsVariant::MulticastDns);
let boxed = Box::new(state);
return Box::into_raw(boxed) as *mut _;
}
/// Get the mDNS response answer name and index i.
///
/// Very similar to the DNS version, but mDNS is always to_server.
#[no_mangle]
pub unsafe extern "C" fn SCMdnsTxGetAnswerName(
_de: *mut DetectEngineThreadCtx, tx: *const c_void, _flow_flags: u8, i: u32,
buf: *mut *const u8, len: *mut u32,
) -> bool {
let tx = cast_pointer!(tx, dns::DNSTransaction);
let answers = if tx.request.is_some() {
tx.request.as_ref().map(|request| &request.answers)
} else {
tx.response.as_ref().map(|response| &response.answers)
};
let index = i as usize;
if let Some(answers) = answers {
if let Some(answer) = answers.get(index) {
if !answer.name.value.is_empty() {
*buf = answer.name.value.as_ptr();
*len = answer.name.value.len() as u32;
return true;
}
}
}
false
}
#[no_mangle]
pub unsafe extern "C" fn SCRegisterMdnsParser() {
let default_port = std::ffi::CString::new("[5353]").unwrap();
let parser = RustParser {
name: b"mdns\0".as_ptr() as *const std::os::raw::c_char,
default_port: default_port.as_ptr(),
ipproto: IPPROTO_UDP,
probe_ts: Some(probe),
probe_tc: Some(probe),
min_depth: 0,
max_depth: std::mem::size_of::<dns::DNSHeader>() as u16,
state_new,
state_free: dns::state_free,
tx_free: dns::state_tx_free,
parse_ts: dns::parse_request,
parse_tc: dns::parse_request,
get_tx_count: dns::state_get_tx_count,
get_tx: dns::state_get_tx,
tx_comp_st_ts: 1,
tx_comp_st_tc: 1,
tx_get_progress: dns::tx_get_alstate_progress,
get_eventinfo: Some(dns::DNSEvent::get_event_info),
get_eventinfo_byid: Some(dns::DNSEvent::get_event_info_by_id),
localstorage_new: None,
localstorage_free: None,
get_tx_files: None,
get_tx_iterator: Some(
crate::applayer::state_get_tx_iterator::<dns::DNSState, dns::DNSTransaction>,
),
get_tx_data: dns::state_get_tx_data,
get_state_data: dns::dns_get_state_data,
apply_tx_config: None,
flags: 0,
get_frame_id_by_name: Some(dns::DnsFrameType::ffi_id_from_name),
get_frame_name_by_id: Some(dns::DnsFrameType::ffi_name_from_id),
get_state_id_by_name: None,
get_state_name_by_id: None,
};
let ip_proto_str = CString::new("udp").unwrap();
if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
ALPROTO_MDNS = alproto;
if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
let _ = AppLayerRegisterParser(&parser, alproto);
}
}
}

@ -0,0 +1,21 @@
/* Copyright (C) 2025 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.
*/
//! mDNS parser, detection, logger and application layer module.
pub mod log;
pub mod mdns;

@ -43,8 +43,9 @@ pub enum AppProtoEnum {
ALPROTO_HTTP2 = 34,
ALPROTO_BITTORRENT_DHT = 35,
ALPROTO_POP3 = 36,
ALPROTO_HTTP = 37,
ALPROTO_MAX_STATIC = 38,
ALPROTO_MDNS = 37,
ALPROTO_HTTP = 38,
ALPROTO_MAX_STATIC = 39,
}
pub type AppProto = u16;
extern "C" {

@ -392,6 +392,7 @@ noinst_HEADERS = \
output-json-ftp.h \
output-json-http.h \
output-json-ike.h \
output-json-mdns.h \
output-json-metadata.h \
output-json-mqtt.h \
output-json-netflow.h \
@ -983,6 +984,7 @@ libsuricata_c_a_SOURCES = \
output-json-ftp.c \
output-json-http.c \
output-json-ike.c \
output-json-mdns.c \
output-json-metadata.c \
output-json-mqtt.c \
output-json-netflow.c \

@ -1798,6 +1798,7 @@ void AppLayerParserRegisterProtocolParsers(void)
SCRegisterWebSocketParser();
SCRegisterLdapTcpParser();
SCRegisterLdapUdpParser();
SCRegisterMdnsParser();
SCRegisterTemplateParser();
SCRfbRegisterParser();
SCMqttRegisterParser();

@ -69,6 +69,7 @@ enum AppProtoEnum {
ALPROTO_HTTP2,
ALPROTO_BITTORRENT_DHT,
ALPROTO_POP3,
ALPROTO_MDNS,
// signature-only (ie not seen in flow)
// HTTP for any version (ALPROTO_HTTP1 (version 1) or ALPROTO_HTTP2)

@ -1065,6 +1065,7 @@ static void AppLayerNamesSetup(void)
AppProtoRegisterProtoString(ALPROTO_WEBSOCKET, "websocket");
AppProtoRegisterProtoString(ALPROTO_LDAP, "ldap");
AppProtoRegisterProtoString(ALPROTO_DOH2, "doh2");
AppProtoRegisterProtoString(ALPROTO_MDNS, "mdns");
AppProtoRegisterProtoString(ALPROTO_TEMPLATE, "template");
AppProtoRegisterProtoString(ALPROTO_RDP, "rdp");
AppProtoRegisterProtoString(ALPROTO_HTTP2, "http2");

@ -46,12 +46,17 @@ static int answer_buffer_id = 0;
static int authority_buffer_id = 0;
static int additional_buffer_id = 0;
static int mdns_query_buffer_id = 0;
static int mdns_answer_buffer_id = 0;
static int mdns_authority_buffer_id = 0;
static int mdns_additional_buffer_id = 0;
static int DetectSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str, int id)
{
if (SCDetectBufferSetActiveList(de_ctx, s, id) < 0) {
return -1;
}
if (SCDetectSignatureSetAppProto(s, ALPROTO_DNS) < 0) {
if (SCDetectSignatureSetAppProto(s, s->alproto) < 0) {
return -1;
}
@ -78,9 +83,29 @@ static int SetupAuthoritiesBuffer(DetectEngineCtx *de_ctx, Signature *s, const c
return DetectSetup(de_ctx, s, str, authority_buffer_id);
}
static int SetupQueryBufferMdns(DetectEngineCtx *de_ctx, Signature *s, const char *str)
{
return DetectSetup(de_ctx, s, str, mdns_query_buffer_id);
}
static int SetupAnswerBufferMdns(DetectEngineCtx *de_ctx, Signature *s, const char *str)
{
return DetectSetup(de_ctx, s, str, mdns_answer_buffer_id);
}
static int SetupAdditionalsBufferMdns(DetectEngineCtx *de_ctx, Signature *s, const char *str)
{
return DetectSetup(de_ctx, s, str, mdns_additional_buffer_id);
}
static int SetupAuthoritiesBufferMdns(DetectEngineCtx *de_ctx, Signature *s, const char *str)
{
return DetectSetup(de_ctx, s, str, mdns_authority_buffer_id);
}
static int Register(const char *keyword, const char *desc, const char *doc,
int (*Setup)(DetectEngineCtx *, Signature *, const char *),
InspectionMultiBufferGetDataPtr GetBufferFn)
InspectionMultiBufferGetDataPtr GetBufferFn, AppProto alproto)
{
int keyword_id = SCDetectHelperNewKeywordId();
sigmatch_table[keyword_id].name = keyword;
@ -90,8 +115,8 @@ static int Register(const char *keyword, const char *desc, const char *doc,
sigmatch_table[keyword_id].flags |= SIGMATCH_NOOPT;
sigmatch_table[keyword_id].flags |= SIGMATCH_INFO_STICKY_BUFFER;
DetectAppLayerMultiRegister(keyword, ALPROTO_DNS, SIG_FLAG_TOSERVER, 1, GetBufferFn, 2);
DetectAppLayerMultiRegister(keyword, ALPROTO_DNS, SIG_FLAG_TOCLIENT, 1, GetBufferFn, 2);
DetectAppLayerMultiRegister(keyword, alproto, SIG_FLAG_TOSERVER, 1, GetBufferFn, 2);
DetectAppLayerMultiRegister(keyword, alproto, SIG_FLAG_TOCLIENT, 1, GetBufferFn, 2);
DetectBufferTypeSetDescriptionByName(keyword, keyword);
DetectBufferTypeSupportsMultiInstance(keyword);
@ -102,14 +127,31 @@ static int Register(const char *keyword, const char *desc, const char *doc,
void DetectDnsNameRegister(void)
{
query_buffer_id = Register("dns.queries.rrname", "DNS query rrname sticky buffer",
"/rules/dns-keywords.html#dns.queries.rrname", SetupQueryBuffer, SCDnsTxGetQueryName);
"/rules/dns-keywords.html#dns.queries.rrname", SetupQueryBuffer, SCDnsTxGetQueryName,
ALPROTO_DNS);
answer_buffer_id = Register("dns.answers.rrname", "DNS answer rrname sticky buffer",
"/rules/dns-keywords.html#dns.answers.rrname", SetupAnswerBuffer, SCDnsTxGetAnswerName);
"/rules/dns-keywords.html#dns.answers.rrname", SetupAnswerBuffer, SCDnsTxGetAnswerName,
ALPROTO_DNS);
additional_buffer_id =
Register("dns.additionals.rrname", "DNS additionals rrname sticky buffer",
"/rules/dns-keywords.html#dns-additionals-rrname", SetupAdditionalsBuffer,
SCDnsTxGetAdditionalName);
SCDnsTxGetAdditionalName, ALPROTO_DNS);
authority_buffer_id = Register("dns.authorities.rrname", "DNS authorities rrname sticky buffer",
"/rules/dns-keywords.html#dns-authorities-rrname", SetupAuthoritiesBuffer,
SCDnsTxGetAuthorityName);
SCDnsTxGetAuthorityName, ALPROTO_DNS);
mdns_query_buffer_id = Register("mdns.queries.rrname", "mDNS query rrname sticky buffer",
"/rules/mdns-keywords.html#mdns.queries.rrname", SetupQueryBufferMdns,
SCDnsTxGetQueryName, ALPROTO_MDNS);
mdns_answer_buffer_id = Register("mdns.answers.rrname", "mDNS answer rrname sticky buffer",
"/rules/mdns-keywords.html#mdns.answers.rrname", SetupAnswerBufferMdns,
SCMdnsTxGetAnswerName, ALPROTO_MDNS);
mdns_additional_buffer_id =
Register("mdns.additionals.rrname", "mDNS additionals rrname sticky buffer",
"/rules/mdns-keywords.html#mdns-additionals-rrname", SetupAdditionalsBufferMdns,
SCDnsTxGetAdditionalName, ALPROTO_MDNS);
mdns_authority_buffer_id =
Register("mdns.authorities.rrname", "mDNS authorities rrname sticky buffer",
"/rules/mdns-keywords.html#mdns-authorities-rrname", SetupAuthoritiesBufferMdns,
SCDnsTxGetAuthorityName, ALPROTO_MDNS);
}

@ -28,11 +28,14 @@
#include "detect-engine-mpm.h"
#include "detect-engine-prefilter.h"
#include "detect-engine-content-inspection.h"
#include "detect-engine-helper.h"
#include "detect-dns-response.h"
#include "util-profiling.h"
#include "rust.h"
static int detect_buffer_id = 0;
static int mdns_detect_buffer_id = 0;
typedef struct PrefilterMpm {
int list_id;
const MpmCtx *mpm_ctx;
@ -67,6 +70,18 @@ static int DetectSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str)
return 0;
}
static int MdnsDetectSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str)
{
if (SCDetectBufferSetActiveList(de_ctx, s, mdns_detect_buffer_id) < 0) {
return -1;
}
if (SCDetectSignatureSetAppProto(s, ALPROTO_MDNS) < 0) {
return -1;
}
return 0;
}
static InspectionBuffer *GetBuffer(DetectEngineThreadCtx *det_ctx, uint8_t flags,
const DetectEngineTransforms *transforms, void *txv, struct DnsResponseGetDataArgs *cbdata,
int list_id, bool get_rdata)
@ -311,6 +326,29 @@ static int DetectDnsResponsePrefilterMpmRegister(DetectEngineCtx *de_ctx, SigGro
DetectDnsResponsePrefilterMpmFree, mpm_reg->pname);
}
static void SCDetectMdnsResponseRrnameRegister(void)
{
static const char *keyword = "mdns.response.rrname";
int keyword_id = SCDetectHelperNewKeywordId();
sigmatch_table[keyword_id].name = keyword;
sigmatch_table[keyword_id].desc = "mDNS response rrname buffer";
sigmatch_table[keyword_id].url = "/rules/mdns-keywords.html#mdns-response-rrname";
sigmatch_table[keyword_id].Setup = MdnsDetectSetup;
sigmatch_table[keyword_id].flags |= SIGMATCH_NOOPT;
sigmatch_table[keyword_id].flags |= SIGMATCH_INFO_STICKY_BUFFER;
/* Register in the TO_SERVER direction, as all mDNS is toserver. */
DetectAppLayerInspectEngineRegister(
keyword, ALPROTO_MDNS, SIG_FLAG_TOSERVER, 1, DetectEngineInspectCb, NULL);
DetectAppLayerMpmRegister(keyword, SIG_FLAG_TOSERVER, 2, DetectDnsResponsePrefilterMpmRegister,
NULL, ALPROTO_MDNS, 1);
DetectBufferTypeSetDescriptionByName(keyword, "mdns response rdata");
DetectBufferTypeSupportsMultiInstance(keyword);
mdns_detect_buffer_id = DetectBufferTypeGetByName(keyword);
}
void DetectDnsResponseRegister(void)
{
static const char *keyword = "dns.response.rrname";
@ -331,4 +369,6 @@ void DetectDnsResponseRegister(void)
DetectBufferTypeSupportsMultiInstance(keyword);
detect_buffer_id = DetectBufferTypeGetByName(keyword);
SCDetectMdnsResponseRrnameRegister();
}

@ -0,0 +1,160 @@
/* Copyright (C) 2025 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 "conf.h"
#include "threadvars.h"
#include "util-debug.h"
#include "app-layer-parser.h"
#include "output.h"
#include "output-json.h"
#include "output-json-mdns.h"
#include "rust.h"
typedef struct SCDnsLogFileCtx_ {
uint64_t flags; /** Store mode */
OutputJsonCtx *eve_ctx;
uint8_t version;
} SCDnsLogFileCtx;
typedef struct SCDnsLogThread_ {
SCDnsLogFileCtx *dnslog_ctx;
OutputJsonThreadCtx *ctx;
} SCDnsLogThread;
bool AlertJsonMdns(void *txptr, SCJsonBuilder *js)
{
return SCMdnsLogJson(txptr, js);
}
static int JsonMdnsLogger(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f,
void *alstate, void *txptr, uint64_t tx_id)
{
SCDnsLogThread *td = (SCDnsLogThread *)thread_data;
SCDnsLogFileCtx *dnslog_ctx = td->dnslog_ctx;
SCJsonBuilder *jb = CreateEveHeader(p, LOG_DIR_FLOW, "mdns", NULL, dnslog_ctx->eve_ctx);
if (unlikely(jb == NULL)) {
return TM_ECODE_OK;
}
if (SCMdnsLogJson(txptr, jb)) {
OutputJsonBuilderBuffer(tv, p, p->flow, jb, td->ctx);
}
SCJbFree(jb);
return TM_ECODE_OK;
}
static TmEcode SCDnsLogThreadInit(ThreadVars *t, const void *initdata, void **data)
{
SCDnsLogThread *aft = SCCalloc(1, sizeof(SCDnsLogThread));
if (unlikely(aft == NULL))
return TM_ECODE_FAILED;
if (initdata == NULL) {
SCLogDebug("Error getting log context for eve-log.mdns. \"initdata\" argument NULL");
goto error_exit;
}
/* Use the Output Context (file pointer and mutex) */
aft->dnslog_ctx = ((OutputCtx *)initdata)->data;
aft->ctx = CreateEveThreadCtx(t, aft->dnslog_ctx->eve_ctx);
if (!aft->ctx) {
goto error_exit;
}
*data = (void *)aft;
return TM_ECODE_OK;
error_exit:
SCFree(aft);
return TM_ECODE_FAILED;
}
static TmEcode SCDnsLogThreadDeinit(ThreadVars *t, void *data)
{
SCDnsLogThread *aft = (SCDnsLogThread *)data;
if (aft == NULL) {
return TM_ECODE_OK;
}
FreeEveThreadCtx(aft->ctx);
/* clear memory */
memset(aft, 0, sizeof(SCDnsLogThread));
SCFree(aft);
return TM_ECODE_OK;
}
static void DnsLogDeInitCtxSub(OutputCtx *output_ctx)
{
SCDnsLogFileCtx *dnslog_ctx = (SCDnsLogFileCtx *)output_ctx->data;
SCFree(dnslog_ctx);
SCFree(output_ctx);
}
static OutputInitResult DnsLogInitCtxSub(SCConfNode *conf, OutputCtx *parent_ctx)
{
OutputInitResult result = { NULL, false };
const char *enabled = SCConfNodeLookupChildValue(conf, "enabled");
if (enabled != NULL && !SCConfValIsTrue(enabled)) {
result.ok = true;
return result;
}
OutputJsonCtx *ojc = parent_ctx->data;
SCDnsLogFileCtx *dnslog_ctx = SCCalloc(1, sizeof(SCDnsLogFileCtx));
if (unlikely(dnslog_ctx == NULL)) {
return result;
}
dnslog_ctx->eve_ctx = ojc;
dnslog_ctx->version = DNS_LOG_VERSION_3;
/* For mDNS, log everything.
*
* TODO: Maybe add flags for request and/or response only.
*/
dnslog_ctx->flags = ~0ULL;
OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx));
if (unlikely(output_ctx == NULL)) {
SCFree(dnslog_ctx);
return result;
}
output_ctx->data = dnslog_ctx;
output_ctx->DeInit = DnsLogDeInitCtxSub;
AppLayerParserRegisterLogger(IPPROTO_UDP, ALPROTO_MDNS);
result.ctx = output_ctx;
result.ok = true;
return result;
}
void JsonMdnsLogRegister(void)
{
OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonMdnsLog", "eve-log.mdns",
DnsLogInitCtxSub, ALPROTO_MDNS, JsonMdnsLogger, SCDnsLogThreadInit,
SCDnsLogThreadDeinit);
}

@ -0,0 +1,24 @@
/* Copyright (C) 2025 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 SURICATA_OUTPUT_JSON_MDNS_H
#define SURICATA_OUTPUT_JSON_MDNS_H
void JsonMdnsLogRegister(void);
bool AlertJsonMdns(void *txptr, SCJsonBuilder *js);
#endif /* SURICATA_OUTPUT_JSON_MDNS_H */

@ -55,6 +55,7 @@
#include "log-httplog.h"
#include "output-json-http.h"
#include "output-json-dns.h"
#include "output-json-mdns.h"
#include "log-tlslog.h"
#include "log-tlsstore.h"
#include "output-json-tls.h"
@ -900,6 +901,7 @@ void OutputRegisterRootLoggers(void)
// ALPROTO_SMB special: uses state
// ALPROTO_DCERPC special: uses state
RegisterSimpleJsonApplayerLogger(ALPROTO_DNS, (EveJsonSimpleTxLogFunc)AlertJsonDns, NULL);
RegisterSimpleJsonApplayerLogger(ALPROTO_MDNS, (EveJsonSimpleTxLogFunc)AlertJsonMdns, NULL);
// either need a cast here or in rust for ModbusTransaction, done here
RegisterSimpleJsonApplayerLogger(ALPROTO_MODBUS, (EveJsonSimpleTxLogFunc)SCModbusToJson, NULL);
RegisterSimpleJsonApplayerLogger(ALPROTO_ENIP, (EveJsonSimpleTxLogFunc)SCEnipLoggerLog, NULL);
@ -1059,6 +1061,8 @@ void OutputRegisterLoggers(void)
OutputFilestoreRegister();
/* dns */
JsonDnsLogRegister();
/* mdns */
JsonMdnsLogRegister();
/* modbus */
OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonModbusLog", "eve-log.modbus",
OutputJsonLogInitSub, ALPROTO_MODBUS, JsonGenericDirFlowLogger, JsonLogThreadInit,
@ -1150,6 +1154,10 @@ void OutputRegisterLoggers(void)
OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonPop3Log", "eve-log.pop3",
OutputJsonLogInitSub, ALPROTO_POP3, JsonGenericDirFlowLogger, JsonLogThreadInit,
JsonLogThreadDeinit);
/* Mdns JSON logger. */
OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonMdnsLog", "eve-log.template",
OutputJsonLogInitSub, ALPROTO_MDNS, JsonGenericDirPacketLogger, JsonLogThreadInit,
JsonLogThreadDeinit);
/* Template JSON logger. */
OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonTemplateLog", "eve-log.template",
OutputJsonLogInitSub, ALPROTO_TEMPLATE, JsonGenericDirPacketLogger, JsonLogThreadInit,

@ -281,6 +281,7 @@ outputs:
# DNS record types to log, based on the query type.
# Default: all.
#types: [a, aaaa, cname, mx, ns, ptr, txt]
- mdns:
- tls:
extended: yes # enable this for extended logging information
# output TLS transaction where the session is resumed using a
@ -1255,6 +1256,9 @@ app-layer:
# Maximum number of live LDAP transactions per flow
# max-tx: 1024
mdns:
enabled: yes
# Limit for the maximum number of asn1 frames to decode (default 256)
asn1-max-frames: 256

Loading…
Cancel
Save