Allow debug queries to etcd middleware (#150)

With this you can retreive the raw data that the etcd middleware
used to create the reply. The debug data is put in TXT records
that are stuffed in the CH classs. This is only enabled if you
specify `debug` in the etcd stanza.

You can retrieve it by prefixing your query with 'o-o.debug.'
For instance:

; <<>> DiG 9.10.3-P4-Ubuntu <<>> @localhost -p 1053 SRV o-o.debug.production.*.skydns.local
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 47798
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 3

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;o-o.debug.production.*.skydns.local. IN	SRV

;; ANSWER SECTION:
production.*.skydns.local. 154	IN	SRV	10 50 8080 service1.example.com.
production.*.skydns.local. 154	IN	SRV	10 50 8080 service2.example.com.

;; ADDITIONAL SECTION:
skydns.local.skydns.east.production.rails.1. 154 CH TXT	"service1.example.com:8080(10,0,,false)[0,]"
skydns.local.skydns.west.production.rails.2. 154 CH TXT	"service2.example.com:8080(10,0,,false)[0,]"
This commit is contained in:
Miek Gieben 2016-05-22 21:16:26 +01:00
parent d35394a8df
commit c30671f4c0
9 changed files with 323 additions and 62 deletions

View file

@ -68,6 +68,8 @@ func etcdParse(c *Controller) (etcd.Etcd, bool, error) {
switch c.Val() { switch c.Val() {
case "stubzones": case "stubzones":
stubzones = true stubzones = true
case "debug":
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()
@ -103,6 +105,8 @@ func etcdParse(c *Controller) (etcd.Etcd, bool, error) {
switch c.Val() { switch c.Val() {
case "stubzones": case "stubzones":
stubzones = true stubzones = true
case "debug":
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()

View file

@ -27,6 +27,7 @@ etcd [zones...] {
endpoint endpoint... endpoint endpoint...
upstream address... upstream address...
tls cert key cacert tls cert key cacert
debug
} }
~~~ ~~~
@ -38,6 +39,13 @@ 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 reveive extra information in the
additional section of the reply in the form of text records:
skydns.test.skydns.dom.a. 300 CH TXT "127.0.0.1:0(10,0,,false)[0,]"
This shows the complete key as the owername, the rdata of the TXT record has:
`host:port(priority,weight,txt content,mail)[targetstrip,group]`.
## Examples ## Examples

38
middleware/etcd/debug.go Normal file
View file

@ -0,0 +1,38 @@
package etcd
import (
"strings"
"github.com/miekg/coredns/middleware/etcd/msg"
"github.com/miekg/dns"
)
const debugName = "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
// name with o-o.debug. stripped off.
func isDebug(name string) string {
if len(name) == len(debugName) {
return ""
}
debug := strings.HasPrefix(name, debugName)
if !debug {
return ""
}
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
}

View file

@ -0,0 +1,147 @@
package etcd
import (
"sort"
"testing"
"github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/etcd/msg"
"github.com/miekg/coredns/middleware/test"
"github.com/miekg/dns"
)
func TestisDebug(t *testing.T) {
if ok := isDebug("o-o.debug.miek.nl."); ok != "miek.nl." {
t.Errorf("expected o-o.debug.miek.nl. to be debug")
}
if ok := isDebug("o-o.Debug.miek.nl."); ok != "miek.nl." {
t.Errorf("expected o-o.Debug.miek.nl. to be debug")
}
if ok := isDebug("i-o.Debug.miek.nl."); ok != "" {
t.Errorf("expected i-o.Debug.miek.nl. to be non-debug")
}
if ok := isDebug("i-o.Debug."); ok != "" {
t.Errorf("expected o-o.Debug. to be non-debug")
}
}
func TestDebugLookup(t *testing.T) {
for _, serv := range servicesDebug {
set(t, etc, serv.Key, 0, serv)
defer delete(t, etc, serv.Key)
}
etc.Debug = true
defer func() { etc.Debug = false }()
for _, tc := range dnsTestCasesDebug {
m := tc.Msg()
rec := middleware.NewResponseRecorder(&test.ResponseWriter{})
_, err := etc.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("expected no error, got %v\n", err)
continue
}
resp := rec.Msg()
sort.Sort(test.RRSet(resp.Answer))
sort.Sort(test.RRSet(resp.Ns))
sort.Sort(test.RRSet(resp.Extra))
if !test.Header(t, tc, resp) {
t.Logf("%v\n", resp)
continue
}
if !test.Section(t, tc, test.Answer, resp.Answer) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Ns, resp.Ns) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Extra, resp.Extra) {
t.Logf("%v\n", resp)
}
}
}
func TestDebugLookupFalse(t *testing.T) {
for _, serv := range servicesDebug {
set(t, etc, serv.Key, 0, serv)
defer delete(t, etc, serv.Key)
}
for _, tc := range dnsTestCasesDebugFalse {
m := tc.Msg()
rec := middleware.NewResponseRecorder(&test.ResponseWriter{})
_, err := etc.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("expected no error, got %v\n", err)
continue
}
resp := rec.Msg()
sort.Sort(test.RRSet(resp.Answer))
sort.Sort(test.RRSet(resp.Ns))
sort.Sort(test.RRSet(resp.Extra))
if !test.Header(t, tc, resp) {
t.Logf("%v\n", resp)
continue
}
if !test.Section(t, tc, test.Answer, resp.Answer) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Ns, resp.Ns) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Extra, resp.Extra) {
t.Logf("%v\n", resp)
}
}
}
var servicesDebug = []*msg.Service{
{Host: "127.0.0.1", Key: "a.dom.skydns.test."},
{Host: "127.0.0.2", Key: "b.sub.dom.skydns.test."},
}
var dnsTestCasesDebug = []test.Case{
{
Qname: "o-o.debug.dom.skydns.test.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("dom.skydns.test. 300 IN A 127.0.0.1"),
test.A("dom.skydns.test. 300 IN A 127.0.0.2"),
},
Extra: []dns.RR{
test.TXT(`skydns.test.skydns.dom.a. 300 CH TXT "127.0.0.1:0(10,0,,false)[0,]"`),
test.TXT(`skydns.test.skydns.dom.sub.b. 300 CH TXT "127.0.0.2:0(10,0,,false)[0,]"`),
},
},
{
Qname: "o-o.debug.dom.skydns.test.", Qtype: dns.TypeTXT,
Ns: []dns.RR{
test.SOA("skydns.test. 300 IN SOA ns.dns.skydns.test. hostmaster.skydns.test. 1463943291 7200 1800 86400 60"),
},
Extra: []dns.RR{
test.TXT(`skydns.test.skydns.dom.a. 300 CH TXT "127.0.0.1:0(10,0,,false)[0,]"`),
test.TXT(`skydns.test.skydns.dom.sub.b. 300 CH TXT "127.0.0.2:0(10,0,,false)[0,]"`),
},
},
}
var dnsTestCasesDebugFalse = []test.Case{
{
Qname: "o-o.debug.dom.skydns.test.", Qtype: dns.TypeA,
Rcode: dns.RcodeNameError,
Ns: []dns.RR{
test.SOA("skydns.test. 300 IN SOA ns.dns.skydns.test. hostmaster.skydns.test. 1463943291 7200 1800 86400 60"),
},
},
{
Qname: "o-o.debug.dom.skydns.test.", Qtype: dns.TypeTXT,
Rcode: dns.RcodeNameError,
Ns: []dns.RR{
test.SOA("skydns.test. 300 IN SOA ns.dns.skydns.test. hostmaster.skydns.test. 1463943291 7200 1800 86400 60"),
},
},
}

