Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions core/udp.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,44 +20,43 @@ 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 {
log.Fatalf("Map '%s' not found", mapName)
}
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)
Expand All @@ -68,14 +67,14 @@ func RunUDPMonitor() {
for {
select {
case <-ticker.C:
iter := m.Iterate()
var (
key udpKey
value uint32
total uint32
)

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)
Expand All @@ -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
}
}
Expand Down
5 changes: 5 additions & 0 deletions ebpf-programs/udp_monitor/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[build]
rustflags = [
"-Zbuild-std=core,compiler_builtins",
"-Zbuild-std-features=compiler-builtins-mem"
]
18 changes: 18 additions & 0 deletions ebpf-programs/udp_monitor/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
4 changes: 4 additions & 0 deletions ebpf-programs/udp_monitor/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[toolchain]
channel = "1.78.0"
targets = ["bpfel-unknown-none"]
components = ["rust-src"]
46 changes: 46 additions & 0 deletions ebpf-programs/udp_monitor/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<UdpKey, u32> = 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";
88 changes: 70 additions & 18 deletions scripts/build_ebpf.sh
Original file line number Diff line number Diff line change
@@ -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/'"
Binary file added scripts/sendto_test
Binary file not shown.
21 changes: 21 additions & 0 deletions scripts/sendto_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>

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;
}
30 changes: 6 additions & 24 deletions scripts/test_udp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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