Add NSID plugin support for CoreDNS (#1273)

* Add NSID plugin support for CoreDNS

This fix adds NSID plugin support for CoreDNS, as was proposed
in 1256.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

* Add test cases for NSID plugin

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

* Generate code for NSID plugin

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

* Use hostname as the default (as with bind), and remove unneeded copy

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

* Add README.md

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
This commit is contained in:
Yong Tang 2017-12-04 08:28:27 -08:00 committed by GitHub
parent a04eeb9c73
commit b52c3418b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 263 additions and 0 deletions

View file

@ -12,6 +12,7 @@ package dnsserver
var directives = []string{ var directives = []string{
"tls", "tls",
"nsid",
"root", "root",
"bind", "bind",
"debug", "debug",

View file

@ -23,6 +23,7 @@ import (
_ "github.com/coredns/coredns/plugin/loadbalance" _ "github.com/coredns/coredns/plugin/loadbalance"
_ "github.com/coredns/coredns/plugin/log" _ "github.com/coredns/coredns/plugin/log"
_ "github.com/coredns/coredns/plugin/metrics" _ "github.com/coredns/coredns/plugin/metrics"
_ "github.com/coredns/coredns/plugin/nsid"
_ "github.com/coredns/coredns/plugin/pprof" _ "github.com/coredns/coredns/plugin/pprof"
_ "github.com/coredns/coredns/plugin/proxy" _ "github.com/coredns/coredns/plugin/proxy"
_ "github.com/coredns/coredns/plugin/reverse" _ "github.com/coredns/coredns/plugin/reverse"

View file

@ -20,6 +20,7 @@
# log:log # log:log
tls:tls tls:tls
nsid:nsid
root:root root:root
bind:bind bind:bind
debug:debug debug:debug

27
plugin/nsid/README.md Normal file
View file

@ -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
}
~~

54
plugin/nsid/nsid.go Normal file
View file

@ -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" }

73
plugin/nsid/nsid_test.go Normal file
View file

@ -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)
}
}
}
}
}
}

48
plugin/nsid/setup.go Normal file
View file

@ -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
}

58
plugin/nsid/setup_test.go Normal file
View file

@ -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)
}
}
}