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>
This commit is contained in:
CyberMind-FR 2026-02-03 16:22:37 +01:00
parent 373d77368e
commit 2d810a2e95
8 changed files with 490 additions and 13 deletions

View File

@ -56,6 +56,18 @@ var callBlock = rpc.declare({
params: ['mac']
});
var callDhcpStatus = rpc.declare({
object: 'luci.mac-guardian',
method: 'dhcp_status',
expect: { '': {} }
});
var callDhcpCleanup = rpc.declare({
object: 'luci.mac-guardian',
method: 'dhcp_cleanup',
expect: { '': {} }
});
function formatDate(ts) {
if (!ts || ts === 0) return '-';
var d = new Date(ts * 1000);
@ -83,7 +95,8 @@ return view.extend({
uci.load('mac-guardian'),
callStatus(),
callGetClients(),
callGetEvents(10)
callGetEvents(10),
callDhcpStatus()
]);
},
@ -91,6 +104,7 @@ return view.extend({
var status = data[1];
var clientData = data[2];
var eventData = data[3];
var dhcpStatus = data[4] || {};
var clients = (clientData && clientData.clients) ? clientData.clients : [];
var events = (eventData && eventData.events) ? eventData.events : [];
var m, s, o;
@ -143,6 +157,17 @@ return view.extend({
}
html += '</div>';
// DHCP Protection card
var dhcpColor = dhcpStatus.enabled ? '#080' : '#888';
var dhcpLabel = dhcpStatus.enabled ? 'Enabled' : 'Disabled';
html += '<div style="min-width:160px;">';
html += '<h4 style="margin:0 0 8px 0;border-bottom:1px solid #ddd;padding-bottom:4px;">DHCP Protection</h4>';
html += '<p><b>Status:</b> <span style="color:' + dhcpColor + ';font-weight:bold;">' + dhcpLabel + '</span></p>';
html += '<p><b>Leases:</b> ' + (dhcpStatus.leases || 0) + '</p>';
html += '<p><b>Conflicts:</b> <span style="color:' + ((dhcpStatus.conflicts || 0) > 0 ? '#c60' : '#080') + ';">' + (dhcpStatus.conflicts || 0) + '</span></p>';
html += '<p><b>Stale:</b> <span style="color:' + ((dhcpStatus.stale || 0) > 0 ? '#c60' : '#080') + ';">' + (dhcpStatus.stale || 0) + '</span></p>';
html += '</div>';
html += '</div>';
return html;
};
@ -175,6 +200,19 @@ return view.extend({
});
};
o = s.option(form.Button, '_dhcp_cleanup', _('DHCP Cleanup'));
o.inputtitle = _('Clean Up');
o.inputstyle = 'reload';
o.onclick = function() {
ui.showModal(_('Cleaning'), [
E('p', { 'class': 'spinning' }, _('Running DHCP lease maintenance...'))
]);
return callDhcpCleanup().then(function() {
ui.hideModal();
window.location.reload();
});
};
// ==========================================
// Clients Table
// ==========================================

View File

@ -8,7 +8,7 @@ MG_LOGFILE="/var/log/mac-guardian.log"
case "$1" in
list)
echo '{"status":{},"get_clients":{},"get_events":{"count":"int"},"scan":{},"start":{},"stop":{},"restart":{},"trust":{"mac":"str"},"block":{"mac":"str"}}'
echo '{"status":{},"get_clients":{},"get_events":{"count":"int"},"scan":{},"start":{},"stop":{},"restart":{},"trust":{"mac":"str"},"block":{"mac":"str"},"dhcp_status":{},"dhcp_cleanup":{}}'
;;
call)
case "$2" in
@ -183,6 +183,46 @@ case "$1" in
echo '{"success":false,"error":"missing mac"}'
fi
;;
dhcp_status)
json_init
dhcp_enabled=$(uci -q get mac-guardian.dhcp.enabled)
json_add_boolean "enabled" ${dhcp_enabled:-1}
leases=0
conflicts=0
stale=0
if [ -f /tmp/dhcp.leases ] && [ -s /tmp/dhcp.leases ]; then
leases=$(wc -l < /tmp/dhcp.leases)
# Count hostname conflicts
conflicts=$(awk '{print $4}' /tmp/dhcp.leases | grep -v '^\*$' | sort | uniq -d | wc -l)
# Count stale leases
now=$(date +%s)
stale_timeout=$(uci -q get mac-guardian.dhcp.stale_timeout)
stale_timeout=${stale_timeout:-3600}
cutoff=$((now - stale_timeout))
stale=$(awk -v cutoff="$cutoff" '$1 < cutoff' /tmp/dhcp.leases | wc -l)
fi
json_add_int "leases" $leases
json_add_int "conflicts" $conflicts
json_add_int "stale" $stale
json_dump
;;
dhcp_cleanup)
/usr/sbin/mac-guardian dhcp-cleanup >/dev/null 2>&1
removed=0
json_init
json_add_boolean "success" 1
json_add_int "removed" $removed
json_dump
;;
esac
;;
esac

