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:
parent
373d77368e
commit
2d810a2e95
@ -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
|
||||
// ==========================================
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
;;
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user