plugin/dnstap: remove config struct (#4258)
* plugin/dnstap: remove config struct this struct is an uneeded intermidiate to get a dnstap it can be removed. Remove the dnstapio subpkg: it's also not needed. Make *many* functions and structs private now that we can. Signed-off-by: Miek Gieben <miek@miek.nl> * correct logging Signed-off-by: Miek Gieben <miek@miek.nl>
This commit is contained in:
parent
fb5efa203d
commit
123da4c844
8 changed files with 102 additions and 102 deletions
|
@ -18,7 +18,7 @@ Every message is sent to the socket as soon as it comes in, the *dnstap* plugin
|
||||||
dnstap SOCKET [full]
|
dnstap SOCKET [full]
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
* **SOCKET** is the socket path supplied to the dnstap command line tool.
|
* **SOCKET** is the socket (path) supplied to the dnstap command line tool.
|
||||||
* `full` to include the wire-format DNS message.
|
* `full` to include the wire-format DNS message.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
// Package dnstapio is a small wrapper around golang-framestream
|
package dnstap
|
||||||
package dnstapio
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
@ -10,12 +9,12 @@ import (
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Encoder wraps a fs.Encoder.
|
// encoder wraps a golang-framestream.Encoder.
|
||||||
type Encoder struct {
|
type encoder struct {
|
||||||
fs *fs.Encoder
|
fs *fs.Encoder
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEncoder(w io.Writer, timeout time.Duration) (*Encoder, error) {
|
func newEncoder(w io.Writer, timeout time.Duration) (*encoder, error) {
|
||||||
fs, err := fs.NewEncoder(w, &fs.EncoderOptions{
|
fs, err := fs.NewEncoder(w, &fs.EncoderOptions{
|
||||||
ContentType: []byte("protobuf:dnstap.Dnstap"),
|
ContentType: []byte("protobuf:dnstap.Dnstap"),
|
||||||
Bidirectional: true,
|
Bidirectional: true,
|
||||||
|
@ -24,18 +23,18 @@ func newEncoder(w io.Writer, timeout time.Duration) (*Encoder, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Encoder{fs}, nil
|
return &encoder{fs}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Encoder) writeMsg(msg *tap.Dnstap) error {
|
func (e *encoder) writeMsg(msg *tap.Dnstap) error {
|
||||||
buf, err := proto.Marshal(msg)
|
buf, err := proto.Marshal(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = e.fs.Write(buf) // n < len(buf) should return an error
|
_, err = e.fs.Write(buf) // n < len(buf) should return an error?
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Encoder) flush() error { return e.fs.Flush() }
|
func (e *encoder) flush() error { return e.fs.Flush() }
|
||||||
func (e *Encoder) close() error { return e.fs.Close() }
|
func (e *encoder) close() error { return e.fs.Close() }
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
"github.com/coredns/coredns/plugin/dnstap/dnstapio"
|
|
||||||
|
|
||||||
tap "github.com/dnstap/golang-dnstap"
|
tap "github.com/dnstap/golang-dnstap"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
@ -14,7 +13,7 @@ import (
|
||||||
// Dnstap is the dnstap handler.
|
// Dnstap is the dnstap handler.
|
||||||
type Dnstap struct {
|
type Dnstap struct {
|
||||||
Next plugin.Handler
|
Next plugin.Handler
|
||||||
io dnstapio.Tapper
|
io tapper
|
||||||
|
|
||||||
// IncludeRawMessage will include the raw DNS message into the dnstap messages if true.
|
// IncludeRawMessage will include the raw DNS message into the dnstap messages if true.
|
||||||
IncludeRawMessage bool
|
IncludeRawMessage bool
|
||||||
|
@ -31,8 +30,8 @@ func (h Dnstap) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
|
||||||
rw := &ResponseWriter{
|
rw := &ResponseWriter{
|
||||||
ResponseWriter: w,
|
ResponseWriter: w,
|
||||||
Dnstap: h,
|
Dnstap: h,
|
||||||
Query: r,
|
query: r,
|
||||||
QueryTime: time.Now(),
|
queryTime: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return plugin.NextOrFailure(h.Name(), h.Next, ctx, rw, r)
|
return plugin.NextOrFailure(h.Name(), h.Next, ctx, rw, r)
|
||||||
|
|
|
@ -1,26 +1,23 @@
|
||||||
package dnstapio
|
package dnstap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
clog "github.com/coredns/coredns/plugin/pkg/log"
|
|
||||||
|
|
||||||
tap "github.com/dnstap/golang-dnstap"
|
tap "github.com/dnstap/golang-dnstap"
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = clog.NewWithPlugin("dnstap")
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tcpWriteBufSize = 1024 * 1024 // there is no good explanation for why this number (see #xxx)
|
tcpWriteBufSize = 1024 * 1024 // there is no good explanation for why this number has this value.
|
||||||
queueSize = 10000 // see #xxxx
|
queueSize = 10000 // idem.
|
||||||
tcpTimeout = 4 * time.Second
|
|
||||||
flushTimeout = 1 * time.Second
|
tcpTimeout = 4 * time.Second
|
||||||
|
flushTimeout = 1 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tapper interface is used in testing to mock the Dnstap method.
|
// tapper interface is used in testing to mock the Dnstap method.
|
||||||
type Tapper interface {
|
type tapper interface {
|
||||||
Dnstap(tap.Dnstap)
|
Dnstap(tap.Dnstap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +26,7 @@ type dio struct {
|
||||||
endpoint string
|
endpoint string
|
||||||
proto string
|
proto string
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
enc *Encoder
|
enc *encoder
|
||||||
queue chan tap.Dnstap
|
queue chan tap.Dnstap
|
||||||
dropped uint32
|
dropped uint32
|
||||||
quit chan struct{}
|
quit chan struct{}
|
||||||
|
@ -37,8 +34,8 @@ type dio struct {
|
||||||
tcpTimeout time.Duration
|
tcpTimeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new and initialized pointer to a dio.
|
// newIO returns a new and initialized pointer to a dio.
|
||||||
func New(proto, endpoint string) *dio {
|
func newIO(proto, endpoint string) *dio {
|
||||||
return &dio{
|
return &dio{
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
proto: proto,
|
proto: proto,
|
||||||
|
@ -64,11 +61,10 @@ func (d *dio) dial() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect connects to the dnstap endpoint.
|
// Connect connects to the dnstap endpoint.
|
||||||
func (d *dio) Connect() {
|
func (d *dio) connect() error {
|
||||||
if err := d.dial(); err != nil {
|
err := d.dial()
|
||||||
log.Errorf("No connection to dnstap endpoint: %s", err)
|
|
||||||
}
|
|
||||||
go d.serve()
|
go d.serve()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dnstap enqueues the payload for log.
|
// Dnstap enqueues the payload for log.
|
||||||
|
@ -80,8 +76,8 @@ func (d *dio) Dnstap(payload tap.Dnstap) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close waits until the I/O routine is finished to return.
|
// close waits until the I/O routine is finished to return.
|
||||||
func (d *dio) Close() { close(d.quit) }
|
func (d *dio) close() { close(d.quit) }
|
||||||
|
|
||||||
func (d *dio) write(payload *tap.Dnstap) error {
|
func (d *dio) write(payload *tap.Dnstap) error {
|
||||||
if d.enc == nil {
|
if d.enc == nil {
|
|
@ -1,4 +1,4 @@
|
||||||
package dnstapio
|
package dnstap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
@ -14,7 +14,7 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
msgType = tap.Dnstap_MESSAGE
|
msgType = tap.Dnstap_MESSAGE
|
||||||
msg = tap.Dnstap{Type: &msgType}
|
tmsg = tap.Dnstap{Type: &msgType}
|
||||||
)
|
)
|
||||||
|
|
||||||
func accept(t *testing.T, l net.Listener, count int) {
|
func accept(t *testing.T, l net.Listener, count int) {
|
||||||
|
@ -42,14 +42,12 @@ func accept(t *testing.T, l net.Listener, count int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTransport(t *testing.T) {
|
func TestTransport(t *testing.T) {
|
||||||
|
|
||||||
transport := [2][2]string{
|
transport := [2][2]string{
|
||||||
{"tcp", ":0"},
|
{"tcp", ":0"},
|
||||||
{"unix", "dnstap.sock"},
|
{"unix", "dnstap.sock"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, param := range transport {
|
for _, param := range transport {
|
||||||
// Start TCP listener
|
|
||||||
l, err := reuseport.Listen(param[0], param[1])
|
l, err := reuseport.Listen(param[0], param[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Cannot start listener: %s", err)
|
t.Fatalf("Cannot start listener: %s", err)
|
||||||
|
@ -62,16 +60,16 @@ func TestTransport(t *testing.T) {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
dio := New(param[0], l.Addr().String())
|
dio := newIO(param[0], l.Addr().String())
|
||||||
dio.tcpTimeout = 10 * time.Millisecond
|
dio.tcpTimeout = 10 * time.Millisecond
|
||||||
dio.flushTimeout = 30 * time.Millisecond
|
dio.flushTimeout = 30 * time.Millisecond
|
||||||
dio.Connect()
|
dio.connect()
|
||||||
|
|
||||||
dio.Dnstap(msg)
|
dio.Dnstap(tmsg)
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
l.Close()
|
l.Close()
|
||||||
dio.Close()
|
dio.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,17 +89,17 @@ func TestRace(t *testing.T) {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
dio := New("tcp", l.Addr().String())
|
dio := newIO("tcp", l.Addr().String())
|
||||||
dio.tcpTimeout = 10 * time.Millisecond
|
dio.tcpTimeout = 10 * time.Millisecond
|
||||||
dio.flushTimeout = 30 * time.Millisecond
|
dio.flushTimeout = 30 * time.Millisecond
|
||||||
dio.Connect()
|
dio.connect()
|
||||||
defer dio.Close()
|
defer dio.close()
|
||||||
|
|
||||||
wg.Add(count)
|
wg.Add(count)
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
go func() {
|
go func() {
|
||||||
msg := tap.Dnstap_MESSAGE
|
tmsg := tap.Dnstap_MESSAGE
|
||||||
dio.Dnstap(tap.Dnstap{Type: &msg})
|
dio.Dnstap(tap.Dnstap{Type: &tmsg})
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -124,13 +122,13 @@ func TestReconnect(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
addr := l.Addr().String()
|
addr := l.Addr().String()
|
||||||
dio := New("tcp", addr)
|
dio := newIO("tcp", addr)
|
||||||
dio.tcpTimeout = 10 * time.Millisecond
|
dio.tcpTimeout = 10 * time.Millisecond
|
||||||
dio.flushTimeout = 30 * time.Millisecond
|
dio.flushTimeout = 30 * time.Millisecond
|
||||||
dio.Connect()
|
dio.connect()
|
||||||
defer dio.Close()
|
defer dio.close()
|
||||||
|
|
||||||
dio.Dnstap(msg)
|
dio.Dnstap(tmsg)
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
|
@ -151,7 +149,7 @@ func TestReconnect(t *testing.T) {
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
dio.Dnstap(msg)
|
dio.Dnstap(tmsg)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
|
@ -6,64 +6,62 @@ import (
|
||||||
"github.com/coredns/caddy"
|
"github.com/coredns/caddy"
|
||||||
"github.com/coredns/coredns/core/dnsserver"
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
"github.com/coredns/coredns/plugin/dnstap/dnstapio"
|
clog "github.com/coredns/coredns/plugin/pkg/log"
|
||||||
"github.com/coredns/coredns/plugin/pkg/parse"
|
"github.com/coredns/coredns/plugin/pkg/parse"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var log = clog.NewWithPlugin("dnstap")
|
||||||
|
|
||||||
func init() { plugin.Register("dnstap", setup) }
|
func init() { plugin.Register("dnstap", setup) }
|
||||||
|
|
||||||
type config struct {
|
func parseConfig(c *caddy.Controller) (Dnstap, error) {
|
||||||
proto string
|
c.Next() // directive name
|
||||||
target string
|
d := Dnstap{}
|
||||||
full bool
|
endpoint := ""
|
||||||
}
|
|
||||||
|
|
||||||
func parseConfig(d *caddy.Controller) (c config, err error) {
|
if !c.Args(&endpoint) {
|
||||||
d.Next() // directive name
|
return d, c.ArgErr()
|
||||||
|
|
||||||
if !d.Args(&c.target) {
|
|
||||||
return c, d.ArgErr()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(c.target, "tcp://") {
|
if strings.HasPrefix(endpoint, "tcp://") {
|
||||||
// remote IP endpoint
|
// remote IP endpoint
|
||||||
servers, err := parse.HostPortOrFile(c.target[6:])
|
servers, err := parse.HostPortOrFile(endpoint[6:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c, d.ArgErr()
|
return d, c.ArgErr()
|
||||||
}
|
}
|
||||||
c.target = servers[0]
|
dio := newIO("tcp", servers[0])
|
||||||
c.proto = "tcp"
|
d = Dnstap{io: dio}
|
||||||
} else {
|
} else {
|
||||||
c.target = strings.TrimPrefix(c.target, "unix://")
|
endpoint = strings.TrimPrefix(endpoint, "unix://")
|
||||||
c.proto = "unix"
|
dio := newIO("unix", endpoint)
|
||||||
|
d = Dnstap{io: dio}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.full = d.NextArg() && d.Val() == "full"
|
d.IncludeRawMessage = c.NextArg() && c.Val() == "full"
|
||||||
|
|
||||||
return
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup(c *caddy.Controller) error {
|
func setup(c *caddy.Controller) error {
|
||||||
conf, err := parseConfig(c)
|
dnstap, err := parseConfig(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return plugin.Error("dnstap", err)
|
return plugin.Error("dnstap", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dio := dnstapio.New(conf.proto, conf.target)
|
|
||||||
dnstap := Dnstap{io: dio, IncludeRawMessage: conf.full}
|
|
||||||
|
|
||||||
c.OnStartup(func() error {
|
c.OnStartup(func() error {
|
||||||
dio.Connect()
|
if err := dnstap.io.(*dio).connect(); err != nil {
|
||||||
|
log.Errorf("No connection to dnstap endpoint: %s", err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
c.OnRestart(func() error {
|
c.OnRestart(func() error {
|
||||||
dio.Close()
|
dnstap.io.(*dio).close()
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
c.OnFinalShutdown(func() error {
|
c.OnFinalShutdown(func() error {
|
||||||
dio.Close()
|
dnstap.io.(*dio).close()
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -8,26 +8,38 @@ import (
|
||||||
|
|
||||||
func TestConfig(t *testing.T) {
|
func TestConfig(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
file string
|
in string
|
||||||
path string
|
endpoint string
|
||||||
full bool
|
full bool
|
||||||
proto string
|
proto string
|
||||||
fail bool
|
fail bool
|
||||||
}{
|
}{
|
||||||
{"dnstap dnstap.sock full", "dnstap.sock", true, "unix", false},
|
{"dnstap dnstap.sock full", "dnstap.sock", true, "unix", false},
|
||||||
{"dnstap unix://dnstap.sock", "dnstap.sock", false, "unix", false},
|
{"dnstap unix://dnstap.sock", "dnstap.sock", false, "unix", false},
|
||||||
{"dnstap tcp://127.0.0.1:6000", "127.0.0.1:6000", false, "tcp", false},
|
{"dnstap tcp://127.0.0.1:6000", "127.0.0.1:6000", false, "tcp", false},
|
||||||
{"dnstap", "fail", false, "tcp", true},
|
{"dnstap", "fail", false, "tcp", true},
|
||||||
}
|
}
|
||||||
for _, c := range tests {
|
for i, tc := range tests {
|
||||||
cad := caddy.NewTestController("dns", c.file)
|
c := caddy.NewTestController("dns", tc.in)
|
||||||
conf, err := parseConfig(cad)
|
tap, err := parseConfig(c)
|
||||||
if c.fail {
|
if tc.fail && err == nil {
|
||||||
if err == nil {
|
t.Fatalf("Test %d: expected test to fail: %s: %s", i, tc.in, err)
|
||||||
t.Errorf("%s: %s", c.file, err)
|
}
|
||||||
}
|
if tc.fail {
|
||||||
} else if err != nil || conf.target != c.path || conf.full != c.full || conf.proto != c.proto {
|
continue
|
||||||
t.Errorf("Expected: %+v\nhave: %+v\nerror: %s", c, conf, err)
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test %d: expected no error, got %s", i, err)
|
||||||
|
}
|
||||||
|
if x := tap.io.(*dio).endpoint; x != tc.endpoint {
|
||||||
|
t.Errorf("Test %d: expected endpoint %s, got %s", i, tc.endpoint, x)
|
||||||
|
}
|
||||||
|
if x := tap.io.(*dio).proto; x != tc.proto {
|
||||||
|
t.Errorf("Test %d: expected proto %s, got %s", i, tc.proto, x)
|
||||||
|
}
|
||||||
|
if x := tap.IncludeRawMessage; x != tc.full {
|
||||||
|
t.Errorf("Test %d: expected IncludeRawMessage %t, got %t", i, tc.full, x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,25 +9,23 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResponseWriter captures the client response and logs the query to dnstap.
|
// ResponseWriter captures the client response and logs the query to dnstap.
|
||||||
// Single request use.
|
|
||||||
type ResponseWriter struct {
|
type ResponseWriter struct {
|
||||||
QueryTime time.Time
|
queryTime time.Time
|
||||||
Query *dns.Msg
|
query *dns.Msg
|
||||||
dns.ResponseWriter
|
dns.ResponseWriter
|
||||||
Dnstap
|
Dnstap
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteMsg writes back the response to the client and THEN works 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.
|
|
||||||
func (w *ResponseWriter) WriteMsg(resp *dns.Msg) error {
|
func (w *ResponseWriter) WriteMsg(resp *dns.Msg) error {
|
||||||
err := w.ResponseWriter.WriteMsg(resp)
|
err := w.ResponseWriter.WriteMsg(resp)
|
||||||
|
|
||||||
q := new(tap.Message)
|
q := new(tap.Message)
|
||||||
msg.SetQueryTime(q, w.QueryTime)
|
msg.SetQueryTime(q, w.queryTime)
|
||||||
msg.SetQueryAddress(q, w.RemoteAddr())
|
msg.SetQueryAddress(q, w.RemoteAddr())
|
||||||
|
|
||||||
if w.IncludeRawMessage {
|
if w.IncludeRawMessage {
|
||||||
buf, _ := w.Query.Pack()
|
buf, _ := w.query.Pack()
|
||||||
q.QueryMessage = buf
|
q.QueryMessage = buf
|
||||||
}
|
}
|
||||||
msg.SetType(q, tap.Message_CLIENT_QUERY)
|
msg.SetType(q, tap.Message_CLIENT_QUERY)
|
||||||
|
@ -38,7 +36,7 @@ func (w *ResponseWriter) WriteMsg(resp *dns.Msg) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := new(tap.Message)
|
r := new(tap.Message)
|
||||||
msg.SetQueryTime(r, w.QueryTime)
|
msg.SetQueryTime(r, w.queryTime)
|
||||||
msg.SetResponseTime(r, time.Now())
|
msg.SetResponseTime(r, time.Now())
|
||||||
msg.SetQueryAddress(r, w.RemoteAddr())
|
msg.SetQueryAddress(r, w.RemoteAddr())
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue