Add passive DHCP-snooping scanner

Listen for DHCP DISCOVER/REQUEST broadcasts on UDP 67 to catch devices as
early as possible — a device must request an address before doing almost
anything else, often before it has an IP.

Follows the mDNS/SSDP scanner pattern (finder/scanner/status modules) and
feeds the shared devices/events/notifications pipeline. The client MAC is
taken directly from the packet's chaddr, so no ARP probe is needed; a
DISCOVER with no assigned IP reuses any previously recorded address rather
than clobbering it. Exposes GET /api/dhcp_scanner/status, wired into the
OpenAPI generation, and adds a Dhcp variant to DeviceEventScanner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
rzuasti
2026-06-01 18:34:02 -04:00
co-authored by Claude Opus 4.8
parent 440959b10d
commit 189b79233b
10 changed files with 578 additions and 3 deletions
+6
View File
@@ -6,6 +6,7 @@ use crate::model::notifications::{Notification, NotificationType};
use crate::settings::get_settings;
use crate::web_server::arp_scanner::ArpScannerStatusResponse;
use crate::web_server::devices::{RegisterDevicePayload, UpdateDevicePayload};
use crate::web_server::dhcp_scanner::DhcpScannerStatusResponse;
use crate::web_server::mdns_scanner::MdnsScannerStatusResponse;
use crate::web_server::ssdp_scanner::SsdpScannerStatusResponse;
use axum::Json;
@@ -27,6 +28,7 @@ use utoipa_swagger_ui::SwaggerUi;
pub mod arp_scanner;
pub mod device_events;
pub mod devices;
pub mod dhcp_scanner;
pub mod mdns_scanner;
pub mod notifications;
pub mod ssdp_scanner;
@@ -56,6 +58,7 @@ pub mod utils;
arp_scanner::status,
mdns_scanner::status,
ssdp_scanner::status,
dhcp_scanner::status,
),
components(schemas(
Device,
@@ -70,6 +73,7 @@ pub mod utils;
ArpScannerStatusResponse,
MdnsScannerStatusResponse,
SsdpScannerStatusResponse,
DhcpScannerStatusResponse,
)),
modifiers(&SecurityAddon),
tags(
@@ -79,6 +83,7 @@ pub mod utils;
(name = "arp_scanner", description = "ARP scanner process status"),
(name = "mdns_scanner", description = "mDNS/Bonjour scanner process status"),
(name = "ssdp_scanner", description = "SSDP/UPnP scanner process status"),
(name = "dhcp_scanner", description = "DHCP scanner process status"),
)
)]
struct ApiDoc;
@@ -125,6 +130,7 @@ pub async fn serve() -> Result<(), Box<dyn Error>> {
.route("/api/arp_scanner/status", get(arp_scanner::status))
.route("/api/mdns_scanner/status", get(mdns_scanner::status))
.route("/api/ssdp_scanner/status", get(ssdp_scanner::status))
.route("/api/dhcp_scanner/status", get(dhcp_scanner::status))
.route("/api/notifications", get(notifications::list))
.route(
"/api/notifications/mark_all_as_old",