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 <xe@tailscale.com>
pull/2762/head
Christine Dodrill 3 years ago committed by David Crawshaw
parent 00b3c1c042
commit 0b9e938152

@ -43,6 +43,7 @@ type Server struct {
DERPMap *tailcfg.DERPMap // nil means to use prod DERP map DERPMap *tailcfg.DERPMap // nil means to use prod DERP map
RequireAuth bool RequireAuth bool
Verbose bool Verbose bool
DNSConfig *tailcfg.DNSConfig // nil means no DNS config
// ExplicitBaseURL or HTTPTestServer must be set. // ExplicitBaseURL or HTTPTestServer must be set.
ExplicitBaseURL string // e.g. "http://127.0.0.1:1234" with no trailing URL 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, MachineAuthorized: machineAuthorized,
Addresses: allowedIPs, Addresses: allowedIPs,
AllowedIPs: allowedIPs, AllowedIPs: allowedIPs,
Hostinfo: *req.Hostinfo,
} }
requireAuth := s.RequireAuth requireAuth := s.RequireAuth
if requireAuth && s.nodeKeyAuthed[req.NodeKey] { if requireAuth && s.nodeKeyAuthed[req.NodeKey] {
@ -691,6 +693,7 @@ func (s *Server) MapResponse(req *tailcfg.MapRequest) (res *tailcfg.MapResponse,
Debug: &tailcfg.Debug{ Debug: &tailcfg.Debug{
DisableUPnP: "true", DisableUPnP: "true",
}, },
DNSConfig: s.DNSConfig,
} }
for _, p := range s.AllNodes() { for _, p := range s.AllNodes() {
if p.StableID != node.StableID { if p.StableID != node.StableID {

@ -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
}

@ -26,8 +26,10 @@ import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"golang.org/x/net/proxy" "golang.org/x/net/proxy"
"inet.af/netaddr" "inet.af/netaddr"
"tailscale.com/tailcfg"
"tailscale.com/tstest/integration" "tailscale.com/tstest/integration"
"tailscale.com/tstest/integration/testcontrol" "tailscale.com/tstest/integration/testcontrol"
"tailscale.com/types/dnstype"
) )
type Harness struct { type Harness struct {
@ -55,7 +57,17 @@ func newHarness(t *testing.T) *Harness {
}) })
t.Logf("host:port: %s", ln.Addr()) 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) derpMap := integration.RunDERPAndSTUN(t, t.Logf, bindHost)
cs.DERPMap = derpMap cs.DERPMap = derpMap

@ -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.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) { func runTestCommands(t *testing.T, timeout time.Duration, cli *ssh.Client, batch []expect.Batcher) {

Loading…
Cancel
Save