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:
parent
a04eeb9c73
commit
b52c3418b2
8 changed files with 263 additions and 0 deletions
|
@ -12,6 +12,7 @@ package dnsserver
|
|||
|
||||
var directives = []string{
|
||||
"tls",
|
||||
"nsid",
|
||||
"root",
|
||||
"bind",
|
||||
"debug",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
# log:log
|
||||
|
||||
tls:tls
|
||||
nsid:nsid
|
||||
root:root
|
||||
bind:bind
|
||||
debug:debug
|
||||
|
|
27
plugin/nsid/README.md
Normal file
27
plugin/nsid/README.md
Normal 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
54
plugin/nsid/nsid.go
Normal 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
73
plugin/nsid/nsid_test.go
Normal 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
48
plugin/nsid/setup.go
Normal 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
58
plugin/nsid/setup_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue