http-keywords
file-keywords
dns-keywords
+ mdns-keywords
tls-keywords
ssh-keywords
ja-keywords
--- /dev/null
+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;)
"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,
"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"
"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"
"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"
}
/// 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 {
}
/// 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 {
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.
}
impl DNSTransaction {
- fn new(direction: Direction) -> Self {
+ pub(crate) fn new(direction: Direction) -> Self {
Self {
tx_data: AppLayerTxData::for_direction(direction),
..Default::default()
}
}
-#[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.
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 {
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);
}
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");
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) {
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;
}
}
- 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,
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
}
/// 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();
/// 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 {
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
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);
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]
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 {
}
/// 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)?;
}
/// 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)?;
}
/// 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();
}
/// 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)?;
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;
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 {
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);
pub mod lua;
pub mod dns;
+pub mod mdns;
pub mod nfs;
pub mod ftp;
pub mod smb;
--- /dev/null
+/* 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()
+}
--- /dev/null
+/* 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);
+ }
+ }
+}
--- /dev/null
+/* 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;
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" {
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 \
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 \
SCRegisterWebSocketParser();
SCRegisterLdapTcpParser();
SCRegisterLdapUdpParser();
+ SCRegisterMdnsParser();
SCRegisterTemplateParser();
SCRfbRegisterParser();
SCMqttRegisterParser();
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)
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");
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;
}
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;
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);
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);
}
#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;
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)
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";
DetectBufferTypeSupportsMultiInstance(keyword);
detect_buffer_id = DetectBufferTypeGetByName(keyword);
+
+ SCDetectMdnsResponseRrnameRegister();
}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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 */
#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"
// 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);
OutputFilestoreRegister();
/* dns */
JsonDnsLogRegister();
+ /* mdns */
+ JsonMdnsLogRegister();
/* modbus */
OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonModbusLog", "eve-log.modbus",
OutputJsonLogInitSub, ALPROTO_MODBUS, JsonGenericDirFlowLogger, JsonLogThreadInit,
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,
# 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
# 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