mirror of
https://github.com/coredns/coredns.git
synced 2025-10-27 16:24:19 -04:00
IP endpoint for dnstap (#1002)
* adds the option to log to a remote endpoint * examples * tests * tcp:// or default to unix:// * cosmetic update * bad naked returns
This commit is contained in:
@@ -24,7 +24,13 @@ dnstap /tmp/dnstap.sock
|
|||||||
Log information including the wire-format DNS message about client requests and responses to */tmp/dnstap.sock*.
|
Log information including the wire-format DNS message about client requests and responses to */tmp/dnstap.sock*.
|
||||||
|
|
||||||
~~~ txt
|
~~~ txt
|
||||||
dnstap /tmp/dnstap.sock full
|
dnstap unix:///tmp/dnstap.sock full
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Log to a remote endpoint.
|
||||||
|
|
||||||
|
~~~ txt
|
||||||
|
dnstap tcp://127.0.0.1:6000 full
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
## Dnstap command line tool
|
## Dnstap command line tool
|
||||||
@@ -47,3 +53,9 @@ The following command listens on the given socket and saves message payloads to
|
|||||||
~~~ sh
|
~~~ sh
|
||||||
dnstap -u /tmp/dnstap.sock -w /tmp/test.dnstap
|
dnstap -u /tmp/dnstap.sock -w /tmp/test.dnstap
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
Listen for dnstap messages on port 6000.
|
||||||
|
|
||||||
|
~~~ sh
|
||||||
|
dnstap -l 127.0.0.1:6000
|
||||||
|
~~~
|
||||||
|
|||||||
59
middleware/dnstap/out/tcp.go
Normal file
59
middleware/dnstap/out/tcp.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package out
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
fs "github.com/farsightsec/golang-framestream"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TCP is a Frame Streams encoder over TCP.
|
||||||
|
type TCP struct {
|
||||||
|
address string
|
||||||
|
frames [][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTCP returns a TCP writer.
|
||||||
|
func NewTCP(address string) *TCP {
|
||||||
|
s := &TCP{address: address}
|
||||||
|
s.frames = make([][]byte, 0, 13) // 13 messages buffer
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a single Frame Streams frame.
|
||||||
|
func (s *TCP) Write(frame []byte) (n int, err error) {
|
||||||
|
s.frames = append(s.frames, frame)
|
||||||
|
if len(s.frames) == cap(s.frames) {
|
||||||
|
return len(frame), s.Flush()
|
||||||
|
}
|
||||||
|
return len(frame), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush the remaining frames.
|
||||||
|
func (s *TCP) Flush() error {
|
||||||
|
defer func() {
|
||||||
|
s.frames = s.frames[0:]
|
||||||
|
}()
|
||||||
|
c, err := net.DialTimeout("tcp", s.address, time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
enc, err := fs.NewEncoder(c, &fs.EncoderOptions{
|
||||||
|
ContentType: []byte("protobuf:dnstap.Dnstap"),
|
||||||
|
Bidirectional: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, frame := range s.frames {
|
||||||
|
if _, err = enc.Write(frame); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return enc.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is an alias to Flush to satisfy io.WriteCloser similarly to type Socket.
|
||||||
|
func (s *TCP) Close() error {
|
||||||
|
return s.Flush()
|
||||||
|
}
|
||||||
66
middleware/dnstap/out/tcp_test.go
Normal file
66
middleware/dnstap/out/tcp_test.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package out
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sendOneTcp(tcp *TCP) error {
|
||||||
|
if _, err := tcp.Write([]byte("frame")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tcp.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func TestTcp(t *testing.T) {
|
||||||
|
tcp := NewTCP("localhost:14000")
|
||||||
|
|
||||||
|
if err := sendOneTcp(tcp); err == nil {
|
||||||
|
t.Fatal("Not listening but no error.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := net.Listen("tcp", "localhost:14000")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wait := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
acceptOne(t, l)
|
||||||
|
wait <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := sendOneTcp(tcp); err != nil {
|
||||||
|
t.Fatalf("send one: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
<-wait
|
||||||
|
|
||||||
|
// TODO: When the server isn't responding according to the framestream protocol
|
||||||
|
// the thread is blocked.
|
||||||
|
/*
|
||||||
|
if err := sendOneTcp(tcp); err == nil {
|
||||||
|
panic("must fail")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
acceptOne(t, l)
|
||||||
|
wait <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := sendOneTcp(tcp); err != nil {
|
||||||
|
t.Fatalf("send one: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
<-wait
|
||||||
|
if err := l.Close(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,14 @@ package dnstap
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/coredns/coredns/core/dnsserver"
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
"github.com/coredns/coredns/middleware"
|
"github.com/coredns/coredns/middleware"
|
||||||
"github.com/coredns/coredns/middleware/dnstap/out"
|
"github.com/coredns/coredns/middleware/dnstap/out"
|
||||||
|
"github.com/coredns/coredns/middleware/pkg/dnsutil"
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
"github.com/mholt/caddy/caddyfile"
|
"github.com/mholt/caddy/caddyfile"
|
||||||
@@ -26,30 +29,55 @@ func wrapSetup(c *caddy.Controller) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseConfig(c *caddyfile.Dispenser) (path string, full bool, err error) {
|
type config struct {
|
||||||
c.Next() // directive name
|
target string
|
||||||
|
socket bool
|
||||||
|
full bool
|
||||||
|
}
|
||||||
|
|
||||||
if !c.Args(&path) {
|
func parseConfig(d *caddyfile.Dispenser) (c config, err error) {
|
||||||
err = c.ArgErr()
|
d.Next() // directive name
|
||||||
return
|
|
||||||
|
if !d.Args(&c.target) {
|
||||||
|
return c, d.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
full = c.NextArg() && c.Val() == "full"
|
if strings.HasPrefix(c.target, "tcp://") {
|
||||||
|
// remote IP endpoint
|
||||||
|
servers, err := dnsutil.ParseHostPortOrFile(c.target[6:])
|
||||||
|
if err != nil {
|
||||||
|
return c, d.ArgErr()
|
||||||
|
}
|
||||||
|
c.target = servers[0]
|
||||||
|
} else {
|
||||||
|
// default to UNIX socket
|
||||||
|
if strings.HasPrefix(c.target, "unix://") {
|
||||||
|
c.target = c.target[7:]
|
||||||
|
}
|
||||||
|
c.socket = true
|
||||||
|
}
|
||||||
|
|
||||||
|
c.full = d.NextArg() && d.Val() == "full"
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup(c *caddy.Controller) error {
|
func setup(c *caddy.Controller) error {
|
||||||
path, full, err := parseConfig(&c.Dispenser)
|
conf, err := parseConfig(&c.Dispenser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dnstap := Dnstap{Pack: full}
|
dnstap := Dnstap{Pack: conf.full}
|
||||||
|
|
||||||
o, err := out.NewSocket(path)
|
var o io.WriteCloser
|
||||||
|
if conf.socket {
|
||||||
|
o, err = out.NewSocket(conf.target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARN] Can't connect to %s at the moment", path)
|
log.Printf("[WARN] Can't connect to %s at the moment: %s", conf.target, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
o = out.NewTCP(conf.target)
|
||||||
}
|
}
|
||||||
dnstap.Out = o
|
dnstap.Out = o
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,29 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestConfig(t *testing.T) {
|
func TestConfig(t *testing.T) {
|
||||||
file := "dnstap dnstap.sock full"
|
tests := []struct {
|
||||||
c := caddy.NewTestController("dns", file)
|
file string
|
||||||
if path, full, err := parseConfig(&c.Dispenser); path != "dnstap.sock" || !full {
|
path string
|
||||||
t.Fatalf("%s: %s", file, err)
|
full bool
|
||||||
|
socket bool
|
||||||
|
fail bool
|
||||||
|
}{
|
||||||
|
{"dnstap dnstap.sock full", "dnstap.sock", true, true, false},
|
||||||
|
{"dnstap unix://dnstap.sock", "dnstap.sock", false, true, false},
|
||||||
|
{"dnstap tcp://127.0.0.1:6000", "127.0.0.1:6000", false, false, false},
|
||||||
|
{"dnstap", "fail", false, true, true},
|
||||||
|
}
|
||||||
|
for _, c := range tests {
|
||||||
|
cad := caddy.NewTestController("dns", c.file)
|
||||||
|
conf, err := parseConfig(&cad.Dispenser)
|
||||||
|
if c.fail {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("%s: %s", c.file, err)
|
||||||
|
}
|
||||||
|
} else if err != nil || conf.target != c.path ||
|
||||||
|
conf.full != c.full || conf.socket != c.socket {
|
||||||
|
|
||||||
|
t.Errorf("expected: %+v\nhave: %+v\nerror: %s\n", c, conf, err)
|
||||||
}
|
}
|
||||||
file = "dnstap dnstap.sock"
|
|
||||||
c = caddy.NewTestController("dns", file)
|
|
||||||
if path, full, err := parseConfig(&c.Dispenser); path != "dnstap.sock" || full {
|
|
||||||
t.Fatalf("%s: %s", file, err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user