2021-05-31 15:35:01 +00:00
|
|
|
// 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.
|
|
|
|
|
|
|
|
// Package integration contains Tailscale integration tests.
|
|
|
|
//
|
|
|
|
// This package is considered internal and the public API is subject
|
|
|
|
// to change without notice.
|
|
|
|
package integration
|
|
|
|
|
|
|
|
import (
|
2021-06-25 19:59:45 +00:00
|
|
|
"crypto/rand"
|
|
|
|
"crypto/tls"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2021-05-31 15:35:01 +00:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2021-06-25 19:59:45 +00:00
|
|
|
"tailscale.com/derp"
|
|
|
|
"tailscale.com/derp/derphttp"
|
|
|
|
"tailscale.com/net/stun/stuntest"
|
|
|
|
"tailscale.com/tailcfg"
|
|
|
|
"tailscale.com/types/key"
|
|
|
|
"tailscale.com/types/logger"
|
|
|
|
"tailscale.com/types/nettype"
|
2021-05-31 15:35:01 +00:00
|
|
|
"tailscale.com/version"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Binaries are the paths to a tailscaled and tailscale binary.
|
|
|
|
// These can be shared by multiple nodes.
|
|
|
|
type Binaries struct {
|
|
|
|
Dir string // temp dir for tailscale & tailscaled
|
|
|
|
Daemon string // tailscaled
|
|
|
|
CLI string // tailscale
|
|
|
|
}
|
|
|
|
|
|
|
|
// BuildTestBinaries builds tailscale and tailscaled, failing the test
|
|
|
|
// if they fail to compile.
|
|
|
|
func BuildTestBinaries(t testing.TB) *Binaries {
|
|
|
|
td := t.TempDir()
|
|
|
|
build(t, td, "tailscale.com/cmd/tailscaled", "tailscale.com/cmd/tailscale")
|
|
|
|
return &Binaries{
|
|
|
|
Dir: td,
|
|
|
|
Daemon: filepath.Join(td, "tailscaled"+exe()),
|
|
|
|
CLI: filepath.Join(td, "tailscale"+exe()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// buildMu limits our use of "go build" to one at a time, so we don't
|
|
|
|
// fight Go's built-in caching trying to do the same build concurrently.
|
|
|
|
var buildMu sync.Mutex
|
|
|
|
|
|
|
|
func build(t testing.TB, outDir string, targets ...string) {
|
|
|
|
buildMu.Lock()
|
|
|
|
defer buildMu.Unlock()
|
|
|
|
|
|
|
|
t0 := time.Now()
|
|
|
|
defer func() { t.Logf("built %s in %v", targets, time.Since(t0).Round(time.Millisecond)) }()
|
|
|
|
|
|
|
|
goBin := findGo(t)
|
|
|
|
cmd := exec.Command(goBin, "install")
|
|
|
|
if version.IsRace() {
|
|
|
|
cmd.Args = append(cmd.Args, "-race")
|
|
|
|
}
|
|
|
|
cmd.Args = append(cmd.Args, targets...)
|
|
|
|
cmd.Env = append(os.Environ(), "GOARCH="+runtime.GOARCH, "GOBIN="+outDir)
|
|
|
|
errOut, err := cmd.CombinedOutput()
|
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if strings.Contains(string(errOut), "when GOBIN is set") {
|
|
|
|
// Fallback slow path for cross-compiled binaries.
|
|
|
|
for _, target := range targets {
|
|
|
|
outFile := filepath.Join(outDir, path.Base(target)+exe())
|
|
|
|
cmd := exec.Command(goBin, "build", "-o", outFile, target)
|
|
|
|
cmd.Env = append(os.Environ(), "GOARCH="+runtime.GOARCH)
|
|
|
|
if errOut, err := cmd.CombinedOutput(); err != nil {
|
|
|
|
t.Fatalf("failed to build %v with %v: %v, %s", target, goBin, err, errOut)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.Fatalf("failed to build %v with %v: %v, %s", targets, goBin, err, errOut)
|
|
|
|
}
|
|
|
|
|
|
|
|
func findGo(t testing.TB) string {
|
|
|
|
goBin := filepath.Join(runtime.GOROOT(), "bin", "go"+exe())
|
|
|
|
if fi, err := os.Stat(goBin); err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
t.Fatalf("failed to find go at %v", goBin)
|
|
|
|
}
|
|
|
|
t.Fatalf("looking for go binary: %v", err)
|
|
|
|
} else if !fi.Mode().IsRegular() {
|
|
|
|
t.Fatalf("%v is unexpected %v", goBin, fi.Mode())
|
|
|
|
}
|
|
|
|
return goBin
|
|
|
|
}
|
|
|
|
|
|
|
|
func exe() string {
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
return ".exe"
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
2021-06-25 19:59:45 +00:00
|
|
|
|
|
|
|
// RunDERPAndSTUN runs a local DERP and STUN server for tests, returning the derpMap
|
|
|
|
// that clients should use. This creates resources that must be cleaned up with the
|
|
|
|
// returned cleanup function.
|
|
|
|
func RunDERPAndSTUN(t testing.TB, logf logger.Logf, ipAddress string) (derpMap *tailcfg.DERPMap) {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
var serverPrivateKey key.Private
|
|
|
|
if _, err := rand.Read(serverPrivateKey[:]); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
d := derp.NewServer(serverPrivateKey, logf)
|
|
|
|
|
|
|
|
httpsrv := httptest.NewUnstartedServer(derphttp.Handler(d))
|
|
|
|
httpsrv.Config.ErrorLog = logger.StdLogger(logf)
|
|
|
|
httpsrv.Config.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
|
|
|
|
httpsrv.StartTLS()
|
|
|
|
|
|
|
|
stunAddr, stunCleanup := stuntest.ServeWithPacketListener(t, nettype.Std{})
|
|
|
|
|
|
|
|
m := &tailcfg.DERPMap{
|
|
|
|
Regions: map[int]*tailcfg.DERPRegion{
|
|
|
|
1: {
|
|
|
|
RegionID: 1,
|
|
|
|
RegionCode: "test",
|
|
|
|
Nodes: []*tailcfg.DERPNode{
|
|
|
|
{
|
|
|
|
Name: "t1",
|
|
|
|
RegionID: 1,
|
|
|
|
HostName: ipAddress,
|
|
|
|
IPv4: ipAddress,
|
|
|
|
IPv6: "none",
|
|
|
|
STUNPort: stunAddr.Port,
|
|
|
|
DERPTestPort: httpsrv.Listener.Addr().(*net.TCPAddr).Port,
|
|
|
|
STUNTestIP: stunAddr.IP.String(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Cleanup(func() {
|
|
|
|
httpsrv.CloseClientConnections()
|
|
|
|
httpsrv.Close()
|
|
|
|
d.Close()
|
|
|
|
stunCleanup()
|
|
|
|
})
|
|
|
|
|
|
|
|
return m
|
|
|
|
}
|