aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/dnsserver/zdirectives.go1
-rw-r--r--core/zplugin.go1
-rw-r--r--plugin.cfg1
-rw-r--r--plugin/nsid/README.md27
-rw-r--r--plugin/nsid/nsid.go54
-rw-r--r--plugin/nsid/nsid_test.go73
-rw-r--r--plugin/nsid/setup.go48
-rw-r--r--plugin/nsid/setup_test.go58
8 files changed, 263 insertions, 0 deletions
diff --git a/core/dnsserver/zdirectives.go b/core/dnsserver/zdirectives.go
index c8079204b..d7874f17b 100644
--- a/core/dnsserver/zdirectives.go
+++ b/core/dnsserver/zdirectives.go
@@ -12,6 +12,7 @@ package dnsserver
var directives = []string{
"tls",
+ "nsid",
"root",
"bind",
"debug",
diff --git a/core/zplugin.go b/core/zplugin.go
index 7ec05e543..30539b500 100644
--- a/core/zplugin.go
+++ b/core/zplugin.go
@@ -23,6 +23,7 @@ import (
_ "github.com/coredns/coredns/plugin/loadbalance"
_ "github.com/coredns/coredns/plugin/log"
_ "github.com/coredns/coredns/plugin/metrics"
+ _ "github.com/coredns/coredns/plugin/nsid"
_ "github.com/coredns/coredns/plugin/pprof"
_ "github.com/coredns/coredns/plugin/proxy"
_ "github.com/coredns/coredns/plugin/reverse"
diff --git a/plugin.cfg b/plugin.cfg
index cad019926..21881bf5c 100644
--- a/plugin.cfg
+++ b/plugin.cfg
@@ -20,6 +20,7 @@
# log:log
tls:tls
+nsid:nsid
root:root
bind:bind
debug:debug
diff --git a/plugin/nsid/README.md b/plugin/nsid/README.md
new file mode 100644
index 000000000..3a0805d5d
--- /dev/null
+++ b/plugin/nsid/README.md
@@ -0,0 +1,27 @@
+# nsid
+
+*nsid* add an identifier of this server to each reply.
+
+This plugin implements RFC 5001 and adds an EDNS0 OPT resource record to replies that uniquely
+identifies the server. This can be useful in anycast setups to see which server was responsible for
+generating the reply and for debugging.
+
+## Syntax
+
+~~ txt
+nsid [DATA]
+~~
+
+**DATA** is the string to use in the nsid record.
+
+If **DATA** is not given, the host's name is used.
+
+## Examples
+
+Enable nsid:
+
+~~ corefile
+. {
+ nsid
+}
+~~
diff --git a/plugin/nsid/nsid.go b/plugin/nsid/nsid.go
new file mode 100644
index 000000000..728c14bdd
--- /dev/null
+++ b/plugin/nsid/nsid.go
@@ -0,0 +1,54 @@
+// Package nsid implements NSID protocol
+package nsid
+
+import (
+ "encoding/hex"
+
+ "github.com/coredns/coredns/plugin"
+
+ "github.com/miekg/dns"
+ "golang.org/x/net/context"
+)
+
+// Nsid plugin
+type Nsid struct {
+ Next plugin.Handler
+ Data string
+}
+
+// ResponseWriter is a response writer that adds NSID response
+type ResponseWriter struct {
+ dns.ResponseWriter
+ Data string
+}
+
+// ServeDNS implements the plugin.Handler interface.
+func (n Nsid) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
+ if option := r.IsEdns0(); option != nil {
+ for _, o := range option.Option {
+ if _, ok := o.(*dns.EDNS0_NSID); ok {
+ nw := &ResponseWriter{ResponseWriter: w, Data: n.Data}
+ return plugin.NextOrFailure(n.Name(), n.Next, ctx, nw, r)
+
+ }
+ }
+ }
+ return plugin.NextOrFailure(n.Name(), n.Next, ctx, w, r)
+}
+
+// WriteMsg implements the dns.ResponseWriter interface.
+func (w *ResponseWriter) WriteMsg(res *dns.Msg) error {
+ if option := res.IsEdns0(); option != nil {
+ for _, o := range option.Option {
+ if e, ok := o.(*dns.EDNS0_NSID); ok {
+ e.Code = dns.EDNS0NSID
+ e.Nsid = hex.EncodeToString([]byte(w.Data))
+ }
+ }
+ }
+ returned := w.ResponseWriter.WriteMsg(res)
+ return returned
+}
+
+// Name implements the Handler interface.
+func (n Nsid) Name() string { return "nsid" }
diff --git a/plugin/nsid/nsid_test.go b/plugin/nsid/nsid_test.go
new file mode 100644
index 000000000..143d40cba
--- /dev/null
+++ b/plugin/nsid/nsid_test.go
@@ -0,0 +1,73 @@
+package nsid
+
+import (
+ "encoding/hex"
+ "testing"
+
+ "github.com/coredns/coredns/plugin"
+ "github.com/coredns/coredns/plugin/pkg/dnstest"
+ "github.com/coredns/coredns/plugin/test"
+ "github.com/coredns/coredns/plugin/whoami"
+
+ "github.com/miekg/dns"
+ "golang.org/x/net/context"
+)
+
+func TestNsid(t *testing.T) {
+ em := Nsid{
+ Data: "NSID",
+ }
+
+ tests := []struct {
+ next plugin.Handler
+ qname string
+ qtype uint16
+ expectedCode int
+ expectedReply string
+ expectedErr error
+ }{
+ {
+ next: whoami.Whoami{},
+ qname: ".",
+ expectedCode: dns.RcodeSuccess,
+ expectedReply: hex.EncodeToString([]byte("NSID")),
+ expectedErr: nil,
+ },
+ }
+
+ ctx := context.TODO()
+
+ for i, tc := range tests {
+ req := new(dns.Msg)
+ if tc.qtype == 0 {
+ tc.qtype = dns.TypeA
+ }
+ req.SetQuestion(dns.Fqdn(tc.qname), tc.qtype)
+ req.Question[0].Qclass = dns.ClassINET
+
+ req.SetEdns0(4096, false)
+ option := req.Extra[0].(*dns.OPT)
+ option.Option = append(option.Option, &dns.EDNS0_NSID{Code: dns.EDNS0NSID, Nsid: ""})
+ em.Next = tc.next
+
+ rec := dnstest.NewRecorder(&test.ResponseWriter{})
+ code, err := em.ServeDNS(ctx, rec, req)
+
+ if err != tc.expectedErr {
+ t.Errorf("Test %d: Expected error %v, but got %v", i, tc.expectedErr, err)
+ }
+ if code != int(tc.expectedCode) {
+ t.Errorf("Test %d: Expected status code %d, but got %d", i, tc.expectedCode, code)
+ }
+ if tc.expectedReply != "" {
+ for _, extra := range rec.Msg.Extra {
+ if option, ok := extra.(*dns.OPT); ok {
+ e := option.Option[0].(*dns.EDNS0_NSID)
+ if e.Nsid != tc.expectedReply {
+ t.Errorf("Test %d: Expected answer %s, but got %s", i, tc.expectedReply, e.Nsid)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/plugin/nsid/setup.go b/plugin/nsid/setup.go
new file mode 100644
index 000000000..104bdfd9a
--- /dev/null
+++ b/plugin/nsid/setup.go
@@ -0,0 +1,48 @@
+package nsid
+
+import (
+ "os"
+ "strings"
+
+ "github.com/coredns/coredns/core/dnsserver"
+ "github.com/coredns/coredns/plugin"
+
+ "github.com/mholt/caddy"
+)
+
+func init() {
+ caddy.RegisterPlugin("nsid", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+func setup(c *caddy.Controller) error {
+ nsid, err := nsidParse(c)
+ if err != nil {
+ return plugin.Error("nsid", err)
+ }
+
+ dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
+ return Nsid{Next: next, Data: nsid}
+ })
+
+ return nil
+}
+
+func nsidParse(c *caddy.Controller) (string, error) {
+ // Use hostname as the default
+ nsid, err := os.Hostname()
+ if err != nil {
+ nsid = "localhost"
+ }
+ for c.Next() {
+ args := c.RemainingArgs()
+ if len(args) == 0 {
+ return nsid, nil
+ }
+ nsid = strings.Join(args, " ")
+ return nsid, nil
+ }
+ return nsid, nil
+}
diff --git a/plugin/nsid/setup_test.go b/plugin/nsid/setup_test.go
new file mode 100644
index 000000000..71ed90d0a
--- /dev/null
+++ b/plugin/nsid/setup_test.go
@@ -0,0 +1,58 @@
+package nsid
+
+import (
+ "os"
+ "strings"
+ "testing"
+
+ "github.com/mholt/caddy"
+)
+
+func TestSetupNsid(t *testing.T) {
+ defaultNsid, err := os.Hostname()
+ if err != nil {
+ defaultNsid = "localhost"
+ }
+ tests := []struct {
+ input string
+ shouldErr bool
+ expectedData string
+ expectedErrContent string // substring from the expected error. Empty for positive cases.
+ }{
+ {
+ `nsid`, false, defaultNsid, "",
+ },
+ {
+ `nsid "ps0"`, false, "ps0", "",
+ },
+ {
+ `nsid "worker1"`, false, "worker1", "",
+ },
+ {
+ `nsid "tf 2"`, false, "tf 2", "",
+ },
+ }
+
+ for i, test := range tests {
+ c := caddy.NewTestController("dns", test.input)
+ nsid, err := nsidParse(c)
+
+ if test.shouldErr && err == nil {
+ t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)
+ }
+
+ if err != nil {
+ if !test.shouldErr {
+ t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err)
+ }
+
+ if !strings.Contains(err.Error(), test.expectedErrContent) {
+ t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input)
+ }
+ }
+
+ if !test.shouldErr && nsid != test.expectedData {
+ t.Errorf("Nsid not correctly set for input %s. Expected: %s, actual: %s", test.input, test.expectedData, nsid)
+ }
+ }
+}