Switch mDNS probe to multicast, use mDNS for reachability

Unicast mDNS queries (direct to IP:5353) are ignored by some HomeKit
devices. Switch to multicast (224.0.0.251:5353) and filter responses
by sender IP. Also consider mDNS response as reachability signal.

Split probe timeouts: 100ms for ports/DNS/HTTP, 120ms total to give
mDNS extra time. HomeKit responds in ~0.2ms via multicast.
This commit is contained in:
eduard256
2026-04-05 12:06:11 +00:00
parent f084135701
commit e2e24c7578
2 changed files with 34 additions and 20 deletions
+9 -5
View File
@@ -16,7 +16,7 @@ import (
_ "modernc.org/sqlite"
)
const probeTimeout = 100 * time.Millisecond
const probeTimeout = 120 * time.Millisecond
var log zerolog.Logger
var db *sql.DB
@@ -78,14 +78,17 @@ func runProbe(parent context.Context, ip string) *probe.Response {
}()
}
fastCtx, fastCancel := context.WithTimeout(ctx, 100*time.Millisecond)
defer fastCancel()
run(func() {
r, _ := probe.ScanPorts(ctx, ip, ports)
r, _ := probe.ScanPorts(fastCtx, ip, ports)
mu.Lock()
resp.Probes.Ports = r
mu.Unlock()
})
run(func() {
r, _ := probe.ReverseDNS(ctx, ip)
r, _ := probe.ReverseDNS(fastCtx, ip)
mu.Lock()
resp.Probes.DNS = r
mu.Unlock()
@@ -107,7 +110,7 @@ func runProbe(parent context.Context, ip string) *probe.Response {
mu.Unlock()
})
run(func() {
r, _ := probe.ProbeHTTP(ctx, ip, nil)
r, _ := probe.ProbeHTTP(fastCtx, ip, nil)
mu.Lock()
resp.Probes.HTTP = r
mu.Unlock()
@@ -116,7 +119,8 @@ func runProbe(parent context.Context, ip string) *probe.Response {
wg.Wait()
// determine reachable
resp.Reachable = resp.Probes.Ports != nil && len(resp.Probes.Ports.Open) > 0
resp.Reachable = (resp.Probes.Ports != nil && len(resp.Probes.Ports.Open) > 0) ||
resp.Probes.MDNS != nil
// determine type
resp.Type = "standard"
+25 -15
View File
@@ -22,8 +22,11 @@ const (
categoryDoorbell = "18"
)
// QueryHAP sends unicast mDNS query to ip:5353 for HomeKit service.
// Returns nil if device is not a HomeKit camera/doorbell.
var multicastAddr = &net.UDPAddr{IP: net.IP{224, 0, 0, 251}, Port: 5353}
// QueryHAP sends multicast mDNS query for HomeKit service and waits
// for a response from the specified ip. Returns nil if device is not
// a HomeKit camera/doorbell.
func QueryHAP(ctx context.Context, ip string) (*MDNSResult, error) {
msg := &dns.Msg{
Question: []dns.Question{
@@ -36,7 +39,7 @@ func QueryHAP(ctx context.Context, ip string) (*MDNSResult, error) {
return nil, err
}
conn, err := net.ListenPacket("udp4", ":0")
conn, err := net.ListenMulticastUDP("udp4", nil, multicastAddr)
if err != nil {
return nil, err
}
@@ -44,27 +47,34 @@ func QueryHAP(ctx context.Context, ip string) (*MDNSResult, error) {
deadline, ok := ctx.Deadline()
if !ok {
deadline = time.Now().Add(100 * time.Millisecond)
deadline = time.Now().Add(time.Second)
}
_ = conn.SetDeadline(deadline)
addr := &net.UDPAddr{IP: net.ParseIP(ip), Port: 5353}
if _, err = conn.WriteTo(query, addr); err != nil {
if _, err = conn.WriteTo(query, multicastAddr); err != nil {
return nil, err
}
targetIP := net.ParseIP(ip)
buf := make([]byte, 1500)
n, _, err := conn.ReadFrom(buf)
if err != nil {
return nil, nil // timeout = not a HomeKit device
}
var resp dns.Msg
if err = resp.Unpack(buf[:n]); err != nil {
return nil, nil
}
for {
n, from, err := conn.ReadFrom(buf)
if err != nil {
return nil, nil // timeout
}
return parseHAPResponse(&resp)
if !from.(*net.UDPAddr).IP.Equal(targetIP) {
continue
}
var resp dns.Msg
if err = resp.Unpack(buf[:n]); err != nil {
continue
}
return parseHAPResponse(&resp)
}
}
// internals