package tendrils import ( "context" "log" "net" "strings" "time" "github.com/miekg/dns" ) const ( mdnsAddr = "224.0.0.251:5353" ) func (t *Tendrils) listenMDNS(ctx context.Context, iface net.Interface) { addr, err := net.ResolveUDPAddr("udp4", mdnsAddr) if err != nil { log.Printf("[ERROR] failed to resolve mdns address: %v", err) return } conn, err := net.ListenMulticastUDP("udp4", &iface, addr) if err != nil { log.Printf("[ERROR] failed to listen mdns on %s: %v", iface.Name, err) return } defer conn.Close() go t.runMDNSQuerier(ctx, iface) buf := make([]byte, 65536) for { select { case <-ctx.Done(): return default: } conn.SetReadDeadline(time.Now().Add(1 * time.Second)) n, src, err := conn.ReadFromUDP(buf) if err != nil { if netErr, ok := err.(net.Error); ok && netErr.Timeout() { continue } continue } t.handleMDNSPacket(iface.Name, src.IP, buf[:n]) } } func (t *Tendrils) handleMDNSPacket(ifaceName string, srcIP net.IP, data []byte) { msg := new(dns.Msg) if err := msg.Unpack(data); err != nil { return } if msg.Response { t.processMDNSResponse(ifaceName, srcIP, msg) } } func (t *Tendrils) processMDNSResponse(ifaceName string, srcIP net.IP, msg *dns.Msg) { var hostname string allRecords := append(msg.Answer, msg.Extra...) for _, rr := range allRecords { switch r := rr.(type) { case *dns.A: name := strings.TrimSuffix(r.Hdr.Name, ".local.") name = strings.TrimSuffix(name, ".") if name != "" && r.A.Equal(srcIP) { hostname = name } case *dns.AAAA: continue case *dns.PTR: name := strings.TrimSuffix(r.Ptr, ".local.") name = strings.TrimSuffix(name, ".") if hostname == "" && name != "" && !strings.HasPrefix(name, "_") { hostname = name } } } if hostname != "" { if t.DebugMDNS { log.Printf("[mdns] %s: %s -> %s", ifaceName, srcIP, hostname) } t.nodes.Update(nil, nil, []net.IP{srcIP}, "", hostname, "mdns") } } func (t *Tendrils) runMDNSQuerier(ctx context.Context, iface net.Interface) { addrs, err := iface.Addrs() if err != nil { return } var srcIP net.IP for _, addr := range addrs { if ipnet, ok := addr.(*net.IPNet); ok && ipnet.IP.To4() != nil { srcIP = ipnet.IP.To4() break } } if srcIP == nil { return } ticker := time.NewTicker(60 * time.Second) defer ticker.Stop() t.sendMDNSQuery(iface.Name, srcIP) for { select { case <-ctx.Done(): return case <-ticker.C: t.sendMDNSQuery(iface.Name, srcIP) } } } func (t *Tendrils) sendMDNSQuery(ifaceName string, srcIP net.IP) { conn, err := net.DialUDP("udp4", &net.UDPAddr{IP: srcIP}, &net.UDPAddr{IP: net.IPv4(224, 0, 0, 251), Port: 5353}) if err != nil { return } defer conn.Close() msg := new(dns.Msg) msg.SetQuestion("_services._dns-sd._udp.local.", dns.TypePTR) msg.RecursionDesired = false data, err := msg.Pack() if err != nil { return } conn.Write(data) if t.DebugMDNS { log.Printf("[mdns] %s: sent query", ifaceName) } }