diff options
Diffstat (limited to 'tests/dnstap/src')
-rw-r--r-- | tests/dnstap/src/dnstap-test/config | 14 | ||||
-rw-r--r-- | tests/dnstap/src/dnstap-test/main.go | 247 | ||||
-rwxr-xr-x | tests/dnstap/src/dnstap-test/run.sh | 31 | ||||
-rw-r--r-- | tests/dnstap/src/dnstap-test/vendor/manifest | 55 |
4 files changed, 347 insertions, 0 deletions
diff --git a/tests/dnstap/src/dnstap-test/config b/tests/dnstap/src/dnstap-test/config new file mode 100644 index 0000000..5f15308 --- /dev/null +++ b/tests/dnstap/src/dnstap-test/config @@ -0,0 +1,14 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +modules = { + 'hints', + dnstap = { + socket_path = "dnstap.sock", + client = { + log_queries = true, + log_responses = true, + } + } +} +hints['fake1.localdomain'] = '1.2.3.4' +hints['fake2.localdomain'] = '1.2.3.5' +hints['fake3.localdomain'] = '1.2.3.6' diff --git a/tests/dnstap/src/dnstap-test/main.go b/tests/dnstap/src/dnstap-test/main.go new file mode 100644 index 0000000..c04b4c1 --- /dev/null +++ b/tests/dnstap/src/dnstap-test/main.go @@ -0,0 +1,247 @@ +package main + +import ( + "context" + "flag" + "fmt" + "github.com/cloudflare/dns" + dnstap "github.com/dnstap/golang-dnstap" + "github.com/golang/protobuf/proto" + "io" + "io/ioutil" + "log" + "net" + "os" + "os/exec" + "strings" + "time" +) + +var ( + kresdArgs = []string{ + "-n", + "-q", + } +) + +func qnameFromFrame(b []byte) (string, error) { + dt := &dnstap.Dnstap{} + var name string + if err := proto.Unmarshal(b, dt); err != nil { + return name, err + } + + var msg_raw []byte + m := dt.Message + if *m.Type == dnstap.Message_CLIENT_QUERY { + msg_raw = m.QueryMessage + } else if *m.Type == dnstap.Message_CLIENT_RESPONSE { + msg_raw = m.ResponseMessage + } else { + return name, fmt.Errorf("incorrect message type: %v", *m.Type) + } + + if msg_raw == nil { + return name, fmt.Errorf("no message payload") + } + if err := dns.IsMsg(msg_raw); err != nil { + return name, err + } + var msg dns.Msg + if err := msg.Unpack(msg_raw); err != nil { + return name, err + } + if len(msg.Question) < 1 { + return name, fmt.Errorf("question empty") + } + return msg.Question[0].Name, nil +} + +func listenOn() (net.Addr, *os.File, error) { + udpConn, err := net.ListenUDP("udp", &net.UDPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: 0, + }) + if err != nil { + return nil, nil, err + } + + file, err := udpConn.File() + if err != nil { + return nil, nil, err + } + return udpConn.LocalAddr(), file, nil +} + +func runKresd(ctx context.Context, path, configFile string, grace time.Duration) (chan bool, error) { + ch := make(chan bool) + kresdArgs = append(kresdArgs, "-c"+configFile) + // we have 1 object in ExtraFiles with index 0 + // child fd will be 3 + i = 3 + kresdArgs = append(kresdArgs, "-S3") + + file := ctx.Value("file").(*os.File) + debug := ctx.Value("debug").(bool) + + cmd := exec.CommandContext(ctx, path, kresdArgs...) + cmd.ExtraFiles = []*os.File{file} + + var stdout, stderr io.ReadCloser + var err error + if debug { + stdout, err = cmd.StdoutPipe() + if err != nil { + log.Printf("stdoutpipe: %v\n", err) + return ch, err + } + + stderr, err = cmd.StderrPipe() + if err != nil { + log.Printf("stderrpipe: %v\n", err) + return ch, err + } + } + + go func() { + status := false + defer func() { + ch <- status // kresd done + }() + if err := cmd.Start(); err != nil { + log.Printf("start: %v\n", err) + return + } + time.Sleep(grace) + ch <- true // Started kresd + + if debug { + s, err := ioutil.ReadAll(stdout) + if err != nil { + log.Printf("readall: %v\n", err) + return + } + if len(s) > 0 { + fmt.Printf("stdout:\n%s\n", s) + } + + s, err = ioutil.ReadAll(stderr) + if err != nil { + log.Printf("readall: %v\n", err) + return + } + if len(s) > 0 { + fmt.Printf("stderr:\n%s\n", s) + } + } + + if err := cmd.Wait(); err != nil && err.Error() != "signal: killed" { + log.Printf("wait: %v\n", err) + return + } + status = true + }() + return ch, nil +} + +func main() { + var ( + unixSocket = flag.String("u", "dnstap.sock", "dnstap socket") + kresdPath = flag.String("cmd", "kresd", "kresd path") + configFile = flag.String("c", "config", "config file") + qnames = flag.String("q", ".", "list of comma separated zones") + grace = flag.String("g", "1s", "Time to wait for daemon start") + timeout = flag.String("t", "60s", "Test Timeout") + debug = flag.Bool("d", false, "Debug") + ) + + flag.Parse() + + kresdStartGracePeriod, err := time.ParseDuration(*grace) + if err != nil { + panic(err) + } + + testTimeout, err := time.ParseDuration(*timeout) + if err != nil { + panic(err) + } + + input, err := dnstap.NewFrameStreamSockInputFromPath(*unixSocket) + if err != nil { + panic(err) + } + + output := make(chan []byte) + go input.ReadInto(output) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + + // Create a UDP listening socket on random port + // FD will be passed on to kresd + addr, file, err := listenOn() + if err != nil { + panic(err) + } + if *debug { + log.Printf("listen addr:%v", addr) + } + ctx = context.WithValue(ctx, "file", file) + ctx = context.WithValue(ctx, "debug", *debug) + + ch, err := runKresd(ctx, *kresdPath, *configFile, kresdStartGracePeriod) + if err != nil { + panic(err) + } + + log.Printf("Waiting for kresd to start\n") + status := <-ch + if !status { + os.Exit(1) // error starting + } + + go func() { + parts := strings.Split(*qnames, ",") + + if len(parts) == 0 { + log.Printf("qname count is 0") + } + for _, name := range parts { + m := new(dns.Msg) + fqdn := dns.Fqdn(name) + m.SetQuestion(fqdn, dns.TypeA) + c := new(dns.Client) + resp, _, err := c.Exchange(m, fmt.Sprintf("%v", addr)) + if err != nil { + log.Printf("%v\n", err) + os.Exit(1) // Test Failed + } + if *debug { + log.Printf("Response: %v", resp) + } + + for range "QR" { // Checking Query and Response is the same ATM + o := <-output + if *debug { + log.Printf("raw dnstap:%v", o) + } + dtName, err := qnameFromFrame(o) + if err != nil { + log.Printf("%v\n", err) + os.Exit(1) + } + if fqdn != dtName { + log.Printf("expected %v got %v", fqdn, dtName) + os.Exit(1) // Test failed + } + log.Printf("matched qname: %v", dtName) + } + } + cancel() // Send signal to close daemon + }() + + status = <-ch + if !status { + os.Exit(1) // error in wait + } + log.Printf("Tested OK\n") +} diff --git a/tests/dnstap/src/dnstap-test/run.sh b/tests/dnstap/src/dnstap-test/run.sh new file mode 100755 index 0000000..2f32ea1 --- /dev/null +++ b/tests/dnstap/src/dnstap-test/run.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -e +KRESD_CMD=$1 +MESON_BUILD_ROOT=$(pwd) +mkdir -p tests/dnstap +export GOPATH=$MESON_BUILD_ROOT/tests/dnstap +cd "$(dirname $0)" +DNSTAP_TEST=dnstap-test + +if [ -z "$GITLAB_CI" ]; then + type -P go >/dev/null || exit 77 + echo "Building the dnstap test and its dependencies..." + # some packages may be missing on the system right now + go get github.com/{FiloSottile/gvt,cloudflare/dns,dnstap/golang-dnstap,golang/protobuf/proto} +else + # In CI we've prebuilt dependencies into the default GOPATH. + # We're in a scratch container, so we just add the dnstap test inside. + export GOPATH=/root/go +fi +DTAP=$GOPATH/src/$DNSTAP_TEST +rm -f $DTAP && ln -s $(realpath ..)/$DNSTAP_TEST $DTAP +go install $DNSTAP_TEST + + +CONFIG=$(realpath ./config) +ZONES="fake1.localdomain,fake2.localdomain,fake3.localdomain" +TIMEOUT=60s +GRACE=5s +cd $MESON_BUILD_ROOT/tests/dnstap # don't leave stuff like *.mdb in ./. +$GOPATH/bin/$DNSTAP_TEST -c $CONFIG -cmd $KRESD_CMD -q $ZONES -t $TIMEOUT -g $GRACE -d + diff --git a/tests/dnstap/src/dnstap-test/vendor/manifest b/tests/dnstap/src/dnstap-test/vendor/manifest new file mode 100644 index 0000000..27c1dec --- /dev/null +++ b/tests/dnstap/src/dnstap-test/vendor/manifest @@ -0,0 +1,55 @@ +{ + "version": 0, + "dependencies": [ + { + "importpath": "github.com/cloudflare/dns", + "repository": "https://github.com/cloudflare/dns", + "vcs": "git", + "revision": "e20ffa3da443071c7b3d164dec5b1f80dfb2ecf3", + "branch": "master", + "notests": true + }, + { + "importpath": "github.com/dnstap/golang-dnstap", + "repository": "https://github.com/dnstap/golang-dnstap", + "vcs": "git", + "revision": "0145fd8482619f9c04788c7ba4e96cdeef64a041", + "branch": "master", + "notests": true + }, + { + "importpath": "github.com/farsightsec/golang-framestream", + "repository": "https://github.com/farsightsec/golang-framestream", + "vcs": "git", + "revision": "b600ccf606747139c84b6d69b5c3988164db4d42", + "branch": "master", + "notests": true + }, + { + "importpath": "github.com/golang/protobuf/proto", + "repository": "https://github.com/golang/protobuf", + "vcs": "git", + "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", + "branch": "master", + "path": "/proto", + "notests": true + }, + { + "importpath": "github.com/golang/protobuf/ptypes/any", + "repository": "https://github.com/golang/protobuf", + "vcs": "git", + "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", + "branch": "master", + "path": "ptypes/any", + "notests": true + }, + { + "importpath": "github.com/miekg/dns", + "repository": "https://github.com/miekg/dns", + "vcs": "git", + "revision": "f4d2b086946a624202dc59e6a43f72e8f3f02bc1", + "branch": "master", + "notests": true + } + ] +}
\ No newline at end of file |