Fix stubzone retention (#198)

Make the receiver a pointer so that the uptdateStubZones map update will
retain the stubzones found, unlike the current case where the update
will be applied and then promptly forgotten, because it is working on a
copy.

Add test/etcd_test.go to test a large part of the code. This didn't
catch the chaos middleware hack though. The chaos middleware zones are
now *not* automatically added. You have to take care of that by yourself
(docs updates).

When using debug queries and falling through to the next middleware in
etcd, restore the original (with o-o.debug) query before passing it on.
This commit is contained in:
Miek Gieben 2016-08-08 19:18:55 -07:00 committed by GitHub
parent c079de65b5
commit ad76aef5fc
18 changed files with 210 additions and 104 deletions

4
conf/chaosCorefile Normal file
View file

@ -0,0 +1,4 @@
.:1053 authors.bind:1053 {
chaos CoreDNS-001 "Miek Gieben" miek@miek.nl
proxy . 8.8.8.8:53
}

10
conf/etcdCorefile Normal file
View file

@ -0,0 +1,10 @@
.:1053 {
etcd skydns.local {
stubzones
path /skydns
endpoint http://localhost:2379
upstream 8.8.8.8:53 8.8.4.4:53
debug
}
proxy . 8.8.8.8:53
}

View file

@ -38,7 +38,7 @@ func Etcd(c *Controller) (middleware.Middleware, error) {
}, nil }, nil
} }
func etcdParse(c *Controller) (etcd.Etcd, bool, error) { func etcdParse(c *Controller) (*etcd.Etcd, bool, error) {
stub := make(map[string]proxy.Proxy) stub := make(map[string]proxy.Proxy)
etc := etcd.Etcd{ etc := etcd.Etcd{
Proxy: proxy.New([]string{"8.8.8.8:53", "8.8.4.4:53"}), Proxy: proxy.New([]string{"8.8.8.8:53", "8.8.4.4:53"}),
@ -72,19 +72,19 @@ func etcdParse(c *Controller) (etcd.Etcd, bool, error) {
etc.Debug = true etc.Debug = true
case "path": case "path":
if !c.NextArg() { if !c.NextArg() {
return etcd.Etcd{}, false, c.ArgErr() return &etcd.Etcd{}, false, c.ArgErr()
} }
etc.PathPrefix = c.Val() etc.PathPrefix = c.Val()
case "endpoint": case "endpoint":
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) == 0 { if len(args) == 0 {
return etcd.Etcd{}, false, c.ArgErr() return &etcd.Etcd{}, false, c.ArgErr()
} }
endpoints = args endpoints = args
case "upstream": case "upstream":
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) == 0 { if len(args) == 0 {
return etcd.Etcd{}, false, c.ArgErr() return &etcd.Etcd{}, false, c.ArgErr()
} }
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
h, p, e := net.SplitHostPort(args[i]) h, p, e := net.SplitHostPort(args[i])
@ -97,7 +97,7 @@ func etcdParse(c *Controller) (etcd.Etcd, bool, error) {
case "tls": // cert key cacertfile case "tls": // cert key cacertfile
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) != 3 { if len(args) != 3 {
return etcd.Etcd{}, false, c.ArgErr() return &etcd.Etcd{}, false, c.ArgErr()
} }
tlsCertFile, tlsKeyFile, tlsCAcertFile = args[0], args[1], args[2] tlsCertFile, tlsKeyFile, tlsCAcertFile = args[0], args[1], args[2]
} }
@ -109,19 +109,19 @@ func etcdParse(c *Controller) (etcd.Etcd, bool, error) {
etc.Debug = true etc.Debug = true
case "path": case "path":
if !c.NextArg() { if !c.NextArg() {
return etcd.Etcd{}, false, c.ArgErr() return &etcd.Etcd{}, false, c.ArgErr()
} }
etc.PathPrefix = c.Val() etc.PathPrefix = c.Val()
case "endpoint": case "endpoint":
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) == 0 { if len(args) == 0 {
return etcd.Etcd{}, false, c.ArgErr() return &etcd.Etcd{}, false, c.ArgErr()
} }
endpoints = args endpoints = args
case "upstream": case "upstream":
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) == 0 { if len(args) == 0 {
return etcd.Etcd{}, false, c.ArgErr() return &etcd.Etcd{}, false, c.ArgErr()
} }
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
h, p, e := net.SplitHostPort(args[i]) h, p, e := net.SplitHostPort(args[i])
@ -133,7 +133,7 @@ func etcdParse(c *Controller) (etcd.Etcd, bool, error) {
case "tls": // cert key cacertfile case "tls": // cert key cacertfile
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) != 3 { if len(args) != 3 {
return etcd.Etcd{}, false, c.ArgErr() return &etcd.Etcd{}, false, c.ArgErr()
} }
tlsCertFile, tlsKeyFile, tlsCAcertFile = args[0], args[1], args[2] tlsCertFile, tlsKeyFile, tlsCAcertFile = args[0], args[1], args[2]
} }
@ -141,13 +141,13 @@ func etcdParse(c *Controller) (etcd.Etcd, bool, error) {
} }
client, err := newEtcdClient(endpoints, tlsCertFile, tlsKeyFile, tlsCAcertFile) client, err := newEtcdClient(endpoints, tlsCertFile, tlsKeyFile, tlsCAcertFile)
if err != nil { if err != nil {
return etcd.Etcd{}, false, err return &etcd.Etcd{}, false, err
} }
etc.Client = client etc.Client = client
return etc, stubzones, nil return &etc, stubzones, nil
} }
} }
return etcd.Etcd{}, false, nil return &etcd.Etcd{}, false, nil
} }
func newEtcdClient(endpoints []string, tlsCert, tlsKey, tlsCACert string) (etcdc.KeysAPI, error) { func newEtcdClient(endpoints []string, tlsCert, tlsKey, tlsCACert string) (etcdc.KeysAPI, error) {

View file

@ -1,7 +1,7 @@
# chaos # chaos
The `chaos` middleware allows CoreDNS to response to TXT queries in CH class. The `chaos` middleware allows CoreDNS to response to TXT queries in CH class.
Useful for retrieving version or author information from the server. Useful for retrieving version or author information from the server. If
## Syntax ## Syntax
@ -12,9 +12,9 @@ chaos [version] [authors...]
* `version` the version to return, defaults to CoreDNS. * `version` the version to return, defaults to CoreDNS.
* `authors` what authors to return. No default. * `authors` what authors to return. No default.
Note this middleware can only be specified for a zone once. This is because it hijacks Note that you have to make sure that this middleware will get actual queries for the
the zones `version.bind`, `version.server`, `authors.bind`, `hostname.bind` and following zones: `version.bind`, `version.server`, `authors.bind`, `hostname.bind` and
`id.server`, which means it can only be routed to one middleware. `id.server`.
## Examples ## Examples

View file

@ -12,11 +12,12 @@ const debugName = "o-o.debug."
// isDebug checks if name is a debugging name, i.e. starts with o-o.debug. // isDebug checks if name is a debugging name, i.e. starts with o-o.debug.
// it return the empty string if it is not a debug message, otherwise it will return the // it return the empty string if it is not a debug message, otherwise it will return the
// name with o-o.debug. stripped off. // name with o-o.debug. stripped off. Must be called with name lowercased.
func isDebug(name string) string { func isDebug(name string) string {
if len(name) == len(debugName) { if len(name) == len(debugName) {
return "" return ""
} }
name = strings.ToLower(name)
debug := strings.HasPrefix(name, debugName) debug := strings.HasPrefix(name, debugName)
if !debug { if !debug {
return "" return ""

View file

@ -4,6 +4,7 @@ package etcd
import ( import (
"sort" "sort"
"strings"
"testing" "testing"
"github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware"
@ -13,17 +14,17 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
) )
func TestisDebug(t *testing.T) { func TestIsDebug(t *testing.T) {
if ok := isDebug("o-o.debug.miek.nl."); ok != "miek.nl." { if ok := isDebug("o-o.debug.miek.nl."); ok != "miek.nl." {
t.Errorf("expected o-o.debug.miek.nl. to be debug") t.Errorf("expected o-o.debug.miek.nl. to be debug")
} }
if ok := isDebug("o-o.Debug.miek.nl."); ok != "miek.nl." { if ok := isDebug(strings.ToLower("o-o.Debug.miek.nl.")); ok != "miek.nl." {
t.Errorf("expected o-o.Debug.miek.nl. to be debug") t.Errorf("expected o-o.Debug.miek.nl. to be debug")
} }
if ok := isDebug("i-o.Debug.miek.nl."); ok != "" { if ok := isDebug("i-o.debug.miek.nl."); ok != "" {
t.Errorf("expected i-o.Debug.miek.nl. to be non-debug") t.Errorf("expected i-o.Debug.miek.nl. to be non-debug")
} }
if ok := isDebug("i-o.Debug."); ok != "" { if ok := isDebug(strings.ToLower("i-o.Debug.")); ok != "" {
t.Errorf("expected o-o.Debug. to be non-debug") t.Errorf("expected o-o.Debug. to be non-debug")
} }
} }
@ -35,6 +36,7 @@ func TestDebugLookup(t *testing.T) {
} }
etc.Debug = true etc.Debug = true
defer func() { etc.Debug = false }() defer func() { etc.Debug = false }()
for _, tc := range dnsTestCasesDebug { for _, tc := range dnsTestCasesDebug {
m := tc.Msg() m := tc.Msg()

View file

@ -26,35 +26,34 @@ type Etcd struct {
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. Debug bool // Do we allow debug queries.
debug string // Should we return debugging information, if so, contains original qname.
} }
// Records looks up records in etcd. If exact is true, it will lookup just // 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 // this name. This is used when find matches when completing SRV lookups
// for instance. // for instance.
func (g 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, g.PathPrefix) path, star := msg.PathWithWildcard(name, e.PathPrefix)
r, err := g.Get(path, true) r, err := e.Get(path, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
segments := strings.Split(msg.Path(name, g.PathPrefix), "/") segments := strings.Split(msg.Path(name, e.PathPrefix), "/")
switch { switch {
case exact && r.Node.Dir: case exact && r.Node.Dir:
return nil, nil return nil, nil
case r.Node.Dir: case r.Node.Dir:
return g.loopNodes(r.Node.Nodes, segments, star, nil) return e.loopNodes(r.Node.Nodes, segments, star, nil)
default: default:
return g.loopNodes([]*etcdc.Node{r.Node}, segments, false, nil) return e.loopNodes([]*etcdc.Node{r.Node}, segments, false, nil)
} }
} }
// 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 (g Etcd) Get(path string, recursive bool) (*etcdc.Response, error) { func (e *Etcd) Get(path string, recursive bool) (*etcdc.Response, error) {
resp, err := g.Inflight.Do(path, func() (interface{}, error) { resp, err := e.Inflight.Do(path, func() (interface{}, error) {
ctx, cancel := context.WithTimeout(g.Ctx, etcdTimeout) ctx, cancel := context.WithTimeout(e.Ctx, etcdTimeout)
defer cancel() defer cancel()
r, e := g.Client.Get(ctx, path, &etcdc.GetOptions{Sort: false, Recursive: recursive}) r, e := e.Client.Get(ctx, path, &etcdc.GetOptions{Sort: false, Recursive: recursive})
if e != nil { if e != nil {
return nil, e return nil, e
} }
@ -74,14 +73,14 @@ func (g 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 (g 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)
} }
Nodes: Nodes:
for _, n := range ns { for _, n := range ns {
if n.Dir { if n.Dir {
nodes, err := g.loopNodes(n.Nodes, nameParts, star, bx) nodes, err := e.loopNodes(n.Nodes, nameParts, star, bx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -114,7 +113,7 @@ Nodes:
bx[b] = true bx[b] = true
serv.Key = n.Key serv.Key = n.Key
serv.Ttl = g.TTL(n, serv) serv.Ttl = e.TTL(n, serv)
if serv.Priority == 0 { if serv.Priority == 0 {
serv.Priority = priority serv.Priority = priority
} }
@ -125,7 +124,7 @@ Nodes:
// TTL returns the smaller of the etcd TTL and the service's // TTL returns the smaller of the etcd TTL and the service's
// TTL. If neither of these are set (have a zero value), a default is used. // TTL. If neither of these are set (have a zero value), a default is used.
func (g Etcd) TTL(node *etcdc.Node, serv *msg.Service) uint32 { func (e *Etcd) TTL(node *etcdc.Node, serv *msg.Service) uint32 {
etcdTTL := uint32(node.TTL) etcdTTL := uint32(node.TTL)
if etcdTTL == 0 && serv.Ttl == 0 { if etcdTTL == 0 && serv.Ttl == 0 {

View file

@ -10,7 +10,8 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
) )
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{}
state := middleware.State{W: w, Req: r} state := middleware.State{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")
@ -18,7 +19,7 @@ func (e Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i
name := state.Name() name := state.Name()
if e.Debug { if e.Debug {
if debug := isDebug(name); debug != "" { if debug := isDebug(name); debug != "" {
e.debug = r.Question[0].Name opt.Debug = r.Question[0].Name
state.Clear() state.Clear()
state.Req.Question[0].Name = debug state.Req.Question[0].Name = debug
} }
@ -41,6 +42,9 @@ func (e Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i
if e.Next == nil { if e.Next == nil {
return dns.RcodeServerFailure, nil return dns.RcodeServerFailure, nil
} }
if opt.Debug != "" {
r.Question[0].Name = opt.Debug
}
return e.Next.ServeDNS(ctx, w, r) return e.Next.ServeDNS(ctx, w, r)
} }
@ -51,47 +55,47 @@ func (e Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i
) )
switch state.Type() { switch state.Type() {
case "A": case "A":
records, debug, err = e.A(zone, state, nil) records, debug, err = e.A(zone, state, nil, opt)
case "AAAA": case "AAAA":
records, debug, err = e.AAAA(zone, state, nil) records, debug, err = e.AAAA(zone, state, nil, opt)
case "TXT": case "TXT":
records, debug, err = e.TXT(zone, state) records, debug, err = e.TXT(zone, state, opt)
case "CNAME": case "CNAME":
records, debug, err = e.CNAME(zone, state) records, debug, err = e.CNAME(zone, state, opt)
case "PTR": case "PTR":
records, debug, err = e.PTR(zone, state) records, debug, err = e.PTR(zone, state, opt)
case "MX": case "MX":
records, extra, debug, err = e.MX(zone, state) records, extra, debug, err = e.MX(zone, state, opt)
case "SRV": case "SRV":
records, extra, debug, err = e.SRV(zone, state) records, extra, debug, err = e.SRV(zone, state, opt)
case "SOA": case "SOA":
records, debug, err = e.SOA(zone, state) records, debug, err = e.SOA(zone, state, opt)
case "NS": case "NS":
if state.Name() == zone { if state.Name() == zone {
records, extra, debug, err = e.NS(zone, state) records, extra, debug, err = e.NS(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) _, debug, err = e.A(zone, state, nil, opt)
} }
if e.debug != "" { if opt.Debug != "" {
// Substitute this name with the original when we return the request. // Substitute this name with the original when we return the request.
state.Clear() state.Clear()
state.Req.Question[0].Name = e.debug state.Req.Question[0].Name = opt.Debug
} }
if isEtcdNameError(err) { if isEtcdNameError(err) {
return e.Err(zone, dns.RcodeNameError, state, debug, err) return e.Err(zone, dns.RcodeNameError, state, debug, err, opt)
} }
if err != nil { if err != nil {
return e.Err(zone, dns.RcodeServerFailure, state, debug, err) return e.Err(zone, dns.RcodeServerFailure, state, debug, err, opt)
} }
if len(records) == 0 { if len(records) == 0 {
return e.Err(zone, dns.RcodeSuccess, state, debug, err) return e.Err(zone, dns.RcodeSuccess, state, debug, err, opt)
} }
m := new(dns.Msg) m := new(dns.Msg)
@ -99,7 +103,7 @@ func (e Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i
m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true
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 e.debug != "" { if opt.Debug != "" {
m.Extra = append(m.Extra, servicesToTxt(debug)...) m.Extra = append(m.Extra, servicesToTxt(debug)...)
} }
@ -111,12 +115,12 @@ func (e Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i
} }
// Err write an error response to the client. // Err write an error response to the client.
func (e Etcd) Err(zone string, rcode int, state middleware.State, debug []msg.Service, err error) (int, error) { func (e *Etcd) Err(zone string, rcode int, state middleware.State, debug []msg.Service, err error, opt Options) (int, error) {
m := new(dns.Msg) m := new(dns.Msg)
m.SetRcode(state.Req, rcode) m.SetRcode(state.Req, rcode)
m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true
m.Ns, _, _ = e.SOA(zone, state) m.Ns, _, _ = e.SOA(zone, state, opt)
if e.debug != "" { if opt.Debug != "" {
m.Extra = servicesToTxt(debug) m.Extra = servicesToTxt(debug)
txt := errorToTxt(err) txt := errorToTxt(err)
if txt != nil { if txt != nil {

View file

@ -12,20 +12,24 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
) )
func (e Etcd) records(state middleware.State, exact bool) (services, debug []msg.Service, err error) { type Options struct {
Debug string
}
func (e Etcd) records(state middleware.State, exact bool, opt Options) (services, debug []msg.Service, err error) {
services, err = e.Records(state.Name(), exact) services, err = e.Records(state.Name(), exact)
if err != nil { if err != nil {
return return
} }
if e.debug != "" { if opt.Debug != "" {
debug = services debug = services
} }
services = msg.Group(services) services = msg.Group(services)
return return
} }
func (e Etcd) A(zone string, state middleware.State, previousRecords []dns.RR) (records []dns.RR, debug []msg.Service, err error) { func (e Etcd) A(zone string, state middleware.State, previousRecords []dns.RR, opt Options) (records []dns.RR, debug []msg.Service, err error) {
services, debug, err := e.records(state, false) services, debug, err := e.records(state, false, opt)
if err != nil { if err != nil {
return nil, debug, err return nil, debug, err
} }
@ -50,7 +54,7 @@ func (e Etcd) A(zone string, state middleware.State, previousRecords []dns.RR) (
} }
state1 := copyState(state, serv.Host, state.QType()) state1 := copyState(state, serv.Host, state.QType())
nextRecords, nextDebug, err := e.A(zone, state1, append(previousRecords, newRecord)) nextRecords, nextDebug, err := e.A(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.
@ -84,8 +88,8 @@ func (e Etcd) A(zone string, state middleware.State, previousRecords []dns.RR) (
return records, debug, nil return records, debug, nil
} }
func (e Etcd) AAAA(zone string, state middleware.State, previousRecords []dns.RR) (records []dns.RR, debug []msg.Service, err error) { func (e Etcd) AAAA(zone string, state middleware.State, previousRecords []dns.RR, opt Options) (records []dns.RR, debug []msg.Service, err error) {
services, debug, err := e.records(state, false) services, debug, err := e.records(state, false, opt)
if err != nil { if err != nil {
return nil, debug, err return nil, debug, err
} }
@ -110,7 +114,7 @@ func (e Etcd) AAAA(zone string, state middleware.State, previousRecords []dns.RR
} }
state1 := copyState(state, serv.Host, state.QType()) state1 := copyState(state, serv.Host, state.QType())
nextRecords, nextDebug, err := e.AAAA(zone, state1, append(previousRecords, newRecord)) nextRecords, nextDebug, err := e.AAAA(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.
@ -147,8 +151,8 @@ func (e Etcd) AAAA(zone string, state middleware.State, previousRecords []dns.RR
// SRV returns SRV records from etcd. // SRV returns SRV records from etcd.
// 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 middleware.State) (records, extra []dns.RR, debug []msg.Service, err error) { func (e Etcd) SRV(zone string, state middleware.State, opt Options) (records, extra []dns.RR, debug []msg.Service, err error) {
services, debug, err := e.records(state, false) services, debug, err := e.records(state, false, opt)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
@ -206,7 +210,7 @@ func (e Etcd) SRV(zone string, state middleware.State) (records, extra []dns.RR,
// 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 := copyState(state, srv.Target, dns.TypeA) state1 := copyState(state, srv.Target, dns.TypeA)
addr, debugAddr, e1 := e.A(zone, state1, nil) addr, debugAddr, e1 := e.A(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...)
@ -231,8 +235,8 @@ func (e Etcd) SRV(zone string, state middleware.State) (records, extra []dns.RR,
// MX returns MX records from etcd. // MX returns MX records from etcd.
// 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) MX(zone string, state middleware.State) (records, extra []dns.RR, debug []msg.Service, err error) { func (e Etcd) MX(zone string, state middleware.State, opt Options) (records, extra []dns.RR, debug []msg.Service, err error) {
services, debug, err := e.records(state, false) services, debug, err := e.records(state, false, opt)
if err != nil { if err != nil {
return nil, nil, debug, err return nil, nil, debug, err
} }
@ -271,7 +275,7 @@ func (e Etcd) MX(zone string, state middleware.State) (records, extra []dns.RR,
} }
// Internal name // Internal name
state1 := copyState(state, mx.Mx, dns.TypeA) state1 := copyState(state, mx.Mx, dns.TypeA)
addr, debugAddr, e1 := e.A(zone, state1, nil) addr, debugAddr, e1 := e.A(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...)
@ -290,8 +294,8 @@ func (e Etcd) MX(zone string, state middleware.State) (records, extra []dns.RR,
return records, extra, debug, nil return records, extra, debug, nil
} }
func (e Etcd) CNAME(zone string, state middleware.State) (records []dns.RR, debug []msg.Service, err error) { func (e Etcd) CNAME(zone string, state middleware.State, opt Options) (records []dns.RR, debug []msg.Service, err error) {
services, debug, err := e.records(state, true) services, debug, err := e.records(state, true, opt)
if err != nil { if err != nil {
return nil, debug, err return nil, debug, err
} }
@ -306,8 +310,8 @@ func (e Etcd) CNAME(zone string, state middleware.State) (records []dns.RR, debu
} }
// PTR returns the PTR records, only services that have a domain name as host are included. // PTR returns the PTR records, only services that have a domain name as host are included.
func (e Etcd) PTR(zone string, state middleware.State) (records []dns.RR, debug []msg.Service, err error) { func (e Etcd) PTR(zone string, state middleware.State, opt Options) (records []dns.RR, debug []msg.Service, err error) {
services, debug, err := e.records(state, true) services, debug, err := e.records(state, true, opt)
if err != nil { if err != nil {
return nil, debug, err return nil, debug, err
} }
@ -320,8 +324,8 @@ func (e Etcd) PTR(zone string, state middleware.State) (records []dns.RR, debug
return records, debug, nil return records, debug, nil
} }
func (e Etcd) TXT(zone string, state middleware.State) (records []dns.RR, debug []msg.Service, err error) { func (e Etcd) TXT(zone string, state middleware.State, opt Options) (records []dns.RR, debug []msg.Service, err error) {
services, debug, err := e.records(state, false) services, debug, err := e.records(state, false, opt)
if err != nil { if err != nil {
return nil, debug, err return nil, debug, err
} }
@ -335,14 +339,14 @@ func (e Etcd) TXT(zone string, state middleware.State) (records []dns.RR, debug
return records, debug, nil return records, debug, nil
} }
func (e Etcd) NS(zone string, state middleware.State) (records, extra []dns.RR, debug []msg.Service, err error) { func (e Etcd) NS(zone string, state middleware.State, 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) services, debug, err := e.records(state, false, opt)
if err != nil { if err != nil {
return nil, nil, debug, err return nil, nil, debug, err
} }
@ -368,7 +372,7 @@ func (e Etcd) NS(zone string, state middleware.State) (records, extra []dns.RR,
} }
// SOA Record returns a SOA record. // SOA Record returns a SOA record.
func (e Etcd) SOA(zone string, state middleware.State) ([]dns.RR, []msg.Service, error) { func (e Etcd) SOA(zone string, state middleware.State, 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,

View file

@ -14,13 +14,13 @@ import (
) )
func TestMultiLookup(t *testing.T) { func TestMultiLookup(t *testing.T) {
etcMulti := etc etcMulti := *etc
etcMulti.Zones = []string{"skydns.test.", "miek.nl."} etcMulti.Zones = []string{"skydns.test.", "miek.nl."}
etcMulti.Next = test.ErrorHandler() etcMulti.Next = test.ErrorHandler()
for _, serv := range servicesMulti { for _, serv := range servicesMulti {
set(t, etcMulti, serv.Key, 0, serv) set(t, &etcMulti, serv.Key, 0, serv)
defer delete(t, etcMulti, serv.Key) defer delete(t, &etcMulti, serv.Key)
} }
for _, tc := range dnsTestCasesMulti { for _, tc := range dnsTestCasesMulti {
m := tc.Msg() m := tc.Msg()

View file

@ -20,7 +20,7 @@ import (
) )
var ( var (
etc Etcd etc *Etcd
client etcdc.KeysAPI client etcdc.KeysAPI
ctxt context.Context ctxt context.Context
) )
@ -32,7 +32,7 @@ func init() {
Endpoints: []string{"http://localhost:2379"}, Endpoints: []string{"http://localhost:2379"},
} }
cli, _ := etcdc.New(etcdCfg) cli, _ := etcdc.New(etcdCfg)
etc = Etcd{ etc = &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(),
@ -42,7 +42,7 @@ func init() {
} }
} }
func set(t *testing.T, e Etcd, k string, ttl time.Duration, m *msg.Service) { func set(t *testing.T, e *Etcd, k string, ttl time.Duration, m *msg.Service) {
b, err := json.Marshal(m) b, err := json.Marshal(m)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -51,7 +51,7 @@ func set(t *testing.T, e Etcd, k string, ttl time.Duration, m *msg.Service) {
e.Client.Set(ctxt, path, string(b), &etcdc.SetOptions{TTL: ttl}) e.Client.Set(ctxt, path, string(b), &etcdc.SetOptions{TTL: ttl})
} }
func delete(t *testing.T, e Etcd, k string) { func delete(t *testing.T, e *Etcd, k string) {
path, _ := msg.PathWithWildcard(k, e.PathPrefix) path, _ := msg.PathWithWildcard(k, e.PathPrefix)
e.Client.Delete(ctxt, path, &etcdc.DeleteOptions{Recursive: false}) e.Client.Delete(ctxt, path, &etcdc.DeleteOptions{Recursive: false})
} }

View file

@ -13,7 +13,7 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
) )
func (e Etcd) UpdateStubZones() { func (e *Etcd) UpdateStubZones() {
go func() { go func() {
for { for {
e.updateStubZones() e.updateStubZones()

View file

@ -17,6 +17,7 @@ func TestStubCycle(t *testing.T) {
defer delete(t, etc, serv.Key) defer delete(t, etc, serv.Key)
} }
etc.updateStubZones() etc.updateStubZones()
defer func() { etc.Stubmap = nil }()
for _, tc := range dnsTestCasesCycleStub { for _, tc := range dnsTestCasesCycleStub {
m := tc.Msg() m := tc.Msg()

View file

@ -12,7 +12,7 @@ import (
// Stub wraps an Etcd. We have this type so that it can have a ServeDNS method. // Stub wraps an Etcd. We have this type so that it can have a ServeDNS method.
type Stub struct { type Stub struct {
Etcd *Etcd
Zone string // for what zone (and thus what nameservers are we called) Zone string // for what zone (and thus what nameservers are we called)
} }

View file

@ -19,6 +19,7 @@ func TestStubLookup(t *testing.T) {
defer delete(t, etc, serv.Key) defer delete(t, etc, serv.Key)
} }
etc.updateStubZones() etc.updateStubZones()
defer func() { etc.Stubmap = nil }()
for _, tc := range dnsTestCasesStub { for _, tc := range dnsTestCasesStub {
m := tc.Msg() m := tc.Msg()

View file

@ -21,6 +21,7 @@ func (p RRSet) Len() int { return len(p) }
func (p RRSet) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p RRSet) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p RRSet) Less(i, j int) bool { return p[i].String() < p[j].String() } func (p RRSet) Less(i, j int) bool { return p[i].String() < p[j].String() }
// If the TTL of a record is 303 we don't care what the TTL is.
type Case struct { type Case struct {
Qname string Qname string
Qtype uint16 Qtype uint16

View file

@ -16,7 +16,6 @@ import (
"time" "time"
"github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/chaos"
"github.com/miekg/coredns/middleware/metrics" "github.com/miekg/coredns/middleware/metrics"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -111,19 +110,6 @@ func New(addr string, configs []Config, gracefulTimeout time.Duration) (*Server,
} }
s.zones[conf.Host] = z s.zones[conf.Host] = z
// A bit of a hack. Loop through the middlewares of this zone and check if
// they have enabled the chaos middleware. If so add the special chaos zones.
Middleware:
for _, mid := range z.config.Middleware {
fn := mid(nil)
if _, ok := fn.(chaos.Chaos); ok {
for _, ch := range []string{"authors.bind.", "version.bind.", "version.server.", "hostname.bind.", "id.server."} {
s.zones[ch] = z
}
break Middleware
}
}
} }
return s, nil return s, nil

View file

@ -2,10 +2,103 @@
package test package test
import "testing" import (
"encoding/json"
"io/ioutil"
"log"
"testing"
"time"
"github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/etcd"
"github.com/miekg/coredns/middleware/etcd/msg"
"github.com/miekg/coredns/middleware/proxy"
"github.com/miekg/coredns/middleware/test"
etcdc "github.com/coreos/etcd/client"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
func etcdMiddleware() *etcd.Etcd {
etcdCfg := etcdc.Config{
Endpoints: []string{"http://localhost:2379"},
}
cli, _ := etcdc.New(etcdCfg)
client := etcdc.NewKeysAPI(cli)
return &etcd.Etcd{Client: client}
}
// This test starts two coredns servers (and needs etcd). Configure a stubzones in both (that will loop) and // This test starts two coredns servers (and needs etcd). Configure a stubzones in both (that will loop) and
// will then test if we detect this loop. // will then test if we detect this loop.
func TestEtcdStubForwarding(t *testing.T) { func TestEtcdStubForwarding(t *testing.T) {
// TODO(miek) // TODO(miek)
} }
func TestEtcdStubAndProxyLookup(t *testing.T) {
corefile := `.:0 {
etcd skydns.local {
stubzones
path /skydns
endpoint http://localhost:2379
upstream 8.8.8.8:53 8.8.4.4:53
}
proxy . 8.8.8.8:53
}`
etc := etcdMiddleware()
ex, _, udp, err := Server(t, corefile)
if err != nil {
t.Fatalf("Could get server: %s", err)
}
defer ex.Stop()
log.SetOutput(ioutil.Discard)
var ctx = context.TODO()
for _, serv := range servicesStub { // adds example.{net,org} as stubs
set(ctx, t, etc, serv.Key, 0, serv)
defer delete(ctx, t, etc, serv.Key)
}
p := proxy.New([]string{udp}) // use udp port from the server
state := middleware.State{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
resp, err := p.Lookup(state, "example.com.", dns.TypeA)
if err != nil {
t.Error("Expected to receive reply, but didn't")
return
}
if len(resp.Answer) == 0 {
t.Error("Expected to at least one RR in the answer section, got none")
}
if resp.Answer[0].Header().Rrtype != dns.TypeA {
t.Error("Expected RR to A, got: %d", resp.Answer[0].Header().Rrtype)
}
if resp.Answer[0].(*dns.A).A.String() != "93.184.216.34" {
t.Error("Expected 93.184.216.34, got: %d", resp.Answer[0].(*dns.A).A.String())
}
}
var servicesStub = []*msg.Service{
// Two tests, ask a question that should return servfail because remote it no accessible
// and one with edns0 option added, that should return refused.
{Host: "127.0.0.1", Port: 666, Key: "b.example.org.stub.dns.skydns.test."},
// Actual test that goes out to the internet.
{Host: "199.43.132.53", Key: "a.example.net.stub.dns.skydns.test."},
}
// Copied from middleware/etcd/setup_test.go
func set(ctx context.Context, t *testing.T, e *etcd.Etcd, k string, ttl time.Duration, m *msg.Service) {
b, err := json.Marshal(m)
if err != nil {
t.Fatal(err)
}
path, _ := msg.PathWithWildcard(k, e.PathPrefix)
e.Client.Set(ctx, path, string(b), &etcdc.SetOptions{TTL: ttl})
}
// Copied from middleware/etcd/setup_test.go
func delete(ctx context.Context, t *testing.T, e *etcd.Etcd, k string) {
path, _ := msg.PathWithWildcard(k, e.PathPrefix)
e.Client.Delete(ctx, path, &etcdc.DeleteOptions{Recursive: false})
}