mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-29 01:04:15 -04:00 
			
		
		
		
	ServiceBackend interface (#369)
* Add ServiceBackend interface This adds a ServiceBackend interface that is shared between etcd/etcd3 (later) and kubernetes, leading to a massive reduction in code. When returning the specific records from their backend. Fixes #273
This commit is contained in:
		
							
								
								
									
										29
									
								
								middleware/backend.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								middleware/backend.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | package middleware | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/miekg/coredns/middleware/etcd/msg" | ||||||
|  | 	"github.com/miekg/coredns/request" | ||||||
|  |  | ||||||
|  | 	"github.com/miekg/dns" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ServiceBackend defines a (dynamic) backend that returns a slice of service definitions. | ||||||
|  | type ServiceBackend interface { | ||||||
|  | 	// Services communitates with the backend to retrieve the service defintion. Exact indicates | ||||||
|  | 	// on exact much are that we are allowed to recurs. | ||||||
|  | 	Services(state request.Request, exact bool, opt Options) ([]msg.Service, []msg.Service, error) | ||||||
|  |  | ||||||
|  | 	// Lookup is used to find records else where. | ||||||
|  | 	Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) | ||||||
|  |  | ||||||
|  | 	// IsNameError return true if err indicated a record not found condition | ||||||
|  | 	IsNameError(err error) bool | ||||||
|  |  | ||||||
|  | 	// Debug returns a string used when returning debug services. | ||||||
|  | 	Debug() string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Options are extra options that can be specified for a lookup. | ||||||
|  | type Options struct { | ||||||
|  | 	Debug string // This is a debug query. A query prefixed with debug.o-o | ||||||
|  | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package etcd | package middleware | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| @@ -6,7 +6,6 @@ import ( | |||||||
| 	"net" | 	"net" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/miekg/coredns/middleware" |  | ||||||
| 	"github.com/miekg/coredns/middleware/etcd/msg" | 	"github.com/miekg/coredns/middleware/etcd/msg" | ||||||
| 	"github.com/miekg/coredns/middleware/pkg/dnsutil" | 	"github.com/miekg/coredns/middleware/pkg/dnsutil" | ||||||
| 	"github.com/miekg/coredns/request" | 	"github.com/miekg/coredns/request" | ||||||
| @@ -14,26 +13,9 @@ import ( | |||||||
| 	"github.com/miekg/dns" | 	"github.com/miekg/dns" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Options are extra options that can be specified for a lookup. | // A returns A records from Backend or an error. | ||||||
| type Options struct { | func A(b ServiceBackend, zone string, state request.Request, previousRecords []dns.RR, opt Options) (records []dns.RR, debug []msg.Service, err error) { | ||||||
| 	Debug string // This is a debug query. A query prefixed with debug.o-o | 	services, debug, err := b.Services(state, false, opt) | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // A returns A records from etcd or an error. |  | ||||||
| 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 { | 	if err != nil { | ||||||
| 		return nil, debug, err | 		return nil, debug, err | ||||||
| 	} | 	} | ||||||
| @@ -42,8 +24,7 @@ func (e Etcd) A(zone string, state request.Request, previousRecords []dns.RR, op | |||||||
| 		ip := net.ParseIP(serv.Host) | 		ip := net.ParseIP(serv.Host) | ||||||
| 		switch { | 		switch { | ||||||
| 		case ip == nil: | 		case ip == nil: | ||||||
| 			// TODO(miek): lowercasing? Should lowercase in everything see #85 | 			if Name(state.Name()).Matches(dns.Fqdn(serv.Host)) { | ||||||
| 			if middleware.Name(state.Name()).Matches(dns.Fqdn(serv.Host)) { |  | ||||||
| 				// x CNAME x is a direct loop, don't add those | 				// x CNAME x is a direct loop, don't add those | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| @@ -58,7 +39,7 @@ func (e Etcd) A(zone string, state request.Request, previousRecords []dns.RR, op | |||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			state1 := state.NewWithQuestion(serv.Host, state.QType()) | 			state1 := state.NewWithQuestion(serv.Host, state.QType()) | ||||||
| 			nextRecords, nextDebug, err := e.A(zone, state1, append(previousRecords, newRecord), opt) | 			nextRecords, nextDebug, err := A(b, zone, state1, append(previousRecords, newRecord), opt) | ||||||
| 
 | 
 | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				// Not only have we found something we should add the CNAME and the IP addresses. | 				// Not only have we found something we should add the CNAME and the IP addresses. | ||||||
| @@ -75,9 +56,10 @@ func (e Etcd) A(zone string, state request.Request, previousRecords []dns.RR, op | |||||||
| 				// We should already have found it | 				// We should already have found it | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 			m1, e1 := e.Proxy.Lookup(state, target, state.QType()) | 			// Lookup | ||||||
|  | 			m1, e1 := b.Lookup(state, target, state.QType()) | ||||||
| 			if e1 != nil { | 			if e1 != nil { | ||||||
| 				debugMsg := msg.Service{Key: msg.Path(target, e.PathPrefix), Host: target, Text: " IN " + state.Type() + ": " + e1.Error()} | 				debugMsg := msg.Service{Key: msg.Path(target, b.Debug()), Host: target, Text: " IN " + state.Type() + ": " + e1.Error()} | ||||||
| 				debug = append(debug, debugMsg) | 				debug = append(debug, debugMsg) | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| @@ -94,9 +76,9 @@ func (e Etcd) A(zone string, state request.Request, previousRecords []dns.RR, op | |||||||
| 	return records, debug, nil | 	return records, debug, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // AAAA returns AAAA records from etcd or an error. | // AAAA returns AAAA records from Backend or an error. | ||||||
| func (e Etcd) AAAA(zone string, state request.Request, previousRecords []dns.RR, opt Options) (records []dns.RR, debug []msg.Service, err error) { | func AAAA(b ServiceBackend, 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) | 	services, debug, err := b.Services(state, false, opt) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, debug, err | 		return nil, debug, err | ||||||
| 	} | 	} | ||||||
| @@ -106,7 +88,7 @@ func (e Etcd) AAAA(zone string, state request.Request, previousRecords []dns.RR, | |||||||
| 		switch { | 		switch { | ||||||
| 		case ip == nil: | 		case ip == nil: | ||||||
| 			// Try to resolve as CNAME if it's not an IP, but only if we don't create loops. | 			// 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)) { | 			if Name(state.Name()).Matches(dns.Fqdn(serv.Host)) { | ||||||
| 				// x CNAME x is a direct loop, don't add those | 				// x CNAME x is a direct loop, don't add those | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| @@ -121,7 +103,7 @@ func (e Etcd) AAAA(zone string, state request.Request, previousRecords []dns.RR, | |||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			state1 := state.NewWithQuestion(serv.Host, state.QType()) | 			state1 := state.NewWithQuestion(serv.Host, state.QType()) | ||||||
| 			nextRecords, nextDebug, err := e.AAAA(zone, state1, append(previousRecords, newRecord), opt) | 			nextRecords, nextDebug, err := AAAA(b, zone, state1, append(previousRecords, newRecord), opt) | ||||||
| 
 | 
 | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				// Not only have we found something we should add the CNAME and the IP addresses. | 				// Not only have we found something we should add the CNAME and the IP addresses. | ||||||
| @@ -138,9 +120,9 @@ func (e Etcd) AAAA(zone string, state request.Request, previousRecords []dns.RR, | |||||||
| 				// We should already have found it | 				// We should already have found it | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 			m1, e1 := e.Proxy.Lookup(state, target, state.QType()) | 			m1, e1 := b.Lookup(state, target, state.QType()) | ||||||
| 			if e1 != nil { | 			if e1 != nil { | ||||||
| 				debugMsg := msg.Service{Key: msg.Path(target, e.PathPrefix), Host: target, Text: " IN " + state.Type() + ": " + e1.Error()} | 				debugMsg := msg.Service{Key: msg.Path(target, b.Debug()), Host: target, Text: " IN " + state.Type() + ": " + e1.Error()} | ||||||
| 				debug = append(debug, debugMsg) | 				debug = append(debug, debugMsg) | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| @@ -158,10 +140,10 @@ func (e Etcd) AAAA(zone string, state request.Request, previousRecords []dns.RR, | |||||||
| 	return records, debug, nil | 	return records, debug, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SRV returns SRV records from etcd. | // SRV returns SRV records from the Backend. | ||||||
| // If the Target is not a name but an IP address, a name is created on the fly. | // 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) { | func SRV(b ServiceBackend, zone string, state request.Request, opt Options) (records, extra []dns.RR, debug []msg.Service, err error) { | ||||||
| 	services, debug, err := e.records(state, false, opt) | 	services, debug, err := b.Services(state, false, opt) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, nil, nil, err | 		return nil, nil, nil, err | ||||||
| 	} | 	} | ||||||
| @@ -201,15 +183,15 @@ func (e Etcd) SRV(zone string, state request.Request, opt Options) (records, ext | |||||||
| 			lookup[srv.Target] = true | 			lookup[srv.Target] = true | ||||||
| 
 | 
 | ||||||
| 			if !dns.IsSubDomain(zone, srv.Target) { | 			if !dns.IsSubDomain(zone, srv.Target) { | ||||||
| 				m1, e1 := e.Proxy.Lookup(state, srv.Target, dns.TypeA) | 				m1, e1 := b.Lookup(state, srv.Target, dns.TypeA) | ||||||
| 				if e1 == nil { | 				if e1 == nil { | ||||||
| 					extra = append(extra, m1.Answer...) | 					extra = append(extra, m1.Answer...) | ||||||
| 				} else { | 				} else { | ||||||
| 					debugMsg := msg.Service{Key: msg.Path(srv.Target, e.PathPrefix), Host: srv.Target, Text: " IN A: " + e1.Error()} | 					debugMsg := msg.Service{Key: msg.Path(srv.Target, b.Debug()), Host: srv.Target, Text: " IN A: " + e1.Error()} | ||||||
| 					debug = append(debug, debugMsg) | 					debug = append(debug, debugMsg) | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				m1, e1 = e.Proxy.Lookup(state, srv.Target, dns.TypeAAAA) | 				m1, e1 = b.Lookup(state, srv.Target, dns.TypeAAAA) | ||||||
| 				if e1 == nil { | 				if e1 == nil { | ||||||
| 					// If we have seen CNAME's we *assume* that they are already added. | 					// If we have seen CNAME's we *assume* that they are already added. | ||||||
| 					for _, a := range m1.Answer { | 					for _, a := range m1.Answer { | ||||||
| @@ -218,7 +200,7 @@ func (e Etcd) SRV(zone string, state request.Request, opt Options) (records, ext | |||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} else { | 				} else { | ||||||
| 					debugMsg := msg.Service{Key: msg.Path(srv.Target, e.PathPrefix), Host: srv.Target, Text: " IN AAAA: " + e1.Error()} | 					debugMsg := msg.Service{Key: msg.Path(srv.Target, b.Debug()), Host: srv.Target, Text: " IN AAAA: " + e1.Error()} | ||||||
| 					debug = append(debug, debugMsg) | 					debug = append(debug, debugMsg) | ||||||
| 				} | 				} | ||||||
| 				break | 				break | ||||||
| @@ -226,12 +208,12 @@ func (e Etcd) SRV(zone string, state request.Request, opt Options) (records, ext | |||||||
| 			// Internal name, we should have some info on them, either v4 or v6 | 			// 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. | 			// Clients expect a complete answer, because we are a recursor in their view. | ||||||
| 			state1 := state.NewWithQuestion(srv.Target, dns.TypeA) | 			state1 := state.NewWithQuestion(srv.Target, dns.TypeA) | ||||||
| 			addr, debugAddr, e1 := e.A(zone, state1, nil, opt) | 			addr, debugAddr, e1 := A(b, zone, state1, nil, Options(opt)) | ||||||
| 			if e1 == nil { | 			if e1 == nil { | ||||||
| 				extra = append(extra, addr...) | 				extra = append(extra, addr...) | ||||||
| 				debug = append(debug, debugAddr...) | 				debug = append(debug, debugAddr...) | ||||||
| 			} | 			} | ||||||
| 			// e.AAA(zone, state1, nil) as well...? | 			// IPv6 lookups here as well? AAAA(zone, state1, nil). | ||||||
| 		case ip.To4() != nil: | 		case ip.To4() != nil: | ||||||
| 			serv.Host = msg.Domain(serv.Key) | 			serv.Host = msg.Domain(serv.Key) | ||||||
| 			srv := serv.NewSRV(state.QName(), weight) | 			srv := serv.NewSRV(state.QName(), weight) | ||||||
| @@ -249,10 +231,9 @@ func (e Etcd) SRV(zone string, state request.Request, opt Options) (records, ext | |||||||
| 	return records, extra, debug, nil | 	return records, extra, debug, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // MX returns MX records from etcd. | // MX returns MX records from the Backend. If the Target is not a name but an IP address, a name is created on the fly. | ||||||
| // If the Target is not a name but an IP address, a name is created on the fly. | func MX(b ServiceBackend, zone string, state request.Request, opt Options) (records, extra []dns.RR, debug []msg.Service, err error) { | ||||||
| func (e Etcd) MX(zone string, state request.Request, opt Options) (records, extra []dns.RR, debug []msg.Service, err error) { | 	services, debug, err := b.Services(state, false, opt) | ||||||
| 	services, debug, err := e.records(state, false, opt) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, nil, debug, err | 		return nil, nil, debug, err | ||||||
| 	} | 	} | ||||||
| @@ -274,14 +255,14 @@ func (e Etcd) MX(zone string, state request.Request, opt Options) (records, extr | |||||||
| 			lookup[mx.Mx] = true | 			lookup[mx.Mx] = true | ||||||
| 
 | 
 | ||||||
| 			if !dns.IsSubDomain(zone, mx.Mx) { | 			if !dns.IsSubDomain(zone, mx.Mx) { | ||||||
| 				m1, e1 := e.Proxy.Lookup(state, mx.Mx, dns.TypeA) | 				m1, e1 := b.Lookup(state, mx.Mx, dns.TypeA) | ||||||
| 				if e1 == nil { | 				if e1 == nil { | ||||||
| 					extra = append(extra, m1.Answer...) | 					extra = append(extra, m1.Answer...) | ||||||
| 				} else { | 				} else { | ||||||
| 					debugMsg := msg.Service{Key: msg.Path(mx.Mx, e.PathPrefix), Host: mx.Mx, Text: " IN A: " + e1.Error()} | 					debugMsg := msg.Service{Key: msg.Path(mx.Mx, b.Debug()), Host: mx.Mx, Text: " IN A: " + e1.Error()} | ||||||
| 					debug = append(debug, debugMsg) | 					debug = append(debug, debugMsg) | ||||||
| 				} | 				} | ||||||
| 				m1, e1 = e.Proxy.Lookup(state, mx.Mx, dns.TypeAAAA) | 				m1, e1 = b.Lookup(state, mx.Mx, dns.TypeAAAA) | ||||||
| 				if e1 == nil { | 				if e1 == nil { | ||||||
| 					// If we have seen CNAME's we *assume* that they are already added. | 					// If we have seen CNAME's we *assume* that they are already added. | ||||||
| 					for _, a := range m1.Answer { | 					for _, a := range m1.Answer { | ||||||
| @@ -290,14 +271,14 @@ func (e Etcd) MX(zone string, state request.Request, opt Options) (records, extr | |||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} else { | 				} else { | ||||||
| 					debugMsg := msg.Service{Key: msg.Path(mx.Mx, e.PathPrefix), Host: mx.Mx, Text: " IN AAAA: " + e1.Error()} | 					debugMsg := msg.Service{Key: msg.Path(mx.Mx, b.Debug()), Host: mx.Mx, Text: " IN AAAA: " + e1.Error()} | ||||||
| 					debug = append(debug, debugMsg) | 					debug = append(debug, debugMsg) | ||||||
| 				} | 				} | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
| 			// Internal name | 			// Internal name | ||||||
| 			state1 := state.NewWithQuestion(mx.Mx, dns.TypeA) | 			state1 := state.NewWithQuestion(mx.Mx, dns.TypeA) | ||||||
| 			addr, debugAddr, e1 := e.A(zone, state1, nil, opt) | 			addr, debugAddr, e1 := A(b, zone, state1, nil, opt) | ||||||
| 			if e1 == nil { | 			if e1 == nil { | ||||||
| 				extra = append(extra, addr...) | 				extra = append(extra, addr...) | ||||||
| 				debug = append(debug, debugAddr...) | 				debug = append(debug, debugAddr...) | ||||||
| @@ -316,9 +297,9 @@ func (e Etcd) MX(zone string, state request.Request, opt Options) (records, extr | |||||||
| 	return records, extra, debug, nil | 	return records, extra, debug, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // CNAME returns CNAME records from etcd or an error. | // CNAME returns CNAME records from the backend or an error. | ||||||
| func (e Etcd) CNAME(zone string, state request.Request, opt Options) (records []dns.RR, debug []msg.Service, err error) { | func CNAME(b ServiceBackend, zone string, state request.Request, opt Options) (records []dns.RR, debug []msg.Service, err error) { | ||||||
| 	services, debug, err := e.records(state, true, opt) | 	services, debug, err := b.Services(state, true, opt) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, debug, err | 		return nil, debug, err | ||||||
| 	} | 	} | ||||||
| @@ -332,24 +313,9 @@ func (e Etcd) CNAME(zone string, state request.Request, opt Options) (records [] | |||||||
| 	return records, debug, nil | 	return records, debug, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // PTR returns the PTR records, only services that have a domain name as host are included. | // TXT returns TXT records from Backend or an error. | ||||||
| func (e Etcd) PTR(zone string, state request.Request, opt Options) (records []dns.RR, debug []msg.Service, err error) { | func TXT(b ServiceBackend, zone string, state request.Request, opt Options) (records []dns.RR, debug []msg.Service, err error) { | ||||||
| 	services, debug, err := e.records(state, true, opt) | 	services, debug, err := b.Services(state, false, 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 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // TXT returns TXT records from etcd or an error. |  | ||||||
| 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 { | 	if err != nil { | ||||||
| 		return nil, debug, err | 		return nil, debug, err | ||||||
| 	} | 	} | ||||||
| @@ -363,15 +329,31 @@ func (e Etcd) TXT(zone string, state request.Request, opt Options) (records []dn | |||||||
| 	return records, debug, nil | 	return records, debug, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NS returns NS records from etcd or an error. | // PTR returns the PTR records from the backend, only services that have a domain name as host are included. | ||||||
| func (e Etcd) NS(zone string, state request.Request, opt Options) (records, extra []dns.RR, debug []msg.Service, err error) { | // TODO(miek|infoblox): move k8s to this as well. | ||||||
|  | func PTR(b ServiceBackend, zone string, state request.Request, opt Options) (records []dns.RR, debug []msg.Service, err error) { | ||||||
|  | 	services, debug, err := b.Services(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 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NS returns NS records from  the backend | ||||||
|  | func NS(b ServiceBackend, 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. | 	// NS record for this zone live in a special place, ns.dns.<zone>. Fake our lookup. | ||||||
| 	// only a tad bit fishy... | 	// only a tad bit fishy... | ||||||
| 	old := state.QName() | 	old := state.QName() | ||||||
| 
 | 
 | ||||||
| 	state.Clear() | 	state.Clear() | ||||||
| 	state.Req.Question[0].Name = "ns.dns." + zone | 	state.Req.Question[0].Name = "ns.dns." + zone | ||||||
| 	services, debug, err := e.records(state, false, opt) | 	services, debug, err := b.Services(state, false, opt) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, nil, debug, err | 		return nil, nil, debug, err | ||||||
| 	} | 	} | ||||||
| @@ -396,8 +378,8 @@ func (e Etcd) NS(zone string, state request.Request, opt Options) (records, extr | |||||||
| 	return records, extra, debug, nil | 	return records, extra, debug, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SOA returns a SOA record from etcd. | // SOA returns a SOA record from the backend. | ||||||
| func (e Etcd) SOA(zone string, state request.Request, opt Options) ([]dns.RR, []msg.Service, error) { | func SOA(b ServiceBackend, 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} | 	header := dns.RR_Header{Name: zone, Rrtype: dns.TypeSOA, Ttl: 300, Class: dns.ClassINET} | ||||||
| 
 | 
 | ||||||
| 	soa := &dns.SOA{Hdr: header, | 	soa := &dns.SOA{Hdr: header, | ||||||
| @@ -409,6 +391,62 @@ func (e Etcd) SOA(zone string, state request.Request, opt Options) ([]dns.RR, [] | |||||||
| 		Expire:  86400, | 		Expire:  86400, | ||||||
| 		Minttl:  minTTL, | 		Minttl:  minTTL, | ||||||
| 	} | 	} | ||||||
| 	// TODO(miek): fake some msg.Service here when returning. | 	// TODO(miek): fake some msg.Service here when returning? | ||||||
| 	return []dns.RR{soa}, nil, nil | 	return []dns.RR{soa}, nil, nil | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // BackendError writes an error response to the client. | ||||||
|  | func BackendError(b ServiceBackend, zone string, rcode int, state request.Request, debug []msg.Service, err error, opt Options) (int, error) { | ||||||
|  | 	m := new(dns.Msg) | ||||||
|  | 	m.SetRcode(state.Req, rcode) | ||||||
|  | 	m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true | ||||||
|  | 	m.Ns, _, _ = SOA(b, zone, state, opt) | ||||||
|  | 	if opt.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 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ServicesToTxt puts debug in TXT RRs. | ||||||
|  | func ServicesToTxt(debug []msg.Service) []dns.RR { | ||||||
|  | 	if debug == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	rr := make([]dns.RR, len(debug)) | ||||||
|  | 	for i, d := range debug { | ||||||
|  | 		rr[i] = d.RR() | ||||||
|  | 	} | ||||||
|  | 	return rr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ErrorToTxt puts in error's text into an TXT RR. | ||||||
|  | func ErrorToTxt(err error) dns.RR { | ||||||
|  | 	if err == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	msg := err.Error() | ||||||
|  | 	if len(msg) > 255 { | ||||||
|  | 		msg = msg[:255] | ||||||
|  | 	} | ||||||
|  | 	t := new(dns.TXT) | ||||||
|  | 	t.Hdr.Class = dns.ClassCHAOS | ||||||
|  | 	t.Hdr.Ttl = 0 | ||||||
|  | 	t.Hdr.Rrtype = dns.TypeTXT | ||||||
|  | 	t.Hdr.Name = "." | ||||||
|  | 
 | ||||||
|  | 	t.Txt = []string{msg} | ||||||
|  | 	return t | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	minTTL     = 60 | ||||||
|  | 	hostmaster = "hostmaster" | ||||||
|  | ) | ||||||
| @@ -9,8 +9,8 @@ This is useful for retrieving version or author information from the server. | |||||||
| chaos [VERSION] [AUTHORS...] | chaos [VERSION] [AUTHORS...] | ||||||
| ~~~ | ~~~ | ||||||
|  |  | ||||||
| * **VERSION** the version to return. Defaults to CoreDNS-<version>, if not set. | * **VERSION** is the version to return. Defaults to `CoreDNS-<version>`, if not set. | ||||||
| * **AUTHORS** what authors to return. No default. | * **AUTHORS** is what authors to return. No default. | ||||||
|  |  | ||||||
| Note that you have to make sure that this middleware will get actual queries for the | Note that you have to make sure that this middleware will get actual queries for the | ||||||
| following zones: `version.bind`, `version.server`, `authors.bind`, `hostname.bind` and | following zones: `version.bind`, `version.server`, `authors.bind`, `hostname.bind` and | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ etcd [ZONES...] { | |||||||
|   pointing to external names. If you want CoreDNS to act as a proxy for clients, you'll need to add |   pointing to external names. If you want CoreDNS to act as a proxy for clients, you'll need to add | ||||||
|   the proxy middleware. |   the proxy middleware. | ||||||
| * `tls` followed the cert, key and the CA's cert filenames. | * `tls` followed the cert, key and the CA's cert filenames. | ||||||
| * `debug` allow debug queries. Prefix the name with `o-o.debug.` to retrieve extra information in the | * `debug` allows for debug queries. Prefix the name with `o-o.debug.` to retrieve extra information in the | ||||||
|   additional section of the reply in the form of TXT records. |   additional section of the reply in the form of TXT records. | ||||||
|  |  | ||||||
| ## Examples | ## Examples | ||||||
|   | |||||||
| @@ -1,12 +1,6 @@ | |||||||
| package etcd | package etcd | ||||||
|  |  | ||||||
| import ( | import "strings" | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"github.com/miekg/coredns/middleware/etcd/msg" |  | ||||||
|  |  | ||||||
| 	"github.com/miekg/dns" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| const debugName = "o-o.debug." | const debugName = "o-o.debug." | ||||||
|  |  | ||||||
| @@ -24,34 +18,3 @@ func isDebug(name string) string { | |||||||
| 	} | 	} | ||||||
| 	return name[len(debugName):] | 	return name[len(debugName):] | ||||||
| } | } | ||||||
|  |  | ||||||
| // servicesToTxt puts debug in TXT RRs. |  | ||||||
| func servicesToTxt(debug []msg.Service) []dns.RR { |  | ||||||
| 	if debug == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	rr := make([]dns.RR, len(debug)) |  | ||||||
| 	for i, d := range debug { |  | ||||||
| 		rr[i] = d.RR() |  | ||||||
| 	} |  | ||||||
| 	return rr |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func errorToTxt(err error) dns.RR { |  | ||||||
| 	if err == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	msg := err.Error() |  | ||||||
| 	if len(msg) > 255 { |  | ||||||
| 		msg = msg[:255] |  | ||||||
| 	} |  | ||||||
| 	t := new(dns.TXT) |  | ||||||
| 	t.Hdr.Class = dns.ClassCHAOS |  | ||||||
| 	t.Hdr.Ttl = 0 |  | ||||||
| 	t.Hdr.Rrtype = dns.TypeTXT |  | ||||||
| 	t.Hdr.Name = "." |  | ||||||
|  |  | ||||||
| 	t.Txt = []string{msg} |  | ||||||
| 	return t |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ func TestIsDebug(t *testing.T) { | |||||||
|  |  | ||||||
| func TestDebugLookup(t *testing.T) { | func TestDebugLookup(t *testing.T) { | ||||||
| 	etc := newEtcdMiddleware() | 	etc := newEtcdMiddleware() | ||||||
| 	etc.Debug = true | 	etc.Debugging = true | ||||||
|  |  | ||||||
| 	for _, serv := range servicesDebug { | 	for _, serv := range servicesDebug { | ||||||
| 		set(t, etc, serv.Key, 0, serv) | 		set(t, etc, serv.Key, 0, serv) | ||||||
|   | |||||||
| @@ -11,8 +11,10 @@ import ( | |||||||
| 	"github.com/miekg/coredns/middleware/etcd/msg" | 	"github.com/miekg/coredns/middleware/etcd/msg" | ||||||
| 	"github.com/miekg/coredns/middleware/pkg/singleflight" | 	"github.com/miekg/coredns/middleware/pkg/singleflight" | ||||||
| 	"github.com/miekg/coredns/middleware/proxy" | 	"github.com/miekg/coredns/middleware/proxy" | ||||||
|  | 	"github.com/miekg/coredns/request" | ||||||
|  |  | ||||||
| 	etcdc "github.com/coreos/etcd/client" | 	etcdc "github.com/coreos/etcd/client" | ||||||
|  | 	"github.com/miekg/dns" | ||||||
| 	"golang.org/x/net/context" | 	"golang.org/x/net/context" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -26,17 +28,47 @@ type Etcd struct { | |||||||
| 	Ctx        context.Context | 	Ctx        context.Context | ||||||
| 	Inflight   *singleflight.Group | 	Inflight   *singleflight.Group | ||||||
| 	Stubmap    *map[string]proxy.Proxy // list of proxies for stub resolving. | 	Stubmap    *map[string]proxy.Proxy // list of proxies for stub resolving. | ||||||
| 	Debug      bool                    // Do we allow debug queries. | 	Debugging  bool                    // Do we allow debug queries. | ||||||
|  |  | ||||||
| 	endpoints []string // Stored here as well, to aid in testing. | 	endpoints []string // Stored here as well, to aid in testing. | ||||||
| } | } | ||||||
|  |  | ||||||
| // Records looks up records in etcd. If exact is true, it will lookup just | // Services implements the ServiceBackend interface. | ||||||
| // this name. This is used when find matches when completing SRV lookups | func (e *Etcd) Services(state request.Request, exact bool, opt middleware.Options) (services, debug []msg.Service, err error) { | ||||||
| // for instance. | 	services, err = e.Records(state.Name(), exact) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if opt.Debug != "" { | ||||||
|  | 		debug = services | ||||||
|  | 	} | ||||||
|  | 	services = msg.Group(services) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Lookup implements the ServiceBackend interface. | ||||||
|  | func (e *Etcd) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) { | ||||||
|  | 	return e.Proxy.Lookup(state, name, typ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IsNameError implements the ServiceBackend interface. | ||||||
|  | func (e *Etcd) IsNameError(err error) bool { | ||||||
|  | 	if ee, ok := err.(etcdc.Error); ok && ee.Code == etcdc.ErrorCodeKeyNotFound { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Debug implements the ServiceBackend interface. | ||||||
|  | func (e *Etcd) Debug() string { | ||||||
|  | 	return e.PathPrefix | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Records looks up records in etcd. If exact is true, it will lookup just this | ||||||
|  | // name. This is used when find matches when completing SRV lookups for instance. | ||||||
| func (e *Etcd) Records(name string, exact bool) ([]msg.Service, error) { | func (e *Etcd) Records(name string, exact bool) ([]msg.Service, error) { | ||||||
| 	path, star := msg.PathWithWildcard(name, e.PathPrefix) | 	path, star := msg.PathWithWildcard(name, e.PathPrefix) | ||||||
| 	r, err := e.Get(path, true) | 	r, err := e.get(path, true) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -51,8 +83,8 @@ func (e *Etcd) Records(name string, exact bool) ([]msg.Service, error) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // Get is a wrapper for client.Get that uses SingleInflight to suppress multiple outstanding queries. | // get is a wrapper for client.Get that uses SingleInflight to suppress multiple outstanding queries. | ||||||
| func (e *Etcd) Get(path string, recursive bool) (*etcdc.Response, error) { | func (e *Etcd) get(path string, recursive bool) (*etcdc.Response, error) { | ||||||
| 	resp, err := e.Inflight.Do(path, func() (interface{}, error) { | 	resp, err := e.Inflight.Do(path, func() (interface{}, error) { | ||||||
| 		ctx, cancel := context.WithTimeout(e.Ctx, etcdTimeout) | 		ctx, cancel := context.WithTimeout(e.Ctx, etcdTimeout) | ||||||
| 		defer cancel() | 		defer cancel() | ||||||
| @@ -76,7 +108,7 @@ func (e *Etcd) Get(path string, recursive bool) (*etcdc.Response, error) { | |||||||
|  |  | ||||||
| // loopNodes recursively loops through the nodes and returns all the values. The nodes' keyname | // loopNodes recursively loops through the nodes and returns all the values. The nodes' keyname | ||||||
| // will be match against any wildcards when star is true. | // will be match against any wildcards when star is true. | ||||||
| func (e Etcd) loopNodes(ns []*etcdc.Node, nameParts []string, star bool, bx map[msg.Service]bool) (sx []msg.Service, err error) { | func (e *Etcd) loopNodes(ns []*etcdc.Node, nameParts []string, star bool, bx map[msg.Service]bool) (sx []msg.Service, err error) { | ||||||
| 	if bx == nil { | 	if bx == nil { | ||||||
| 		bx = make(map[msg.Service]bool) | 		bx = make(map[msg.Service]bool) | ||||||
| 	} | 	} | ||||||
| @@ -145,18 +177,8 @@ func (e *Etcd) TTL(node *etcdc.Node, serv *msg.Service) uint32 { | |||||||
| 	return serv.TTL | 	return serv.TTL | ||||||
| } | } | ||||||
|  |  | ||||||
| // etcNameError checks if the error is ErrorCodeKeyNotFound from etcd. |  | ||||||
| func isEtcdNameError(err error) bool { |  | ||||||
| 	if e, ok := err.(etcdc.Error); ok && e.Code == etcdc.ErrorCodeKeyNotFound { |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	priority    = 10  // default priority when nothing is set | 	priority    = 10  // default priority when nothing is set | ||||||
| 	ttl         = 300 // default ttl when nothing is set | 	ttl         = 300 // default ttl when nothing is set | ||||||
| 	minTTL      = 60 |  | ||||||
| 	hostmaster  = "hostmaster" |  | ||||||
| 	etcdTimeout = 5 * time.Second | 	etcdTimeout = 5 * time.Second | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -14,13 +14,13 @@ import ( | |||||||
|  |  | ||||||
| // ServeDNS implements the middleware.Handler interface. | // ServeDNS implements the middleware.Handler interface. | ||||||
| func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { | func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { | ||||||
| 	opt := Options{} | 	opt := middleware.Options{} | ||||||
| 	state := request.Request{W: w, Req: r} | 	state := request.Request{W: w, Req: r} | ||||||
| 	if state.QClass() != dns.ClassINET { | 	if state.QClass() != dns.ClassINET { | ||||||
| 		return dns.RcodeServerFailure, fmt.Errorf("can only deal with ClassINET") | 		return dns.RcodeServerFailure, fmt.Errorf("can only deal with ClassINET") | ||||||
| 	} | 	} | ||||||
| 	name := state.Name() | 	name := state.Name() | ||||||
| 	if e.Debug { | 	if e.Debugging { | ||||||
| 		if debug := isDebug(name); debug != "" { | 		if debug := isDebug(name); debug != "" { | ||||||
| 			opt.Debug = r.Question[0].Name | 			opt.Debug = r.Question[0].Name | ||||||
| 			state.Clear() | 			state.Clear() | ||||||
| @@ -58,30 +58,30 @@ func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) ( | |||||||
| 	) | 	) | ||||||
| 	switch state.Type() { | 	switch state.Type() { | ||||||
| 	case "A": | 	case "A": | ||||||
| 		records, debug, err = e.A(zone, state, nil, opt) | 		records, debug, err = middleware.A(e, zone, state, nil, opt) | ||||||
| 	case "AAAA": | 	case "AAAA": | ||||||
| 		records, debug, err = e.AAAA(zone, state, nil, opt) | 		records, debug, err = middleware.AAAA(e, zone, state, nil, opt) | ||||||
| 	case "TXT": | 	case "TXT": | ||||||
| 		records, debug, err = e.TXT(zone, state, opt) | 		records, debug, err = middleware.TXT(e, zone, state, opt) | ||||||
| 	case "CNAME": | 	case "CNAME": | ||||||
| 		records, debug, err = e.CNAME(zone, state, opt) | 		records, debug, err = middleware.CNAME(e, zone, state, opt) | ||||||
| 	case "PTR": | 	case "PTR": | ||||||
| 		records, debug, err = e.PTR(zone, state, opt) | 		records, debug, err = middleware.PTR(e, zone, state, opt) | ||||||
| 	case "MX": | 	case "MX": | ||||||
| 		records, extra, debug, err = e.MX(zone, state, opt) | 		records, extra, debug, err = middleware.MX(e, zone, state, opt) | ||||||
| 	case "SRV": | 	case "SRV": | ||||||
| 		records, extra, debug, err = e.SRV(zone, state, opt) | 		records, extra, debug, err = middleware.SRV(e, zone, state, opt) | ||||||
| 	case "SOA": | 	case "SOA": | ||||||
| 		records, debug, err = e.SOA(zone, state, opt) | 		records, debug, err = middleware.SOA(e, zone, state, opt) | ||||||
| 	case "NS": | 	case "NS": | ||||||
| 		if state.Name() == zone { | 		if state.Name() == zone { | ||||||
| 			records, extra, debug, err = e.NS(zone, state, opt) | 			records, extra, debug, err = middleware.NS(e, zone, state, opt) | ||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
| 		fallthrough | 		fallthrough | ||||||
| 	default: | 	default: | ||||||
| 		// Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN | 		// Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN | ||||||
| 		_, debug, err = e.A(zone, state, nil, opt) | 		_, debug, err = middleware.A(e, zone, state, nil, opt) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if opt.Debug != "" { | 	if opt.Debug != "" { | ||||||
| @@ -90,15 +90,15 @@ func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) ( | |||||||
| 		state.Req.Question[0].Name = opt.Debug | 		state.Req.Question[0].Name = opt.Debug | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if isEtcdNameError(err) { | 	if e.IsNameError(err) { | ||||||
| 		return e.Err(zone, dns.RcodeNameError, state, debug, err, opt) | 		return middleware.BackendError(e, zone, dns.RcodeNameError, state, debug, err, opt) | ||||||
| 	} | 	} | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return e.Err(zone, dns.RcodeServerFailure, state, debug, err, opt) | 		return middleware.BackendError(e, zone, dns.RcodeServerFailure, state, debug, err, opt) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(records) == 0 { | 	if len(records) == 0 { | ||||||
| 		return e.Err(zone, dns.RcodeSuccess, state, debug, err, opt) | 		return middleware.BackendError(e, zone, dns.RcodeSuccess, state, debug, err, opt) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	m := new(dns.Msg) | 	m := new(dns.Msg) | ||||||
| @@ -107,7 +107,7 @@ func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) ( | |||||||
| 	m.Answer = append(m.Answer, records...) | 	m.Answer = append(m.Answer, records...) | ||||||
| 	m.Extra = append(m.Extra, extra...) | 	m.Extra = append(m.Extra, extra...) | ||||||
| 	if opt.Debug != "" { | 	if opt.Debug != "" { | ||||||
| 		m.Extra = append(m.Extra, servicesToTxt(debug)...) | 		m.Extra = append(m.Extra, middleware.ServicesToTxt(debug)...) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	m = dnsutil.Dedup(m) | 	m = dnsutil.Dedup(m) | ||||||
| @@ -119,22 +119,3 @@ func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) ( | |||||||
|  |  | ||||||
| // Name implements the Handler interface. | // Name implements the Handler interface. | ||||||
| func (e *Etcd) Name() string { return "etcd" } | func (e *Etcd) Name() string { return "etcd" } | ||||||
|  |  | ||||||
| // Err write an error response to the client. |  | ||||||
| func (e *Etcd) Err(zone string, rcode int, state request.Request, debug []msg.Service, err error, opt Options) (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, opt) |  | ||||||
| 	if opt.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 |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ import ( | |||||||
| func TestProxyLookupFailDebug(t *testing.T) { | func TestProxyLookupFailDebug(t *testing.T) { | ||||||
| 	etc := newEtcdMiddleware() | 	etc := newEtcdMiddleware() | ||||||
| 	etc.Proxy = proxy.New([]string{"127.0.0.1:154"}) | 	etc.Proxy = proxy.New([]string{"127.0.0.1:154"}) | ||||||
| 	etc.Debug = true | 	etc.Debugging = true | ||||||
|  |  | ||||||
| 	for _, serv := range servicesProxy { | 	for _, serv := range servicesProxy { | ||||||
| 		set(t, etc, serv.Key, 0, serv) | 		set(t, etc, serv.Key, 0, serv) | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ func setup(c *caddy.Controller) error { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return middleware.Error("etcd", err) | 		return middleware.Error("etcd", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if stubzones { | 	if stubzones { | ||||||
| 		c.OnStartup(func() error { | 		c.OnStartup(func() error { | ||||||
| 			e.UpdateStubZones() | 			e.UpdateStubZones() | ||||||
| @@ -55,7 +56,6 @@ func etcdParse(c *caddy.Controller) (*Etcd, bool, error) { | |||||||
| 		Stubmap:    &stub, | 		Stubmap:    &stub, | ||||||
| 	} | 	} | ||||||
| 	var ( | 	var ( | ||||||
| 		client        etcdc.KeysAPI |  | ||||||
| 		tlsCertFile   = "" | 		tlsCertFile   = "" | ||||||
| 		tlsKeyFile    = "" | 		tlsKeyFile    = "" | ||||||
| 		tlsCAcertFile = "" | 		tlsCAcertFile = "" | ||||||
| @@ -64,7 +64,6 @@ func etcdParse(c *caddy.Controller) (*Etcd, bool, error) { | |||||||
| 	) | 	) | ||||||
| 	for c.Next() { | 	for c.Next() { | ||||||
| 		if c.Val() == "etcd" { | 		if c.Val() == "etcd" { | ||||||
| 			etc.Client = client |  | ||||||
| 			etc.Zones = c.RemainingArgs() | 			etc.Zones = c.RemainingArgs() | ||||||
| 			if len(etc.Zones) == 0 { | 			if len(etc.Zones) == 0 { | ||||||
| 				etc.Zones = make([]string, len(c.ServerBlockKeys)) | 				etc.Zones = make([]string, len(c.ServerBlockKeys)) | ||||||
| @@ -77,7 +76,7 @@ func etcdParse(c *caddy.Controller) (*Etcd, bool, error) { | |||||||
| 				case "stubzones": | 				case "stubzones": | ||||||
| 					stubzones = true | 					stubzones = true | ||||||
| 				case "debug": | 				case "debug": | ||||||
| 					etc.Debug = true | 					etc.Debugging = true | ||||||
| 				case "path": | 				case "path": | ||||||
| 					if !c.NextArg() { | 					if !c.NextArg() { | ||||||
| 						return &Etcd{}, false, c.ArgErr() | 						return &Etcd{}, false, c.ArgErr() | ||||||
| @@ -117,7 +116,7 @@ func etcdParse(c *caddy.Controller) (*Etcd, bool, error) { | |||||||
| 					case "stubzones": | 					case "stubzones": | ||||||
| 						stubzones = true | 						stubzones = true | ||||||
| 					case "debug": | 					case "debug": | ||||||
| 						etc.Debug = true | 						etc.Debugging = true | ||||||
| 					case "path": | 					case "path": | ||||||
| 						if !c.NextArg() { | 						if !c.NextArg() { | ||||||
| 							return &Etcd{}, false, c.ArgErr() | 							return &Etcd{}, false, c.ArgErr() | ||||||
| @@ -161,6 +160,7 @@ func etcdParse(c *caddy.Controller) (*Etcd, bool, error) { | |||||||
| 			} | 			} | ||||||
| 			etc.Client = client | 			etc.Client = client | ||||||
| 			etc.endpoints = endpoints | 			etc.endpoints = endpoints | ||||||
|  |  | ||||||
| 			return &etc, stubzones, nil | 			return &etc, stubzones, nil | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -28,17 +28,16 @@ func init() { | |||||||
| func newEtcdMiddleware() *Etcd { | func newEtcdMiddleware() *Etcd { | ||||||
| 	ctxt, _ = context.WithTimeout(context.Background(), etcdTimeout) | 	ctxt, _ = context.WithTimeout(context.Background(), etcdTimeout) | ||||||
|  |  | ||||||
| 	etcdCfg := etcdc.Config{ | 	endpoints := []string{"http://localhost:2379"} | ||||||
| 		Endpoints: []string{"http://localhost:2379"}, | 	client, _ := newEtcdClient(endpoints, "", "", "") | ||||||
| 	} |  | ||||||
| 	cli, _ := etcdc.New(etcdCfg) |  | ||||||
| 	return &Etcd{ | 	return &Etcd{ | ||||||
| 		Proxy:      proxy.New([]string{"8.8.8.8:53"}), | 		Proxy:      proxy.New([]string{"8.8.8.8:53"}), | ||||||
| 		PathPrefix: "skydns", | 		PathPrefix: "skydns", | ||||||
| 		Ctx:        context.Background(), | 		Ctx:        context.Background(), | ||||||
| 		Inflight:   &singleflight.Group{}, | 		Inflight:   &singleflight.Group{}, | ||||||
| 		Zones:      []string{"skydns.test.", "skydns_extra.test.", "in-addr.arpa."}, | 		Zones:      []string{"skydns.test.", "skydns_extra.test.", "in-addr.arpa."}, | ||||||
| 		Client:     etcdc.NewKeysAPI(cli), | 		Client:     client, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -23,6 +23,8 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M | |||||||
| 	m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true | 	m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true | ||||||
|  |  | ||||||
| 	// TODO: find an alternative to this block | 	// TODO: find an alternative to this block | ||||||
|  | 	// TODO(miek): Why is this even here, why does the path Etcd takes not work? | ||||||
|  | 	// Should be a "case PTR" below. I would also like to use middleware.PTR for this. | ||||||
| 	ip := dnsutil.ExtractAddressFromReverse(state.Name()) | 	ip := dnsutil.ExtractAddressFromReverse(state.Name()) | ||||||
| 	if ip != "" { | 	if ip != "" { | ||||||
| 		records := k.getServiceRecordForIP(ip, state.Name()) | 		records := k.getServiceRecordForIP(ip, state.Name()) | ||||||
| @@ -54,41 +56,38 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M | |||||||
| 	) | 	) | ||||||
| 	switch state.Type() { | 	switch state.Type() { | ||||||
| 	case "A": | 	case "A": | ||||||
| 		records, err = k.A(zone, state, nil) | 		records, _, err = middleware.A(&k, zone, state, nil, middleware.Options{}) // Hmm wrt to '&k' | ||||||
| 	case "AAAA": | 	case "AAAA": | ||||||
| 		records, err = k.AAAA(zone, state, nil) | 		records, _, err = middleware.AAAA(&k, zone, state, nil, middleware.Options{}) | ||||||
| 	case "TXT": | 	case "TXT": | ||||||
| 		records, err = k.TXT(zone, state) | 		records, _, err = middleware.TXT(&k, zone, state, middleware.Options{}) | ||||||
| 		// TODO: change lookup to return appropriate error. Then add code below |  | ||||||
| 		// this switch to check for the error and return not implemented. |  | ||||||
| 		//return dns.RcodeNotImplemented, nil |  | ||||||
| 	case "CNAME": | 	case "CNAME": | ||||||
| 		records, err = k.CNAME(zone, state) | 		records, _, err = middleware.CNAME(&k, zone, state, middleware.Options{}) | ||||||
| 	case "MX": | 	case "MX": | ||||||
| 		records, extra, err = k.MX(zone, state) | 		records, extra, _, err = middleware.MX(&k, zone, state, middleware.Options{}) | ||||||
| 	case "SRV": | 	case "SRV": | ||||||
| 		records, extra, err = k.SRV(zone, state) | 		records, extra, _, err = middleware.SRV(&k, zone, state, middleware.Options{}) | ||||||
| 	case "SOA": | 	case "SOA": | ||||||
| 		records = []dns.RR{k.SOA(zone, state)} | 		records, _, err = middleware.SOA(&k, zone, state, middleware.Options{}) | ||||||
| 	case "NS": | 	case "NS": | ||||||
| 		if state.Name() == zone { | 		if state.Name() == zone { | ||||||
| 			records, extra, err = k.NS(zone, state) | 			records, extra, _, err = middleware.NS(&k, zone, state, middleware.Options{}) | ||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
| 		fallthrough | 		fallthrough | ||||||
| 	default: | 	default: | ||||||
| 		// Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN | 		// Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN | ||||||
| 		_, err = k.A(zone, state, nil) | 		_, _, err = middleware.A(&k, zone, state, nil, middleware.Options{}) | ||||||
| 	} | 	} | ||||||
| 	if isKubernetesNameError(err) { | 	if k.IsNameError(err) { | ||||||
| 		return k.Err(zone, dns.RcodeNameError, state) | 		return middleware.BackendError(&k, zone, dns.RcodeNameError, state, nil /*debug*/, err, middleware.Options{}) | ||||||
| 	} | 	} | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return dns.RcodeServerFailure, err | 		return dns.RcodeServerFailure, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(records) == 0 { | 	if len(records) == 0 { | ||||||
| 		return k.Err(zone, dns.RcodeSuccess, state) | 		return middleware.BackendError(&k, zone, dns.RcodeSuccess, state, nil /*debug*/, nil, middleware.Options{}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	m.Answer = append(m.Answer, records...) | 	m.Answer = append(m.Answer, records...) | ||||||
| @@ -103,13 +102,3 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M | |||||||
|  |  | ||||||
| // Name implements the Handler interface. | // Name implements the Handler interface. | ||||||
| func (k Kubernetes) Name() string { return "kubernetes" } | func (k Kubernetes) Name() string { return "kubernetes" } | ||||||
|  |  | ||||||
| // Err writes an error response back to the client. |  | ||||||
| func (k Kubernetes) Err(zone string, rcode int, state request.Request) (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 |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ package kubernetes | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"fmt" | ||||||
| 	"log" | 	"log" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| @@ -11,8 +12,9 @@ import ( | |||||||
| 	"github.com/miekg/coredns/middleware/etcd/msg" | 	"github.com/miekg/coredns/middleware/etcd/msg" | ||||||
| 	"github.com/miekg/coredns/middleware/kubernetes/nametemplate" | 	"github.com/miekg/coredns/middleware/kubernetes/nametemplate" | ||||||
| 	"github.com/miekg/coredns/middleware/pkg/dnsutil" | 	"github.com/miekg/coredns/middleware/pkg/dnsutil" | ||||||
| 	dns_strings "github.com/miekg/coredns/middleware/pkg/strings" | 	dnsstrings "github.com/miekg/coredns/middleware/pkg/strings" | ||||||
| 	"github.com/miekg/coredns/middleware/proxy" | 	"github.com/miekg/coredns/middleware/proxy" | ||||||
|  | 	"github.com/miekg/coredns/request" | ||||||
|  |  | ||||||
| 	"github.com/miekg/dns" | 	"github.com/miekg/dns" | ||||||
| 	"k8s.io/kubernetes/pkg/api" | 	"k8s.io/kubernetes/pkg/api" | ||||||
| @@ -41,6 +43,28 @@ type Kubernetes struct { | |||||||
| 	Selector      *labels.Selector | 	Selector      *labels.Selector | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Services implements the ServiceBackend interface. | ||||||
|  | func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.Options) ([]msg.Service, []msg.Service, error) { | ||||||
|  | 	s, e := k.Records(state.Name(), exact) | ||||||
|  | 	return s, nil, e // Haven't implemented debug queries yet. | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Lookup implements the ServiceBackend interface. | ||||||
|  | func (k *Kubernetes) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) { | ||||||
|  | 	return k.Proxy.Lookup(state, name, typ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IsNameError implements the ServiceBackend interface. | ||||||
|  | // TODO(infoblox): implement! | ||||||
|  | func (k *Kubernetes) IsNameError(err error) bool { | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Debug implements the ServiceBackend interface. | ||||||
|  | func (k *Kubernetes) Debug() string { | ||||||
|  | 	return "debug" | ||||||
|  | } | ||||||
|  |  | ||||||
| func (k *Kubernetes) getClientConfig() (*restclient.Config, error) { | func (k *Kubernetes) getClientConfig() (*restclient.Config, error) { | ||||||
| 	// For a custom api server or running outside a k8s cluster | 	// For a custom api server or running outside a k8s cluster | ||||||
| 	// set URL in env.KUBERNETES_MASTER or set endpoint in Corefile | 	// set URL in env.KUBERNETES_MASTER or set endpoint in Corefile | ||||||
| @@ -73,7 +97,6 @@ func (k *Kubernetes) getClientConfig() (*restclient.Config, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| // InitKubeCache initializes a new Kubernetes cache. | // InitKubeCache initializes a new Kubernetes cache. | ||||||
| // TODO(miek): is this correct? |  | ||||||
| func (k *Kubernetes) InitKubeCache() error { | func (k *Kubernetes) InitKubeCache() error { | ||||||
|  |  | ||||||
| 	config, err := k.getClientConfig() | 	config, err := k.getClientConfig() | ||||||
| @@ -83,21 +106,24 @@ func (k *Kubernetes) InitKubeCache() error { | |||||||
|  |  | ||||||
| 	kubeClient, err := clientset_generated.NewForConfig(config) | 	kubeClient, err := clientset_generated.NewForConfig(config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Printf("[ERROR] Failed to create kubernetes notification controller: %v", err) | 		return fmt.Errorf("Failed to create kubernetes notification controller: %v", err) | ||||||
| 		return err |  | ||||||
| 	} | 	} | ||||||
| 	if k.LabelSelector == nil { |  | ||||||
| 		log.Printf("[INFO] Kubernetes middleware configured without a label selector. No label-based filtering will be performed.") | 	if k.LabelSelector != nil { | ||||||
| 	} else { |  | ||||||
| 		var selector labels.Selector | 		var selector labels.Selector | ||||||
| 		selector, err = unversionedapi.LabelSelectorAsSelector(k.LabelSelector) | 		selector, err = unversionedapi.LabelSelectorAsSelector(k.LabelSelector) | ||||||
| 		k.Selector = &selector | 		k.Selector = &selector | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Printf("[ERROR] Unable to create Selector for LabelSelector '%s'.Error was: %s", k.LabelSelector, err) | 			return fmt.Errorf("Unable to create Selector for LabelSelector '%s'.Error was: %s", k.LabelSelector, err) | ||||||
| 			return err |  | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if k.LabelSelector == nil { | ||||||
|  | 		log.Printf("[INFO] Kubernetes middleware configured without a label selector. No label-based filtering will be performed.") | ||||||
|  | 	} else { | ||||||
| 		log.Printf("[INFO] Kubernetes middleware configured with the label selector '%s'. Only kubernetes objects matching this label selector will be exposed.", unversionedapi.FormatLabelSelector(k.LabelSelector)) | 		log.Printf("[INFO] Kubernetes middleware configured with the label selector '%s'. Only kubernetes objects matching this label selector will be exposed.", unversionedapi.FormatLabelSelector(k.LabelSelector)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	k.APIConn = newdnsController(kubeClient, k.ResyncPeriod, k.Selector) | 	k.APIConn = newdnsController(kubeClient, k.ResyncPeriod, k.Selector) | ||||||
|  |  | ||||||
| 	return err | 	return err | ||||||
| @@ -125,12 +151,11 @@ func (k *Kubernetes) getZoneForName(name string) (string, []string) { | |||||||
| 	return zone, serviceSegments | 	return zone, serviceSegments | ||||||
| } | } | ||||||
|  |  | ||||||
| // Records looks up services in kubernetes. | // Records looks up services in kubernetes. If exact is true, it will lookup | ||||||
| // If exact is true, it will lookup just | // just this name. This is used when find matches when completing SRV lookups | ||||||
| // this name. This is used when find matches when completing SRV lookups |  | ||||||
| // for instance. | // for instance. | ||||||
| func (k *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) { | func (k *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) { | ||||||
| 	// TODO: refector this. | 	// TODO: refactor this. | ||||||
| 	// Right now NamespaceFromSegmentArray do not supports PRE queries | 	// Right now NamespaceFromSegmentArray do not supports PRE queries | ||||||
| 	ip := dnsutil.ExtractAddressFromReverse(name) | 	ip := dnsutil.ExtractAddressFromReverse(name) | ||||||
| 	if ip != "" { | 	if ip != "" { | ||||||
| @@ -169,7 +194,7 @@ func (k *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) { | |||||||
|  |  | ||||||
| 	// Abort if the namespace does not contain a wildcard, and namespace is not published per CoreFile | 	// Abort if the namespace does not contain a wildcard, and namespace is not published per CoreFile | ||||||
| 	// Case where namespace contains a wildcard is handled in Get(...) method. | 	// Case where namespace contains a wildcard is handled in Get(...) method. | ||||||
| 	if (!nsWildcard) && (len(k.Namespaces) > 0) && (!dns_strings.StringInSlice(namespace, k.Namespaces)) { | 	if (!nsWildcard) && (len(k.Namespaces) > 0) && (!dnsstrings.StringInSlice(namespace, k.Namespaces)) { | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -219,7 +244,7 @@ func (k *Kubernetes) Get(namespace string, nsWildcard bool, servicename string, | |||||||
| 		if symbolMatches(namespace, item.Namespace, nsWildcard) && symbolMatches(servicename, item.Name, serviceWildcard) { | 		if symbolMatches(namespace, item.Namespace, nsWildcard) && symbolMatches(servicename, item.Name, serviceWildcard) { | ||||||
| 			// If namespace has a wildcard, filter results against Corefile namespace list. | 			// If namespace has a wildcard, filter results against Corefile namespace list. | ||||||
| 			// (Namespaces without a wildcard were filtered before the call to this function.) | 			// (Namespaces without a wildcard were filtered before the call to this function.) | ||||||
| 			if nsWildcard && (len(k.Namespaces) > 0) && (!dns_strings.StringInSlice(item.Namespace, k.Namespaces)) { | 			if nsWildcard && (len(k.Namespaces) > 0) && (!dnsstrings.StringInSlice(item.Namespace, k.Namespaces)) { | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 			resultItems = append(resultItems, item) | 			resultItems = append(resultItems, item) | ||||||
| @@ -242,11 +267,6 @@ func symbolMatches(queryString string, candidateString string, wildcard bool) bo | |||||||
| 	return result | 	return result | ||||||
| } | } | ||||||
|  |  | ||||||
| // kubernetesNameError checks if the error is ErrorCodeKeyNotFound from kubernetes. |  | ||||||
| func isKubernetesNameError(err error) bool { |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (k *Kubernetes) getServiceRecordForIP(ip, name string) []msg.Service { | func (k *Kubernetes) getServiceRecordForIP(ip, name string) []msg.Service { | ||||||
| 	svcList, err := k.APIConn.svcLister.List(labels.Everything()) | 	svcList, err := k.APIConn.svcLister.List(labels.Everything()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
| @@ -2,11 +2,8 @@ package kubernetes | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"math" |  | ||||||
| 	"net" | 	"net" | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/miekg/coredns/middleware" |  | ||||||
| 	"github.com/miekg/coredns/middleware/etcd/msg" | 	"github.com/miekg/coredns/middleware/etcd/msg" | ||||||
| 	"github.com/miekg/coredns/middleware/pkg/dnsutil" | 	"github.com/miekg/coredns/middleware/pkg/dnsutil" | ||||||
| 	"github.com/miekg/coredns/request" | 	"github.com/miekg/coredns/request" | ||||||
| @@ -19,278 +16,10 @@ func (k Kubernetes) records(state request.Request, exact bool) ([]msg.Service, e | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	// TODO: Do we want to support the SkyDNS (hacky) Group feature? |  | ||||||
| 	services = msg.Group(services) | 	services = msg.Group(services) | ||||||
| 	return services, nil | 	return services, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // A returns A records from kubernetes or an error. |  | ||||||
| func (k Kubernetes) A(zone string, state request.Request, 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 dnsutil.DuplicateCNAME(newRecord, previousRecords) { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			state1 := state.NewWithQuestion(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 |  | ||||||
| 			} |  | ||||||
| 			mes, err := k.Proxy.Lookup(state, target, state.QType()) |  | ||||||
| 			if err != nil { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			// Len(mes.Answer) > 0 here is well? |  | ||||||
| 			records = append(records, newRecord) |  | ||||||
| 			records = append(records, mes.Answer...) |  | ||||||
| 			continue |  | ||||||
| 		case ip.To4() != nil: |  | ||||||
| 			records = append(records, serv.NewA(state.QName(), ip.To4())) |  | ||||||
| 		case ip.To4() == nil: |  | ||||||
| 			// nodata? |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return records, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // AAAA returns AAAA records from kubernetes or an error. |  | ||||||
| func (k Kubernetes) AAAA(zone string, state request.Request, 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: |  | ||||||
| 			// 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, err := k.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...) |  | ||||||
| 				} |  | ||||||
| 				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 |  | ||||||
| 			// both here again |  | ||||||
| 		case ip.To4() != nil: |  | ||||||
| 			// nada? |  | ||||||
| 		case ip.To4() == nil: |  | ||||||
| 			records = append(records, serv.NewAAAA(state.QName(), ip.To16())) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return records, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SRV returns SRV records from kubernetes. |  | ||||||
| // If the Target is not a name but an IP address, a name is created on the fly and the IP address is put in |  | ||||||
| // the additional section. |  | ||||||
| func (k Kubernetes) SRV(zone string, state request.Request) (records []dns.RR, extra []dns.RR, err error) { |  | ||||||
| 	services, err := k.records(state, false) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 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 := k.Proxy.Lookup(state, srv.Target, dns.TypeA) |  | ||||||
| 				if e1 == nil { |  | ||||||
| 					extra = append(extra, m1.Answer...) |  | ||||||
| 				} |  | ||||||
| 				m1, e1 = k.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 := state.NewWithQuestion(srv.Target, dns.TypeA) |  | ||||||
| 			addr, e1 := k.A(zone, state1, nil) |  | ||||||
| 			if e1 == nil { |  | ||||||
| 				extra = append(extra, addr...) |  | ||||||
| 			} |  | ||||||
| 			// k.AAA(zone, state1, nil) as well...? |  | ||||||
| 		case ip.To4() != nil: |  | ||||||
| 			serv.Host = 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 = serv.Key |  | ||||||
| 			srv := serv.NewSRV(state.QName(), weight) |  | ||||||
|  |  | ||||||
| 			records = append(records, srv) |  | ||||||
| 			extra = append(extra, serv.NewAAAA(srv.Target, ip.To16())) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return records, extra, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // MX returns MX records from kubernetes. Not implemented! |  | ||||||
| func (k Kubernetes) MX(zone string, state request.Request) (records []dns.RR, extra []dns.RR, err error) { |  | ||||||
| 	return nil, nil, err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // CNAME returns CNAME records from kubernetes. Not implemented! |  | ||||||
| func (k Kubernetes) CNAME(zone string, state request.Request) (records []dns.RR, err error) { |  | ||||||
| 	return nil, err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // TXT returns TXT records from kubernetes. Not implemented! |  | ||||||
| func (k Kubernetes) TXT(zone string, state request.Request) (records []dns.RR, err error) { |  | ||||||
| 	return nil, err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NS returns NS records from kubernetes. |  | ||||||
| func (k Kubernetes) NS(zone string, state request.Request) (records, extra []dns.RR, 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, err := k.records(state, false) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, nil, 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, fmt.Errorf("NS record must be an IP address: %s", serv.Host) |  | ||||||
| 		case ip.To4() != nil: |  | ||||||
| 			serv.Host = serv.Key |  | ||||||
| 			records = append(records, serv.NewNS(state.QName())) |  | ||||||
| 			extra = append(extra, serv.NewA(serv.Host, ip.To4())) |  | ||||||
| 		case ip.To4() == nil: |  | ||||||
| 			serv.Host = serv.Key |  | ||||||
| 			records = append(records, serv.NewNS(state.QName())) |  | ||||||
| 			extra = append(extra, serv.NewAAAA(serv.Host, ip.To16())) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return records, extra, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SOA Record returns a SOA record from kubernetes. |  | ||||||
| func (k Kubernetes) SOA(zone string, state request.Request) *dns.SOA { |  | ||||||
| 	header := dns.RR_Header{Name: zone, Rrtype: dns.TypeSOA, Ttl: 300, Class: dns.ClassINET} |  | ||||||
| 	return &dns.SOA{Hdr: header, |  | ||||||
| 		Mbox:    "hostmaster." + zone, |  | ||||||
| 		Ns:      "ns.dns." + zone, |  | ||||||
| 		Serial:  uint32(time.Now().Unix()), |  | ||||||
| 		Refresh: 7200, |  | ||||||
| 		Retry:   1800, |  | ||||||
| 		Expire:  86400, |  | ||||||
| 		Minttl:  60, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // PTR Record returns PTR records from kubernetes. | // PTR Record returns PTR records from kubernetes. | ||||||
| func (k Kubernetes) PTR(zone string, state request.Request) ([]dns.RR, error) { | func (k Kubernetes) PTR(zone string, state request.Request) ([]dns.RR, error) { | ||||||
| 	reverseIP := dnsutil.ExtractAddressFromReverse(state.Name()) | 	reverseIP := dnsutil.ExtractAddressFromReverse(state.Name()) | ||||||
|   | |||||||
| @@ -54,9 +54,9 @@ func New(hosts []string) Proxy { | |||||||
|  |  | ||||||
| // Lookup will use name and type to forge a new message and will send that upstream. It will | // 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. | // set any EDNS0 options correctly so that downstream will be able to process the reply. | ||||||
| func (p Proxy) Lookup(state request.Request, name string, tpe uint16) (*dns.Msg, error) { | func (p Proxy) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) { | ||||||
| 	req := new(dns.Msg) | 	req := new(dns.Msg) | ||||||
| 	req.SetQuestion(name, tpe) | 	req.SetQuestion(name, typ) | ||||||
| 	state.SizeAndDo(req) | 	state.SizeAndDo(req) | ||||||
|  |  | ||||||
| 	return p.lookup(state, req) | 	return p.lookup(state, req) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user