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" | ||||
| 	"io" | ||||
|  | ||||
| 	"golang.org/x/net/context" | ||||
|  | ||||
| 	"github.com/coredns/coredns/middleware" | ||||
| 	"github.com/coredns/coredns/middleware/dnstap/msg" | ||||
| 	"github.com/coredns/coredns/middleware/dnstap/taprw" | ||||
|  | ||||
| 	tap "github.com/dnstap/golang-dnstap" | ||||
| 	"github.com/miekg/dns" | ||||
| 	"golang.org/x/net/context" | ||||
| ) | ||||
|  | ||||
| // Dnstap is the dnstap handler. | ||||
| type Dnstap struct { | ||||
| 	Next middleware.Handler | ||||
| 	Out  io.Writer | ||||
| 	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 { | ||||
| 	frame, err := msg.Marshal(m) | ||||
| 	if err != nil { | ||||
| @@ -29,15 +47,22 @@ func tapMessageTo(w io.Writer, m *tap.Message) error { | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // TapMessage implements Tapper. | ||||
| func (h Dnstap) TapMessage(m *tap.Message) error { | ||||
| 	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) { | ||||
| 	rw := &taprw.ResponseWriter{ResponseWriter: w, Taper: &h, Query: r, Pack: h.Pack} | ||||
| 	rw := &taprw.ResponseWriter{ResponseWriter: w, Tapper: &h, Query: r} | ||||
| 	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 { | ||||
| 		// ignore dnstap errors | ||||
| 		return code, err | ||||
| @@ -49,4 +74,6 @@ func (h Dnstap) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) | ||||
|  | ||||
| 	return code, nil | ||||
| } | ||||
|  | ||||
| // Name returns dnstap. | ||||
| func (h Dnstap) Name() string { return "dnstap" } | ||||
|   | ||||
| @@ -4,18 +4,39 @@ package msg | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/coredns/coredns/request" | ||||
|  | ||||
| 	tap "github.com/dnstap/golang-dnstap" | ||||
| 	"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. | ||||
| // It can be transformed into the actual Message using this package. | ||||
| type Data struct { | ||||
| 	Type        tap.Message_Type | ||||
| 	Packed      []byte | ||||
| 	SocketProto tap.SocketProtocol | ||||
| 	SocketFam   tap.SocketFamily | ||||
| @@ -24,8 +45,34 @@ type Data struct { | ||||
| 	TimeSec     uint64 | ||||
| } | ||||
|  | ||||
| func (d *Data) FromRequest(r request.Request) error { | ||||
| 	switch addr := r.W.RemoteAddr().(type) { | ||||
| // HostPort decodes into Data any string returned by dnsutil.ParseHostPortOrFile. | ||||
| 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: | ||||
| 		d.Address = addr.IP | ||||
| 		d.Port = uint32(addr.Port) | ||||
| @@ -47,6 +94,7 @@ func (d *Data) FromRequest(r request.Request) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Pack encodes the DNS message into Data. | ||||
| func (d *Data) Pack(m *dns.Msg) error { | ||||
| 	packed, err := m.Pack() | ||||
| 	if err != nil { | ||||
| @@ -56,15 +104,21 @@ func (d *Data) Pack(m *dns.Msg) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (d *Data) Epoch() { | ||||
| 	d.TimeSec = uint64(time.Now().Unix()) | ||||
| // Epoch returns the epoch time in seconds. | ||||
| 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 { | ||||
| 	d.Type = tap.Message_CLIENT_RESPONSE | ||||
| 	t := tap.Message_CLIENT_RESPONSE | ||||
| 	return &tap.Message{ | ||||
| 		Type:            &d.Type, | ||||
| 		Type:            &t, | ||||
| 		SocketFamily:    &d.SocketFam, | ||||
| 		SocketProtocol:  &d.SocketProto, | ||||
| 		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 { | ||||
| 	d.Type = tap.Message_CLIENT_QUERY | ||||
| 	t := tap.Message_CLIENT_QUERY | ||||
| 	return &tap.Message{ | ||||
| 		Type:           &d.Type, | ||||
| 		Type:           &t, | ||||
| 		SocketFamily:   &d.SocketFam, | ||||
| 		SocketProtocol: &d.SocketProto, | ||||
| 		QueryTimeSec:   &d.TimeSec, | ||||
| @@ -87,3 +141,29 @@ func (d *Data) ToClientQuery() *tap.Message { | ||||
| 		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) { | ||||
| 	d := Data{} | ||||
| 	if err := d.FromRequest(r); err != nil { | ||||
| 	if err := d.RemoteAddr(r.W.RemoteAddr()); err != nil { | ||||
| 		t.Fail() | ||||
| 		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) { | ||||
| 	data, err = proto.Marshal(wrap(m)) | ||||
| 	if err != nil { | ||||
|   | ||||
| @@ -6,79 +6,68 @@ import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/coredns/coredns/middleware/dnstap/msg" | ||||
| 	"github.com/coredns/coredns/request" | ||||
|  | ||||
| 	tap "github.com/dnstap/golang-dnstap" | ||||
| 	"github.com/miekg/dns" | ||||
| ) | ||||
|  | ||||
| type Taper interface { | ||||
| // Tapper is what ResponseWriter needs to log to dnstap. | ||||
| type Tapper interface { | ||||
| 	TapMessage(m *tap.Message) error | ||||
| 	TapBuilder() msg.Builder | ||||
| } | ||||
|  | ||||
| // ResponseWriter captures the client response and logs the query to dnstap. | ||||
| // Single request use. | ||||
| type ResponseWriter struct { | ||||
| 	queryData msg.Data | ||||
| 	queryEpoch uint64 | ||||
| 	Query      *dns.Msg | ||||
| 	dns.ResponseWriter | ||||
| 	Taper | ||||
| 	Pack bool | ||||
| 	Tapper | ||||
| 	err error | ||||
| } | ||||
|  | ||||
| // Check if a dnstap error occurred. | ||||
| // Set during ResponseWriter.Write. | ||||
| // DnstapError check if a dnstap error occurred during Write and returns it. | ||||
| func (w ResponseWriter) DnstapError() error { | ||||
| 	return w.err | ||||
| } | ||||
|  | ||||
| // To be called as soon as possible. | ||||
| // QueryEpoch sets the query epoch as reported by dnstap. | ||||
| 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. | ||||
| // Dnstap errors to be checked by DnstapError. | ||||
| func (w *ResponseWriter) WriteMsg(resp *dns.Msg) error { | ||||
| 	writeErr := w.ResponseWriter.WriteMsg(resp) | ||||
| // Dnstap errors are to be checked by DnstapError. | ||||
| func (w *ResponseWriter) WriteMsg(resp *dns.Msg) (writeErr error) { | ||||
| 	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) | ||||
| 		// don't forget to call DnstapError later | ||||
| 	} | ||||
|  | ||||
| 	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) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return writeErr | ||||
| } | ||||
| 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()) | ||||
| 	return | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import ( | ||||
| 	"errors" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/coredns/coredns/middleware/dnstap/msg" | ||||
| 	"github.com/coredns/coredns/middleware/dnstap/test" | ||||
| 	mwtest "github.com/coredns/coredns/middleware/test" | ||||
|  | ||||
| @@ -17,12 +18,15 @@ type TapFailer struct { | ||||
| func (TapFailer) TapMessage(*tap.Message) error { | ||||
| 	return errors.New("failed") | ||||
| } | ||||
| func (TapFailer) TapBuilder() msg.Builder { | ||||
| 	return msg.Builder{Full: true} | ||||
| } | ||||
|  | ||||
| func TestDnstapError(t *testing.T) { | ||||
| 	rw := ResponseWriter{ | ||||
| 		Query:          new(dns.Msg), | ||||
| 		ResponseWriter: &mwtest.ResponseWriter{}, | ||||
| 		Taper:          TapFailer{}, | ||||
| 		Tapper:         TapFailer{}, | ||||
| 	} | ||||
| 	if err := rw.WriteMsg(new(dns.Msg)); err != nil { | ||||
| 		t.Errorf("dnstap error during Write: %s", err) | ||||
| @@ -39,15 +43,15 @@ func testingMsg() (m *dns.Msg) { | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func TestClientResponse(t *testing.T) { | ||||
| 	trapper := test.TrapTaper{} | ||||
| func TestClientQueryResponse(t *testing.T) { | ||||
| 	trapper := test.TrapTapper{Full: true} | ||||
| 	m := testingMsg() | ||||
| 	rw := ResponseWriter{ | ||||
| 		Pack:           true, | ||||
| 		Taper:          &trapper, | ||||
| 		Query:          m, | ||||
| 		Tapper:         &trapper, | ||||
| 		ResponseWriter: &mwtest.ResponseWriter{}, | ||||
| 	} | ||||
| 	d := test.TestingData() | ||||
| 	m := testingMsg() | ||||
|  | ||||
| 	// will the wire-format msg be reported? | ||||
| 	bin, err := m.Pack() | ||||
| @@ -57,40 +61,22 @@ func TestClientResponse(t *testing.T) { | ||||
| 	} | ||||
| 	d.Packed = bin | ||||
|  | ||||
| 	if err := tapResponse(&rw, m); err != nil { | ||||
| 	if err := rw.WriteMsg(m); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 		return | ||||
| 	} | ||||
| 	want := d.ToClientResponse() | ||||
| 	if l := len(trapper.Trap); l != 1 { | ||||
| 	if l := len(trapper.Trap); l != 2 { | ||||
| 		t.Fatalf("%d msg trapped", l) | ||||
| 		return | ||||
| 	} | ||||
| 	want := d.ToClientQuery() | ||||
| 	have := trapper.Trap[0] | ||||
| 	if !test.MsgEqual(want, have) { | ||||
| 		t.Fatalf("want: %v\nhave: %v", want, have) | ||||
| 		t.Fatalf("query: want: %v\nhave: %v", want, have) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 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] | ||||
| 	want = d.ToClientResponse() | ||||
| 	have = trapper.Trap[1] | ||||
| 	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" | ||||
|  | ||||
| 	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) { | ||||
| 	d = &msg.Data{ | ||||
| 		Type:        tap.Message_CLIENT_RESPONSE, | ||||
| 		SocketFam:   tap.SocketFamily_INET, | ||||
| 		SocketProto: tap.SocketProtocol_UDP, | ||||
| 		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 { | ||||
| 	return reflect.DeepEqual(toComp(a), toComp(b)) | ||||
| } | ||||
|  | ||||
| type TrapTaper struct { | ||||
| // TrapTapper traps messages. | ||||
| type TrapTapper struct { | ||||
| 	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) | ||||
| 	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} | ||||
| } | ||||
|  | ||||
| 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) OnShutdown(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) | ||||
| 	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 | ||||
| 	OnShutdown(*Proxy) error | ||||
| } | ||||
|   | ||||
| @@ -112,6 +112,10 @@ func (g *google) exchangeJSON(addr, json string) ([]byte, error) { | ||||
| 	return buf, nil | ||||
| } | ||||
|  | ||||
| func (g *google) Transport() string { | ||||
| 	return "tcp" | ||||
| } | ||||
|  | ||||
| func (g *google) Protocol() string { return "https_google" } | ||||
|  | ||||
| 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 | ||||
| } | ||||
|  | ||||
| func (g *grpcClient) Transport() string { return "tcp" } | ||||
|  | ||||
| func (g *grpcClient) Protocol() string { return "grpc" } | ||||
|  | ||||
| func (g *grpcClient) OnShutdown(p *Proxy) error { | ||||
|   | ||||
| @@ -7,9 +7,12 @@ import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"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/request" | ||||
|  | ||||
| 	tap "github.com/dnstap/golang-dnstap" | ||||
| 	"github.com/miekg/dns" | ||||
| 	ot "github.com/opentracing/opentracing-go" | ||||
| 	"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) | ||||
| 			queryEpoch := msg.Epoch() | ||||
|  | ||||
| 			reply, backendErr := upstream.Exchanger().Exchange(ctx, host.Name, state) | ||||
|  | ||||
| 			respEpoch := msg.Epoch() | ||||
| 			atomic.AddInt64(&host.Conns, -1) | ||||
|  | ||||
| 			if child != nil { | ||||
| 				child.Finish() | ||||
| 			} | ||||
|  | ||||
| 			taperr := toDnstap(ctx, host.Name, upstream.Exchanger(), state, reply, | ||||
| 				queryEpoch, respEpoch) | ||||
|  | ||||
| 			if backendErr == nil { | ||||
| 				w.WriteMsg(reply) | ||||
|  | ||||
| 				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 | ||||
| 			if timeout == 0 { | ||||
| 				timeout = 10 * time.Second | ||||
| @@ -145,3 +154,40 @@ func (p Proxy) Name() string { return "proxy" } | ||||
|  | ||||
| // defaultTimeout is the default networking timeout for DNS requests. | ||||
| 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