// parsePacket is used to parse an incoming packet func (s *Server) parsePacket(packet []byte, from net.Addr) error { var msg dns.Msg if err := msg.Unpack(packet); err != nil { log.Printf("[ERR] mdns: Failed to unpack packet: %v", err) return err } return s.handleQuery(&msg, from) }
// sendQuery is used to multicast a query out func (c *client) sendQuery(q *dns.Msg) error { buf, err := q.Pack() if err != nil { return err } if c.ipv4UnicastConn != nil { c.ipv4UnicastConn.WriteToUDP(buf, ipv4Addr) } if c.ipv6UnicastConn != nil { c.ipv6UnicastConn.WriteToUDP(buf, ipv6Addr) } return nil }
// sendResponse is used to send a response packet func (s *Server) sendResponse(resp *dns.Msg, from net.Addr, unicast bool) error { // TODO(reddaly): Respect the unicast argument, and allow sending responses // over multicast. buf, err := resp.Pack() if err != nil { return err } // Determine the socket to send from addr := from.(*net.UDPAddr) if addr.IP.To4() != nil { _, err = s.ipv4List.WriteToUDP(buf, addr) return err } else { _, err = s.ipv6List.WriteToUDP(buf, addr) return err } }
// recv is used to receive until we get a shutdown func (c *client) recv(l *net.UDPConn, msgCh chan *dns.Msg) { if l == nil { return } buf := make([]byte, 65536) for !c.closed { n, err := l.Read(buf) if err != nil { log.Printf("[ERR] mdns: Failed to read packet: %v", err) continue } msg := new(dns.Msg) if err := msg.Unpack(buf[:n]); err != nil { log.Printf("[ERR] mdns: Failed to unpack packet: %v", err) continue } select { case msgCh <- msg: case <-c.closedCh: return } } }
// query is used to perform a lookup and stream results func (c *client) query(params *QueryParam) error { // Create the service name serviceAddr := fmt.Sprintf("%s.%s.", trimDot(params.Service), trimDot(params.Domain)) // Start listening for response packets msgCh := make(chan *dns.Msg, 32) go c.recv(c.ipv4UnicastConn, msgCh) go c.recv(c.ipv6UnicastConn, msgCh) go c.recv(c.ipv4MulticastConn, msgCh) go c.recv(c.ipv6MulticastConn, msgCh) // Send the query m := new(dns.Msg) m.SetQuestion(serviceAddr, dns.TypePTR) // RFC 6762, section 18.12. Repurposing of Top Bit of qclass in Question // Section // // In the Question Section of a Multicast DNS query, the top bit of the qclass // field is used to indicate that unicast responses are preferred for this // particular question. (See Section 5.4.) if params.WantUnicastResponse { m.Question[0].Qclass |= 1 << 15 } m.RecursionDesired = false if err := c.sendQuery(m); err != nil { return err } // Map the in-progress responses inprogress := make(map[string]*ServiceEntry) // Listen until we reach the timeout finish := time.After(params.Timeout) for { select { case resp := <-msgCh: var inp *ServiceEntry for _, answer := range append(resp.Answer, resp.Extra...) { // TODO(reddaly): Check that response corresponds to serviceAddr? switch rr := answer.(type) { case *dns.PTR: // Create new entry for this inp = ensureName(inprogress, rr.Ptr) case *dns.SRV: // Check for a target mismatch if rr.Target != rr.Hdr.Name { alias(inprogress, rr.Hdr.Name, rr.Target) } // Get the port inp = ensureName(inprogress, rr.Hdr.Name) inp.Host = rr.Target inp.Port = int(rr.Port) case *dns.TXT: // Pull out the txt inp = ensureName(inprogress, rr.Hdr.Name) inp.Info = strings.Join(rr.Txt, "|") inp.hasTXT = true case *dns.A: // Pull out the IP inp = ensureName(inprogress, rr.Hdr.Name) inp.Addr = rr.A // @Deprecated inp.AddrV4 = rr.A case *dns.AAAA: // Pull out the IP inp = ensureName(inprogress, rr.Hdr.Name) inp.Addr = rr.AAAA // @Deprecated inp.AddrV6 = rr.AAAA } } if inp == nil { continue } // Check if this entry is complete if inp.complete() { if inp.sent { continue } inp.sent = true select { case params.Entries <- inp: default: } } else { // Fire off a node specific query m := new(dns.Msg) m.SetQuestion(inp.Name, dns.TypePTR) m.RecursionDesired = false if err := c.sendQuery(m); err != nil { log.Printf("[ERR] mdns: Failed to query instance %s: %v", inp.Name, err) } } case <-finish: return nil } } }