package authoritative import ( "strings" "github.com/miekg/dns" ) // maxCNAMEChain bounds in-zone CNAME following so a loop cannot hang a query. const maxCNAMEChain = 12 // Answer builds an authoritative reply for the question in req. // // It returns nil when no configured zone covers the question, which tells the // caller to fall through to the cache and the recursive resolver. func (idx *Index) Answer(req *dns.Msg, do bool) *dns.Msg { if idx == nil || len(req.Question) == 0 { return nil } q := req.Question[0] if q.Qclass != dns.ClassINET && q.Qclass != dns.ClassANY { return nil } z := idx.Lookup(q.Name) if z == nil { return nil } return z.Answer(req, do) } // Answer builds an authoritative reply for req from this zone. func (z *Zone) Answer(req *dns.Msg, do bool) *dns.Msg { q := req.Question[0] qname := strings.ToLower(dns.Fqdn(q.Name)) qtype := q.Qtype m := new(dns.Msg) m.SetReply(req) m.Authoritative = true m.Compress = true // A delegation between the apex and the queried name means the answer // belongs to a child zone: return a referral rather than our own data. if z.hasDelegations { if dp := z.delegationFor(qname, qtype); dp != "" { z.writeReferral(m, dp, do) return m } } name := qname for depth := 0; depth < maxCNAMEChain; depth++ { node, synthesised := z.resolveNode(name) if node == nil { // The name has no data. Distinguish "exists but no records of this // type" (NODATA) from "does not exist at all" (NXDOMAIN). if _, exists := z.ents[name]; exists || depth > 0 { z.writeNoData(m, do) } else { m.Rcode = dns.RcodeNameError z.writeNoData(m, do) } return m } // A CNAME is followed unless the client asked for the CNAME itself. if cnames := node.types[dns.TypeCNAME]; len(cnames) > 0 && qtype != dns.TypeCNAME && qtype != dns.TypeANY { rr := materialise(cnames[0], name, synthesised) m.Answer = append(m.Answer, rr) z.appendSignatures(m, node, dns.TypeCNAME, name, synthesised, do) target := strings.ToLower(rr.(*dns.CNAME).Target) if !dns.IsSubDomain(z.Name, target) { // The chain leaves our zone; the client (or the recursor in // front of it) has to continue from here. m.Authoritative = true return m } name = target continue } if qtype == dns.TypeANY { for _, t := range node.typeList() { for _, rr := range node.types[t] { m.Answer = append(m.Answer, materialise(rr, name, synthesised)) } } if len(m.Answer) == 0 { z.writeNoData(m, do) } else { z.addAuthorityNS(m, do) } return m } rrs := node.types[qtype] if len(rrs) == 0 { z.writeNoData(m, do) return m } for _, rr := range rrs { m.Answer = append(m.Answer, materialise(rr, name, synthesised)) } z.appendSignatures(m, node, qtype, name, synthesised, do) z.addAuthorityNS(m, do) z.addAdditional(m, do) return m } // Chain too long: return what we have rather than looping. return m } // resolveNode finds the RRsets for a name, falling back to wildcard synthesis // using the RFC 4592 closest-encloser rule. func (z *Zone) resolveNode(name string) (node *nameNode, synthesised bool) { if n, ok := z.names[name]; ok { return n, false } if _, exists := z.ents[name]; exists { return nil, false // empty non-terminal: exists, but holds no data } if len(z.wildcards) == 0 { return nil, false } ce := z.closestEncloser(name) if wn, ok := z.wildcards["*."+ce]; ok { return wn, true } return nil, false } // closestEncloser returns the deepest ancestor of name that exists in the zone. func (z *Zone) closestEncloser(name string) string { n := name for { if n == z.Name { return z.Name } i, end := dns.NextLabel(n, 0) if end { return z.Name } n = n[i:] if !dns.IsSubDomain(z.Name, n) { return z.Name } if _, ok := z.ents[n]; ok { return n } } } // delegationFor returns the deepest delegation point at or above qname, or "". // A DS query at the delegation point itself is answered from the parent side, // so it is not treated as a referral. func (z *Zone) delegationFor(qname string, qtype uint16) string { n := qname for { if n == z.Name || !dns.IsSubDomain(z.Name, n) { return "" } if _, ok := z.delegations[n]; ok { if n == qname && qtype == dns.TypeDS { return "" } return n } i, end := dns.NextLabel(n, 0) if end { return "" } n = n[i:] } } // writeReferral fills the authority section with the child zone's NS records // and the additional section with any in-zone glue. func (z *Zone) writeReferral(m *dns.Msg, delegation string, do bool) { m.Authoritative = false node := z.delegations[delegation] if node == nil { return } for _, rr := range node.types[dns.TypeNS] { m.Ns = append(m.Ns, dns.Copy(rr)) } // A signed delegation carries a DS RRset (or a proof of its absence). if dsNode, ok := z.names[delegation]; ok && do { for _, rr := range dsNode.types[dns.TypeDS] { m.Ns = append(m.Ns, dns.Copy(rr)) } for _, rr := range dsNode.types[dns.TypeRRSIG] { if sig, ok := rr.(*dns.RRSIG); ok && sig.TypeCovered == dns.TypeDS { m.Ns = append(m.Ns, dns.Copy(rr)) } } } z.addGlueFor(m, m.Ns) } // writeNoData puts the SOA in the authority section, which is what tells a // resolver how long to cache the negative answer. func (z *Zone) writeNoData(m *dns.Msg, do bool) { if z.soa == nil { return } soa := dns.Copy(z.soa).(*dns.SOA) // RFC 2308: the negative caching TTL is the lesser of the SOA TTL and the // SOA MINIMUM field. if soa.Minttl < soa.Hdr.Ttl { soa.Hdr.Ttl = soa.Minttl } m.Ns = append(m.Ns, soa) if do { if apex, ok := z.names[z.Name]; ok { for _, rr := range apex.types[dns.TypeRRSIG] { if sig, ok := rr.(*dns.RRSIG); ok && sig.TypeCovered == dns.TypeSOA { m.Ns = append(m.Ns, dns.Copy(rr)) } } } } } // addAuthorityNS adds the zone's NS RRset to a positive answer, except when the // answer already is that RRset. func (z *Zone) addAuthorityNS(m *dns.Msg, do bool) { if len(m.Answer) == 0 || len(z.ns) == 0 { return } if h := m.Answer[0].Header(); h.Rrtype == dns.TypeNS && h.Name == z.Name { return } if h := m.Answer[0].Header(); h.Rrtype == dns.TypeSOA { return } for _, rr := range z.ns { m.Ns = append(m.Ns, dns.Copy(rr)) } if do { if apex, ok := z.names[z.Name]; ok { for _, rr := range apex.types[dns.TypeRRSIG] { if sig, ok := rr.(*dns.RRSIG); ok && sig.TypeCovered == dns.TypeNS { m.Ns = append(m.Ns, dns.Copy(rr)) } } } } } // addAdditional supplies address records for names referenced by the answer, // saving the client a follow-up query. func (z *Zone) addAdditional(m *dns.Msg, do bool) { z.addGlueFor(m, m.Answer) z.addGlueFor(m, m.Ns) } func (z *Zone) addGlueFor(m *dns.Msg, section []dns.RR) { seen := map[string]bool{} for _, rr := range m.Extra { seen[strings.ToLower(rr.Header().Name)] = true } for _, rr := range section { var target string switch v := rr.(type) { case *dns.MX: target = v.Mx case *dns.SRV: target = v.Target case *dns.NS: target = v.Ns default: continue } target = strings.ToLower(dns.Fqdn(target)) if target == "" || seen[target] || !dns.IsSubDomain(z.Name, target) { continue } node, ok := z.names[target] if !ok { continue } seen[target] = true for _, t := range []uint16{dns.TypeA, dns.TypeAAAA} { for _, arr := range node.types[t] { m.Extra = append(m.Extra, dns.Copy(arr)) } } } } // appendSignatures adds the RRSIGs covering an RRset when the client set DO. // // Zones served here are not signed by this application; signatures are only // present when a pre-signed zone file was imported. Serving them unchanged // keeps such zones verifiable, and leaves room for an in-process signer later. func (z *Zone) appendSignatures(m *dns.Msg, node *nameNode, covered uint16, name string, synthesised, do bool) { if !do { return } for _, rr := range node.types[dns.TypeRRSIG] { sig, ok := rr.(*dns.RRSIG) if !ok || sig.TypeCovered != covered { continue } m.Answer = append(m.Answer, materialise(rr, name, synthesised)) } } // materialise copies an RR, rewriting the owner name when the record came from // a wildcard node. func materialise(rr dns.RR, owner string, synthesised bool) dns.RR { c := dns.Copy(rr) if synthesised { c.Header().Name = owner } return c }