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