secubox-openwrt/package/secubox/secubox-app-mac-guardian/tests/test_detection.sh
CyberMind-FR 2d810a2e95 feat(mac-guardian): Add DHCP lease protection for odhcpd
Prevent odhcpd crashes from MAC randomization causing hostname conflicts,
stale lease pile-up, and lease flooding. Adds hostname dedup, stale lease
cleanup, flood detection, CLI commands, RPC methods, and LuCI dashboard card.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 16:22:37 +01:00

255 lines
7.3 KiB
Bash

#!/bin/sh
# TAP test suite for mac-guardian detection logic
# Run: sh tests/test_detection.sh
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
TEST_TMPDIR=$(mktemp -d)
trap 'rm -rf "$TEST_TMPDIR"' EXIT
# Override paths for testing
MG_RUNDIR="$TEST_TMPDIR/run"
MG_DBFILE="$MG_RUNDIR/known.db"
MG_LOCKDIR="$MG_RUNDIR/lock"
MG_LOGFILE="$TEST_TMPDIR/mac-guardian.log"
MG_EVENTS_LOG="$TEST_TMPDIR/mac-guardian.log"
MG_OUI_FILE="$SCRIPT_DIR/files/usr/lib/secubox/mac-guardian/oui.tsv"
MG_STATS_FILE="$TEST_TMPDIR/stats.json"
MG_MAX_LOG_SIZE=524288
mkdir -p "$MG_RUNDIR"
touch "$MG_DBFILE"
touch "$MG_EVENTS_LOG"
# Stub logger and UCI
logger() { :; }
config_load() { :; }
config_get() {
local var="$1" section="$2" option="$3" default="$4"
eval "$var=\"\${$var:-$default}\""
}
config_list_foreach() { :; }
# Source functions
. "$SCRIPT_DIR/files/usr/lib/secubox/mac-guardian/functions.sh"
# Re-apply path overrides (sourcing functions.sh resets them)
MG_RUNDIR="$TEST_TMPDIR/run"
MG_DBFILE="$MG_RUNDIR/known.db"
MG_LOCKDIR="$MG_RUNDIR/lock"
MG_LOGFILE="$TEST_TMPDIR/mac-guardian.log"
MG_EVENTS_LOG="$TEST_TMPDIR/mac-guardian.log"
MG_OUI_FILE="$SCRIPT_DIR/files/usr/lib/secubox/mac-guardian/oui.tsv"
MG_STATS_FILE="$TEST_TMPDIR/stats.json"
MG_MAX_LOG_SIZE=524288
# Reset config
MG_ENABLED=1
MG_DEBUG=0
MG_DETECT_RANDOM=1
MG_DETECT_OUI_DUP=1
MG_OUI_DUP_THRESHOLD=3
MG_DETECT_FLIP=1
MG_FLIP_WINDOW=300
MG_FLIP_THRESHOLD=3
MG_DETECT_SPOOF=1
MG_POLICY="alert"
MG_NOTIFY_CROWDSEC=0
MG_WL_MACS=""
MG_WL_OUIS=""
MG_START_TIME=$(date +%s)
MG_TOTAL_SCANS=0
MG_TOTAL_ALERTS=0
MG_DHCP_ENABLED=1
MG_DHCP_CLEANUP_STALE=1
MG_DHCP_DEDUP_HOSTNAMES=1
MG_DHCP_FLOOD_THRESHOLD=3
MG_DHCP_FLOOD_WINDOW=60
MG_DHCP_STALE_TIMEOUT=3600
# --- TAP output ---
TESTS=0
PASS=0
FAIL=0
ok() {
TESTS=$((TESTS + 1))
PASS=$((PASS + 1))
echo "ok $TESTS - $1"
}
not_ok() {
TESTS=$((TESTS + 1))
FAIL=$((FAIL + 1))
echo "not ok $TESTS - $1"
}
echo "TAP version 13"
echo "1..12"
# --- Test 1: Randomized MAC generates alert ---
: > "$MG_EVENTS_LOG"
: > "$MG_DBFILE"
mg_check_station "02:aa:bb:cc:dd:ee" "-65" "wlan0"
if grep -q "randomized_mac" "$MG_EVENTS_LOG"; then
ok "Randomized MAC (02:xx) triggers alert"
else
not_ok "Randomized MAC (02:xx) triggers alert"
fi
# --- Test 2: Non-randomized MAC does NOT generate randomized alert ---
: > "$MG_EVENTS_LOG"
: > "$MG_DBFILE"
mg_check_station "00:11:22:33:44:55" "-60" "wlan0"
if grep -q "randomized_mac" "$MG_EVENTS_LOG"; then
not_ok "Non-randomized MAC (00:xx) does not trigger randomized alert"
else
ok "Non-randomized MAC (00:xx) does not trigger randomized alert"
fi
# --- Test 3: New station event logged ---
: > "$MG_EVENTS_LOG"
: > "$MG_DBFILE"
mg_check_station "00:aa:bb:cc:dd:01" "-55" "wlan0"
if grep -q "new_station" "$MG_EVENTS_LOG"; then
ok "New station event logged for unknown MAC"
else
not_ok "New station event logged for unknown MAC"
fi
# --- Test 4: OUI anomaly detection ---
: > "$MG_EVENTS_LOG"
: > "$MG_DBFILE"
now=$(date +%s)
# Seed database with 4 MACs sharing same OUI on wlan0 (threshold is 3)
echo "aa:bb:cc:00:00:01|AA:BB:CC|$now|$now|wlan0|host1|unknown" >> "$MG_DBFILE"
echo "aa:bb:cc:00:00:02|AA:BB:CC|$now|$now|wlan0|host2|unknown" >> "$MG_DBFILE"
echo "aa:bb:cc:00:00:03|AA:BB:CC|$now|$now|wlan0|host3|unknown" >> "$MG_DBFILE"
echo "aa:bb:cc:00:00:04|AA:BB:CC|$now|$now|wlan0|host4|unknown" >> "$MG_DBFILE"
mg_detect_oui_anomaly "wlan0"
if grep -q "oui_anomaly" "$MG_EVENTS_LOG"; then
ok "OUI anomaly triggered when count exceeds threshold"
else
not_ok "OUI anomaly triggered when count exceeds threshold"
fi
# --- Test 5: OUI anomaly NOT triggered below threshold ---
: > "$MG_EVENTS_LOG"
: > "$MG_DBFILE"
echo "aa:bb:cc:00:00:01|AA:BB:CC|$now|$now|wlan0|host1|unknown" >> "$MG_DBFILE"
echo "aa:bb:cc:00:00:02|AA:BB:CC|$now|$now|wlan0|host2|unknown" >> "$MG_DBFILE"
mg_detect_oui_anomaly "wlan0"
if grep -q "oui_anomaly" "$MG_EVENTS_LOG"; then
not_ok "OUI anomaly not triggered below threshold"
else
ok "OUI anomaly not triggered below threshold"
fi
# --- Test 6: MAC flood detection ---
: > "$MG_EVENTS_LOG"
: > "$MG_DBFILE"
now=$(date +%s)
# Seed with many recent MACs (threshold is 3)
echo "00:11:22:33:44:01|00:11:22|$now|$now|wlan0||unknown" >> "$MG_DBFILE"
echo "00:11:22:33:44:02|00:11:22|$now|$now|wlan0||unknown" >> "$MG_DBFILE"
echo "00:11:22:33:44:03|00:11:22|$now|$now|wlan0||unknown" >> "$MG_DBFILE"
echo "00:11:22:33:44:04|00:11:22|$now|$now|wlan0||unknown" >> "$MG_DBFILE"
mg_detect_mac_flip "wlan0"
if grep -q "mac_flood" "$MG_EVENTS_LOG"; then
ok "MAC flood detected when new MACs exceed threshold in window"
else
not_ok "MAC flood detected when new MACs exceed threshold in window"
fi
# --- Test 7: Spoof detection (interface change) ---
: > "$MG_EVENTS_LOG"
: > "$MG_DBFILE"
now=$(date +%s)
# Seed a MAC on wlan0
echo "00:aa:bb:cc:dd:99|00:AA:BB|$((now - 60))|$((now - 10))|wlan0|testhost|unknown" >> "$MG_DBFILE"
# Check same MAC on wlan1 -> should trigger spoof
mg_check_station "00:aa:bb:cc:dd:99" "-70" "wlan1"
if grep -q "spoof_detected" "$MG_EVENTS_LOG"; then
ok "Spoof detected when MAC appears on different interface"
else
not_ok "Spoof detected when MAC appears on different interface"
fi
# --- Test 8: Stats generation ---
: > "$MG_DBFILE"
echo "aa:bb:cc:00:00:01|AA:BB:CC|$now|$now|wlan0|host1|trusted" >> "$MG_DBFILE"
echo "aa:bb:cc:00:00:02|AA:BB:CC|$now|$now|wlan0|host2|suspect" >> "$MG_DBFILE"
echo "aa:bb:cc:00:00:03|AA:BB:CC|$now|$now|wlan0|host3|unknown" >> "$MG_DBFILE"
mg_stats_generate
if [ -f "$MG_STATS_FILE" ] && grep -q '"trusted":1' "$MG_STATS_FILE"; then
ok "Stats generation produces valid JSON with correct counts"
else
not_ok "Stats generation produces valid JSON with correct counts"
fi
# --- DHCP Detection Tests ---
# Override DHCP leases path for testing
MG_DHCP_LEASES="$TEST_TMPDIR/dhcp.leases"
# --- Test 9: DHCP hostname conflict event logged ---
: > "$MG_EVENTS_LOG"
: > "$MG_DBFILE"
now=$(date +%s)
cat > "$MG_DHCP_LEASES" <<EOF
$((now - 50)) 00:aa:bb:cc:01:01 192.168.1.50 duphost *
$now 00:aa:bb:cc:01:02 192.168.1.51 duphost *
EOF
mg_dhcp_find_hostname_dupes
if grep -q "dhcp_hostname_conflict" "$MG_EVENTS_LOG"; then
ok "DHCP hostname conflict event logged"
else
not_ok "DHCP hostname conflict event logged"
fi
# --- Test 10: DHCP stale removal event logged ---
: > "$MG_EVENTS_LOG"
now=$(date +%s)
cat > "$MG_DHCP_LEASES" <<EOF
$((now - 7200)) 00:aa:bb:cc:02:01 192.168.1.60 oldhost *
EOF
mg_dhcp_cleanup_stale
if grep -q "dhcp_stale_removed" "$MG_EVENTS_LOG"; then
ok "DHCP stale removal event logged"
else
not_ok "DHCP stale removal event logged"
fi
# --- Test 11: DHCP lease flood event logged ---
: > "$MG_EVENTS_LOG"
now=$(date +%s)
cat > "$MG_DHCP_LEASES" <<EOF
$now 00:aa:bb:cc:03:01 192.168.1.70 f1 *
$now 00:aa:bb:cc:03:02 192.168.1.71 f2 *
$now 00:aa:bb:cc:03:03 192.168.1.72 f3 *
$now 00:aa:bb:cc:03:04 192.168.1.73 f4 *
EOF
mg_dhcp_detect_flood
if grep -q "dhcp_lease_flood" "$MG_EVENTS_LOG"; then
ok "DHCP lease flood event logged"
else
not_ok "DHCP lease flood event logged"
fi
# --- Test 12: Empty leases file is handled safely ---
: > "$MG_EVENTS_LOG"
: > "$MG_DHCP_LEASES"
mg_dhcp_maintenance
if [ -f "$MG_DHCP_LEASES" ]; then
ok "Empty leases file handled safely by maintenance"
else
not_ok "Empty leases file handled safely by maintenance"
fi
# --- Summary ---
echo ""
echo "# Tests: $TESTS, Passed: $PASS, Failed: $FAIL"
if [ "$FAIL" -gt 0 ]; then
exit 1
fi
exit 0