mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-08 09:07:44 +00:00
tstest/integration: add testNode.AwaitListening, DERP+STUN, improve proxy trap
Updates #1840
This commit is contained in:
parent
1f48d3556f
commit
3237e140c4
@ -26,11 +26,11 @@ type stunStats struct {
|
|||||||
readIPv6 int
|
readIPv6 int
|
||||||
}
|
}
|
||||||
|
|
||||||
func Serve(t *testing.T) (addr *net.UDPAddr, cleanupFn func()) {
|
func Serve(t testing.TB) (addr *net.UDPAddr, cleanupFn func()) {
|
||||||
return ServeWithPacketListener(t, nettype.Std{})
|
return ServeWithPacketListener(t, nettype.Std{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServeWithPacketListener(t *testing.T, ln nettype.PacketListener) (addr *net.UDPAddr, cleanupFn func()) {
|
func ServeWithPacketListener(t testing.TB, ln nettype.PacketListener) (addr *net.UDPAddr, cleanupFn func()) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
// TODO(crawshaw): use stats to test re-STUN logic
|
// TODO(crawshaw): use stats to test re-STUN logic
|
||||||
@ -52,7 +52,7 @@ func ServeWithPacketListener(t *testing.T, ln nettype.PacketListener) (addr *net
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSTUN(t *testing.T, pc net.PacketConn, stats *stunStats, done chan<- struct{}) {
|
func runSTUN(t testing.TB, pc net.PacketConn, stats *stunStats, done chan<- struct{}) {
|
||||||
defer close(done)
|
defer close(done)
|
||||||
|
|
||||||
var buf [64 << 10]byte
|
var buf [64 << 10]byte
|
||||||
|
@ -7,11 +7,14 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
crand "crypto/rand"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
@ -21,15 +24,38 @@
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go4.org/mem"
|
"go4.org/mem"
|
||||||
|
"tailscale.com/derp"
|
||||||
|
"tailscale.com/derp/derphttp"
|
||||||
|
"tailscale.com/net/stun/stuntest"
|
||||||
|
"tailscale.com/safesocket"
|
||||||
"tailscale.com/smallzstd"
|
"tailscale.com/smallzstd"
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/tstest"
|
"tailscale.com/tstest"
|
||||||
"tailscale.com/tstest/integration/testcontrol"
|
"tailscale.com/tstest/integration/testcontrol"
|
||||||
|
"tailscale.com/types/key"
|
||||||
|
"tailscale.com/types/logger"
|
||||||
|
"tailscale.com/types/nettype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var mainError atomic.Value // of error
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
v := m.Run()
|
||||||
|
if v != 0 {
|
||||||
|
os.Exit(v)
|
||||||
|
}
|
||||||
|
if err, ok := mainError.Load().(error); ok {
|
||||||
|
fmt.Fprintf(os.Stderr, "FAIL: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
func TestIntegration(t *testing.T) {
|
func TestIntegration(t *testing.T) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
t.Skip("not tested/working on Windows yet")
|
t.Skip("not tested/working on Windows yet")
|
||||||
@ -37,7 +63,7 @@ func TestIntegration(t *testing.T) {
|
|||||||
|
|
||||||
bins := buildTestBinaries(t)
|
bins := buildTestBinaries(t)
|
||||||
|
|
||||||
env := newTestEnv(bins)
|
env := newTestEnv(t, bins)
|
||||||
defer env.Close()
|
defer env.Close()
|
||||||
|
|
||||||
n1 := newTestNode(t, env)
|
n1 := newTestNode(t, env)
|
||||||
@ -45,16 +71,13 @@ func TestIntegration(t *testing.T) {
|
|||||||
dcmd := n1.StartDaemon(t)
|
dcmd := n1.StartDaemon(t)
|
||||||
defer dcmd.Process.Kill()
|
defer dcmd.Process.Kill()
|
||||||
|
|
||||||
var json []byte
|
n1.AwaitListening(t)
|
||||||
if err := tstest.WaitFor(20*time.Second, func() (err error) {
|
|
||||||
json, err = n1.Tailscale("status", "--json").CombinedOutput()
|
json, err := n1.Tailscale("status", "--json").CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("running tailscale status: %v, %s", err, json)
|
t.Fatalf("running tailscale status: %v, %s", err, json)
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
t.Logf("Status: %s", json)
|
||||||
|
|
||||||
if err := tstest.WaitFor(20*time.Second, func() error {
|
if err := tstest.WaitFor(20*time.Second, func() error {
|
||||||
const sub = `Program starting: `
|
const sub = `Program starting: `
|
||||||
@ -66,10 +89,16 @@ func TestIntegration(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Logf("Running up --login-server=%s ...", env.ControlServer.URL)
|
||||||
if err := n1.Tailscale("up", "--login-server="+env.ControlServer.URL).Run(); err != nil {
|
if err := n1.Tailscale("up", "--login-server="+env.ControlServer.URL).Run(); err != nil {
|
||||||
t.Fatalf("up: %v", err)
|
t.Fatalf("up: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if d, _ := time.ParseDuration(os.Getenv("TS_POST_UP_SLEEP")); d > 0 {
|
||||||
|
t.Logf("Sleeping for %v to give 'up' time to misbehave (https://github.com/tailscale/tailscale/issues/1840) ...", d)
|
||||||
|
time.Sleep(d)
|
||||||
|
}
|
||||||
|
|
||||||
var ip string
|
var ip string
|
||||||
if err := tstest.WaitFor(20*time.Second, func() error {
|
if err := tstest.WaitFor(20*time.Second, func() error {
|
||||||
out, err := n1.Tailscale("ip").Output()
|
out, err := n1.Tailscale("ip").Output()
|
||||||
@ -94,6 +123,10 @@ func TestIntegration(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("number of HTTP logcatcher requests: %v", env.LogCatcher.numRequests())
|
t.Logf("number of HTTP logcatcher requests: %v", env.LogCatcher.numRequests())
|
||||||
|
if err, ok := mainError.Load().(error); ok {
|
||||||
|
t.Error(err)
|
||||||
|
t.Logf("logs: %s", env.LogCatcher.logsString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// testBinaries are the paths to a tailscaled and tailscale binary.
|
// testBinaries are the paths to a tailscaled and tailscale binary.
|
||||||
@ -131,29 +164,40 @@ type testEnv struct {
|
|||||||
// so any accidental traffic leaving tailscaled goes here and fails
|
// so any accidental traffic leaving tailscaled goes here and fails
|
||||||
// the test. (localhost traffic bypasses HTTP_PROXY)
|
// the test. (localhost traffic bypasses HTTP_PROXY)
|
||||||
CatchBadTrafficServer *httptest.Server
|
CatchBadTrafficServer *httptest.Server
|
||||||
|
|
||||||
|
derpShutdown func()
|
||||||
}
|
}
|
||||||
|
|
||||||
// newTestEnv starts a bunch of services and returns a new test
|
// newTestEnv starts a bunch of services and returns a new test
|
||||||
// environment.
|
// environment.
|
||||||
//
|
//
|
||||||
// Call Close to shut everything down.
|
// Call Close to shut everything down.
|
||||||
func newTestEnv(bins *testBinaries) *testEnv {
|
func newTestEnv(t testing.TB, bins *testBinaries) *testEnv {
|
||||||
|
|
||||||
|
derpMap, derpShutdown := runDERPAndStun(t, t.Logf)
|
||||||
|
|
||||||
logc := new(logCatcher)
|
logc := new(logCatcher)
|
||||||
control := new(testcontrol.Server)
|
control := &testcontrol.Server{
|
||||||
return &testEnv{
|
DERPMap: derpMap,
|
||||||
|
}
|
||||||
|
e := &testEnv{
|
||||||
Binaries: bins,
|
Binaries: bins,
|
||||||
LogCatcher: logc,
|
LogCatcher: logc,
|
||||||
LogCatcherServer: httptest.NewServer(logc),
|
LogCatcherServer: httptest.NewServer(logc),
|
||||||
CatchBadTrafficServer: httptest.NewServer(http.HandlerFunc(catchUnexpectedTraffic)),
|
|
||||||
Control: control,
|
Control: control,
|
||||||
ControlServer: httptest.NewServer(control),
|
ControlServer: httptest.NewServer(control),
|
||||||
|
|
||||||
|
derpShutdown: derpShutdown,
|
||||||
}
|
}
|
||||||
|
e.CatchBadTrafficServer = httptest.NewServer(http.HandlerFunc(e.catchUnexpectedTraffic))
|
||||||
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *testEnv) Close() error {
|
func (e *testEnv) Close() error {
|
||||||
e.LogCatcherServer.Close()
|
e.LogCatcherServer.Close()
|
||||||
e.CatchBadTrafficServer.Close()
|
e.CatchBadTrafficServer.Close()
|
||||||
e.ControlServer.Close()
|
e.ControlServer.Close()
|
||||||
|
e.derpShutdown()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +243,21 @@ func (n *testNode) StartDaemon(t testing.TB) *exec.Cmd {
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AwaitListening waits for the tailscaled to be serving local clients
|
||||||
|
// over its localhost IPC mechanism. (Unix socket, etc)
|
||||||
|
func (n *testNode) AwaitListening(t testing.TB) {
|
||||||
|
if err := tstest.WaitFor(20*time.Second, func() (err error) {
|
||||||
|
c, err := safesocket.Connect(n.sockFile, 41112)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Close()
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Tailscale returns a command that runs the tailscale CLI with the provided arguments.
|
// Tailscale returns a command that runs the tailscale CLI with the provided arguments.
|
||||||
// It does not start the process.
|
// It does not start the process.
|
||||||
func (n *testNode) Tailscale(arg ...string) *exec.Cmd {
|
func (n *testNode) Tailscale(arg ...string) *exec.Cmd {
|
||||||
@ -317,12 +376,58 @@ type Entry struct {
|
|||||||
w.WriteHeader(200) // must have no content, but not a 204
|
w.WriteHeader(200) // must have no content, but not a 204
|
||||||
}
|
}
|
||||||
|
|
||||||
// catchUnexpectedTraffic is an HTTP proxy handler to blow up
|
// catchUnexpectedTraffic is an HTTP proxy handler to note whether any
|
||||||
// if any HTTP traffic tries to leave localhost from
|
// HTTP traffic tries to leave localhost from tailscaled. We don't
|
||||||
// tailscaled.
|
// expect any, so any request triggers a failure.
|
||||||
func catchUnexpectedTraffic(w http.ResponseWriter, r *http.Request) {
|
func (e *testEnv) catchUnexpectedTraffic(w http.ResponseWriter, r *http.Request) {
|
||||||
var got bytes.Buffer
|
var got bytes.Buffer
|
||||||
r.Write(&got)
|
r.Write(&got)
|
||||||
err := fmt.Errorf("unexpected HTTP proxy via proxy: %s", got.Bytes())
|
err := fmt.Errorf("unexpected HTTP proxy via proxy: %s", got.Bytes())
|
||||||
go panic(err)
|
mainError.Store(err)
|
||||||
|
log.Printf("Error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDERPAndStun(t testing.TB, logf logger.Logf) (derpMap *tailcfg.DERPMap, cleanup func()) {
|
||||||
|
var serverPrivateKey key.Private
|
||||||
|
if _, err := crand.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: "127.0.0.1", // to bypass HTTP proxy
|
||||||
|
IPv4: "127.0.0.1",
|
||||||
|
IPv6: "none",
|
||||||
|
STUNPort: stunAddr.Port,
|
||||||
|
DERPTestPort: httpsrv.Listener.Addr().(*net.TCPAddr).Port,
|
||||||
|
STUNTestIP: stunAddr.IP.String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup = func() {
|
||||||
|
httpsrv.CloseClientConnections()
|
||||||
|
httpsrv.Close()
|
||||||
|
d.Close()
|
||||||
|
stunCleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, cleanup
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user