mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-31 02:03:20 -04:00 
			
		
		
		
	Move all (almost all) Go files in middleware into their own packages. This makes for better naming and discoverability. Lot of changes elsewhere to make this change. The middleware.State was renamed to request.Request which is better, but still does not cover all use-cases. It was also moved out middleware because it is used by `dnsserver` as well. A pkg/dnsutil packages was added for shared, handy, dns util functions. All normalize functions are now put in normalize.go
		
			
				
	
	
		
			409 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			409 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package etcd
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"math"
 | |
| 	"net"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/miekg/coredns/middleware"
 | |
| 	"github.com/miekg/coredns/middleware/etcd/msg"
 | |
| 	"github.com/miekg/coredns/middleware/pkg/dnsutil"
 | |
| 	"github.com/miekg/coredns/request"
 | |
| 
 | |
| 	"github.com/miekg/dns"
 | |
| )
 | |
| 
 | |
| type Options struct {
 | |
| 	Debug string
 | |
| }
 | |
| 
 | |
| func (e Etcd) records(state request.Request, exact bool, opt Options) (services, debug []msg.Service, err error) {
 | |
| 	services, err = e.Records(state.Name(), exact)
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 	if opt.Debug != "" {
 | |
| 		debug = services
 | |
| 	}
 | |
| 	services = msg.Group(services)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (e Etcd) A(zone string, state request.Request, previousRecords []dns.RR, opt Options) (records []dns.RR, debug []msg.Service, err error) {
 | |
| 	services, debug, err := e.records(state, false, opt)
 | |
| 	if err != nil {
 | |
| 		return nil, debug, 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 dnsutil.DuplicateCNAME(newRecord, previousRecords) {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			state1 := state.NewWithQuestion(serv.Host, state.QType())
 | |
| 			nextRecords, nextDebug, err := e.A(zone, state1, append(previousRecords, newRecord), opt)
 | |
| 
 | |
| 			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 {
 | |
| 				debugMsg := msg.Service{Key: msg.Path(target, e.PathPrefix), Host: target, Text: " IN " + state.Type() + ": " + e1.Error()}
 | |
| 				debug = append(debug, debugMsg)
 | |
| 				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, debug, nil
 | |
| }
 | |
| 
 | |
| func (e Etcd) AAAA(zone string, state request.Request, previousRecords []dns.RR, opt Options) (records []dns.RR, debug []msg.Service, err error) {
 | |
| 	services, debug, err := e.records(state, false, opt)
 | |
| 	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 dnsutil.DuplicateCNAME(newRecord, previousRecords) {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			state1 := state.NewWithQuestion(serv.Host, state.QType())
 | |
| 			nextRecords, nextDebug, err := e.AAAA(zone, state1, append(previousRecords, newRecord), opt)
 | |
| 
 | |
| 			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 {
 | |
| 				debugMsg := msg.Service{Key: msg.Path(target, e.PathPrefix), Host: target, Text: " IN " + state.Type() + ": " + e1.Error()}
 | |
| 				debug = append(debug, debugMsg)
 | |
| 				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
 | |
| }
 | |
| 
 | |
| // 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 request.Request, opt Options) (records, extra []dns.RR, debug []msg.Service, err error) {
 | |
| 	services, debug, err := e.records(state, false, opt)
 | |
| 	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...)
 | |
| 				} else {
 | |
| 					debugMsg := msg.Service{Key: msg.Path(srv.Target, e.PathPrefix), Host: srv.Target, Text: " IN A: " + e1.Error()}
 | |
| 					debug = append(debug, debugMsg)
 | |
| 				}
 | |
| 
 | |
| 				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)
 | |
| 						}
 | |
| 					}
 | |
| 				} else {
 | |
| 					debugMsg := msg.Service{Key: msg.Path(srv.Target, e.PathPrefix), Host: srv.Target, Text: " IN AAAA: " + e1.Error()}
 | |
| 					debug = append(debug, debugMsg)
 | |
| 				}
 | |
| 				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 := state.NewWithQuestion(srv.Target, dns.TypeA)
 | |
| 			addr, debugAddr, e1 := e.A(zone, state1, nil, opt)
 | |
| 			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
 | |
| }
 | |
| 
 | |
| // 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 request.Request, opt Options) (records, extra []dns.RR, debug []msg.Service, err error) {
 | |
| 	services, debug, err := e.records(state, false, opt)
 | |
| 	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...)
 | |
| 				} else {
 | |
| 					debugMsg := msg.Service{Key: msg.Path(mx.Mx, e.PathPrefix), Host: mx.Mx, Text: " IN A: " + e1.Error()}
 | |
| 					debug = append(debug, debugMsg)
 | |
| 				}
 | |
| 				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)
 | |
| 						}
 | |
| 					}
 | |
| 				} else {
 | |
| 					debugMsg := msg.Service{Key: msg.Path(mx.Mx, e.PathPrefix), Host: mx.Mx, Text: " IN AAAA: " + e1.Error()}
 | |
| 					debug = append(debug, debugMsg)
 | |
| 				}
 | |
| 				break
 | |
| 			}
 | |
| 			// Internal name
 | |
| 			state1 := state.NewWithQuestion(mx.Mx, dns.TypeA)
 | |
| 			addr, debugAddr, e1 := e.A(zone, state1, nil, opt)
 | |
| 			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 (e Etcd) CNAME(zone string, state request.Request, opt Options) (records []dns.RR, debug []msg.Service, err error) {
 | |
| 	services, debug, err := e.records(state, true, opt)
 | |
| 	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
 | |
| }
 | |
| 
 | |
| // PTR returns the PTR records, only services that have a domain name as host are included.
 | |
| func (e Etcd) PTR(zone string, state request.Request, opt Options) (records []dns.RR, debug []msg.Service, err error) {
 | |
| 	services, debug, err := e.records(state, true, opt)
 | |
| 	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) TXT(zone string, state request.Request, opt Options) (records []dns.RR, debug []msg.Service, err error) {
 | |
| 	services, debug, err := e.records(state, false, opt)
 | |
| 	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 request.Request, opt Options) (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, opt)
 | |
| 	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
 | |
| }
 | |
| 
 | |
| // SOA Record returns a SOA record.
 | |
| func (e Etcd) SOA(zone string, state request.Request, opt Options) ([]dns.RR, []msg.Service, error) {
 | |
| 	header := dns.RR_Header{Name: zone, Rrtype: dns.TypeSOA, Ttl: 300, Class: dns.ClassINET}
 | |
| 
 | |
| 	soa := &dns.SOA{Hdr: header,
 | |
| 		Mbox:    hostmaster + "." + zone,
 | |
| 		Ns:      "ns.dns." + zone,
 | |
| 		Serial:  uint32(time.Now().Unix()),
 | |
| 		Refresh: 7200,
 | |
| 		Retry:   1800,
 | |
| 		Expire:  86400,
 | |
| 		Minttl:  minTTL,
 | |
| 	}
 | |
| 	// TODO(miek): fake some msg.Service here when returning.
 | |
| 	return []dns.RR{soa}, nil, nil
 | |
| }
 |