add-bpf-capability (#1)

1) add-bpf-capability
2) Not so clean but for now it's okay to start with

Co-authored-by: Harshavardhan Musanalli <harshavmb@gmail.com>
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2025-10-22 08:16:40 +00:00
parent 1f01c38881
commit f69e1dbc66
25 changed files with 3273 additions and 26 deletions

387
ebpf_simple_manager.go Normal file
View File

@@ -0,0 +1,387 @@
package main
import (
"context"
"fmt"
"log"
"os"
"os/exec"
"strings"
"sync"
"time"
)
// EBPFEvent represents an event captured by eBPF programs
type EBPFEvent struct {
Timestamp int64 `json:"timestamp"`
EventType string `json:"event_type"`
ProcessID int `json:"process_id"`
ProcessName string `json:"process_name"`
UserID int `json:"user_id"`
Data map[string]interface{} `json:"data"`
}
// EBPFTrace represents a collection of eBPF events for a specific investigation
type EBPFTrace struct {
TraceID string `json:"trace_id"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
Capability string `json:"capability"`
Events []EBPFEvent `json:"events"`
Summary string `json:"summary"`
EventCount int `json:"event_count"`
ProcessList []string `json:"process_list"`
}
// EBPFRequest represents a request to run eBPF monitoring
type EBPFRequest struct {
Name string `json:"name"`
Type string `json:"type"` // "tracepoint", "kprobe", "kretprobe"
Target string `json:"target"` // tracepoint path or function name
Duration int `json:"duration"` // seconds
Filters map[string]string `json:"filters,omitempty"`
Description string `json:"description"`
}
// EBPFManagerInterface defines the interface for eBPF managers
type EBPFManagerInterface interface {
GetCapabilities() map[string]bool
GetSummary() map[string]interface{}
StartEBPFProgram(req EBPFRequest) (string, error)
GetProgramResults(programID string) (*EBPFTrace, error)
StopProgram(programID string) error
ListActivePrograms() []string
}
// SimpleEBPFManager implements basic eBPF functionality using bpftrace
type SimpleEBPFManager struct {
programs map[string]*RunningProgram
programsLock sync.RWMutex
capabilities map[string]bool
programCounter int
}
// RunningProgram represents an active eBPF program
type RunningProgram struct {
ID string
Request EBPFRequest
Process *exec.Cmd
Events []EBPFEvent
StartTime time.Time
Cancel context.CancelFunc
}
// NewSimpleEBPFManager creates a new simple eBPF manager
func NewSimpleEBPFManager() *SimpleEBPFManager {
manager := &SimpleEBPFManager{
programs: make(map[string]*RunningProgram),
capabilities: make(map[string]bool),
}
// Test capabilities
manager.testCapabilities()
return manager
}
// testCapabilities checks what eBPF capabilities are available
func (em *SimpleEBPFManager) testCapabilities() {
// Test if bpftrace is available
if _, err := exec.LookPath("bpftrace"); err == nil {
em.capabilities["bpftrace"] = true
}
// Test root privileges (required for eBPF)
em.capabilities["root_access"] = os.Geteuid() == 0
// Test kernel version (simplified check)
cmd := exec.Command("uname", "-r")
output, err := cmd.Output()
if err == nil {
version := strings.TrimSpace(string(output))
em.capabilities["kernel_ebpf"] = strings.Contains(version, "4.") || strings.Contains(version, "5.") || strings.Contains(version, "6.")
} else {
em.capabilities["kernel_ebpf"] = false
}
log.Printf("eBPF capabilities: %+v", em.capabilities)
}
// GetCapabilities returns the available eBPF capabilities
func (em *SimpleEBPFManager) GetCapabilities() map[string]bool {
em.programsLock.RLock()
defer em.programsLock.RUnlock()
caps := make(map[string]bool)
for k, v := range em.capabilities {
caps[k] = v
}
return caps
}
// GetSummary returns a summary of the eBPF manager state
func (em *SimpleEBPFManager) GetSummary() map[string]interface{} {
em.programsLock.RLock()
defer em.programsLock.RUnlock()
return map[string]interface{}{
"capabilities": em.capabilities,
"active_programs": len(em.programs),
"program_ids": em.ListActivePrograms(),
}
}
// StartEBPFProgram starts a new eBPF monitoring program
func (em *SimpleEBPFManager) StartEBPFProgram(req EBPFRequest) (string, error) {
if !em.capabilities["bpftrace"] {
return "", fmt.Errorf("bpftrace not available")
}
if !em.capabilities["root_access"] {
return "", fmt.Errorf("root access required for eBPF programs")
}
em.programsLock.Lock()
defer em.programsLock.Unlock()
// Generate program ID
em.programCounter++
programID := fmt.Sprintf("prog_%d", em.programCounter)
// Create bpftrace script
script, err := em.generateBpftraceScript(req)
if err != nil {
return "", fmt.Errorf("failed to generate script: %w", err)
}
// Start bpftrace process
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(req.Duration)*time.Second)
cmd := exec.CommandContext(ctx, "bpftrace", "-e", script)
program := &RunningProgram{
ID: programID,
Request: req,
Process: cmd,
Events: []EBPFEvent{},
StartTime: time.Now(),
Cancel: cancel,
}
// Start the program
if err := cmd.Start(); err != nil {
cancel()
return "", fmt.Errorf("failed to start bpftrace: %w", err)
}
em.programs[programID] = program
// Monitor the program in a goroutine
go em.monitorProgram(programID)
log.Printf("Started eBPF program %s for %s", programID, req.Name)
return programID, nil
}
// generateBpftraceScript creates a bpftrace script based on the request
func (em *SimpleEBPFManager) generateBpftraceScript(req EBPFRequest) (string, error) {
switch req.Type {
case "network":
return `
BEGIN {
printf("Starting network monitoring...\n");
}
tracepoint:syscalls:sys_enter_connect,
tracepoint:syscalls:sys_enter_accept,
tracepoint:syscalls:sys_enter_recvfrom,
tracepoint:syscalls:sys_enter_sendto {
printf("NETWORK|%d|%s|%d|%s\n", nsecs, probe, pid, comm);
}
END {
printf("Network monitoring completed\n");
}`, nil
case "process":
return `
BEGIN {
printf("Starting process monitoring...\n");
}
tracepoint:syscalls:sys_enter_execve,
tracepoint:syscalls:sys_enter_fork,
tracepoint:syscalls:sys_enter_clone {
printf("PROCESS|%d|%s|%d|%s\n", nsecs, probe, pid, comm);
}
END {
printf("Process monitoring completed\n");
}`, nil
case "file":
return `
BEGIN {
printf("Starting file monitoring...\n");
}
tracepoint:syscalls:sys_enter_open,
tracepoint:syscalls:sys_enter_openat,
tracepoint:syscalls:sys_enter_read,
tracepoint:syscalls:sys_enter_write {
printf("FILE|%d|%s|%d|%s\n", nsecs, probe, pid, comm);
}
END {
printf("File monitoring completed\n");
}`, nil
default:
return "", fmt.Errorf("unsupported eBPF program type: %s", req.Type)
}
}
// monitorProgram monitors a running eBPF program and collects events
func (em *SimpleEBPFManager) monitorProgram(programID string) {
em.programsLock.Lock()
program, exists := em.programs[programID]
if !exists {
em.programsLock.Unlock()
return
}
em.programsLock.Unlock()
// Wait for the program to complete
err := program.Process.Wait()
// Clean up
program.Cancel()
em.programsLock.Lock()
if err != nil {
log.Printf("eBPF program %s completed with error: %v", programID, err)
} else {
log.Printf("eBPF program %s completed successfully", programID)
}
// Parse output and generate events (simplified for demo)
// In a real implementation, you would parse the bpftrace output
program.Events = []EBPFEvent{
{
Timestamp: time.Now().Unix(),
EventType: program.Request.Type,
ProcessID: 0,
ProcessName: "example",
UserID: 0,
Data: map[string]interface{}{
"description": "Sample eBPF event",
"program_id": programID,
},
},
}
em.programsLock.Unlock()
log.Printf("Generated %d events for program %s", len(program.Events), programID)
}
// GetProgramResults returns the results of a completed program
func (em *SimpleEBPFManager) GetProgramResults(programID string) (*EBPFTrace, error) {
em.programsLock.RLock()
defer em.programsLock.RUnlock()
program, exists := em.programs[programID]
if !exists {
return nil, fmt.Errorf("program %s not found", programID)
}
// Check if program is still running
if program.Process.ProcessState == nil {
return nil, fmt.Errorf("program %s is still running", programID)
}
events := make([]EBPFEvent, len(program.Events))
copy(events, program.Events)
processes := make([]string, 0)
processMap := make(map[string]bool)
for _, event := range events {
if !processMap[event.ProcessName] {
processes = append(processes, event.ProcessName)
processMap[event.ProcessName] = true
}
}
trace := &EBPFTrace{
TraceID: programID,
StartTime: program.StartTime,
EndTime: time.Now(),
Capability: program.Request.Type,
Events: events,
EventCount: len(events),
ProcessList: processes,
Summary: fmt.Sprintf("Collected %d events for %s monitoring", len(events), program.Request.Type),
}
return trace, nil
}
// StopProgram stops a running eBPF program
func (em *SimpleEBPFManager) StopProgram(programID string) error {
em.programsLock.Lock()
defer em.programsLock.Unlock()
program, exists := em.programs[programID]
if !exists {
return fmt.Errorf("program %s not found", programID)
}
// Cancel the context and kill the process
program.Cancel()
if program.Process.Process != nil {
program.Process.Process.Kill()
}
delete(em.programs, programID)
log.Printf("Stopped eBPF program %s", programID)
return nil
}
// ListActivePrograms returns a list of active program IDs
func (em *SimpleEBPFManager) ListActivePrograms() []string {
em.programsLock.RLock()
defer em.programsLock.RUnlock()
programs := make([]string, 0, len(em.programs))
for id := range em.programs {
programs = append(programs, id)
}
return programs
}
// GetCommonEBPFRequests returns predefined eBPF programs for common use cases
func (em *SimpleEBPFManager) GetCommonEBPFRequests() []EBPFRequest {
return []EBPFRequest{
{
Name: "network_activity",
Type: "network",
Target: "syscalls:sys_enter_connect,sys_enter_accept,sys_enter_recvfrom,sys_enter_sendto",
Duration: 30,
Description: "Monitor network connections and data transfers",
},
{
Name: "process_activity",
Type: "process",
Target: "syscalls:sys_enter_execve,sys_enter_fork,sys_enter_clone",
Duration: 30,
Description: "Monitor process creation and execution",
},
{
Name: "file_access",
Type: "file",
Target: "syscalls:sys_enter_open,sys_enter_openat,sys_enter_read,sys_enter_write",
Duration: 30,
Description: "Monitor file system access and I/O operations",
},
}
}
// Helper functions - using system_info.go functions
// isRoot and checkKernelVersion are available from system_info.go