From 0b9e9381528feef84038359892515e524c4c4c14 Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Wed, 21 Jul 2021 10:27:50 -0400 Subject: [PATCH] tstest/integration/vms: test DNS configuration This uses a neat little tool to dump the output of DNS queries to standard out. This is the first end-to-end test of DNS that runs against actual linux systems. The /etc/resolv.conf test may look superflous, however this will help for correlating system state if one of the DNS tests fails. Signed-off-by: Christine Dodrill --- tstest/integration/testcontrol/testcontrol.go | 3 + tstest/integration/vms/dns_tester.go | 55 +++++++++++++++++++ tstest/integration/vms/harness_test.go | 14 ++++- tstest/integration/vms/vms_test.go | 42 ++++++++++++++ 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 tstest/integration/vms/dns_tester.go diff --git a/tstest/integration/testcontrol/testcontrol.go b/tstest/integration/testcontrol/testcontrol.go index 8b9d49e01..f452bba46 100644 --- a/tstest/integration/testcontrol/testcontrol.go +++ b/tstest/integration/testcontrol/testcontrol.go @@ -43,6 +43,7 @@ type Server struct { DERPMap *tailcfg.DERPMap // nil means to use prod DERP map RequireAuth bool Verbose bool + DNSConfig *tailcfg.DNSConfig // nil means no DNS config // ExplicitBaseURL or HTTPTestServer must be set. ExplicitBaseURL string // e.g. "http://127.0.0.1:1234" with no trailing URL @@ -453,6 +454,7 @@ func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey tail MachineAuthorized: machineAuthorized, Addresses: allowedIPs, AllowedIPs: allowedIPs, + Hostinfo: *req.Hostinfo, } requireAuth := s.RequireAuth if requireAuth && s.nodeKeyAuthed[req.NodeKey] { @@ -691,6 +693,7 @@ func (s *Server) MapResponse(req *tailcfg.MapRequest) (res *tailcfg.MapResponse, Debug: &tailcfg.Debug{ DisableUPnP: "true", }, + DNSConfig: s.DNSConfig, } for _, p := range s.AllNodes() { if p.StableID != node.StableID { diff --git a/tstest/integration/vms/dns_tester.go b/tstest/integration/vms/dns_tester.go new file mode 100644 index 000000000..9fbeab699 --- /dev/null +++ b/tstest/integration/vms/dns_tester.go @@ -0,0 +1,55 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ignore + +// Command dns_tester exists in order to perform tests of our DNS +// configuration stack. This was written because the state of DNS +// in our target environments is so diverse that we need a little tool +// to do this test for us. +package main + +import ( + "context" + "encoding/json" + "flag" + "net" + "os" + "time" +) + +func main() { + flag.Parse() + target := flag.Arg(0) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + errCount := 0 + wait := 25 * time.Millisecond + for range make([]struct{}, 5) { + err := lookup(ctx, target) + if err != nil { + errCount++ + time.Sleep(wait) + wait = wait * 2 + continue + } + + break + } +} + +func lookup(ctx context.Context, target string) error { + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + + hosts, err := net.LookupHost(target) + if err != nil { + return err + } + + json.NewEncoder(os.Stdout).Encode(hosts) + return nil +} diff --git a/tstest/integration/vms/harness_test.go b/tstest/integration/vms/harness_test.go index e606f40c7..7c07bed9c 100644 --- a/tstest/integration/vms/harness_test.go +++ b/tstest/integration/vms/harness_test.go @@ -26,8 +26,10 @@ import ( "golang.org/x/crypto/ssh" "golang.org/x/net/proxy" "inet.af/netaddr" + "tailscale.com/tailcfg" "tailscale.com/tstest/integration" "tailscale.com/tstest/integration/testcontrol" + "tailscale.com/types/dnstype" ) type Harness struct { @@ -55,7 +57,17 @@ func newHarness(t *testing.T) *Harness { }) t.Logf("host:port: %s", ln.Addr()) - cs := &testcontrol.Server{} + cs := &testcontrol.Server{ + DNSConfig: &tailcfg.DNSConfig{ + // TODO: this is wrong. + // It is also only one of many configurations. + // Figure out how to scale it up. + Resolvers: []dnstype.Resolver{{Addr: "100.100.100.100"}, {Addr: "8.8.8.8"}}, + Domains: []string{"record"}, + Proxied: true, + ExtraRecords: []tailcfg.DNSRecord{{Name: "extratest.record", Type: "A", Value: "1.2.3.4"}}, + }, + } derpMap := integration.RunDERPAndSTUN(t, t.Logf, bindHost) cs.DERPMap = derpMap diff --git a/tstest/integration/vms/vms_test.go b/tstest/integration/vms/vms_test.go index 2a594dff3..94d0cb683 100644 --- a/tstest/integration/vms/vms_test.go +++ b/tstest/integration/vms/vms_test.go @@ -624,6 +624,48 @@ func (h *Harness) testDistro(t *testing.T, d Distro, ipm ipMapping) { t.Fatalf("wanted %q from vm, got: %q", securePassword, msg) } }) + + t.Run("dns-test", func(t *testing.T) { + t.Run("etc-resolv-conf", func(t *testing.T) { + sess := getSession(t, cli) + sess.Stdout = logger.FuncWriter(t.Logf) + sess.Stderr = logger.FuncWriter(t.Errorf) + if err := sess.Run("cat /etc/resolv.conf"); err != nil { + t.Fatal(err) + } + }) + + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("can't get working directory: %v", err) + } + dir := t.TempDir() + run(t, cwd, "go", "build", "-o", filepath.Join(dir, "dns_tester"), "./dns_tester.go") + + sftpCli, err := sftp.NewClient(cli) + if err != nil { + t.Fatalf("can't connect over sftp to copy binaries: %v", err) + } + defer sftpCli.Close() + + copyFile(t, sftpCli, filepath.Join(dir, "dns_tester"), "/dns_tester") + + for _, record := range []string{"extratest.record", "extratest"} { + t.Run(record, func(t *testing.T) { + sess := getSession(t, cli) + sess.Stderr = logger.FuncWriter(t.Errorf) + msg, err := sess.Output("/dns_tester " + record) + if err != nil { + t.Fatal(err) + } + + msg = bytes.TrimSpace(msg) + if want := []byte("1.2.3.4"); !bytes.Contains(msg, want) { + t.Fatalf("got: %q, want: %q", msg, want) + } + }) + } + }) } func runTestCommands(t *testing.T, timeout time.Duration, cli *ssh.Client, batch []expect.Batcher) {