|
|
|
@ -15,17 +15,19 @@ import (
|
|
|
|
"net/http/httputil"
|
|
|
|
"net/http/httputil"
|
|
|
|
"net/netip"
|
|
|
|
"net/netip"
|
|
|
|
"net/url"
|
|
|
|
"net/url"
|
|
|
|
"runtime"
|
|
|
|
|
|
|
|
"slices"
|
|
|
|
"slices"
|
|
|
|
"strconv"
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"sync"
|
|
|
|
"testing"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"testing/synctest"
|
|
|
|
"time"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"tailscale.com/control/controlbase"
|
|
|
|
"tailscale.com/control/controlbase"
|
|
|
|
"tailscale.com/control/controlhttp/controlhttpcommon"
|
|
|
|
"tailscale.com/control/controlhttp/controlhttpcommon"
|
|
|
|
"tailscale.com/control/controlhttp/controlhttpserver"
|
|
|
|
"tailscale.com/control/controlhttp/controlhttpserver"
|
|
|
|
"tailscale.com/health"
|
|
|
|
"tailscale.com/health"
|
|
|
|
|
|
|
|
"tailscale.com/net/memnet"
|
|
|
|
"tailscale.com/net/netmon"
|
|
|
|
"tailscale.com/net/netmon"
|
|
|
|
"tailscale.com/net/netx"
|
|
|
|
"tailscale.com/net/netx"
|
|
|
|
"tailscale.com/net/socks5"
|
|
|
|
"tailscale.com/net/socks5"
|
|
|
|
@ -545,35 +547,13 @@ func brokenMITMHandler(clock tstime.Clock) http.HandlerFunc {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestDialPlan(t *testing.T) {
|
|
|
|
func TestDialPlan(t *testing.T) {
|
|
|
|
if runtime.GOOS != "linux" {
|
|
|
|
|
|
|
|
t.Skip("only works on Linux due to multiple localhost addresses")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
client, server := key.NewMachine(), key.NewMachine()
|
|
|
|
client, server := key.NewMachine(), key.NewMachine()
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
const (
|
|
|
|
testProtocolVersion = 1
|
|
|
|
testProtocolVersion = 1
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
getRandomPort := func() string {
|
|
|
|
makeHandler := func(t *testing.T, memNet *memnet.Network, name string, host netip.Addr, wrap func(http.Handler) http.Handler) {
|
|
|
|
ln, err := net.Listen("tcp", ":0")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("net.Listen: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
defer ln.Close()
|
|
|
|
|
|
|
|
_, port, err := net.SplitHostPort(ln.Addr().String())
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return port
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// We need consistent ports for each address; these are chosen
|
|
|
|
|
|
|
|
// randomly and we hope that they won't conflict during this test.
|
|
|
|
|
|
|
|
httpPort := getRandomPort()
|
|
|
|
|
|
|
|
httpsPort := getRandomPort()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
makeHandler := func(t *testing.T, name string, host netip.Addr, wrap func(http.Handler) http.Handler) {
|
|
|
|
|
|
|
|
done := make(chan struct{})
|
|
|
|
done := make(chan struct{})
|
|
|
|
t.Cleanup(func() {
|
|
|
|
t.Cleanup(func() {
|
|
|
|
close(done)
|
|
|
|
close(done)
|
|
|
|
@ -592,11 +572,11 @@ func TestDialPlan(t *testing.T) {
|
|
|
|
handler = wrap(handler)
|
|
|
|
handler = wrap(handler)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
httpLn, err := net.Listen("tcp", host.String()+":"+httpPort)
|
|
|
|
httpLn, err := memNet.Listen("tcp", host.String()+":80")
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("HTTP listen: %v", err)
|
|
|
|
t.Fatalf("HTTP listen: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
httpsLn, err := net.Listen("tcp", host.String()+":"+httpsPort)
|
|
|
|
httpsLn, err := memNet.Listen("tcp", host.String()+":443")
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("HTTPS listen: %v", err)
|
|
|
|
t.Fatalf("HTTPS listen: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -616,7 +596,6 @@ func TestDialPlan(t *testing.T) {
|
|
|
|
t.Cleanup(func() {
|
|
|
|
t.Cleanup(func() {
|
|
|
|
httpsServer.Close()
|
|
|
|
httpsServer.Close()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fallbackAddr := netip.MustParseAddr("127.0.0.1")
|
|
|
|
fallbackAddr := netip.MustParseAddr("127.0.0.1")
|
|
|
|
@ -686,74 +665,82 @@ func TestDialPlan(t *testing.T) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, tt := range testCases {
|
|
|
|
for _, tt := range testCases {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
// TODO(awly): replace this with tstest.NewClock and update the
|
|
|
|
synctest.Test(t, func(t *testing.T) {
|
|
|
|
// test to advance the clock correctly.
|
|
|
|
|
|
|
|
clock := tstime.StdClock{}
|
|
|
|
// Get the synctest clock way out to 2025 at least so the
|
|
|
|
makeHandler(t, "fallback", fallbackAddr, nil)
|
|
|
|
// net/http/httptest TLS client certs are valid?
|
|
|
|
makeHandler(t, "good", goodAddr, nil)
|
|
|
|
// TODO(bradfitz): this might not be necessary. Still debugging.
|
|
|
|
makeHandler(t, "other", otherAddr, nil)
|
|
|
|
time.Sleep(26 * 365 * 24 * time.Hour)
|
|
|
|
makeHandler(t, "other2", other2Addr, nil)
|
|
|
|
|
|
|
|
makeHandler(t, "broken", brokenAddr, func(h http.Handler) http.Handler {
|
|
|
|
var memNet memnet.Network
|
|
|
|
return brokenMITMHandler(clock)
|
|
|
|
|
|
|
|
})
|
|
|
|
clock := tstime.StdClock{}
|
|
|
|
|
|
|
|
makeHandler(t, &memNet, "fallback", fallbackAddr, nil)
|
|
|
|
|
|
|
|
makeHandler(t, &memNet, "good", goodAddr, nil)
|
|
|
|
|
|
|
|
makeHandler(t, &memNet, "other", otherAddr, nil)
|
|
|
|
|
|
|
|
makeHandler(t, &memNet, "other2", other2Addr, nil)
|
|
|
|
|
|
|
|
makeHandler(t, &memNet, "broken", brokenAddr, func(h http.Handler) http.Handler {
|
|
|
|
|
|
|
|
return brokenMITMHandler(clock)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dialer := closeTrackDialer{
|
|
|
|
|
|
|
|
t: t,
|
|
|
|
|
|
|
|
inner: memNet.Dial,
|
|
|
|
|
|
|
|
conns: make(map[*closeTrackConn]bool),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
defer dialer.Done()
|
|
|
|
|
|
|
|
|
|
|
|
dialer := closeTrackDialer{
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
t: t,
|
|
|
|
defer cancel()
|
|
|
|
inner: tsdial.NewDialer(netmon.NewStatic()).SystemDial,
|
|
|
|
|
|
|
|
conns: make(map[*closeTrackConn]bool),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
defer dialer.Done()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
// By default, we intentionally point to something that
|
|
|
|
defer cancel()
|
|
|
|
// we know won't connect, since we want a fallback to
|
|
|
|
|
|
|
|
// DNS to be an error.
|
|
|
|
|
|
|
|
host := "example.com"
|
|
|
|
|
|
|
|
if tt.allowFallback {
|
|
|
|
|
|
|
|
host = "localhost"
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// By default, we intentionally point to something that
|
|
|
|
a := &Dialer{
|
|
|
|
// we know won't connect, since we want a fallback to
|
|
|
|
Hostname: host,
|
|
|
|
// DNS to be an error.
|
|
|
|
MachineKey: client,
|
|
|
|
host := "example.com"
|
|
|
|
ControlKey: server.Public(),
|
|
|
|
if tt.allowFallback {
|
|
|
|
ProtocolVersion: testProtocolVersion,
|
|
|
|
host = "localhost"
|
|
|
|
Dialer: dialer.Dial,
|
|
|
|
}
|
|
|
|
Logf: t.Logf,
|
|
|
|
|
|
|
|
DialPlan: tt.plan,
|
|
|
|
|
|
|
|
proxyFunc: func(*http.Request) (*url.URL, error) { return nil, nil },
|
|
|
|
|
|
|
|
omitCertErrorLogging: true,
|
|
|
|
|
|
|
|
testFallbackDelay: 50 * time.Millisecond,
|
|
|
|
|
|
|
|
Clock: clock,
|
|
|
|
|
|
|
|
HealthTracker: health.NewTracker(eventbustest.NewBus(t)),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
drained := make(chan struct{})
|
|
|
|
conn, err := a.dial(ctx)
|
|
|
|
a := &Dialer{
|
|
|
|
if err != nil {
|
|
|
|
Hostname: host,
|
|
|
|
t.Fatalf("dialing controlhttp: %v", err)
|
|
|
|
HTTPPort: httpPort,
|
|
|
|
}
|
|
|
|
HTTPSPort: httpsPort,
|
|
|
|
defer conn.Close()
|
|
|
|
MachineKey: client,
|
|
|
|
|
|
|
|
ControlKey: server.Public(),
|
|
|
|
|
|
|
|
ProtocolVersion: testProtocolVersion,
|
|
|
|
|
|
|
|
Dialer: dialer.Dial,
|
|
|
|
|
|
|
|
Logf: t.Logf,
|
|
|
|
|
|
|
|
DialPlan: tt.plan,
|
|
|
|
|
|
|
|
proxyFunc: func(*http.Request) (*url.URL, error) { return nil, nil },
|
|
|
|
|
|
|
|
drainFinished: drained,
|
|
|
|
|
|
|
|
omitCertErrorLogging: true,
|
|
|
|
|
|
|
|
testFallbackDelay: 50 * time.Millisecond,
|
|
|
|
|
|
|
|
Clock: clock,
|
|
|
|
|
|
|
|
HealthTracker: health.NewTracker(eventbustest.NewBus(t)),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
conn, err := a.dial(ctx)
|
|
|
|
raddrStr := conn.RemoteAddr().String()
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("dialing controlhttp: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
defer conn.Close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
raddr := conn.RemoteAddr().(*net.TCPAddr)
|
|
|
|
raddrStr = strings.TrimSuffix(raddrStr, "|1") // memnet noise
|
|
|
|
|
|
|
|
raddrPort, err := netip.ParseAddrPort(raddrStr)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
t.Fatalf("parsing remote addr %q: %v", raddrStr, err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
got, ok := netip.AddrFromSlice(raddr.IP)
|
|
|
|
got := raddrPort.Addr()
|
|
|
|
if !ok {
|
|
|
|
if got != tt.want {
|
|
|
|
t.Errorf("invalid remote IP: %v", raddr.IP)
|
|
|
|
t.Errorf("got connection from %q; want %q", got, tt.want)
|
|
|
|
} else if got != tt.want {
|
|
|
|
} else {
|
|
|
|
t.Errorf("got connection from %q; want %q", got, tt.want)
|
|
|
|
t.Logf("successfully connected to %q", got)
|
|
|
|
} else {
|
|
|
|
}
|
|
|
|
t.Logf("successfully connected to %q", raddr.String())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Wait until our dialer drains so we can verify that
|
|
|
|
// Wait until our dialer drains so we can verify that
|
|
|
|
// all connections are closed.
|
|
|
|
// all connections are closed.
|
|
|
|
<-drained
|
|
|
|
synctest.Wait()
|
|
|
|
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|