Update mDNS server handler

This commit is contained in:
Alex X
2025-02-25 16:16:38 +03:00
parent effff6f88d
commit c50738005d
2 changed files with 126 additions and 112 deletions

View File

@@ -13,7 +13,10 @@ import (
"github.com/miekg/dns" // awesome library for parsing mDNS records "github.com/miekg/dns" // awesome library for parsing mDNS records
) )
const ServiceHAP = "_hap._tcp.local." // HomeKit Accessory Protocol const (
ServiceDNSSD = "_services._dns-sd._udp.local."
ServiceHAP = "_hap._tcp.local." // HomeKit Accessory Protocol
)
type ServiceEntry struct { type ServiceEntry struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
@@ -153,6 +156,7 @@ type Browser struct {
Service string Service string
Addr net.Addr Addr net.Addr
Nets []*net.IPNet
Recv net.PacketConn Recv net.PacketConn
Sends []net.PacketConn Sends []net.PacketConn
@@ -165,7 +169,7 @@ type Browser struct {
// Receiver will get multicast responses on senders requests. // Receiver will get multicast responses on senders requests.
func (b *Browser) ListenMulticastUDP() error { func (b *Browser) ListenMulticastUDP() error {
// 1. Collect IPv4 interfaces // 1. Collect IPv4 interfaces
ip4s, err := InterfacesIP4() nets, err := IPNets()
if err != nil { if err != nil {
return err return err
} }
@@ -182,11 +186,12 @@ func (b *Browser) ListenMulticastUDP() error {
ctx := context.Background() ctx := context.Background()
for _, ip4 := range ip4s { for _, ipn := range nets {
conn, err := lc1.ListenPacket(ctx, "udp4", ip4.String()+":5353") // same port important conn, err := lc1.ListenPacket(ctx, "udp4", ipn.IP.String()+":5353") // same port important
if err != nil { if err != nil {
continue continue
} }
b.Nets = append(b.Nets, ipn)
b.Sends = append(b.Sends, conn) b.Sends = append(b.Sends, conn)
} }
@@ -365,35 +370,39 @@ func NewServiceEntries(msg *dns.Msg, ip net.IP) (entries []*ServiceEntry) {
return return
} }
func InterfacesIP4() ([]net.IP, error) { // Common docker addresses (class B):
// https://en.wikipedia.org/wiki/Private_network
// - docker0 172.17.0.1/16
// - br-xxxx 172.18.0.1/16
// - hassio 172.30.32.1/23
var docker = net.IPNet{
IP: []byte{172, 16, 0, 0},
Mask: []byte{255, 240, 0, 0},
}
func IPNets() ([]*net.IPNet, error) {
intfs, err := net.Interfaces() intfs, err := net.Interfaces()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var ips []net.IP var nets []*net.IPNet
loop:
for _, intf := range intfs { for _, intf := range intfs {
if intf.Flags&net.FlagUp == 0 || intf.Flags&net.FlagLoopback != 0 { if intf.Flags&net.FlagUp == 0 || intf.Flags&net.FlagLoopback != 0 {
continue continue
} }
addrs, err := intf.Addrs() addrs, _ := intf.Addrs()
if err != nil {
continue
}
for _, addr := range addrs { for _, addr := range addrs {
switch v := addr.(type) { switch v := addr.(type) {
case *net.IPNet: case *net.IPNet:
if ip := v.IP.To4(); ip != nil { if ip := v.IP.To4(); ip != nil && !docker.Contains(ip) {
ips = append(ips, ip) nets = append(nets, v)
continue loop
} }
} }
} }
} }
return ips, nil return nets, nil
} }

View File

@@ -20,7 +20,11 @@ func Serve(service string, entries []*ServiceEntry) error {
} }
func (b *Browser) Serve(entries []*ServiceEntry) error { func (b *Browser) Serve(entries []*ServiceEntry) error {
var msg dns.Msg names := make(map[string]*ServiceEntry, len(entries))
for _, entry := range entries {
name := entry.name() + "." + b.Service
names[name] = entry
}
buf := make([]byte, 1500) buf := make([]byte, 1500)
for { for {
@@ -29,51 +33,86 @@ func (b *Browser) Serve(entries []*ServiceEntry) error {
break break
} }
if err = msg.Unpack(buf[:n]); err != nil { var req dns.Msg // request
if err = req.Unpack(buf[:n]); err != nil {
continue continue
} }
if !HasQuestionPTP(&msg, b.Service) { // skip messages without Questions
if req.Question == nil {
continue continue
} }
remoteIP := addr.(*net.UDPAddr).IP remoteIP := addr.(*net.UDPAddr).IP
localIP := MatchLocalIP(remoteIP) localIP := b.MatchLocalIP(remoteIP)
// skip messages from unknown networks (can be docker network)
if localIP == nil { if localIP == nil {
continue continue
} }
answer, err := NewDNSAnswer(entries, b.Service, localIP).Pack() var res dns.Msg // response
for _, q := range req.Question {
if q.Qtype != dns.TypePTR || q.Qclass != dns.ClassINET {
continue
}
if q.Name == ServiceDNSSD {
AppendDNSSD(&res, b.Service)
} else if q.Name == b.Service {
for _, entry := range entries {
AppendEntry(&res, entry, b.Service, localIP)
}
} else if entry, ok := names[q.Name]; ok {
AppendEntry(&res, entry, b.Service, localIP)
}
}
if res.Answer == nil {
continue
}
res.MsgHdr.Response = true
res.MsgHdr.Authoritative = true
data, err := res.Pack()
if err != nil { if err != nil {
continue continue
} }
for _, send := range b.Sends { for _, send := range b.Sends {
_, _ = send.WriteTo(answer, MulticastAddr) _, _ = send.WriteTo(data, MulticastAddr)
} }
} }
return nil return nil
} }
func HasQuestionPTP(msg *dns.Msg, name string) bool { func (b *Browser) MatchLocalIP(remote net.IP) net.IP {
for _, q := range msg.Question { for _, ipn := range b.Nets {
if q.Qtype == dns.TypePTR && q.Name == name { if ipn.Contains(remote) {
return true return ipn.IP
} }
} }
return false return nil
} }
func NewDNSAnswer(entries []*ServiceEntry, service string, ip net.IP) *dns.Msg { func AppendDNSSD(msg *dns.Msg, service string) {
msg := dns.Msg{ msg.Answer = append(
MsgHdr: dns.MsgHdr{ msg.Answer,
Response: true, &dns.PTR{
Authoritative: true, Hdr: dns.RR_Header{
Name: ServiceDNSSD, // _services._dns-sd._udp.local.
Rrtype: dns.TypePTR, // 12
Class: dns.ClassINET, // 1
Ttl: 4500,
}, },
} Ptr: service, // _home-assistant._tcp.local.
},
)
}
for _, entry := range entries { func AppendEntry(msg *dns.Msg, entry *ServiceEntry, service string, ip net.IP) {
ptrName := entry.name() + "." + service ptrName := entry.name() + "." + service
srvName := entry.name() + ".local." srvName := entry.name() + ".local."
@@ -81,77 +120,43 @@ func NewDNSAnswer(entries []*ServiceEntry, service string, ip net.IP) *dns.Msg {
msg.Answer, msg.Answer,
&dns.PTR{ &dns.PTR{
Hdr: dns.RR_Header{ Hdr: dns.RR_Header{
Name: service, Name: service, // _home-assistant._tcp.local.
Rrtype: dns.TypePTR, Rrtype: dns.TypePTR, // 12
Class: dns.ClassINET, Class: dns.ClassINET, // 1
Ttl: 4500, Ttl: 4500,
}, },
Ptr: ptrName, Ptr: ptrName, // Home\ Assistant._home-assistant._tcp.local.
}, },
) )
msg.Extra = append( msg.Extra = append(
msg.Extra, msg.Extra,
&dns.TXT{ &dns.TXT{
Hdr: dns.RR_Header{ Hdr: dns.RR_Header{
Name: ptrName, Name: ptrName, // Home\ Assistant._home-assistant._tcp.local.
Rrtype: dns.TypeTXT, Rrtype: dns.TypeTXT, // 16
Class: ClassCacheFlush, Class: ClassCacheFlush, // 32769
Ttl: 4500, Ttl: 4500,
}, },
Txt: entry.TXT(), Txt: entry.TXT(),
}, },
&dns.SRV{ &dns.SRV{
Hdr: dns.RR_Header{ Hdr: dns.RR_Header{
Name: ptrName, Name: ptrName, // Home\ Assistant._home-assistant._tcp.local.
Rrtype: dns.TypeSRV, Rrtype: dns.TypeSRV, // 33
Class: ClassCacheFlush, Class: ClassCacheFlush, // 32769
Ttl: 120, Ttl: 120,
Rdlength: 0,
}, },
Port: entry.Port, Port: entry.Port, // 8123
Target: srvName, Target: srvName, // 963f1fa82b7142809711cebe7c826322.local.
}, },
&dns.A{ &dns.A{
Hdr: dns.RR_Header{ Hdr: dns.RR_Header{
Name: srvName, Name: srvName, // 963f1fa82b7142809711cebe7c826322.local.
Rrtype: dns.TypeA, Rrtype: dns.TypeA, // 1
Class: ClassCacheFlush, Class: ClassCacheFlush, // 32769
Ttl: 120, Ttl: 120,
Rdlength: 0,
}, },
A: ip, A: ip,
}, },
) )
}
return &msg
}
func MatchLocalIP(remote net.IP) net.IP {
intfs, err := net.Interfaces()
if err != nil {
return nil
}
for _, intf := range intfs {
if intf.Flags&net.FlagUp == 0 || intf.Flags&net.FlagLoopback != 0 {
continue
}
addrs, err := intf.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
switch v := addr.(type) {
case *net.IPNet:
if local := v.IP.To4(); local != nil && v.Contains(remote) {
return local
}
}
}
}
return nil
} }