mirror of
https://github.com/OISF/suricata.git
synced 2026-10-12 02:07:10 +00:00
llmnr: implement logger
This adds an LLMNR protocol logger that reuses existing DNS functions, following the same approach as the mDNS logger: - No grouped logging - Rdata is logged in a field that is named after the rdata type - Types are logged in lower case - Flags are logged as an array Ticket #8366
This commit is contained in:
committed by
Victor Julien
parent
13df0f7a06
commit
8f205bb34f
+431
@@ -3358,6 +3358,413 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"llmnr": {
|
||||
"description": "LLMNR requests and responses",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"additionals": {
|
||||
"description": "LLMNR additional records",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"a": {
|
||||
"description": "IPv4 address",
|
||||
"type": "string"
|
||||
},
|
||||
"aaaa": {
|
||||
"description": "IPv6 address",
|
||||
"type": "string"
|
||||
},
|
||||
"cname": {
|
||||
"description": "Canonical name",
|
||||
"type": "string"
|
||||
},
|
||||
"mx": {
|
||||
"description": "Mail exchange",
|
||||
"type": "string"
|
||||
},
|
||||
"ns": {
|
||||
"description": "Name server",
|
||||
"type": "string"
|
||||
},
|
||||
"null": {
|
||||
"description": "NULL record data",
|
||||
"type": "string"
|
||||
},
|
||||
"opt": {
|
||||
"description": "EDNS OPT records",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"code": {
|
||||
"description": "EDNS option code",
|
||||
"type": "integer"
|
||||
},
|
||||
"data": {
|
||||
"description": "EDNS option data",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ptr": {
|
||||
"description": "Pointer record",
|
||||
"type": "string"
|
||||
},
|
||||
"rrname": {
|
||||
"description": "Resource name of the record",
|
||||
"type": "string"
|
||||
},
|
||||
"rrname_truncated": {
|
||||
"description": "Name was truncated by Suricata due to length",
|
||||
"type": "boolean"
|
||||
},
|
||||
"rrtype": {
|
||||
"description": "Resource record type",
|
||||
"type": "string"
|
||||
},
|
||||
"ttl": {
|
||||
"description": "Time to live in seconds",
|
||||
"type": "integer"
|
||||
},
|
||||
"txt": {
|
||||
"description": "TXT record values",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"unknown": {
|
||||
"description": "Unknown record type data",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"answers": {
|
||||
"description": "LLMNR answer records",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"a": {
|
||||
"description": "IPv4 address",
|
||||
"type": "string"
|
||||
},
|
||||
"aaaa": {
|
||||
"description": "IPv6 address",
|
||||
"type": "string"
|
||||
},
|
||||
"cname": {
|
||||
"description": "Canonical name",
|
||||
"type": "string"
|
||||
},
|
||||
"mx": {
|
||||
"description": "Mail exchange",
|
||||
"type": "string"
|
||||
},
|
||||
"ns": {
|
||||
"description": "Name server",
|
||||
"type": "string"
|
||||
},
|
||||
"null": {
|
||||
"description": "NULL record data",
|
||||
"type": "string"
|
||||
},
|
||||
"ptr": {
|
||||
"description": "Pointer record",
|
||||
"type": "string"
|
||||
},
|
||||
"rrname": {
|
||||
"description": "Resource name of the record",
|
||||
"type": "string"
|
||||
},
|
||||
"rrname_truncated": {
|
||||
"description": "Name was truncated by Suricata due to length",
|
||||
"type": "boolean"
|
||||
},
|
||||
"rrtype": {
|
||||
"description": "Resource record type",
|
||||
"type": "string"
|
||||
},
|
||||
"soa": {
|
||||
"description": "SOA record data",
|
||||
"$ref": "#/$defs/dns.soa"
|
||||
},
|
||||
"srv": {
|
||||
"description": "SRV record data",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Target hostname",
|
||||
"type": "string"
|
||||
},
|
||||
"port": {
|
||||
"description": "Target port",
|
||||
"type": "integer"
|
||||
},
|
||||
"priority": {
|
||||
"description": "Priority value",
|
||||
"type": "integer"
|
||||
},
|
||||
"weight": {
|
||||
"description": "Weight value",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sshfp": {
|
||||
"description": "SSH fingerprint record",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"algo": {
|
||||
"description": "Algorithm number",
|
||||
"type": "integer"
|
||||
},
|
||||
"fingerprint": {
|
||||
"description": "Fingerprint value",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "Fingerprint type",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ttl": {
|
||||
"description": "Time to live in seconds",
|
||||
"type": "integer"
|
||||
},
|
||||
"txt": {
|
||||
"description": "TXT record values",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"unknown": {
|
||||
"description": "Unknown record type data",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"authorities": {
|
||||
"description": "LLMNR authority records",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"a": {
|
||||
"description": "IPv4 address",
|
||||
"type": "string"
|
||||
},
|
||||
"aaaa": {
|
||||
"description": "IPv6 address",
|
||||
"type": "string"
|
||||
},
|
||||
"cname": {
|
||||
"description": "Canonical name",
|
||||
"type": "string"
|
||||
},
|
||||
"mx": {
|
||||
"description": "Mail exchange",
|
||||
"type": "string"
|
||||
},
|
||||
"ns": {
|
||||
"description": "Name server",
|
||||
"type": "string"
|
||||
},
|
||||
"null": {
|
||||
"description": "NULL record data",
|
||||
"type": "string"
|
||||
},
|
||||
"ptr": {
|
||||
"description": "Pointer record",
|
||||
"type": "string"
|
||||
},
|
||||
"rrname": {
|
||||
"description": "Resource name of the record",
|
||||
"type": "string"
|
||||
},
|
||||
"rrname_truncated": {
|
||||
"description": "Name was truncated by Suricata due to length",
|
||||
"type": "boolean"
|
||||
},
|
||||
"rrtype": {
|
||||
"description": "Resource record type",
|
||||
"type": "string"
|
||||
},
|
||||
"soa": {
|
||||
"description": "SOA record data",
|
||||
"$ref": "#/$defs/dns.soa"
|
||||
},
|
||||
"ttl": {
|
||||
"description": "Time to live in seconds",
|
||||
"type": "integer"
|
||||
},
|
||||
"txt": {
|
||||
"description": "TXT record values",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"unknown": {
|
||||
"description": "Unknown record type data",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flags": {
|
||||
"description": "LLMNR message flags",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["c", "tc", "t"]
|
||||
}
|
||||
},
|
||||
"grouped": {
|
||||
"description": "Grouped answer records by type",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"A": {
|
||||
"description": "IPv4 address records",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"AAAA": {
|
||||
"description": "IPv6 address records",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"CNAME": {
|
||||
"description": "Canonical name records",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"MX": {
|
||||
"description": "Mail exchange records",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"NS": {
|
||||
"description": "Name server records",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"NULL": {
|
||||
"description": "NULL record data",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"PTR": {
|
||||
"description": "Pointer records",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"SOA": {
|
||||
"description": "Start of authority records",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/dns.soa"
|
||||
}
|
||||
},
|
||||
"TXT": {
|
||||
"description": "Text records",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"description": "LLMNR transaction ID",
|
||||
"type": "integer"
|
||||
},
|
||||
"opcode": {
|
||||
"description": "LLMNR opcode value",
|
||||
"type": "integer"
|
||||
},
|
||||
"queries": {
|
||||
"description": "LLMNR query records",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"rrname": {
|
||||
"description": "Resource name being requested",
|
||||
"type": "string"
|
||||
},
|
||||
"rrname_truncated": {
|
||||
"description": "Name was truncated by Suricata due to length",
|
||||
"type": "boolean"
|
||||
},
|
||||
"rrtype": {
|
||||
"type": "string",
|
||||
"description": "Type of resource being requested"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tx_id": {
|
||||
"description": "Internal transaction ID",
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"description": "Type of message, either a request or response",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"request",
|
||||
"response"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"log_level": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5886,6 +6293,14 @@
|
||||
"description": "Errors encountered parsing LDAP/UDP protocol",
|
||||
"$ref": "#/$defs/stats_applayer_error"
|
||||
},
|
||||
"llmnr_tcp": {
|
||||
"description": "Errors encountered parsing LLMNR/TCP protocol",
|
||||
"$ref": "#/$defs/stats_applayer_error"
|
||||
},
|
||||
"llmnr_udp": {
|
||||
"description": "Errors encountered parsing LLMNR/UDP protocol",
|
||||
"$ref": "#/$defs/stats_applayer_error"
|
||||
},
|
||||
"mdns": {
|
||||
"description": "Errors encountered parsing mDNS",
|
||||
"$ref": "#/$defs/stats_applayer_error"
|
||||
@@ -6069,6 +6484,14 @@
|
||||
"type": "integer",
|
||||
"description": "Number of flows LDAP/UDP protocol"
|
||||
},
|
||||
"llmnr_tcp": {
|
||||
"type": "integer",
|
||||
"description": "Number of flows for LLMNR/TCP protocol"
|
||||
},
|
||||
"llmnr_udp": {
|
||||
"type": "integer",
|
||||
"description": "Number of flows for LLMNR/UDP protocol"
|
||||
},
|
||||
"mdns": {
|
||||
"description": "Number of flows for mDNS",
|
||||
"type": "integer"
|
||||
@@ -6243,6 +6666,14 @@
|
||||
"type": "integer",
|
||||
"description": "Number of transactions for LDAP/UDP protocol"
|
||||
},
|
||||
"llmnr_tcp": {
|
||||
"type": "integer",
|
||||
"description": "Number of transactions for LLMNR/TCP protocol"
|
||||
},
|
||||
"llmnr_udp": {
|
||||
"type": "integer",
|
||||
"description": "Number of transactions for LLMNR/UDP protocol"
|
||||
},
|
||||
"mdns": {
|
||||
"description": "Number of transactions for mDNS",
|
||||
"type": "integer"
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
// written by Giuseppe Longo <[email protected]>
|
||||
|
||||
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 llmnr_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("llmnr")?;
|
||||
|
||||
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 LLMNR transaction ID.
|
||||
jb.set_uint("id", tx.tx_id() as u64)?;
|
||||
|
||||
let header = &message.header;
|
||||
if header.flags & (0x0400 | 0x0200 | 0x0100) != 0 {
|
||||
jb.open_array("flags")?;
|
||||
if header.flags & 0x0400 != 0 {
|
||||
jb.append_string("c")?;
|
||||
}
|
||||
if header.flags & 0x0200 != 0 {
|
||||
jb.append_string("tc")?;
|
||||
}
|
||||
if header.flags & 0x0100 != 0 {
|
||||
jb.append_string("t")?;
|
||||
}
|
||||
jb.close()?;
|
||||
}
|
||||
|
||||
let opcode = ((header.flags >> 11) & 0xf) as u8;
|
||||
jb.set_uint("opcode", opcode as u64)?;
|
||||
|
||||
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(&llmnr_log_json_answer_detail(entry)?)?;
|
||||
}
|
||||
jb.close()?;
|
||||
}
|
||||
|
||||
if !message.authorities.is_empty() {
|
||||
jb.open_array("authorities")?;
|
||||
for entry in &message.authorities {
|
||||
jb.append_object(&llmnr_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(&llmnr_log_json_answer_detail(entry)?)?;
|
||||
}
|
||||
if is_jb_open {
|
||||
jb.close()?;
|
||||
}
|
||||
}
|
||||
|
||||
jb.close()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn SCLLMNRLogJson(tx: &mut DNSTransaction, jb: &mut JsonBuilder) -> bool {
|
||||
log_json(tx, jb).is_ok()
|
||||
}
|
||||
@@ -20,3 +20,4 @@
|
||||
// written by Giuseppe Longo <[email protected]>
|
||||
|
||||
pub mod llmnr;
|
||||
pub mod log;
|
||||
|
||||
@@ -369,6 +369,7 @@ noinst_HEADERS = \
|
||||
output-json-ftp.h \
|
||||
output-json-http.h \
|
||||
output-json-ike.h \
|
||||
output-json-llmnr.h \
|
||||
output-json-mdns.h \
|
||||
output-json-metadata.h \
|
||||
output-json-mqtt.h \
|
||||
@@ -943,6 +944,7 @@ libsuricata_c_a_SOURCES = \
|
||||
output-json-ftp.c \
|
||||
output-json-http.c \
|
||||
output-json-ike.c \
|
||||
output-json-llmnr.c \
|
||||
output-json-mdns.c \
|
||||
output-json-metadata.c \
|
||||
output-json-mqtt.c \
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
// written by Giuseppe Longo <[email protected]>
|
||||
|
||||
#include "suricata-common.h"
|
||||
#include "conf.h"
|
||||
|
||||
#include "threadvars.h"
|
||||
|
||||
#include "util-byte.h"
|
||||
#include "util-debug.h"
|
||||
#include "util-mem.h"
|
||||
#include "app-layer-parser.h"
|
||||
#include "output.h"
|
||||
|
||||
#include "output-json.h"
|
||||
#include "output-json-llmnr.h"
|
||||
#include "rust.h"
|
||||
|
||||
typedef struct SCLLMNRLogFileCtx_ {
|
||||
uint64_t flags; /** Store mode */
|
||||
OutputJsonCtx *eve_ctx;
|
||||
} SCLLMNRLogFileCtx;
|
||||
|
||||
typedef struct LogLLMNRLogThread_ {
|
||||
SCLLMNRLogFileCtx *llmnrlog_ctx;
|
||||
OutputJsonThreadCtx *ctx;
|
||||
} SCLLMNRLogThread;
|
||||
|
||||
bool AlertJsonLLMNR(void *txptr, SCJsonBuilder *js)
|
||||
{
|
||||
return SCLLMNRLogJson(txptr, js);
|
||||
}
|
||||
|
||||
static int JsonLLMNRLogger(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f,
|
||||
void *alstate, void *txptr, uint64_t tx_id)
|
||||
{
|
||||
SCLLMNRLogThread *td = (SCLLMNRLogThread *)thread_data;
|
||||
SCLLMNRLogFileCtx *llmnrlog_ctx = td->llmnrlog_ctx;
|
||||
|
||||
SCJsonBuilder *jb = CreateEveHeader(p, LOG_DIR_FLOW, "llmnr", NULL, llmnrlog_ctx->eve_ctx);
|
||||
if (unlikely(jb == NULL)) {
|
||||
return TM_ECODE_OK;
|
||||
}
|
||||
|
||||
if (SCLLMNRLogJson(txptr, jb)) {
|
||||
OutputJsonBuilderBuffer(tv, p, p->flow, jb, td->ctx);
|
||||
}
|
||||
SCJbFree(jb);
|
||||
|
||||
return TM_ECODE_OK;
|
||||
}
|
||||
|
||||
static TmEcode SCLLMNRLogThreadInit(ThreadVars *t, const void *initdata, void **data)
|
||||
{
|
||||
SCLLMNRLogThread *aft = SCCalloc(1, sizeof(SCLLMNRLogThread));
|
||||
if (unlikely(aft == NULL))
|
||||
return TM_ECODE_FAILED;
|
||||
|
||||
if (initdata == NULL) {
|
||||
SCLogDebug("Error getting context for EveLogLLMNR. \"initdata\" argument NULL");
|
||||
goto error_exit;
|
||||
}
|
||||
|
||||
/* Use the Output Context (file pointer and mutex) */
|
||||
aft->llmnrlog_ctx = ((OutputCtx *)initdata)->data;
|
||||
aft->ctx = CreateEveThreadCtx(t, aft->llmnrlog_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 SCLLMNRLogThreadDeinit(ThreadVars *t, void *data)
|
||||
{
|
||||
SCLLMNRLogThread *aft = (SCLLMNRLogThread *)data;
|
||||
if (aft == NULL) {
|
||||
return TM_ECODE_OK;
|
||||
}
|
||||
FreeEveThreadCtx(aft->ctx);
|
||||
|
||||
/* clear memory */
|
||||
memset(aft, 0, sizeof(SCLLMNRLogThread));
|
||||
|
||||
SCFree(aft);
|
||||
return TM_ECODE_OK;
|
||||
}
|
||||
|
||||
static void SCLLMNRLogDeInitCtxSub(OutputCtx *output_ctx)
|
||||
{
|
||||
SCLogDebug("cleaning up sub output_ctx %p", output_ctx);
|
||||
SCLLMNRLogFileCtx *llmnrlog_ctx = (SCLLMNRLogFileCtx *)output_ctx->data;
|
||||
SCFree(llmnrlog_ctx);
|
||||
SCFree(output_ctx);
|
||||
}
|
||||
|
||||
static OutputInitResult JsonLLMNRLogInitCtxSub(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;
|
||||
|
||||
SCLLMNRLogFileCtx *llmnrlog_ctx = SCCalloc(1, sizeof(SCLLMNRLogFileCtx));
|
||||
if (unlikely(llmnrlog_ctx == NULL)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
llmnrlog_ctx->eve_ctx = ojc;
|
||||
llmnrlog_ctx->flags = ~0ULL;
|
||||
|
||||
OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx));
|
||||
if (unlikely(output_ctx == NULL)) {
|
||||
SCFree(llmnrlog_ctx);
|
||||
return result;
|
||||
}
|
||||
|
||||
output_ctx->data = llmnrlog_ctx;
|
||||
output_ctx->DeInit = SCLLMNRLogDeInitCtxSub;
|
||||
|
||||
SCLogDebug("LLMNR log sub-module initialized");
|
||||
|
||||
SCAppLayerParserRegisterLogger(IPPROTO_UDP, ALPROTO_LLMNR);
|
||||
SCAppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_LLMNR);
|
||||
|
||||
result.ctx = output_ctx;
|
||||
result.ok = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void JsonLLMNRLogRegister(void)
|
||||
{
|
||||
OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonLLMNRLog", "eve-log.llmnr",
|
||||
JsonLLMNRLogInitCtxSub, ALPROTO_LLMNR, JsonLLMNRLogger, SCLLMNRLogThreadInit,
|
||||
SCLLMNRLogThreadDeinit);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
// written by Giuseppe Longo <[email protected]>
|
||||
|
||||
#ifndef SURICATA_OUTPUT_JSON_LLMNR_H
|
||||
#define SURICATA_OUTPUT_JSON_LLMNR_H
|
||||
|
||||
void JsonLLMNRLogRegister(void);
|
||||
bool AlertJsonLLMNR(void *txptr, SCJsonBuilder *js);
|
||||
|
||||
#endif /* SURICATA_OUTPUT_JSON_LLMNR_H */
|
||||
@@ -83,6 +83,7 @@
|
||||
#include "app-layer-parser.h"
|
||||
#include "output-filestore.h"
|
||||
#include "output-json-arp.h"
|
||||
#include "output-json-llmnr.h"
|
||||
|
||||
typedef struct RootLogger_ {
|
||||
OutputLogFunc LogFunc;
|
||||
@@ -948,6 +949,7 @@ void OutputRegisterRootLoggers(void)
|
||||
// ALPROTO_DCERPC special: uses state
|
||||
RegisterSimpleJsonApplayerLogger(ALPROTO_DNS, (EveJsonSimpleTxLogFunc)AlertJsonDns, NULL);
|
||||
RegisterSimpleJsonApplayerLogger(ALPROTO_MDNS, (EveJsonSimpleTxLogFunc)AlertJsonMdns, NULL);
|
||||
RegisterSimpleJsonApplayerLogger(ALPROTO_LLMNR, (EveJsonSimpleTxLogFunc)AlertJsonLLMNR, 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);
|
||||
@@ -1225,6 +1227,8 @@ void OutputRegisterLoggers(void)
|
||||
}
|
||||
/* ARP JSON logger */
|
||||
JsonArpLogRegister();
|
||||
/* LLMNR JSON logger */
|
||||
JsonLLMNRLogRegister();
|
||||
|
||||
for (size_t i = 0; i < preregistered_loggers_nb; i++) {
|
||||
OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", preregistered_loggers[i].logname,
|
||||
|
||||
@@ -345,6 +345,7 @@ outputs:
|
||||
- quic
|
||||
- ldap
|
||||
- pop3
|
||||
- llmnr
|
||||
#- modbus
|
||||
- arp:
|
||||
enabled: no # Many events can be logged. Disabled by default
|
||||
|
||||
Reference in New Issue
Block a user