diff --git a/core/udp.go b/core/udp.go index e1ba62f..a1d9377 100644 --- a/core/udp.go +++ b/core/udp.go @@ -20,36 +20,35 @@ type udpKey struct { func RunUDPMonitor() { const ( bpfFile = "bin/udp_monitor.o" - progName = "trace_udp" mapName = "udp_attempts" - hookFunc = "sys_enter_sendto" + progName = "udp_monitor" ) - fmt.Printf("Starting monitor: %s → kprobe:%s\n", progName, hookFunc) + fmt.Println("Starting UDP Monitor...") - // Load compiled eBPF object spec, err := ebpf.LoadCollectionSpec(bpfFile) if err != nil { - log.Fatalf("Load spec failed: %v", err) + log.Fatalf("Failed to load collection spec: %v", err) } coll, err := ebpf.NewCollection(spec) if err != nil { - log.Fatalf("Load collection failed: %v", err) + log.Fatalf("Failed to load eBPF collection: %v", err) } defer coll.Close() prog := coll.Programs[progName] if prog == nil { - log.Fatalf("Program '%s' not found", progName) + log.Fatalf("Program '%s' not found in collection", progName) } defer prog.Close() - - lk, err := link.Tracepoint("syscalls", "sys_enter_sendto", prog, nil) + + // Attach to __x64_sys_sendmsg (or change to sendto if needed) + kp, err := link.Kprobe("__x64_sys_sendmsg", prog, nil) if err != nil { - log.Fatalf("Attach failed: %v", err) + log.Fatalf("Attach to __x64_sys_sendmsg failed: %v", err) } - defer lk.Close() + defer kp.Close() m := coll.Maps[mapName] if m == nil { @@ -57,7 +56,7 @@ func RunUDPMonitor() { } defer m.Close() - fmt.Println("eBPF attached. Watching UDP sends per PID and IP...") + fmt.Println("eBPF probe attached. Watching UDP sends per PID and IP...") sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt, unix.SIGTERM) @@ -68,7 +67,6 @@ func RunUDPMonitor() { for { select { case <-ticker.C: - iter := m.Iterate() var ( key udpKey value uint32 @@ -76,6 +74,7 @@ func RunUDPMonitor() { ) fmt.Println("[udp_attempts] PID@IP -> Count") + iter := m.Iterate() for iter.Next(&key, &value) { ipStr := FormatIPv4(key.DstIP) fmt.Printf(" %d@%s -> %d\n", key.PID, ipStr, value) @@ -84,11 +83,10 @@ func RunUDPMonitor() { fmt.Printf("[udp_attempts] Total: %d\n\n", total) if err := iter.Err(); err != nil { - log.Printf("Iter error: %v", err) + log.Printf("Map iteration error: %v", err) } - case <-sig: - fmt.Println("Monitor stopped") + fmt.Println("Monitor stopped.") return } } diff --git a/ebpf-programs/udp_monitor/.cargo/config.toml b/ebpf-programs/udp_monitor/.cargo/config.toml new file mode 100644 index 0000000..82cb39c --- /dev/null +++ b/ebpf-programs/udp_monitor/.cargo/config.toml @@ -0,0 +1,5 @@ +[build] +rustflags = [ + "-Zbuild-std=core,compiler_builtins", + "-Zbuild-std-features=compiler-builtins-mem" +] diff --git a/ebpf-programs/udp_monitor/Cargo.toml b/ebpf-programs/udp_monitor/Cargo.toml new file mode 100644 index 0000000..49a4076 --- /dev/null +++ b/ebpf-programs/udp_monitor/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "udp_monitor" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["staticlib"] +name = "udp_monitor" + +[dependencies] +aya-ebpf = { version = "0.1.1", default-features = false } + +[profile.release] +opt-level = "z" +lto = true +panic = "abort" +codegen-units = 1 +strip = "debuginfo" \ No newline at end of file diff --git a/ebpf-programs/udp_monitor/rust-toolchain.toml b/ebpf-programs/udp_monitor/rust-toolchain.toml new file mode 100644 index 0000000..c356973 --- /dev/null +++ b/ebpf-programs/udp_monitor/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.78.0" +targets = ["bpfel-unknown-none"] +components = ["rust-src"] diff --git a/ebpf-programs/udp_monitor/src/lib.rs b/ebpf-programs/udp_monitor/src/lib.rs new file mode 100644 index 0000000..b73f81b --- /dev/null +++ b/ebpf-programs/udp_monitor/src/lib.rs @@ -0,0 +1,46 @@ +#![no_std] +#![no_main] +#![allow(static_mut_refs)] +#![allow(unused_unsafe)] + +use aya_ebpf::{ + helpers::{bpf_get_current_pid_tgid, bpf_probe_read}, + macros::{kprobe, map}, + maps::HashMap, + programs::ProbeContext, +}; + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct UdpKey { + pub pid: u32, + pub dst_ip: u32, +} + +#[map(name = "udp_attempts")] +static mut UDP_ATTEMPTS: HashMap = HashMap::with_max_entries(1024, 0); + +#[kprobe] +pub fn udp_monitor(_ctx: ProbeContext) -> u32 { + let pid = (unsafe { bpf_get_current_pid_tgid() } >> 32) as u32; + let key = UdpKey { pid, dst_ip: 0 }; // Use dummy IP for now + + unsafe { + if let Some(count) = UDP_ATTEMPTS.get_ptr_mut(&key) { + *count += 1; + } else { + let _ = UDP_ATTEMPTS.insert(&key, &1, 0); + } + } + + 0 +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[no_mangle] +#[link_section = "license"] +pub static LICENSE: [u8; 4] = *b"GPL\0"; diff --git a/scripts/build_ebpf.sh b/scripts/build_ebpf.sh index 8fd66f8..c7514eb 100755 --- a/scripts/build_ebpf.sh +++ b/scripts/build_ebpf.sh @@ -1,28 +1,80 @@ #!/bin/bash set -e -PROBE_DIR="ebpf-programs/ssh_monitor" -TARGET_DIR="$PROBE_DIR/target/bpfel-unknown-none/release/deps" +# ------------------------------------- +# CONFIGURATION +# ------------------------------------- +BASE_DIR="ebpf-programs" OUT_DIR="bin" -OUT_FILE="$OUT_DIR/ssh_monitor.o" +TARGET="bpfel-unknown-none" +RELEASE_PATH="target/${TARGET}/release" +DEPS_PATH="$RELEASE_PATH/deps" -echo "📦 Building eBPF LLVM bitcode..." - -cargo +nightly rustc --release \ - --manifest-path "$PROBE_DIR/Cargo.toml" \ - --target bpfel-unknown-none -Z build-std=core \ - -- --emit=obj - -echo "🔍 Searching for compiled bitcode..." -OBJ_BC=$(find "$TARGET_DIR" -maxdepth 1 -name 'ssh_monitor_ebpf-*.o' | head -n1) +# ------------------------------------- +# TOOL CHECKS +# ------------------------------------- +command -v llc-20 >/dev/null 2>&1 || { + echo "Error: 'llc-20' not found. Install via LLVM 12–20." >&2 + exit 1 +} -if [[ -z "$OBJ_BC" ]]; then - echo "❌ Failed: Bitcode .o not found" +command -v cargo >/dev/null 2>&1 || { + echo "Error: 'cargo' not found in PATH." >&2 exit 1 -fi +} -echo "🔧 Converting to ELF using llc-20..." mkdir -p "$OUT_DIR" -llc-20 -march=bpf -filetype=obj -o "$OUT_FILE" "$OBJ_BC" -echo "✅ Done: ELF object copied to $OUT_FILE" +# ------------------------------------- +# MAIN LOOP +# ------------------------------------- +for PROBE_DIR in "$BASE_DIR"/*; do + [[ -d "$PROBE_DIR" && -f "$PROBE_DIR/Cargo.toml" ]] || continue + + echo "----------------------------------------" + + # Auto-detect probe (crate) name from Cargo.toml + PROBE_NAME=$(grep '^name' "$PROBE_DIR/Cargo.toml" | head -n1 | cut -d'"' -f2) + + if [[ -z "$PROBE_NAME" ]]; then + echo "Error: Could not detect crate name in $PROBE_DIR/Cargo.toml" >&2 + exit 1 + fi + + echo "Building eBPF probe: $PROBE_NAME" + echo "Source path : $PROBE_DIR" + echo "Target output : $OUT_DIR/$PROBE_NAME.o" + echo + + # Step 1: Build + echo "[1/3] Building with Cargo..." + cargo +nightly rustc --release \ + --manifest-path "$PROBE_DIR/Cargo.toml" \ + --target "$TARGET" -Z build-std=core \ + -- --emit=obj + + # Step 2: Locate compiled object + echo "[2/3] Locating compiled bitcode object..." + OBJ_PATH=$(find "$PROBE_DIR/$DEPS_PATH" -maxdepth 1 -name "${PROBE_NAME}_ebpf-*.o" -o -name "${PROBE_NAME}-*.o" | head -n1) + + if [[ -z "$OBJ_PATH" ]]; then + OBJ_PATH=$(find "$PROBE_DIR/$RELEASE_PATH" -maxdepth 1 -name "${PROBE_NAME}-*.o" | head -n1) + fi + + if [[ -z "$OBJ_PATH" ]]; then + echo "Error: Compiled object (.o) not found for $PROBE_NAME." >&2 + echo "Searched in:" >&2 + echo " $PROBE_DIR/$DEPS_PATH/" >&2 + echo " $PROBE_DIR/$RELEASE_PATH/" >&2 + exit 1 + fi + + # Step 3: Convert to final ELF format + echo "[3/3] Converting to ELF with llc-20..." + llc-20 -march=bpf -filetype=obj -o "$OUT_DIR/$PROBE_NAME.o" "$OBJ_PATH" + + echo "Success: $OUT_DIR/$PROBE_NAME.o created." + echo +done + +echo "All eBPF probes compiled and placed in '$OUT_DIR/'" diff --git a/scripts/sendto_test b/scripts/sendto_test new file mode 100755 index 0000000..97ba52a Binary files /dev/null and b/scripts/sendto_test differ diff --git a/scripts/sendto_test.c b/scripts/sendto_test.c new file mode 100644 index 0000000..893533b --- /dev/null +++ b/scripts/sendto_test.c @@ -0,0 +1,21 @@ +#include +#include +#include +#include +#include + +int main() { + int sock = socket(AF_INET, SOCK_DGRAM, 0); + struct sockaddr_in dest; + + dest.sin_family = AF_INET; + dest.sin_port = htons(9090); + inet_pton(AF_INET, "127.0.0.1", &dest.sin_addr); + + for (int i = 0; i < 50; i++) { + sendto(sock, "hello", 5, 0, (struct sockaddr *)&dest, sizeof(dest)); + } + + close(sock); + return 0; +} diff --git a/scripts/test_udp.sh b/scripts/test_udp.sh index f8f482b..ff02266 100755 --- a/scripts/test_udp.sh +++ b/scripts/test_udp.sh @@ -4,38 +4,20 @@ set -euo pipefail # Config TARGET_IP="127.0.0.1" PORT=9090 -INTERFACE="wlp2s0" PACKETS=50 DELAY=0.05 -MONITOR_LOG="udp_monitor.log" -echo "[*] Using interface: $INTERFACE" - -# Start a UDP listener in background -echo "[*] Starting UDP listener on $TARGET_IP:$PORT" +echo "[*] Starting UDP listener..." nc -u -l "$TARGET_IP" "$PORT" > /dev/null & LISTENER_PID=$! sleep 0.5 -# Send test packets -echo "[*] Sending $PACKETS UDP packets to $TARGET_IP:$PORT..." +echo "[*] Sending $PACKETS UDP packets to $TARGET_IP:$PORT" for i in $(seq 1 $PACKETS); do - echo "Test packet $i" | nc -u -w1 "$TARGET_IP" "$PORT" + echo "packet $i" | nc -u -w1 "$TARGET_IP" "$PORT" sleep $DELAY done -echo "[✓] UDP packet send complete." - -# Wait for monitor to print updates -echo "[*] Waiting for eBPF monitor output..." -sleep 5 - -# Show monitor logs -echo -e "\n[+] eBPF Monitor Output:" -tail -n 30 "$MONITOR_LOG" - -# Cleanup -echo "[*] Cleaning up..." -kill $MONITOR_PID >/dev/null 2>&1 || true -kill $LISTENER_PID >/dev/null 2>&1 || true +echo "[✓] Packets sent." -echo "[✓] Test complete." +echo "[*] Cleaning up listener..." +kill $LISTENER_PID 2>/dev/null || true