diff --git a/core/coredns.go b/core/coredns.go index 186c59612..369230492 100644 --- a/core/coredns.go +++ b/core/coredns.go @@ -21,6 +21,7 @@ import ( _ "github.com/miekg/coredns/middleware/proxy" _ "github.com/miekg/coredns/middleware/rewrite" _ "github.com/miekg/coredns/middleware/secondary" + _ "github.com/miekg/coredns/middleware/whoami" ) // Quiet mode will not show any informative output on initialization. diff --git a/middleware/chaos/chaos_test.go b/middleware/chaos/chaos_test.go index 6a3261754..333083187 100644 --- a/middleware/chaos/chaos_test.go +++ b/middleware/chaos/chaos_test.go @@ -26,21 +26,21 @@ func TestChaos(t *testing.T) { expectedErr error }{ { - next: genHandler(dns.RcodeSuccess, nil), + next: test.NextHandler(dns.RcodeSuccess, nil), qname: "version.bind", expectedCode: dns.RcodeSuccess, expectedReply: version, expectedErr: nil, }, { - next: genHandler(dns.RcodeSuccess, nil), + next: test.NextHandler(dns.RcodeSuccess, nil), qname: "authors.bind", expectedCode: dns.RcodeSuccess, expectedReply: "Miek Gieben", expectedErr: nil, }, { - next: genHandler(dns.RcodeSuccess, nil), + next: test.NextHandler(dns.RcodeSuccess, nil), qname: "authors.bind", qtype: dns.TypeSRV, expectedCode: dns.RcodeSuccess, @@ -77,10 +77,4 @@ func TestChaos(t *testing.T) { } } -func genHandler(rcode int, err error) middleware.Handler { - return middleware.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - return rcode, err - }) -} - const version = "CoreDNS-001" diff --git a/middleware/middleware.go b/middleware/middleware.go index 33990c1be..804209fa9 100644 --- a/middleware/middleware.go +++ b/middleware/middleware.go @@ -19,9 +19,13 @@ type ( // // If ServeDNS writes to the response body, it should return a status // code. If the status code is not one of the following: + // // * SERVFAIL (dns.RcodeServerFailure) + // // * REFUSED (dns.RecodeRefused) + // // * FORMERR (dns.RcodeFormatError) + // // * NOTIMP (dns.RcodeNotImplemented) // // CoreDNS assumes *no* reply has yet been written. All other response diff --git a/middleware/test/helpers.go b/middleware/test/helpers.go index 01a6f156b..8ed27261f 100644 --- a/middleware/test/helpers.go +++ b/middleware/test/helpers.go @@ -224,6 +224,7 @@ func Section(t *testing.T, tc Case, sect Sect, rr []dns.RR) bool { return true } +// ErrorHanlder returns a Handler that returns ServerFailure error when called. func ErrorHandler() Handler { return HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { m := new(dns.Msg) @@ -233,7 +234,14 @@ func ErrorHandler() Handler { }) } -// Copied here to prevent an import cycle. +// NextHandler returns a Handler that returns rcode and err. +func NextHandler(rcode int, err error) Handler { + return HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { + return rcode, err + }) +} + +// Copied here to prevent an import cycle, so that we can define to above handlers. type ( // HandlerFunc is a convenience type like dns.HandlerFunc, except // ServeDNS returns an rcode and an error. diff --git a/middleware/test/responsewriter.go b/middleware/test/responsewriter.go index fb70d7e8d..925dc7b35 100644 --- a/middleware/test/responsewriter.go +++ b/middleware/test/responsewriter.go @@ -6,14 +6,19 @@ import ( "github.com/miekg/dns" ) +// ResponseWriter is useful for writing tests. It uses some fixed values for the client. The +// remote will always be 10.240.0.1 and port 40212. The local address is always 127.0.0.1 and +// port 53. type ResponseWriter struct{} +// LocalAddr returns the local address, always 127.0.0.1:53 (UDP). func (t *ResponseWriter) LocalAddr() net.Addr { ip := net.ParseIP("127.0.0.1") port := 53 return &net.UDPAddr{IP: ip, Port: port, Zone: ""} } +// RemoteAddr returns the remote address, always 10.240.0.1:40212 (UDP). func (t *ResponseWriter) RemoteAddr() net.Addr { ip := net.ParseIP("10.240.0.1") port := 40212 diff --git a/middleware/whoami/README.md b/middleware/whoami/README.md new file mode 100644 index 000000000..4ebdac47f --- /dev/null +++ b/middleware/whoami/README.md @@ -0,0 +1,38 @@ +# whoami + +whoami returns your local IP address, port and transport used. Your local IP address is returned in +the additional section as either an A or AAAA record. + +The port and transport are included in the additional section as a SRV record, transport can be +"tcp" or "udp". + +~~~ txt +._.qname. 0 IN SRV 0 0 . +~~~ + +The *whoami* middleware will respond to every A or AAAA query, regardless of the query name. + +## Syntax + +~~~ txt +whoami +~~~ + +## Examples + +~~~ txt +.:53 { + whoami +} +~~~ + +When queried for "example.org A", CoreDNS will respond with: + +~~~ txt +;; QUESTION SECTION: +;example.org. IN A + +;; ADDITIONAL SECTION: +example.org. 0 IN A 10.240.0.1 +_udp.example.org. 0 IN SRV 0 0 40212 +~~~ diff --git a/middleware/whoami/setup.go b/middleware/whoami/setup.go new file mode 100644 index 000000000..ad9995085 --- /dev/null +++ b/middleware/whoami/setup.go @@ -0,0 +1,28 @@ +package whoami + +import ( + "github.com/miekg/coredns/core/dnsserver" + "github.com/miekg/coredns/middleware" + + "github.com/mholt/caddy" +) + +func init() { + caddy.RegisterPlugin("whoami", caddy.Plugin{ + ServerType: "dns", + Action: setupWhoami, + }) +} + +func setupWhoami(c *caddy.Controller) error { + c.Next() // 'whoami' + if c.NextArg() { + return middleware.Error("whoami", c.ArgErr()) + } + + dnsserver.GetConfig(c).AddMiddleware(func(next dnsserver.Handler) dnsserver.Handler { + return Whoami{Next: next} + }) + + return nil +} diff --git a/middleware/whoami/setup_test.go b/middleware/whoami/setup_test.go new file mode 100644 index 000000000..73db67d88 --- /dev/null +++ b/middleware/whoami/setup_test.go @@ -0,0 +1,19 @@ +package whoami + +import ( + "testing" + + "github.com/mholt/caddy" +) + +func TestSetupWhoami(t *testing.T) { + c := caddy.NewTestController("dns", `whoami`) + if err := setupWhoami(c); err != nil { + t.Fatalf("Expected no errors, but got: %v", err) + } + + c = caddy.NewTestController("dns", `whoami example.org`) + if err := setupWhoami(c); err == nil { + t.Fatalf("Expected errors, but got: %v", err) + } +} diff --git a/middleware/whoami/whoami.go b/middleware/whoami/whoami.go new file mode 100644 index 000000000..4117db35e --- /dev/null +++ b/middleware/whoami/whoami.go @@ -0,0 +1,50 @@ +package whoami + +import ( + "net" + "strconv" + + "github.com/miekg/coredns/middleware" + "github.com/miekg/coredns/request" + + "github.com/miekg/dns" + "golang.org/x/net/context" +) + +type Whoami struct { + Next middleware.Handler +} + +func (wh Whoami) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { + state := request.Request{W: w, Req: r} + + a := new(dns.Msg) + a.SetReply(r) + a.Compress = true + a.Authoritative = true + + ip := state.IP() + var rr dns.RR + + switch state.Family() { + case 1: + rr = new(dns.A) + rr.(*dns.A).Hdr = dns.RR_Header{Name: state.QName(), Rrtype: state.QType(), Class: state.QClass()} + rr.(*dns.A).A = net.ParseIP(ip).To4() + case 2: + rr = new(dns.AAAA) + rr.(*dns.AAAA).Hdr = dns.RR_Header{Name: state.QName(), Rrtype: state.QType(), Class: state.QClass()} + rr.(*dns.AAAA).AAAA = net.ParseIP(ip) + } + + srv := new(dns.SRV) + srv.Hdr = dns.RR_Header{Name: "_" + state.Proto() + "." + state.QName(), Rrtype: dns.TypeSRV, Class: state.QClass()} + port, _ := strconv.Atoi(state.Port()) + srv.Port = uint16(port) + + a.Extra = append(a.Extra, rr) + a.Extra = append(a.Extra, srv) + + w.WriteMsg(a) + return 0, nil +} diff --git a/middleware/whoami/whoami_test.go b/middleware/whoami/whoami_test.go new file mode 100644 index 000000000..a143185ef --- /dev/null +++ b/middleware/whoami/whoami_test.go @@ -0,0 +1,62 @@ +package whoami + +import ( + "testing" + + "github.com/miekg/coredns/middleware" + "github.com/miekg/coredns/middleware/pkg/dnsrecorder" + "github.com/miekg/coredns/middleware/test" + + "github.com/miekg/dns" + "golang.org/x/net/context" +) + +func TestWhoami(t *testing.T) { + wh := Whoami{} + + tests := []struct { + next middleware.Handler + qname string + qtype uint16 + expectedCode int + expectedReply []string // ownernames for the records in the additional section. + expectedErr error + }{ + { + next: test.NextHandler(dns.RcodeSuccess, nil), + qname: "example.org", + qtype: dns.TypeA, + expectedCode: dns.RcodeSuccess, + expectedReply: []string{"example.org.", "_udp.example.org."}, + expectedErr: nil, + }, + } + + ctx := context.TODO() + + for i, tc := range tests { + wh.Next = tc.next + req := new(dns.Msg) + req.SetQuestion(dns.Fqdn(tc.qname), tc.qtype) + + rec := dnsrecorder.New(&test.ResponseWriter{}) + code, err := wh.ServeDNS(ctx, rec, req) + + t.Logf("%s\n", rec.Msg) + + 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 len(tc.expectedReply) != 0 { + for i, expected := range tc.expectedReply { + actual := rec.Msg.Extra[i].Header().Name + if actual != expected { + t.Errorf("Test %d: Expected answer %s, but got %s", i, expected, actual) + } + } + } + } +}