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:
Miek Gieben 2016-10-30 15:54:16 +00:00 committed by GitHub
parent 81d5baee28
commit 27d893cf33
15 changed files with 273 additions and 503 deletions

29
middleware/backend.go Normal file
View 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
}

View file

@ -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"
)

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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)

View file

@ -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
) )

View file

@ -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
}

View file

@ -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)

View file

@ -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
} }
} }

View file

@ -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,
} }
} }

View file

@ -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
}

View file

@ -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 {

View file

@ -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())

View file

@ -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)