middleware/httpproxy: add debug queries (#446)

* middleware/httproxy: implement debug queries

Not too useful at the moment, but o-o.debug queries are supported
and return the Comment from dns.google.com.

Note that this is not always set.

* improve documentation

* Testing cleanups
This commit is contained in:
Miek Gieben 2016-11-29 09:54:57 +00:00 committed by GitHub
parent a1b9f96d87
commit 4cfd19c7c9
12 changed files with 108 additions and 55 deletions

View file

@ -37,7 +37,7 @@ etcd [ZONES...] {
* **ENDPOINT** the etcd endpoints. Defaults to "http://localhost:2397". * **ENDPOINT** the etcd endpoints. Defaults to "http://localhost:2397".
* `upstream` upstream resolvers to be used resolve external names found in etcd (think CNAMEs) * `upstream` upstream resolvers to be used resolve external names found in etcd (think CNAMEs)
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. **ADDRESS* can be an IP address, and IP:port or a string pointing to a file the proxy middleware. **ADDRESS** can be an IP address, and IP:port or a string pointing to a file
that is structured as /etc/resolv.conf. that is structured as /etc/resolv.conf.
* `tls` followed the cert, key and the CA's cert filenames. * `tls` followed the cert, key and the CA's cert filenames.
* `debug` allows for 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
@ -127,7 +127,7 @@ Or with *debug* queries enabled:
When debug queries are enabled CoreDNS will return errors and etcd records encountered during the resolution When debug queries are enabled CoreDNS will return errors and etcd records encountered during the resolution
process in the response. The general form looks like this: process in the response. The general form looks like this:
skydns.test.skydns.dom.a. 300 CH TXT "127.0.0.1:0(10,0,,false)[0,]" skydns.test.skydns.dom.a. 0 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: This shows the complete key as the owername, the rdata of the TXT record has:
`host:port(priority,weight,txt content,mail)[targetstrip,group]`. `host:port(priority,weight,txt content,mail)[targetstrip,group]`.

View file

@ -1,20 +0,0 @@
package etcd
import "strings"
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. Must be called with name lowercased.
func isDebug(name string) string {
if len(name) == len(debugName) {
return ""
}
name = strings.ToLower(name)
debug := strings.HasPrefix(name, debugName)
if !debug {
return ""
}
return name[len(debugName):]
}

View file

@ -4,7 +4,6 @@ package etcd
import ( import (
"sort" "sort"
"strings"
"testing" "testing"
"github.com/miekg/coredns/middleware/etcd/msg" "github.com/miekg/coredns/middleware/etcd/msg"
@ -14,21 +13,6 @@ import (
"github.com/miekg/dns" "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(strings.ToLower("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(strings.ToLower("i-o.Debug.")); ok != "" {
t.Errorf("expected o-o.Debug. to be non-debug")
}
}
func TestDebugLookup(t *testing.T) { func TestDebugLookup(t *testing.T) {
etc := newEtcdMiddleware() etc := newEtcdMiddleware()
etc.Debugging = true etc.Debugging = true

View file

@ -5,6 +5,7 @@ import (
"github.com/miekg/coredns/middleware" "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/debug"
"github.com/miekg/coredns/middleware/pkg/dnsutil" "github.com/miekg/coredns/middleware/pkg/dnsutil"
"github.com/miekg/coredns/request" "github.com/miekg/coredns/request"
@ -21,10 +22,10 @@ func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (
} }
name := state.Name() name := state.Name()
if e.Debugging { if e.Debugging {
if debug := isDebug(name); debug != "" { if bug := debug.IsDebug(name); bug != "" {
opt.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 = bug
} }
} }

View file

@ -48,3 +48,24 @@ proxy . dns.google.com {
upstream /etc/resolv.conf upstream /etc/resolv.conf
} }
~~~ ~~~
## Debug queries
Debug queries are enabled by default and currently there is no way to turn them off. When CoreDNS
receives a debug queries (i.e. the name is prefixed with `o-o.debug.` a TXT record with Comment from
`dns.google.com` is added. Note this is not always set, but sometimes you'll see:
`dig @localhost -p 1053 mx o-o.debug.example.org`:
~~~ txt
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;o-o.debug.example.org. IN MX
;; AUTHORITY SECTION:
example.org. 1799 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2016110711 7200 3600 1209600 3600
;; ADDITIONAL SECTION:
. 0 CH TXT "Response from 199.43.133.53"
~~~

View file

@ -12,7 +12,9 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/miekg/coredns/middleware/pkg/debug"
"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"
) )
@ -30,11 +32,17 @@ type google struct {
func newGoogle() *google { return &google{client: newClient(ghost), quit: make(chan bool)} } func newGoogle() *google { return &google{client: newClient(ghost), quit: make(chan bool)} }
func (g *google) Exchange(req *dns.Msg) (*dns.Msg, error) { func (g *google) Exchange(state request.Request) (*dns.Msg, error) {
v := url.Values{} v := url.Values{}
v.Set("name", req.Question[0].Name) v.Set("name", state.Name())
v.Set("type", fmt.Sprintf("%d", req.Question[0].Qtype)) v.Set("type", fmt.Sprintf("%d", state.QType()))
optDebug := false
if bug := debug.IsDebug(state.Name()); bug != "" {
optDebug = true
v.Set("name", bug)
}
start := time.Now() start := time.Now()
@ -60,12 +68,20 @@ func (g *google) Exchange(req *dns.Msg) (*dns.Msg, error) {
return nil, err return nil, err
} }
m, err := toMsg(gm) m, debug, err := toMsg(gm)
if err != nil { if err != nil {
return nil, err return nil, err
} }
m.Id = req.Id if optDebug {
// reset question
m.Question[0].Name = state.QName()
// prepend debug RR to the additional section
m.Extra = append([]dns.RR{debug}, m.Extra...)
}
m.Id = state.Req.Id
return m, nil return m, nil
} }
@ -223,8 +239,11 @@ func (g *google) do(addr, json string) ([]byte, error) {
return buf, nil return buf, nil
} }
func toMsg(g *googleMsg) (*dns.Msg, error) { // toMsg converts a googleMsg into the dns message. The returned RR is the comment disquised as a TXT
// record.
func toMsg(g *googleMsg) (*dns.Msg, dns.RR, error) {
m := new(dns.Msg) m := new(dns.Msg)
m.Response = true
m.Rcode = g.Status m.Rcode = g.Status
m.Truncated = g.TC m.Truncated = g.TC
m.RecursionDesired = g.RD m.RecursionDesired = g.RD
@ -243,23 +262,24 @@ func toMsg(g *googleMsg) (*dns.Msg, error) {
for i := 0; i < len(m.Answer); i++ { for i := 0; i < len(m.Answer); i++ {
m.Answer[i], err = toRR(g.Answer[i]) m.Answer[i], err = toRR(g.Answer[i])
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
} }
for i := 0; i < len(m.Ns); i++ { for i := 0; i < len(m.Ns); i++ {
m.Ns[i], err = toRR(g.Authority[i]) m.Ns[i], err = toRR(g.Authority[i])
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
} }
for i := 0; i < len(m.Extra); i++ { for i := 0; i < len(m.Extra); i++ {
m.Extra[i], err = toRR(g.Additional[i]) m.Extra[i], err = toRR(g.Additional[i])
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
} }
return m, nil txt, _ := dns.NewRR(". 0 CH TXT " + g.Comment)
return m, txt, nil
} }
func toRR(g googleRR) (dns.RR, error) { func toRR(g googleRR) (dns.RR, error) {

View file

@ -27,9 +27,9 @@ func (p *Proxy) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
start := time.Now() start := time.Now()
state := request.Request{W: w, Req: r} state := request.Request{W: w, Req: r}
reply, backendErr := p.e.Exchange(r) reply, backendErr := p.e.Exchange(state)
if backendErr == nil { if backendErr == nil && reply != nil {
state.SizeAndDo(reply) state.SizeAndDo(reply)
w.WriteMsg(reply) w.WriteMsg(reply)

View file

@ -2,6 +2,7 @@ package httpproxy
import ( import (
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"strings" "strings"
"testing" "testing"
@ -9,7 +10,9 @@ import (
"github.com/mholt/caddy" "github.com/mholt/caddy"
) )
func TestSetupChaos(t *testing.T) { func TestSetupHttpproxy(t *testing.T) {
log.SetOutput(ioutil.Discard)
tests := []struct { tests := []struct {
input string input string
shouldErr bool shouldErr bool
@ -55,7 +58,6 @@ func TestSetupChaos(t *testing.T) {
} }
if err != nil { if err != nil {
t.Logf("%q", err)
if !test.shouldErr { if !test.shouldErr {
t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err) t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err)
} }

View file

@ -5,13 +5,14 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/miekg/coredns/request"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
// Exchanger is an interface that specifies a type implementing a DNS resolver that // Exchanger is an interface that specifies a type implementing a DNS resolver that
// uses a HTTPS server. // uses a HTTPS server.
type Exchanger interface { type Exchanger interface {
Exchange(*dns.Msg) (*dns.Msg, error) Exchange(request.Request) (*dns.Msg, error)
SetUpstream(*simpleUpstream) error SetUpstream(*simpleUpstream) error
OnStartup() error OnStartup() error

View file

@ -0,0 +1,20 @@
package debug
import "strings"
const Name = "o-o.debug."
// IsDebug checks if name is a debugging name, i.e. starts with o-o.debug.
// it returns the empty string if it is not a debug message, otherwise it will return the
// name with o-o.debug. stripped off. Must be called with name lowercased.
func IsDebug(name string) string {
if len(name) == len(Name) {
return ""
}
name = strings.ToLower(name)
debug := strings.HasPrefix(name, Name)
if !debug {
return ""
}
return name[len(Name):]
}

View file

@ -0,0 +1,21 @@
package debug
import (
"strings"
"testing"
)
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(strings.ToLower("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(strings.ToLower("i-o.Debug.")); ok != "" {
t.Errorf("expected o-o.Debug. to be non-debug")
}
}

View file

@ -3,6 +3,7 @@ package root
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -14,6 +15,8 @@ import (
) )
func TestRoot(t *testing.T) { func TestRoot(t *testing.T) {
log.SetOutput(ioutil.Discard)
// Predefined error substrings // Predefined error substrings
parseErrContent := "Parse error:" parseErrContent := "Parse error:"
unableToAccessErrContent := "Unable to access root path" unableToAccessErrContent := "Unable to access root path"