View file

@ -24,6 +24,8 @@ 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.
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

View file

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/etcd/msg"
"github.com/miekg/dns" "github.com/miekg/dns"
"golang.org/x/net/context" "golang.org/x/net/context"
@ -14,11 +15,18 @@ func (e Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i
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()
if e.Debug {
if debug := isDebug(name); debug != "" {
e.debug = r.Question[0].Name
state.Clear()
state.Req.Question[0].Name = debug
}
}
// We need to check stubzones first, because we may get a request for a zone we // We need to check stubzones first, because we may get a request for a zone we
// are not auth. for *but* do have a stubzone forward for. If we do the stubzone // are not auth. for *but* do have a stubzone forward for. If we do the stubzone
// handler will handle the request. // handler will handle the request.
name := state.Name()
if e.Stubmap != nil && len(*e.Stubmap) > 0 { if e.Stubmap != nil && len(*e.Stubmap) > 0 {
for zone, _ := range *e.Stubmap { for zone, _ := range *e.Stubmap {
if middleware.Name(zone).Matches(name) { if middleware.Name(zone).Matches(name) {
@ -36,52 +44,62 @@ func (e Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i
return e.Next.ServeDNS(ctx, w, r) return e.Next.ServeDNS(ctx, w, r)
} }
m := new(dns.Msg)
m.SetReply(r)
m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true
var ( var (
records, extra []dns.RR records, extra []dns.RR
debug []msg.Service
err error err error
) )
switch state.Type() { switch state.Type() {
case "A": case "A":
records, err = e.A(zone, state, nil) records, debug, err = e.A(zone, state, nil)
case "AAAA": case "AAAA":
records, err = e.AAAA(zone, state, nil) records, debug, err = e.AAAA(zone, state, nil)
case "TXT": case "TXT":
records, err = e.TXT(zone, state) records, debug, err = e.TXT(zone, state)
case "CNAME": case "CNAME":
records, err = e.CNAME(zone, state) records, debug, err = e.CNAME(zone, state)
case "MX": case "MX":
records, extra, err = e.MX(zone, state) records, extra, debug, err = e.MX(zone, state)
case "SRV": case "SRV":
records, extra, err = e.SRV(zone, state) records, extra, debug, err = e.SRV(zone, state)
case "SOA": case "SOA":
records = []dns.RR{e.SOA(zone, state)} records, debug, err = e.SOA(zone, state)
case "NS": case "NS":
if state.Name() == zone { if state.Name() == zone {
records, extra, err = e.NS(zone, state) records, extra, debug, err = e.NS(zone, state)
break break
} }
fallthrough fallthrough
default: default:
// Do a fake A lookup, so we can distinguish betwen NODATA and NXDOMAIN // Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN
_, err = e.A(zone, state, nil) _, debug, err = e.A(zone, state, nil)
} }
if e.debug != "" {
// substitute this name with the original when we return the request.
state.Clear()
state.Req.Question[0].Name = e.debug
}
if isEtcdNameError(err) { if isEtcdNameError(err) {
return e.Err(zone, dns.RcodeNameError, state) return e.Err(zone, dns.RcodeNameError, state, debug)
} }
if err != nil { if err != nil {
return dns.RcodeServerFailure, err return dns.RcodeServerFailure, err
} }
if len(records) == 0 { if len(records) == 0 {
return e.Err(zone, dns.RcodeSuccess, state) return e.Err(zone, dns.RcodeSuccess, state, debug)
} }
m := new(dns.Msg)
m.SetReply(r)
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 != "" {
m.Extra = append(m.Extra, servicesToTxt(debug)...)
}
m = dedup(m) m = dedup(m)
state.SizeAndDo(m) state.SizeAndDo(m)
@ -90,11 +108,12 @@ func (e Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i
return dns.RcodeSuccess, nil return dns.RcodeSuccess, nil
} }
// NoData write a nodata response to the client. // Err write an error response to the client.
func (e Etcd) Err(zone string, rcode int, state middleware.State) (int, error) { func (e Etcd) Err(zone string, rcode int, state middleware.State, debug []msg.Service) (int, error) {
m := new(dns.Msg) m := new(dns.Msg)
m.SetRcode(state.Req, rcode) m.SetRcode(state.Req, rcode)
m.Ns = []dns.RR{e.SOA(zone, state)} m.Ns, _, _ = e.SOA(zone, state)
m.Extra = servicesToTxt(debug)
state.SizeAndDo(m) state.SizeAndDo(m)
state.W.WriteMsg(m) state.W.WriteMsg(m)
return rcode, nil return rcode, nil

View file

@ -12,19 +12,22 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
) )
func (e Etcd) records(state middleware.State, exact bool) ([]msg.Service, error) { func (e Etcd) records(state middleware.State, exact bool) (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 nil, err return
}
if e.debug != "" {
debug = services
} }
services = msg.Group(services) services = msg.Group(services)
return services, nil return
} }
func (e Etcd) A(zone string, state middleware.State, previousRecords []dns.RR) (records []dns.RR, err error) { func (e Etcd) A(zone string, state middleware.State, previousRecords []dns.RR) (records []dns.RR, debug []msg.Service, err error) {
services, err := e.records(state, false) services, debug, err := e.records(state, false)
if err != nil { if err != nil {
return nil, err return nil, debug, err
} }
for _, serv := range services { for _, serv := range services {
@ -47,13 +50,14 @@ 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, err := e.A(zone, state1, append(previousRecords, newRecord)) nextRecords, nextDebug, err := e.A(zone, state1, append(previousRecords, newRecord))
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.
if len(nextRecords) > 0 { if len(nextRecords) > 0 {
records = append(records, newRecord) records = append(records, newRecord)
records = append(records, nextRecords...) records = append(records, nextRecords...)
debug = append(debug, nextDebug...)
} }
continue continue
} }
@ -77,13 +81,13 @@ func (e Etcd) A(zone string, state middleware.State, previousRecords []dns.RR) (
// nodata? // nodata?
} }
} }
return records, nil return records, debug, nil
} }
func (e Etcd) AAAA(zone string, state middleware.State, previousRecords []dns.RR) (records []dns.RR, err error) { func (e Etcd) AAAA(zone string, state middleware.State, previousRecords []dns.RR) (records []dns.RR, debug []msg.Service, err error) {
services, err := e.records(state, false) services, debug, err := e.records(state, false)
if err != nil { if err != nil {
return nil, err return nil, debug, err
} }
for _, serv := range services { for _, serv := range services {
@ -106,13 +110,14 @@ 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, err := e.AAAA(zone, state1, append(previousRecords, newRecord)) nextRecords, nextDebug, err := e.AAAA(zone, state1, append(previousRecords, newRecord))
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.
if len(nextRecords) > 0 { if len(nextRecords) > 0 {
records = append(records, newRecord) records = append(records, newRecord)
records = append(records, nextRecords...) records = append(records, nextRecords...)
debug = append(debug, nextDebug...)
} }
continue continue
} }
@ -137,15 +142,15 @@ func (e Etcd) AAAA(zone string, state middleware.State, previousRecords []dns.RR
records = append(records, serv.NewAAAA(state.QName(), ip.To16())) records = append(records, serv.NewAAAA(state.QName(), ip.To16()))
} }
} }
return records, nil return records, debug, nil
} }
// 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 []dns.RR, extra []dns.RR, err error) { func (e Etcd) SRV(zone string, state middleware.State) (records, extra []dns.RR, debug []msg.Service, err error) {
services, err := e.records(state, false) services, debug, err := e.records(state, false)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, nil, err
} }
// Looping twice to get the right weight vs priority // Looping twice to get the right weight vs priority
@ -201,9 +206,10 @@ func (e Etcd) SRV(zone string, state middleware.State) (records []dns.RR, extra
// 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, e1 := e.A(zone, state1, nil) addr, debugAddr, e1 := e.A(zone, state1, nil)
if e1 == nil { if e1 == nil {
extra = append(extra, addr...) extra = append(extra, addr...)
debug = append(debug, debugAddr...)
} }
// e.AAA(zone, state1, nil) as well...? // e.AAA(zone, state1, nil) as well...?
case ip.To4() != nil: case ip.To4() != nil:
@ -220,15 +226,15 @@ func (e Etcd) SRV(zone string, state middleware.State) (records []dns.RR, extra
extra = append(extra, serv.NewAAAA(srv.Target, ip.To16())) extra = append(extra, serv.NewAAAA(srv.Target, ip.To16()))
} }
} }
return records, extra, nil return records, extra, debug, nil
} }
// 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 []dns.RR, extra []dns.RR, err error) { func (e Etcd) MX(zone string, state middleware.State) (records, extra []dns.RR, debug []msg.Service, err error) {
services, err := e.records(state, false) services, debug, err := e.records(state, false)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, debug, err
} }
lookup := make(map[string]bool) lookup := make(map[string]bool)
@ -265,9 +271,10 @@ func (e Etcd) MX(zone string, state middleware.State) (records []dns.RR, extra [
} }
// Internal name // Internal name
state1 := copyState(state, mx.Mx, dns.TypeA) state1 := copyState(state, mx.Mx, dns.TypeA)
addr, e1 := e.A(zone, state1, nil) addr, debugAddr, e1 := e.A(zone, state1, nil)
if e1 == nil { if e1 == nil {
extra = append(extra, addr...) extra = append(extra, addr...)
debug = append(debug, debugAddr...)
} }
// e.AAAA as well // e.AAAA as well
case ip.To4() != nil: case ip.To4() != nil:
@ -280,13 +287,13 @@ func (e Etcd) MX(zone string, state middleware.State) (records []dns.RR, extra [
extra = append(extra, serv.NewAAAA(serv.Host, ip.To16())) extra = append(extra, serv.NewAAAA(serv.Host, ip.To16()))
} }
} }
return records, extra, nil return records, extra, debug, nil
} }
func (e Etcd) CNAME(zone string, state middleware.State) (records []dns.RR, err error) { func (e Etcd) CNAME(zone string, state middleware.State) (records []dns.RR, debug []msg.Service, err error) {
services, err := e.records(state, true) services, debug, err := e.records(state, true)
if err != nil { if err != nil {
return nil, err return nil, debug, err
} }
if len(services) > 0 { if len(services) > 0 {
@ -295,13 +302,13 @@ func (e Etcd) CNAME(zone string, state middleware.State) (records []dns.RR, err
records = append(records, serv.NewCNAME(state.QName(), serv.Host)) records = append(records, serv.NewCNAME(state.QName(), serv.Host))
} }
} }
return records, nil return records, debug, nil
} }
func (e Etcd) TXT(zone string, state middleware.State) (records []dns.RR, err error) { func (e Etcd) TXT(zone string, state middleware.State) (records []dns.RR, debug []msg.Service, err error) {
services, err := e.records(state, false) services, debug, err := e.records(state, false)
if err != nil { if err != nil {
return nil, err return nil, debug, err
} }
for _, serv := range services { for _, serv := range services {
@ -310,19 +317,19 @@ func (e Etcd) TXT(zone string, state middleware.State) (records []dns.RR, err er
} }
records = append(records, serv.NewTXT(state.QName())) records = append(records, serv.NewTXT(state.QName()))
} }
return records, nil return records, debug, nil
} }
func (e Etcd) NS(zone string, state middleware.State) (records, extra []dns.RR, err error) { func (e Etcd) NS(zone string, state middleware.State) (records, extra []dns.RR, debug []msg.Service, err error) {
// NS record for this zone live in a special place, ns.dns.<zone>. Fake our lookup. // 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, err := e.records(state, false) services, debug, err := e.records(state, false)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, debug, err
} }
// ... and reset // ... and reset
state.Req.Question[0].Name = old state.Req.Question[0].Name = old
@ -331,7 +338,7 @@ func (e Etcd) NS(zone string, state middleware.State) (records, extra []dns.RR,
ip := net.ParseIP(serv.Host) ip := net.ParseIP(serv.Host)
switch { switch {
case ip == nil: case ip == nil:
return nil, nil, fmt.Errorf("NS record must be an IP address: %s", serv.Host) return nil, nil, debug, fmt.Errorf("NS record must be an IP address: %s", serv.Host)
case ip.To4() != nil: case ip.To4() != nil:
serv.Host = e.Domain(serv.Key) serv.Host = e.Domain(serv.Key)
records = append(records, serv.NewNS(state.QName())) records = append(records, serv.NewNS(state.QName()))
@ -342,13 +349,14 @@ func (e Etcd) NS(zone string, state middleware.State) (records, extra []dns.RR,
extra = append(extra, serv.NewAAAA(serv.Host, ip.To16())) extra = append(extra, serv.NewAAAA(serv.Host, ip.To16()))
} }
} }
return records, extra, nil return records, extra, debug, nil
} }
// SOA Record returns a SOA record. // SOA Record returns a SOA record.
func (e Etcd) SOA(zone string, state middleware.State) *dns.SOA { func (e Etcd) SOA(zone string, state middleware.State) ([]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}
return &dns.SOA{Hdr: header,
soa := &dns.SOA{Hdr: header,
Mbox: "hostmaster." + zone, Mbox: "hostmaster." + zone,
Ns: "ns.dns." + zone, Ns: "ns.dns." + zone,
Serial: uint32(time.Now().Unix()), Serial: uint32(time.Now().Unix()),
@ -357,10 +365,10 @@ func (e Etcd) SOA(zone string, state middleware.State) *dns.SOA {
Expire: 86400, Expire: 86400,
Minttl: 60, Minttl: 60,
} }
// TODO(miek): fake some msg.Service here when returning.
return []dns.RR{soa}, nil, nil
} }
// TODO(miek): DNSKEY and friends... intercepted by the DNSSEC middleware?
func isDuplicateCNAME(r *dns.CNAME, records []dns.RR) bool { func isDuplicateCNAME(r *dns.CNAME, records []dns.RR) bool {
for _, rec := range records { for _, rec := range records {
if v, ok := rec.(*dns.CNAME); ok { if v, ok := rec.(*dns.CNAME); ok {
@ -372,6 +380,7 @@ func isDuplicateCNAME(r *dns.CNAME, records []dns.RR) bool {
return false return false
} }
// TODO(miek): Move to middleware?
func copyState(state middleware.State, target string, typ uint16) middleware.State { func copyState(state middleware.State, target string, typ uint16) middleware.State {
state1 := middleware.State{W: state.W, Req: state.Req.Copy()} state1 := middleware.State{W: state.W, Req: state.Req.Copy()}
state1.Req.Question[0] = dns.Question{dns.Fqdn(target), dns.ClassINET, typ} state1.Req.Question[0] = dns.Question{dns.Fqdn(target), dns.ClassINET, typ}

View file

@ -1,6 +1,7 @@
package msg package msg
import ( import (
"fmt"
"net" "net"
"strings" "strings"
@ -35,6 +36,37 @@ type Service struct {
Key string `json:"-"` Key string `json:"-"`
} }
// RR returns an RR representation of s. It is in a condensed form to minimize space
// when this is returned in a DNS message.
// The RR will look like:
// skydns.local.skydns.east.production.rails.1. 300 CH TXT "service1.example.com:8080(10,0,,false)[0,]"
// etcd Key Ttl Host:Port < see below >
// between parens: (Priority, Weight, Text (only first 200 bytes!), Mail)
// between blockquotes: [TargetStrip,Group]
// If the record is synthesised by CoreDNS (i.e. no lookup in etcd happened):
//
// skydns.local.skydns.east.production.rails.1. 300 CH TXT "service1.example.com:8080(10,0,,false)[0,]"
//
func (s *Service) RR() *dns.TXT {
l := len(s.Text)
if l > 200 {
l = 200
}
t := new(dns.TXT)
t.Hdr.Class = dns.ClassCHAOS
t.Hdr.Ttl = s.Ttl
t.Hdr.Rrtype = dns.TypeTXT
// TODO(miek): key guaranteerd to be > 1?
t.Hdr.Name = strings.Replace(s.Key[1:], "/", ".", -1) + "." // TODO(miek): slightly more like etcd.Domain()
t.Txt = make([]string, 1)
t.Txt[0] = fmt.Sprintf("%s:%d(%d,%d,%s,%t)[%d,%s]",
s.Host, s.Port,
s.Priority, s.Weight, s.Text[:l], s.Mail,
s.TargetStrip, s.Group)
return t
}
// NewSRV returns a new SRV record based on the Service. // NewSRV returns a new SRV record based on the Service.
func (s *Service) NewSRV(name string, weight uint16) *dns.SRV { func (s *Service) NewSRV(name string, weight uint16) *dns.SRV {
host := targetStrip(dns.Fqdn(s.Host), s.TargetStrip) host := targetStrip(dns.Fqdn(s.Host), s.TargetStrip)

View file

@ -190,7 +190,8 @@ func (s *State) Type() string { return dns.Type(s.Req.Question[0].Qtype).String(
func (s *State) QType() uint16 { return s.Req.Question[0].Qtype } func (s *State) QType() uint16 { return s.Req.Question[0].Qtype }
// Name returns the name of the question in the request. Note // Name returns the name of the question in the request. Note
// this name will always have a closing dot and will be lower cased. // this name will always have a closing dot and will be lower cased. After a call Name
// the value will be cached. To clear this caching call Clear.
func (s *State) Name() string { func (s *State) Name() string {
if s.name != "" { if s.name != "" {
return s.name return s.name
@ -222,6 +223,7 @@ func (s *State) Clear() {
} }
const ( const (
// TODO(miek): make this less awkward.
doTrue = 1 doTrue = 1
doFalse = 2 doFalse = 2
) )