View File

@ -21,6 +21,14 @@ config whitelist 'whitelist'
# list mac 'aa:bb:cc:dd:ee:ff'
# list oui '00:50:E4'
config dhcp_protection 'dhcp'
option enabled '1'
option cleanup_stale '1'
option dedup_hostnames '1'
option flood_threshold '10'
option flood_window '60'
option stale_timeout '3600'
config reporting 'reporting'
option stats_file '/var/run/mac-guardian/stats.json'
option stats_interval '60'

View File

@ -46,7 +46,7 @@ config_get enabled main enabled 0
fi
;;
AP-STA-DISCONNECTED)
# Lightweight: just update last_seen
# Update last_seen and clean up stale DHCP lease for this MAC
if mg_validate_mac "$mac"; then
mg_lock && {
local existing
@ -56,6 +56,7 @@ config_get enabled main enabled 0
hostname=$(mg_resolve_hostname "$mac")
mg_db_upsert "$mac" "$iface" "$hostname"
fi
mg_dhcp_cleanup_stale_mac "$mac"
mg_unlock
}
fi

View File

@ -28,6 +28,14 @@ MG_STATS_FILE="/var/run/mac-guardian/stats.json"
MG_STATS_INTERVAL=60
MG_MAX_LOG_SIZE=524288
# DHCP protection config
MG_DHCP_ENABLED=1
MG_DHCP_CLEANUP_STALE=1
MG_DHCP_DEDUP_HOSTNAMES=1
MG_DHCP_FLOOD_THRESHOLD=10
MG_DHCP_FLOOD_WINDOW=60
MG_DHCP_STALE_TIMEOUT=3600
# Whitelist arrays stored as newline-separated strings
MG_WL_MACS=""
MG_WL_OUIS=""
@ -79,6 +87,14 @@ mg_load_config() {
config_get MG_STATS_INTERVAL reporting stats_interval 60
config_get MG_MAX_LOG_SIZE reporting max_log_size 524288
# dhcp protection section
config_get MG_DHCP_ENABLED dhcp enabled 1
config_get MG_DHCP_CLEANUP_STALE dhcp cleanup_stale 1
config_get MG_DHCP_DEDUP_HOSTNAMES dhcp dedup_hostnames 1
config_get MG_DHCP_FLOOD_THRESHOLD dhcp flood_threshold 10
config_get MG_DHCP_FLOOD_WINDOW dhcp flood_window 60
config_get MG_DHCP_STALE_TIMEOUT dhcp stale_timeout 3600
# whitelist lists
MG_WL_MACS=""
MG_WL_OUIS=""
@ -340,6 +356,146 @@ mg_resolve_hostname() {
echo "$hostname"
}
# ============================================================
# DHCP Lease Protection
# ============================================================
MG_DHCP_LEASES="/tmp/dhcp.leases"
mg_dhcp_read_leases() {
local tmpfile="${MG_RUNDIR}/dhcp_leases.$$"
if [ -f "$MG_DHCP_LEASES" ] && [ -s "$MG_DHCP_LEASES" ]; then
cp "$MG_DHCP_LEASES" "$tmpfile"
else
: > "$tmpfile"
fi
echo "$tmpfile"
}
mg_dhcp_find_hostname_dupes() {
[ "$MG_DHCP_DEDUP_HOSTNAMES" != "1" ] && return 0
[ ! -f "$MG_DHCP_LEASES" ] || [ ! -s "$MG_DHCP_LEASES" ] && return 0
local leases_copy
leases_copy=$(mg_dhcp_read_leases)
# Format: timestamp mac ip hostname clientid
# Find hostnames that appear with more than one MAC
local dupes_file="${MG_RUNDIR}/hostname_dupes.$$"
awk '{print $4, $2, $1}' "$leases_copy" | \
sort -k1,1 -k3,3n | \
awk '
{
hostname=$1; mac=$2; ts=$3
if (hostname != "*" && hostname != "" && hostname == prev_host && mac != prev_mac) {
# Duplicate hostname with different MAC - print the older one
if (ts < prev_ts) {
print mac
} else {
print prev_mac
}
}
prev_host=hostname; prev_mac=mac; prev_ts=ts
}' | sort -u > "$dupes_file"
if [ -s "$dupes_file" ]; then
while read -r dup_mac; do
[ -z "$dup_mac" ] && continue
local hostname
hostname=$(awk -v m="$dup_mac" 'tolower($2)==tolower(m) {print $4; exit}' "$leases_copy")
mg_log_event "dhcp_hostname_conflict" "$dup_mac" "" "hostname=${hostname}"
mg_dhcp_remove_lease "$dup_mac"
done < "$dupes_file"
fi
rm -f "$dupes_file" "$leases_copy"
}
mg_dhcp_cleanup_stale() {
[ "$MG_DHCP_CLEANUP_STALE" != "1" ] && return 0
[ ! -f "$MG_DHCP_LEASES" ] || [ ! -s "$MG_DHCP_LEASES" ] && return 0
local now
now=$(date +%s)
local cutoff=$((now - MG_DHCP_STALE_TIMEOUT))
local stale_file="${MG_RUNDIR}/stale_leases.$$"
# Find leases with timestamp older than cutoff
awk -v cutoff="$cutoff" '$1 < cutoff {print $2}' "$MG_DHCP_LEASES" > "$stale_file"
if [ -s "$stale_file" ]; then
while read -r stale_mac; do
[ -z "$stale_mac" ] && continue
mg_log_event "dhcp_stale_removed" "$stale_mac" "" "timeout=${MG_DHCP_STALE_TIMEOUT}s"
mg_dhcp_remove_lease "$stale_mac"
done < "$stale_file"
fi
rm -f "$stale_file"
}
mg_dhcp_cleanup_stale_mac() {
local target_mac="$1"
[ "$MG_DHCP_CLEANUP_STALE" != "1" ] && return 0
[ ! -f "$MG_DHCP_LEASES" ] || [ ! -s "$MG_DHCP_LEASES" ] && return 0
local now
now=$(date +%s)
local cutoff=$((now - MG_DHCP_STALE_TIMEOUT))
# Check if this specific MAC's lease is stale
local lease_ts
lease_ts=$(awk -v m="$target_mac" 'tolower($2)==tolower(m) {print $1; exit}' "$MG_DHCP_LEASES")
[ -z "$lease_ts" ] && return 0
if [ "$lease_ts" -lt "$cutoff" ] 2>/dev/null; then
mg_log_event "dhcp_stale_removed" "$target_mac" "" "timeout=${MG_DHCP_STALE_TIMEOUT}s"
mg_dhcp_remove_lease "$target_mac"
fi
}
mg_dhcp_detect_flood() {
[ "$MG_DHCP_ENABLED" != "1" ] && return 0
[ ! -f "$MG_DHCP_LEASES" ] || [ ! -s "$MG_DHCP_LEASES" ] && return 0
local now
now=$(date +%s)
local window_start=$((now - MG_DHCP_FLOOD_WINDOW))
local recent_count
recent_count=$(awk -v ws="$window_start" '$1 >= ws' "$MG_DHCP_LEASES" | wc -l)
recent_count=$((recent_count + 0))
if [ "$recent_count" -gt "$MG_DHCP_FLOOD_THRESHOLD" ]; then
mg_log_event "dhcp_lease_flood" "" "" "leases=${recent_count} window=${MG_DHCP_FLOOD_WINDOW}s threshold=${MG_DHCP_FLOOD_THRESHOLD}"
fi
}
mg_dhcp_remove_lease() {
local mac="$1"
[ -z "$mac" ] && return 1
[ ! -f "$MG_DHCP_LEASES" ] && return 0
local tmpfile="${MG_DHCP_LEASES}.tmp.$$"
grep -iv " ${mac} " "$MG_DHCP_LEASES" > "$tmpfile" 2>/dev/null || : > "$tmpfile"
mv "$tmpfile" "$MG_DHCP_LEASES"
# Signal odhcpd to reload leases
local odhcpd_pid
odhcpd_pid=$(pgrep odhcpd)
if [ -n "$odhcpd_pid" ]; then
kill -HUP "$odhcpd_pid" 2>/dev/null
fi
}
mg_dhcp_maintenance() {
[ "$MG_DHCP_ENABLED" != "1" ] && return 0
mg_dhcp_find_hostname_dupes
mg_dhcp_cleanup_stale
mg_dhcp_detect_flood
}
# ============================================================
# Logging
# ============================================================
@ -658,6 +814,8 @@ mg_scan_all() {
mg_scan_iface "$iface"
done
mg_dhcp_maintenance
MG_TOTAL_SCANS=$((MG_TOTAL_SCANS + 1))
mg_unlock

View File

@ -219,21 +219,78 @@ cmd_list() {
done < "$MG_DBFILE"
}
cmd_dhcp_status() {
mg_load_config
mg_init
echo "DHCP Lease Protection"
echo "====================="
if [ "$MG_DHCP_ENABLED" != "1" ]; then
echo "Status: DISABLED"
return
fi
echo "Status: ENABLED"
local lease_count=0 conflict_count=0 stale_count=0
if [ -f /tmp/dhcp.leases ] && [ -s /tmp/dhcp.leases ]; then
lease_count=$(wc -l < /tmp/dhcp.leases)
# Count hostname conflicts (hostnames with >1 MAC)
conflict_count=$(awk '{print $4}' /tmp/dhcp.leases | grep -v '^\*$' | sort | uniq -d | wc -l)
# Count stale leases
local now
now=$(date +%s)
local cutoff=$((now - MG_DHCP_STALE_TIMEOUT))
stale_count=$(awk -v cutoff="$cutoff" '$1 < cutoff' /tmp/dhcp.leases | wc -l)
fi
echo "Leases: $lease_count"
echo "Conflicts: $conflict_count"
echo "Stale: $stale_count"
echo ""
echo "Settings:"
echo " Dedup hostnames: $MG_DHCP_DEDUP_HOSTNAMES"
echo " Cleanup stale: $MG_DHCP_CLEANUP_STALE"
echo " Stale timeout: ${MG_DHCP_STALE_TIMEOUT}s"
echo " Flood threshold: $MG_DHCP_FLOOD_THRESHOLD"
echo " Flood window: ${MG_DHCP_FLOOD_WINDOW}s"
}
cmd_dhcp_cleanup() {
mg_load_config
mg_init
if [ "$MG_DHCP_ENABLED" != "1" ]; then
echo "DHCP protection is disabled."
exit 1
fi
echo "Running DHCP lease maintenance..."
mg_dhcp_maintenance
echo "Done."
}
cmd_version() {
echo "mac-guardian v${MG_VERSION}"
}
# --- Main dispatcher ---
case "${1:-}" in
start) cmd_start ;;
scan) cmd_scan ;;
status) cmd_status ;;
trust) cmd_trust "$2" ;;
block) cmd_block "$2" ;;
list) cmd_list "$2" ;;
version) cmd_version ;;
start) cmd_start ;;
scan) cmd_scan ;;
status) cmd_status ;;
trust) cmd_trust "$2" ;;
block) cmd_block "$2" ;;
list) cmd_list "$2" ;;
dhcp-status) cmd_dhcp_status ;;
dhcp-cleanup) cmd_dhcp_cleanup ;;
version) cmd_version ;;
*)
echo "Usage: mac-guardian {start|scan|status|trust|block|list|version}"
echo "Usage: mac-guardian {start|scan|status|trust|block|list|dhcp-status|dhcp-cleanup|version}"
echo ""
echo "Commands:"
echo " start Start the daemon"
@ -242,6 +299,8 @@ case "${1:-}" in
echo " trust <MAC> Add MAC to trusted whitelist"
echo " block <MAC> Block and deauthenticate MAC"
echo " list [filter] List known clients (trusted|blocked|suspect|unknown|all)"
echo " dhcp-status Show DHCP lease protection status"
echo " dhcp-cleanup Run one-shot DHCP lease maintenance"
echo " version Print version"
exit 1
;;

