middleware/proxy: dnstap (#786)
* experimental dnstap support into proxy * proxy reports dnstap errors * refactoring * add a message builder for less dnstap code * msg lint * context * proxy by DNS: dnstap comments * TapBuilder * resolves conflict * dnstap into ServeDNS * testing * more tests * `go lint` * doc update
This commit is contained in:
parent
8f77566cdd
commit
c5efd45720
13 changed files with 317 additions and 97 deletions
|
@ -4,22 +4,40 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/coredns/coredns/middleware"
|
"github.com/coredns/coredns/middleware"
|
||||||
"github.com/coredns/coredns/middleware/dnstap/msg"
|
"github.com/coredns/coredns/middleware/dnstap/msg"
|
||||||
"github.com/coredns/coredns/middleware/dnstap/taprw"
|
"github.com/coredns/coredns/middleware/dnstap/taprw"
|
||||||
|
|
||||||
tap "github.com/dnstap/golang-dnstap"
|
tap "github.com/dnstap/golang-dnstap"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Dnstap is the dnstap handler.
|
||||||
type Dnstap struct {
|
type Dnstap struct {
|
||||||
Next middleware.Handler
|
Next middleware.Handler
|
||||||
Out io.Writer
|
Out io.Writer
|
||||||
Pack bool
|
Pack bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Tapper is implemented by the Context passed by the dnstap handler.
|
||||||
|
Tapper interface {
|
||||||
|
TapMessage(*tap.Message) error
|
||||||
|
TapBuilder() msg.Builder
|
||||||
|
}
|
||||||
|
tapContext struct {
|
||||||
|
context.Context
|
||||||
|
Dnstap
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// TapperFromContext will return a Tapper if the dnstap middleware is enabled.
|
||||||
|
func TapperFromContext(ctx context.Context) (t Tapper) {
|
||||||
|
t, _ = ctx.(Tapper)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func tapMessageTo(w io.Writer, m *tap.Message) error {
|
func tapMessageTo(w io.Writer, m *tap.Message) error {
|
||||||
frame, err := msg.Marshal(m)
|
frame, err := msg.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -29,15 +47,22 @@ func tapMessageTo(w io.Writer, m *tap.Message) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TapMessage implements Tapper.
|
||||||
func (h Dnstap) TapMessage(m *tap.Message) error {
|
func (h Dnstap) TapMessage(m *tap.Message) error {
|
||||||
return tapMessageTo(h.Out, m)
|
return tapMessageTo(h.Out, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TapBuilder implements Tapper.
|
||||||
|
func (h Dnstap) TapBuilder() msg.Builder {
|
||||||
|
return msg.Builder{Full: h.Pack}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeDNS logs the client query and response to dnstap and passes the dnstap Context.
|
||||||
func (h Dnstap) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
func (h Dnstap) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||||
rw := &taprw.ResponseWriter{ResponseWriter: w, Taper: &h, Query: r, Pack: h.Pack}
|
rw := &taprw.ResponseWriter{ResponseWriter: w, Tapper: &h, Query: r}
|
||||||
rw.QueryEpoch()
|
rw.QueryEpoch()
|
||||||
|
|
||||||
code, err := middleware.NextOrFailure(h.Name(), h.Next, ctx, rw, r)
|
code, err := middleware.NextOrFailure(h.Name(), h.Next, tapContext{ctx, h}, rw, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// ignore dnstap errors
|
// ignore dnstap errors
|
||||||
return code, err
|
return code, err
|
||||||
|
@ -49,4 +74,6 @@ func (h Dnstap) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
|
||||||
|
|
||||||
return code, nil
|
return code, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns dnstap.
|
||||||
func (h Dnstap) Name() string { return "dnstap" }
|
func (h Dnstap) Name() string { return "dnstap" }
|
||||||
|
|
|
@ -4,18 +4,39 @@ package msg
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coredns/coredns/request"
|
|
||||||
|
|
||||||
tap "github.com/dnstap/golang-dnstap"
|
tap "github.com/dnstap/golang-dnstap"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Builder helps to build Data by being aware of the dnstap middleware configuration.
|
||||||
|
type Builder struct {
|
||||||
|
Full bool
|
||||||
|
Data
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddrMsg parses the info of net.Addr and dns.Msg.
|
||||||
|
func (b *Builder) AddrMsg(a net.Addr, m *dns.Msg) (err error) {
|
||||||
|
err = b.RemoteAddr(a)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return b.Msg(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msg parses the info of dns.Msg.
|
||||||
|
func (b *Builder) Msg(m *dns.Msg) (err error) {
|
||||||
|
if b.Full {
|
||||||
|
err = b.Pack(m)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Data helps to build a dnstap Message.
|
// Data helps to build a dnstap Message.
|
||||||
// It can be transformed into the actual Message using this package.
|
// It can be transformed into the actual Message using this package.
|
||||||
type Data struct {
|
type Data struct {
|
||||||
Type tap.Message_Type
|
|
||||||
Packed []byte
|
Packed []byte
|
||||||
SocketProto tap.SocketProtocol
|
SocketProto tap.SocketProtocol
|
||||||
SocketFam tap.SocketFamily
|
SocketFam tap.SocketFamily
|
||||||
|
@ -24,8 +45,34 @@ type Data struct {
|
||||||
TimeSec uint64
|
TimeSec uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Data) FromRequest(r request.Request) error {
|
// HostPort decodes into Data any string returned by dnsutil.ParseHostPortOrFile.
|
||||||
switch addr := r.W.RemoteAddr().(type) {
|
func (d *Data) HostPort(addr string) error {
|
||||||
|
ip, port, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p, err := strconv.ParseUint(port, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.Port = uint32(p)
|
||||||
|
|
||||||
|
if ip := net.ParseIP(ip); ip != nil {
|
||||||
|
d.Address = []byte(ip)
|
||||||
|
if ip := ip.To4(); ip != nil {
|
||||||
|
d.SocketFam = tap.SocketFamily_INET
|
||||||
|
} else {
|
||||||
|
d.SocketFam = tap.SocketFamily_INET6
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return errors.New("not an ip address")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr parses the information about the remote address into Data.
|
||||||
|
func (d *Data) RemoteAddr(remote net.Addr) error {
|
||||||
|
switch addr := remote.(type) {
|
||||||
case *net.TCPAddr:
|
case *net.TCPAddr:
|
||||||
d.Address = addr.IP
|
d.Address = addr.IP
|
||||||
d.Port = uint32(addr.Port)
|
d.Port = uint32(addr.Port)
|
||||||
|
@ -47,6 +94,7 @@ func (d *Data) FromRequest(r request.Request) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pack encodes the DNS message into Data.
|
||||||
func (d *Data) Pack(m *dns.Msg) error {
|
func (d *Data) Pack(m *dns.Msg) error {
|
||||||
packed, err := m.Pack()
|
packed, err := m.Pack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -56,15 +104,21 @@ func (d *Data) Pack(m *dns.Msg) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Data) Epoch() {
|
// Epoch returns the epoch time in seconds.
|
||||||
d.TimeSec = uint64(time.Now().Unix())
|
func Epoch() uint64 {
|
||||||
|
return uint64(time.Now().Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform the data into a client response message.
|
// Epoch sets the dnstap message epoch.
|
||||||
|
func (d *Data) Epoch() {
|
||||||
|
d.TimeSec = Epoch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToClientResponse transforms Data into a client response message.
|
||||||
func (d *Data) ToClientResponse() *tap.Message {
|
func (d *Data) ToClientResponse() *tap.Message {
|
||||||
d.Type = tap.Message_CLIENT_RESPONSE
|
t := tap.Message_CLIENT_RESPONSE
|
||||||
return &tap.Message{
|
return &tap.Message{
|
||||||
Type: &d.Type,
|
Type: &t,
|
||||||
SocketFamily: &d.SocketFam,
|
SocketFamily: &d.SocketFam,
|
||||||
SocketProtocol: &d.SocketProto,
|
SocketProtocol: &d.SocketProto,
|
||||||
ResponseTimeSec: &d.TimeSec,
|
ResponseTimeSec: &d.TimeSec,
|
||||||
|
@ -74,11 +128,11 @@ func (d *Data) ToClientResponse() *tap.Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform the data into a client query message.
|
// ToClientQuery transforms Data into a client query message.
|
||||||
func (d *Data) ToClientQuery() *tap.Message {
|
func (d *Data) ToClientQuery() *tap.Message {
|
||||||
d.Type = tap.Message_CLIENT_QUERY
|
t := tap.Message_CLIENT_QUERY
|
||||||
return &tap.Message{
|
return &tap.Message{
|
||||||
Type: &d.Type,
|
Type: &t,
|
||||||
SocketFamily: &d.SocketFam,
|
SocketFamily: &d.SocketFam,
|
||||||
SocketProtocol: &d.SocketProto,
|
SocketProtocol: &d.SocketProto,
|
||||||
QueryTimeSec: &d.TimeSec,
|
QueryTimeSec: &d.TimeSec,
|
||||||
|
@ -87,3 +141,29 @@ func (d *Data) ToClientQuery() *tap.Message {
|
||||||
QueryPort: &d.Port,
|
QueryPort: &d.Port,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToOutsideQuery transforms the data into a forwarder or resolver query message.
|
||||||
|
func (d *Data) ToOutsideQuery(t tap.Message_Type) *tap.Message {
|
||||||
|
return &tap.Message{
|
||||||
|
Type: &t,
|
||||||
|
SocketFamily: &d.SocketFam,
|
||||||
|
SocketProtocol: &d.SocketProto,
|
||||||
|
QueryTimeSec: &d.TimeSec,
|
||||||
|
QueryMessage: d.Packed,
|
||||||
|
ResponseAddress: d.Address,
|
||||||
|
ResponsePort: &d.Port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToOutsideResponse transforms the data into a forwarder or resolver response message.
|
||||||
|
func (d *Data) ToOutsideResponse(t tap.Message_Type) *tap.Message {
|
||||||
|
return &tap.Message{
|
||||||
|
Type: &t,
|
||||||
|
SocketFamily: &d.SocketFam,
|
||||||
|
SocketProtocol: &d.SocketProto,
|
||||||
|
ResponseTimeSec: &d.TimeSec,
|
||||||
|
ResponseMessage: d.Packed,
|
||||||
|
ResponseAddress: d.Address,
|
||||||
|
ResponsePort: &d.Port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
|
|
||||||
func testRequest(t *testing.T, expected Data, r request.Request) {
|
func testRequest(t *testing.T, expected Data, r request.Request) {
|
||||||
d := Data{}
|
d := Data{}
|
||||||
if err := d.FromRequest(r); err != nil {
|
if err := d.RemoteAddr(r.W.RemoteAddr()); err != nil {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ func wrap(m *lib.Message) *lib.Dnstap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Marshal encodes the message to a binary dnstap payload.
|
||||||
func Marshal(m *lib.Message) (data []byte, err error) {
|
func Marshal(m *lib.Message) (data []byte, err error) {
|
||||||
data, err = proto.Marshal(wrap(m))
|
data, err = proto.Marshal(wrap(m))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -6,79 +6,68 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/coredns/coredns/middleware/dnstap/msg"
|
"github.com/coredns/coredns/middleware/dnstap/msg"
|
||||||
"github.com/coredns/coredns/request"
|
|
||||||
|
|
||||||
tap "github.com/dnstap/golang-dnstap"
|
tap "github.com/dnstap/golang-dnstap"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Taper interface {
|
// Tapper is what ResponseWriter needs to log to dnstap.
|
||||||
|
type Tapper interface {
|
||||||
TapMessage(m *tap.Message) error
|
TapMessage(m *tap.Message) error
|
||||||
|
TapBuilder() msg.Builder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResponseWriter captures the client response and logs the query to dnstap.
|
||||||
// Single request use.
|
// Single request use.
|
||||||
type ResponseWriter struct {
|
type ResponseWriter struct {
|
||||||
queryData msg.Data
|
queryEpoch uint64
|
||||||
Query *dns.Msg
|
Query *dns.Msg
|
||||||
dns.ResponseWriter
|
dns.ResponseWriter
|
||||||
Taper
|
Tapper
|
||||||
Pack bool
|
err error
|
||||||
err error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a dnstap error occurred.
|
// DnstapError check if a dnstap error occurred during Write and returns it.
|
||||||
// Set during ResponseWriter.Write.
|
|
||||||
func (w ResponseWriter) DnstapError() error {
|
func (w ResponseWriter) DnstapError() error {
|
||||||
return w.err
|
return w.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// To be called as soon as possible.
|
// QueryEpoch sets the query epoch as reported by dnstap.
|
||||||
func (w *ResponseWriter) QueryEpoch() {
|
func (w *ResponseWriter) QueryEpoch() {
|
||||||
w.queryData.Epoch()
|
w.queryEpoch = msg.Epoch()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write back the response to the client and THEN work on logging the request
|
// WriteMsg writes back the response to the client and THEN works on logging the request
|
||||||
// and response to dnstap.
|
// and response to dnstap.
|
||||||
// Dnstap errors to be checked by DnstapError.
|
// Dnstap errors are to be checked by DnstapError.
|
||||||
func (w *ResponseWriter) WriteMsg(resp *dns.Msg) error {
|
func (w *ResponseWriter) WriteMsg(resp *dns.Msg) (writeErr error) {
|
||||||
writeErr := w.ResponseWriter.WriteMsg(resp)
|
writeErr = w.ResponseWriter.WriteMsg(resp)
|
||||||
|
writeEpoch := msg.Epoch()
|
||||||
|
|
||||||
if err := tapQuery(w); err != nil {
|
b := w.TapBuilder()
|
||||||
|
b.TimeSec = w.queryEpoch
|
||||||
|
if err := func() (err error) {
|
||||||
|
err = b.AddrMsg(w.ResponseWriter.RemoteAddr(), w.Query)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return w.TapMessage(b.ToClientQuery())
|
||||||
|
}(); err != nil {
|
||||||
w.err = fmt.Errorf("client query: %s", err)
|
w.err = fmt.Errorf("client query: %s", err)
|
||||||
// don't forget to call DnstapError later
|
// don't forget to call DnstapError later
|
||||||
}
|
}
|
||||||
|
|
||||||
if writeErr == nil {
|
if writeErr == nil {
|
||||||
if err := tapResponse(w, resp); err != nil {
|
if err := func() (err error) {
|
||||||
|
b.TimeSec = writeEpoch
|
||||||
|
if err = b.Msg(resp); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return w.TapMessage(b.ToClientResponse())
|
||||||
|
}(); err != nil {
|
||||||
w.err = fmt.Errorf("client response: %s", err)
|
w.err = fmt.Errorf("client response: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeErr
|
return
|
||||||
}
|
|
||||||
func tapQuery(w *ResponseWriter) error {
|
|
||||||
req := request.Request{W: w.ResponseWriter, Req: w.Query}
|
|
||||||
if err := w.queryData.FromRequest(req); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if w.Pack {
|
|
||||||
if err := w.queryData.Pack(w.Query); err != nil {
|
|
||||||
return fmt.Errorf("pack: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return w.Taper.TapMessage(w.queryData.ToClientQuery())
|
|
||||||
}
|
|
||||||
func tapResponse(w *ResponseWriter, resp *dns.Msg) error {
|
|
||||||
d := &msg.Data{}
|
|
||||||
d.Epoch()
|
|
||||||
req := request.Request{W: w, Req: resp}
|
|
||||||
if err := d.FromRequest(req); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if w.Pack {
|
|
||||||
if err := d.Pack(resp); err != nil {
|
|
||||||
return fmt.Errorf("pack: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return w.Taper.TapMessage(d.ToClientResponse())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coredns/coredns/middleware/dnstap/msg"
|
||||||
"github.com/coredns/coredns/middleware/dnstap/test"
|
"github.com/coredns/coredns/middleware/dnstap/test"
|
||||||
mwtest "github.com/coredns/coredns/middleware/test"
|
mwtest "github.com/coredns/coredns/middleware/test"
|
||||||
|
|
||||||
|
@ -17,12 +18,15 @@ type TapFailer struct {
|
||||||
func (TapFailer) TapMessage(*tap.Message) error {
|
func (TapFailer) TapMessage(*tap.Message) error {
|
||||||
return errors.New("failed")
|
return errors.New("failed")
|
||||||
}
|
}
|
||||||
|
func (TapFailer) TapBuilder() msg.Builder {
|
||||||
|
return msg.Builder{Full: true}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDnstapError(t *testing.T) {
|
func TestDnstapError(t *testing.T) {
|
||||||
rw := ResponseWriter{
|
rw := ResponseWriter{
|
||||||
Query: new(dns.Msg),
|
Query: new(dns.Msg),
|
||||||
ResponseWriter: &mwtest.ResponseWriter{},
|
ResponseWriter: &mwtest.ResponseWriter{},
|
||||||
Taper: TapFailer{},
|
Tapper: TapFailer{},
|
||||||
}
|
}
|
||||||
if err := rw.WriteMsg(new(dns.Msg)); err != nil {
|
if err := rw.WriteMsg(new(dns.Msg)); err != nil {
|
||||||
t.Errorf("dnstap error during Write: %s", err)
|
t.Errorf("dnstap error during Write: %s", err)
|
||||||
|
@ -39,15 +43,15 @@ func testingMsg() (m *dns.Msg) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientResponse(t *testing.T) {
|
func TestClientQueryResponse(t *testing.T) {
|
||||||
trapper := test.TrapTaper{}
|
trapper := test.TrapTapper{Full: true}
|
||||||
|
m := testingMsg()
|
||||||
rw := ResponseWriter{
|
rw := ResponseWriter{
|
||||||
Pack: true,
|
Query: m,
|
||||||
Taper: &trapper,
|
Tapper: &trapper,
|
||||||
ResponseWriter: &mwtest.ResponseWriter{},
|
ResponseWriter: &mwtest.ResponseWriter{},
|
||||||
}
|
}
|
||||||
d := test.TestingData()
|
d := test.TestingData()
|
||||||
m := testingMsg()
|
|
||||||
|
|
||||||
// will the wire-format msg be reported?
|
// will the wire-format msg be reported?
|
||||||
bin, err := m.Pack()
|
bin, err := m.Pack()
|
||||||
|
@ -57,40 +61,22 @@ func TestClientResponse(t *testing.T) {
|
||||||
}
|
}
|
||||||
d.Packed = bin
|
d.Packed = bin
|
||||||
|
|
||||||
if err := tapResponse(&rw, m); err != nil {
|
if err := rw.WriteMsg(m); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
want := d.ToClientResponse()
|
if l := len(trapper.Trap); l != 2 {
|
||||||
if l := len(trapper.Trap); l != 1 {
|
|
||||||
t.Fatalf("%d msg trapped", l)
|
t.Fatalf("%d msg trapped", l)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
want := d.ToClientQuery()
|
||||||
have := trapper.Trap[0]
|
have := trapper.Trap[0]
|
||||||
if !test.MsgEqual(want, have) {
|
if !test.MsgEqual(want, have) {
|
||||||
t.Fatalf("want: %v\nhave: %v", want, have)
|
t.Fatalf("query: want: %v\nhave: %v", want, have)
|
||||||
}
|
}
|
||||||
}
|
want = d.ToClientResponse()
|
||||||
|
have = trapper.Trap[1]
|
||||||
func TestClientQuery(t *testing.T) {
|
|
||||||
trapper := test.TrapTaper{}
|
|
||||||
rw := ResponseWriter{
|
|
||||||
Pack: false, // no binary this time
|
|
||||||
Taper: &trapper,
|
|
||||||
ResponseWriter: &mwtest.ResponseWriter{},
|
|
||||||
Query: testingMsg(),
|
|
||||||
}
|
|
||||||
if err := tapQuery(&rw); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
want := test.TestingData().ToClientQuery()
|
|
||||||
if l := len(trapper.Trap); l != 1 {
|
|
||||||
t.Fatalf("%d msg trapped", l)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
have := trapper.Trap[0]
|
|
||||||
if !test.MsgEqual(want, have) {
|
if !test.MsgEqual(want, have) {
|
||||||
t.Fatalf("want: %v\nhave: %v", want, have)
|
t.Fatalf("response: want: %v\nhave: %v", want, have)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,18 @@ import (
|
||||||
"github.com/coredns/coredns/middleware/dnstap/msg"
|
"github.com/coredns/coredns/middleware/dnstap/msg"
|
||||||
|
|
||||||
tap "github.com/dnstap/golang-dnstap"
|
tap "github.com/dnstap/golang-dnstap"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Context is a message trap.
|
||||||
|
type Context struct {
|
||||||
|
context.Context
|
||||||
|
TrapTapper
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestingData returns the Data matching coredns/test.ResponseWriter.
|
||||||
func TestingData() (d *msg.Data) {
|
func TestingData() (d *msg.Data) {
|
||||||
d = &msg.Data{
|
d = &msg.Data{
|
||||||
Type: tap.Message_CLIENT_RESPONSE,
|
|
||||||
SocketFam: tap.SocketFamily_INET,
|
SocketFam: tap.SocketFamily_INET,
|
||||||
SocketProto: tap.SocketProtocol_UDP,
|
SocketProto: tap.SocketProtocol_UDP,
|
||||||
Address: net.ParseIP("10.240.0.1"),
|
Address: net.ParseIP("10.240.0.1"),
|
||||||
|
@ -50,15 +57,24 @@ func toComp(m *tap.Message) comp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MsgEqual compares two dnstap messages ignoring timestamps.
|
||||||
func MsgEqual(a, b *tap.Message) bool {
|
func MsgEqual(a, b *tap.Message) bool {
|
||||||
return reflect.DeepEqual(toComp(a), toComp(b))
|
return reflect.DeepEqual(toComp(a), toComp(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
type TrapTaper struct {
|
// TrapTapper traps messages.
|
||||||
|
type TrapTapper struct {
|
||||||
Trap []*tap.Message
|
Trap []*tap.Message
|
||||||
|
Full bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TrapTaper) TapMessage(m *tap.Message) error {
|
// TapMessage adds the message to the trap.
|
||||||
|
func (t *TrapTapper) TapMessage(m *tap.Message) error {
|
||||||
t.Trap = append(t.Trap, m)
|
t.Trap = append(t.Trap, m)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TapBuilder returns a test msg.Builder.
|
||||||
|
func (t *TrapTapper) TapBuilder() msg.Builder {
|
||||||
|
return msg.Builder{Full: t.Full}
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,14 @@ func newDNSExWithOption(opt Options) *dnsEx {
|
||||||
return &dnsEx{Timeout: defaultTimeout * time.Second, Options: opt}
|
return &dnsEx{Timeout: defaultTimeout * time.Second, Options: opt}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *dnsEx) Transport() string {
|
||||||
|
if d.Options.ForceTCP {
|
||||||
|
return "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
|
// The protocol will be determined by `state.Proto()` during Exchange.
|
||||||
|
return ""
|
||||||
|
}
|
||||||
func (d *dnsEx) Protocol() string { return "dns" }
|
func (d *dnsEx) Protocol() string { return "dns" }
|
||||||
func (d *dnsEx) OnShutdown(p *Proxy) error { return nil }
|
func (d *dnsEx) OnShutdown(p *Proxy) error { return nil }
|
||||||
func (d *dnsEx) OnStartup(p *Proxy) error { return nil }
|
func (d *dnsEx) OnStartup(p *Proxy) error { return nil }
|
||||||
|
|
57
middleware/proxy/dnstap_test.go
Normal file
57
middleware/proxy/dnstap_test.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coredns/coredns/middleware/dnstap/msg"
|
||||||
|
"github.com/coredns/coredns/middleware/dnstap/test"
|
||||||
|
mwtest "github.com/coredns/coredns/middleware/test"
|
||||||
|
"github.com/coredns/coredns/request"
|
||||||
|
|
||||||
|
tap "github.com/dnstap/golang-dnstap"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testCase(t *testing.T, ex Exchanger, q, r *dns.Msg, datq, datr *msg.Data) {
|
||||||
|
tapq := datq.ToOutsideQuery(tap.Message_FORWARDER_QUERY)
|
||||||
|
tapr := datr.ToOutsideResponse(tap.Message_FORWARDER_RESPONSE)
|
||||||
|
ctx := test.Context{}
|
||||||
|
err := toDnstap(&ctx, "10.240.0.1:40212", ex,
|
||||||
|
request.Request{W: &mwtest.ResponseWriter{}, Req: q}, r, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(ctx.Trap) != 2 {
|
||||||
|
t.Fatalf("messages: %d", len(ctx.Trap))
|
||||||
|
}
|
||||||
|
if !test.MsgEqual(ctx.Trap[0], tapq) {
|
||||||
|
t.Errorf("want: %v\nhave: %v", tapq, ctx.Trap[0])
|
||||||
|
}
|
||||||
|
if !test.MsgEqual(ctx.Trap[1], tapr) {
|
||||||
|
t.Errorf("want: %v\nhave: %v", tapr, ctx.Trap[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDnstap(t *testing.T) {
|
||||||
|
q := mwtest.Case{Qname: "example.org", Qtype: dns.TypeA}.Msg()
|
||||||
|
r := mwtest.Case{
|
||||||
|
Qname: "example.org.", Qtype: dns.TypeA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
mwtest.A("example.org. 3600 IN A 10.0.0.1"),
|
||||||
|
},
|
||||||
|
}.Msg()
|
||||||
|
tapq, tapr := test.TestingData(), test.TestingData()
|
||||||
|
testCase(t, newDNSEx(), q, r, tapq, tapr)
|
||||||
|
tapq.SocketProto = tap.SocketProtocol_TCP
|
||||||
|
tapr.SocketProto = tap.SocketProtocol_TCP
|
||||||
|
testCase(t, newDNSExWithOption(Options{ForceTCP: true}), q, r, tapq, tapr)
|
||||||
|
testCase(t, newGoogle("", []string{"8.8.8.8:53", "8.8.4.4:53"}), q, r, tapq, tapr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoDnstap(t *testing.T) {
|
||||||
|
err := toDnstap(context.TODO(), "", nil, request.Request{}, nil, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,10 @@ type Exchanger interface {
|
||||||
Exchange(ctx context.Context, addr string, state request.Request) (*dns.Msg, error)
|
Exchange(ctx context.Context, addr string, state request.Request) (*dns.Msg, error)
|
||||||
Protocol() string
|
Protocol() string
|
||||||
|
|
||||||
|
// Transport returns the only transport protocol used by this Exchanger or "".
|
||||||
|
// If the return value is "", Exchange must use `state.Proto()`.
|
||||||
|
Transport() string
|
||||||
|
|
||||||
OnStartup(*Proxy) error
|
OnStartup(*Proxy) error
|
||||||
OnShutdown(*Proxy) error
|
OnShutdown(*Proxy) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,10 @@ func (g *google) exchangeJSON(addr, json string) ([]byte, error) {
|
||||||
return buf, nil
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *google) Transport() string {
|
||||||
|
return "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
func (g *google) Protocol() string { return "https_google" }
|
func (g *google) Protocol() string { return "https_google" }
|
||||||
|
|
||||||
func (g *google) OnShutdown(p *Proxy) error {
|
func (g *google) OnShutdown(p *Proxy) error {
|
||||||
|
|
|
@ -54,6 +54,8 @@ func (g *grpcClient) Exchange(ctx context.Context, addr string, state request.Re
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *grpcClient) Transport() string { return "tcp" }
|
||||||
|
|
||||||
func (g *grpcClient) Protocol() string { return "grpc" }
|
func (g *grpcClient) Protocol() string { return "grpc" }
|
||||||
|
|
||||||
func (g *grpcClient) OnShutdown(p *Proxy) error {
|
func (g *grpcClient) OnShutdown(p *Proxy) error {
|
||||||
|
|
|
@ -7,9 +7,12 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coredns/coredns/middleware"
|
"github.com/coredns/coredns/middleware"
|
||||||
|
"github.com/coredns/coredns/middleware/dnstap"
|
||||||
|
"github.com/coredns/coredns/middleware/dnstap/msg"
|
||||||
"github.com/coredns/coredns/middleware/pkg/healthcheck"
|
"github.com/coredns/coredns/middleware/pkg/healthcheck"
|
||||||
"github.com/coredns/coredns/request"
|
"github.com/coredns/coredns/request"
|
||||||
|
|
||||||
|
tap "github.com/dnstap/golang-dnstap"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
ot "github.com/opentracing/opentracing-go"
|
ot "github.com/opentracing/opentracing-go"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -85,22 +88,28 @@ func (p Proxy) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic.AddInt64(&host.Conns, 1)
|
atomic.AddInt64(&host.Conns, 1)
|
||||||
|
queryEpoch := msg.Epoch()
|
||||||
|
|
||||||
reply, backendErr := upstream.Exchanger().Exchange(ctx, host.Name, state)
|
reply, backendErr := upstream.Exchanger().Exchange(ctx, host.Name, state)
|
||||||
|
|
||||||
|
respEpoch := msg.Epoch()
|
||||||
atomic.AddInt64(&host.Conns, -1)
|
atomic.AddInt64(&host.Conns, -1)
|
||||||
|
|
||||||
if child != nil {
|
if child != nil {
|
||||||
child.Finish()
|
child.Finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
taperr := toDnstap(ctx, host.Name, upstream.Exchanger(), state, reply,
|
||||||
|
queryEpoch, respEpoch)
|
||||||
|
|
||||||
if backendErr == nil {
|
if backendErr == nil {
|
||||||
w.WriteMsg(reply)
|
w.WriteMsg(reply)
|
||||||
|
|
||||||
RequestDuration.WithLabelValues(state.Proto(), upstream.Exchanger().Protocol(), upstream.From()).Observe(float64(time.Since(start) / time.Millisecond))
|
RequestDuration.WithLabelValues(state.Proto(), upstream.Exchanger().Protocol(), upstream.From()).Observe(float64(time.Since(start) / time.Millisecond))
|
||||||
|
|
||||||
return 0, nil
|
return 0, taperr
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout := host.FailTimeout
|
timeout := host.FailTimeout
|
||||||
if timeout == 0 {
|
if timeout == 0 {
|
||||||
timeout = 10 * time.Second
|
timeout = 10 * time.Second
|
||||||
|
@ -145,3 +154,40 @@ func (p Proxy) Name() string { return "proxy" }
|
||||||
|
|
||||||
// defaultTimeout is the default networking timeout for DNS requests.
|
// defaultTimeout is the default networking timeout for DNS requests.
|
||||||
const defaultTimeout = 5 * time.Second
|
const defaultTimeout = 5 * time.Second
|
||||||
|
|
||||||
|
func toDnstap(ctx context.Context, host string, ex Exchanger, state request.Request, reply *dns.Msg, queryEpoch, respEpoch uint64) (err error) {
|
||||||
|
if tapper := dnstap.TapperFromContext(ctx); tapper != nil {
|
||||||
|
// Query
|
||||||
|
b := tapper.TapBuilder()
|
||||||
|
b.TimeSec = queryEpoch
|
||||||
|
if err = b.HostPort(host); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t := ex.Transport()
|
||||||
|
if t == "" {
|
||||||
|
t = state.Proto()
|
||||||
|
}
|
||||||
|
if t == "tcp" {
|
||||||
|
b.SocketProto = tap.SocketProtocol_TCP
|
||||||
|
} else {
|
||||||
|
b.SocketProto = tap.SocketProtocol_UDP
|
||||||
|
}
|
||||||
|
if err = b.Msg(state.Req); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = tapper.TapMessage(b.ToOutsideQuery(tap.Message_FORWARDER_QUERY))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response
|
||||||
|
if reply != nil {
|
||||||
|
b.TimeSec = respEpoch
|
||||||
|
if err = b.Msg(reply); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = tapper.TapMessage(b.ToOutsideResponse(tap.Message_FORWARDER_RESPONSE))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue