Add optional Fingerbank device fingerprinting to deep-check
Folded into the existing on-demand Deep check button: queries Fingerbank's interrogate API with the device's MAC plus the SSDP SERVER header when deep-check-device.sh finds one, showing the confidence band alongside the result. Runs directly from the API container (no host-level access needed, just an outbound HTTPS call), unlike the SSDP/mDNS steps. Confirmed via direct testing: without DHCP fingerprint data (which we structurally don't have, not being the DHCP server), MAC-only queries often can't get past manufacturer-level confidence -- same info the free OUI lookup already provides. Documented honestly in docs/device-discovery.md rather than overselling it. Still worth having as opt-in enrichment for devices that do expose richer signals. Gated behind optional FINGERBANK_API_KEY -- missing key, API errors, or no match all degrade gracefully without affecting the rest of deep-check's local findings. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,7 +28,11 @@ fi
|
||||
MDNS=$(timeout 2 avahi-resolve -a "$IP" 2>/dev/null | awk '{print $2}' || true)
|
||||
RDNS=$(timeout 2 getent hosts "$IP" 2>/dev/null | awk '{print $2}' | head -1 || true)
|
||||
|
||||
SSDP_LOCATION=$(timeout 4 python3 - "$IP" <<'PYEOF' || true
|
||||
# Prints LOCATION and SERVER headers (one per line) from the first ssdp:all
|
||||
# response the target IP sends -- SERVER is a useful extra fingerprinting
|
||||
# signal (e.g. "Linux/3.10 UPnP/1.0 MiniDLNA/1.1") beyond just the device
|
||||
# descriptor XML, fed to Fingerbank as an upnp_user_agents hint.
|
||||
SSDP_RAW=$(timeout 4 python3 - "$IP" <<'PYEOF' || true
|
||||
import socket, sys, time
|
||||
target = sys.argv[1]
|
||||
msg = "\r\n".join([
|
||||
@@ -44,14 +48,21 @@ while time.time() - start < 3:
|
||||
try:
|
||||
data, addr = sock.recvfrom(4096)
|
||||
if addr[0] == target:
|
||||
location = server = ""
|
||||
for line in data.decode(errors="replace").split("\r\n"):
|
||||
if line.lower().startswith("location:"):
|
||||
print(line.split(":", 1)[1].strip())
|
||||
sys.exit(0)
|
||||
location = line.split(":", 1)[1].strip()
|
||||
elif line.lower().startswith("server:"):
|
||||
server = line.split(":", 1)[1].strip()
|
||||
print(location)
|
||||
print(server)
|
||||
sys.exit(0)
|
||||
except socket.timeout:
|
||||
break
|
||||
PYEOF
|
||||
)
|
||||
SSDP_LOCATION=$(echo "$SSDP_RAW" | sed -n '1p')
|
||||
SSDP_SERVER=$(echo "$SSDP_RAW" | sed -n '2p')
|
||||
|
||||
SSDP_FRIENDLY="" SSDP_MANUFACTURER="" SSDP_MODEL=""
|
||||
if [[ -n "$SSDP_LOCATION" ]]; then
|
||||
@@ -85,11 +96,11 @@ for p in "${open_ports[@]}"; do
|
||||
http_titles+=("$p|$title|$server")
|
||||
done
|
||||
|
||||
python3 - "$IP" "$MDNS" "$RDNS" "$SSDP_LOCATION" "$SSDP_FRIENDLY" "$SSDP_MANUFACTURER" "$SSDP_MODEL" \
|
||||
python3 - "$IP" "$MDNS" "$RDNS" "$SSDP_LOCATION" "$SSDP_SERVER" "$SSDP_FRIENDLY" "$SSDP_MANUFACTURER" "$SSDP_MODEL" \
|
||||
"$(IFS=,; echo "${open_ports[*]}")" "$(printf '%s\n' "${http_titles[@]}")" <<'PYEOF'
|
||||
import json, sys
|
||||
|
||||
ip, mdns, rdns, ssdp_loc, ssdp_friendly, ssdp_mfr, ssdp_model, ports_csv, http_raw = sys.argv[1:10]
|
||||
ip, mdns, rdns, ssdp_loc, ssdp_server, ssdp_friendly, ssdp_mfr, ssdp_model, ports_csv, http_raw = sys.argv[1:11]
|
||||
|
||||
http = []
|
||||
for line in http_raw.splitlines():
|
||||
@@ -108,6 +119,7 @@ result = {
|
||||
"reverseDns": rdns or None,
|
||||
"ssdp": {
|
||||
"location": ssdp_loc or None,
|
||||
"server": ssdp_server or None,
|
||||
"friendlyName": ssdp_friendly or None,
|
||||
"manufacturer": ssdp_mfr or None,
|
||||
"modelName": ssdp_model or None,
|
||||
|
||||
Reference in New Issue
Block a user