secubox-openwrt/package/secubox/luci-app-mac-guardian/root/usr/libexec/rpcd/luci.mac-guardian
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

231 lines
6.1 KiB
Bash

#!/bin/sh
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
MG_DBFILE="/var/run/mac-guardian/known.db"
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"},"dhcp_status":{},"dhcp_cleanup":{}}'
;;
call)
case "$2" in
status)
json_init
enabled=$(uci -q get mac-guardian.main.enabled)
policy=$(uci -q get mac-guardian.enforcement.policy)
scan_interval=$(uci -q get mac-guardian.main.scan_interval)
detect_random=$(uci -q get mac-guardian.detection.random_mac)
detect_spoof=$(uci -q get mac-guardian.detection.spoof_detection)
json_add_boolean "enabled" ${enabled:-0}
json_add_string "policy" "${policy:-alert}"
json_add_int "scan_interval" ${scan_interval:-30}
json_add_boolean "detect_random" ${detect_random:-1}
json_add_boolean "detect_spoof" ${detect_spoof:-1}
# Service running?
if pgrep mac-guardian >/dev/null 2>&1; then
json_add_string "service_status" "running"
else
json_add_string "service_status" "stopped"
fi
# WiFi interfaces
json_add_array "interfaces"
if command -v iwinfo >/dev/null 2>&1; then
iwinfo 2>/dev/null | grep "ESSID" | while read -r line; do
iface=$(echo "$line" | awk '{print $1}')
essid=$(echo "$line" | sed 's/.*ESSID: "\(.*\)"/\1/')
sta_count=$(iwinfo "$iface" assoclist 2>/dev/null | grep -cE '[0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2}){5}')
json_add_object ""
json_add_string "name" "$iface"
json_add_string "essid" "$essid"
json_add_int "stations" ${sta_count:-0}
json_close_object
done
fi
json_close_array
# DB stats
total=0
trusted=0
suspect=0
blocked=0
unknown=0
if [ -f "$MG_DBFILE" ] && [ -s "$MG_DBFILE" ]; then
total=$(wc -l < "$MG_DBFILE")
trusted=$(grep -c '|trusted$' "$MG_DBFILE" 2>/dev/null || echo 0)
suspect=$(grep -c '|suspect$' "$MG_DBFILE" 2>/dev/null || echo 0)
blocked=$(grep -c '|blocked$' "$MG_DBFILE" 2>/dev/null || echo 0)
unknown=$(grep -c '|unknown$' "$MG_DBFILE" 2>/dev/null || echo 0)
fi
json_add_object "clients"
json_add_int "total" $total
json_add_int "trusted" $trusted
json_add_int "suspect" $suspect
json_add_int "blocked" $blocked
json_add_int "unknown" $unknown
json_close_object
json_dump
;;
get_clients)
json_init
json_add_array "clients"
if [ -f "$MG_DBFILE" ] && [ -s "$MG_DBFILE" ]; then
while IFS='|' read -r mac oui first_seen last_seen iface hostname status; do
[ -z "$mac" ] && continue
json_add_object ""
json_add_string "mac" "$mac"
json_add_string "oui" "$oui"
json_add_int "first_seen" ${first_seen:-0}
json_add_int "last_seen" ${last_seen:-0}
json_add_string "iface" "$iface"
json_add_string "hostname" "${hostname:--}"
json_add_string "status" "$status"
# OUI vendor lookup
vendor=""
if [ -f /usr/lib/secubox/mac-guardian/oui.tsv ]; then
oui_upper=$(echo "$oui" | tr 'a-f' 'A-F')
vendor=$(grep -i "^${oui_upper} " /usr/lib/secubox/mac-guardian/oui.tsv 2>/dev/null | cut -f2 | head -1)
fi
json_add_string "vendor" "${vendor:--}"
# Randomized check
first_octet=$(echo "$mac" | cut -d: -f1)
is_rand=0
[ $((0x$first_octet & 0x02)) -ne 0 ] && is_rand=1
json_add_boolean "randomized" $is_rand
json_close_object
done < "$MG_DBFILE"
fi
json_close_array
json_dump
;;
get_events)
read -r input
count=$(echo "$input" | jsonfilter -e '@.count' 2>/dev/null)
[ -z "$count" ] && count=20
json_init
json_add_array "events"
if [ -f "$MG_LOGFILE" ] && [ -s "$MG_LOGFILE" ]; then
tail -"$count" "$MG_LOGFILE" 2>/dev/null | while read -r line; do
json_add_string "" "$line"
done
fi
json_close_array
json_dump
;;
scan)
/usr/sbin/mac-guardian scan >/dev/null 2>&1
json_init
json_add_boolean "success" 1
json_dump
;;
start)
/etc/init.d/mac-guardian start >/dev/null 2>&1
json_init
json_add_boolean "success" 1
json_dump
;;
stop)
/etc/init.d/mac-guardian stop >/dev/null 2>&1
json_init
json_add_boolean "success" 1
json_dump
;;
restart)
/etc/init.d/mac-guardian restart >/dev/null 2>&1
json_init
json_add_boolean "success" 1
json_dump
;;
trust)
read -r input
mac=$(echo "$input" | jsonfilter -e '@.mac' 2>/dev/null)
if [ -n "$mac" ]; then
/usr/sbin/mac-guardian trust "$mac" >/dev/null 2>&1
json_init
json_add_boolean "success" 1
json_dump
else
echo '{"success":false,"error":"missing mac"}'
fi
;;
block)
read -r input
mac=$(echo "$input" | jsonfilter -e '@.mac' 2>/dev/null)
if [ -n "$mac" ]; then
/usr/sbin/mac-guardian block "$mac" >/dev/null 2>&1
json_init
json_add_boolean "success" 1
json_dump
else
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
exit 0