Files
go2rtc/pkg/mdns/mdns.go
T
2023-07-11 07:37:06 +03:00

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
}