aboutsummaryrefslogtreecommitdiff
path: root/middleware
diff options
context:
space:
mode:
authorGravatar varyoo <varyoo@users.noreply.github.com> 2017-09-01 12:41:41 +0200
committerGravatar Miek Gieben <miek@miek.nl> 2017-09-01 12:41:41 +0200
commitc5efd45720a4e91569ac70f0f8b8aeaa4576e508 (patch)
tree08570d14ba603d58942910b72f46a798f38c56fe /middleware
parent8f77566cdd47ce59f28366d6a5892f3bac58baba (diff)
downloadcoredns-c5efd45720a4e91569ac70f0f8b8aeaa4576e508.tar.gz
coredns-c5efd45720a4e91569ac70f0f8b8aeaa4576e508.tar.zst
coredns-c5efd45720a4e91569ac70f0f8b8aeaa4576e508.zip
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
Diffstat (limited to 'middleware')
-rw-r--r--middleware/dnstap/handler.go35
-rw-r--r--middleware/dnstap/msg/msg.go104
-rw-r--r--middleware/dnstap/msg/msg_test.go2
-rw-r--r--middleware/dnstap/msg/wrapper.go1
-rw-r--r--middleware/dnstap/taprw/writer.go77
-rw-r--r--middleware/dnstap/taprw/writer_test.go48
-rw-r--r--middleware/dnstap/test/helpers.go22
-rw-r--r--middleware/proxy/dns.go8
-rw-r--r--middleware/proxy/dnstap_test.go57
-rw-r--r--middleware/proxy/exchanger.go4
-rw-r--r--middleware/proxy/google.go4
-rw-r--r--middleware/proxy/grpc.go2
-rw-r--r--middleware/proxy/proxy.go48
13 files changed, 316 insertions, 96 deletions
diff --git a/middleware/dnstap/handler.go b/middleware/dnstap/handler.go
index 0cf281e69..eb2924be5 100644
--- a/middleware/dnstap/handler.go
+++ b/middleware/dnstap/handler.go
@@ -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" }
diff --git a/middleware/dnstap/msg/msg.go b/middleware/dnstap/msg/msg.go
index 97c7ec7cc..1d42ea3ba 100644
--- a/middleware/dnstap/msg/msg.go
+++ b/middleware/dnstap/msg/msg.go
@@ -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
}
+// Epoch returns the epoch time in seconds.
+func Epoch() uint64 {
+ return uint64(time.Now().Unix())
+}
+
+// Epoch sets the dnstap message epoch.
func (d *Data) Epoch() {
- d.TimeSec = uint64(time.Now().Unix())
+ d.TimeSec = Epoch()
}
-// Transform the data into a client response message.
+// 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,
+ }
+}
diff --git a/middleware/dnstap/msg/msg_test.go b/middleware/dnstap/msg/msg_test.go
index 6a54a9e8c..2f80a90cd 100644
--- a/middleware/dnstap/msg/msg_test.go
+++ b/middleware/dnstap/msg/msg_test.go
@@ -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
}
diff --git a/middleware/dnstap/msg/wrapper.go b/middleware/dnstap/msg/wrapper.go
index 0cb6a76c0..a74c604d8 100644
--- a/middleware/dnstap/msg/wrapper.go
+++ b/middleware/dnstap/msg/wrapper.go
@@ -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 {
diff --git a/middleware/dnstap/taprw/writer.go b/middleware/dnstap/taprw/writer.go
index 6f4af330f..99572afd9 100644
--- a/middleware/dnstap/taprw/writer.go
+++ b/middleware/dnstap/taprw/writer.go
@@ -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
- Query *dns.Msg
+ queryEpoch uint64
+ Query *dns.Msg
dns.ResponseWriter
- Taper
- Pack bool
- err error
+ 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
}
diff --git a/middleware/dnstap/taprw/writer_test.go b/middleware/dnstap/taprw/writer_test.go
index a19271e7c..426f1f580 100644
--- a/middleware/dnstap/taprw/writer_test.go
+++ b/middleware/dnstap/taprw/writer_test.go
@@ -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)
- }
-}
-
-func TestClientQuery(t *testing.T) {
- trapper := test.TrapTaper{}
- rw := ResponseWriter{
- Pack: false, // no binary this time
- Taper: &trapper,
- ResponseWriter: &mwtest.ResponseWriter{},
- Query: testingMsg(),
+ t.Fatalf("query: want: %v\nhave: %v", want, have)
}
- 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)
}
}
diff --git a/middleware/dnstap/test/helpers.go b/middleware/dnstap/test/helpers.go
index fba291dfe..46ba327ab 100644
--- a/middleware/dnstap/test/helpers.go
+++ b/middleware/dnstap/test/helpers.go
@@ -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}
+}
diff --git a/middleware/proxy/dns.go b/middleware/proxy/dns.go
index c1c0ad078..4d8038422 100644
--- a/middleware/proxy/dns.go
+++ b/middleware/proxy/dns.go
@@ -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 }
diff --git a/middleware/proxy/dnstap_test.go b/middleware/proxy/dnstap_test.go
new file mode 100644
index 000000000..b3c31c207
--- /dev/null
+++ b/middleware/proxy/dnstap_test.go
@@ -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)
+ }
+}
diff --git a/middleware/proxy/exchanger.go b/middleware/proxy/exchanger.go
index 28e1b1b11..b98a687e7 100644
--- a/middleware/proxy/exchanger.go
+++ b/middleware/proxy/exchanger.go
@@ -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
}
diff --git a/middleware/proxy/google.go b/middleware/proxy/google.go
index b71d0fb1b..7b215f517 100644
--- a/middleware/proxy/google.go
+++ b/middleware/proxy/google.go
@@ -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 {
diff --git a/middleware/proxy/grpc.go b/middleware/proxy/grpc.go
index 031869c60..8aabf0eb0 100644
--- a/middleware/proxy/grpc.go
+++ b/middleware/proxy/grpc.go
@@ -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 {
diff --git a/middleware/proxy/proxy.go b/middleware/proxy/proxy.go
index 7e662c42e..cef58f658 100644
--- a/middleware/proxy/proxy.go
+++ b/middleware/proxy/proxy.go
@@ -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
+}