From 4d6c2fd878db74f65a22657a835e806d6252dcd5 Mon Sep 17 00:00:00 2001 From: eduard256 Date: Mon, 16 Mar 2026 13:57:41 +0000 Subject: [PATCH] Add GET /api/v1/probe endpoint for device inspection Fast (~1-3s) endpoint that gathers network info about a device before full stream discovery. Runs ping first, then parallel probes. Features: - Ping with ICMP + TCP fallback (works without root) - Reverse DNS hostname lookup - ARP table MAC address + OUI vendor identification (2403 entries, 51 camera vendors) - mDNS HomeKit detection (camera/doorbell, paired status) - Extensible Prober interface for adding new probe types - 3-second overall timeout, parallel execution Response includes "type" field: - "unreachable" - device not responding - "standard" - normal IP camera (RTSP/HTTP/ONVIF flow) - "homekit" - Apple HomeKit camera (PIN pairing flow) --- data/camera_oui.json | 2405 ++++++++++++++++++++++ go.mod | 19 +- go.sum | 46 +- internal/api/handlers/probe.go | 82 + internal/api/routes.go | 21 + internal/camera/discovery/oui.go | 76 + internal/camera/discovery/probe.go | 180 ++ internal/camera/discovery/prober_arp.go | 80 + internal/camera/discovery/prober_dns.go | 36 + internal/camera/discovery/prober_mdns.go | 57 + internal/camera/discovery/prober_ping.go | 127 ++ internal/config/config.go | 2 + internal/models/probe.go | 45 + 13 files changed, 3164 insertions(+), 12 deletions(-) create mode 100644 data/camera_oui.json create mode 100644 internal/api/handlers/probe.go create mode 100644 internal/camera/discovery/oui.go create mode 100644 internal/camera/discovery/probe.go create mode 100644 internal/camera/discovery/prober_arp.go create mode 100644 internal/camera/discovery/prober_dns.go create mode 100644 internal/camera/discovery/prober_mdns.go create mode 100644 internal/camera/discovery/prober_ping.go create mode 100644 internal/models/probe.go diff --git a/data/camera_oui.json b/data/camera_oui.json new file mode 100644 index 0000000..a72f1cb --- /dev/null +++ b/data/camera_oui.json @@ -0,0 +1,2405 @@ +{ + "00:00:95": "Sony", + "00:00:F0": "Samsung", + "00:01:31": "Bosch", + "00:01:4A": "Sony", + "00:02:D1": "Vivotek", + "00:03:22": "IDIS", + "00:03:C5": "Mobotix", + "00:04:1F": "Sony", + "00:04:63": "Bosch", + "00:05:5D": "D-Link", + "00:06:4A": "Honeywell", + "00:06:68": "Vicon", + "00:07:AB": "Samsung", + "00:09:18": "Hanwha", + "00:09:5B": "Netgear", + "00:0A:13": "Honeywell", + "00:0A:D9": "Sony", + "00:0A:EB": "TP-Link", + "00:0B:7F": "Ring", + "00:0B:88": "IDIS", + "00:0C:80": "Pelco", + "00:0D:5C": "Bosch", + "00:0D:88": "D-Link", + "00:0E:07": "Sony", + "00:0F:12": "Panasonic", + "00:0F:3D": "D-Link", + "00:0F:7C": "ACTi", + "00:0F:B5": "Netgear", + "00:0F:DE": "Sony", + "00:10:BE": "March Networks", + "00:11:12": "Honeywell", + "00:11:14": "EverFocus", + "00:11:95": "D-Link", + "00:12:47": "Samsung", + "00:12:67": "Panasonic", + "00:12:81": "March Networks", + "00:12:EE": "Sony", + "00:12:FB": "Samsung", + "00:13:15": "Sony", + "00:13:46": "D-Link", + "00:13:56": "FLIR", + "00:13:77": "Samsung", + "00:13:A9": "Sony", + "00:13:E2": "Geovision", + "00:14:6C": "Netgear", + "00:14:78": "TP-Link", + "00:15:6D": "Ubiquiti", + "00:15:99": "Samsung", + "00:15:B9": "Samsung", + "00:15:C1": "Sony", + "00:15:E9": "D-Link", + "00:16:20": "Sony", + "00:16:32": "Samsung", + "00:16:6B": "Samsung", + "00:16:6C": "Samsung", + "00:16:B8": "Sony", + "00:16:DB": "Samsung", + "00:17:9A": "D-Link", + "00:17:C9": "Samsung", + "00:17:D5": "Samsung", + "00:18:13": "Sony", + "00:18:4D": "Netgear", + "00:18:AE": "TVT", + "00:18:AF": "Samsung", + "00:19:5B": "D-Link", + "00:19:63": "Sony", + "00:19:87": "Panasonic", + "00:19:C5": "Sony", + "00:19:E0": "TP-Link", + "00:19:EE": "Arlo", + "00:1A:11": "Nest", + "00:1A:75": "Sony", + "00:1A:80": "Sony", + "00:1A:8A": "Samsung", + "00:1B:11": "D-Link", + "00:1B:2F": "Netgear", + "00:1B:59": "Sony", + "00:1B:98": "Samsung", + "00:1B:D3": "Panasonic", + "00:1B:D8": "FLIR", + "00:1C:43": "Samsung", + "00:1C:A4": "Sony", + "00:1C:B8": "Ganz", + "00:1C:F0": "D-Link", + "00:1D:0D": "Sony", + "00:1D:0F": "TP-Link", + "00:1D:25": "Samsung", + "00:1D:28": "Sony", + "00:1D:BA": "Sony", + "00:1D:F6": "Samsung", + "00:1E:1E": "Honeywell", + "00:1E:2A": "Netgear", + "00:1E:45": "Sony", + "00:1E:58": "D-Link", + "00:1E:7D": "Samsung", + "00:1E:DC": "Sony", + "00:1E:E1": "Samsung", + "00:1E:E2": "Samsung", + "00:1F:33": "Netgear", + "00:1F:54": "Lorex", + "00:1F:55": "Honeywell", + "00:1F:A7": "Sony", + "00:1F:CC": "Samsung", + "00:1F:CD": "Samsung", + "00:1F:E4": "Sony", + "00:20:04": "Honeywell", + "00:20:3D": "Honeywell", + "00:20:D9": "Panasonic", + "00:21:27": "TP-Link", + "00:21:4C": "Samsung", + "00:21:91": "D-Link", + "00:21:9E": "Sony", + "00:21:D1": "Samsung", + "00:21:D2": "Samsung", + "00:22:3F": "Netgear", + "00:22:6A": "Honeywell", + "00:22:98": "Sony", + "00:22:A6": "Sony", + "00:22:B0": "D-Link", + "00:23:39": "Samsung", + "00:23:3A": "Samsung", + "00:23:45": "Sony", + "00:23:63": "Sannce", + "00:23:99": "Samsung", + "00:23:B6": "Honeywell", + "00:23:C2": "Samsung", + "00:23:CD": "TP-Link", + "00:23:D6": "Samsung", + "00:23:D7": "Samsung", + "00:23:F1": "Sony", + "00:24:01": "D-Link", + "00:24:54": "Samsung", + "00:24:8D": "Sony", + "00:24:90": "Samsung", + "00:24:91": "Samsung", + "00:24:B2": "Netgear", + "00:24:BE": "Sony", + "00:24:E9": "Samsung", + "00:24:EF": "Sony", + "00:25:38": "Samsung", + "00:25:66": "Samsung", + "00:25:67": "Samsung", + "00:25:86": "TP-Link", + "00:25:E7": "Sony", + "00:26:5A": "D-Link", + "00:26:5D": "Samsung", + "00:26:5F": "Samsung", + "00:26:F2": "Netgear", + "00:27:19": "TP-Link", + "00:27:22": "Ubiquiti", + "00:2A:10": "Zmodo", + "00:2B:70": "Samsung", + "00:30:AF": "Honeywell", + "00:31:92": "TP-Link", + "00:40:48": "Amcrest", + "00:40:7F": "FLIR", + "00:40:84": "Honeywell", + "00:40:8C": "Axis", + "00:50:40": "Panasonic", + "00:50:A1": "Arlo", + "00:50:BA": "D-Link", + "00:5F:67": "TP-Link", + "00:60:34": "Bosch", + "00:62:6E": "Reolink", + "00:65:1E": "Amcrest", + "00:6F:64": "Samsung", + "00:71:47": "Ring", + "00:72:04": "Samsung", + "00:73:E0": "Samsung", + "00:7C:2D": "Samsung", + "00:7D:3B": "Samsung", + "00:80:A7": "Honeywell", + "00:80:C8": "D-Link", + "00:80:F0": "Panasonic", + "00:86:21": "Ring", + "00:87:01": "Samsung", + "00:8E:F2": "Netgear", + "00:90:30": "Honeywell", + "00:90:32": "Pelco", + "00:9E:C8": "Xiaomi", + "00:AD:24": "D-Link", + "00:B4:63": "Ring", + "00:B5:D0": "Samsung", + "00:BB:3A": "Ring", + "00:BC:99": "Hikvision", + "00:BF:61": "Samsung", + "00:C0:8F": "Panasonic", + "00:C3:0A": "Xiaomi", + "00:C3:F4": "Samsung", + "00:D0:60": "Panasonic", + "00:D9:D1": "Sony", + "00:E0:64": "Samsung", + "00:E0:F2": "Arlo", + "00:E3:B2": "Samsung", + "00:E4:21": "Sony", + "00:EB:2D": "Sony", + "00:EC:0A": "Xiaomi", + "00:F3:61": "Ring", + "00:F4:6F": "Samsung", + "00:F6:20": "Nest", + "00:FA:21": "Samsung", + "00:FC:8B": "Ring", + "04:00:6E": "Nest", + "04:03:12": "Hikvision", + "04:04:B8": "Panasonic", + "04:0F:66": "TP-Link", + "04:10:6B": "Xiaomi", + "04:18:0F": "Samsung", + "04:18:D6": "Ubiquiti", + "04:1B:BA": "Samsung", + "04:20:9A": "Panasonic", + "04:29:2E": "Samsung", + "04:5C:06": "Zmodo", + "04:5D:4B": "Sony", + "04:67:61": "Xiaomi", + "04:7A:0B": "Xiaomi", + "04:A1:51": "Netgear", + "04:AE:47": "Xiaomi", + "04:B1:67": "Xiaomi", + "04:B1:A1": "Samsung", + "04:B4:29": "Samsung", + "04:B9:E3": "Samsung", + "04:BA:8D": "Samsung", + "04:BA:D6": "D-Link", + "04:BD:BF": "Samsung", + "04:C8:07": "Xiaomi", + "04:C8:45": "TP-Link", + "04:C8:B0": "Nest", + "04:CB:01": "Samsung", + "04:CF:8C": "Xiaomi", + "04:D1:3A": "Xiaomi", + "04:E4:B6": "Samsung", + "04:E5:98": "Xiaomi", + "04:EE:CD": "Hikvision", + "04:F7:78": "Sony", + "04:F9:F8": "TP-Link", + "04:FE:31": "Samsung", + "08:00:23": "Panasonic", + "08:00:46": "Sony", + "08:02:3C": "Samsung", + "08:02:8E": "Netgear", + "08:08:C2": "Samsung", + "08:10:93": "Samsung", + "08:12:A5": "Ring", + "08:15:2F": "Samsung", + "08:1C:6E": "Xiaomi", + "08:1F:71": "TP-Link", + "08:21:EF": "Samsung", + "08:25:25": "Xiaomi", + "08:36:C9": "Netgear", + "08:37:3D": "Samsung", + "08:3B:C1": "Hikvision", + "08:3D:88": "Samsung", + "08:4B:44": "Bosch", + "08:54:11": "Hikvision", + "08:57:00": "TP-Link", + "08:57:FB": "Ring", + "08:5A:11": "D-Link", + "08:6A:E5": "Ring", + "08:78:08": "Samsung", + "08:7C:39": "Ring", + "08:84:9D": "Ring", + "08:8B:C8": "Nest", + "08:8C:2C": "Samsung", + "08:91:15": "Ring", + "08:91:A3": "Ring", + "08:9E:08": "Nest", + "08:A1:89": "Hikvision", + "08:A5:DF": "Samsung", + "08:A6:BC": "Ring", + "08:AE:D6": "Samsung", + "08:B3:39": "Xiaomi", + "08:B4:B1": "Nest", + "08:BD:43": "Netgear", + "08:BF:A0": "Samsung", + "08:C2:24": "Ring", + "08:CC:81": "Hikvision", + "08:D4:2B": "Samsung", + "08:EC:A9": "Samsung", + "08:ED:ED": "Dahua", + "08:EE:8B": "Samsung", + "08:FC:88": "Samsung", + "08:FD:0E": "Samsung", + "0C:02:BD": "Samsung", + "0C:07:DF": "Xiaomi", + "0C:0E:76": "D-Link", + "0C:14:20": "Samsung", + "0C:1D:AF": "Xiaomi", + "0C:23:69": "Honeywell", + "0C:2F:B0": "Samsung", + "0C:32:3A": "Samsung", + "0C:43:F9": "Ring", + "0C:47:C9": "Ring", + "0C:4B:54": "TP-Link", + "0C:65:9A": "Panasonic", + "0C:70:43": "Sony", + "0C:71:5D": "Samsung", + "0C:72:2C": "TP-Link", + "0C:75:D2": "Hikvision", + "0C:80:63": "TP-Link", + "0C:82:68": "TP-Link", + "0C:89:10": "Samsung", + "0C:8D:CA": "Samsung", + "0C:98:38": "Xiaomi", + "0C:A8:A7": "Samsung", + "0C:B3:19": "Samsung", + "0C:B6:D2": "D-Link", + "0C:C4:13": "Nest", + "0C:C6:FD": "Xiaomi", + "0C:DC:91": "Ring", + "0C:DF:A4": "Samsung", + "0C:E0:DC": "Samsung", + "0C:EA:14": "Ubiquiti", + "0C:ED:C8": "Xiaomi", + "0C:EE:99": "Ring", + "0C:EF:15": "TP-Link", + "0C:F3:46": "Xiaomi", + "0C:FE:45": "Sony", + "10:07:B6": "Samsung", + "10:09:F9": "Ring", + "10:0C:6B": "Netgear", + "10:0D:7F": "Netgear", + "10:12:FB": "Hikvision", + "10:1D:C0": "Samsung", + "10:27:F5": "TP-Link", + "10:29:AB": "Samsung", + "10:2A:B3": "Xiaomi", + "10:2B:41": "Samsung", + "10:30:47": "Samsung", + "10:39:17": "Samsung", + "10:3B:59": "Samsung", + "10:3F:44": "Xiaomi", + "10:4F:A8": "Sony", + "10:5A:95": "TP-Link", + "10:62:EB": "D-Link", + "10:66:50": "Bosch", + "10:77:B1": "Samsung", + "10:89:FB": "Samsung", + "10:8E:E0": "Samsung", + "10:92:66": "Samsung", + "10:96:93": "Ring", + "10:AB:C9": "Samsung", + "10:AE:60": "Ring", + "10:BD:43": "Bosch", + "10:BE:F5": "D-Link", + "10:BF:67": "Ring", + "10:C1:97": "Xiaomi", + "10:CE:02": "Ring", + "10:D3:8A": "Samsung", + "10:D5:42": "Samsung", + "10:D9:A2": "Nest", + "10:DA:43": "Netgear", + "10:E4:C2": "Samsung", + "10:EC:81": "Samsung", + "10:FE:ED": "TP-Link", + "14:01:52": "Samsung", + "14:07:08": "CP Plus", + "14:0A:C5": "Ring", + "14:0B:9E": "Samsung", + "14:1F:78": "Samsung", + "14:22:3B": "Nest", + "14:32:D1": "Samsung", + "14:3F:A6": "Sony", + "14:41:46": "Honeywell", + "14:49:D4": "Xiaomi", + "14:56:8E": "Samsung", + "14:59:C0": "Netgear", + "14:75:90": "TP-Link", + "14:86:92": "TP-Link", + "14:89:FD": "Samsung", + "14:90:7A": "Xiaomi", + "14:91:38": "Ring", + "14:96:E5": "Samsung", + "14:99:3E": "Xiaomi", + "14:9F:3C": "Samsung", + "14:A3:64": "Samsung", + "14:A7:8B": "Dahua", + "14:B4:84": "Samsung", + "14:BB:6E": "Samsung", + "14:C1:4E": "Nest", + "14:CC:20": "TP-Link", + "14:CF:92": "TP-Link", + "14:D6:4D": "D-Link", + "14:D8:64": "TP-Link", + "14:D8:81": "Xiaomi", + "14:E0:1D": "Samsung", + "14:E6:E4": "TP-Link", + "14:EB:B6": "TP-Link", + "14:F4:2A": "Samsung", + "14:F6:5A": "Xiaomi", + "18:00:2D": "Sony", + "18:01:F1": "Xiaomi", + "18:0B:1B": "Ring", + "18:0F:76": "D-Link", + "18:16:C9": "Samsung", + "18:19:D6": "Samsung", + "18:1E:B0": "Samsung", + "18:21:95": "Samsung", + "18:22:7E": "Samsung", + "18:26:54": "Samsung", + "18:26:66": "Samsung", + "18:3A:2D": "Samsung", + "18:3F:47": "Samsung", + "18:46:17": "Samsung", + "18:48:BE": "Ring", + "18:4E:16": "Samsung", + "18:4E:CB": "Samsung", + "18:54:CF": "Samsung", + "18:59:36": "Xiaomi", + "18:5B:B3": "Samsung", + "18:67:B0": "Samsung", + "18:68:CB": "Hikvision", + "18:69:45": "TP-Link", + "18:69:D4": "Samsung", + "18:74:2E": "Ring", + "18:7F:88": "Ring", + "18:80:25": "Hikvision", + "18:83:31": "Samsung", + "18:87:40": "Xiaomi", + "18:89:5B": "Samsung", + "18:A6:F7": "TP-Link", + "18:AB:1D": "Samsung", + "18:B4:30": "Nest", + "18:BF:B3": "Samsung", + "18:C2:3C": "Aqara", + "18:CE:94": "Samsung", + "18:D6:C7": "TP-Link", + "18:E2:C2": "Samsung", + "18:E8:29": "Ubiquiti", + "18:F0:E4": "Xiaomi", + "18:F2:2C": "TP-Link", + "1C:0B:8B": "Ubiquiti", + "1C:12:B0": "Ring", + "1C:23:2C": "Samsung", + "1C:2A:B0": "Xiaomi", + "1C:3A:DE": "Samsung", + "1C:3B:F3": "TP-Link", + "1C:44:19": "TP-Link", + "1C:4D:66": "Ring", + "1C:53:F9": "Nest", + "1C:5A:3E": "Samsung", + "1C:5F:2B": "D-Link", + "1C:61:B4": "TP-Link", + "1C:62:B8": "Samsung", + "1C:66:AA": "Samsung", + "1C:6A:1B": "Ubiquiti", + "1C:76:F2": "Samsung", + "1C:7B:21": "Sony", + "1C:7E:E5": "D-Link", + "1C:86:9A": "Samsung", + "1C:8B:EF": "Xiaomi", + "1C:93:C4": "Ring", + "1C:AF:05": "Samsung", + "1C:AF:4A": "Samsung", + "1C:AF:F7": "D-Link", + "1C:BD:B9": "D-Link", + "1C:C3:16": "Milesight", + "1C:CC:D6": "Xiaomi", + "1C:E5:7F": "Samsung", + "1C:E6:1D": "Samsung", + "1C:EA:AC": "Xiaomi", + "1C:F2:9A": "Nest", + "1C:F8:D0": "Samsung", + "1C:FA:68": "TP-Link", + "1C:FE:2B": "Ring", + "20:0C:C8": "Netgear", + "20:0E:0F": "Panasonic", + "20:10:B1": "Ring", + "20:13:E0": "Samsung", + "20:15:DE": "Samsung", + "20:1F:3B": "Nest", + "20:23:51": "TP-Link", + "20:25:CC": "Xiaomi", + "20:2D:07": "Samsung", + "20:32:6C": "Samsung", + "20:33:89": "Nest", + "20:34:62": "Xiaomi", + "20:34:FB": "Xiaomi", + "20:36:26": "TP-Link", + "20:3B:34": "Xiaomi", + "20:3B:67": "Samsung", + "20:47:DA": "Xiaomi", + "20:4E:7F": "Netgear", + "20:54:76": "Sony", + "20:55:31": "Samsung", + "20:5E:F7": "Samsung", + "20:6B:E7": "TP-Link", + "20:6E:9C": "Samsung", + "20:72:A9": "Xiaomi", + "20:82:C0": "Xiaomi", + "20:99:52": "Xiaomi", + "20:A1:71": "Ring", + "20:A6:0C": "Xiaomi", + "20:BE:B8": "Ring", + "20:C6:EB": "Panasonic", + "20:D3:90": "Samsung", + "20:D5:BF": "Samsung", + "20:DB:AB": "Samsung", + "20:DC:E6": "TP-Link", + "20:DE:88": "IC Realtime", + "20:DF:B9": "Nest", + "20:E1:5D": "TP-Link", + "20:E5:2A": "Netgear", + "20:E5:9B": "Panasonic", + "20:F0:94": "Nest", + "20:F4:78": "Xiaomi", + "20:FE:00": "Ring", + "24:05:88": "Nest", + "24:09:35": "Samsung", + "24:0A:3F": "Samsung", + "24:0F:9B": "Hikvision", + "24:11:45": "Xiaomi", + "24:11:53": "Samsung", + "24:21:AB": "Sony", + "24:24:B7": "Samsung", + "24:28:FD": "Hikvision", + "24:29:34": "Nest", + "24:2B:D6": "Ring", + "24:2F:D0": "TP-Link", + "24:32:AE": "Hikvision", + "24:48:45": "Hikvision", + "24:4B:03": "Samsung", + "24:4B:81": "Samsung", + "24:4C:E3": "Ring", + "24:52:6A": "Dahua", + "24:5A:4C": "Ubiquiti", + "24:5A:5F": "TP-Link", + "24:5A:B5": "Samsung", + "24:60:B3": "Samsung", + "24:64:77": "Xiaomi", + "24:68:B0": "Samsung", + "24:69:68": "TP-Link", + "24:78:23": "Panasonic", + "24:92:0E": "Samsung", + "24:95:2F": "Nest", + "24:A4:3C": "Ubiquiti", + "24:A4:52": "Samsung", + "24:A8:7D": "Panasonic", + "24:B1:05": "Hikvision", + "24:C6:13": "Samsung", + "24:C6:96": "Samsung", + "24:CE:33": "Ring", + "24:CF:24": "Xiaomi", + "24:D3:37": "Xiaomi", + "24:DB:ED": "Samsung", + "24:E1:24": "Milesight", + "24:E5:0F": "Nest", + "24:F0:D3": "Samsung", + "24:F4:0A": "Samsung", + "24:F5:AA": "Samsung", + "24:FC:E5": "Samsung", + "28:02:D8": "Samsung", + "28:07:08": "Samsung", + "28:0D:FC": "Sony", + "28:10:7B": "D-Link", + "28:16:7F": "Xiaomi", + "28:18:FD": "CP Plus", + "28:24:C9": "Ring", + "28:27:BF": "Samsung", + "28:2C:B2": "TP-Link", + "28:39:5E": "Samsung", + "28:3B:82": "D-Link", + "28:3D:C2": "Samsung", + "28:3F:69": "Sony", + "28:40:DD": "Sony", + "28:57:BE": "Hikvision", + "28:59:23": "Xiaomi", + "28:6C:07": "Xiaomi", + "28:6D:97": "Aqara", + "28:70:4E": "Ubiquiti", + "28:73:F6": "Ring", + "28:80:88": "Netgear", + "28:83:35": "Samsung", + "28:87:BA": "TP-Link", + "28:94:01": "Netgear", + "28:98:7B": "Samsung", + "28:9F:04": "Samsung", + "28:A1:86": "Blink", + "28:AF:42": "Samsung", + "28:BA:B5": "Samsung", + "28:BD:89": "Nest", + "28:C6:8E": "Netgear", + "28:CC:01": "Samsung", + "28:D1:27": "Xiaomi", + "28:DE:1C": "Samsung", + "28:E3:1F": "Xiaomi", + "28:E6:A9": "Samsung", + "28:EE:52": "TP-Link", + "28:EF:01": "Ring", + "2C:0B:97": "Xiaomi", + "2C:0D:CF": "Xiaomi", + "2C:15:BF": "Samsung", + "2C:19:5C": "Xiaomi", + "2C:30:33": "Netgear", + "2C:40:53": "Samsung", + "2C:44:01": "Samsung", + "2C:71:FF": "Ring", + "2C:79:BE": "TP-Link", + "2C:97:ED": "Sony", + "2C:99:75": "Samsung", + "2C:9E:00": "Sony", + "2C:A5:9C": "Hikvision", + "2C:AA:8E": "Wyze", + "2C:AE:2B": "Samsung", + "2C:B0:5D": "Netgear", + "2C:BA:BA": "Samsung", + "2C:CA:75": "Bosch", + "2C:CC:44": "Sony", + "2C:D0:66": "Xiaomi", + "2C:DA:46": "Samsung", + "2C:FE:4F": "Xiaomi", + "30:17:C8": "Sony", + "30:19:66": "Samsung", + "30:39:26": "Sony", + "30:46:9A": "Netgear", + "30:4C:7E": "Panasonic", + "30:4D:1F": "Ring", + "30:50:CE": "Xiaomi", + "30:68:93": "TP-Link", + "30:6A:85": "Samsung", + "30:74:67": "Samsung", + "30:75:12": "Sony", + "30:96:FB": "Samsung", + "30:A8:DB": "Sony", + "30:B4:9E": "TP-Link", + "30:B5:C2": "TP-Link", + "30:C7:AE": "Samsung", + "30:C9:CC": "Samsung", + "30:CB:F8": "Samsung", + "30:CD:A7": "Samsung", + "30:D5:87": "Samsung", + "30:D6:C9": "Samsung", + "30:DD:AA": "Dahua", + "30:DE:4B": "TP-Link", + "30:E0:44": "Nest", + "30:F9:ED": "Sony", + "30:FC:68": "TP-Link", + "30:FD:38": "Nest", + "34:02:9C": "D-Link", + "34:08:04": "D-Link", + "34:09:62": "Hikvision", + "34:0A:33": "D-Link", + "34:14:5F": "Samsung", + "34:1C:F0": "Xiaomi", + "34:25:BE": "Ring", + "34:2D:0D": "Samsung", + "34:31:11": "Samsung", + "34:31:7F": "Panasonic", + "34:32:E6": "Panasonic", + "34:39:16": "Nest", + "34:3E:A4": "Ring", + "34:60:F9": "TP-Link", + "34:80:B3": "Xiaomi", + "34:82:C5": "Samsung", + "34:8A:7B": "Samsung", + "34:96:72": "TP-Link", + "34:98:B5": "Netgear", + "34:AA:8B": "Samsung", + "34:AF:B3": "Ring", + "34:B9:8D": "Xiaomi", + "34:BE:00": "Samsung", + "34:C3:AC": "Samsung", + "34:C7:E9": "Nest", + "34:CE:00": "Xiaomi", + "34:D2:70": "Ring", + "34:E3:FB": "Samsung", + "34:E8:94": "TP-Link", + "34:F0:15": "Xiaomi", + "34:F0:43": "Samsung", + "34:F6:D2": "Panasonic", + "34:F7:16": "TP-Link", + "34:FA:1C": "Xiaomi", + "38:01:95": "Samsung", + "38:0A:94": "Samsung", + "38:0B:40": "Samsung", + "38:16:D1": "Samsung", + "38:18:4C": "Sony", + "38:2D:D1": "Samsung", + "38:2D:E8": "Samsung", + "38:4A:80": "Samsung", + "38:68:A4": "Samsung", + "38:6A:77": "Samsung", + "38:78:62": "Sony", + "38:83:45": "TP-Link", + "38:86:F7": "Nest", + "38:8A:06": "Samsung", + "38:8B:59": "Nest", + "38:8C:EF": "Samsung", + "38:8F:30": "Samsung", + "38:94:96": "Samsung", + "38:94:ED": "Netgear", + "38:9A:F6": "Samsung", + "38:A4:ED": "Xiaomi", + "38:AF:29": "Dahua", + "38:C6:BD": "Xiaomi", + "38:D4:0B": "Samsung", + "38:E6:0A": "Xiaomi", + "38:EC:E4": "Samsung", + "38:F7:3D": "Ring", + "3C:01:EF": "Sony", + "3C:05:18": "Samsung", + "3C:06:A7": "TP-Link", + "3C:07:71": "Sony", + "3C:0A:7A": "Samsung", + "3C:13:5A": "Xiaomi", + "3C:19:5E": "Samsung", + "3C:1B:F8": "Hikvision", + "3C:1E:04": "D-Link", + "3C:20:F6": "Samsung", + "3C:28:6D": "Nest", + "3C:2C:A6": "Xiaomi", + "3C:31:74": "Nest", + "3C:31:8A": "Samsung", + "3C:33:32": "D-Link", + "3C:37:86": "Netgear", + "3C:38:24": "Xiaomi", + "3C:38:F4": "Sony", + "3C:46:D8": "TP-Link", + "3C:52:A1": "TP-Link", + "3C:57:6C": "Samsung", + "3C:5A:37": "Samsung", + "3C:5A:B4": "Nest", + "3C:5C:C4": "Ring", + "3C:62:00": "Samsung", + "3C:64:CF": "TP-Link", + "3C:6A:48": "TP-Link", + "3C:6A:D2": "TP-Link", + "3C:6F:EA": "Panasonic", + "3C:78:95": "TP-Link", + "3C:7F:6E": "Xiaomi", + "3C:84:6A": "TP-Link", + "3C:8B:FE": "Samsung", + "3C:8D:20": "Nest", + "3C:A0:70": "Blink", + "3C:A1:0D": "Samsung", + "3C:AF:B7": "Xiaomi", + "3C:BB:FD": "Samsung", + "3C:BD:3E": "Xiaomi", + "3C:CD:57": "Xiaomi", + "3C:DA:6D": "Tiandy", + "3C:DC:BC": "Samsung", + "3C:E3:6B": "Dahua", + "3C:E4:41": "Ring", + "3C:EF:8C": "Dahua", + "3C:F7:A4": "Samsung", + "40:08:77": "Xiaomi", + "40:11:C3": "Samsung", + "40:16:3B": "Samsung", + "40:16:9F": "TP-Link", + "40:2B:A1": "Sony", + "40:31:3C": "Xiaomi", + "40:35:E6": "Samsung", + "40:3F:8C": "TP-Link", + "40:40:A7": "Sony", + "40:5D:82": "Netgear", + "40:5E:F6": "Samsung", + "40:7A:A4": "Dahua", + "40:86:CB": "D-Link", + "40:89:C6": "Ring", + "40:95:95": "TP-Link", + "40:9B:CD": "D-Link", + "40:A2:DB": "Ring", + "40:A4:4A": "Nest", + "40:A9:CF": "Ring", + "40:AC:BF": "Hikvision", + "40:AE:30": "TP-Link", + "40:B4:CD": "Ring", + "40:B8:37": "Sony", + "40:D3:AE": "Samsung", + "40:DE:24": "Samsung", + "40:ED:00": "TP-Link", + "40:F6:BC": "Ring", + "44:00:49": "Ring", + "44:07:0B": "Nest", + "44:0C:EE": "Bosch", + "44:10:30": "Nest", + "44:16:FA": "Samsung", + "44:19:B6": "Hikvision", + "44:23:7C": "Xiaomi", + "44:36:59": "Bosch", + "44:3D:54": "Ring", + "44:42:01": "Ring", + "44:47:CC": "Hikvision", + "44:4A:37": "Xiaomi", + "44:4E:1A": "Samsung", + "44:5C:E9": "Samsung", + "44:65:0D": "Ring", + "44:66:90": "TP-Link", + "44:6D:6C": "Samsung", + "44:6D:7F": "Ring", + "44:71:47": "Xiaomi", + "44:74:6C": "Sony", + "44:78:3E": "Samsung", + "44:8F:17": "Samsung", + "44:94:FC": "Netgear", + "44:A5:6E": "Netgear", + "44:A6:42": "Hikvision", + "44:B3:2D": "TP-Link", + "44:B4:23": "Hanwha", + "44:B4:B2": "Ring", + "44:BB:3B": "Nest", + "44:BD:C8": "Xiaomi", + "44:C6:3C": "Samsung", + "44:CB:AD": "Xiaomi", + "44:D4:E0": "Sony", + "44:D5:CC": "Ring", + "44:D7:7E": "Bosch", + "44:D9:E7": "Ubiquiti", + "44:DF:65": "Xiaomi", + "44:EA:30": "Samsung", + "44:F4:59": "Samsung", + "44:F7:70": "Xiaomi", + "48:0E:EC": "TP-Link", + "48:13:7E": "Samsung", + "48:22:54": "TP-Link", + "48:27:EA": "Samsung", + "48:2C:A0": "Xiaomi", + "48:31:33": "Bosch", + "48:43:DD": "Ring", + "48:44:F7": "Samsung", + "48:49:C7": "Samsung", + "48:51:69": "Samsung", + "48:5F:08": "TP-Link", + "48:5F:2D": "Ring", + "48:61:EE": "Samsung", + "48:62:64": "Arlo", + "48:78:5B": "Hikvision", + "48:78:5E": "Ring", + "48:79:4D": "Samsung", + "48:7D:2E": "TP-Link", + "48:87:59": "Xiaomi", + "48:9D:D1": "Samsung", + "48:B4:23": "Ring", + "48:BC:E1": "Samsung", + "48:C3:81": "TP-Link", + "48:C7:96": "Samsung", + "48:D6:D5": "Nest", + "48:EA:63": "Uniview", + "48:EE:0C": "D-Link", + "48:EF:1C": "Samsung", + "48:FD:A3": "Xiaomi", + "4C:02:20": "Xiaomi", + "4C:10:D5": "TP-Link", + "4C:11:BF": "Dahua", + "4C:17:44": "Ring", + "4C:1F:86": "Hikvision", + "4C:21:8C": "Panasonic", + "4C:21:D0": "Sony", + "4C:2E:5E": "Samsung", + "4C:36:4E": "Panasonic", + "4C:39:46": "Samsung", + "4C:3C:16": "Samsung", + "4C:49:E3": "Xiaomi", + "4C:53:FD": "Ring", + "4C:55:B2": "Xiaomi", + "4C:57:39": "Samsung", + "4C:60:AD": "Ring", + "4C:60:DE": "Netgear", + "4C:62:DF": "Hikvision", + "4C:63:71": "Xiaomi", + "4C:66:A6": "Samsung", + "4C:8E:19": "Xiaomi", + "4C:99:E8": "Dahua", + "4C:A5:6D": "Samsung", + "4C:BC:A5": "Samsung", + "4C:BD:8F": "Hikvision", + "4C:C6:4C": "Xiaomi", + "4C:C9:5E": "Samsung", + "4C:DD:31": "Samsung", + "4C:E0:DB": "Xiaomi", + "4C:E2:0F": "Xiaomi", + "4C:EB:B0": "Samsung", + "4C:EF:C0": "Ring", + "4C:F2:02": "Xiaomi", + "4C:F5:DC": "Hikvision", + "50:01:BB": "Samsung", + "50:07:C3": "Ring", + "50:32:75": "Samsung", + "50:3D:A1": "Samsung", + "50:3D:C6": "Xiaomi", + "50:3D:D1": "TP-Link", + "50:3E:AA": "TP-Link", + "50:49:B0": "Samsung", + "50:4A:6E": "Netgear", + "50:4F:3B": "Xiaomi", + "50:50:A4": "Samsung", + "50:56:BF": "Samsung", + "50:64:2B": "Xiaomi", + "50:6A:03": "Netgear", + "50:77:05": "Samsung", + "50:85:69": "Samsung", + "50:88:11": "Xiaomi", + "50:8E:49": "Xiaomi", + "50:8F:4C": "Xiaomi", + "50:91:E3": "TP-Link", + "50:92:6A": "Xiaomi", + "50:92:B9": "Samsung", + "50:98:39": "Xiaomi", + "50:99:5A": "Ring", + "50:9E:A7": "Samsung", + "50:A0:09": "Xiaomi", + "50:A4:C8": "Samsung", + "50:B0:3B": "Sony", + "50:B3:63": "Ring", + "50:B7:C3": "Samsung", + "50:BD:5F": "TP-Link", + "50:C7:BF": "TP-Link", + "50:C8:E5": "Samsung", + "50:D2:F5": "Xiaomi", + "50:D4:5C": "Ring", + "50:D4:F7": "TP-Link", + "50:DA:D6": "Xiaomi", + "50:DC:E7": "Ring", + "50:E5:38": "Hikvision", + "50:EC:50": "Xiaomi", + "50:F0:D3": "Samsung", + "50:F5:20": "Samsung", + "50:F5:DA": "Ring", + "50:FA:84": "TP-Link", + "50:FC:9F": "Samsung", + "50:FE:39": "Xiaomi", + "54:07:7D": "Netgear", + "54:10:4F": "Samsung", + "54:21:9D": "Samsung", + "54:26:3D": "Sony", + "54:2B:1C": "Ring", + "54:2B:57": "Night Owl", + "54:3A:D6": "Samsung", + "54:40:AD": "Samsung", + "54:42:49": "Sony", + "54:44:A3": "Samsung", + "54:48:E6": "Xiaomi", + "54:53:ED": "Sony", + "54:5B:86": "Panasonic", + "54:60:09": "Nest", + "54:67:49": "Nest", + "54:75:95": "TP-Link", + "54:8C:81": "Hikvision", + "54:92:BE": "Samsung", + "54:9B:12": "Samsung", + "54:A7:03": "TP-Link", + "54:AF:97": "TP-Link", + "54:B8:02": "Samsung", + "54:B8:0A": "D-Link", + "54:BD:79": "Samsung", + "54:C4:15": "Hikvision", + "54:C8:0F": "TP-Link", + "54:CD:10": "Panasonic", + "54:D1:7D": "Samsung", + "54:DD:4F": "Samsung", + "54:E0:19": "Ring", + "54:E6:FC": "TP-Link", + "54:E6:FD": "Sony", + "54:EF:44": "Aqara", + "54:F2:01": "Samsung", + "54:FA:3E": "Samsung", + "54:FC:F0": "Samsung", + "58:03:FB": "Hikvision", + "58:04:4F": "TP-Link", + "58:09:87": "Ring", + "58:17:0C": "Sony", + "58:18:62": "Sony", + "58:20:59": "Xiaomi", + "58:20:71": "Samsung", + "58:24:29": "Nest", + "58:41:20": "TP-Link", + "58:44:98": "Xiaomi", + "58:48:22": "Sony", + "58:50:ED": "Hikvision", + "58:5B:69": "TVT", + "58:79:E0": "Samsung", + "58:9A:3E": "Ring", + "58:A6:39": "Samsung", + "58:A8:E8": "Ring", + "58:B1:0F": "Samsung", + "58:B6:23": "Xiaomi", + "58:C3:8B": "Samsung", + "58:C5:CB": "Samsung", + "58:CB:52": "Nest", + "58:D5:6E": "D-Link", + "58:D6:1F": "Ubiquiti", + "58:D8:12": "TP-Link", + "58:E4:88": "Ring", + "58:EA:1F": "Xiaomi", + "58:FC:C8": "Honeywell", + "5C:02:14": "Xiaomi", + "5C:10:C5": "Samsung", + "5C:2E:59": "Samsung", + "5C:33:7B": "Nest", + "5C:34:5B": "Hikvision", + "5C:35:48": "CP Plus", + "5C:3C:27": "Samsung", + "5C:40:71": "Xiaomi", + "5C:41:5A": "Ring", + "5C:47:5E": "Ring", + "5C:49:7D": "Samsung", + "5C:51:36": "Samsung", + "5C:51:81": "Samsung", + "5C:5E:0A": "Samsung", + "5C:62:8B": "TP-Link", + "5C:63:BF": "TP-Link", + "5C:84:3C": "Sony", + "5C:86:5C": "Samsung", + "5C:89:9A": "TP-Link", + "5C:8B:6B": "Ring", + "5C:96:66": "Sony", + "5C:99:60": "Samsung", + "5C:A6:4F": "TP-Link", + "5C:A6:E6": "TP-Link", + "5C:AC:3D": "Samsung", + "5C:B5:24": "Sony", + "5C:C1:D7": "Samsung", + "5C:C9:D3": "Ring", + "5C:CB:99": "Samsung", + "5C:D0:6E": "Xiaomi", + "5C:D3:3D": "Samsung", + "5C:D9:98": "D-Link", + "5C:DC:49": "Samsung", + "5C:E5:0C": "Xiaomi", + "5C:E8:EB": "Samsung", + "5C:E9:31": "TP-Link", + "5C:ED:F4": "Samsung", + "5C:F2:07": "Speco", + "5C:F5:1A": "Dahua", + "5C:F6:DC": "Samsung", + "60:22:32": "Ubiquiti", + "60:29:2B": "TP-Link", + "60:32:B1": "TP-Link", + "60:3A:7C": "TP-Link", + "60:3A:AF": "Samsung", + "60:63:4C": "D-Link", + "60:68:4E": "Samsung", + "60:6B:BD": "Samsung", + "60:6E:E8": "Xiaomi", + "60:70:6C": "Nest", + "60:77:E2": "Samsung", + "60:7F:CB": "Samsung", + "60:83:E7": "TP-Link", + "60:8E:08": "Samsung", + "60:8F:5C": "Samsung", + "60:95:78": "Samsung", + "60:A1:0A": "Samsung", + "60:A3:E3": "TP-Link", + "60:A4:B7": "TP-Link", + "60:A4:D0": "Samsung", + "60:AB:67": "Xiaomi", + "60:AF:6D": "Samsung", + "60:B4:A2": "Samsung", + "60:B7:6E": "Nest", + "60:C5:AD": "Samsung", + "60:C7:27": "Ring", + "60:D0:A9": "Samsung", + "60:E3:27": "TP-Link", + "60:FF:12": "Samsung", + "64:03:7F": "Samsung", + "64:07:F6": "Samsung", + "64:09:80": "Xiaomi", + "64:16:66": "Nest", + "64:17:CD": "Samsung", + "64:1B:2F": "Samsung", + "64:1C:AE": "Samsung", + "64:1C:B0": "Samsung", + "64:29:43": "D-Link", + "64:56:01": "TP-Link", + "64:5D:F4": "Samsung", + "64:63:06": "Xiaomi", + "64:64:4A": "Xiaomi", + "64:66:B3": "TP-Link", + "64:66:D8": "Samsung", + "64:6C:B2": "Samsung", + "64:6E:97": "TP-Link", + "64:70:02": "TP-Link", + "64:77:91": "Samsung", + "64:7B:CE": "Samsung", + "64:89:F1": "Samsung", + "64:90:C1": "Xiaomi", + "64:9A:63": "Ring", + "64:9D:38": "Nest", + "64:9E:31": "Xiaomi", + "64:A2:00": "Xiaomi", + "64:AC:E0": "Samsung", + "64:B3:10": "Samsung", + "64:B4:73": "Xiaomi", + "64:B5:F2": "Samsung", + "64:B8:53": "Samsung", + "64:CC:2E": "Xiaomi", + "64:CD:C2": "Ring", + "64:D0:D6": "Samsung", + "64:DA:A0": "Bosch", + "64:DB:8B": "Hikvision", + "64:DD:E9": "Xiaomi", + "64:E7:D8": "Samsung", + "64:FD:29": "Dahua", + "68:05:71": "Samsung", + "68:13:F3": "Ring", + "68:27:37": "Samsung", + "68:28:6C": "Sony", + "68:37:E9": "Ring", + "68:48:98": "Samsung", + "68:49:B2": "Arlo", + "68:4A:E9": "Samsung", + "68:4D:B6": "Xiaomi", + "68:54:FD": "Ring", + "68:5A:CF": "Samsung", + "68:6D:BC": "Hikvision", + "68:72:51": "Ubiquiti", + "68:72:C3": "Samsung", + "68:76:4F": "Sony", + "68:77:24": "TP-Link", + "68:7D:6B": "Samsung", + "68:7F:F0": "TP-Link", + "68:9A:87": "Ring", + "68:9F:D4": "Ring", + "68:AB:BC": "Xiaomi", + "68:B6:91": "Ring", + "68:B8:BB": "Xiaomi", + "68:BF:C4": "Samsung", + "68:C4:4C": "Xiaomi", + "68:D7:9A": "Ubiquiti", + "68:DB:F5": "Ring", + "68:DD:B7": "TP-Link", + "68:DF:DD": "Xiaomi", + "68:DF:E4": "Samsung", + "68:E7:C2": "Samsung", + "68:EB:AE": "Samsung", + "68:F6:3B": "Ring", + "68:FC:CA": "Samsung", + "68:FF:7B": "TP-Link", + "6C:00:6B": "Samsung", + "6C:0C:9A": "Ring", + "6C:0D:C4": "Xiaomi", + "6C:0E:0D": "Sony", + "6C:19:8F": "D-Link", + "6C:1C:71": "Dahua", + "6C:23:B9": "Sony", + "6C:2F:2C": "Samsung", + "6C:2F:8A": "Samsung", + "6C:48:3F": "Xiaomi", + "6C:4C:BC": "TP-Link", + "6C:55:63": "Samsung", + "6C:55:B1": "Ring", + "6C:56:97": "Ring", + "6C:5A:B0": "TP-Link", + "6C:63:F8": "Ubiquiti", + "6C:70:CB": "Samsung", + "6C:72:20": "D-Link", + "6C:83:36": "Samsung", + "6C:99:9D": "Ring", + "6C:AC:C2": "Samsung", + "6C:B0:CE": "Netgear", + "6C:B1:58": "TP-Link", + "6C:B2:27": "Sony", + "6C:B7:F4": "Samsung", + "6C:CD:D6": "Netgear", + "6C:DD:BC": "Samsung", + "6C:E8:73": "TP-Link", + "6C:F1:7E": "Uniview", + "6C:F3:73": "Samsung", + "6C:F7:84": "Xiaomi", + "70:09:71": "Samsung", + "70:1A:D5": "Avigilon", + "70:1F:3C": "Samsung", + "70:21:7F": "Xiaomi", + "70:26:05": "Sony", + "70:28:8B": "Samsung", + "70:2A:D5": "Samsung", + "70:3A:51": "Xiaomi", + "70:3A:CB": "Nest", + "70:4E:E0": "Samsung", + "70:4F:57": "TP-Link", + "70:58:12": "Panasonic", + "70:5A:AC": "Samsung", + "70:5F:A3": "Xiaomi", + "70:62:B8": "D-Link", + "70:66:2A": "Sony", + "70:70:AA": "Ring", + "70:97:51": "Xiaomi", + "70:9E:29": "Sony", + "70:A7:41": "Ubiquiti", + "70:AD:43": "Blink", + "70:B1:3D": "Samsung", + "70:BB:E9": "Xiaomi", + "70:CE:8C": "Samsung", + "70:F7:4F": "Bosch", + "70:F9:27": "Samsung", + "70:FD:46": "Samsung", + "74:05:A5": "TP-Link", + "74:15:75": "Xiaomi", + "74:19:0A": "Samsung", + "74:1E:B1": "Samsung", + "74:23:44": "Xiaomi", + "74:38:22": "Xiaomi", + "74:39:89": "TP-Link", + "74:3F:C2": "Hikvision", + "74:44:01": "Netgear", + "74:45:8A": "Samsung", + "74:51:BA": "Xiaomi", + "74:58:F3": "Ring", + "74:6D:FA": "Samsung", + "74:74:46": "Nest", + "74:75:48": "Blink", + "74:83:C2": "Ubiquiti", + "74:9E:F5": "Samsung", + "74:A5:7E": "Panasonic", + "74:A7:EA": "Ring", + "74:AB:93": "Blink", + "74:AC:B9": "Ubiquiti", + "74:C2:46": "Ring", + "74:C9:29": "Dahua", + "74:D4:23": "Ring", + "74:D6:37": "Ring", + "74:D7:CA": "Panasonic", + "74:DA:88": "TP-Link", + "74:DA:DA": "D-Link", + "74:E2:0C": "Ring", + "74:EA:3A": "TP-Link", + "74:EB:80": "Samsung", + "74:EC:B2": "Ring", + "74:F2:FA": "Xiaomi", + "74:F4:41": "Samsung", + "74:F6:7A": "Samsung", + "74:F9:2C": "Ubiquiti", + "74:FA:29": "Ubiquiti", + "74:FE:CE": "TP-Link", + "78:00:9E": "Samsung", + "78:02:F8": "Xiaomi", + "78:11:DC": "Xiaomi", + "78:1F:DB": "Samsung", + "78:20:51": "TP-Link", + "78:23:27": "Samsung", + "78:25:AD": "Samsung", + "78:32:1B": "D-Link", + "78:33:C6": "Samsung", + "78:37:16": "Samsung", + "78:40:E4": "Samsung", + "78:44:FD": "TP-Link", + "78:45:58": "Ubiquiti", + "78:46:D4": "Samsung", + "78:47:1D": "Samsung", + "78:52:1A": "Samsung", + "78:53:33": "Xiaomi", + "78:54:2E": "D-Link", + "78:59:5E": "Samsung", + "78:60:5B": "TP-Link", + "78:60:89": "Samsung", + "78:6C:84": "Ring", + "78:84:3C": "Sony", + "78:8A:20": "Ubiquiti", + "78:8C:B5": "TP-Link", + "78:98:E8": "D-Link", + "78:99:87": "Xiaomi", + "78:9E:D0": "Samsung", + "78:A0:3F": "Ring", + "78:A1:06": "TP-Link", + "78:A8:73": "Samsung", + "78:AB:BB": "Samsung", + "78:B6:FE": "Samsung", + "78:BD:BC": "Samsung", + "78:C1:1D": "Samsung", + "78:C3:E9": "Samsung", + "78:C8:81": "Sony", + "78:D2:94": "Netgear", + "78:D8:40": "Xiaomi", + "78:E1:03": "Ring", + "78:F2:38": "Samsung", + "78:F7:BE": "Samsung", + "7C:03:5E": "Xiaomi", + "7C:03:AB": "Xiaomi", + "7C:0A:3F": "Samsung", + "7C:0B:C6": "Samsung", + "7C:1C:68": "Samsung", + "7C:1D:D9": "Xiaomi", + "7C:23:02": "Samsung", + "7C:2A:DB": "Xiaomi", + "7C:2E:BD": "Nest", + "7C:2E:DD": "Samsung", + "7C:38:AD": "Samsung", + "7C:49:EB": "Xiaomi", + "7C:61:66": "Ring", + "7C:63:05": "Ring", + "7C:64:56": "Samsung", + "7C:75:2D": "Samsung", + "7C:78:7E": "Samsung", + "7C:78:B2": "Wyze", + "7C:7B:BF": "Samsung", + "7C:89:56": "Samsung", + "7C:8B:B5": "Samsung", + "7C:8B:CA": "TP-Link", + "7C:91:22": "Samsung", + "7C:A4:49": "Xiaomi", + "7C:B5:9B": "TP-Link", + "7C:C2:25": "Samsung", + "7C:C2:94": "Xiaomi", + "7C:C2:C6": "TP-Link", + "7C:D5:66": "Ring", + "7C:D6:61": "Xiaomi", + "7C:D9:5C": "Nest", + "7C:ED:C6": "Ring", + "7C:F1:7E": "TP-Link", + "7C:F8:54": "Samsung", + "7C:F9:0E": "Samsung", + "7C:FD:6B": "Xiaomi", + "80:07:94": "Samsung", + "80:0C:F9": "Ring", + "80:0D:3F": "Samsung", + "80:18:A7": "Samsung", + "80:19:70": "Samsung", + "80:20:FD": "Samsung", + "80:26:89": "D-Link", + "80:2A:A8": "Ubiquiti", + "80:31:F0": "Samsung", + "80:35:C1": "Xiaomi", + "80:37:73": "Netgear", + "80:39:8C": "Samsung", + "80:3C:04": "TP-Link", + "80:47:86": "Samsung", + "80:48:2C": "Wyze", + "80:48:9F": "Hikvision", + "80:4E:70": "Samsung", + "80:4E:81": "Samsung", + "80:54:2D": "Samsung", + "80:54:9C": "Samsung", + "80:57:19": "Samsung", + "80:65:6D": "Samsung", + "80:6D:71": "Ring", + "80:75:BF": "Samsung", + "80:7B:3E": "Samsung", + "80:7C:62": "Hikvision", + "80:86:D9": "Samsung", + "80:89:17": "TP-Link", + "80:8A:BD": "Samsung", + "80:8F:1D": "TP-Link", + "80:8F:97": "Xiaomi", + "80:99:E7": "Sony", + "80:9F:F5": "Samsung", + "80:AD:16": "Xiaomi", + "80:AE:54": "TP-Link", + "80:BE:AF": "Hikvision", + "80:C7:55": "Panasonic", + "80:CC:9C": "Netgear", + "80:CE:B9": "Samsung", + "80:E6:3C": "Xiaomi", + "80:EA:07": "TP-Link", + "80:F5:AE": "Hikvision", + "80:FF:A8": "IDIS", + "84:00:D2": "Sony", + "84:11:9E": "Samsung", + "84:16:F9": "TP-Link", + "84:1B:5E": "Netgear", + "84:22:89": "Samsung", + "84:25:19": "Samsung", + "84:25:DB": "Samsung", + "84:28:59": "Ring", + "84:2E:27": "Samsung", + "84:37:D5": "Samsung", + "84:3C:4C": "Bosch", + "84:46:93": "Xiaomi", + "84:48:80": "Ring", + "84:51:81": "Samsung", + "84:55:A5": "Samsung", + "84:5F:04": "Samsung", + "84:78:48": "Ubiquiti", + "84:8E:DF": "Sony", + "84:94:59": "Hikvision", + "84:98:66": "Samsung", + "84:9A:40": "Hikvision", + "84:A4:66": "Samsung", + "84:A8:24": "Nest", + "84:AE:DE": "Xiaomi", + "84:B5:41": "Samsung", + "84:B8:90": "TP-Link", + "84:C0:EF": "Samsung", + "84:C7:EA": "Sony", + "84:C9:B2": "D-Link", + "84:D6:D0": "Ring", + "84:D8:1B": "TP-Link", + "84:E6:57": "Sony", + "84:EE:E4": "Samsung", + "88:25:93": "TP-Link", + "88:26:3F": "Uniview", + "88:29:9C": "Samsung", + "88:29:BF": "Ring", + "88:2F:92": "Xiaomi", + "88:3D:24": "Nest", + "88:41:C1": "Ring", + "88:46:04": "Xiaomi", + "88:52:EB": "Xiaomi", + "88:54:1F": "Nest", + "88:5E:54": "Samsung", + "88:6C:60": "Xiaomi", + "88:71:E5": "Ring", + "88:75:98": "Samsung", + "88:76:B9": "D-Link", + "88:83:22": "Samsung", + "88:99:86": "TP-Link", + "88:9B:39": "Samsung", + "88:9F:6F": "Samsung", + "88:A3:03": "Samsung", + "88:AD:D2": "Samsung", + "88:B9:51": "Xiaomi", + "88:BD:45": "Samsung", + "88:C3:97": "Xiaomi", + "88:C9:E8": "Sony", + "88:DE:39": "Hikvision", + "8C:19:52": "Ring", + "8C:1A:BF": "Samsung", + "8C:1D:55": "Hanwha", + "8C:21:0A": "TP-Link", + "8C:22:D2": "Hikvision", + "8C:2A:85": "Ring", + "8C:2E:72": "Samsung", + "8C:30:66": "Ubiquiti", + "8C:3B:AD": "Netgear", + "8C:4E:BB": "Ring", + "8C:53:C3": "Xiaomi", + "8C:5A:F8": "Xiaomi", + "8C:64:22": "Sony", + "8C:6A:3B": "Samsung", + "8C:71:F8": "Samsung", + "8C:77:12": "Samsung", + "8C:79:F5": "Samsung", + "8C:7A:3D": "Xiaomi", + "8C:83:E1": "Samsung", + "8C:86:DD": "TP-Link", + "8C:90:2D": "TP-Link", + "8C:A3:EC": "Samsung", + "8C:A6:DF": "TP-Link", + "8C:AA:CE": "Xiaomi", + "8C:B0:E9": "Samsung", + "8C:BE:BE": "Xiaomi", + "8C:BF:A6": "Samsung", + "8C:C1:21": "Panasonic", + "8C:C5:D0": "Samsung", + "8C:C8:CD": "Samsung", + "8C:D0:B2": "Xiaomi", + "8C:D9:D6": "Xiaomi", + "8C:DE:E6": "Samsung", + "8C:DE:F9": "Xiaomi", + "8C:E5:C0": "Samsung", + "8C:E7:48": "Hikvision", + "8C:E9:B4": "Dahua", + "8C:EA:48": "Samsung", + "8C:ED:E1": "Ubiquiti", + "90:00:DB": "Samsung", + "90:02:A9": "Dahua", + "90:06:28": "Samsung", + "90:0C:C8": "Nest", + "90:11:95": "Ring", + "90:16:C8": "Amcrest", + "90:23:5B": "Ring", + "90:2A:EE": "Xiaomi", + "90:39:5F": "Ring", + "90:41:B2": "Ubiquiti", + "90:47:48": "Sony", + "90:48:6C": "Ring", + "90:63:3B": "Samsung", + "90:78:B2": "Xiaomi", + "90:81:75": "Samsung", + "90:8D:78": "D-Link", + "90:94:E4": "D-Link", + "90:97:F3": "Samsung", + "90:9A:4A": "TP-Link", + "90:A8:22": "Ring", + "90:AE:1B": "TP-Link", + "90:B1:44": "Samsung", + "90:B6:22": "Samsung", + "90:C1:15": "Sony", + "90:CA:FA": "Nest", + "90:EE:C7": "Samsung", + "90:F1:AA": "Samsung", + "90:F6:52": "TP-Link", + "90:F8:2E": "Ring", + "90:FB:5D": "Xiaomi", + "94:01:C2": "Samsung", + "94:05:B6": "Lilin", + "94:0C:6D": "TP-Link", + "94:17:00": "Xiaomi", + "94:18:65": "Netgear", + "94:2A:6F": "Ubiquiti", + "94:2D:DC": "Samsung", + "94:35:0A": "Samsung", + "94:3A:91": "Ring", + "94:3B:22": "Netgear", + "94:45:60": "Nest", + "94:51:03": "Samsung", + "94:52:44": "Samsung", + "94:5A:FC": "Ring", + "94:62:6D": "Xiaomi", + "94:63:D1": "Samsung", + "94:76:B7": "Samsung", + "94:78:06": "Uniview", + "94:7B:AE": "Xiaomi", + "94:7B:E7": "Samsung", + "94:87:E0": "Xiaomi", + "94:8B:93": "Xiaomi", + "94:8B:C1": "Samsung", + "94:95:A0": "Nest", + "94:9D:57": "Panasonic", + "94:A6:7E": "Netgear", + "94:B1:0A": "Samsung", + "94:CA:0F": "Honeywell", + "94:CE:2C": "Sony", + "94:D3:31": "Xiaomi", + "94:D7:71": "Samsung", + "94:D9:B3": "TP-Link", + "94:DB:56": "Sony", + "94:E1:29": "Samsung", + "94:E1:AC": "Hikvision", + "94:E6:BA": "Samsung", + "94:EB:2C": "Nest", + "94:EF:50": "TP-Link", + "98:03:8E": "TP-Link", + "98:06:3C": "Samsung", + "98:0D:6F": "Samsung", + "98:12:E0": "Xiaomi", + "98:17:1A": "Xiaomi", + "98:1D:FA": "Samsung", + "98:22:6E": "Ring", + "98:25:4A": "TP-Link", + "98:2D:68": "Samsung", + "98:39:8E": "Samsung", + "98:3A:1F": "Nest", + "98:3F:E8": "Samsung", + "98:48:27": "TP-Link", + "98:4E:8A": "Samsung", + "98:52:B1": "Samsung", + "98:73:C4": "Ring", + "98:80:EE": "Samsung", + "98:83:89": "Samsung", + "98:8B:0A": "Hikvision", + "98:97:CC": "TP-Link", + "98:98:FB": "Nest", + "98:9D:E5": "Hikvision", + "98:B0:8B": "Samsung", + "98:B8:BC": "Samsung", + "98:BA:5F": "TP-Link", + "98:CC:F3": "Ring", + "98:D2:93": "Nest", + "98:D7:42": "Samsung", + "98:DA:C4": "TP-Link", + "98:DE:D0": "TP-Link", + "98:DF:82": "Hikvision", + "98:E0:81": "Xiaomi", + "98:EE:94": "Xiaomi", + "98:F1:12": "Hikvision", + "98:F6:21": "Xiaomi", + "98:F9:CC": "Dahua", + "98:FA:2E": "Sony", + "98:FA:E3": "Xiaomi", + "98:FB:27": "Samsung", + "9C:02:98": "Samsung", + "9C:05:D6": "Ubiquiti", + "9C:14:63": "Dahua", + "9C:21:6A": "TP-Link", + "9C:25:95": "Samsung", + "9C:28:F7": "Xiaomi", + "9C:2A:83": "Samsung", + "9C:2E:7A": "Samsung", + "9C:2E:A1": "Xiaomi", + "9C:37:CB": "Sony", + "9C:39:28": "Samsung", + "9C:3A:AF": "Samsung", + "9C:3D:CF": "Netgear", + "9C:47:82": "TP-Link", + "9C:4F:5F": "Nest", + "9C:53:22": "TP-Link", + "9C:5A:81": "Xiaomi", + "9C:5C:F9": "Sony", + "9C:5F:B0": "Samsung", + "9C:61:1D": "Panasonic", + "9C:65:B0": "Samsung", + "9C:73:B1": "Samsung", + "9C:76:13": "Ring", + "9C:83:06": "Samsung", + "9C:8C:6E": "Samsung", + "9C:8E:CD": "Amcrest", + "9C:99:A0": "Xiaomi", + "9C:9D:7E": "Xiaomi", + "9C:9E:D5": "Xiaomi", + "9C:A2:F4": "TP-Link", + "9C:A5:13": "Samsung", + "9C:A6:15": "TP-Link", + "9C:BC:F0": "Xiaomi", + "9C:C8:E9": "Ring", + "9C:C9:EB": "Netgear", + "9C:D3:5B": "Samsung", + "9C:D3:6D": "Netgear", + "9C:D6:43": "D-Link", + "9C:E0:63": "Samsung", + "9C:E6:E7": "Samsung", + "A0:02:DC": "Ring", + "A0:03:63": "Bosch", + "A0:04:60": "Netgear", + "A0:07:98": "Samsung", + "A0:10:81": "Samsung", + "A0:1B:9E": "Samsung", + "A0:21:95": "Samsung", + "A0:21:B7": "Netgear", + "A0:27:B6": "Samsung", + "A0:40:A0": "Netgear", + "A0:56:2C": "Samsung", + "A0:60:32": "Amcrest", + "A0:60:90": "Samsung", + "A0:63:91": "Netgear", + "A0:75:91": "Samsung", + "A0:7D:9C": "Samsung", + "A0:82:1F": "Samsung", + "A0:86:C6": "Xiaomi", + "A0:9F:7A": "D-Link", + "A0:A3:F0": "D-Link", + "A0:AB:1B": "D-Link", + "A0:AC:69": "Samsung", + "A0:B0:BD": "Samsung", + "A0:B4:A5": "Samsung", + "A0:BD:1D": "Dahua", + "A0:CB:FD": "Samsung", + "A0:D0:5B": "Samsung", + "A0:D0:DC": "Ring", + "A0:D2:B1": "Ring", + "A0:D7:22": "Samsung", + "A0:D7:F3": "Samsung", + "A0:E0:25": "Provision-ISR", + "A0:E4:53": "Sony", + "A0:F3:C1": "TP-Link", + "A0:FF:0C": "Hikvision", + "A4:02:B7": "Ring", + "A4:07:B6": "Samsung", + "A4:08:01": "Ring", + "A4:11:15": "Bosch", + "A4:11:62": "Arlo", + "A4:14:37": "Hikvision", + "A4:1A:3A": "TP-Link", + "A4:29:02": "Hikvision", + "A4:2A:95": "D-Link", + "A4:2B:8C": "Netgear", + "A4:2B:B0": "TP-Link", + "A4:30:7A": "Samsung", + "A4:39:B3": "Xiaomi", + "A4:45:19": "Xiaomi", + "A4:4B:D5": "Xiaomi", + "A4:4B:D9": "Hikvision", + "A4:50:46": "Xiaomi", + "A4:55:90": "Xiaomi", + "A4:6C:F1": "Samsung", + "A4:75:B9": "Samsung", + "A4:77:33": "Nest", + "A4:84:31": "Samsung", + "A4:9A:58": "Samsung", + "A4:9D:DD": "Samsung", + "A4:9F:E7": "Samsung", + "A4:A4:59": "Hikvision", + "A4:A4:90": "Samsung", + "A4:A9:30": "Xiaomi", + "A4:BA:70": "Xiaomi", + "A4:C3:BE": "Xiaomi", + "A4:C6:9A": "Samsung", + "A4:C7:88": "Xiaomi", + "A4:CC:B3": "Xiaomi", + "A4:CF:12": "ZOSI", + "A4:D5:C2": "Hikvision", + "A4:D9:90": "Samsung", + "A4:E2:87": "Xiaomi", + "A4:EB:D3": "Samsung", + "A4:F8:FF": "Ubiquiti", + "A4:FF:9F": "Xiaomi", + "A8:06:00": "Samsung", + "A8:13:74": "Panasonic", + "A8:15:4D": "TP-Link", + "A8:16:D0": "Samsung", + "A8:29:48": "TP-Link", + "A8:2B:B9": "Samsung", + "A8:2B:D5": "Xiaomi", + "A8:30:BC": "Samsung", + "A8:34:6A": "Samsung", + "A8:42:A1": "TP-Link", + "A8:4B:4D": "Samsung", + "A8:51:5B": "Samsung", + "A8:57:4E": "TP-Link", + "A8:5B:6C": "Bosch", + "A8:63:7D": "D-Link", + "A8:6A:86": "Xiaomi", + "A8:6E:84": "TP-Link", + "A8:76:50": "Samsung", + "A8:79:8D": "Samsung", + "A8:7C:01": "Samsung", + "A8:81:95": "Samsung", + "A8:87:B3": "Samsung", + "A8:9C:6C": "Ubiquiti", + "A8:9C:ED": "Xiaomi", + "A8:9F:BA": "Samsung", + "A8:BA:69": "Samsung", + "A8:CA:77": "Ring", + "A8:CA:87": "Dahua", + "A8:D1:62": "Samsung", + "A8:DC:5A": "Digital Watchdog", + "A8:E3:EE": "Sony", + "A8:E6:21": "Ring", + "A8:EE:67": "Samsung", + "A8:F2:74": "Samsung", + "AC:15:A2": "TP-Link", + "AC:1E:92": "Samsung", + "AC:1E:9E": "Xiaomi", + "AC:36:13": "Samsung", + "AC:3E:B1": "Nest", + "AC:41:6A": "Ring", + "AC:5A:14": "Samsung", + "AC:63:BE": "Ring", + "AC:67:84": "Nest", + "AC:6C:90": "Samsung", + "AC:77:13": "Honeywell", + "AC:80:0A": "Sony", + "AC:80:FB": "Samsung", + "AC:84:C6": "TP-Link", + "AC:8B:A9": "Ubiquiti", + "AC:8C:46": "Xiaomi", + "AC:9B:0A": "Sony", + "AC:9F:C3": "Ring", + "AC:A7:F1": "TP-Link", + "AC:AF:B9": "Samsung", + "AC:B9:2F": "Hikvision", + "AC:C1:EE": "Xiaomi", + "AC:C3:3A": "Samsung", + "AC:CB:51": "Hikvision", + "AC:CC:8E": "Axis", + "AC:CC:FC": "Ring", + "AC:E6:BB": "Nest", + "AC:EE:9E": "Samsung", + "AC:F1:DF": "D-Link", + "AC:F7:F3": "Xiaomi", + "B0:09:DA": "Ring", + "B0:19:21": "TP-Link", + "B0:2A:43": "Nest", + "B0:39:56": "Netgear", + "B0:47:BF": "Samsung", + "B0:48:7A": "TP-Link", + "B0:4A:6A": "Samsung", + "B0:4E:26": "TP-Link", + "B0:54:76": "Samsung", + "B0:6A:41": "Nest", + "B0:6F:E0": "Samsung", + "B0:73:9C": "Ring", + "B0:7F:B9": "Netgear", + "B0:8B:A8": "Ring", + "B0:95:75": "TP-Link", + "B0:95:8E": "TP-Link", + "B0:99:D7": "Samsung", + "B0:9C:63": "Xiaomi", + "B0:A7:B9": "TP-Link", + "B0:B9:8A": "Netgear", + "B0:BE:76": "TP-Link", + "B0:C4:E7": "Samsung", + "B0:C5:54": "D-Link", + "B0:C5:59": "Samsung", + "B0:CF:CB": "Ring", + "B0:D0:9C": "Samsung", + "B0:D5:FB": "Nest", + "B0:D8:88": "Panasonic", + "B0:DF:3A": "Samsung", + "B0:E2:35": "Xiaomi", + "B0:E4:5C": "Samsung", + "B0:E4:D5": "Nest", + "B0:EC:71": "Samsung", + "B0:F2:F6": "Samsung", + "B0:F7:C4": "Ring", + "B0:FC:0D": "Ring", + "B4:05:A1": "Xiaomi", + "B4:0A:D8": "Sony", + "B4:0B:1D": "Samsung", + "B4:10:7A": "Ring", + "B4:13:24": "Nest", + "B4:1A:1D": "Samsung", + "B4:1F:4D": "Sony", + "B4:23:A2": "Nest", + "B4:37:D8": "D-Link", + "B4:3A:28": "Samsung", + "B4:40:DC": "Samsung", + "B4:4C:3B": "Dahua", + "B4:52:7D": "Sony", + "B4:52:7E": "Sony", + "B4:5B:D1": "TP-Link", + "B4:60:ED": "Xiaomi", + "B4:62:93": "Samsung", + "B4:6C:47": "Panasonic", + "B4:70:64": "Samsung", + "B4:74:43": "Samsung", + "B4:7C:9C": "Ring", + "B4:9D:02": "Samsung", + "B4:A3:82": "Hikvision", + "B4:B0:24": "TP-Link", + "B4:B7:42": "Ring", + "B4:BF:F6": "Samsung", + "B4:C0:C3": "TP-Link", + "B4:C4:FC": "Xiaomi", + "B4:CE:40": "Samsung", + "B4:D5:E5": "Samsung", + "B4:E4:54": "Ring", + "B4:EF:39": "Samsung", + "B4:FB:E4": "Ubiquiti", + "B8:20:8E": "Panasonic", + "B8:3B:CC": "Xiaomi", + "B8:50:D8": "Xiaomi", + "B8:52:E0": "Xiaomi", + "B8:53:84": "Xiaomi", + "B8:57:D8": "Samsung", + "B8:5A:73": "Samsung", + "B8:5E:7B": "Samsung", + "B8:5F:98": "Ring", + "B8:6C:E8": "Samsung", + "B8:7B:D4": "Nest", + "B8:94:E7": "Xiaomi", + "B8:A0:B8": "Samsung", + "B8:A3:86": "D-Link", + "B8:A4:4F": "Axis", + "B8:A8:25": "Samsung", + "B8:B4:09": "Samsung", + "B8:BB:AF": "Samsung", + "B8:BC:5B": "Samsung", + "B8:C6:8E": "Samsung", + "B8:D9:CE": "Samsung", + "B8:DB:38": "Nest", + "B8:EA:98": "Xiaomi", + "B8:F4:A4": "Nest", + "B8:F8:83": "TP-Link", + "B8:F9:34": "Sony", + "B8:FB:B3": "TP-Link", + "BC:07:1D": "TP-Link", + "BC:0E:AB": "Samsung", + "BC:0F:9A": "D-Link", + "BC:10:7B": "Samsung", + "BC:14:85": "Samsung", + "BC:20:A4": "Samsung", + "BC:22:28": "D-Link", + "BC:27:7A": "Samsung", + "BC:29:78": "Hikvision", + "BC:32:5F": "Dahua", + "BC:32:B2": "Samsung", + "BC:33:29": "Sony", + "BC:3E:0B": "Panasonic", + "BC:44:86": "Samsung", + "BC:45:5B": "Samsung", + "BC:46:99": "TP-Link", + "BC:47:60": "Samsung", + "BC:51:FE": "Swann", + "BC:52:74": "Samsung", + "BC:54:51": "Samsung", + "BC:5E:33": "Hikvision", + "BC:60:A7": "Sony", + "BC:61:93": "Xiaomi", + "BC:69:CB": "Panasonic", + "BC:6A:D1": "Xiaomi", + "BC:6E:64": "Sony", + "BC:72:B1": "Samsung", + "BC:76:5E": "Samsung", + "BC:79:AD": "Samsung", + "BC:7A:BF": "Samsung", + "BC:7E:8B": "Samsung", + "BC:7F:A4": "Xiaomi", + "BC:85:1F": "Samsung", + "BC:90:3A": "Bosch", + "BC:93:07": "Samsung", + "BC:9B:5E": "Hikvision", + "BC:A0:80": "Samsung", + "BC:A5:11": "Netgear", + "BC:A5:8B": "Samsung", + "BC:AD:28": "Hikvision", + "BC:B1:F3": "Samsung", + "BC:B2:CC": "Samsung", + "BC:BA:C2": "Hikvision", + "BC:C3:42": "Panasonic", + "BC:D1:1F": "Samsung", + "BC:D1:77": "TP-Link", + "BC:DF:58": "Nest", + "BC:E6:3F": "Samsung", + "BC:F6:85": "D-Link", + "BC:F7:30": "Samsung", + "C0:06:C3": "TP-Link", + "C0:11:73": "Samsung", + "C0:15:1B": "Sony", + "C0:16:93": "Xiaomi", + "C0:17:4D": "Samsung", + "C0:1C:6A": "Nest", + "C0:23:8D": "Samsung", + "C0:25:E9": "TP-Link", + "C0:39:5A": "Dahua", + "C0:3A:55": "TP-Link", + "C0:3D:03": "Samsung", + "C0:3F:0E": "Netgear", + "C0:48:E6": "Samsung", + "C0:4A:00": "TP-Link", + "C0:51:7E": "Hikvision", + "C0:56:E3": "Hikvision", + "C0:5B:44": "Xiaomi", + "C0:61:18": "TP-Link", + "C0:65:99": "Samsung", + "C0:6D:ED": "Hikvision", + "C0:7A:D6": "Samsung", + "C0:87:EB": "Samsung", + "C0:89:97": "Samsung", + "C0:8D:51": "Ring", + "C0:91:B9": "Ring", + "C0:95:CF": "Ring", + "C0:A0:BB": "D-Link", + "C0:BD:C8": "Samsung", + "C0:C9:E3": "TP-Link", + "C0:D2:DD": "Samsung", + "C0:D3:C0": "Samsung", + "C0:D5:E2": "Samsung", + "C0:DC:DA": "Samsung", + "C0:E4:2D": "TP-Link", + "C0:FF:D4": "Netgear", + "C4:04:15": "Netgear", + "C4:0B:CB": "Xiaomi", + "C4:12:F5": "D-Link", + "C4:18:E9": "Samsung", + "C4:1C:07": "Samsung", + "C4:2F:90": "Hikvision", + "C4:3A:BE": "Sony", + "C4:3D:C7": "Netgear", + "C4:42:02": "Samsung", + "C4:50:06": "Samsung", + "C4:57:6E": "Samsung", + "C4:5D:83": "Samsung", + "C4:62:EA": "Samsung", + "C4:6A:B7": "Xiaomi", + "C4:6E:1F": "Night Owl", + "C4:71:0F": "Xiaomi", + "C4:71:54": "TP-Link", + "C4:73:1E": "Samsung", + "C4:77:64": "Samsung", + "C4:79:05": "Uniview", + "C4:7D:9F": "Samsung", + "C4:88:E5": "Samsung", + "C4:93:BB": "Xiaomi", + "C4:93:D9": "Samsung", + "C4:95:00": "Ring", + "C4:A8:1D": "D-Link", + "C4:AA:C4": "Dahua", + "C4:AE:12": "Samsung", + "C4:DB:AD": "Ring", + "C4:E9:0A": "D-Link", + "C4:E9:84": "TP-Link", + "C4:EF:3D": "Samsung", + "C4:EF:DA": "Honeywell", + "C8:10:2F": "Netgear", + "C8:12:0B": "Samsung", + "C8:14:79": "Samsung", + "C8:19:F7": "Samsung", + "C8:28:32": "Xiaomi", + "C8:2A:DD": "Nest", + "C8:38:70": "Samsung", + "C8:3D:DC": "Xiaomi", + "C8:41:8A": "Samsung", + "C8:4A:A0": "Sony", + "C8:51:42": "Samsung", + "C8:5C:CC": "Xiaomi", + "C8:63:F1": "Sony", + "C8:6C:3D": "Ring", + "C8:78:7D": "D-Link", + "C8:7E:75": "Samsung", + "C8:90:8A": "Samsung", + "C8:9E:43": "Netgear", + "C8:A6:EF": "Samsung", + "C8:A7:02": "Hikvision", + "C8:A8:23": "Samsung", + "C8:BD:4D": "Samsung", + "C8:BD:69": "Samsung", + "C8:BE:19": "D-Link", + "C8:BF:4C": "Xiaomi", + "C8:D3:A3": "D-Link", + "C8:D7:B0": "Samsung", + "CC:03:3D": "Xiaomi", + "CC:05:1B": "Samsung", + "CC:07:AB": "Samsung", + "CC:08:FB": "TP-Link", + "CC:20:AC": "Samsung", + "CC:21:19": "Samsung", + "CC:22:93": "Ring", + "CC:32:E5": "TP-Link", + "CC:34:29": "TP-Link", + "CC:35:D9": "Ubiquiti", + "CC:3B:FB": "Ring", + "CC:40:D0": "Netgear", + "CC:42:10": "Xiaomi", + "CC:46:4E": "Samsung", + "CC:4D:75": "Xiaomi", + "CC:57:63": "Panasonic", + "CC:68:B6": "TP-Link", + "CC:6E:A4": "Samsung", + "CC:7E:E7": "Panasonic", + "CC:98:8B": "Sony", + "CC:9E:A2": "Ring", + "CC:A7:C1": "Nest", + "CC:AF:E3": "Ring", + "CC:B1:1A": "Samsung", + "CC:B2:55": "D-Link", + "CC:B5:D1": "Xiaomi", + "CC:BA:BD": "TP-Link", + "CC:D8:43": "Xiaomi", + "CC:DA:20": "Xiaomi", + "CC:DD:58": "Bosch", + "CC:E6:86": "Samsung", + "CC:E9:FA": "Samsung", + "CC:EB:5E": "Xiaomi", + "CC:F4:11": "Nest", + "CC:F7:35": "Ring", + "CC:F8:26": "Samsung", + "CC:F9:E8": "Samsung", + "CC:F9:F0": "Samsung", + "CC:FE:3C": "Samsung", + "D0:03:DF": "Samsung", + "D0:04:B0": "Samsung", + "D0:14:11": "Zmodo", + "D0:17:6A": "Samsung", + "D0:1B:49": "Samsung", + "D0:21:F9": "Ubiquiti", + "D0:31:69": "Samsung", + "D0:32:C3": "D-Link", + "D0:37:45": "TP-Link", + "D0:39:FA": "Samsung", + "D0:3F:27": "Wyze", + "D0:51:62": "Sony", + "D0:56:FB": "Samsung", + "D0:59:E4": "Samsung", + "D0:66:7B": "Samsung", + "D0:76:E7": "TP-Link", + "D0:7F:A0": "Samsung", + "D0:87:E2": "Samsung", + "D0:9C:7A": "Xiaomi", + "D0:AE:05": "Xiaomi", + "D0:B1:28": "Samsung", + "D0:B4:98": "Bosch", + "D0:C1:B1": "Samsung", + "D0:C1:BF": "Xiaomi", + "D0:C2:4E": "Samsung", + "D0:C7:C0": "TP-Link", + "D0:CE:C0": "Xiaomi", + "D0:D0:03": "Samsung", + "D0:DF:C7": "Samsung", + "D0:FC:CC": "Samsung", + "D4:01:6D": "TP-Link", + "D4:11:A3": "Samsung", + "D4:17:61": "Xiaomi", + "D4:35:38": "Xiaomi", + "D4:38:9C": "Sony", + "D4:3A:2C": "Nest", + "D4:43:0E": "Dahua", + "D4:43:8A": "Xiaomi", + "D4:53:2A": "Xiaomi", + "D4:5E:EC": "Xiaomi", + "D4:6E:0E": "TP-Link", + "D4:7A:E2": "Samsung", + "D4:87:D8": "Samsung", + "D4:88:90": "Samsung", + "D4:89:C1": "Ubiquiti", + "D4:8A:39": "Samsung", + "D4:91:0F": "Ring", + "D4:97:0B": "Xiaomi", + "D4:9D:C0": "Samsung", + "D4:A3:65": "Xiaomi", + "D4:AE:05": "Samsung", + "D4:DA:21": "Xiaomi", + "D4:E6:B7": "Samsung", + "D4:E8:53": "Hikvision", + "D4:E8:B2": "Samsung", + "D4:F0:EA": "Xiaomi", + "D4:F5:47": "Nest", + "D4:F7:D5": "Sony", + "D8:06:D1": "Honeywell", + "D8:07:B6": "TP-Link", + "D8:08:31": "Samsung", + "D8:0B:9A": "Samsung", + "D8:0D:17": "TP-Link", + "D8:15:0D": "TP-Link", + "D8:31:CF": "Samsung", + "D8:32:E3": "Xiaomi", + "D8:44:89": "TP-Link", + "D8:47:32": "TP-Link", + "D8:55:75": "Samsung", + "D8:57:EF": "Samsung", + "D8:5B:2A": "Samsung", + "D8:5D:4C": "TP-Link", + "D8:63:75": "Xiaomi", + "D8:68:A0": "Samsung", + "D8:68:C3": "Samsung", + "D8:6C:63": "Nest", + "D8:71:54": "Samsung", + "D8:8C:79": "Nest", + "D8:90:E8": "Samsung", + "D8:93:D4": "Xiaomi", + "D8:A3:5C": "Samsung", + "D8:AF:F1": "Panasonic", + "D8:B0:53": "Xiaomi", + "D8:B1:2A": "Panasonic", + "D8:B3:70": "Ubiquiti", + "D8:BE:65": "Ring", + "D8:C4:E9": "Samsung", + "D8:CE:3A": "Xiaomi", + "D8:D4:3C": "Sony", + "D8:E0:E1": "Samsung", + "D8:E3:74": "Xiaomi", + "D8:EB:46": "Nest", + "D8:F1:2E": "TP-Link", + "D8:FB:D6": "Ring", + "D8:FE:E3": "D-Link", + "DC:00:77": "TP-Link", + "DC:07:F8": "Hikvision", + "DC:44:B6": "Samsung", + "DC:54:D7": "Ring", + "DC:62:79": "TP-Link", + "DC:66:72": "Samsung", + "DC:69:E2": "Samsung", + "DC:6A:E7": "Xiaomi", + "DC:74:A8": "Samsung", + "DC:89:83": "Samsung", + "DC:91:BF": "Ring", + "DC:9F:DB": "Ubiquiti", + "DC:A0:D0": "Ring", + "DC:B3:B4": "Honeywell", + "DC:B7:2E": "Xiaomi", + "DC:C4:9C": "Samsung", + "DC:CC:E6": "Samsung", + "DC:CF:96": "Samsung", + "DC:D2:6A": "Hikvision", + "DC:DC:E2": "Samsung", + "DC:E5:5B": "Nest", + "DC:EA:E7": "D-Link", + "DC:ED:83": "Xiaomi", + "DC:EF:09": "Netgear", + "DC:F7:56": "Samsung", + "DC:FE:18": "TP-Link", + "E0:03:6B": "Samsung", + "E0:05:C5": "TP-Link", + "E0:1A:DF": "Nest", + "E0:1C:FC": "D-Link", + "E0:1F:88": "Xiaomi", + "E0:28:0A": "TP-Link", + "E0:2E:FE": "Dahua", + "E0:46:9A": "Netgear", + "E0:46:EE": "Netgear", + "E0:50:8B": "Dahua", + "E0:62:67": "Xiaomi", + "E0:63:DA": "Ubiquiti", + "E0:63:E5": "Sony", + "E0:80:6B": "Xiaomi", + "E0:91:F5": "Netgear", + "E0:99:71": "Samsung", + "E0:9D:13": "Samsung", + "E0:A7:00": "Verkada", + "E0:AA:96": "Samsung", + "E0:B6:55": "Xiaomi", + "E0:BA:AD": "Hikvision", + "E0:C2:50": "Netgear", + "E0:C3:77": "Samsung", + "E0:CA:3C": "Hikvision", + "E0:CB:1D": "Ring", + "E0:CB:EE": "Samsung", + "E0:CC:F8": "Xiaomi", + "E0:D0:83": "Samsung", + "E0:D3:62": "TP-Link", + "E0:DB:10": "Samsung", + "E0:DC:FF": "Xiaomi", + "E0:DF:13": "Hikvision", + "E0:EE:1B": "Panasonic", + "E0:F7:28": "Ring", + "E4:10:88": "Samsung", + "E4:12:1D": "Samsung", + "E4:1B:43": "Xiaomi", + "E4:24:6C": "Dahua", + "E4:30:22": "Hanwha", + "E4:32:CB": "Samsung", + "E4:38:83": "Ubiquiti", + "E4:40:E2": "Samsung", + "E4:45:19": "Xiaomi", + "E4:46:DA": "Xiaomi", + "E4:58:B8": "Samsung", + "E4:58:E7": "Samsung", + "E4:5D:75": "Samsung", + "E4:5E:1B": "Nest", + "E4:6F:13": "D-Link", + "E4:7C:F9": "Samsung", + "E4:7D:BD": "Samsung", + "E4:84:D3": "Xiaomi", + "E4:92:82": "Samsung", + "E4:92:FB": "Samsung", + "E4:9F:7D": "Samsung", + "E4:A4:30": "Samsung", + "E4:AA:E4": "Xiaomi", + "E4:B0:21": "Samsung", + "E4:BC:AA": "Xiaomi", + "E4:C3:2A": "TP-Link", + "E4:D3:32": "TP-Link", + "E4:D5:8B": "Hikvision", + "E4:DB:6D": "Xiaomi", + "E4:E0:C5": "Samsung", + "E4:E6:6C": "Tiandy", + "E4:EC:E8": "Samsung", + "E4:F0:42": "Nest", + "E4:F3:C4": "Samsung", + "E4:F4:C6": "Netgear", + "E4:F8:EF": "Samsung", + "E4:FA:C4": "TP-Link", + "E4:FA:ED": "Samsung", + "E4:FE:43": "Xiaomi", + "E8:03:9A": "Samsung", + "E8:11:32": "Samsung", + "E8:27:25": "Axis", + "E8:3A:12": "Samsung", + "E8:48:B8": "TP-Link", + "E8:4A:54": "Xiaomi", + "E8:4C:4A": "Ring", + "E8:4E:84": "Samsung", + "E8:54:97": "Samsung", + "E8:5A:8B": "Xiaomi", + "E8:5F:B4": "Xiaomi", + "E8:6D:CB": "Samsung", + "E8:6E:3A": "Sony", + "E8:7E:EF": "Xiaomi", + "E8:7F:6B": "Samsung", + "E8:88:43": "Xiaomi", + "E8:93:09": "Samsung", + "E8:94:F6": "TP-Link", + "E8:98:47": "Xiaomi", + "E8:A0:ED": "Hikvision", + "E8:AA:CB": "Samsung", + "E8:B4:C8": "Samsung", + "E8:C9:13": "Samsung", + "E8:CC:18": "D-Link", + "E8:D5:2B": "Nest", + "E8:D8:7E": "Ring", + "E8:DE:27": "TP-Link", + "E8:E5:D6": "Samsung", + "E8:F7:91": "Xiaomi", + "E8:FC:AF": "Netgear", + "EC:08:6B": "TP-Link", + "EC:0D:E4": "Ring", + "EC:10:55": "Xiaomi", + "EC:10:7B": "Samsung", + "EC:17:2F": "TP-Link", + "EC:22:80": "D-Link", + "EC:26:CA": "TP-Link", + "EC:2B:EB": "Ring", + "EC:30:B3": "Xiaomi", + "EC:31:5F": "Ring", + "EC:41:18": "Xiaomi", + "EC:4D:3E": "Xiaomi", + "EC:60:73": "TP-Link", + "EC:65:CC": "Panasonic", + "EC:71:DB": "Reolink", + "EC:74:8C": "Sony", + "EC:75:0C": "TP-Link", + "EC:7C:B6": "Samsung", + "EC:88:8F": "TP-Link", + "EC:8A:C4": "Ring", + "EC:90:C1": "Samsung", + "EC:A1:38": "Ring", + "EC:A9:71": "Hikvision", + "EC:AA:25": "Samsung", + "EC:AD:E0": "D-Link", + "EC:B5:50": "Samsung", + "EC:C8:9C": "Hikvision", + "EC:D0:9F": "Xiaomi", + "EC:E0:9B": "Samsung", + "EC:FA:5C": "Xiaomi", + "F0:05:1B": "Samsung", + "F0:08:F1": "Samsung", + "F0:09:0D": "TP-Link", + "F0:27:2D": "Ring", + "F0:2F:9E": "Ring", + "F0:39:65": "Samsung", + "F0:4F:7C": "Ring", + "F0:54:94": "Honeywell", + "F0:5A:09": "Samsung", + "F0:5B:7B": "Samsung", + "F0:5C:77": "Nest", + "F0:65:AE": "Samsung", + "F0:6B:CA": "Samsung", + "F0:6C:5D": "Xiaomi", + "F0:70:4F": "Samsung", + "F0:72:8C": "Samsung", + "F0:72:EA": "Nest", + "F0:74:C1": "Blink", + "F0:7D:68": "D-Link", + "F0:81:73": "Ring", + "F0:8A:76": "Samsung", + "F0:9F:C2": "Ubiquiti", + "F0:A2:25": "Ring", + "F0:A7:31": "TP-Link", + "F0:B4:29": "Xiaomi", + "F0:B4:D2": "D-Link", + "F0:BF:97": "Sony", + "F0:C8:8B": "Wyze", + "F0:CD:31": "Samsung", + "F0:D2:F1": "Ring", + "F0:E7:7E": "Samsung", + "F0:EE:10": "Samsung", + "F0:EF:86": "Nest", + "F0:F0:A4": "Ring", + "F0:F3:36": "TP-Link", + "F0:F5:64": "Samsung", + "F4:03:04": "Nest", + "F4:03:2A": "Ring", + "F4:0E:22": "Samsung", + "F4:1A:9C": "Xiaomi", + "F4:2A:7D": "TP-Link", + "F4:2B:8C": "Samsung", + "F4:30:8B": "Xiaomi", + "F4:42:8F": "Samsung", + "F4:60:E2": "Xiaomi", + "F4:64:12": "Sony", + "F4:6D:2F": "TP-Link", + "F4:71:90": "Samsung", + "F4:7B:5E": "Samsung", + "F4:7D:EF": "Samsung", + "F4:83:CD": "TP-Link", + "F4:84:8D": "TP-Link", + "F4:8B:32": "Xiaomi", + "F4:8C:EB": "D-Link", + "F4:92:BF": "Ubiquiti", + "F4:9F:54": "Samsung", + "F4:B1:C2": "Dahua", + "F4:C2:48": "Samsung", + "F4:D9:FB": "Samsung", + "F4:DD:06": "Samsung", + "F4:E2:C6": "Ubiquiti", + "F4:EC:38": "TP-Link", + "F4:F2:6D": "TP-Link", + "F4:F3:09": "Samsung", + "F4:F5:0B": "TP-Link", + "F4:F5:D8": "Nest", + "F4:F5:DB": "Xiaomi", + "F4:F5:E8": "Nest", + "F4:FE:FB": "Samsung", + "F8:0F:F9": "Nest", + "F8:1A:2B": "Nest", + "F8:1A:67": "TP-Link", + "F8:20:97": "CP Plus", + "F8:3F:51": "Samsung", + "F8:43:EF": "Xiaomi", + "F8:46:1C": "Sony", + "F8:4D:FC": "Hikvision", + "F8:4E:17": "Sony", + "F8:4E:58": "Samsung", + "F8:54:B8": "Ring", + "F8:5B:6E": "Samsung", + "F8:6F:B0": "TP-Link", + "F8:71:0C": "Xiaomi", + "F8:73:94": "Netgear", + "F8:77:B8": "Samsung", + "F8:83:06": "Xiaomi", + "F8:84:F2": "Samsung", + "F8:8C:21": "TP-Link", + "F8:8F:07": "Samsung", + "F8:8F:CA": "Nest", + "F8:A4:5F": "Xiaomi", + "F8:AB:82": "Xiaomi", + "F8:C9:03": "TP-Link", + "F8:CE:07": "Dahua", + "F8:CE:21": "TP-Link", + "F8:D0:AC": "Sony", + "F8:D0:BD": "Samsung", + "F8:D1:11": "TP-Link", + "F8:E6:1A": "Samsung", + "F8:E9:03": "D-Link", + "F8:F1:E6": "Samsung", + "F8:FC:E1": "Ring", + "FC:02:96": "Xiaomi", + "FC:03:9F": "Samsung", + "FC:0F:E6": "Sony", + "FC:19:10": "Samsung", + "FC:19:99": "Xiaomi", + "FC:1A:46": "Samsung", + "FC:41:16": "Nest", + "FC:42:03": "Samsung", + "FC:43:45": "Xiaomi", + "FC:49:2D": "Ring", + "FC:5B:8C": "Xiaomi", + "FC:5F:49": "Dahua", + "FC:64:3A": "Samsung", + "FC:64:BA": "Xiaomi", + "FC:65:DE": "Ring", + "FC:75:16": "D-Link", + "FC:8F:90": "Samsung", + "FC:91:5D": "Nest", + "FC:93:6B": "Samsung", + "FC:9C:98": "Arlo", + "FC:9F:FD": "Hikvision", + "FC:A1:3E": "Samsung", + "FC:A1:83": "Ring", + "FC:A6:21": "Samsung", + "FC:A6:67": "Ring", + "FC:A9:F5": "Xiaomi", + "FC:AA:B6": "Samsung", + "FC:B6:9D": "Dahua", + "FC:C7:34": "Samsung", + "FC:D6:BD": "Bosch", + "FC:D7:33": "TP-Link", + "FC:D7:49": "Ring", + "FC:D9:08": "Xiaomi", + "FC:DE:90": "Samsung", + "FC:E9:D8": "Ring", + "FC:EC:DA": "Ubiquiti", + "FC:F1:36": "Samsung", + "FC:F1:52": "Sony" +} \ No newline at end of file diff --git a/go.mod b/go.mod index e68e447..45cb83a 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.24.0 toolchain go1.24.9 require ( + github.com/AlexxIT/go2rtc v1.9.14 github.com/IOTechSystems/onvif v1.2.0 github.com/go-chi/chi/v5 v5.2.3 github.com/go-playground/validator/v10 v10.28.0 @@ -20,8 +21,20 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/google/go-cmp v0.7.0 // indirect + github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - golang.org/x/crypto v0.42.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect + github.com/miekg/dns v1.1.70 // indirect + github.com/pion/randutil v0.1.0 // indirect + github.com/pion/rtp v1.10.0 // indirect + github.com/pion/sdp/v3 v3.0.17 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + golang.org/x/tools v0.41.0 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) diff --git a/go.sum b/go.sum index 616a5bb..008ce7d 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,12 @@ +github.com/AlexxIT/go2rtc v1.9.14 h1:HjaZ2pR64nTkoTZcKM8Zjybg7swyCZbA8Biru1mbqcY= +github.com/AlexxIT/go2rtc v1.9.14/go.mod h1:fcN11KXBbIcExYRqMVDlW7amLZ4/z+hpr5+318fBA9U= github.com/IOTechSystems/onvif v1.2.0 h1:vplyPdhFhMRtIdkEbQIkTlrKjXpeDj+WUTt5UW61ZcI= github.com/IOTechSystems/onvif v1.2.0/go.mod h1:/dTr5BtFaGojYGJ2rEBIVWh3seGIcSuCJhcK9zwTsk0= github.com/beevik/etree v1.4.1 h1:PmQJDDYahBGNKDcpdX8uPy1xRCwoCGVUiW669MEirVI= github.com/beevik/etree v1.4.1/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs= github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/elgs/gostrgen v0.0.0-20220325073726-0c3e00d082f6 h1:x9TA+vnGEyqmWY+eA9HfgxNRkOQqwiEpFE9IPXSGuEA= @@ -22,36 +25,58 @@ github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0 github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= +github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA= +github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/rtp v1.10.0 h1:XN/xca4ho6ZEcijpdF2VGFbwuHUfiIMf3ew8eAAE43w= +github.com/pion/rtp v1.10.0/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM= +github.com/pion/sdp/v3 v3.0.17 h1:9SfLAW/fF1XC8yRqQ3iWGzxkySxup4k4V7yN8Fs8nuo= +github.com/pion/sdp/v3 v3.0.17/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 h1:aeN+ghOV0b2VCmKKO3gqnDQ8mLbpABZgRR2FVYx4ouI= +github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9/go.mod h1:roo6cZ/uqpwKMuvPG0YmzI5+AmUiMWfjCBZpGXqbTxE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -60,14 +85,17 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/api/handlers/probe.go b/internal/api/handlers/probe.go new file mode 100644 index 0000000..d4b2190 --- /dev/null +++ b/internal/api/handlers/probe.go @@ -0,0 +1,82 @@ +package handlers + +import ( + "encoding/json" + "net" + "net/http" + + "github.com/eduard256/Strix/internal/camera/discovery" +) + +// ProbeHandler handles device probe requests. +// GET /api/v1/probe?ip=192.168.1.50 +type ProbeHandler struct { + probeService *discovery.ProbeService + logger interface { + Debug(string, ...any) + Error(string, error, ...any) + Info(string, ...any) + } +} + +// NewProbeHandler creates a new probe handler. +func NewProbeHandler( + probeService *discovery.ProbeService, + logger interface { + Debug(string, ...any) + Error(string, error, ...any) + Info(string, ...any) + }, +) *ProbeHandler { + return &ProbeHandler{ + probeService: probeService, + logger: logger, + } +} + +// ServeHTTP handles probe requests. +func (h *ProbeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + ip := r.URL.Query().Get("ip") + if ip == "" { + h.sendError(w, "Missing required parameter: ip", http.StatusBadRequest) + return + } + + // Validate IP format + if net.ParseIP(ip) == nil { + h.sendError(w, "Invalid IP address: "+ip, http.StatusBadRequest) + return + } + + h.logger.Info("probe requested", "ip", ip, "remote_addr", r.RemoteAddr) + + // Run probe + result := h.probeService.Probe(r.Context(), ip) + + // Send response + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + if err := json.NewEncoder(w).Encode(result); err != nil { + h.logger.Error("failed to encode probe response", err) + } +} + +// sendError sends a JSON error response. +func (h *ProbeHandler) sendError(w http.ResponseWriter, message string, statusCode int) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + + response := map[string]interface{}{ + "error": true, + "message": message, + "code": statusCode, + } + + _ = json.NewEncoder(w).Encode(response) +} diff --git a/internal/api/routes.go b/internal/api/routes.go index 2f283cf..282d49c 100644 --- a/internal/api/routes.go +++ b/internal/api/routes.go @@ -20,6 +20,7 @@ type Server struct { loader *database.Loader searchEngine *database.SearchEngine scanner *discovery.Scanner + probeService *discovery.ProbeService sseServer *sse.Server logger interface{ Debug(string, ...any); Error(string, error, ...any); Info(string, ...any) } } @@ -75,6 +76,22 @@ func NewServer( // Initialize SSE server sseServer := sse.NewServer(logger) + // Initialize OUI database for vendor identification + ouiDB := discovery.NewOUIDatabase() + if err := ouiDB.LoadFromFile(cfg.Database.OUIPath); err != nil { + logger.Error("failed to load OUI database, vendor lookup will be unavailable", err) + } else { + logger.Info("OUI database loaded", "entries", ouiDB.Size()) + } + + // Initialize ProbeService with all probers + probers := []discovery.Prober{ + &discovery.DNSProber{}, + discovery.NewARPProber(ouiDB), + &discovery.MDNSProber{}, + } + probeService := discovery.NewProbeService(probers, logger) + // Create server server := &Server{ router: chi.NewRouter(), @@ -82,6 +99,7 @@ func NewServer( loader: loader, searchEngine: searchEngine, scanner: scanner, + probeService: probeService, sseServer: sseServer, logger: logger, } @@ -127,6 +145,9 @@ func (s *Server) setupRoutes() { // Stream discovery (SSE) s.router.Post("/streams/discover", handlers.NewDiscoverHandler(s.scanner, s.sseServer, s.logger).ServeHTTP) + + // Device probe (ping + DNS + ARP/OUI + mDNS) + s.router.Get("/probe", handlers.NewProbeHandler(s.probeService, s.logger).ServeHTTP) } // ServeHTTP implements http.Handler diff --git a/internal/camera/discovery/oui.go b/internal/camera/discovery/oui.go new file mode 100644 index 0000000..ec9780c --- /dev/null +++ b/internal/camera/discovery/oui.go @@ -0,0 +1,76 @@ +package discovery + +import ( + "encoding/json" + "fmt" + "os" + "strings" + "sync" +) + +// OUIDatabase provides MAC address prefix to vendor name lookup. +// Data is loaded from a JSON file containing camera/surveillance vendor OUI prefixes. +type OUIDatabase struct { + data map[string]string // "C0:56:E3" -> "Hikvision" + mu sync.RWMutex +} + +// NewOUIDatabase creates an empty OUI database. +func NewOUIDatabase() *OUIDatabase { + return &OUIDatabase{ + data: make(map[string]string), + } +} + +// LoadFromFile loads OUI data from a JSON file. +// Expected format: {"C0:56:E3": "Hikvision", "54:EF:44": "Lumi/Aqara", ...} +func (db *OUIDatabase) LoadFromFile(path string) error { + file, err := os.Open(path) + if err != nil { + return fmt.Errorf("failed to open OUI database: %w", err) + } + defer file.Close() + + var data map[string]string + if err := json.NewDecoder(file).Decode(&data); err != nil { + return fmt.Errorf("failed to decode OUI database: %w", err) + } + + // Normalize all keys to uppercase + normalized := make(map[string]string, len(data)) + for k, v := range data { + normalized[strings.ToUpper(k)] = v + } + + db.mu.Lock() + db.data = normalized + db.mu.Unlock() + + return nil +} + +// LookupVendor returns the vendor name for a given MAC address. +// MAC can be in any format: "C0:56:E3:AA:BB:CC", "c0:56:e3:aa:bb:cc", "C0-56-E3-AA-BB-CC". +// Returns empty string if not found. +func (db *OUIDatabase) LookupVendor(mac string) string { + if len(mac) < 8 { + return "" + } + + // Normalize: uppercase and replace dashes with colons + prefix := strings.ToUpper(mac[:8]) + prefix = strings.ReplaceAll(prefix, "-", ":") + + db.mu.RLock() + vendor := db.data[prefix] + db.mu.RUnlock() + + return vendor +} + +// Size returns the number of entries in the database. +func (db *OUIDatabase) Size() int { + db.mu.RLock() + defer db.mu.RUnlock() + return len(db.data) +} diff --git a/internal/camera/discovery/probe.go b/internal/camera/discovery/probe.go new file mode 100644 index 0000000..7996da6 --- /dev/null +++ b/internal/camera/discovery/probe.go @@ -0,0 +1,180 @@ +package discovery + +import ( + "context" + "sync" + "time" + + "github.com/eduard256/Strix/internal/models" +) + +const ( + // ProbeTimeout is the overall timeout for all probes combined. + ProbeTimeout = 3 * time.Second + + // ProbeTypeUnreachable indicates the device did not respond to ping. + ProbeTypeUnreachable = "unreachable" + // ProbeTypeStandard indicates a normal IP camera (RTSP/HTTP/ONVIF). + ProbeTypeStandard = "standard" + // ProbeTypeHomeKit indicates an Apple HomeKit camera that needs PIN pairing. + ProbeTypeHomeKit = "homekit" +) + +// Prober is an interface for network probe implementations. +// Each prober discovers specific information about a device at a given IP. +// New probers can be added by implementing this interface and registering +// them with ProbeService. +type Prober interface { + // Name returns a unique identifier for this prober (e.g., "dns", "arp", "mdns"). + Name() string + // Probe runs the probe against the given IP address. + // Must respect context cancellation/timeout. + // Returns nil result if nothing was found (not an error). + Probe(ctx context.Context, ip string) (any, error) +} + +// ProbeService orchestrates multiple probers to gather information about a device. +// It first pings the device, then runs all registered probers in parallel. +type ProbeService struct { + pinger *PingProber + probers []Prober + logger interface { + Debug(string, ...any) + Error(string, error, ...any) + Info(string, ...any) + } +} + +// NewProbeService creates a new ProbeService with the given probers. +// The ping prober is always included and runs first. +func NewProbeService( + probers []Prober, + logger interface { + Debug(string, ...any) + Error(string, error, ...any) + Info(string, ...any) + }, +) *ProbeService { + return &ProbeService{ + pinger: &PingProber{}, + probers: probers, + logger: logger, + } +} + +// Probe runs ping + all registered probers against the given IP. +// Overall timeout is 3 seconds. Results are collected from whatever +// finishes in time; slow probers are omitted (nil in response). +func (s *ProbeService) Probe(ctx context.Context, ip string) *models.ProbeResponse { + ctx, cancel := context.WithTimeout(ctx, ProbeTimeout) + defer cancel() + + response := &models.ProbeResponse{ + IP: ip, + Type: ProbeTypeStandard, + } + + // Step 1: Ping + s.logger.Debug("probing device", "ip", ip) + + pingResult, err := s.pinger.Ping(ctx, ip) + if err != nil || !pingResult.Reachable { + errMsg := "device unreachable" + if err != nil { + errMsg = err.Error() + } + s.logger.Debug("ping failed", "ip", ip, "error", errMsg) + response.Reachable = false + response.Type = ProbeTypeUnreachable + response.Error = errMsg + return response + } + + response.Reachable = true + response.LatencyMs = pingResult.LatencyMs + s.logger.Debug("ping OK", "ip", ip, "latency_ms", pingResult.LatencyMs) + + // Step 2: Run all probers in parallel + type probeResult struct { + name string + data any + err error + } + + results := make(chan probeResult, len(s.probers)) + var wg sync.WaitGroup + + for _, p := range s.probers { + wg.Add(1) + go func(prober Prober) { + defer wg.Done() + data, err := prober.Probe(ctx, ip) + results <- probeResult{ + name: prober.Name(), + data: data, + err: err, + } + }(p) + } + + // Close results channel when all probers finish + go func() { + wg.Wait() + close(results) + }() + + // Collect results + for r := range results { + if r.err != nil { + s.logger.Debug("prober failed", "prober", r.name, "error", r.err.Error()) + continue + } + if r.data == nil { + continue + } + + switch r.name { + case "dns": + if v, ok := r.data.(*models.DNSProbeResult); ok { + response.Probes.DNS = v + } + case "arp": + if v, ok := r.data.(*models.ARPProbeResult); ok { + response.Probes.ARP = v + } + case "mdns": + if v, ok := r.data.(*models.MDNSProbeResult); ok { + response.Probes.MDNS = v + } + } + } + + // Step 3: Determine type based on probe results + response.Type = s.determineType(response) + + s.logger.Info("probe completed", + "ip", ip, + "reachable", response.Reachable, + "type", response.Type, + "latency_ms", response.LatencyMs, + ) + + return response +} + +// determineType decides the device type based on collected probe results. +func (s *ProbeService) determineType(response *models.ProbeResponse) string { + if !response.Reachable { + return ProbeTypeUnreachable + } + + // HomeKit camera that is not yet paired + if response.Probes.MDNS != nil && !response.Probes.MDNS.Paired { + category := response.Probes.MDNS.Category + if category == "camera" || category == "doorbell" { + return ProbeTypeHomeKit + } + } + + return ProbeTypeStandard +} diff --git a/internal/camera/discovery/prober_arp.go b/internal/camera/discovery/prober_arp.go new file mode 100644 index 0000000..ce7968c --- /dev/null +++ b/internal/camera/discovery/prober_arp.go @@ -0,0 +1,80 @@ +package discovery + +import ( + "bufio" + "context" + "fmt" + "os" + "strings" + + "github.com/eduard256/Strix/internal/models" +) + +// ARPProber looks up the MAC address from the system ARP table +// and resolves it to a vendor name using the OUI database. +type ARPProber struct { + ouiDB *OUIDatabase +} + +// NewARPProber creates a new ARP prober with the given OUI database. +func NewARPProber(ouiDB *OUIDatabase) *ARPProber { + return &ARPProber{ouiDB: ouiDB} +} + +func (p *ARPProber) Name() string { return "arp" } + +// Probe looks up the MAC address for the given IP in the ARP table. +// Returns nil if the IP is not in the ARP table (e.g., different subnet, VPN). +// This only works on Linux (reads /proc/net/arp). +func (p *ARPProber) Probe(ctx context.Context, ip string) (any, error) { + mac, err := p.lookupARP(ip) + if err != nil || mac == "" { + return nil, nil // Not in ARP table is not an error + } + + vendor := "" + if p.ouiDB != nil { + vendor = p.ouiDB.LookupVendor(mac) + } + + return &models.ARPProbeResult{ + MAC: mac, + Vendor: vendor, + }, nil +} + +// lookupARP reads /proc/net/arp to find the MAC address for the given IP. +// +// Format of /proc/net/arp: +// +// IP address HW type Flags HW address Mask Device +// 192.168.1.1 0x1 0x2 aa:bb:cc:dd:ee:ff * eth0 +func (p *ARPProber) lookupARP(ip string) (string, error) { + file, err := os.Open("/proc/net/arp") + if err != nil { + return "", fmt.Errorf("failed to open ARP table: %w", err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + scanner.Scan() // Skip header line + + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) < 4 { + continue + } + + // fields[0] = IP address, fields[3] = HW address + if fields[0] == ip { + mac := fields[3] + // "00:00:00:00:00:00" means incomplete ARP entry + if mac == "00:00:00:00:00:00" { + return "", nil + } + return strings.ToUpper(mac), nil + } + } + + return "", nil +} diff --git a/internal/camera/discovery/prober_dns.go b/internal/camera/discovery/prober_dns.go new file mode 100644 index 0000000..d5abc9e --- /dev/null +++ b/internal/camera/discovery/prober_dns.go @@ -0,0 +1,36 @@ +package discovery + +import ( + "context" + "net" + "strings" + + "github.com/eduard256/Strix/internal/models" +) + +// DNSProber performs reverse DNS lookup to find the hostname of a device. +type DNSProber struct{} + +func (p *DNSProber) Name() string { return "dns" } + +// Probe performs a reverse DNS lookup on the given IP. +// Returns nil if no hostname is found (not an error). +func (p *DNSProber) Probe(ctx context.Context, ip string) (any, error) { + resolver := net.DefaultResolver + + names, err := resolver.LookupAddr(ctx, ip) + if err != nil || len(names) == 0 { + return nil, nil // No hostname found is not an error + } + + // LookupAddr returns FQDNs with trailing dot, remove it + hostname := strings.TrimSuffix(names[0], ".") + + if hostname == "" { + return nil, nil + } + + return &models.DNSProbeResult{ + Hostname: hostname, + }, nil +} diff --git a/internal/camera/discovery/prober_mdns.go b/internal/camera/discovery/prober_mdns.go new file mode 100644 index 0000000..88e0ac9 --- /dev/null +++ b/internal/camera/discovery/prober_mdns.go @@ -0,0 +1,57 @@ +package discovery + +import ( + "context" + + "github.com/AlexxIT/go2rtc/pkg/hap" + "github.com/AlexxIT/go2rtc/pkg/mdns" + "github.com/eduard256/Strix/internal/models" +) + +// MDNSProber performs mDNS unicast query to detect HomeKit devices. +// It sends a DNS query to ip:5353 for the _hap._tcp.local. service +// and parses TXT records to extract device information. +type MDNSProber struct{} + +func (p *MDNSProber) Name() string { return "mdns" } + +// Probe queries the device for HomeKit (HAP) mDNS service. +// Returns nil if the device does not advertise HomeKit or is not a camera/doorbell. +func (p *MDNSProber) Probe(ctx context.Context, ip string) (any, error) { + // Unicast mDNS query directly to the device IP. + // mdns.Query has internal timeouts (~1s), which fits within our 3s budget. + entry, err := mdns.Query(ip, mdns.ServiceHAP) + if err != nil || entry == nil { + return nil, nil // Not a HomeKit device is not an error + } + + // Check if it's complete (has IP, port, and TXT records) + if !entry.Complete() { + return nil, nil + } + + // Check if it's a camera or doorbell + category := entry.Info[hap.TXTCategory] + if category != hap.CategoryCamera && category != hap.CategoryDoorbell { + return nil, nil // Not a camera/doorbell, ignore + } + + // Map category ID to human-readable name + categoryName := "camera" + if category == hap.CategoryDoorbell { + categoryName = "doorbell" + } + + // Determine paired status: sf=0 means paired, sf=1 means not paired + paired := entry.Info[hap.TXTStatusFlags] == hap.StatusPaired + + return &models.MDNSProbeResult{ + Name: entry.Name, + DeviceID: entry.Info[hap.TXTDeviceID], + Model: entry.Info[hap.TXTModel], + Category: categoryName, + Paired: paired, + Port: int(entry.Port), + Feature: entry.Info[hap.TXTFeatureFlags], + }, nil +} diff --git a/internal/camera/discovery/prober_ping.go b/internal/camera/discovery/prober_ping.go new file mode 100644 index 0000000..4e00118 --- /dev/null +++ b/internal/camera/discovery/prober_ping.go @@ -0,0 +1,127 @@ +package discovery + +import ( + "context" + "fmt" + "net" + "time" +) + +// PingResult contains the result of a ping probe. +type PingResult struct { + Reachable bool + LatencyMs float64 +} + +// PingProber checks if a device is reachable on the network. +// It tries ICMP ping first (requires root/CAP_NET_RAW), then falls back +// to TCP connect on common camera ports (80, 554, 443, 8080). +type PingProber struct{} + +// Ping checks if the device at the given IP is reachable. +func (p *PingProber) Ping(ctx context.Context, ip string) (*PingResult, error) { + // Try ICMP first (works if running as root or with CAP_NET_RAW) + result, err := p.tryICMP(ctx, ip) + if err == nil { + return result, nil + } + + // Fallback: TCP connect on common camera ports + result, err = p.tryTCP(ctx, ip) + if err == nil { + return result, nil + } + + return &PingResult{Reachable: false}, fmt.Errorf("device unreachable: %s", ip) +} + +// tryICMP attempts an ICMP ping using raw socket. +func (p *PingProber) tryICMP(ctx context.Context, ip string) (*PingResult, error) { + deadline, ok := ctx.Deadline() + if !ok { + deadline = time.Now().Add(2 * time.Second) + } + + timeout := time.Until(deadline) + if timeout <= 0 { + return nil, context.DeadlineExceeded + } + // Cap ICMP timeout to 2 seconds to leave time for other probes + if timeout > 2*time.Second { + timeout = 2 * time.Second + } + + start := time.Now() + conn, err := net.DialTimeout("ip4:icmp", ip, timeout) + if err != nil { + return nil, err + } + conn.Close() + + return &PingResult{ + Reachable: true, + LatencyMs: float64(time.Since(start).Microseconds()) / 1000.0, + }, nil +} + +// tryTCP attempts TCP connect on common camera ports as a ping fallback. +// This works without root privileges and is reliable for cameras since +// they almost always have at least one of these ports open. +func (p *PingProber) tryTCP(ctx context.Context, ip string) (*PingResult, error) { + commonPorts := []int{80, 554, 443, 8080, 8443, 34567, 5353} + + deadline, ok := ctx.Deadline() + if !ok { + deadline = time.Now().Add(2 * time.Second) + } + + timeout := time.Until(deadline) + if timeout <= 0 { + return nil, context.DeadlineExceeded + } + // Cap per-port timeout + perPortTimeout := timeout / time.Duration(len(commonPorts)) + if perPortTimeout > 500*time.Millisecond { + perPortTimeout = 500 * time.Millisecond + } + + type tcpResult struct { + latency time.Duration + err error + } + + results := make(chan tcpResult, len(commonPorts)) + + for _, port := range commonPorts { + go func(port int) { + addr := fmt.Sprintf("%s:%d", ip, port) + start := time.Now() + conn, err := net.DialTimeout("tcp", addr, perPortTimeout) + if err != nil { + results <- tcpResult{err: err} + return + } + conn.Close() + results <- tcpResult{latency: time.Since(start)} + }(port) + } + + // Wait for first success or all failures + var lastErr error + for range commonPorts { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case r := <-results: + if r.err == nil { + return &PingResult{ + Reachable: true, + LatencyMs: float64(r.latency.Microseconds()) / 1000.0, + }, nil + } + lastErr = r.err + } + } + + return nil, fmt.Errorf("all TCP ports closed: %w", lastErr) +} diff --git a/internal/config/config.go b/internal/config/config.go index 719bd30..7c0cb58 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -33,6 +33,7 @@ type DatabaseConfig struct { BrandsPath string PatternsPath string ParametersPath string + OUIPath string CacheEnabled bool CacheTTL time.Duration } @@ -80,6 +81,7 @@ func Load() *Config { BrandsPath: filepath.Join(dataPath, "brands"), PatternsPath: filepath.Join(dataPath, "popular_stream_patterns.json"), ParametersPath: filepath.Join(dataPath, "query_parameters.json"), + OUIPath: filepath.Join(dataPath, "camera_oui.json"), CacheEnabled: true, CacheTTL: 5 * time.Minute, }, diff --git a/internal/models/probe.go b/internal/models/probe.go new file mode 100644 index 0000000..1306d12 --- /dev/null +++ b/internal/models/probe.go @@ -0,0 +1,45 @@ +package models + +// ProbeResponse represents the result of probing an IP address. +// The Type field determines which UI flow the frontend should use: +// - "unreachable" -- device did not respond to ping +// - "standard" -- normal IP camera (RTSP/HTTP/ONVIF) +// - "homekit" -- Apple HomeKit camera (needs PIN pairing) +type ProbeResponse struct { + IP string `json:"ip"` + Reachable bool `json:"reachable"` + LatencyMs float64 `json:"latency_ms,omitempty"` + Type string `json:"type"` + Error string `json:"error,omitempty"` + Probes ProbeResults `json:"probes"` +} + +// ProbeResults contains results from all parallel probers. +// Nil fields mean the prober did not find anything or timed out. +type ProbeResults struct { + DNS *DNSProbeResult `json:"dns"` + ARP *ARPProbeResult `json:"arp"` + MDNS *MDNSProbeResult `json:"mdns"` +} + +// DNSProbeResult contains reverse DNS lookup result. +type DNSProbeResult struct { + Hostname string `json:"hostname"` +} + +// ARPProbeResult contains ARP table lookup + OUI vendor identification. +type ARPProbeResult struct { + MAC string `json:"mac"` + Vendor string `json:"vendor"` +} + +// MDNSProbeResult contains mDNS service discovery result (HomeKit). +type MDNSProbeResult struct { + Name string `json:"name"` + DeviceID string `json:"device_id"` + Model string `json:"model"` + Category string `json:"category"` // "camera", "doorbell" + Paired bool `json:"paired"` + Port int `json:"port"` + Feature string `json:"feature"` +}