#include <sys/un.h>
#include <cassert>
+#include <chrono>
#include <unordered_map>
#include "log/messages.h"
+#include "main.h"
#include "main/shell.h"
#include "main/snort_config.h"
#include "main/thread.h"
#endif
static int listener = -1;
-static socklen_t sock_addr_size = 0;
+static socklen_t sock_addr_size = 0, my_sock_addr_size = 0;
static struct sockaddr* sock_addr = nullptr;
static struct sockaddr_in in_addr;
static struct sockaddr_un unix_addr;
// FIXIT-M want to disable time wait
int on = 1;
setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
-
+ my_sock_addr_size = sock_addr_size;
if (bind(listener, sock_addr, sock_addr_size) < 0)
FatalError("Failed to bind control listener: %s\n", get_error(errno));
return 0;
}
+// Helper function to wait for the prompt with a timeout
+static bool wait_for_prompt(int fd, const std::string& prompt, int timeout_ms)
+{
+ char buffer[1024];
+ std::string response;
+ auto start_time = std::chrono::steady_clock::now();
+
+ while (true)
+ {
+ ssize_t bytes = read(fd, buffer, sizeof(buffer) - 1);
+ if (bytes < 0)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ {
+ // Check if the timeout has been exceeded
+ auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now() - start_time);
+ if (elapsed.count() >= timeout_ms)
+ {
+ return false; // Timeout
+ }
+ continue; // Retry
+ }
+ else
+ {
+ ErrorMessage("Failed to read response: %s\n", get_error(errno));
+ return false;
+ }
+ }
+ else if (bytes == 0)
+ {
+ return false; // Connection closed
+ }
+
+ buffer[bytes] = '\0';
+ response += buffer;
+
+ // Check if the prompt is found in the response
+ if (response.find(prompt) != std::string::npos)
+ {
+ return true;
+ }
+ }
+}
+
+bool ControlMgmt::send_command_to_socket(const std::string& command)
+{
+ std::string prompt = get_prompt();
+ if (listener < 0)
+ {
+ ErrorMessage("Listener is not initialized.\n");
+ return false;
+ }
+
+ int fd = socket(sock_addr->sa_family, SOCK_STREAM, 0);
+ if (fd < 0)
+ {
+ ErrorMessage("Failed to create socket: %s\n", get_error(errno));
+ return false;
+ }
+
+ if (connect(fd, sock_addr, my_sock_addr_size) >= 0 && wait_for_prompt(fd, prompt, 30) &&
+ write(fd, command.c_str(), command.size()) >= 0)
+ {
+ // Wait for the prompt again to ensure the command was processed
+ if (wait_for_prompt(fd, prompt, 90))
+ {
+ close(fd);
+ return true;
+ }
+ }
+ close(fd);
+ return false;
+}
+
+
void ControlMgmt::socket_term()
{
clear_controls();
#ifndef CONTROL_MGMT_H
#define CONTROL_MGMT_H
+#include <string>
+
class ControlConn;
struct lua_State;
static void socket_term();
static ControlConn* find_control(const lua_State*);
+ static bool send_command_to_socket(const std::string& command);
static bool service_users();
};
add_library ( log OBJECT
${LOG_INCLUDES}
+ batched_logger.cc
+ batched_logger.h
log.cc
log.h
log_errors.h
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2025-2025 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+// batched_logger.cc author Steven Baigal <sbaigal@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "batched_logger.h"
+
+#include <algorithm>
+#include <cstdlib>
+#include <memory>
+#include <pthread.h>
+#include <sched.h>
+
+#include "control/control_mgmt.h"
+#include "main/snort_config.h"
+#include "main/thread_config.h"
+#include "utils/snort_pcre.h"
+#include "utils/util.h"
+
+namespace BatchedLogger
+{
+
+thread_local LogBuffer BatchedLogManager::buffer;
+BatchQueue BatchedLogManager::queue;
+std::thread BatchedLogManager::writer_thread;
+std::atomic<bool> BatchedLogManager::running(false);
+std::atomic<uint64_t> BatchQueue::overwrite_count(0);
+
+void LogBuffer::append(FILE* fh, bool use_syslog, const char* msg, size_t len)
+{
+ if (size + len >= LOG_BUFFER_THRESHOLD)
+ flush();
+ std::memcpy(buffer + size, msg, len);
+ size += len;
+ this->fh = fh;
+ this->use_syslog = use_syslog;
+}
+
+void LogBuffer::flush()
+{
+ if (size == 0)
+ return;
+
+ LogBatch batch;
+ batch.data.assign(buffer, buffer + size);
+ batch.size = size;
+ batch.fh = fh;
+ batch.use_syslog = use_syslog;
+ batch.is_control_message = false;
+ BatchedLogManager::push_batch(std::move(batch));
+ size = 0;
+ last_flush_time = std::chrono::steady_clock::now();
+}
+
+void LogBuffer::send_control_message(const char* msg, size_t len)
+{
+ if (len > LOG_BUFFER_THRESHOLD)
+ return;
+
+ LogBatch batch;
+ batch.data.assign(msg, msg + len);
+ batch.size = len;
+ batch.fh = nullptr;
+ batch.use_syslog = false;
+ batch.is_control_message = true;
+ BatchedLogManager::push_batch(std::move(batch));
+}
+
+void BatchQueue::push(LogBatch&& batch)
+{
+ std::lock_guard<std::mutex> lock(mtx);
+ size_t next_tail = tail + 1;
+
+ if (next_tail >= LOG_QUEUE_SIZE)
+ next_tail = 0;
+ if (next_tail == head)
+ {
+ head++;
+ if (head >= LOG_QUEUE_SIZE)
+ head = 0;
+ overwrite_count++;
+ }
+
+ buffer[tail] = std::move(batch);
+ tail = next_tail;
+ cv.notify_one();
+}
+
+bool BatchQueue::pop(LogBatch& batch)
+{
+ std::unique_lock<std::mutex> lock(mtx);
+
+ if (head == tail)
+ return false;
+
+ batch = std::move(buffer[head]);
+ head++;
+ if (head >= LOG_QUEUE_SIZE)
+ head = 0;
+ return true;
+}
+
+void BatchQueue::wait()
+{
+ std::unique_lock<std::mutex> lock(mtx);
+ cv.wait(lock, [this] { return head != tail; });
+}
+
+bool BatchQueue::empty() const
+{
+ std::lock_guard<std::mutex> lock(mtx);
+ return head == tail;
+}
+
+struct FilterData
+{
+ std::string filter;
+ pcre2_code* re = nullptr;
+ pcre2_match_data* match_data = nullptr;
+ bool stop_trace = false;
+ uint64_t revision = 0;
+
+ void clear()
+ {
+ if (match_data) pcre2_match_data_free(match_data);
+ if (re) pcre2_code_free(re);
+ filter.clear();
+ match_data = nullptr;
+ re = nullptr;
+ stop_trace = false;
+ revision++;
+ }
+};
+
+
+static FilterData s_filter;
+
+static void update_filter(const std::string& pattern_s)
+{
+ s_filter.clear();
+ if (pattern_s.empty())
+ return;
+
+ if (pattern_s[0] == 'Y')
+ s_filter.stop_trace = true;
+
+ std::string pattern = pattern_s.substr(1);
+ int error_code;
+ PCRE2_SIZE error_offset;
+
+ pcre2_code* re = pcre2_compile((PCRE2_SPTR)pattern.c_str(), PCRE2_ZERO_TERMINATED,
+ PCRE2_MULTILINE, &error_code, &error_offset, nullptr);
+
+ if (!re)
+ return;
+
+ pcre2_match_data* match_data = pcre2_match_data_create_from_pattern(re, nullptr);
+ if (!match_data)
+ {
+ pcre2_code_free(re);
+ return;
+ }
+
+ s_filter.filter = std::move(pattern);
+ s_filter.re = re;
+ s_filter.match_data = match_data;
+}
+
+void BatchedLogManager::set_filter(const std::string& pattern)
+{
+ LogBuffer::send_control_message(pattern.c_str(), pattern.size());
+}
+
+void BatchedLogManager::shutdown()
+{
+ if (!running)
+ return;
+ flush_thread_buffers();
+ running = false;
+ queue.push({});
+
+ if (writer_thread.joinable())
+ writer_thread.join();
+
+ if (BatchQueue::get_overwrite_count() > 0)
+ fprintf(stderr, "BatchedLogManager Stats: Ring buffer overwrites = %lu\n",
+ static_cast<unsigned long>(BatchQueue::get_overwrite_count()));
+
+ if (!s_filter.filter.empty())
+ s_filter.clear();
+}
+
+void BatchedLogManager::log(FILE* fh, bool use_syslog, const char* msg, size_t len)
+{
+ buffer.append(fh, use_syslog, msg, len);
+ auto now = std::chrono::steady_clock::now();
+ auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - buffer.last_flush_time);
+
+ if (buffer.size >= LOG_BUFFER_THRESHOLD || elapsed >= LOG_TIME_THRESHOLD)
+ buffer.flush();
+#ifdef REG_TEST
+ flush_thread_buffers(); // Force flush for regression tests
+#endif
+}
+
+#if 0
+void BatchedLogManager::log(FILE* fh, bool use_syslog, const char* format, va_list& ap)
+{
+ static char temp[1024];
+ int len = vsnprintf(temp, sizeof(temp), format, ap);
+
+ buffer.append(fh, use_syslog, temp, len);
+ auto now = std::chrono::steady_clock::now();
+ auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - buffer.last_flush_time);
+
+ if (buffer.size >= LOG_BUFFER_THRESHOLD || elapsed >= LOG_TIME_THRESHOLD)
+ buffer.flush();
+
+#ifdef REG_TEST
+ flush_thread_buffers(); // Force flush for regression tests
+#endif
+
+}
+#endif
+void BatchedLogManager::flush_thread_buffers()
+{
+ buffer.flush();
+}
+
+void BatchedLogManager::push_batch(LogBatch&& batch)
+{
+ queue.push(std::move(batch));
+}
+
+void BatchedLogManager::print_batch(const LogBatch& batch)
+{
+ static bool stop_trace = false;
+ static uint64_t revision = 0;
+
+ // reset condition if filter was changed by user
+ if (s_filter.revision != revision)
+ {
+ revision = s_filter.revision;
+ stop_trace = false;
+ }
+ if (stop_trace)
+ return;
+
+ if (!s_filter.filter.empty())
+ {
+ int rc = pcre2_match(
+ s_filter.re, reinterpret_cast<PCRE2_SPTR>(batch.data.data()),
+ batch.size, 0, 0, s_filter.match_data, nullptr);
+
+ if (rc < 0)
+ return; // Skip printing if no match
+ stop_trace = s_filter.stop_trace;
+ revision = s_filter.revision;
+ }
+
+ if (!batch.use_syslog && batch.fh)
+ {
+ if ( snort::SnortConfig::log_quiet() and batch.fh == stdout )
+ return;
+
+ fprintf(batch.fh, "%.*s", static_cast<int>(batch.size), batch.data.data());
+ fflush(batch.fh);
+ }
+ else
+ {
+ const char* data = batch.data.data();
+ size_t len = batch.size;
+ const char* start = data;
+ const char* end = data + len;
+
+ while (start < end)
+ {
+ const char* line_end = start;
+
+ while (line_end < end && *line_end != '\n')
+ ++line_end;
+
+ size_t line_len = line_end - start;
+
+ if (line_len > 0)
+ syslog(LOG_DAEMON | LOG_NOTICE, "%.*s", static_cast<int>(line_len), start);
+
+ start = line_end + 1;
+ }
+ }
+#ifdef SHELL
+ if (stop_trace)
+ if (!ControlMgmt::send_command_to_socket("packet_tracer.disable()\n"))
+ fprintf(stderr, "Batched_logger: Failed to send command to control socket\n");
+#endif
+}
+
+void BatchedLogManager::writer_thread_func()
+{
+ while (running || !queue.empty())
+ {
+ LogBatch batch;
+
+ if (queue.pop(batch))
+ {
+ if (batch.is_control_message)
+ update_filter(std::string(batch.data.data(), batch.size));
+ else
+ print_batch(batch);
+ }
+ else
+ queue.wait();
+ }
+}
+
+void BatchedLogManager::init()
+{
+ running = true;
+
+ snort::ThreadConfig* thread_config = snort::SnortConfig::get_conf()->thread_config;
+ thread_config->implement_named_thread_affinity("BatchedLoggerWriter");
+ writer_thread = std::thread(writer_thread_func);
+ SET_THREAD_NAME(writer_thread.native_handle(), "snort.logger");
+ thread_config->implement_thread_affinity(STHREAD_TYPE_MAIN, snort::ThreadConfig::DEFAULT_THREAD_ID);
+
+ std::atexit(BatchedLogManager::shutdown);
+
+ sched_param sch_params;
+ sch_params.sched_priority = 1;
+ pthread_setschedparam(writer_thread.native_handle(), SCHED_OTHER, &sch_params);
+}
+
+} // namespace BatchedLogger
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2025-2025 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+// batched_logger.h author Steven Baigal <sbaigal@cisco.com>
+
+#ifndef BATCHED_LOGGER_H
+#define BATCHED_LOGGER_H
+
+#include <atomic>
+#include <condition_variable>
+#include <chrono>
+#include <cstdarg>
+#include <cstdint>
+#include <cstring>
+#include <syslog.h>
+#include <stdio.h>
+#include <vector>
+#include <thread>
+
+namespace BatchedLogger
+{
+
+const size_t LOG_BUFFER_THRESHOLD = 8192;
+const size_t LOG_QUEUE_SIZE = 8192;
+const std::chrono::milliseconds LOG_TIME_THRESHOLD(10);
+
+struct LogBatch
+{
+ std::vector<char> data;
+ size_t size = 0;
+ FILE* fh = nullptr;
+ bool use_syslog = false;
+ bool is_control_message = false;
+};
+
+class LogBuffer
+{
+public:
+ char buffer[LOG_BUFFER_THRESHOLD];
+ size_t size = 0;
+ std::chrono::steady_clock::time_point last_flush_time = std::chrono::steady_clock::now();
+
+ void append(FILE* fh, bool use_syslog, const char* msg, size_t len);
+ void flush();
+ static void send_control_message(const char* msg, size_t len);
+private:
+ FILE* fh = nullptr;
+ bool use_syslog = true;
+};
+
+class BatchQueue
+{
+private:
+ std::vector<LogBatch> buffer;
+ size_t head = 0;
+ size_t tail = 0;
+ mutable std::mutex mtx;
+ std::condition_variable cv;
+ static std::atomic<uint64_t> overwrite_count;
+
+public:
+ BatchQueue() : buffer(LOG_QUEUE_SIZE) {}
+
+ void push(LogBatch&& batch);
+ bool pop(LogBatch& batch);
+ void wait();
+ bool empty() const;
+ static uint64_t get_overwrite_count() { return overwrite_count.load(); }
+};
+
+class BatchedLogManager
+{
+public:
+ static void init();
+ static void shutdown();
+ static void log(FILE* fh, bool use_syslog, const char* msg, size_t len);
+ //static void log(FILE* fh, bool use_syslog, const char* format, va_list& ap);
+ static void flush_thread_buffers();
+ static void push_batch(LogBatch&& batch);
+ static void set_filter(const std::string& filter);
+
+private:
+ static thread_local LogBuffer buffer;
+ static BatchQueue queue;
+ static std::thread writer_thread;
+ static std::atomic<bool> running;
+
+ static void writer_thread_func();
+ static void print_batch(const LogBatch& batch);
+};
+
+} // namespace BatchedLogger
+
+#endif // BATCHED_LOGGER_H
#include "utils/util.h"
#include "utils/util_cstring.h"
+#include "batched_logger.h"
#include "log_text.h"
#include "messages.h"
void CloseLogger()
{
+ BatchedLogger::BatchedLogManager::shutdown();
TextLog_Term(text_log);
}
#include "catch/snort_catch.h"
#endif
+#include "log/batched_logger.h"
#include "snort_config.h"
#include "thread_config.h"
init(argc, argv);
const SnortConfig* sc = SnortConfig::get_conf();
+ BatchedLogger::BatchedLogManager::init();
if ( sc->daemon_mode() )
daemonize();
if ( !SnortConfig::get_conf()->test_mode() ) // FIXIT-M ideally the check is in one place
PrintStatistics();
- CloseLogger();
ThreadConfig::term();
clean_exit(0);
+ CloseLogger();
}
void Snort::reload_failure_cleanup(SnortConfig* sc)
eth_t* eth_close(eth_t*) { return nullptr; }
ssize_t eth_send(eth_t*, const void*, size_t) { return -1; }
void HostAttributesManager::initialize() { }
-
void select_default_policy(const _daq_pkt_hdr&, const snort::SnortConfig*) { }
void select_default_policy(const _daq_flow_stats&, const snort::SnortConfig*) { }
#include <sstream>
#include "detection/ips_context.h"
+#include "log/batched_logger.h"
#include "log/messages.h"
+#include "main/snort_config.h"
#include "main/thread.h"
#include "protocols/eth.h"
#include "protocols/icmp4.h"
const char* drop_reason = p->active->get_drop_reason();
if (drop_reason)
PacketTracer::log("Verdict Reason: %s, %s\n", drop_reason, p->active->get_action_string() );
- LogMessage(s_pkt_trace->log_fh, "%s\n", s_pkt_trace->buffer);
+ if (s_pkt_trace->buff_len < max_buff_size - 1)
+ s_pkt_trace->buffer[s_pkt_trace->buff_len++] = '\n';
+ BatchedLogger::BatchedLogManager::log(s_pkt_trace->log_fh, SnortConfig::log_syslog(),
+ s_pkt_trace->buffer, s_pkt_trace->buff_len);
}
s_pkt_trace->reset(false);
// -----------------------------------------------------------------------------
PacketTracer::PacketTracer()
+ : pt_timer(new Stopwatch<SnortClock>),
+ buffer(new char[max_buff_size]()),
+ daq_buffer(new char[max_buff_size]()),
+ debug_session(new char[PT_DEBUG_SESSION_ID_SIZE])
{
- pt_timer = new Stopwatch<SnortClock>;
- buffer = new char[max_buff_size] { };
- daq_buffer = new char[max_buff_size] { };
- debug_session = new char[PT_DEBUG_SESSION_ID_SIZE];
+ dbg_str.reserve(256);
+ debugstr.reserve(256);
mutes.resize(global_mutes.val, false);
open_file();
void PacketTracer::log_va(const char* format, va_list ap, bool daq_log, bool msg_only)
{
// FIXIT-L Need to find way to add 'PktTracerDbg' string as part of format string.
- std::string dbg_str;
if (shell_enabled and !daq_log) // only add debug string during shell execution
{
dbg_str = "PktTracerDbg ";
if (shell_enabled)
{
PacketTracer::log("\n");
-
oss << sipstr << " " << sport << " -> "
<< dipstr << " " << dport << " "
<< std::to_string(to_utype(proto))
void open_file();
virtual void dump_to_daq(Packet*);
void reset(bool);
+private:
+ std::string dbg_str;
};
struct PacketTracerSuspend
#include <lua.hpp>
#include "control/control.h"
+#include "log/batched_logger.h"
#include "log/messages.h"
#include "main/analyzer_command.h"
#include "main/snort_config.h"
{"dst_ip", Parameter::PT_STRING, nullptr, nullptr, "destination IP address filter"},
{"dst_port", Parameter::PT_INT, "0:65535", nullptr, "destination port filter"},
{"tenants", Parameter::PT_STRING, nullptr, nullptr, "tenants filter"},
+ {"regex", Parameter::PT_STRING, nullptr, nullptr, "regex filter"},
+ {"stop_after_match", Parameter::PT_BOOL, nullptr, nullptr, "stop trace after match is found"},
{nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr}
};
int dport = luaL_optint(L, 5, 0);
const char *tenantsstr = luaL_optstring(L, 6, nullptr);
+ const char *regexstr = luaL_optstring(L, 7, nullptr);
+ bool stop_after_match = luaL_opt(L,lua_toboolean, 8, false);
SfIp sip, dip;
sip.clear();
constraints.set_bits |= PacketConstraints::SetBits::SRC_PORT;
if ( dport )
constraints.set_bits |= PacketConstraints::SetBits::DST_PORT;
+
+ std::string filter_str = regexstr ? regexstr : "";
+ if (!filter_str.empty()) filter_str = (stop_after_match ? "Y" : "N") + filter_str;
+ BatchedLogger::BatchedLogManager::set_filter(filter_str);
main_broadcast_command(new PacketTracerDebug(&constraints), ControlConn::query_from_lua(L));
return 0;
}