aboutsummaryrefslogtreecommitdiff
path: root/middleware
diff options
context:
space:
mode:
authorGravatar varyoo <varyoo@users.noreply.github.com> 2017-09-01 14:07:21 +0200
committerGravatar Miek Gieben <miek@miek.nl> 2017-09-01 14:07:21 +0200
commit345dee82edc693ae74d5ac83571f0f5eac844fb2 (patch)
tree14ff88b2f53579aaf8dc4c12d6222b344acf4b9a /middleware
parentc5efd45720a4e91569ac70f0f8b8aeaa4576e508 (diff)
downloadcoredns-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.md14
-rw-r--r--middleware/dnstap/out/tcp.go59
-rw-r--r--middleware/dnstap/out/tcp_test.go66
-rw-r--r--middleware/dnstap/setup.go50
-rw-r--r--middleware/dnstap/setup_test.go31
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)
+ }
}
}