View File

@ -59,6 +59,12 @@ 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
@ -78,7 +84,7 @@ not_ok() {
}
echo "TAP version 13"
echo "1..8"
echo "1..12"
# --- Test 1: Randomized MAC generates alert ---
: > "$MG_EVENTS_LOG"
@ -180,6 +186,65 @@ 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"

View File

@ -58,6 +58,12 @@ MG_POLICY="alert"
MG_NOTIFY_CROWDSEC=0
MG_WL_MACS=""
MG_WL_OUIS=""
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
@ -101,7 +107,7 @@ assert_eq() {
}
echo "TAP version 13"
echo "1..19"
echo "1..27"
# --- Test mg_is_randomized ---
assert_true 'mg_is_randomized "02:11:22:33:44:55"' "mg_is_randomized: 02:xx is randomized"
@ -150,6 +156,108 @@ assert_true '[ -d "$MG_LOCKDIR" ]' "mg_lock: lock directory created"
mg_unlock
assert_false '[ -d "$MG_LOCKDIR" ]' "mg_unlock: lock directory removed"
# --- DHCP Protection Tests ---
# Override DHCP leases path for testing
MG_DHCP_LEASES="$TEST_TMPDIR/dhcp.leases"
# Test 20: mg_dhcp_read_leases returns valid temp file
echo "1000 aa:bb:cc:dd:ee:01 192.168.1.10 host1 *" > "$MG_DHCP_LEASES"
lease_tmp=$(mg_dhcp_read_leases)
if [ -f "$lease_tmp" ] && grep -q "aa:bb:cc:dd:ee:01" "$lease_tmp"; then
ok "mg_dhcp_read_leases: returns readable temp copy"
else
not_ok "mg_dhcp_read_leases: returns readable temp copy"
fi
rm -f "$lease_tmp"
# Test 21: mg_dhcp_find_hostname_dupes detects duplicate hostnames
: > "$MG_EVENTS_LOG"
now=$(date +%s)
cat > "$MG_DHCP_LEASES" <<EOF
$((now - 100)) aa:bb:cc:dd:ee:01 192.168.1.10 myhost *
$now aa:bb:cc:dd:ee:02 192.168.1.11 myhost *
EOF
mg_dhcp_find_hostname_dupes
if grep -q "dhcp_hostname_conflict" "$MG_EVENTS_LOG"; then
ok "mg_dhcp_find_hostname_dupes: detects hostname conflict"
else
not_ok "mg_dhcp_find_hostname_dupes: detects hostname conflict"
fi
# Test 22: mg_dhcp_find_hostname_dupes removes older duplicate
if ! grep -q "aa:bb:cc:dd:ee:01" "$MG_DHCP_LEASES" && grep -q "aa:bb:cc:dd:ee:02" "$MG_DHCP_LEASES"; then
ok "mg_dhcp_find_hostname_dupes: removes older duplicate lease"
else
not_ok "mg_dhcp_find_hostname_dupes: removes older duplicate lease"
fi
# Test 23: mg_dhcp_cleanup_stale removes expired leases
: > "$MG_EVENTS_LOG"
now=$(date +%s)
cat > "$MG_DHCP_LEASES" <<EOF
$((now - 7200)) aa:bb:cc:dd:ee:03 192.168.1.12 stale-host *
$now aa:bb:cc:dd:ee:04 192.168.1.13 fresh-host *
EOF
mg_dhcp_cleanup_stale
if grep -q "dhcp_stale_removed" "$MG_EVENTS_LOG" && grep -q "aa:bb:cc:dd:ee:04" "$MG_DHCP_LEASES"; then
ok "mg_dhcp_cleanup_stale: removes expired, keeps fresh"
else
not_ok "mg_dhcp_cleanup_stale: removes expired, keeps fresh"
fi
# Test 24: mg_dhcp_remove_lease removes specific MAC
now=$(date +%s)
cat > "$MG_DHCP_LEASES" <<EOF
$now aa:bb:cc:dd:ee:05 192.168.1.14 host5 *
$now aa:bb:cc:dd:ee:06 192.168.1.15 host6 *
EOF
mg_dhcp_remove_lease "aa:bb:cc:dd:ee:05"
if ! grep -q "aa:bb:cc:dd:ee:05" "$MG_DHCP_LEASES" && grep -q "aa:bb:cc:dd:ee:06" "$MG_DHCP_LEASES"; then
ok "mg_dhcp_remove_lease: removes target MAC, keeps others"
else
not_ok "mg_dhcp_remove_lease: removes target MAC, keeps others"
fi
# Test 25: mg_dhcp_detect_flood detects lease flooding
: > "$MG_EVENTS_LOG"
now=$(date +%s)
cat > "$MG_DHCP_LEASES" <<EOF
$now aa:bb:cc:dd:ee:10 192.168.1.20 h1 *
$now aa:bb:cc:dd:ee:11 192.168.1.21 h2 *
$now aa:bb:cc:dd:ee:12 192.168.1.22 h3 *
$now aa:bb:cc:dd:ee:13 192.168.1.23 h4 *
EOF
mg_dhcp_detect_flood
if grep -q "dhcp_lease_flood" "$MG_EVENTS_LOG"; then
ok "mg_dhcp_detect_flood: detects flood above threshold"
else
not_ok "mg_dhcp_detect_flood: detects flood above threshold"
fi
# Test 26: mg_dhcp_detect_flood does not trigger below threshold
: > "$MG_EVENTS_LOG"
now=$(date +%s)
cat > "$MG_DHCP_LEASES" <<EOF
$now aa:bb:cc:dd:ee:10 192.168.1.20 h1 *
$now aa:bb:cc:dd:ee:11 192.168.1.21 h2 *
EOF
mg_dhcp_detect_flood
if grep -q "dhcp_lease_flood" "$MG_EVENTS_LOG"; then
not_ok "mg_dhcp_detect_flood: no flood below threshold"
else
ok "mg_dhcp_detect_flood: no flood below threshold"
fi
# Test 27: mg_dhcp_remove_lease handles empty leases file
: > "$MG_DHCP_LEASES"
mg_dhcp_remove_lease "ff:ff:ff:ff:ff:ff"
if [ -f "$MG_DHCP_LEASES" ]; then
ok "mg_dhcp_remove_lease: handles empty leases safely"
else
not_ok "mg_dhcp_remove_lease: handles empty leases safely"
fi
# --- Summary ---
echo ""
echo "# Tests: $TESTS, Passed: $PASS, Failed: $FAIL"