func (s *Server) composeBrowsingAnswers(resp *dns.Msg, ttl uint32) { ptr := &dns.PTR{ Hdr: dns.RR_Header{ Name: s.service.ServiceName(), Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: ttl, }, Ptr: s.service.ServiceInstanceName(), } resp.Answer = append(resp.Answer, ptr) txt := &dns.TXT{ Hdr: dns.RR_Header{ Name: s.service.ServiceInstanceName(), Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: ttl, }, Txt: s.service.Text, } srv := &dns.SRV{ Hdr: dns.RR_Header{ Name: s.service.ServiceInstanceName(), Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: ttl, }, Priority: 0, Weight: 0, Port: uint16(s.service.Port), Target: s.service.HostName, } resp.Extra = append(resp.Extra, srv, txt) if s.service.AddrIPv4 != nil { a := &dns.A{ Hdr: dns.RR_Header{ Name: s.service.HostName, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: ttl, }, A: s.service.AddrIPv4, } resp.Extra = append(resp.Extra, a) } if s.service.AddrIPv6 != nil { aaaa := &dns.AAAA{ Hdr: dns.RR_Header{ Name: s.service.HostName, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: ttl, }, AAAA: s.service.AddrIPv6, } resp.Extra = append(resp.Extra, aaaa) } }
// Sign signs a message m, it takes care of negative or nodata responses as // well by synthesising NSEC3 records. It will also cache the signatures, using // a hash of the signed data as a key. // We also fake the origin TTL in the signature, because we don't want to // throw away signatures when services decide to have longer TTL. So we just // set the origTTL to 60. // TODO(miek): revisit origTTL func (s *server) Sign(m *dns.Msg, bufsize uint16) { now := time.Now().UTC() incep := uint32(now.Add(-3 * time.Hour).Unix()) // 2+1 hours, be sure to catch daylight saving time and such expir := uint32(now.Add(7 * 24 * time.Hour).Unix()) // sign for a week defer func() { promCacheSize.WithLabelValues("signature").Set(float64(s.scache.Size())) }() for _, r := range rrSets(m.Answer) { if r[0].Header().Rrtype == dns.TypeRRSIG { continue } if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { continue } if sig, err := s.signSet(r, now, incep, expir); err == nil { m.Answer = append(m.Answer, sig) } } for _, r := range rrSets(m.Ns) { if r[0].Header().Rrtype == dns.TypeRRSIG { continue } if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { continue } if sig, err := s.signSet(r, now, incep, expir); err == nil { m.Ns = append(m.Ns, sig) } } for _, r := range rrSets(m.Extra) { if r[0].Header().Rrtype == dns.TypeRRSIG || r[0].Header().Rrtype == dns.TypeOPT { continue } if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { continue } if sig, err := s.signSet(r, now, incep, expir); err == nil { m.Extra = append(m.Extra, sig) } } if bufsize >= 512 || bufsize <= 4096 { // TCP here? promErrorCount.WithLabelValues("truncated").Inc() m.Truncated = m.Len() > int(bufsize) } o := new(dns.OPT) o.Hdr.Name = "." o.Hdr.Rrtype = dns.TypeOPT o.SetDo() o.SetUDPSize(4096) // TODO(miek): echo client m.Extra = append(m.Extra, o) return }
func (c *config) registerVersionHandler() { // special handler for reporting version: dig . @host TXT dns.HandleFunc(".", func(w dns.ResponseWriter, req *dns.Msg) { m := new(dns.Msg) m.SetReply(req) if req.Question[0].Name == "." && req.Question[0].Qtype == dns.TypeTXT { m.Authoritative = true m.Answer = []dns.RR{} m.Answer = append(m.Answer, &dns.TXT{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0}, Txt: []string{"v" + version}}) m.Extra = []dns.RR{} m.Extra = append(m.Extra, &dns.TXT{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0}, Txt: []string{"NedDNS"}}) } w.WriteMsg(m) }) }
func Respond(w dns.ResponseWriter, req *dns.Msg, records []dns.RR) { m := new(dns.Msg) m.SetReply(req) m.Authoritative = true m.RecursionAvailable = true m.Compress = true m.Answer = records // Figure out the max response size bufsize := uint16(512) tcp := isTcp(w) if o := req.IsEdns0(); o != nil { bufsize = o.UDPSize() } if tcp { bufsize = dns.MaxMsgSize - 1 } else if bufsize < 512 { bufsize = 512 } if m.Len() > dns.MaxMsgSize { fqdn := dns.Fqdn(req.Question[0].Name) log.WithFields(log.Fields{"fqdn": fqdn}).Debug("Response too big, dropping Extra") m.Extra = nil if m.Len() > dns.MaxMsgSize { log.WithFields(log.Fields{"fqdn": fqdn}).Debug("Response still too big") m := new(dns.Msg) m.SetRcode(m, dns.RcodeServerFailure) } } if m.Len() > int(bufsize) && !tcp { log.Debug("Too big 1") m.Extra = nil if m.Len() > int(bufsize) { log.Debug("Too big 2") m.Answer = nil m.Truncated = true } } err := w.WriteMsg(m) if err != nil { log.Warn("Failed to return reply: ", err, m.Len()) } }
func findGlue(m *dns.Msg, z *dns.Zone, nameserver string) { glue := z.Find(nameserver) if glue != nil { if a4, ok := glue.RR[dns.TypeAAAA]; ok { m.Extra = append(m.Extra, a4...) return } if a, ok := glue.RR[dns.TypeA]; ok { m.Extra = append(m.Extra, a...) return } } // length or the returned packet! TODO(mg) return }
func dnsAppend(q dns.Question, m *dns.Msg, rr dns.RR) { hdr := dns.RR_Header{Name: q.Name, Class: q.Qclass, Ttl: 0} if rrS, ok := rr.(*dns.A); ok { hdr.Rrtype = dns.TypeA rrS.Hdr = hdr } else if rrS, ok := rr.(*dns.AAAA); ok { hdr.Rrtype = dns.TypeAAAA rrS.Hdr = hdr } else if rrS, ok := rr.(*dns.CNAME); ok { hdr.Rrtype = dns.TypeCNAME rrS.Hdr = hdr } else if rrS, ok := rr.(*dns.TXT); ok { hdr.Rrtype = dns.TypeTXT rrS.Hdr = hdr } else { log.Printf("error: unknown dnsAppend RR type: %+v\n", rr) return } if q.Qtype == dns.TypeANY || q.Qtype == rr.Header().Rrtype { m.Answer = append(m.Answer, rr) } else { m.Extra = append(m.Extra, rr) } }
func lookup(msg *dns.Msg, client *dns.Client, server string, edns bool) (*dns.Msg, error) { if edns { opt := &dns.OPT{ Hdr: dns.RR_Header{ Name: ".", Rrtype: dns.TypeOPT, }, } opt.SetUDPSize(dns.DefaultMsgSize) msg.Extra = append(msg.Extra, opt) } response, _, err := client.Exchange(msg, server) if err != nil { return nil, err } if msg.Id != response.Id { return nil, fmt.Errorf("DNS ID mismatch, request: %d, response: %d", msg.Id, response.Id) } if response.MsgHdr.Truncated { if client.Net == "tcp" { return nil, fmt.Errorf("Got truncated message on tcp") } if edns { // Truncated even though EDNS is used client.Net = "tcp" } return lookup(msg, client, server, !edns) } return response, nil }
// serviceARecords is used to add the SRV records for a service lookup func (d *DNSServer) serviceSRVRecords(dc string, nodes structs.CheckServiceNodes, req, resp *dns.Msg, ttl time.Duration) { handled := make(map[string]struct{}) for _, node := range nodes { // Avoid duplicate entries, possible if a node has // the same service the same port, etc. tuple := fmt.Sprintf("%s:%d", node.Node.Node, node.Service.Port) if _, ok := handled[tuple]; ok { continue } handled[tuple] = struct{}{} // Add the SRV record srvRec := &dns.SRV{ Hdr: dns.RR_Header{ Name: req.Question[0].Name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: uint32(ttl / time.Second), }, Priority: 1, Weight: 1, Port: uint16(node.Service.Port), Target: fmt.Sprintf("%s.node.%s.%s", node.Node.Node, dc, d.domain), } resp.Answer = append(resp.Answer, srvRec) // Add the extra record records := d.formatNodeRecord(&node.Node, srvRec.Target, dns.TypeANY, ttl) if records != nil { resp.Extra = append(resp.Extra, records...) } } }
func dedup(m *dns.Msg) *dns.Msg { // TODO(miek): expensive! m.Answer = dns.Dedup(m.Answer, nil) m.Ns = dns.Dedup(m.Ns, nil) m.Extra = dns.Dedup(m.Extra, nil) return m }
// Construct a response for a single DNS request. func (ds *DjdnsServer) Handle(query *dns.Msg) (*dns.Msg, error) { response := new(dns.Msg) response.MsgHdr.Id = query.MsgHdr.Id response.Question = query.Question if len(query.Question) > 0 { // Ignore secondary questions question := query.Question[0] records, err := ds.GetRecords(question.Name) if err != nil { return nil, err } response.Answer = make([]dns.RR, len(records)) for i, record := range records { answer, err := record.ToDns() if err != nil { return nil, err } response.Answer[i] = answer } response.Ns = make([]dns.RR, 0) response.Extra = make([]dns.RR, 0) } return response, nil }
func (s *jujuNameServer) handleRequest(w dns.ResponseWriter, r *dns.Msg) { m := new(dns.Msg) m.SetReply(r) for _, q := range r.Question { rr, err := s.answer(q) if err != nil { m.SetRcodeFormatError(r) t := new(dns.TXT) t.Hdr = dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassNONE, } t.Txt = []string{err.Error()} m.Extra = append(m.Extra, t) continue } else if rr != nil { m.Answer = append(m.Answer, rr) } } m.Authoritative = true // recursion isn't really available, but it's apparently // necessary to set this to make nslookup happy. m.RecursionAvailable = true w.WriteMsg(m) }
func RenewDnsMsg(m *dns.Msg) { m.Extra = nil m.Answer = nil m.AuthenticatedData = false m.CheckingDisabled = false m.Question = nil }
func (res *Resolver) handleSRV(rs *records.RecordGenerator, name string, m, r *dns.Msg) error { var errs multiError for _, srv := range rs.SRVs[name] { srvRR, err := res.formatSRV(r.Question[0].Name, srv) if err != nil { errs.Add(err) continue } m.Answer = append(m.Answer, srvRR) host := strings.Split(srv, ":")[0] if len(rs.As[host]) == 0 { continue } aRR, err := res.formatA(host, rs.As[host][0]) if err != nil { errs.Add(err) continue } m.Extra = append(m.Extra, aRR) } return errs }
func (manager *DnsManager) dnsHandler(w dns.ResponseWriter, req *dns.Msg) { m := new(dns.Msg) m.SetReply(req) name := req.Question[0].Name name = strings.TrimSuffix(name, "."+manager.BaseName) manager.dbMutex.Lock() entry, ok := manager.db[name] manager.dbMutex.Unlock() if ok { switch req.Question[0].Qtype { case dns.TypeSRV: m.Answer = manager.makeAllSRV(entry) case dns.TypeA: m.Answer = manager.makeAllA(entry) if manager.PushSRV { m.Extra = manager.makeAllSRV(entry) } } } w.WriteMsg(m) }
func (res *Resolver) handleSRV(rs *records.RecordGenerator, name string, m, r *dns.Msg) error { var errs multiError added := map[string]struct{}{} // track the A RR's we've already added, avoid dups for srv := range rs.SRVs[name] { srvRR, err := res.formatSRV(r.Question[0].Name, srv) if err != nil { errs.Add(err) continue } m.Answer = append(m.Answer, srvRR) host := strings.Split(srv, ":")[0] if _, found := added[host]; found { // avoid dups continue } if len(rs.As[host]) == 0 { continue } if a, ok := rs.As.First(host); ok { aRR, err := res.formatA(host, a) if err != nil { errs.Add(err) continue } m.Extra = append(m.Extra, aRR) added[host] = struct{}{} } } return errs }
// syncExtra takes a DNS response message and sets the extra data to the most // minimal set needed to cover the answer data. A pre-made index of RRs is given // so that can be re-used between calls. This assumes that the extra data is // only used to provide info for SRV records. If that's not the case, then this // will wipe out any additional data. func syncExtra(index map[string]dns.RR, resp *dns.Msg) { extra := make([]dns.RR, 0, len(resp.Answer)) resolved := make(map[string]struct{}, len(resp.Answer)) for _, ansRR := range resp.Answer { srv, ok := ansRR.(*dns.SRV) if !ok { continue } // Note that we always use lower case when using the index so // that compares are not case-sensitive. We don't alter the actual // RRs we add into the extra section, however. target := strings.ToLower(srv.Target) RESOLVE: if _, ok := resolved[target]; ok { continue } resolved[target] = struct{}{} extraRR, ok := index[target] if ok { extra = append(extra, extraRR) if cname, ok := extraRR.(*dns.CNAME); ok { target = strings.ToLower(cname.Target) goto RESOLVE } } } resp.Extra = extra }
// Handle produces reply for NS question func (n *nsHandler) Handle(msg *mdns.Msg, zone *config.Zone, question mdns.Question) (err error) { for _, server := range n.config.DNS.Servers { s := strings.Join([]string{ question.Name, "3600", "IN", "NS", server.Name, }, " ") rr, err := mdns.NewRR(s) if err == nil { msg.Answer = append(msg.Answer, rr) } } for _, server := range n.config.DNS.Servers { s := strings.Join([]string{ server.Name, "3600", "IN", "A", server.IP, }, " ") rr, err := mdns.NewRR(s) if err == nil { msg.Extra = append(msg.Extra, rr) } } return }
// truncate removes answers until the given dns.Msg fits the permitted // length of the given transmission channel and sets the TC bit. // See https://tools.ietf.org/html/rfc1035#section-4.2.1 func truncate(m *dns.Msg, udp bool) *dns.Msg { max := dns.MinMsgSize if !udp { max = dns.MaxMsgSize } else if opt := m.IsEdns0(); opt != nil { max = int(opt.UDPSize()) } m.Truncated = m.Len() > max if !m.Truncated { return m } m.Extra = nil // Drop all extra records first if m.Len() < max { return m } answers := m.Answer[:] left, right := 0, len(m.Answer) for { if left == right { break } mid := (left + right) / 2 m.Answer = answers[:mid] if m.Len() < max { left = mid + 1 continue } right = mid } return m }
// Sign signs a message m, it takes care of negative or nodata responses as // well by synthesising NSEC3 records. It will also cache the signatures, using // a hash of the signed data as a key. // We also fake the origin TTL in the signature, because we don't want to // throw away signatures when services decide to have longer TTL. So we just // set the origTTL to 60. // TODO(miek): revisit origTTL func (s *server) Sign(m *dns.Msg, bufsize uint16) { now := time.Now().UTC() incep := uint32(now.Add(-3 * time.Hour).Unix()) // 2+1 hours, be sure to catch daylight saving time and such expir := uint32(now.Add(7 * 24 * time.Hour).Unix()) // sign for a week for _, r := range rrSets(m.Answer) { if r[0].Header().Rrtype == dns.TypeRRSIG { continue } if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { continue } if sig, err := s.signSet(r, now, incep, expir); err == nil { m.Answer = append(m.Answer, sig) } } for _, r := range rrSets(m.Ns) { if r[0].Header().Rrtype == dns.TypeRRSIG { continue } if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { continue } if sig, err := s.signSet(r, now, incep, expir); err == nil { m.Ns = append(m.Ns, sig) } } for _, r := range rrSets(m.Extra) { if r[0].Header().Rrtype == dns.TypeRRSIG || r[0].Header().Rrtype == dns.TypeOPT { continue } if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { continue } if sig, err := s.signSet(r, now, incep, expir); err == nil { m.Extra = append(m.Extra, sig) } } o := new(dns.OPT) o.Hdr.Name = "." o.Hdr.Rrtype = dns.TypeOPT o.SetDo() o.SetUDPSize(4096) // TODO(miek): echo client m.Extra = append(m.Extra, o) return }
func (s *Server) do(Net string, w dns.ResponseWriter, req *dns.Msg) { if len(req.Question) != 1 { dns.HandleFailed(w, req) return } zone, name := s.zones.match(req.Question[0].Name, req.Question[0].Qtype) if zone == nil { s.recurse(w, req) return } s.zones.Lock() defer s.zones.Unlock() m := new(dns.Msg) m.SetReply(req) var answerKnown bool dnsreq := dns.RR_Header{Name: req.Question[0].Name, Rrtype: req.Question[0].Qtype, Class: req.Question[0].Qclass} for _, r := range (*zone)[dnsreq] { m.Answer = append(m.Answer, r) answerKnown = true } // TODO: more logs here s.roundRobin(zone, dnsreq) if !answerKnown && s.recurseTo != "" { s.recurse(w, req) return } // Add Authority section for _, r := range (*zone)[dns.RR_Header{Name: name, Rrtype: dns.TypeNS, Class: dns.ClassINET}] { m.Ns = append(m.Ns, r) // Resolve Authority if possible and serve as Extra for _, r := range (*zone)[dns.RR_Header{Name: r.(*dns.NS).Ns, Rrtype: dns.TypeA, Class: dns.ClassINET}] { m.Extra = append(m.Extra, r) } for _, r := range (*zone)[dns.RR_Header{Name: r.(*dns.NS).Ns, Rrtype: dns.TypeAAAA, Class: dns.ClassINET}] { m.Extra = append(m.Extra, r) } } m.Authoritative = true w.WriteMsg(m) }
func Msg(zone string, typ uint16, o *dns.OPT) *dns.Msg { m := new(dns.Msg) m.SetQuestion(zone, typ) if o != nil { m.Extra = []dns.RR{o} } return m }
// Err write an error response to the client. func (e Etcd) Err(zone string, rcode int, state middleware.State, debug []msg.Service, err error) (int, error) { m := new(dns.Msg) m.SetRcode(state.Req, rcode) m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true m.Ns, _, _ = e.SOA(zone, state) if e.debug != "" { m.Extra = servicesToTxt(debug) txt := errorToTxt(err) if txt != nil { m.Extra = append(m.Extra, errorToTxt(err)) } } state.SizeAndDo(m) state.W.WriteMsg(m) // Return success as the rcode to signal we have written to the client. return dns.RcodeSuccess, nil }
func (s *Server) unregister() error { resp := new(dns.Msg) resp.MsgHdr.Response = true resp.Answer = []dns.RR{} resp.Extra = []dns.RR{} s.composeLookupAnswers(resp, 0) return s.multicastResponse(resp) }
// ServeDNSStubForward forwards a request to a nameservers and returns the response. func (s *server) ServeDNSStubForward(w dns.ResponseWriter, req *dns.Msg, ns []string) *dns.Msg { // Check EDNS0 Stub option, if set drop the packet. option := req.IsEdns0() if option != nil { for _, o := range option.Option { if o.Option() == ednsStubCode && len(o.(*dns.EDNS0_LOCAL).Data) == 1 && o.(*dns.EDNS0_LOCAL).Data[0] == 1 { // Maybe log source IP here? logf("not fowarding stub request to another stub") return nil } } } tcp := isTCP(w) // Add a custom EDNS0 option to the packet, so we can detect loops // when 2 stubs are forwarding to each other. if option != nil { option.Option = append(option.Option, &dns.EDNS0_LOCAL{ednsStubCode, []byte{1}}) } else { req.Extra = append(req.Extra, ednsStub) } var ( r *dns.Msg err error try int ) // Use request Id for "random" nameserver selection. nsid := int(req.Id) % len(ns) Redo: switch tcp { case false: r, _, err = s.dnsUDPclient.Exchange(req, ns[nsid]) case true: r, _, err = s.dnsTCPclient.Exchange(req, ns[nsid]) } if err == nil { r.Compress = true r.Id = req.Id w.WriteMsg(r) return r } // Seen an error, this can only mean, "server not reached", try again // but only if we have not exausted our nameservers. if try < len(ns) { try++ nsid = (nsid + 1) % len(ns) goto Redo } logf("failure to forward stub request %q", err) m := s.ServerFailure(req) w.WriteMsg(m) return m }
func ednsMsg() *dns.Msg { m := new(dns.Msg) m.SetQuestion("example.com.", dns.TypeA) o := new(dns.OPT) o.Hdr.Name = "." o.Hdr.Rrtype = dns.TypeOPT m.Extra = append(m.Extra, o) return m }
// Perform probing & announcement //TODO: implement a proper probing & conflict resolution func (s *Server) probe() { q := new(dns.Msg) q.SetQuestion(s.service.ServiceInstanceName(), dns.TypePTR) q.RecursionDesired = false srv := &dns.SRV{ Hdr: dns.RR_Header{ Name: s.service.ServiceInstanceName(), Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: s.ttl, }, Priority: 0, Weight: 0, Port: uint16(s.service.Port), Target: s.service.HostName, } txt := &dns.TXT{ Hdr: dns.RR_Header{ Name: s.service.ServiceInstanceName(), Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: s.ttl, }, Txt: s.service.Text, } q.Ns = []dns.RR{srv, txt} randomizer := rand.New(rand.NewSource(time.Now().UnixNano())) for i := 0; i < 3; i++ { if err := s.multicastResponse(q); err != nil { log.Println("[ERR] bonjour: failed to send probe:", err.Error()) } time.Sleep(time.Duration(randomizer.Intn(250)) * time.Millisecond) } resp := new(dns.Msg) resp.MsgHdr.Response = true resp.Answer = []dns.RR{} resp.Extra = []dns.RR{} s.composeLookupAnswers(resp, s.ttl) // From RFC6762 // The Multicast DNS responder MUST send at least two unsolicited // responses, one second apart. To provide increased robustness against // packet loss, a responder MAY send up to eight unsolicited responses, // provided that the interval between unsolicited responses increases by // at least a factor of two with every response sent. timeout := 1 * time.Second for i := 0; i < 3; i++ { if err := s.multicastResponse(resp); err != nil { log.Println("[ERR] bonjour: failed to send announcement:", err.Error()) } time.Sleep(timeout) timeout *= 2 } }
func serverHandlerHello(w dns.ResponseWriter, req *dns.Msg) { m := new(dns.Msg) m.SetReply(req) m.Extra = make([]dns.RR, 1) m.Extra[0] = &dns.TXT{ Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0}, Txt: []string{"Hello world"}, } w.WriteMsg(m) }
func prepareAnswerMsg(req *dns.Msg, answers []dns.RR) *dns.Msg { answerMsg := new(dns.Msg) answerMsg.Id = req.Id answerMsg.Response = true answerMsg.Authoritative = true answerMsg.Question = req.Question answerMsg.Answer = answers answerMsg.Rcode = dns.RcodeSuccess answerMsg.Extra = []dns.RR{} return answerMsg }
func (r *RoundRobinResponseWriter) WriteMsg(res *dns.Msg) error { if res.Rcode != dns.RcodeSuccess { return r.ResponseWriter.WriteMsg(res) } res.Answer = roundRobin(res.Answer) res.Ns = roundRobin(res.Ns) res.Extra = roundRobin(res.Extra) return r.ResponseWriter.WriteMsg(res) }
// addStubEdns0 adds our special option to the message's OPT record. func addStubEdns0(m *dns.Msg) *dns.Msg { option := m.IsEdns0() // Add a custom EDNS0 option to the packet, so we can detect loops when 2 stubs are forwarding to each other. if option != nil { option.Option = append(option.Option, &dns.EDNS0_LOCAL{Code: ednsStubCode, Data: []byte{1}}) return m } m.Extra = append(m.Extra, ednsStub) return m }