@ -17,6 +17,7 @@ import (
"strconv"
"strconv"
"sync"
"sync"
"testing"
"testing"
"time"
"tailscale.com/control/controlbase"
"tailscale.com/control/controlbase"
"tailscale.com/net/socks5"
"tailscale.com/net/socks5"
@ -24,16 +25,28 @@ import (
"tailscale.com/types/key"
"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 ) {
func TestControlHTTP ( t * testing . T ) {
tests := [ ] struct {
tests := [ ] httpTestParam {
name string
proxy proxy
} {
// direct connection
// direct connection
{
{
name : "no_proxy" ,
name : "no_proxy" ,
proxy : nil ,
proxy : nil ,
} ,
} ,
// direct connection but port 80 is MITM'ed and broken
{
name : "port80_broken_mitm" ,
proxy : nil ,
makeHTTPHangAfterUpgrade : true ,
} ,
// SOCKS5
// SOCKS5
{
{
name : "socks5" ,
name : "socks5" ,
@ -97,12 +110,13 @@ func TestControlHTTP(t *testing.T) {
for _ , test := range tests {
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
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 ( )
client , server := key . NewMachine ( ) , key . NewMachine ( )
const testProtocolVersion = 1
const testProtocolVersion = 1
@ -133,7 +147,11 @@ func testControlHTTP(t *testing.T, proxy proxy) {
t . Fatalf ( "HTTPS listen: %v" , err )
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 )
go httpServer . Serve ( httpLn )
defer httpServer . Close ( )
defer httpServer . Close ( )
@ -144,19 +162,24 @@ func testControlHTTP(t *testing.T, proxy proxy) {
go httpsServer . ServeTLS ( httpsLn , "" , "" )
go httpsServer . ServeTLS ( httpsLn , "" , "" )
defer httpsServer . Close ( )
defer httpsServer . Close ( )
//ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
ctx := context . Background ( )
//defer cancel()
const debugTimeout = false
if debugTimeout {
var cancel context . CancelFunc
ctx , cancel = context . WithTimeout ( context . Background ( ) , 5 * time . Second )
defer cancel ( )
}
a := dialParams {
a := dialParams {
ctx : context . Background ( ) , //ctx,
host: "localhost" ,
host : "localhost" ,
h ttpPort: strconv . Itoa ( httpLn . Addr ( ) . ( * net . TCPAddr ) . Port ) ,
httpPort : strconv . Itoa ( httpLn . Addr ( ) . ( * net . TCPAddr ) . Port ) ,
http s Port: strconv . Itoa ( http s Ln. Addr ( ) . ( * net . TCPAddr ) . Port ) ,
httpsPort: strconv . Itoa ( httpsLn . Addr ( ) . ( * net . TCPAddr ) . Port ) ,
machineKey: client ,
machineKey: client ,
controlKey: server . Public ( ) ,
controlKey: server . Public ( ) ,
version: testProtocolVersion ,
version: testProtocolVersion ,
insecureTLS: true ,
insecureTLS: true ,
dialer: new ( tsdial . Dialer ) . SystemDial ,
dialer: new ( tsdial . Dialer ) . SystemDial ,
testFallbackDelay: 50 * time . Millisecond ,
}
}
if proxy != nil {
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 {
if err != nil {
t . Fatalf ( "dialing controlhttp: %v" , err )
t . Fatalf ( "dialing controlhttp: %v" , err )
}
}
@ -217,6 +240,7 @@ type proxy interface {
type socksProxy struct {
type socksProxy struct {
sync . Mutex
sync . Mutex
closed bool
proxy socks5 . Server
proxy socks5 . Server
ln net . Listener
ln net . Listener
clientConnAddrs map [ string ] bool // addrs of the local end of outgoing conns from proxy
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 . ln = ln
s . clientConnAddrs = map [ string ] bool { }
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
s . proxy . Dialer = s . dialAndRecord
go s . proxy . Serve ( ln )
go s . proxy . Serve ( ln )
return fmt . Sprintf ( "socks5://%s" , ln . Addr ( ) . String ( ) )
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 ( ) {
func ( s * socksProxy ) Close ( ) {
s . Lock ( )
s . Lock ( )
defer s . Unlock ( )
defer s . Unlock ( )
if s . closed {
return
}
s . closed = true
s . ln . Close ( )
s . ln . Close ( )
}
}
@ -400,3 +435,11 @@ EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==
Certificates : [ ] tls . Certificate { cert } ,
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 ( )
}