From 5c34143b52ac19f4bfea0d134619fbf8cb4e6c4b Mon Sep 17 00:00:00 2001 From: Olivier Date: Mon, 18 May 2026 15:26:50 +0200 Subject: [PATCH] feat: add soft scan mode (slow ICMP) to avoid switch/AP rate-limiting Reduces ICMP concurrency from 100 to 10 workers when soft_scan=true, spreading out probes to avoid rate-limiting on managed switches and APs. The option is hidden in the UI when TCP check is active (redundant). Update README (en/fr/es), docs/backend.md with the new scan modes table and a troubleshooting entry for ICMP rate-limiting. Co-Authored-By: Claude Sonnet 4.6 --- README.es.md | 13 ++++++++++++- README.fr.md | 13 ++++++++++++- README.md | 13 ++++++++++++- backend/routers/discovery.py | 7 ++++++- docs/backend.md | 14 ++++++++++++-- frontend/src/components/DiscoveryModal.vue | 10 ++++++++++ frontend/src/i18n.js | 6 ++++++ 7 files changed, 70 insertions(+), 6 deletions(-) diff --git a/README.es.md b/README.es.md index a07d753..4fc0b7d 100644 --- a/README.es.md +++ b/README.es.md @@ -429,13 +429,24 @@ npm run dev # Servidor dev Vite en :5173, proxifica /api/ hacia :8000 **Causa** — Algunos equipos de red (especialmente UniFi Security Gateway, Dream Machine y equipos similares) activan el proxy-ARP y responden a los pings ICMP para **todas** las IPs de la subred, suplantando la IP de origen de la respuesta. La verificación de IP de origen integrada en el escáner no puede filtrar estos falsos positivos. -**Solución** — Activar la opción **"Verificación TCP (anti proxy-ARP)"** en la pantalla de configuración del escaneo. Esta opción sondea cada host en los puertos TCP 22, 80, 443, 8080 y 8443 tras el ping ICMP: +**Solución** — Activar la opción **"Escaneo TCP (anti proxy-ARP)"** en la pantalla de configuración del escaneo. Esta opción usa TCP en lugar de ICMP para detectar hosts activos (puertos 22, 80, 443, 8080, 8443): - Un **host real** responde con RST (puerto cerrado) o acepta la conexión → considerado activo. - Una **IP fantasma**: el gateway descarta silenciosamente el SYN sin responder → timeout → eliminada. > **Nota**: un equipo cuyo firewall descarte silenciosamente (*DROP*, sin RST) **todos** los puertos sondeados no será descubierto automáticamente y deberá añadirse manualmente. +### Faltan equipos en el escaneo (rate-limiting ICMP) + +**Síntoma** — Algunos hosts (APs Wi-Fi, switches, dispositivos IoT) responden a un ping directo pero no aparecen en un escaneo completo de la subred. + +**Causa** — Cuando 100 workers ICMP concurrentes inundan un `/24`, algunos equipos o switches gestionados limitan las respuestas ICMP. El equipo ignora la sonda durante el escaneo aunque un ping simple funcione correctamente. + +**Solución** — Dos opciones: + +- Activar **"Escaneo suave (ICMP lento)"**: reduce la concurrencia de 100 a 10 workers. El escaneo tarda más pero las sondas ICMP se distribuyen en el tiempo, evitando el rate-limiting. Ideal para redes sin proxy-ARP. +- Activar **"Escaneo TCP (anti proxy-ARP)"**: evita ICMP por completo. Las sondas TCP no están sujetas al mismo rate-limiting. Ideal cuando hay tanto proxy-ARP como rate-limiting. + --- ## 🏗️ Arquitectura diff --git a/README.fr.md b/README.fr.md index 1b7157b..41ac040 100644 --- a/README.fr.md +++ b/README.fr.md @@ -429,13 +429,24 @@ npm run dev # Serveur dev Vite sur :5173, proxifie /api/ vers :8000 **Cause** — Certains équipements réseau (notamment UniFi Security Gateway, Dream Machine et équipements similaires) activent le proxy-ARP et répondent aux pings ICMP pour **toutes** les IP du sous-réseau, en usurpant l'IP source de la réponse. La vérification de l'IP source intégrée au scan ne peut donc pas filtrer ces faux positifs. -**Solution** — Cocher l'option **"Vérification TCP (anti proxy-ARP)"** dans l'écran de configuration du scan. Cette option sonde chaque hôte sur les ports TCP 22, 80, 443, 8080 et 8443 après le ping ICMP : +**Solution** — Cocher l'option **"Scan TCP (anti proxy-ARP)"** dans l'écran de configuration du scan. Cette option utilise TCP au lieu de ICMP pour détecter les hôtes actifs (ports 22, 80, 443, 8080, 8443) : - Un **vrai hôte** répond avec RST (port fermé) ou accepte la connexion → considéré vivant. - Une **IP fantôme** : le gateway droppe silencieusement le SYN sans répondre → timeout → éliminée. > **Note** : un équipement dont le pare-feu bloque silencieusement (*DROP*, sans RST) **tous** les ports sondés ne sera pas découvert automatiquement et devra être ajouté manuellement. +### Des équipements manquent dans le scan (rate-limiting ICMP) + +**Symptôme** — Quelques hôtes (AP Wi-Fi, switchs, appareils IoT) répondent à un ping direct mais n'apparaissent pas lors d'un scan complet du sous-réseau. + +**Cause** — Quand 100 workers ICMP concurrents inondent un `/24`, certains équipements ou switchs managés bridant les réponses ICMP. L'équipement ignore la sonde pendant le scan alors qu'un ping simple fonctionne. + +**Solution** — Deux options : + +- Activer **"Scan doux (ICMP lent)"** : réduit la concurrence de 100 à 10 workers. Le scan prend plus de temps mais les sondes ICMP sont étalées, évitant le rate-limiting. Idéal pour les réseaux sans proxy-ARP. +- Activer **"Scan TCP (anti proxy-ARP)"** : contourne ICMP entièrement. Les sondes TCP ne sont pas soumises au même rate-limiting. Idéal si proxy-ARP et rate-limiting sont tous deux présents. + --- ## 🏗️ Architecture diff --git a/README.md b/README.md index 5f80999..27dd0d2 100644 --- a/README.md +++ b/README.md @@ -429,13 +429,24 @@ npm run dev # Vite dev server on :5173, proxies /api/ to :8000 **Cause** — Some network equipment (notably UniFi Security Gateway, Dream Machine, and similar devices) enables proxy-ARP and responds to ICMP pings for **every** IP in the subnet, spoofing the source IP of the reply. The built-in source-IP check in the scanner cannot filter these false positives. -**Fix** — Enable the **"TCP check (anti proxy-ARP)"** option in the scan configuration screen. This option probes each host on TCP ports 22, 80, 443, 8080, and 8443 after the ICMP ping: +**Fix** — Enable the **"TCP check (anti proxy-ARP)"** option in the scan configuration screen. This option uses TCP instead of ICMP to detect live hosts (ports 22, 80, 443, 8080, 8443): - A **real host** replies with RST (port closed) or accepts the connection → marked alive. - A **ghost IP**: the gateway silently drops the SYN without replying → timeout → discarded. > **Note**: a device whose firewall silently drops (*DROP*, without RST) **all** probed ports will not be discovered automatically and must be added manually. +### Some devices are missing from the scan (ICMP rate-limiting) + +**Symptom** — A few hosts (APs, switches, IoT devices) respond to a direct ping but are not found during a full subnet scan. + +**Cause** — When 100 concurrent ICMP workers flood a `/24`, some devices or managed switches rate-limit ICMP responses. The device drops the probe during the scan even though a single ping works fine. + +**Fix** — Two options: + +- Enable **"Soft scan (slow ICMP)"**: reduces concurrency from 100 to 10 workers. The scan takes longer but ICMP probes are spread out, avoiding rate-limiting. Best for subnets without proxy-ARP. +- Enable **"TCP check (anti proxy-ARP)"**: bypasses ICMP entirely. TCP probes are not subject to the same rate-limiting. Best when both proxy-ARP and rate-limiting are present. + --- ## 🏗️ Architecture diff --git a/backend/routers/discovery.py b/backend/routers/discovery.py index 154d44c..b10d874 100644 --- a/backend/routers/discovery.py +++ b/backend/routers/discovery.py @@ -29,6 +29,7 @@ class ScanRequest(BaseModel): dns_server: str = "" targets: list[ScanTarget] tcp_check: bool = False + soft_scan: bool = False @field_validator("dns_server") @classmethod @@ -189,7 +190,11 @@ def scan(req: ScanRequest): t0 = time.time() results: list[DiscoveredHost] = [] - with ThreadPoolExecutor(max_workers=100) as pool: + # Soft scan reduces ICMP concurrency to avoid rate-limiting on switches/APs. + # Has no effect in tcp_check mode (TCP probes are not rate-limited the same way). + workers = 10 if (req.soft_scan and not req.tcp_check) else 100 + + with ThreadPoolExecutor(max_workers=workers) as pool: futures = [pool.submit(_scan_one, *args, tcp_check=req.tcp_check) for args in tasks] for f in as_completed(futures): host = f.result() diff --git a/docs/backend.md b/docs/backend.md index 1d32e73..2575e31 100644 --- a/docs/backend.md +++ b/docs/backend.md @@ -125,7 +125,9 @@ Ping sweep + DNS PTR reverse lookup for one or more CIDR ranges. "dns_server": "192.168.1.1", "targets": [ { "vlan_id": 1, "cidr": "192.168.1.0/24" } - ] + ], + "tcp_check": false, + "soft_scan": false } // Response @@ -136,7 +138,15 @@ Ping sweep + DNS PTR reverse lookup for one or more CIDR ranges. } ``` -Maximum 1024 hosts per target (rejects /21 and wider). 100 concurrent workers. DNS queries use `dnspython` with a 1s timeout. +Maximum 1024 hosts per target (rejects /21 and wider). DNS queries use `dnspython` with a 1s timeout. + +**Scan modes** (mutually exclusive options): + +| `tcp_check` | `soft_scan` | Behaviour | +|:-----------:|:-----------:|-----------| +| `false` | `false` | ICMP ping, 100 concurrent workers (default) | +| `false` | `true` | ICMP ping, 10 concurrent workers — avoids rate-limiting by switches/APs | +| `true` | _(ignored)_ | TCP-only (ports 22, 80, 443, 8080, 8443), 100 workers — eliminates proxy-ARP false positives | --- diff --git a/frontend/src/components/DiscoveryModal.vue b/frontend/src/components/DiscoveryModal.vue index 34bec67..171a6ef 100644 --- a/frontend/src/components/DiscoveryModal.vue +++ b/frontend/src/components/DiscoveryModal.vue @@ -63,6 +63,14 @@
{{ t('tcpCheckHint') }}
+
+ +
{{ t('softScanHint') }}
+
+
{{ configError }}
@@ -186,6 +194,7 @@ const emit = defineEmits(['close', 'refresh']) const step = ref('config') const dnsServer = ref('') const tcpCheck = ref(false) +const softScan = ref(false) const selectedVlanIds = ref([]) const results = ref([]) const selectedIps = ref([]) @@ -272,6 +281,7 @@ async function startScan() { dns_server: dnsServer.value.trim(), targets, tcp_check: tcpCheck.value, + soft_scan: softScan.value, }) results.value = resp.data.hosts scanMeta.value = { total_scanned: resp.data.total_scanned, duration_s: resp.data.duration_s } diff --git a/frontend/src/i18n.js b/frontend/src/i18n.js index 959df18..ca71b85 100644 --- a/frontend/src/i18n.js +++ b/frontend/src/i18n.js @@ -130,6 +130,8 @@ const LANGS = { scanNote: 'Chaque hôte est pingé puis interrogé en DNS.', tcpCheckLabel: 'Scan TCP (anti proxy-ARP)', tcpCheckHint: 'Utilise TCP au lieu de ICMP pour détecter les hôtes actifs (ports 22, 80, 443, 8080, 8443). Élimine les faux positifs proxy-ARP (UniFi…) et détecte les équipements dont le ping ICMP est bridé sous charge. Peut rater les équipements qui bloquent silencieusement (DROP) tous ces ports.', + softScanLabel: 'Scan doux (ICMP lent)', + softScanHint: 'Réduit la concurrence ICMP de 100 à 10 workers pour éviter le rate-limiting des switchs et AP. Le scan prend plus de temps mais manque moins d'équipements.', hostsFound: 'hôte(s) découvert(s)', addressesScanned: 'adresses scannées', newHosts: 'nouveaux', @@ -289,6 +291,8 @@ const LANGS = { scanNote: 'Each host is pinged then queried via DNS.', tcpCheckLabel: 'TCP scan (anti proxy-ARP)', tcpCheckHint: 'Uses TCP instead of ICMP to detect live hosts (ports 22, 80, 443, 8080, 8443). Eliminates proxy-ARP false positives (UniFi…) and detects hosts whose ICMP is rate-limited under load. May miss devices that silently block (DROP) all probed ports.', + softScanLabel: 'Soft scan (slow ICMP)', + softScanHint: 'Reduces ICMP concurrency from 100 to 10 workers to avoid rate-limiting by switches and APs. The scan takes longer but misses fewer devices.', hostsFound: 'host(s) found', addressesScanned: 'addresses scanned', newHosts: 'new', @@ -447,6 +451,8 @@ const LANGS = { scanNote: 'Cada host es pingado y luego consultado en DNS.', tcpCheckLabel: 'Escaneo TCP (anti proxy-ARP)', tcpCheckHint: 'Usa TCP en lugar de ICMP para detectar hosts activos (puertos 22, 80, 443, 8080, 8443). Elimina falsos positivos de proxy-ARP (UniFi…) y detecta hosts con ICMP limitado bajo carga. Puede omitir equipos que bloqueen silenciosamente (DROP) todos los puertos sondeados.', + softScanLabel: 'Escaneo suave (ICMP lento)', + softScanHint: 'Reduce la concurrencia ICMP de 100 a 10 workers para evitar el rate-limiting de switches y APs. El escaneo tarda más pero detecta más equipos.', hostsFound: 'host(s) descubierto(s)', addressesScanned: 'direcciones escaneadas', newHosts: 'nuevos',