mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-20 09:57:31 +00:00
control/controlhttp: don't assume port 80 upgrade response will work
Just because we get an HTTP upgrade response over port 80, don't assume we'll be able to do bi-di Noise over it. There might be a MITM corp proxy or anti-virus/firewall interfering. Do a bit more work to validate the connection before proceeding to give up on the TLS port 443 dial. Updates #4557 (probably fixes) Change-Id: I0e1bcc195af21ad3d360ffe79daead730dfd86f1 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:

committed by
Brad Fitzpatrick

parent
488e63979e
commit
1237000efe
@@ -17,6 +17,7 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"tailscale.com/control/controlbase"
|
||||
"tailscale.com/net/socks5"
|
||||
@@ -24,16 +25,28 @@ import (
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
type httpTestParam struct {
|
||||
name string
|
||||
proxy proxy
|
||||
|
||||
// makeHTTPHangAfterUpgrade makes the HTTP response hang after sending a
|
||||
// 101 switching protocols.
|
||||
makeHTTPHangAfterUpgrade bool
|
||||
}
|
||||
|
||||
func TestControlHTTP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
proxy proxy
|
||||
}{
|
||||
tests := []httpTestParam{
|
||||
// direct connection
|
||||
{
|
||||
name: "no_proxy",
|
||||
proxy: nil,
|
||||
},
|
||||
// direct connection but port 80 is MITM'ed and broken
|
||||
{
|
||||
name: "port80_broken_mitm",
|
||||
proxy: nil,
|
||||
makeHTTPHangAfterUpgrade: true,
|
||||
},
|
||||
// SOCKS5
|
||||
{
|
||||
name: "socks5",
|
||||
@@ -97,12 +110,13 @@ func TestControlHTTP(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
testControlHTTP(t, test.proxy)
|
||||
testControlHTTP(t, test)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testControlHTTP(t *testing.T, proxy proxy) {
|
||||
func testControlHTTP(t *testing.T, param httpTestParam) {
|
||||
proxy := param.proxy
|
||||
client, server := key.NewMachine(), key.NewMachine()
|
||||
|
||||
const testProtocolVersion = 1
|
||||
@@ -133,7 +147,11 @@ func testControlHTTP(t *testing.T, proxy proxy) {
|
||||
t.Fatalf("HTTPS listen: %v", err)
|
||||
}
|
||||
|
||||
httpServer := &http.Server{Handler: handler}
|
||||
var httpHandler http.Handler = handler
|
||||
if param.makeHTTPHangAfterUpgrade {
|
||||
httpHandler = http.HandlerFunc(brokenMITMHandler)
|
||||
}
|
||||
httpServer := &http.Server{Handler: httpHandler}
|
||||
go httpServer.Serve(httpLn)
|
||||
defer httpServer.Close()
|
||||
|
||||
@@ -144,19 +162,24 @@ func testControlHTTP(t *testing.T, proxy proxy) {
|
||||
go httpsServer.ServeTLS(httpsLn, "", "")
|
||||
defer httpsServer.Close()
|
||||
|
||||
//ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
//defer cancel()
|
||||
ctx := context.Background()
|
||||
const debugTimeout = false
|
||||
if debugTimeout {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
a := dialParams{
|
||||
ctx: context.Background(), //ctx,
|
||||
host: "localhost",
|
||||
httpPort: strconv.Itoa(httpLn.Addr().(*net.TCPAddr).Port),
|
||||
httpsPort: strconv.Itoa(httpsLn.Addr().(*net.TCPAddr).Port),
|
||||
machineKey: client,
|
||||
controlKey: server.Public(),
|
||||
version: testProtocolVersion,
|
||||
insecureTLS: true,
|
||||
dialer: new(tsdial.Dialer).SystemDial,
|
||||
host: "localhost",
|
||||
httpPort: strconv.Itoa(httpLn.Addr().(*net.TCPAddr).Port),
|
||||
httpsPort: strconv.Itoa(httpsLn.Addr().(*net.TCPAddr).Port),
|
||||
machineKey: client,
|
||||
controlKey: server.Public(),
|
||||
version: testProtocolVersion,
|
||||
insecureTLS: true,
|
||||
dialer: new(tsdial.Dialer).SystemDial,
|
||||
testFallbackDelay: 50 * time.Millisecond,
|
||||
}
|
||||
|
||||
if proxy != nil {
|
||||
@@ -175,7 +198,7 @@ func testControlHTTP(t *testing.T, proxy proxy) {
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := a.dial()
|
||||
conn, err := a.dial(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("dialing controlhttp: %v", err)
|
||||
}
|
||||
@@ -217,6 +240,7 @@ type proxy interface {
|
||||
|
||||
type socksProxy struct {
|
||||
sync.Mutex
|
||||
closed bool
|
||||
proxy socks5.Server
|
||||
ln net.Listener
|
||||
clientConnAddrs map[string]bool // addrs of the local end of outgoing conns from proxy
|
||||
@@ -232,7 +256,14 @@ func (s *socksProxy) Start(t *testing.T) (url string) {
|
||||
}
|
||||
s.ln = ln
|
||||
s.clientConnAddrs = map[string]bool{}
|
||||
s.proxy.Logf = t.Logf
|
||||
s.proxy.Logf = func(format string, a ...any) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
if s.closed {
|
||||
return
|
||||
}
|
||||
t.Logf(format, a...)
|
||||
}
|
||||
s.proxy.Dialer = s.dialAndRecord
|
||||
go s.proxy.Serve(ln)
|
||||
return fmt.Sprintf("socks5://%s", ln.Addr().String())
|
||||
@@ -241,6 +272,10 @@ func (s *socksProxy) Start(t *testing.T) (url string) {
|
||||
func (s *socksProxy) Close() {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
if s.closed {
|
||||
return
|
||||
}
|
||||
s.closed = true
|
||||
s.ln.Close()
|
||||
}
|
||||
|
||||
@@ -400,3 +435,11 @@ EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
}
|
||||
|
||||
func brokenMITMHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Upgrade", upgradeHeaderValue)
|
||||
w.Header().Set("Connection", "upgrade")
|
||||
w.WriteHeader(http.StatusSwitchingProtocols)
|
||||
w.(http.Flusher).Flush()
|
||||
<-r.Context().Done()
|
||||
}
|
||||
|
Reference in New Issue
Block a user