aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/dnsserver/zdirectives.go1
-rw-r--r--core/plugin/zplugin.go1
-rw-r--r--plugin.cfg1
-rw-r--r--plugin/loop/OWNERS6
-rw-r--r--plugin/loop/README.md40
-rw-r--r--plugin/loop/log_test.go5
-rw-r--r--plugin/loop/loop.go90
-rw-r--r--plugin/loop/loop_test.go11
-rw-r--r--plugin/loop/setup.go89
-rw-r--r--plugin/loop/setup_test.go19
-rw-r--r--plugin/pkg/log/plugin.go7
-rw-r--r--plugin/whoami/OWNERS2
12 files changed, 272 insertions, 0 deletions
diff --git a/core/dnsserver/zdirectives.go b/core/dnsserver/zdirectives.go
index e2eed2117..280c03144 100644
--- a/core/dnsserver/zdirectives.go
+++ b/core/dnsserver/zdirectives.go
@@ -39,6 +39,7 @@ var Directives = []string{
"auto",
"secondary",
"etcd",
+ "loop",
"forward",
"proxy",
"erratic",
diff --git a/core/plugin/zplugin.go b/core/plugin/zplugin.go
index 80a3edddf..397438663 100644
--- a/core/plugin/zplugin.go
+++ b/core/plugin/zplugin.go
@@ -23,6 +23,7 @@ import (
_ "github.com/coredns/coredns/plugin/kubernetes"
_ "github.com/coredns/coredns/plugin/loadbalance"
_ "github.com/coredns/coredns/plugin/log"
+ _ "github.com/coredns/coredns/plugin/loop"
_ "github.com/coredns/coredns/plugin/metadata"
_ "github.com/coredns/coredns/plugin/metrics"
_ "github.com/coredns/coredns/plugin/nsid"
diff --git a/plugin.cfg b/plugin.cfg
index 646aee92d..6a8ad0ecd 100644
--- a/plugin.cfg
+++ b/plugin.cfg
@@ -48,6 +48,7 @@ file:file
auto:auto
secondary:secondary
etcd:etcd
+loop:loop
forward:forward
proxy:proxy
erratic:erratic
diff --git a/plugin/loop/OWNERS b/plugin/loop/OWNERS
new file mode 100644
index 000000000..3a4ef23a1
--- /dev/null
+++ b/plugin/loop/OWNERS
@@ -0,0 +1,6 @@
+reviewers:
+ - miekg
+ - chrisohaver
+approvers:
+ - miekg
+ - chrisohaver
diff --git a/plugin/loop/README.md b/plugin/loop/README.md
new file mode 100644
index 000000000..0b02a5158
--- /dev/null
+++ b/plugin/loop/README.md
@@ -0,0 +1,40 @@
+# loop
+
+## Name
+
+*loop* - detect forwarding loops and halt the server.
+
+## Description
+
+The *loop* plugin will send a random query to ourselves and will then keep track of how many times
+we see it. If we see it more than twice, we assume CoreDNS is looping and we halt the process.
+
+The plugin will try to send the query for up to 30 seconds. This is done to give CoreDNS enough time
+to start up. Once a query has been successfully sent *loop* disables itself to prevent a query of
+death.
+
+The query send is `<random number>.<random number>.zone` with type set to HINFO.
+
+## Syntax
+
+~~~ txt
+loop
+~~~
+
+## Examples
+
+Start a server on the default port and load the *loop* and *forward* plugins. The *forward* plugin
+forwards to it self.
+
+~~~ txt
+. {
+ loop
+ forward . 127.0.0.1
+}
+~~~
+
+After CoreDNS has started it stops the process while logging:
+
+~~~ txt
+plugin/loop: Seen "HINFO IN 5577006791947779410.8674665223082153551." more than twice, loop detected
+~~~
diff --git a/plugin/loop/log_test.go b/plugin/loop/log_test.go
new file mode 100644
index 000000000..882b5c846
--- /dev/null
+++ b/plugin/loop/log_test.go
@@ -0,0 +1,5 @@
+package loop
+
+import clog "github.com/coredns/coredns/plugin/pkg/log"
+
+func init() { clog.Discard() }
diff --git a/plugin/loop/loop.go b/plugin/loop/loop.go
new file mode 100644
index 000000000..56e039c9c
--- /dev/null
+++ b/plugin/loop/loop.go
@@ -0,0 +1,90 @@
+package loop
+
+import (
+ "context"
+ "sync"
+
+ "github.com/coredns/coredns/plugin"
+ clog "github.com/coredns/coredns/plugin/pkg/log"
+ "github.com/coredns/coredns/request"
+
+ "github.com/miekg/dns"
+)
+
+var log = clog.NewWithPlugin("loop")
+
+// Loop is a plugin that implements loop detection by sending a "random" query.
+type Loop struct {
+ Next plugin.Handler
+
+ zone string
+ qname string
+
+ sync.RWMutex
+ i int
+ off bool
+}
+
+// New returns a new initialized Loop.
+func New(zone string) *Loop { return &Loop{zone: zone, qname: qname(zone)} }
+
+// ServeDNS implements the plugin.Handler interface.
+func (l *Loop) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
+ if r.Question[0].Qtype != dns.TypeHINFO {
+ return plugin.NextOrFailure(l.Name(), l.Next, ctx, w, r)
+ }
+ if l.disabled() {
+ return plugin.NextOrFailure(l.Name(), l.Next, ctx, w, r)
+ }
+
+ state := request.Request{W: w, Req: r}
+
+ zone := plugin.Zones([]string{l.zone}).Matches(state.Name())
+ if zone == "" {
+ return plugin.NextOrFailure(l.Name(), l.Next, ctx, w, r)
+ }
+
+ if state.Name() == l.qname {
+ l.inc()
+ }
+
+ if l.seen() > 2 {
+ log.Fatalf("Seen \"HINFO IN %s\" more than twice, loop detected", l.qname)
+ }
+
+ return plugin.NextOrFailure(l.Name(), l.Next, ctx, w, r)
+}
+
+// Name implements the plugin.Handler interface.
+func (l *Loop) Name() string { return "loop" }
+
+func (l *Loop) exchange(addr string) (*dns.Msg, error) {
+ m := new(dns.Msg)
+ m.SetQuestion(l.qname, dns.TypeHINFO)
+
+ return dns.Exchange(m, addr)
+}
+
+func (l *Loop) seen() int {
+ l.RLock()
+ defer l.RUnlock()
+ return l.i
+}
+
+func (l *Loop) inc() {
+ l.Lock()
+ defer l.Unlock()
+ l.i++
+}
+
+func (l *Loop) setDisabled() {
+ l.Lock()
+ defer l.Unlock()
+ l.off = true
+}
+
+func (l *Loop) disabled() bool {
+ l.RLock()
+ defer l.RUnlock()
+ return l.off
+}
diff --git a/plugin/loop/loop_test.go b/plugin/loop/loop_test.go
new file mode 100644
index 000000000..e7a4b06bb
--- /dev/null
+++ b/plugin/loop/loop_test.go
@@ -0,0 +1,11 @@
+package loop
+
+import "testing"
+
+func TestLoop(t *testing.T) {
+ l := New(".")
+ l.inc()
+ if l.seen() != 1 {
+ t.Errorf("Failed to inc loop, expected %d, got %d", 1, l.seen())
+ }
+}
diff --git a/plugin/loop/setup.go b/plugin/loop/setup.go
new file mode 100644
index 000000000..415a8db2f
--- /dev/null
+++ b/plugin/loop/setup.go
@@ -0,0 +1,89 @@
+package loop
+
+import (
+ "math/rand"
+ "net"
+ "strconv"
+ "time"
+
+ "github.com/coredns/coredns/core/dnsserver"
+ "github.com/coredns/coredns/plugin"
+ "github.com/coredns/coredns/plugin/pkg/dnsutil"
+
+ "github.com/mholt/caddy"
+)
+
+func init() {
+ caddy.RegisterPlugin("loop", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func setup(c *caddy.Controller) error {
+ l, err := parse(c)
+ if err != nil {
+ return plugin.Error("loop", err)
+ }
+
+ dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
+ l.Next = next
+ return l
+ })
+
+ // Send query to ourselves and see if it end up with us again.
+ c.OnStartup(func() error {
+ // Another Go function, otherwise we block startup and can't send the packet.
+ go func() {
+ deadline := time.Now().Add(30 * time.Second)
+ conf := dnsserver.GetConfig(c)
+
+ for time.Now().Before(deadline) {
+ lh := conf.ListenHosts[0]
+ addr := net.JoinHostPort(lh, conf.Port)
+ if _, err := l.exchange(addr); err != nil {
+ time.Sleep(1 * time.Second)
+ continue
+ }
+
+ go func() {
+ time.Sleep(2 * time.Second)
+ l.setDisabled()
+ }()
+ }
+ l.setDisabled()
+ }()
+ return nil
+ })
+
+ return nil
+}
+
+func parse(c *caddy.Controller) (*Loop, error) {
+ i := 0
+ zone := "."
+ for c.Next() {
+ if i > 0 {
+ return nil, plugin.ErrOnce
+ }
+ i++
+ if c.NextArg() {
+ return nil, c.ArgErr()
+ }
+
+ if len(c.ServerBlockKeys) > 0 {
+ zone = plugin.Host(c.ServerBlockKeys[0]).Normalize()
+ }
+ }
+ return New(zone), nil
+}
+
+// qname returns a random name. <rand.Int()>.<rand.Int().<zone>.
+func qname(zone string) string {
+ l1 := strconv.Itoa(r.Int())
+ l2 := strconv.Itoa(r.Int())
+
+ return dnsutil.Join([]string{l1, l2, zone})
+}
+
+var r = rand.New(rand.NewSource(time.Now().UnixNano()))
diff --git a/plugin/loop/setup_test.go b/plugin/loop/setup_test.go
new file mode 100644
index 000000000..2a3c6846f
--- /dev/null
+++ b/plugin/loop/setup_test.go
@@ -0,0 +1,19 @@
+package loop
+
+import (
+ "testing"
+
+ "github.com/mholt/caddy"
+)
+
+func TestSetup(t *testing.T) {
+ c := caddy.NewTestController("dns", `loop`)
+ if err := setup(c); err != nil {
+ t.Fatalf("Expected no errors, but got: %v", err)
+ }
+
+ c = caddy.NewTestController("dns", `loop argument`)
+ if err := setup(c); err == nil {
+ t.Fatal("Expected errors, but got none")
+ }
+}
diff --git a/plugin/pkg/log/plugin.go b/plugin/pkg/log/plugin.go
index 354c19d3f..1df302609 100644
--- a/plugin/pkg/log/plugin.go
+++ b/plugin/pkg/log/plugin.go
@@ -3,6 +3,7 @@ package log
import (
"fmt"
golog "log"
+ "os"
)
// P is a logger that includes the plugin doing the logging.
@@ -58,4 +59,10 @@ func (p P) Error(v ...interface{}) { p.log(err, v...) }
// Errorf logs as log.Errorf.
func (p P) Errorf(format string, v ...interface{}) { p.logf(err, format, v...) }
+// Fatal logs as log.Fatal and calls os.Exit(1).
+func (p P) Fatal(v ...interface{}) { p.log(fatal, v...); os.Exit(1) }
+
+// Fatalf logs as log.Fatalf and calls os.Exit(1).
+func (p P) Fatalf(format string, v ...interface{}) { p.logf(fatal, format, v...); os.Exit(1) }
+
func pFormat(s string) string { return "plugin/" + s + ": " }
diff --git a/plugin/whoami/OWNERS b/plugin/whoami/OWNERS
index eee46f686..3a4ef23a1 100644
--- a/plugin/whoami/OWNERS
+++ b/plugin/whoami/OWNERS
@@ -1,4 +1,6 @@
reviewers:
- miekg
+ - chrisohaver
approvers:
- miekg
+ - chrisohaver