diff --git a/core/dnsserver/config.go b/core/dnsserver/config.go index 942d43d58..c0f879a31 100644 --- a/core/dnsserver/config.go +++ b/core/dnsserver/config.go @@ -23,6 +23,9 @@ type Config struct { // First consumer is the file middleware to looks for zone files in this place. Root string + // Debug controls the panic/recover mechanism that is enabled by default. + Debug bool + // The transport we implement, normally just "dns" over TCP/UDP, but could be // DNS-over-TLS or DNS-over-gRPC. Transport string diff --git a/core/dnsserver/server.go b/core/dnsserver/server.go index 5bbbf9f9c..61ef6ac65 100644 --- a/core/dnsserver/server.go +++ b/core/dnsserver/server.go @@ -36,6 +36,7 @@ type Server struct { dnsWg sync.WaitGroup // used to wait on outstanding connections connTimeout time.Duration // the maximum duration of a graceful shutdown trace trace.Trace // the trace middleware for the server + debug bool // disable recover() } // NewServer returns a new CoreDNS server and compiles all middleware in to it. @@ -56,6 +57,9 @@ func NewServer(addr string, group []*Config) (*Server, error) { s.dnsWg.Add(1) for _, site := range group { + if site.Debug { + s.debug = true + } // set the config per zone s.zones[site.Zone] = site // compile custom middleware for everything @@ -166,13 +170,15 @@ func (s *Server) Address() string { return s.Addr } // defined in the request so that the correct zone // (configuration and middleware stack) will handle the request. func (s *Server) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) { - defer func() { - // In case the user doesn't enable error middleware, we still - // need to make sure that we stay alive up here - if rec := recover(); rec != nil { - DefaultErrorFunc(w, r, dns.RcodeServerFailure) - } - }() + if !s.debug { + defer func() { + // In case the user doesn't enable error middleware, we still + // need to make sure that we stay alive up here + if rec := recover(); rec != nil { + DefaultErrorFunc(w, r, dns.RcodeServerFailure) + } + }() + } if m, err := edns.Version(r); err != nil { // Wrong EDNS version, return at once. w.WriteMsg(m) diff --git a/core/dnsserver/zdirectives.go b/core/dnsserver/zdirectives.go index a794ce762..1af5d3c7e 100644 --- a/core/dnsserver/zdirectives.go +++ b/core/dnsserver/zdirectives.go @@ -14,6 +14,7 @@ var directives = []string{ "tls", "root", "bind", + "debug", "trace", "health", "pprof", diff --git a/core/zmiddleware.go b/core/zmiddleware.go index cd611333e..0496dcd0e 100644 --- a/core/zmiddleware.go +++ b/core/zmiddleware.go @@ -8,6 +8,7 @@ import ( _ "github.com/coredns/coredns/middleware/bind" _ "github.com/coredns/coredns/middleware/cache" _ "github.com/coredns/coredns/middleware/chaos" + _ "github.com/coredns/coredns/middleware/debug" _ "github.com/coredns/coredns/middleware/dnssec" _ "github.com/coredns/coredns/middleware/erratic" _ "github.com/coredns/coredns/middleware/errors" diff --git a/middleware.cfg b/middleware.cfg index 562557b73..00e6eba6c 100644 --- a/middleware.cfg +++ b/middleware.cfg @@ -22,26 +22,27 @@ 1:tls:tls 10:root:root 20:bind:bind -30:trace:trace -40:health:health -50:pprof:pprof -60:prometheus:metrics -70:errors:errors -80:log:log -90:chaos:chaos -100:cache:cache -110:rewrite:rewrite -120:loadbalance:loadbalance -130:dnssec:dnssec -140:reverse:reverse -150:hosts:hosts -160:kubernetes:kubernetes -170:file:file -180:auto:auto -190:secondary:secondary -200:etcd:etcd -210:proxy:proxy -220:whoami:whoami -230:erratic:erratic +30:debug:debug +40:trace:trace +50:health:health +60:pprof:pprof +70:prometheus:metrics +80:errors:errors +90:log:log +100:chaos:chaos +110:cache:cache +120:rewrite:rewrite +130:loadbalance:loadbalance +140:dnssec:dnssec +150:reverse:reverse +160:hosts:hosts +170:kubernetes:kubernetes +180:file:file +190:auto:auto +200:secondary:secondary +210:etcd:etcd +220:proxy:proxy +230:whoami:whoami +240:erratic:erratic 500:startup:github.com/mholt/caddy/startupshutdown 510:shutdown:github.com/mholt/caddy/startupshutdown diff --git a/middleware/debug/README.md b/middleware/debug/README.md new file mode 100644 index 000000000..b74d95612 --- /dev/null +++ b/middleware/debug/README.md @@ -0,0 +1,20 @@ +# debug + +*debug* disables the automatic recovery upon a CoreDNS crash so that you'll get a +nice stack trace. +Note that the *errors* middleware (if loaded) will also set a `recover` negating this setting. +The main use of *debug* is to help testing. + +## Syntax + +~~~ txt +debug +~~~ + +## Examples + +Disable CoreDNS' ability to recover from crashes: + +~~~ txt +debug +~~~ diff --git a/middleware/debug/debug.go b/middleware/debug/debug.go new file mode 100644 index 000000000..f92a27554 --- /dev/null +++ b/middleware/debug/debug.go @@ -0,0 +1,28 @@ +package debug + +import ( + "github.com/coredns/coredns/core/dnsserver" + "github.com/coredns/coredns/middleware" + + "github.com/mholt/caddy" +) + +func init() { + caddy.RegisterPlugin("debug", caddy.Plugin{ + ServerType: "dns", + Action: setup, + }) +} + +func setup(c *caddy.Controller) error { + config := dnsserver.GetConfig(c) + + for c.Next() { + if c.NextArg() { + return middleware.Error("debug", c.ArgErr()) + } + config.Debug = true + } + + return nil +} diff --git a/middleware/debug/debug_test.go b/middleware/debug/debug_test.go new file mode 100644 index 000000000..349b362f1 --- /dev/null +++ b/middleware/debug/debug_test.go @@ -0,0 +1,49 @@ +package debug + +import ( + "io/ioutil" + "log" + "testing" + + "github.com/coredns/coredns/core/dnsserver" + + "github.com/mholt/caddy" +) + +func TestDebug(t *testing.T) { + log.SetOutput(ioutil.Discard) + + tests := []struct { + input string + shouldErr bool + expectedDebug bool + }{ + // positive + { + `debug`, false, true, + }, + // negative + { + `debug off`, true, false, + }, + } + + for i, test := range tests { + c := caddy.NewTestController("dns", test.input) + err := setup(c) + cfg := dnsserver.GetConfig(c) + + if test.shouldErr && err == nil { + t.Fatalf("Test %d: Expected error but found %s for input %s", i, err, test.input) + } + + if err != nil { + if !test.shouldErr { + t.Fatalf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err) + } + } + if cfg.Debug != test.expectedDebug { + t.Fatalf("Test %d: Expected debug to be: %t, but got: %t, input: %s", i, test.expectedDebug, test.input) + } + } +}