From cba4d5afad8098ff941bffacc624d810f87da5e3 Mon Sep 17 00:00:00 2001 From: Eliska Cervinkova Date: Wed, 26 Aug 2026 14:43:05 +0200 Subject: [PATCH] feat: wait for Suricata startup via pidfile + socket --- README.md | 1 + src/argument.rs | 4 ++ src/suricata.rs | 102 ++++++++++++++++++++++++++++++++++++++-------- src/suriconf.yaml | 1 + src/yaml.rs | 12 ++++++ 5 files changed, 103 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index a75c282..f51825a 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ The entire configuration is defined in a YAML file, typically named `suriconf.ya |-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `suri-configuration` | Path to the default Suricata configuration file. | | `log-dir` | Directory for Suricata logs (requires read/write permissions). | +| `socket` | Path to Suricata socket. | | `preconf-time` | Duration of the Suricata preconfiguration run. | | `analysis` | Analysis type: `dynamic` (multiple Suricata runs) or `static` (single Suricata run). | | `mode` | Output mode: `suggestion` (recommendations only) or `modify` (writes changes to Suricata configuration file).
Modify mode with `yaml_change`: `ask` (user confirms each change) or `force` (all detected changes are applied automatically). | diff --git a/src/argument.rs b/src/argument.rs index 1ce4f80..d3caebe 100644 --- a/src/argument.rs +++ b/src/argument.rs @@ -68,6 +68,10 @@ pub enum Commands { #[clap(short='l', long)] path_to_logs: Option, + /// Change path to Unix Socket + #[clap(short='s', long)] + path_to_socket: Option, + /// Change the time of Suricata preconfiguration run (in seconds) #[clap(short='t', long="time")] preconf_time: Option, diff --git a/src/suricata.rs b/src/suricata.rs index a18235d..2ce4b46 100644 --- a/src/suricata.rs +++ b/src/suricata.rs @@ -6,6 +6,8 @@ SPDX-License-Identifier: BSD-3-Clause This file executes Suricata. */ +const PIDFILE: &str = "/var/run/suriconf_suricata.pid"; + use std::process::{Child, Command}; use crate::yaml::{emergency_check_memcap, Suriconf}; use crate::{FLOW_WINDOW, MIN_RUN}; @@ -16,10 +18,11 @@ use std::time::Duration; use crossbeam_channel::{bounded, select, tick, Receiver}; use std::process::Stdio; use std::io::{BufRead, BufReader}; -use std::thread; -use procfs::process::{all_processes, Process}; +use std::{fs, thread}; +use procfs::process::{Process}; use signal_hook::consts::SIGINT; use signal_hook::iterator::Signals; +use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -35,6 +38,14 @@ pub fn execute_suricata<'a>(suriconf: &Suriconf, logs: &mut CreatedLogs, options let mut suricata_again = SuricataAgain::default(); let mut vec_of_sur_cmd: Vec = vec![]; + match delete_pid_file() { + Err(e) => { + panic!("{e}"); + } + Ok(()) => {} + }; + + set_pid_file(&mut vec_of_sur_cmd); get_capture_mode(suriconf, &mut vec_of_sur_cmd); if cfg!(target_os = "windows") { @@ -66,7 +77,7 @@ pub fn execute_suricata<'a>(suriconf: &Suriconf, logs: &mut CreatedLogs, options let mut child = Command::new("sudo") .arg("-n") .args(args) - .stdout(Stdio::piped()) + .stdout(Stdio::null()) .stderr(Stdio::piped()) .spawn() .expect("Failed to execute process."); @@ -81,6 +92,14 @@ pub fn execute_suricata<'a>(suriconf: &Suriconf, logs: &mut CreatedLogs, options } }); + let suri_pid = match wait_on_suricata_start(&suriconf.socket, &mut child) { + Ok(pid) => pid, + Err(e) => { + kill_suricata(&mut child); + panic!("{e}") + } + }; + let timeout = Duration::from_secs(suriconf.preconf_time); let start = std::time::Instant::now(); let ticks_thread1 = tick(Duration::from_millis(100)); @@ -140,7 +159,6 @@ pub fn execute_suricata<'a>(suriconf: &Suriconf, logs: &mut CreatedLogs, options let ticks = tick(Duration::from_millis(100)); let emergency_ticks = tick(Duration::from_secs(FLOW_WINDOW)); - let suri_pid = check_process_name_for_suricata_main().expect("Unable to get Suricata-Main."); loop { select! { @@ -198,6 +216,26 @@ pub fn get_capture_mode(suriconf: &Suriconf, vec_of_sur_cmd: &mut Vec) { }); } +pub fn delete_pid_file() -> std::io::Result<()> { + let status = Command::new("sudo") + .arg("-n") + .arg("rm") + .arg("-f") + .arg(PIDFILE) + .status()?; + + if status.success() { + Ok(()) + } else { + Err(std::io::Error::other("Failed to delete Suricata PID file.")) + } +} + +pub fn set_pid_file(vec_of_sur_cmd: &mut Vec) { + vec_of_sur_cmd.push("--pidfile".to_string()); + vec_of_sur_cmd.push(PIDFILE.to_string()); +} + pub fn get_cpu_usage(sys: &mut SystemVar) { sys.sys.refresh_cpu_usage(); @@ -272,24 +310,53 @@ pub fn get_workers(sys: &mut SystemVar) -> u64 { } workers } - -fn check_process_name_for_suricata_main() -> Option { - for _ in 0..10 { - for prc in all_processes().expect("Unable to get all processes.") { - let process: Process; - match prc { - Ok(prc) => {process = prc} - Err(_) => {continue} +fn get_suricata_pid() -> Result { + for _ in 0..20 { + if let Ok(content) = fs::read_to_string(PIDFILE) { + if let Ok(pid) = content.trim().parse::() { + if let Ok(comm) = fs::read_to_string(format!("/proc/{}/comm", pid)) { + if comm.trim() == "Suricata-Main" { + return Ok(pid); + } + } } + } + thread::sleep(Duration::from_millis(1000)); + } + Err("Suricata process not found.".into()) +} +fn wait_on_suricata_start(socket: &PathBuf, child: &mut Child) -> Result { + match get_suricata_pid() { + Ok(pid) => { + let socket = socket.to_str().ok_or("Socket path is not valid UTF-8.")?; + for _ in 0..120 { + let output = Command::new("sudo") + .arg("-n") + .arg("suricatasc") + .arg("-c") + .arg("uptime") + .arg(socket) + .output() + .map_err(|e| format!("Unable to execute Suricata socket control tool: {e}"))?; + + if output.status.success() { + return Ok(pid) + } - if process.stat().expect("Unable to find stats about process.").comm == "Suricata-Main" { - return Some(process.pid); + if let Some(status) = child.try_wait().expect("Unable to get Suricata status.") { + return Err(format!("Suricata exited during startup with status: {status}.")); + } + + thread::sleep(Duration::from_millis(1000)); } + Err("Suricata could not start.".into()) + } + Err(e) => { + Err(e) } - thread::sleep(Duration::from_millis(100)); } - None } + fn get_cores_with_threads(suri_pid: i32, sys: &mut SystemVar) { let proc = Process::new(suri_pid).expect("Unable to create process."); let tasks = proc.tasks().expect("Unable to get process tasks."); @@ -330,6 +397,7 @@ pub fn kill_suricata(child: &mut Child) { .arg("Suricata-Main") .status() .expect("Unable to pkill Suricata-Main (SIGKILL)."); + if !output.success() { panic!("Process failed."); } @@ -356,4 +424,4 @@ pub fn check_min_suricata_runtime_for_modules(suriconf: &Suriconf) { panic!("Unable to execute Suricata and have enough samples from preconfiguration, \ FlowThreads module needs at least 6 minutes.") } -} \ No newline at end of file +} diff --git a/src/suriconf.yaml b/src/suriconf.yaml index 2837c83..07131d2 100644 --- a/src/suriconf.yaml +++ b/src/suriconf.yaml @@ -9,6 +9,7 @@ ethtool-bin: /usr/bin/ethtool ifconfig-bin: /usr/sbin/ifconfig ip-bin: /usr/sbin/ip log-dir: /var/log/suricata/ +socket: /var/run/suricata/suricata-command.socket preconf-time: 360 # in seconds # 360 is minimum for flow_threads module analysis: static # static/dynamic diff --git a/src/yaml.rs b/src/yaml.rs index d39d6d7..cd63077 100644 --- a/src/yaml.rs +++ b/src/yaml.rs @@ -726,6 +726,7 @@ pub struct Suriconf { pub ifconfig_bin: PathBuf, pub ip_bin: PathBuf, pub log_dir: PathBuf, + pub socket: PathBuf, pub preconf_time: u64, pub analysis: Analysis, pub mode: Mode, @@ -844,6 +845,13 @@ impl Suriconf { self.find_log_dir(suriconf_string).expect("Unable to parse path to logs.") }; + self.socket = if let Some(Commands::Suricata { path_to_socket: Some(p), .. }) = &args.cmd { + p.clone() + } + else { + self.find_socket(suriconf_string).expect("Unable to parse path to unix socket.") + }; + self.preconf_time = if let Some( Commands::Suricata { preconf_time: Some(p), .. }) = &args.cmd { p.clone() } @@ -952,6 +960,10 @@ impl Suriconf { text.get("log-dir").and_then(|c| c.as_str()).map(|c| PathBuf::from(c)) } + pub fn find_socket(&self, text: &Value) -> Option { + text.get("socket").and_then(|c| c.as_str()).map(|c| PathBuf::from(c)) + } + pub fn find_preconf_time(&self, text: &Value) -> Option { text.get("preconf-time").and_then(|t| t.as_u64()) }