diff options
author | 2017-09-01 14:07:21 +0200 | |
---|---|---|
committer | 2017-09-01 14:07:21 +0200 | |
commit | 345dee82edc693ae74d5ac83571f0f5eac844fb2 (patch) | |
tree | 14ff88b2f53579aaf8dc4c12d6222b344acf4b9a /middleware | |
parent | c5efd45720a4e91569ac70f0f8b8aeaa4576e508 (diff) | |
download | coredns-345dee82edc693ae74d5ac83571f0f5eac844fb2.tar.gz coredns-345dee82edc693ae74d5ac83571f0f5eac844fb2.tar.zst coredns-345dee82edc693ae74d5ac83571f0f5eac844fb2.zip |
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
Diffstat (limited to 'middleware')
-rw-r--r-- | middleware/dnstap/README.md | 14 | ||||
-rw-r--r-- | middleware/dnstap/out/tcp.go | 59 | ||||
-rw-r--r-- | middleware/dnstap/out/tcp_test.go | 66 | ||||
-rw-r--r-- | middleware/dnstap/setup.go | 50 | ||||
-rw-r--r-- | middleware/dnstap/setup_test.go | 31 |
5 files changed, 200 insertions, 20 deletions
diff --git a/middleware/dnstap/README.md b/middleware/dnstap/README.md index a74472156..d91c9422c 100644 --- a/middleware/dnstap/README.md +++ b/middleware/dnstap/README.md @@ -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*. ~~~ 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 @@ -47,3 +53,9 @@ The following command listens on the given socket and saves message payloads to ~~~ sh dnstap -u /tmp/dnstap.sock -w /tmp/test.dnstap ~~~ + +Listen for dnstap messages on port 6000. + +~~~ sh +dnstap -l 127.0.0.1:6000 +~~~ diff --git a/middleware/dnstap/out/tcp.go b/middleware/dnstap/out/tcp.go new file mode 100644 index 000000000..8d2c25270 --- /dev/null +++ b/middleware/dnstap/out/tcp.go @@ -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() +} diff --git a/middleware/dnstap/out/tcp_test.go b/middleware/dnstap/out/tcp_test.go new file mode 100644 index 000000000..d76cd9280 --- /dev/null +++ b/middleware/dnstap/out/tcp_test.go @@ -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) + } +} diff --git a/middleware/dnstap/setup.go b/middleware/dnstap/setup.go index 5c74bd346..63a3eb099 100644 --- a/middleware/dnstap/setup.go +++ b/middleware/dnstap/setup.go @@ -2,11 +2,14 @@ package dnstap import ( "fmt" + "io" "log" + "strings" "github.com/coredns/coredns/core/dnsserver" "github.com/coredns/coredns/middleware" "github.com/coredns/coredns/middleware/dnstap/out" + "github.com/coredns/coredns/middleware/pkg/dnsutil" "github.com/mholt/caddy" "github.com/mholt/caddy/caddyfile" @@ -26,30 +29,55 @@ func wrapSetup(c *caddy.Controller) error { return nil } -func parseConfig(c *caddyfile.Dispenser) (path string, full bool, err error) { - c.Next() // directive name +type config struct { + target string + socket bool + full bool +} + +func parseConfig(d *caddyfile.Dispenser) (c config, err error) { + d.Next() // directive name + + if !d.Args(&c.target) { + return c, d.ArgErr() + } - if !c.Args(&path) { - err = c.ArgErr() - return + 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 } - full = c.NextArg() && c.Val() == "full" + c.full = d.NextArg() && d.Val() == "full" return } func setup(c *caddy.Controller) error { - path, full, err := parseConfig(&c.Dispenser) + conf, err := parseConfig(&c.Dispenser) if err != nil { return err } - dnstap := Dnstap{Pack: full} + dnstap := Dnstap{Pack: conf.full} - o, err := out.NewSocket(path) - if err != nil { - log.Printf("[WARN] Can't connect to %s at the moment", path) + var o io.WriteCloser + if conf.socket { + o, err = out.NewSocket(conf.target) + if err != nil { + log.Printf("[WARN] Can't connect to %s at the moment: %s", conf.target, err) + } + } else { + o = out.NewTCP(conf.target) } dnstap.Out = o diff --git a/middleware/dnstap/setup_test.go b/middleware/dnstap/setup_test.go index fb4bca1f0..fc1dc98e0 100644 --- a/middleware/dnstap/setup_test.go +++ b/middleware/dnstap/setup_test.go @@ -6,14 +6,29 @@ import ( ) func TestConfig(t *testing.T) { - file := "dnstap dnstap.sock full" - c := caddy.NewTestController("dns", file) - if path, full, err := parseConfig(&c.Dispenser); path != "dnstap.sock" || !full { - t.Fatalf("%s: %s", file, err) + tests := []struct { + file string + path string + 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}, } - 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) + 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) + } } } |