feat: improve service detection — expanded signatures, port hints, better nmap args

- Expand service_signatures.json from 35 → ~120 entries covering *arr apps,
  smart-home (HA, MQTT, ESPHome), cameras (RTSP, Dahua, Tapo, Reolink),
  network gear (MikroTik, UniFi, Pi-hole), auth (Authelia, Authentik, Vault),
  monitoring (Grafana, InfluxDB, Loki, Uptime Kuma), containers (Portainer),
  and many more home lab services
- Expand nmap port range to cover home lab ports (8989, 7878, 8123, 554, 1883, etc.)
  and increase --host-timeout from 30s to 120s for reliable -sV detection
- Add _PORT_TYPE_HINTS fallback in suggest_node_type for ports without signatures
  (cameras→iot, MQTT→iot, Proxmox→proxmox, MikroTik→router, etc.)
- Show actual port/protocol (e.g. TCP/9999) instead of "unknown_service" so
  all open ports are visible even when not matched
- Update tests to reflect new unknown-port label format
This commit is contained in:
Pouzor
2026-03-07 16:33:03 +01:00
parent bc30250398
commit 1a72f9fa20
4 changed files with 194 additions and 40 deletions
+37 -2
View File
@@ -44,24 +44,59 @@ def fingerprint_ports(open_ports: list[dict[str, Any]]) -> list[dict[str, Any]]:
"category": sig.get("category"),
})
else:
proto = p.get("protocol", "tcp").upper()
results.append({
"port": p["port"],
"protocol": p.get("protocol", "tcp"),
"service_name": "unknown_service",
"service_name": f"{proto}/{p['port']}",
"icon": None,
"category": None,
})
return results
_PORT_TYPE_HINTS: dict[int, str] = {
# Proxmox
8006: "proxmox",
# NAS / storage
5000: "nas", # Synology DSM
5001: "nas", # Synology DSM HTTPS
548: "nas", # AFP
873: "nas", # rsync
# Routers / network devices
8291: "router", # MikroTik Winbox
179: "router", # BGP
# Cameras / RTSP → iot
554: "iot",
8554: "iot",
37777: "iot", # Dahua
34567: "iot", # Amcrest
2020: "iot", # Tapo
# Smart-home / MQTT → iot
1883: "iot",
8883: "iot",
6052: "iot", # ESPHome
# AP / wireless
8880: "ap", # UniFi HTTP
8443: "ap", # UniFi HTTPS
# Switches
161: "switch", # SNMP
162: "switch", # SNMP trap
}
def suggest_node_type(open_ports: list[dict[str, Any]]) -> str:
"""Suggest a node type based on the most specific matched signature."""
priority = ["proxmox", "nas", "router", "lxc", "vm", "server", "ap", "iot", "switch"]
found: set[str] = set()
for p in open_ports:
sig = match_port(p["port"], p.get("protocol", "tcp"))
port = p["port"]
proto = p.get("protocol", "tcp")
sig = match_port(port, proto)
if sig and sig.get("suggested_node_type"):
found.add(sig["suggested_node_type"])
elif port in _PORT_TYPE_HINTS:
found.add(_PORT_TYPE_HINTS[port])
for t in priority:
if t in found:
return t
+11 -1
View File
@@ -27,7 +27,17 @@ def _nmap_scan(target: str) -> list[dict[str, Any]]:
nm = nmap.PortScanner()
try:
nm.scan(hosts=target, arguments="-sV --open -T4 --host-timeout 30s")
# Home lab port range: standard top-1000 + common self-hosted service ports
extra_ports = (
"80,443,22,21,23,25,53,110,143,161,162,179,389,445,548,"
"554,636,873,1883,1880,1935,2020,2375,2376,3000,3001,3306,"
"3389,4711,5000,5001,5432,5601,5900,5984,6052,6379,6432,6443,"
"6767,6789,6800,7878,8000,8006,8080,8081,8086,8088,8090,8096,"
"8112,8123,8200,8291,8428,8443,8554,8686,8789,8843,8880,8883,"
"8971,8989,9000,9001,9090,9091,9092,9093,9100,9117,9200,9300,"
"9411,9443,9696,10051,16686,34567,37777,51413,64738"
)
nm.scan(hosts=target, arguments=f"-sV --open -T4 --host-timeout 120s -p {extra_ports}")
except Exception as exc:
logger.error("nmap scan failed: %s", exc)
raise RuntimeError(str(exc)) from exc