mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-31 02:03:20 -04:00 
			
		
		
		
	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:
		| @@ -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) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user