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/rewrite"
|
||||
_ "github.com/miekg/coredns/middleware/secondary"
|
||||
_ "github.com/miekg/coredns/middleware/whoami"
|
||||
)
|
||||
|
||||
// Quiet mode will not show any informative output on initialization.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
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