func (l Logger) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { state := middleware.State{W: w, Req: r} for _, rule := range l.Rules { if middleware.Name(rule.NameScope).Matches(state.Name()) { responseRecorder := middleware.NewResponseRecorder(w) rcode, err := l.Next.ServeDNS(ctx, responseRecorder, r) if rcode > 0 { // There was an error up the chain, but no response has been written yet. // The error must be handled here so the log entry will record the response size. if l.ErrorFunc != nil { l.ErrorFunc(responseRecorder, r, rcode) } else { rc := middleware.RcodeToString(rcode) answer := new(dns.Msg) answer.SetRcode(r, rcode) state.SizeAndDo(answer) metrics.Report(state, metrics.Dropped, rc, answer.Len(), time.Now()) w.WriteMsg(answer) } rcode = 0 } rep := middleware.NewReplacer(r, responseRecorder, CommonLogEmptyValue) rule.Log.Println(rep.Replace(rule.Format)) return rcode, err } } return l.Next.ServeDNS(ctx, w, r) }
func (e Etcd) AAAA(zone string, state middleware.State, previousRecords []dns.RR) (records []dns.RR, debug []msg.Service, err error) { services, debug, err := e.records(state, false) if err != nil { return nil, debug, err } for _, serv := range services { ip := net.ParseIP(serv.Host) switch { case ip == nil: // Try to resolve as CNAME if it's not an IP, but only if we don't create loops. if middleware.Name(state.Name()).Matches(dns.Fqdn(serv.Host)) { // x CNAME x is a direct loop, don't add those continue } newRecord := serv.NewCNAME(state.QName(), serv.Host) if len(previousRecords) > 7 { // don't add it, and just continue continue } if isDuplicateCNAME(newRecord, previousRecords) { continue } state1 := copyState(state, serv.Host, state.QType()) nextRecords, nextDebug, err := e.AAAA(zone, state1, append(previousRecords, newRecord)) if err == nil { // Not only have we found something we should add the CNAME and the IP addresses. if len(nextRecords) > 0 { records = append(records, newRecord) records = append(records, nextRecords...) debug = append(debug, nextDebug...) } continue } // This means we can not complete the CNAME, try to look else where. target := newRecord.Target if dns.IsSubDomain(zone, target) { // We should already have found it continue } m1, e1 := e.Proxy.Lookup(state, target, state.QType()) if e1 != nil { continue } // Len(m1.Answer) > 0 here is well? records = append(records, newRecord) records = append(records, m1.Answer...) continue // both here again case ip.To4() != nil: // nada? case ip.To4() == nil: records = append(records, serv.NewAAAA(state.QName(), ip.To16())) } } return records, debug, nil }
// NoData write a nodata response to the client. func (k Kubernetes) Err(zone string, rcode int, state middleware.State) (int, error) { m := new(dns.Msg) m.SetRcode(state.Req, rcode) m.Ns = []dns.RR{k.SOA(zone, state)} state.SizeAndDo(m) state.W.WriteMsg(m) return rcode, nil }
func (k Kubernetes) A(zone string, state middleware.State, previousRecords []dns.RR) (records []dns.RR, err error) { services, err := k.records(state, false) if err != nil { return nil, err } for _, serv := range services { ip := net.ParseIP(serv.Host) switch { case ip == nil: // TODO(miek): lowercasing? Should lowercase in everything see #85 if middleware.Name(state.Name()).Matches(dns.Fqdn(serv.Host)) { // x CNAME x is a direct loop, don't add those continue } newRecord := serv.NewCNAME(state.QName(), serv.Host) if len(previousRecords) > 7 { // don't add it, and just continue continue } if isDuplicateCNAME(newRecord, previousRecords) { continue } state1 := copyState(state, serv.Host, state.QType()) nextRecords, err := k.A(zone, state1, append(previousRecords, newRecord)) if err == nil { // Not only have we found something we should add the CNAME and the IP addresses. if len(nextRecords) > 0 { records = append(records, newRecord) records = append(records, nextRecords...) } continue } // This means we can not complete the CNAME, try to look else where. target := newRecord.Target if dns.IsSubDomain(zone, target) { // We should already have found it continue } m1, e1 := k.Proxy.Lookup(state, target, state.QType()) if e1 != nil { continue } // Len(m1.Answer) > 0 here is well? records = append(records, newRecord) records = append(records, m1.Answer...) continue case ip.To4() != nil: records = append(records, serv.NewA(state.QName(), ip.To4())) case ip.To4() == nil: // nodata? } } return records, nil }
func (k Kubernetes) records(state middleware.State, exact bool) ([]msg.Service, error) { services, err := k.Records(state.Name(), exact) if err != nil { return nil, err } // TODO: Do we want to support the SkyDNS (hacky) Group feature? services = msg.Group(services) return services, nil }
// ServeDNS implements the middleware.Handler interface. func (c Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { state := middleware.State{W: w, Req: r} qname := state.Name() qtype := state.QType() zone := middleware.Zones(c.Zones).Matches(qname) if zone == "" { return c.Next.ServeDNS(ctx, w, r) } do := state.Do() // might need more from OPT record? if i, ok := c.get(qname, qtype, do); ok { resp := i.toMsg(r) state.SizeAndDo(resp) w.WriteMsg(resp) cacheHitCount.WithLabelValues(zone).Inc() return dns.RcodeSuccess, nil } cacheMissCount.WithLabelValues(zone).Inc() crr := NewCachingResponseWriter(w, c.cache, c.cap) return c.Next.ServeDNS(ctx, crr, r) }
// ServeDNS implements the middleware.Handler interface. func (d Dnssec) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { state := middleware.State{W: w, Req: r} do := state.Do() qname := state.Name() qtype := state.QType() zone := middleware.Zones(d.zones).Matches(qname) if zone == "" { return d.Next.ServeDNS(ctx, w, r) } // Intercept queries for DNSKEY, but only if one of the zones matches the qname, otherwise we let // the query through. if qtype == dns.TypeDNSKEY { for _, z := range d.zones { if qname == z { resp := d.getDNSKEY(state, z, do) state.SizeAndDo(resp) w.WriteMsg(resp) return dns.RcodeSuccess, nil } } } drr := NewDnssecResponseWriter(w, d) return d.Next.ServeDNS(ctx, drr, r) }
// DefaultErrorFunc responds to an DNS request with an error. func DefaultErrorFunc(w dns.ResponseWriter, r *dns.Msg, rcode int) { state := middleware.State{W: w, Req: r} rc := middleware.RcodeToString(rcode) answer := new(dns.Msg) answer.SetRcode(r, rcode) state.SizeAndDo(answer) metrics.Report(state, metrics.Dropped, rc, answer.Len(), time.Now()) w.WriteMsg(answer) }
func (e Etcd) records(state middleware.State, exact bool) (services, debug []msg.Service, err error) { services, err = e.Records(state.Name(), exact) if err != nil { return } if e.debug != "" { debug = services } services = msg.Group(services) return }
// PTR returns the PTR records, only services that have a domain name as host are included. func (e Etcd) PTR(zone string, state middleware.State) (records []dns.RR, debug []msg.Service, err error) { services, debug, err := e.records(state, true) if err != nil { return nil, debug, err } for _, serv := range services { if ip := net.ParseIP(serv.Host); ip == nil { records = append(records, serv.NewPTR(state.QName(), serv.Host)) } } return records, debug, nil }
func (e Etcd) CNAME(zone string, state middleware.State) (records []dns.RR, debug []msg.Service, err error) { services, debug, err := e.records(state, true) if err != nil { return nil, debug, err } if len(services) > 0 { serv := services[0] if ip := net.ParseIP(serv.Host); ip == nil { records = append(records, serv.NewCNAME(state.QName(), serv.Host)) } } return records, debug, nil }
func (e Etcd) TXT(zone string, state middleware.State) (records []dns.RR, debug []msg.Service, err error) { services, debug, err := e.records(state, false) if err != nil { return nil, debug, err } for _, serv := range services { if serv.Text == "" { continue } records = append(records, serv.NewTXT(state.QName())) } return records, debug, nil }
func (e Etcd) NS(zone string, state middleware.State) (records, extra []dns.RR, debug []msg.Service, err error) { // NS record for this zone live in a special place, ns.dns.<zone>. Fake our lookup. // only a tad bit fishy... old := state.QName() state.Clear() state.Req.Question[0].Name = "ns.dns." + zone services, debug, err := e.records(state, false) if err != nil { return nil, nil, debug, err } // ... and reset state.Req.Question[0].Name = old for _, serv := range services { ip := net.ParseIP(serv.Host) switch { case ip == nil: return nil, nil, debug, fmt.Errorf("NS record must be an IP address: %s", serv.Host) case ip.To4() != nil: serv.Host = msg.Domain(serv.Key) records = append(records, serv.NewNS(state.QName())) extra = append(extra, serv.NewA(serv.Host, ip.To4())) case ip.To4() == nil: serv.Host = msg.Domain(serv.Key) records = append(records, serv.NewNS(state.QName())) extra = append(extra, serv.NewAAAA(serv.Host, ip.To16())) } } return records, extra, debug, nil }
// isNotify checks if state is a notify message and if so, will *also* check if it // is from one of the configured masters. If not it will not be a valid notify // message. If the zone z is not a secondary zone the message will also be ignored. func (z *Zone) isNotify(state middleware.State) bool { if state.Req.Opcode != dns.OpcodeNotify { return false } if len(z.TransferFrom) == 0 { return false } remote := middleware.Addr(state.IP()).Normalize() for _, from := range z.TransferFrom { if from == remote { return true } } return false }
// Sign signs the message m. it takes care of negative or nodata responses. It // uses NSEC black lies for authenticated denial of existence. Signatures // creates will be cached for a short while. By default we sign for 8 days, // starting 3 hours ago. func (d Dnssec) Sign(state middleware.State, zone string, now time.Time) *dns.Msg { req := state.Req mt, _ := middleware.Classify(req) // TODO(miek): need opt record here? if mt == middleware.Delegation { return req } incep, expir := incepExpir(now) if mt == middleware.NameError { if req.Ns[0].Header().Rrtype != dns.TypeSOA || len(req.Ns) > 1 { return req } ttl := req.Ns[0].Header().Ttl if sigs, err := d.sign(req.Ns, zone, ttl, incep, expir); err == nil { req.Ns = append(req.Ns, sigs...) } if sigs, err := d.nsec(state.Name(), zone, ttl, incep, expir); err == nil { req.Ns = append(req.Ns, sigs...) } if len(req.Ns) > 1 { // actually added nsec and sigs, reset the rcode req.Rcode = dns.RcodeSuccess } return req } for _, r := range rrSets(req.Answer) { ttl := r[0].Header().Ttl if sigs, err := d.sign(r, zone, ttl, incep, expir); err == nil { req.Answer = append(req.Answer, sigs...) } } for _, r := range rrSets(req.Ns) { ttl := r[0].Header().Ttl if sigs, err := d.sign(r, zone, ttl, incep, expir); err == nil { req.Ns = append(req.Ns, sigs...) } } for _, r := range rrSets(req.Extra) { ttl := r[0].Header().Ttl if sigs, err := d.sign(r, zone, ttl, incep, expir); err == nil { req.Extra = append(sigs, req.Extra...) // prepend to leave OPT alone } } return req }
func (h ErrorHandler) recovery(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) { rec := recover() if rec == nil { return } state := middleware.State{W: w, Req: r} // Obtain source of panic // From: https://gist.github.com/swdunlop/9629168 var name, file string // function name, file name var line int var pc [16]uintptr n := runtime.Callers(3, pc[:]) for _, pc := range pc[:n] { fn := runtime.FuncForPC(pc) if fn == nil { continue } file, line = fn.FileLine(pc) name = fn.Name() if !strings.HasPrefix(name, "runtime.") { break } } // Trim file path delim := "/coredns/" pkgPathPos := strings.Index(file, delim) if pkgPathPos > -1 && len(file) > pkgPathPos+len(delim) { file = file[pkgPathPos+len(delim):] } panicMsg := fmt.Sprintf("%s [PANIC %s %s] %s:%d - %v", time.Now().Format(timeFormat), r.Question[0].Name, dns.Type(r.Question[0].Qtype), file, line, rec) if h.Debug { // Write error and stack trace to the response rather than to a log var stackBuf [4096]byte stack := stackBuf[:runtime.Stack(stackBuf[:], false)] answer := debugMsg(dns.RcodeServerFailure, r) // add stack buf in TXT, limited to 255 chars for now. txt, _ := dns.NewRR(". IN 0 TXT " + string(stack[:255])) answer.Answer = append(answer.Answer, txt) state.SizeAndDo(answer) w.WriteMsg(answer) } else { // Currently we don't use the function name, since file:line is more conventional h.Log.Printf(panicMsg) } }
func (m Metrics) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { state := middleware.State{W: w, Req: r} qname := state.QName() zone := middleware.Zones(m.ZoneNames).Matches(qname) if zone == "" { zone = "." } // Record response to get status code and size of the reply. rw := middleware.NewResponseRecorder(w) status, err := m.Next.ServeDNS(ctx, rw, r) Report(state, zone, rw.Rcode(), rw.Size(), rw.Start()) return status, err }
// 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 (p Proxy) lookup(state middleware.State, r *dns.Msg) (*dns.Msg, error) { var ( reply *dns.Msg err error ) for _, upstream := range p.Upstreams { // allowed bla bla bla TODO(miek): fix full proxy spec from caddy? start := time.Now() // Since Select() should give us "up" hosts, keep retrying // hosts until timeout (or until we get a nil host). for time.Now().Sub(start) < tryDuration { host := upstream.Select() if host == nil { return nil, errUnreachable } atomic.AddInt64(&host.Conns, 1) // tls+tcp ? if state.Proto() == "tcp" { reply, err = middleware.Exchange(p.Client.TCP, r, host.Name) } else { reply, err = middleware.Exchange(p.Client.UDP, r, host.Name) } atomic.AddInt64(&host.Conns, -1) if err == nil { return reply, nil } timeout := host.FailTimeout if timeout == 0 { timeout = 10 * time.Second } atomic.AddInt32(&host.Fails, 1) go func(host *UpstreamHost, timeout time.Duration) { time.Sleep(timeout) atomic.AddInt32(&host.Fails, -1) }(host, timeout) } return nil, errUnreachable } return nil, errUnreachable }
func (s Stub) ServeDNS(ctx context.Context, w dns.ResponseWriter, req *dns.Msg) (int, error) { if hasStubEdns0(req) { log.Printf("[WARNING] Forwarding cycle detected, refusing msg: %s", req.Question[0].Name) return dns.RcodeRefused, errors.New("stub forward cycle") } req = addStubEdns0(req) proxy, ok := (*s.Etcd.Stubmap)[s.Zone] if !ok { // somebody made a mistake.. return dns.RcodeServerFailure, nil } state := middleware.State{W: w, Req: req} m, e := proxy.Forward(state) if e != nil { return dns.RcodeServerFailure, e } m.RecursionAvailable, m.Compress = true, true state.SizeAndDo(m) w.WriteMsg(m) return dns.RcodeSuccess, nil }
// MX returns MX records from etcd. // If the Target is not a name but an IP address, a name is created on the fly. func (e Etcd) MX(zone string, state middleware.State) (records, extra []dns.RR, debug []msg.Service, err error) { services, debug, err := e.records(state, false) if err != nil { return nil, nil, debug, err } lookup := make(map[string]bool) for _, serv := range services { if !serv.Mail { continue } ip := net.ParseIP(serv.Host) switch { case ip == nil: mx := serv.NewMX(state.QName()) records = append(records, mx) if _, ok := lookup[mx.Mx]; ok { break } lookup[mx.Mx] = true if !dns.IsSubDomain(zone, mx.Mx) { m1, e1 := e.Proxy.Lookup(state, mx.Mx, dns.TypeA) if e1 == nil { extra = append(extra, m1.Answer...) } m1, e1 = e.Proxy.Lookup(state, mx.Mx, dns.TypeAAAA) if e1 == nil { // If we have seen CNAME's we *assume* that they are already added. for _, a := range m1.Answer { if _, ok := a.(*dns.CNAME); !ok { extra = append(extra, a) } } } break } // Internal name state1 := copyState(state, mx.Mx, dns.TypeA) addr, debugAddr, e1 := e.A(zone, state1, nil) if e1 == nil { extra = append(extra, addr...) debug = append(debug, debugAddr...) } // e.AAAA as well case ip.To4() != nil: serv.Host = msg.Domain(serv.Key) records = append(records, serv.NewMX(state.QName())) extra = append(extra, serv.NewA(serv.Host, ip.To4())) case ip.To4() == nil: serv.Host = msg.Domain(serv.Key) records = append(records, serv.NewMX(state.QName())) extra = append(extra, serv.NewAAAA(serv.Host, ip.To16())) } } return records, extra, debug, nil }
func (h ErrorHandler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { defer h.recovery(ctx, w, r) rcode, err := h.Next.ServeDNS(ctx, w, r) if err != nil { state := middleware.State{W: w, Req: r} errMsg := fmt.Sprintf("%s [ERROR %d %s %s] %v", time.Now().Format(timeFormat), rcode, state.Name(), state.Type(), err) if h.Debug { // Write error to response as a txt message instead of to log answer := debugMsg(rcode, r) txt, _ := dns.NewRR(". IN 0 TXT " + errMsg) answer.Answer = append(answer.Answer, txt) state.SizeAndDo(answer) w.WriteMsg(answer) return 0, err } h.Log.Println(errMsg) } return rcode, err }
func (d *DnssecResponseWriter) WriteMsg(res *dns.Msg) error { // By definition we should sign anything that comes back, we should still figure out for // which zone it should be. state := middleware.State{W: d.ResponseWriter, Req: res} qname := state.Name() zone := middleware.Zones(d.d.zones).Matches(qname) if zone == "" { return d.ResponseWriter.WriteMsg(res) } if state.Do() { res = d.d.Sign(state, zone, time.Now().UTC()) } state.SizeAndDo(res) return d.ResponseWriter.WriteMsg(res) }
// Serve an AXFR (and fallback of IXFR) as well. func (x Xfr) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { state := middleware.State{W: w, Req: r} if !x.TransferAllowed(state) { return dns.RcodeServerFailure, nil } if state.QType() != dns.TypeAXFR && state.QType() != dns.TypeIXFR { return 0, fmt.Errorf("xfr called with non transfer type: %d", state.QType()) } records := x.All() if len(records) == 0 { return dns.RcodeServerFailure, nil } ch := make(chan *dns.Envelope) defer close(ch) tr := new(dns.Transfer) go tr.Out(w, r, ch) j, l := 0, 0 records = append(records, records[0]) // add closing SOA to the end log.Printf("[INFO] Outgoing transfer of %d records of zone %s to %s started", len(records), x.origin, state.IP()) for i, r := range records { l += dns.Len(r) if l > transferLength { ch <- &dns.Envelope{RR: records[j:i]} l = 0 j = i } } if j < len(records) { ch <- &dns.Envelope{RR: records[j:]} } w.Hijack() // w.Close() // Client closes connection return dns.RcodeSuccess, nil }
// Report is a plain reporting function that the server can use for REFUSED and other // queries that are turned down because they don't match any middleware. func Report(state middleware.State, zone, rcode string, size int, start time.Time) { if requestCount == nil { // no metrics are enabled return } // Proto and Family net := state.Proto() fam := "1" if state.Family() == 2 { fam = "2" } typ := state.QType() requestCount.WithLabelValues(zone, net, fam).Inc() requestDuration.WithLabelValues(zone).Observe(float64(time.Since(start) / time.Millisecond)) if state.Do() { requestDo.WithLabelValues(zone).Inc() } if _, known := monitorType[typ]; known { requestType.WithLabelValues(zone, dns.Type(typ).String()).Inc() } else { requestType.WithLabelValues(zone, other).Inc() } if typ == dns.TypeIXFR || typ == dns.TypeAXFR { responseTransferSize.WithLabelValues(zone, net).Observe(float64(size)) requestTransferSize.WithLabelValues(zone, net).Observe(float64(state.Size())) } else { responseSize.WithLabelValues(zone, net).Observe(float64(size)) requestSize.WithLabelValues(zone, net).Observe(float64(state.Size())) } responseRcode.WithLabelValues(zone, rcode).Inc() }
func (c Chaos) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { state := middleware.State{W: w, Req: r} if state.QClass() != dns.ClassCHAOS || state.QType() != dns.TypeTXT { return c.Next.ServeDNS(ctx, w, r) } m := new(dns.Msg) m.SetReply(r) hdr := dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0} switch state.Name() { default: return c.Next.ServeDNS(ctx, w, r) case "authors.bind.": for a, _ := range c.Authors { m.Answer = append(m.Answer, &dns.TXT{Hdr: hdr, Txt: []string{trim(a)}}) } case "version.bind.", "version.server.": m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{trim(c.Version)}}} case "hostname.bind.", "id.server.": hostname, err := os.Hostname() if err != nil { hostname = "localhost" } m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{trim(hostname)}}} } state.SizeAndDo(m) w.WriteMsg(m) return 0, nil }
func (e Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { state := middleware.State{W: w, Req: r} if state.QClass() != dns.ClassINET { return dns.RcodeServerFailure, fmt.Errorf("can only deal with ClassINET") } name := state.Name() if e.Debug { if debug := isDebug(name); debug != "" { e.debug = r.Question[0].Name state.Clear() state.Req.Question[0].Name = debug } } // We need to check stubzones first, because we may get a request for a zone we // are not auth. for *but* do have a stubzone forward for. If we do the stubzone // handler will handle the request. if e.Stubmap != nil && len(*e.Stubmap) > 0 { for zone := range *e.Stubmap { if middleware.Name(zone).Matches(name) { stub := Stub{Etcd: e, Zone: zone} return stub.ServeDNS(ctx, w, r) } } } zone := middleware.Zones(e.Zones).Matches(state.Name()) if zone == "" { if e.Next == nil { return dns.RcodeServerFailure, nil } return e.Next.ServeDNS(ctx, w, r) } var ( records, extra []dns.RR debug []msg.Service err error ) switch state.Type() { case "A": records, debug, err = e.A(zone, state, nil) case "AAAA": records, debug, err = e.AAAA(zone, state, nil) case "TXT": records, debug, err = e.TXT(zone, state) case "CNAME": records, debug, err = e.CNAME(zone, state) case "PTR": records, debug, err = e.PTR(zone, state) case "MX": records, extra, debug, err = e.MX(zone, state) case "SRV": records, extra, debug, err = e.SRV(zone, state) case "SOA": records, debug, err = e.SOA(zone, state) case "NS": if state.Name() == zone { records, extra, debug, err = e.NS(zone, state) break } fallthrough default: // Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN _, debug, err = e.A(zone, state, nil) } if e.debug != "" { // Substitute this name with the original when we return the request. state.Clear() state.Req.Question[0].Name = e.debug } if isEtcdNameError(err) { return e.Err(zone, dns.RcodeNameError, state, debug, err) } if err != nil { return e.Err(zone, dns.RcodeServerFailure, state, debug, err) } if len(records) == 0 { return e.Err(zone, dns.RcodeSuccess, state, debug, err) } m := new(dns.Msg) m.SetReply(r) m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true m.Answer = append(m.Answer, records...) m.Extra = append(m.Extra, extra...) if e.debug != "" { m.Extra = append(m.Extra, servicesToTxt(debug)...) } m = dedup(m) state.SizeAndDo(m) m, _ = state.Scrub(m) w.WriteMsg(m) return dns.RcodeSuccess, nil }
// Lookup will use name and type to forge a new message and will send that upstream. It will // set any EDNS0 options correctly so that downstream will be able to process the reply. // Lookup is not suitable for forwarding request. Ssee for that. func (p Proxy) Lookup(state middleware.State, name string, tpe uint16) (*dns.Msg, error) { req := new(dns.Msg) req.SetQuestion(name, tpe) state.SizeAndDo(req) return p.lookup(state, req) }
// SRV returns SRV records from etcd. // If the Target is not a name but an IP address, a name is created on the fly. func (e Etcd) SRV(zone string, state middleware.State) (records, extra []dns.RR, debug []msg.Service, err error) { services, debug, err := e.records(state, false) if err != nil { return nil, nil, nil, err } // Looping twice to get the right weight vs priority w := make(map[int]int) for _, serv := range services { weight := 100 if serv.Weight != 0 { weight = serv.Weight } if _, ok := w[serv.Priority]; !ok { w[serv.Priority] = weight continue } w[serv.Priority] += weight } lookup := make(map[string]bool) for _, serv := range services { w1 := 100.0 / float64(w[serv.Priority]) if serv.Weight == 0 { w1 *= 100 } else { w1 *= float64(serv.Weight) } weight := uint16(math.Floor(w1)) ip := net.ParseIP(serv.Host) switch { case ip == nil: srv := serv.NewSRV(state.QName(), weight) records = append(records, srv) if _, ok := lookup[srv.Target]; ok { break } lookup[srv.Target] = true if !dns.IsSubDomain(zone, srv.Target) { m1, e1 := e.Proxy.Lookup(state, srv.Target, dns.TypeA) if e1 == nil { extra = append(extra, m1.Answer...) } m1, e1 = e.Proxy.Lookup(state, srv.Target, dns.TypeAAAA) if e1 == nil { // If we have seen CNAME's we *assume* that they are already added. for _, a := range m1.Answer { if _, ok := a.(*dns.CNAME); !ok { extra = append(extra, a) } } } break } // Internal name, we should have some info on them, either v4 or v6 // Clients expect a complete answer, because we are a recursor in their view. state1 := copyState(state, srv.Target, dns.TypeA) addr, debugAddr, e1 := e.A(zone, state1, nil) if e1 == nil { extra = append(extra, addr...) debug = append(debug, debugAddr...) } // e.AAA(zone, state1, nil) as well...? case ip.To4() != nil: serv.Host = msg.Domain(serv.Key) srv := serv.NewSRV(state.QName(), weight) records = append(records, srv) extra = append(extra, serv.NewA(srv.Target, ip.To4())) case ip.To4() == nil: serv.Host = msg.Domain(serv.Key) srv := serv.NewSRV(state.QName(), weight) records = append(records, srv) extra = append(extra, serv.NewAAAA(srv.Target, ip.To16())) } } return records, extra, debug, nil }
func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { state := middleware.State{W: w, Req: r} if state.QClass() != dns.ClassINET { return dns.RcodeServerFailure, errors.New("can only deal with ClassINET") } qname := state.Name() zone := middleware.Zones(f.Zones.Names).Matches(qname) if zone == "" { if f.Next != nil { return f.Next.ServeDNS(ctx, w, r) } return dns.RcodeServerFailure, errors.New("no next middleware found") } z, ok := f.Zones.Z[zone] if !ok { return f.Next.ServeDNS(ctx, w, r) } if z == nil { return dns.RcodeServerFailure, nil } if r.Opcode == dns.OpcodeNotify { if z.isNotify(state) { m := new(dns.Msg) m.SetReply(r) m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true state.SizeAndDo(m) w.WriteMsg(m) log.Printf("[INFO] Notify from %s for %s: checking transfer", state.IP(), zone) ok, err := z.shouldTransfer() if ok { z.TransferIn() } else { log.Printf("[INFO] Notify from %s for %s: no serial increase seen", state.IP(), zone) } if err != nil { log.Printf("[WARNING] Notify from %s for %s: failed primary check: %s", state.IP(), zone, err) } return dns.RcodeSuccess, nil } log.Printf("[INFO] Dropping notify from %s for %s", state.IP(), zone) return dns.RcodeSuccess, nil } if z.Expired != nil && *z.Expired { log.Printf("[ERROR] Zone %s is expired", zone) return dns.RcodeServerFailure, nil } if state.QType() == dns.TypeAXFR || state.QType() == dns.TypeIXFR { xfr := Xfr{z} return xfr.ServeDNS(ctx, w, r) } answer, ns, extra, result := z.Lookup(qname, state.QType(), state.Do()) m := new(dns.Msg) m.SetReply(r) m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true m.Answer, m.Ns, m.Extra = answer, ns, extra switch result { case Success: case NoData: case NameError: m.Rcode = dns.RcodeNameError case Delegation: m.Authoritative = false case ServerFailure: return dns.RcodeServerFailure, nil } state.SizeAndDo(m) m, _ = state.Scrub(m) w.WriteMsg(m) return dns.RcodeSuccess, nil }