use crate::ebpf::metrics::*;
use crate::error::Result;
use std::collections::HashMap;
#[cfg(feature = "ebpf")]
use aya::{maps::HashMap as BpfHashMap, Ebpf, EbpfLoader};
#[cfg(feature = "ebpf")]
#[allow(dead_code)]
type EbpfMaps = (
Ebpf,
BpfHashMap<aya::maps::MapData, u32, u32>,
BpfHashMap<aya::maps::MapData, u32, u64>,
);
#[cfg(feature = "ebpf")]
#[repr(align(8))]
struct AlignedBytes<const N: usize>([u8; N]);
#[cfg(feature = "ebpf")]
static SYSCALL_TRACER_BYTECODE_ALIGNED: AlignedBytes<
{ include_bytes!(concat!(env!("OUT_DIR"), "/ebpf/syscall_tracer.o")).len() },
> = AlignedBytes(*include_bytes!(concat!(
env!("OUT_DIR"),
"/ebpf/syscall_tracer.o"
)));
#[cfg(feature = "ebpf")]
const SYSCALL_TRACER_BYTECODE: &[u8] = &SYSCALL_TRACER_BYTECODE_ALIGNED.0;
#[cfg(feature = "ebpf")]
pub struct SyscallTracker {
#[cfg(feature = "ebpf")]
bpf: Option<Ebpf>,
#[cfg(feature = "ebpf")]
syscall_counts: Option<BpfHashMap<aya::maps::MapData, u32, u64>>,
#[cfg(feature = "ebpf")]
_pid_syscall_map: Option<BpfHashMap<aya::maps::MapData, u32, u32>>,
monitored_pids: Vec<u32>,
_last_metrics: HashMap<u32, u64>,
#[cfg(feature = "ebpf")]
_attached_programs: bool,
#[cfg(feature = "ebpf")]
init_error: Option<String>,
}
#[cfg(feature = "ebpf")]
impl std::fmt::Debug for SyscallTracker {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SyscallTracker")
.field("monitored_pids", &self.monitored_pids)
.field("bpf_loaded", &self.bpf.is_some())
.finish()
}
}
impl SyscallTracker {
#[cfg(feature = "ebpf")]
pub fn set_debug_mode(debug: bool) {
unsafe {
crate::ebpf::debug::set_debug_mode(debug);
if debug {
println!("eBPF debug mode enabled - verbose output will be shown");
}
}
}
pub fn new(pids: Vec<u32>) -> Result<Self> {
#[cfg(feature = "ebpf")]
{
log::info!("Initializing eBPF syscall tracker with feature enabled");
crate::ebpf::debug::debug_println("eBPF feature is enabled during compilation");
crate::ebpf::debug::debug_println(&format!("Process ID: {}", std::process::id()));
crate::ebpf::debug::debug_println(&format!(
"Executable: {:?}",
std::env::current_exe().unwrap_or_default()
));
let readiness_check = std::process::Command::new("sh")
.arg("-c")
.arg("echo 'Checking eBPF prerequisites:'; \
echo -n 'Kernel BPF enabled: '; grep -q CONFIG_BPF=y /boot/config-$(uname -r) && echo 'YES' || echo 'NO'; \
echo -n 'Unprivileged BPF disabled: '; cat /proc/sys/kernel/unprivileged_bpf_disabled 2>/dev/null || echo 'N/A'; \
echo -n 'Root user: '; [ $(id -u) -eq 0 ] && echo 'YES' || echo 'NO'; \
echo -n 'CAP_BPF capability: '; getcap $(readlink -f /proc/$$/exe) 2>/dev/null | grep -q cap_bpf && echo 'YES' || echo 'NO'; \
echo -n 'tracefs accessible: '; [ -r /sys/kernel/debug/tracing/events/syscalls ] && echo 'YES' || echo 'NO';")
.output();
if let Ok(output) = readiness_check {
let report = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(&format!("System eBPF readiness:\n{}", report));
log::info!("System eBPF readiness:\n{}", report);
}
match Self::init_ebpf() {
Ok((bpf, pid_syscall_map, syscall_counts)) => {
log::info!("✓ eBPF syscall tracker successfully initialized");
crate::ebpf::debug::debug_println(
"eBPF syscall tracker successfully initialized",
);
Ok(Self {
bpf: Some(bpf),
syscall_counts: Some(syscall_counts),
_pid_syscall_map: Some(pid_syscall_map),
monitored_pids: pids.clone(),
_last_metrics: HashMap::new(),
_attached_programs: true,
init_error: None,
})
}
Err(e) => {
let init_error_msg = e.to_string();
log::warn!("Failed to initialize eBPF syscall tracking: {}", e);
crate::ebpf::debug::debug_println(&format!(
"Failed to initialize eBPF syscall tracking: {}",
e
));
{
log::warn!("Current process ID: {}", std::process::id());
}
if unsafe { libc::geteuid() } == 0 {
crate::ebpf::debug::debug_println(
"Running as root but eBPF initialization still failed",
);
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("test -e /sys/fs/bpf && echo 'BPF filesystem mounted' || echo 'BPF filesystem not mounted'")
.output()
{
let bpf_fs = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(bpf_fs.trim());
log::warn!("{}", bpf_fs.trim());
}
} else {
log::warn!("Not running as root - CAP_BPF capability required");
crate::ebpf::debug::debug_println(
"Not running as root - CAP_BPF capability required",
);
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg(format!(
"getcap {}",
std::env::current_exe().unwrap_or_default().display()
))
.output()
{
let caps = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(&format!(
"Current capabilities: {}",
caps.trim()
));
log::warn!("Current capabilities: {}", caps.trim());
}
}
Ok(Self {
bpf: None,
syscall_counts: None,
_pid_syscall_map: None,
monitored_pids: pids.clone(),
_last_metrics: HashMap::new(),
_attached_programs: false,
init_error: Some(init_error_msg),
})
}
}
}
#[cfg(not(feature = "ebpf"))]
{
Ok(Self {
monitored_pids: pids,
last_metrics: HashMap::new(),
})
}
}
#[cfg(feature = "ebpf")]
#[allow(clippy::type_complexity)]
fn init_ebpf() -> Result<(
Ebpf,
BpfHashMap<aya::maps::MapData, u32, u32>,
BpfHashMap<aya::maps::MapData, u32, u64>,
)> {
log::info!("Loading real eBPF program for syscall tracking...");
crate::ebpf::debug::debug_println("Starting eBPF initialization");
let is_root = unsafe { libc::geteuid() == 0 };
log::info!("Running as root: {}", is_root);
crate::ebpf::debug::debug_println(&format!("Running as root: {}", is_root));
match std::process::Command::new("sh")
.arg("-c")
.arg(format!(
"getcap {}",
std::env::current_exe().unwrap_or_default().display()
))
.output()
{
Ok(output) => {
let caps = String::from_utf8_lossy(&output.stdout);
log::info!("Current capabilities: {}", caps.trim());
crate::ebpf::debug::debug_println(&format!(
"Current capabilities: {}",
caps.trim()
));
}
Err(e) => {
log::warn!("Failed to check capabilities: {}", e);
crate::ebpf::debug::debug_println(&format!("Failed to check capabilities: {}", e));
}
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("grep CONFIG_BPF /boot/config-$(uname -r)")
.output()
{
let config = String::from_utf8_lossy(&output.stdout);
log::info!("Kernel BPF configuration:\n{}", config);
crate::ebpf::debug::debug_println(&format!("Kernel BPF configuration:\n{}", config));
}
log::debug!(
"eBPF bytecode size: {} bytes",
SYSCALL_TRACER_BYTECODE.len()
);
let preview_size = std::cmp::min(SYSCALL_TRACER_BYTECODE.len(), 32);
let hex_bytes: Vec<String> = SYSCALL_TRACER_BYTECODE[..preview_size]
.iter()
.map(|b| format!("{:02x}", b))
.collect();
log::debug!("eBPF bytecode preview: {}", hex_bytes.join(" "));
use std::io::Write;
unsafe {
if crate::ebpf::debug::is_debug_mode() {
if let Ok(mut file) = std::fs::File::create("/tmp/syscall_tracer_dump.o") {
if let Err(e) = file.write_all(SYSCALL_TRACER_BYTECODE) {
crate::ebpf::debug::debug_println(&format!(
"Failed to write bytecode to temp file: {}",
e
));
} else {
crate::ebpf::debug::debug_println(
"Wrote bytecode to /tmp/syscall_tracer_dump.o for inspection",
);
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("readelf -h /tmp/syscall_tracer_dump.o 2>&1 || echo 'Failed to read ELF header'")
.output()
{
let header_info = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(&format!("ELF header info:\n{}", header_info));
log::debug!("ELF header info:\n{}", header_info);
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("readelf -S /tmp/syscall_tracer_dump.o 2>&1 || echo 'Failed to read ELF sections'")
.output()
{
let section_info = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(&format!("ELF section info:\n{}", section_info));
log::debug!("ELF section info:\n{}", section_info);
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("which llvm-objdump && llvm-objdump -d /tmp/syscall_tracer_dump.o 2>&1 || echo 'llvm-objdump not available'")
.output()
{
let objdump_info = String::from_utf8_lossy(&output.stdout);
if !objdump_info.contains("not available") {
crate::ebpf::debug::debug_println("llvm-objdump info available at /tmp/syscall_tracer_dump.objdump");
log::debug!("llvm-objdump info written to /tmp/syscall_tracer_dump.objdump");
if let Ok(mut file) = std::fs::File::create("/tmp/syscall_tracer_dump.objdump") {
let _ = file.write_all(objdump_info.as_bytes());
}
} else {
crate::ebpf::debug::debug_println(objdump_info.trim());
}
}
}
}
}
}
log::debug!(
"Bytecode source: {}",
concat!(env!("OUT_DIR"), "/ebpf/syscall_tracer.o")
);
let bytecode_path = std::path::Path::new(env!("OUT_DIR")).join("ebpf/syscall_tracer.o");
log::debug!(
"Bytecode file exists: {} (size: {})",
bytecode_path.exists(),
if bytecode_path.exists() {
std::fs::metadata(&bytecode_path)
.map(|m| m.len().to_string())
.unwrap_or_else(|_| "unknown".to_string())
} else {
"N/A".to_string()
}
);
crate::ebpf::debug::debug_println(&format!(
"Bytecode file location: {}",
bytecode_path.display()
));
log::debug!("Creating BPF loader...");
crate::ebpf::debug::debug_println("Creating BPF loader");
if bytecode_path.exists() {
match std::fs::read(&bytecode_path) {
Ok(bytes) => {
crate::ebpf::debug::debug_println(&format!(
"Successfully read {} bytes from disk bytecode file",
bytes.len()
));
let disk_preview_size = std::cmp::min(bytes.len(), 32);
let disk_hex_bytes: Vec<String> = bytes[..disk_preview_size]
.iter()
.map(|b| format!("{:02x}", b))
.collect();
crate::ebpf::debug::debug_println(&format!(
"Disk bytecode preview: {}",
disk_hex_bytes.join(" ")
));
if bytes.len() == SYSCALL_TRACER_BYTECODE.len() {
let identical = bytes
.iter()
.zip(SYSCALL_TRACER_BYTECODE.iter())
.all(|(a, b)| a == b);
crate::ebpf::debug::debug_println(&format!(
"Embedded and disk bytecodes are identical: {}",
identical
));
} else {
crate::ebpf::debug::debug_println(&format!(
"Bytecode size mismatch: disk={}, embedded={}",
bytes.len(),
SYSCALL_TRACER_BYTECODE.len()
));
}
}
Err(e) => {
crate::ebpf::debug::debug_println(&format!(
"Failed to read bytecode file: {}",
e
));
}
}
}
log::debug!("Creating BPF loader...");
crate::ebpf::debug::debug_println("Creating BPF loader");
let mut loader = EbpfLoader::new();
crate::ebpf::debug::debug_println("Using Aya for eBPF loading");
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("which llvm-readelf || which readelf || echo 'No ELF tools found'")
.output()
{
let elf_tools = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(&format!(
"Available ELF tools: {}",
elf_tools.trim()
));
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("cat /proc/sys/kernel/bpf_stats_enabled 2>/dev/null || echo 'BPF stats not available'")
.output()
{
let bpf_stats = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(&format!("BPF stats enabled: {}", bpf_stats.trim()));
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("uname -r")
.output()
{
let kernel_version = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(&format!(
"Kernel version: {}",
kernel_version.trim()
));
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("cat /proc/sys/kernel/unprivileged_bpf_disabled 2>/dev/null || echo 'N/A'")
.output()
{
let unprivileged_bpf = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(&format!(
"Unprivileged BPF disabled: {}",
unprivileged_bpf.trim()
));
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("ls -la /sys/fs/bpf/ 2>/dev/null || echo 'BPF filesystem not accessible'")
.output()
{
let bpf_fs = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(&format!("BPF filesystem content:\n{}", bpf_fs));
}
log::debug!("Attempting to load eBPF bytecode...");
crate::ebpf::debug::debug_println(&format!(
"Attempting to load eBPF bytecode (size: {} bytes)",
SYSCALL_TRACER_BYTECODE.len()
));
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("bpftool prog list 2>/dev/null || echo 'bpftool not available'")
.output()
{
let bpf_progs = String::from_utf8_lossy(&output.stdout);
if !bpf_progs.contains("not available") {
crate::ebpf::debug::debug_println(&format!(
"Existing BPF programs:\n{}",
bpf_progs
));
} else {
crate::ebpf::debug::debug_println("bpftool not available to list programs");
}
}
crate::ebpf::debug::debug_println(
"Attempting more verbose eBPF loading with error details",
);
if cfg!(target_endian = "little") {
crate::ebpf::debug::debug_println("Target machine is little-endian");
} else {
crate::ebpf::debug::debug_println("Target machine is big-endian");
}
let load_result = if bytecode_path.exists() {
crate::ebpf::debug::debug_println(&format!(
"Trying to load from file: {}",
bytecode_path.display()
));
let load_attempt = Ebpf::load_file(&bytecode_path);
if let Err(ref e) = load_attempt {
crate::ebpf::debug::debug_println(&format!("File load error: {}", e));
let err_str = format!("{:?}", e);
if err_str.contains("verifier") {
crate::ebpf::debug::debug_println(&format!(
"Error contains verifier information: {}",
err_str
));
}
}
load_attempt
} else {
crate::ebpf::debug::debug_println("File not found, loading from memory");
loader.load(SYSCALL_TRACER_BYTECODE)
};
let mut bpf = match load_result {
Ok(bpf) => {
log::info!("✓ eBPF bytecode loaded successfully");
crate::ebpf::debug::debug_println("eBPF bytecode loaded successfully");
bpf
}
Err(e) => {
log::error!("❌ Failed to load eBPF program: {}", e);
log::error!("BPF load error details: {:?}", e);
crate::ebpf::debug::debug_println(&format!(
"Failed to load eBPF program: {} ({:?})",
e, e
));
let error_string = format!("{:?}", e);
if error_string.contains("verifier") {
crate::ebpf::debug::debug_println("BPF verifier error detected in message");
log::error!("BPF verifier error detected in message");
}
let error_string = format!("{:?}", e);
if error_string.contains("permission denied") {
log::error!("💥 Permission denied error detected. Check capabilities and/or root access");
crate::ebpf::debug::debug_println(
"Permission denied error detected. Check capabilities and/or root access",
);
} else if error_string.contains("not found") {
log::error!("💥 Resource not found error detected. Check if tracefs/debugfs is properly mounted");
crate::ebpf::debug::debug_println("Resource not found error detected. Check if tracefs/debugfs is properly mounted");
} else if error_string.contains("invalid argument") {
log::error!("💥 Invalid argument error detected. This might be due to kernel version incompatibility");
crate::ebpf::debug::debug_println("Invalid argument error detected. This might be due to kernel version incompatibility");
} else if error_string.contains("Invalid ELF header") {
log::error!("💥 Invalid ELF header detected. This suggests bytecode corruption or format incompatibility");
crate::ebpf::debug::debug_println("Invalid ELF header detected. This suggests bytecode corruption or format incompatibility. Try rebuilding with latest clang/LLVM.");
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("readelf -h /tmp/syscall_tracer_dump.o | head -20")
.output()
{
let header_details = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(&format!(
"ELF header details:\n{}",
header_details
));
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("hexdump -C -n 64 /tmp/syscall_tracer_dump.o")
.output()
{
let hex_dump = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(&format!(
"ELF binary hex dump (first 64 bytes):\n{}",
hex_dump
));
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("file /tmp/syscall_tracer_dump.o")
.output()
{
let file_type = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(&format!(
"File type information: {}",
file_type
));
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("clang --version | head -1")
.output()
{
let clang_version = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(&format!(
"Clang version: {}",
clang_version
));
}
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("grep CONFIG_BPF /boot/config-$(uname -r)")
.output()
{
if let Ok(config) = String::from_utf8(output.stdout) {
log::error!("Kernel BPF configuration:\n{}", config);
crate::ebpf::debug::debug_println(&format!(
"Kernel BPF configuration:\n{}",
config
));
}
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("cat /proc/sys/kernel/unprivileged_bpf_disabled 2>/dev/null || echo 'Not available'")
.output()
{
let unprivileged_bpf = String::from_utf8_lossy(&output.stdout);
log::error!("Unprivileged BPF disabled: {}", unprivileged_bpf.trim());
crate::ebpf::debug::debug_println(&format!("Unprivileged BPF disabled: {}", unprivileged_bpf.trim()));
}
if let Ok(output) = std::process::Command::new("uname").arg("-r").output() {
let kernel_version = String::from_utf8_lossy(&output.stdout);
log::error!("Kernel version: {}", kernel_version.trim());
crate::ebpf::debug::debug_println(&format!(
"Kernel version: {}",
kernel_version.trim()
));
}
let mut chain = format!("{}", e);
let mut src: Option<&dyn std::error::Error> = std::error::Error::source(&e);
while let Some(s) = src {
chain.push_str(&format!(" -> {}", s));
src = s.source();
}
return Err(crate::error::DenetError::EbpfInitError(
format!("Failed to load eBPF program: {}. Ensure you have CAP_BPF capability or run as root.", chain)
));
}
};
log::debug!("Verifying BPF maps...");
crate::ebpf::debug::debug_println("Available maps in BPF program:");
for (name, _) in bpf.maps() {
crate::ebpf::debug::debug_println(&format!("- Map '{}' found", name));
}
crate::ebpf::debug::debug_println("Getting syscall_counts map");
let syscall_counts_map = bpf.take_map("syscall_counts");
crate::ebpf::debug::debug_println(&format!(
"syscall_counts map exists: {}",
syscall_counts_map.is_some()
));
if syscall_counts_map.is_none() {
crate::ebpf::debug::debug_println(
"WARNING - syscall_counts map not found in BPF program!",
);
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("bpftool map list 2>/dev/null || echo 'bpftool not available'")
.output()
{
let map_list = String::from_utf8_lossy(&output.stdout);
if !map_list.contains("not available") {
crate::ebpf::debug::debug_println(&format!("System BPF maps:\n{}", map_list));
}
}
}
let syscall_counts: BpfHashMap<_, u32, u64> =
BpfHashMap::try_from(syscall_counts_map.ok_or_else(|| {
crate::error::DenetError::EbpfInitError(
"syscall_counts map not found in eBPF program".to_string(),
)
})?)
.map_err(|e| {
crate::ebpf::debug::debug_println(&format!(
"Failed to create syscall_counts map: {}",
e
));
crate::error::DenetError::EbpfInitError(format!(
"Failed to create syscall_counts map: {}",
e
))
})?;
crate::ebpf::debug::debug_println("syscall_counts map created successfully");
crate::ebpf::debug::debug_println("Getting pid_syscall_map map");
let pid_syscall_map_obj = bpf.take_map("pid_syscall_map");
crate::ebpf::debug::debug_println(&format!(
"pid_syscall_map exists: {}",
pid_syscall_map_obj.is_some()
));
let pid_syscall_map: BpfHashMap<_, u32, u32> =
BpfHashMap::try_from(pid_syscall_map_obj.ok_or_else(|| {
crate::error::DenetError::EbpfInitError(
"pid_syscall_map map not found in eBPF program".to_string(),
)
})?)
.map_err(|e| {
crate::ebpf::debug::debug_println(&format!(
"Failed to create pid_syscall_map: {}",
e
));
crate::error::DenetError::EbpfInitError(format!(
"Failed to create pid_syscall_map: {}",
e
))
})?;
crate::ebpf::debug::debug_println("pid_syscall_map created successfully");
let tracepoints = [
"read",
"write",
"close",
"openat",
"mmap",
"munmap",
"brk",
"clone",
"fork",
"vfork",
"execve",
"exit",
"wait4",
"kill",
"exit_group",
"socket",
"connect",
"accept",
"sendto",
"recvfrom",
"sendmsg",
"recvmsg",
"bind",
"accept4",
"futex",
"pipe",
"pipe2",
"rt_sigaction",
"rt_sigprocmask",
"tgkill",
"nanosleep",
"clock_gettime",
"clock_nanosleep",
];
let mut attached_count = 0;
for syscall_name in tracepoints.iter() {
let program_name = format!("trace_{}_enter", syscall_name);
let tracepoint_name = format!("sys_enter_{}", syscall_name);
if let Some(prog) = bpf.program_mut(&program_name) {
match prog {
aya::programs::Program::TracePoint(tracepoint) => {
if let Err(e) = tracepoint.load() {
let msg = format!("Failed to load {} program: {:?}", syscall_name, e);
log::warn!("{}", msg);
crate::ebpf::debug::debug_println(&msg);
continue;
}
match tracepoint.attach("syscalls", &tracepoint_name) {
Ok(_) => {
attached_count += 1;
log::info!("✓ Attached tracepoint for {}", syscall_name);
crate::ebpf::debug::debug_println(&format!(
"Attached tracepoint for {}",
syscall_name
));
}
Err(e) => {
let msg = format!(
"Failed to attach {} tracepoint: {:?}",
syscall_name, e
);
log::warn!("{}", msg);
crate::ebpf::debug::debug_println(&msg);
}
}
}
_ => {
let msg = format!("Program {} is not a tracepoint", program_name);
log::warn!("{}", msg);
crate::ebpf::debug::debug_println(&msg);
}
}
} else {
let msg = format!("Program {} not found", program_name);
log::warn!("{}", msg);
crate::ebpf::debug::debug_println(&msg);
}
}
if attached_count == 0 {
crate::ebpf::debug::debug_println("CRITICAL ERROR - Failed to attach any tracepoints!");
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("ls -la /sys/kernel/debug/tracing/events/syscalls/sys_enter_read 2>/dev/null || echo 'Cannot access tracefs entry'")
.output()
{
let tracefs_perms = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(&format!("Tracefs sys_enter_read permissions: {}", tracefs_perms.trim()));
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("mount | grep debugfs")
.output()
{
let mount_info = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(&format!(
"Debugfs mount details: {}",
mount_info.trim()
));
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("dmesg | grep -i trace | tail -5")
.output()
{
let dmesg_info = String::from_utf8_lossy(&output.stdout);
if !dmesg_info.trim().is_empty() {
crate::ebpf::debug::debug_println(&format!(
"Recent tracepoint-related kernel messages:\n{}",
dmesg_info
));
}
}
let paranoid = std::fs::read_to_string("/proc/sys/kernel/perf_event_paranoid")
.ok()
.and_then(|s| s.trim().parse::<i32>().ok());
let hint = match paranoid {
Some(v) if v >= 3 => format!(
" perf_event_paranoid={} (Ubuntu/hardened kernel level) blocks perf_event_open \
even with CAP_PERFMON. Fix: sudo sysctl -w kernel.perf_event_paranoid=1",
v
),
Some(v) => format!(" perf_event_paranoid={}", v),
None => String::new(),
};
return Err(crate::error::DenetError::EbpfInitError(format!(
"Failed to attach any tracepoints.{}",
hint
)));
}
log::info!(
"✓ Real eBPF program loaded and attached to {} syscall tracepoints!",
attached_count
);
log::info!("Verifying maps...");
let map_count = bpf.maps().count();
log::info!("Found {} maps in BPF program", map_count);
for (name, _) in bpf.maps() {
log::info!(" - Map '{}' is available", name);
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("sysctl -a 2>/dev/null | grep bpf")
.output()
{
if let Ok(out_str) = std::str::from_utf8(&output.stdout) {
log::info!("BPF kernel settings: {}", out_str);
}
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("ls -la /sys/kernel/debug/tracing/events/syscalls/ 2>&1 || echo 'Cannot access tracefs'")
.output()
{
if let Ok(out_str) = std::str::from_utf8(&output.stdout) {
log::info!("Tracefs access check: {}", out_str);
crate::ebpf::debug::debug_println(&format!("Tracefs access check: {}", out_str));
let test_output = std::process::Command::new("sh")
.arg("-c")
.arg("touch /sys/kernel/debug/tracing/events/syscalls/test_file 2>&1 || echo 'Cannot write to tracefs'")
.output();
if let Ok(test_result) = test_output {
let test_str = String::from_utf8_lossy(&test_result.stdout);
let test_err = String::from_utf8_lossy(&test_result.stderr);
log::info!("Tracefs write test: {}{}", test_str, test_err);
crate::ebpf::debug::debug_println(&format!("Tracefs write test: {}{}", test_str, test_err));
let _ = std::process::Command::new("sh")
.arg("-c")
.arg("rm -f /sys/kernel/debug/tracing/events/syscalls/test_file 2>/dev/null")
.output();
}
}
}
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("mount | grep debugfs")
.output()
{
let mount_info = String::from_utf8_lossy(&output.stdout);
log::info!("Debugfs mount info: {}", mount_info);
crate::ebpf::debug::debug_println(&format!("Debugfs mount info: {}", mount_info));
}
log::info!("✓ Real eBPF program loaded and attached to syscall tracepoints!");
Ok((bpf, pid_syscall_map, syscall_counts))
}
pub fn update_pids(&mut self, pids: Vec<u32>) -> Result<()> {
self.monitored_pids = pids;
Ok(())
}
pub fn get_metrics(&self) -> EbpfMetrics {
#[cfg(feature = "ebpf")]
{
if self.bpf.is_none() {
let euid = unsafe { libc::geteuid() };
let cap_output = std::process::Command::new("sh")
.arg("-c")
.arg("getcap /proc/self/exe 2>&1 || echo 'getcap failed'")
.output()
.ok();
let cap_str = cap_output
.and_then(|o| String::from_utf8(o.stdout).ok())
.unwrap_or_else(|| "unknown".to_string());
let kernel_bpf = std::process::Command::new("sh")
.arg("-c")
.arg("cat /proc/sys/kernel/unprivileged_bpf_disabled 2>/dev/null || echo 'unknown'")
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.unwrap_or_else(|| "unknown".to_string())
.trim()
.to_string();
let tracefs_access = std::process::Command::new("sh")
.arg("-c")
.arg("test -r /sys/kernel/debug/tracing/events/syscalls && echo 'yes' || echo 'no'")
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.unwrap_or_else(|| "unknown".to_string())
.trim()
.to_string();
let cause = self
.init_error
.as_deref()
.unwrap_or("no init error captured");
return EbpfMetrics::error(&format!(
"eBPF not initialized: {}. Running as root: {}, Capabilities: {}, unprivileged_bpf_disabled: {}, tracefs access: {}. Please run with root privileges or set CAP_BPF capability.",
cause,
euid == 0,
cap_str.trim(),
kernel_bpf,
tracefs_access
));
}
match self.collect_syscall_metrics() {
Ok(metrics) if metrics.total > 0 => EbpfMetrics::with_syscalls(metrics),
Ok(_) => EbpfMetrics::default(),
Err(e) => EbpfMetrics::error(&format!("Failed to collect syscall metrics: {}", e)),
}
}
#[cfg(not(feature = "ebpf"))]
{
EbpfMetrics::error("eBPF feature not enabled at compile time")
}
}
#[cfg(feature = "ebpf")]
fn collect_syscall_metrics(&self) -> Result<SyscallMetrics> {
if let Some(ref syscall_map) = self.syscall_counts {
let mut total_syscalls = 0u64;
let mut pid_syscall_counts = HashMap::new();
let mut syscall_counts = HashMap::new();
log::debug!("Attempting to read syscall counts from eBPF map");
log::debug!(
"Monitoring {} PIDs: {:?}",
self.monitored_pids.len(),
self.monitored_pids
);
for &pid in &self.monitored_pids {
match syscall_map.get(&pid, 0) {
Ok(count) => {
log::debug!("PID {}: {} syscalls", pid, count);
if count > 0 {
pid_syscall_counts.insert(pid, count);
total_syscalls += count;
}
}
Err(e) => {
log::debug!("Failed to get syscall count for PID {}: {:?}", pid, e);
}
}
if let Some(ref pid_syscall_map) = self._pid_syscall_map {
for &syscall_nr in &[0, 1, 3, 9, 41, 42, 44, 45, 257] {
let key = (pid << 16) | (syscall_nr & 0xFFFF) as u32;
match pid_syscall_map.get(&key, 0) {
Ok(count) => {
if count > 0 {
*syscall_counts.entry(syscall_nr).or_insert(0) += count as u64;
}
}
Err(e) => {
log::debug!(
"Failed to get count for PID {} syscall {}: {:?}",
pid,
syscall_nr,
e
);
}
}
}
}
}
if total_syscalls > 0 {
log::debug!(
"Collected {} syscalls from eBPF for {} PIDs",
total_syscalls,
self.monitored_pids.len()
);
let mut by_category = HashMap::new();
for (&syscall_nr, &count) in &syscall_counts {
let category = categorize_syscall(syscall_nr);
*by_category.entry(category).or_insert(0) += count;
}
for category in [
"file_io", "memory", "process", "network", "time", "ipc", "security", "signal",
"system", "other",
] {
by_category.entry(category.to_string()).or_insert(0);
}
let mut syscall_vec: Vec<_> = syscall_counts.into_iter().collect();
syscall_vec.sort_by_key(|(_, count)| std::cmp::Reverse(*count));
let top_syscalls = syscall_vec
.into_iter()
.take(10)
.map(|(nr, count)| SyscallCount {
name: syscall_name(nr),
count,
})
.collect();
return Ok(SyscallMetrics {
total: total_syscalls,
by_category,
top_syscalls,
analysis: None, });
}
}
log::debug!("No tracked syscalls found for monitored PIDs");
Ok(SyscallMetrics {
total: 0,
by_category: HashMap::new(),
top_syscalls: vec![],
analysis: None,
})
}
}
#[cfg(feature = "ebpf")]
impl Drop for SyscallTracker {
fn drop(&mut self) {
if let Some(_bpf) = self.bpf.take() {
log::debug!("Cleaning up eBPF syscall tracker");
crate::ebpf::debug::debug_println("Cleaning up eBPF syscall tracker resources");
if let Ok(output) = std::process::Command::new("sh")
.arg("-c")
.arg("ls -la /proc/sys/net/core/bpf_jit_enable 2>/dev/null || echo 'BPF JIT status not available'")
.output()
{
let bpf_status = String::from_utf8_lossy(&output.stdout);
crate::ebpf::debug::debug_println(&format!("BPF JIT status check on cleanup: {}", bpf_status.trim()));
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_syscall_tracker_creation() {
let pids = vec![1234, 5678];
let tracker = SyscallTracker::new(pids.clone());
assert!(tracker.is_ok());
let tracker = tracker.unwrap();
assert_eq!(tracker.monitored_pids, pids);
}
#[test]
fn test_syscall_metrics_structure() {
let pids = vec![std::process::id()];
let tracker = SyscallTracker::new(pids).unwrap();
let metrics = tracker.get_metrics();
assert!(!(metrics.syscalls.is_some() && metrics.error.is_some()));
}
#[test]
fn test_pid_update() {
let initial_pids = vec![1234];
let mut tracker = SyscallTracker::new(initial_pids).unwrap();
let new_pids = vec![5678, 9012];
assert!(tracker.update_pids(new_pids.clone()).is_ok());
assert_eq!(tracker.monitored_pids, new_pids);
}
}