Skip to content

Commit e72c96b

Browse files
author
vsilent
committed
ebpf files
1 parent f750dce commit e72c96b

4 files changed

Lines changed: 152 additions & 29 deletions

File tree

src/collectors/ebpf/enrichment.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ pub fn normalize_timestamp(ts: chrono::DateTime<chrono::Utc>) -> chrono::DateTim
133133
#[cfg(test)]
134134
mod tests {
135135
use super::*;
136-
136+
use chrono::Utc;
137137
#[test]
138138
fn test_enricher_creation() {
139139
let enricher = EventEnricher::new();

src/collectors/ebpf/loader.rs

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,15 @@ impl EbpfLoader {
9797
if _bytes.is_empty() {
9898
return Err(LoadError::LoadFailed("Empty program bytes".to_string()));
9999
}
100-
101-
// TODO: Implement actual loading when eBPF programs are ready
102-
// For now, this is a stub that will be implemented in TASK-004
100+
101+
let bpf = aya::Bpf::load(_bytes)
102+
.map_err(|e| LoadError::LoadFailed(e.to_string()))?;
103+
self.bpf = Some(bpf);
104+
105+
log::info!("eBPF program loaded ({} bytes)", _bytes.len());
103106
Ok(())
104107
}
105-
108+
106109
#[cfg(not(all(target_os = "linux", feature = "ebpf")))]
107110
{
108111
Err(LoadError::NotLinux)
@@ -132,23 +135,80 @@ impl EbpfLoader {
132135
pub fn attach_program(&mut self, _program_name: &str) -> Result<(), LoadError> {
133136
#[cfg(all(target_os = "linux", feature = "ebpf"))]
134137
{
135-
// TODO: Implement actual attachment
136-
// For now, just mark as attached
138+
let (category, tp_name) = program_to_tracepoint(_program_name)
139+
.ok_or_else(|| LoadError::ProgramNotFound(
140+
format!("No tracepoint mapping for '{}'", _program_name)
141+
))?;
142+
143+
let bpf = self.bpf.as_mut()
144+
.ok_or_else(|| LoadError::LoadFailed(
145+
"No eBPF program loaded; call load_program_from_bytes first".to_string()
146+
))?;
147+
148+
let prog: &mut aya::programs::TracePoint = bpf
149+
.program_mut(_program_name)
150+
.ok_or_else(|| LoadError::ProgramNotFound(_program_name.to_string()))?
151+
.try_into()
152+
.map_err(|e: aya::programs::ProgramError| LoadError::AttachFailed(e.to_string()))?;
153+
154+
prog.load()
155+
.map_err(|e| LoadError::AttachFailed(format!("load '{}': {}", _program_name, e)))?;
156+
157+
prog.attach(category, tp_name)
158+
.map_err(|e| LoadError::AttachFailed(
159+
format!("attach '{}/{}': {}", category, tp_name, e)
160+
))?;
161+
137162
self.loaded_programs.insert(
138163
_program_name.to_string(),
139-
ProgramInfo {
140-
name: _program_name.to_string(),
141-
attached: true,
142-
},
164+
ProgramInfo { name: _program_name.to_string(), attached: true },
143165
);
166+
167+
log::info!("eBPF program '{}' attached to {}/{}", _program_name, category, tp_name);
144168
Ok(())
145169
}
146-
170+
171+
#[cfg(not(all(target_os = "linux", feature = "ebpf")))]
172+
{
173+
Err(LoadError::NotLinux)
174+
}
175+
}
176+
177+
/// Attach all known syscall tracepoint programs
178+
pub fn attach_all_programs(&mut self) -> Result<(), LoadError> {
179+
#[cfg(all(target_os = "linux", feature = "ebpf"))]
180+
{
181+
for name in &["trace_execve", "trace_connect", "trace_openat", "trace_ptrace"] {
182+
if let Err(e) = self.attach_program(name) {
183+
log::warn!("Failed to attach '{}': {}", name, e);
184+
}
185+
}
186+
Ok(())
187+
}
188+
147189
#[cfg(not(all(target_os = "linux", feature = "ebpf")))]
148190
{
149191
Err(LoadError::NotLinux)
150192
}
151193
}
194+
195+
/// Extract the EVENTS ring buffer map from the loaded eBPF program.
196+
/// Must be called after load_program_from_bytes and before the Bpf object is dropped.
197+
#[cfg(all(target_os = "linux", feature = "ebpf"))]
198+
pub fn take_ring_buf(&mut self) -> Result<aya::maps::RingBuf<aya::maps::MapData>, LoadError> {
199+
let bpf = self.bpf.as_mut()
200+
.ok_or_else(|| LoadError::LoadFailed(
201+
"No eBPF program loaded".to_string()
202+
))?;
203+
204+
let map = bpf.take_map("EVENTS")
205+
.ok_or_else(|| LoadError::LoadFailed(
206+
"EVENTS ring buffer map not found in eBPF program".to_string()
207+
))?;
208+
209+
aya::maps::RingBuf::try_from(map)
210+
.map_err(|e| LoadError::LoadFailed(format!("Failed to create ring buffer: {}", e)))
211+
}
152212

153213
/// Detach a program
154214
pub fn detach_program(&mut self, program_name: &str) -> Result<(), LoadError> {
@@ -201,8 +261,24 @@ impl EbpfLoader {
201261
}
202262

203263
impl Default for EbpfLoader {
204-
fn default() -> Result<Self, LoadError> {
205-
Self::new()
264+
fn default() -> Self {
265+
Self {
266+
#[cfg(all(target_os = "linux", feature = "ebpf"))]
267+
bpf: None,
268+
loaded_programs: HashMap::new(),
269+
kernel_version: None,
270+
}
271+
}
272+
}
273+
274+
/// Map program name to its tracepoint (category, name) for aya attachment.
275+
fn program_to_tracepoint(name: &str) -> Option<(&'static str, &'static str)> {
276+
match name {
277+
"trace_execve" => Some(("syscalls", "sys_enter_execve")),
278+
"trace_connect" => Some(("syscalls", "sys_enter_connect")),
279+
"trace_openat" => Some(("syscalls", "sys_enter_openat")),
280+
"trace_ptrace" => Some(("syscalls", "sys_enter_ptrace")),
281+
_ => None,
206282
}
207283
}
208284

src/collectors/ebpf/ring_buffer.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ impl EventRingBuffer {
5959
self.capacity
6060
}
6161

62+
/// View events without consuming them
63+
pub fn events(&self) -> &[SyscallEvent] {
64+
&self.buffer
65+
}
66+
6267
/// Clear the buffer
6368
pub fn clear(&mut self) {
6469
self.buffer.clear();

src/collectors/ebpf/syscall_monitor.rs

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ use crate::collectors::ebpf::container::ContainerDetector;
1212
pub struct SyscallMonitor {
1313
#[cfg(all(target_os = "linux", feature = "ebpf"))]
1414
loader: Option<super::loader::EbpfLoader>,
15-
15+
16+
#[cfg(all(target_os = "linux", feature = "ebpf"))]
17+
ring_buf: Option<aya::maps::RingBuf<aya::maps::MapData>>,
18+
1619
running: bool,
1720
event_buffer: EventRingBuffer,
1821
enricher: EventEnricher,
@@ -34,6 +37,7 @@ impl SyscallMonitor {
3437

3538
Ok(Self {
3639
loader: Some(loader),
40+
ring_buf: None,
3741
running: false,
3842
event_buffer: EventRingBuffer::with_capacity(8192),
3943
enricher,
@@ -54,15 +58,36 @@ impl SyscallMonitor {
5458
if self.running {
5559
anyhow::bail!("Monitor is already running");
5660
}
57-
58-
// TODO: Actually start eBPF programs in TASK-004
59-
// For now, just mark as running
61+
62+
if let Some(loader) = &mut self.loader {
63+
let ebpf_path = "target/bpfel-unknown-none/release/stackdog";
64+
match loader.load_program_from_file(ebpf_path) {
65+
Ok(()) => {
66+
loader.attach_all_programs().unwrap_or_else(|e| {
67+
log::warn!("Some eBPF programs failed to attach: {}", e);
68+
});
69+
match loader.take_ring_buf() {
70+
Ok(rb) => { self.ring_buf = Some(rb); }
71+
Err(e) => { log::warn!("Failed to get eBPF ring buffer: {}", e); }
72+
}
73+
}
74+
Err(e) => {
75+
log::warn!(
76+
"eBPF program not found at '{}': {}. \
77+
Running without kernel event collection — \
78+
build the eBPF crate first with `cargo build --release` \
79+
in the ebpf/ directory.",
80+
ebpf_path, e
81+
);
82+
}
83+
}
84+
}
85+
6086
self.running = true;
61-
6287
log::info!("Syscall monitor started");
6388
Ok(())
6489
}
65-
90+
6691
#[cfg(not(all(target_os = "linux", feature = "ebpf")))]
6792
{
6893
anyhow::bail!("SyscallMonitor is only available on Linux");
@@ -73,7 +98,10 @@ impl SyscallMonitor {
7398
pub fn stop(&mut self) -> Result<()> {
7499
self.running = false;
75100
self.event_buffer.clear();
76-
101+
#[cfg(all(target_os = "linux", feature = "ebpf"))]
102+
{
103+
self.ring_buf = None;
104+
}
77105
log::info!("Syscall monitor stopped");
78106
Ok(())
79107
}
@@ -90,25 +118,39 @@ impl SyscallMonitor {
90118
if !self.running {
91119
return Vec::new();
92120
}
93-
94-
// TODO: Actually poll eBPF ring buffer in TASK-004
95-
// For now, drain from internal buffer
121+
122+
// Drain the eBPF ring buffer into the staging buffer
123+
if let Some(rb) = &mut self.ring_buf {
124+
while let Some(item) = rb.next() {
125+
let bytes: &[u8] = &item;
126+
if bytes.len() >= std::mem::size_of::<super::types::EbpfSyscallEvent>() {
127+
// SAFETY: We verified the byte length matches the struct size,
128+
// and EbpfSyscallEvent is #[repr(C)] with no padding surprises.
129+
let raw: super::types::EbpfSyscallEvent = unsafe {
130+
std::ptr::read_unaligned(
131+
bytes.as_ptr() as *const super::types::EbpfSyscallEvent
132+
)
133+
};
134+
self.event_buffer.push(raw.to_syscall_event());
135+
}
136+
}
137+
}
138+
139+
// Drain the staging buffer and enrich with /proc info
96140
let mut events = self.event_buffer.drain();
97-
98-
// Enrich events
99141
for event in &mut events {
100142
let _ = self.enricher.enrich(event);
101143
}
102-
144+
103145
events
104146
}
105-
147+
106148
#[cfg(not(all(target_os = "linux", feature = "ebpf")))]
107149
{
108150
Vec::new()
109151
}
110152
}
111-
153+
112154
/// Get events without consuming them
113155
pub fn peek_events(&self) -> &[SyscallEvent] {
114156
self.event_buffer.events()

0 commit comments

Comments
 (0)