142 lines
2.5 KiB
Go
142 lines
2.5 KiB
Go
package mdns
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/miekg/dns"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const ServiceHAP = "_hap._tcp.local." // HomeKit Accessory Protocol
|
|
|
|
const requestTimeout = time.Millisecond * 505
|
|
const responseTimeout = time.Second * 2
|
|
|
|
type ServiceEntry struct {
|
|
Name string
|
|
IP net.IP
|
|
Port uint16
|
|
Info map[string]string
|
|
}
|
|
|
|
func (e *ServiceEntry) Complete() bool {
|
|
return e.IP != nil && e.Port > 0 && e.Info != nil
|
|
}
|
|
|
|
func (e *ServiceEntry) Addr() string {
|
|
return fmt.Sprintf("%s:%d", e.IP, e.Port)
|
|
}
|
|
|
|
func Discovery(service string, onentry func(*ServiceEntry) bool) error {
|
|
addr := &net.UDPAddr{
|
|
IP: net.IP{224, 0, 0, 251},
|
|
Port: 5353,
|
|
}
|
|
|
|
conn, err := net.ListenMulticastUDP("udp4", nil, addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer conn.Close()
|
|
|
|
if err = conn.SetDeadline(time.Now().Add(responseTimeout)); err != nil {
|
|
return err
|
|
}
|
|
|
|
msg := &dns.Msg{
|
|
Question: []dns.Question{
|
|
{service, dns.TypePTR, dns.ClassINET},
|
|
},
|
|
}
|
|
|
|
b1, err := msg.Pack()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
go func() {
|
|
for {
|
|
if _, err := conn.WriteToUDP(b1, addr); err != nil {
|
|
return
|
|
}
|
|
time.Sleep(requestTimeout)
|
|
}
|
|
}()
|
|
|
|
var skipPTR []string
|
|
|
|
b2 := make([]byte, 1500)
|
|
loop:
|
|
for {
|
|
// in the Hass docker network can receive same msg from different address
|
|
n, _, err := conn.ReadFromUDP(b2)
|
|
if err != nil {
|
|
break
|
|
}
|
|
|
|
if err = msg.Unpack(b2[:n]); err != nil {
|
|
continue
|
|
}
|
|
|
|
ptr := GetPTR(msg)
|
|
|
|
if !strings.HasSuffix(ptr, service) {
|
|
continue
|
|
}
|
|
|
|
for _, s := range skipPTR {
|
|
if s == ptr {
|
|
continue loop
|
|
}
|
|
}
|
|
|
|
if entry := NewServiceEntry(msg); onentry(entry) {
|
|
break
|
|
}
|
|
|
|
skipPTR = append(skipPTR, ptr)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func GetPTR(msg *dns.Msg) string {
|
|
for _, rr := range msg.Answer {
|
|
if rr, ok := rr.(*dns.PTR); ok {
|
|
return rr.Ptr
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func NewServiceEntry(msg *dns.Msg) *ServiceEntry {
|
|
entry := &ServiceEntry{}
|
|
|
|
records := make([]dns.RR, 0, len(msg.Answer)+len(msg.Ns)+len(msg.Extra))
|
|
records = append(records, msg.Answer...)
|
|
records = append(records, msg.Ns...)
|
|
records = append(records, msg.Extra...)
|
|
for _, record := range records {
|
|
switch record := record.(type) {
|
|
case *dns.PTR:
|
|
if i := strings.IndexByte(record.Ptr, '.'); i > 0 {
|
|
entry.Name = record.Ptr[:i]
|
|
}
|
|
case *dns.A:
|
|
entry.IP = record.A
|
|
case *dns.SRV:
|
|
entry.Port = record.Port
|
|
case *dns.TXT:
|
|
entry.Info = make(map[string]string, len(record.Txt))
|
|
for _, txt := range record.Txt {
|
|
k, v, _ := strings.Cut(txt, "=")
|
|
entry.Info[k] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
return entry
|
|
}
|