middleware/whoami: add (#264)
Add a new middleware that tells you who you are; IP, port and transport is echoed back. Also some various cleanup and documentation improvements while at it: * ResponseWriter: improve the documentation of these helper functions. * And add an NextHandler for use in tests. Make chaos_test.go and * whoam_test.go use it.
This commit is contained in:
parent
ed907d3327
commit
30fd224504
10 changed files with 219 additions and 10 deletions
|
@ -21,6 +21,7 @@ import (
|
||||||
_ "github.com/miekg/coredns/middleware/proxy"
|
_ "github.com/miekg/coredns/middleware/proxy"
|
||||||
_ "github.com/miekg/coredns/middleware/rewrite"
|
_ "github.com/miekg/coredns/middleware/rewrite"
|
||||||
_ "github.com/miekg/coredns/middleware/secondary"
|
_ "github.com/miekg/coredns/middleware/secondary"
|
||||||
|
_ "github.com/miekg/coredns/middleware/whoami"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Quiet mode will not show any informative output on initialization.
|
// Quiet mode will not show any informative output on initialization.
|
||||||
|
|
|
@ -26,21 +26,21 @@ func TestChaos(t *testing.T) {
|
||||||
expectedErr error
|
expectedErr error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
next: genHandler(dns.RcodeSuccess, nil),
|
next: test.NextHandler(dns.RcodeSuccess, nil),
|
||||||
qname: "version.bind",
|
qname: "version.bind",
|
||||||
expectedCode: dns.RcodeSuccess,
|
expectedCode: dns.RcodeSuccess,
|
||||||
expectedReply: version,
|
expectedReply: version,
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
next: genHandler(dns.RcodeSuccess, nil),
|
next: test.NextHandler(dns.RcodeSuccess, nil),
|
||||||
qname: "authors.bind",
|
qname: "authors.bind",
|
||||||
expectedCode: dns.RcodeSuccess,
|
expectedCode: dns.RcodeSuccess,
|
||||||
expectedReply: "Miek Gieben",
|
expectedReply: "Miek Gieben",
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
next: genHandler(dns.RcodeSuccess, nil),
|
next: test.NextHandler(dns.RcodeSuccess, nil),
|
||||||
qname: "authors.bind",
|
qname: "authors.bind",
|
||||||
qtype: dns.TypeSRV,
|
qtype: dns.TypeSRV,
|
||||||
expectedCode: dns.RcodeSuccess,
|
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"
|
const version = "CoreDNS-001"
|
||||||
|
|
|
@ -19,9 +19,13 @@ type (
|
||||||
//
|
//
|
||||||
// If ServeDNS writes to the response body, it should return a status
|
// If ServeDNS writes to the response body, it should return a status
|
||||||
// code. If the status code is not one of the following:
|
// code. If the status code is not one of the following:
|
||||||
|
//
|
||||||
// * SERVFAIL (dns.RcodeServerFailure)
|
// * SERVFAIL (dns.RcodeServerFailure)
|
||||||
|
//
|
||||||
// * REFUSED (dns.RecodeRefused)
|
// * REFUSED (dns.RecodeRefused)
|
||||||
|
//
|
||||||
// * FORMERR (dns.RcodeFormatError)
|
// * FORMERR (dns.RcodeFormatError)
|
||||||
|
//
|
||||||
// * NOTIMP (dns.RcodeNotImplemented)
|
// * NOTIMP (dns.RcodeNotImplemented)
|
||||||
//
|
//
|
||||||
// CoreDNS assumes *no* reply has yet been written. All other response
|
// CoreDNS assumes *no* reply has yet been written. All other response
|
||||||
|
|
|
@ -224,6 +224,7 @@ func Section(t *testing.T, tc Case, sect Sect, rr []dns.RR) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrorHanlder returns a Handler that returns ServerFailure error when called.
|
||||||
func ErrorHandler() Handler {
|
func ErrorHandler() Handler {
|
||||||
return HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
return HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||||
m := new(dns.Msg)
|
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 (
|
type (
|
||||||
// HandlerFunc is a convenience type like dns.HandlerFunc, except
|
// HandlerFunc is a convenience type like dns.HandlerFunc, except
|
||||||
// ServeDNS returns an rcode and an error.
|
// ServeDNS returns an rcode and an error.
|
||||||
|
|
|
@ -6,14 +6,19 @@ import (
|
||||||
"github.com/miekg/dns"
|
"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{}
|
type ResponseWriter struct{}
|
||||||
|
|
||||||
|
// LocalAddr returns the local address, always 127.0.0.1:53 (UDP).
|
||||||
func (t *ResponseWriter) LocalAddr() net.Addr {
|
func (t *ResponseWriter) LocalAddr() net.Addr {
|
||||||
ip := net.ParseIP("127.0.0.1")
|
ip := net.ParseIP("127.0.0.1")
|
||||||
port := 53
|
port := 53
|
||||||
return &net.UDPAddr{IP: ip, Port: port, Zone: ""}
|
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 {
|
func (t *ResponseWriter) RemoteAddr() net.Addr {
|
||||||
ip := net.ParseIP("10.240.0.1")
|
ip := net.ParseIP("10.240.0.1")
|
||||||
port := 40212
|
port := 40212
|
||||||
|
|
38
middleware/whoami/README.md
Normal file
38
middleware/whoami/README.md
Normal file
|
@ -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
|
||||||
|
._<transport>.qname. 0 IN SRV 0 0 <port> .
|
||||||
|
~~~
|
||||||
|
|
||||||
|
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
|
||||||
|
~~~
|
28
middleware/whoami/setup.go
Normal file
28
middleware/whoami/setup.go
Normal file
|
@ -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
|
||||||
|
}
|
19
middleware/whoami/setup_test.go
Normal file
19
middleware/whoami/setup_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
50
middleware/whoami/whoami.go
Normal file
50
middleware/whoami/whoami.go
Normal file
|
@ -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
|
||||||
|
}
|
62
middleware/whoami/whoami_test.go
Normal file
62
middleware/whoami/whoami_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue