130 lines
3.3 KiB
Python
130 lines
3.3 KiB
Python
"""
|
|
©AngelaMos | 2026
|
|
loader.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import signal
|
|
import sys
|
|
from collections.abc import Callable
|
|
from typing import Any
|
|
|
|
from .config import (
|
|
EBPF_DIR,
|
|
MIN_KERNEL_MAJOR,
|
|
MIN_KERNEL_MINOR,
|
|
TracerType,
|
|
)
|
|
|
|
TRACER_FILES: dict[str, str] = {
|
|
"process": "process_tracer.c",
|
|
"file": "file_tracer.c",
|
|
"network": "network_tracer.c",
|
|
"privilege": "privilege_tracer.c",
|
|
"system": "system_tracer.c",
|
|
}
|
|
|
|
|
|
def check_privileges() -> None:
|
|
"""
|
|
Verify the process has root privileges
|
|
"""
|
|
if os.geteuid() != 0:
|
|
sys.stderr.write("Error: eBPF tracing requires root privileges.\n"
|
|
"Run with: sudo uv run ebpf-tracer\n")
|
|
sys.exit(1)
|
|
|
|
|
|
def check_kernel_version() -> None:
|
|
"""
|
|
Verify the kernel version supports ring buffers
|
|
"""
|
|
release = os.uname().release
|
|
parts = release.split(".")
|
|
major = int(parts[0])
|
|
minor = int(parts[1].split("-")[0])
|
|
|
|
if (major < MIN_KERNEL_MAJOR
|
|
or (major == MIN_KERNEL_MAJOR and minor < MIN_KERNEL_MINOR)):
|
|
sys.stderr.write(f"Error: Kernel {release} detected. "
|
|
f"Requires {MIN_KERNEL_MAJOR}."
|
|
f"{MIN_KERNEL_MINOR}+ for ring buffer.\n")
|
|
sys.exit(1)
|
|
|
|
|
|
def _resolve_tracers(tracer_type: TracerType, ) -> list[str]:
|
|
"""
|
|
Determine which tracer files to load
|
|
"""
|
|
if tracer_type == "all":
|
|
return list(TRACER_FILES.keys())
|
|
return [tracer_type]
|
|
|
|
|
|
class TracerLoader:
|
|
"""
|
|
Loads and manages eBPF programs via BCC
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
tracer_type: TracerType,
|
|
callback: Callable[..., None],
|
|
) -> None:
|
|
"""
|
|
Initialize the loader with tracer selection
|
|
"""
|
|
self._bpf_objects: list[Any] = []
|
|
self._tracer_type = tracer_type
|
|
self._callback = callback
|
|
self._running = False
|
|
|
|
def load(self) -> None:
|
|
"""
|
|
Compile and load all selected eBPF programs
|
|
"""
|
|
from bcc import BPF # type: ignore[import-untyped]
|
|
|
|
tracers = _resolve_tracers(self._tracer_type)
|
|
|
|
for name in tracers:
|
|
filename = TRACER_FILES[name]
|
|
src_path = EBPF_DIR / filename
|
|
c_text = src_path.read_text()
|
|
|
|
bpf = BPF(text=c_text)
|
|
bpf["events"].open_ring_buffer(self._callback)
|
|
self._bpf_objects.append(bpf)
|
|
|
|
def poll(self) -> None:
|
|
"""
|
|
Start polling all ring buffers for events
|
|
"""
|
|
self._running = True
|
|
original_sigint = signal.getsignal(signal.SIGINT)
|
|
original_sigterm = signal.getsignal(signal.SIGTERM)
|
|
|
|
def _handle_stop(signum: int, frame: Any) -> None:
|
|
self._running = False
|
|
|
|
signal.signal(signal.SIGINT, _handle_stop)
|
|
signal.signal(signal.SIGTERM, _handle_stop)
|
|
|
|
try:
|
|
while self._running:
|
|
for bpf in self._bpf_objects:
|
|
bpf.ring_buffer_poll(timeout=100)
|
|
finally:
|
|
signal.signal(signal.SIGINT, original_sigint)
|
|
signal.signal(signal.SIGTERM, original_sigterm)
|
|
self.cleanup()
|
|
|
|
def cleanup(self) -> None:
|
|
"""
|
|
Detach all eBPF programs and free resources
|
|
"""
|
|
for bpf in self._bpf_objects:
|
|
bpf.cleanup()
|
|
self._bpf_objects.clear()
|