Files
Strix/internal/camera/discovery/prober_mdns.go
T
eduard256 4d6c2fd878 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)
2026-03-16 13:57:41 +00:00

58 lines
1.7 KiB
Go

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
}