Commit b52c3418 authored by Yong Tang's avatar Yong Tang Committed by GitHub

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: default avatarYong Tang <yong.tang.github@outlook.com>

* Add test cases for NSID plugin
Signed-off-by: default avatarYong Tang <yong.tang.github@outlook.com>

* Generate code for NSID plugin
Signed-off-by: default avatarYong Tang <yong.tang.github@outlook.com>

* Use hostname as the default (as with bind), and remove unneeded copy
Signed-off-by: default avatarYong Tang <yong.tang.github@outlook.com>

* Add README.md
Signed-off-by: default avatarYong Tang <yong.tang.github@outlook.com>
parent a04eeb9c
...@@ -12,6 +12,7 @@ package dnsserver ...@@ -12,6 +12,7 @@ package dnsserver
var directives = []string{ var directives = []string{
"tls", "tls",
"nsid",
"root", "root",
"bind", "bind",
"debug", "debug",
......
...@@ -23,6 +23,7 @@ import ( ...@@ -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"
......
...@@ -20,6 +20,7 @@ ...@@ -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
......
# 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
}
~~
// 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" }
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)
}
}
}
}
}
}
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
}
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)
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment