@ -40,57 +40,49 @@ import (
"tailscale.com/net/netutil"
"tailscale.com/net/netutil"
"tailscale.com/net/tlsdial"
"tailscale.com/net/tlsdial"
"tailscale.com/net/tshttpproxy"
"tailscale.com/net/tshttpproxy"
"tailscale.com/types/key"
)
)
// Dial connects to the HTTP server at host:httpPort, requests to switch to the
var stdDialer net . Dialer
// Tailscale control protocol, and returns an established control
// Dial connects to the HTTP server at this Dialer's Host:HTTPPort, requests to
// switch to the Tailscale control protocol, and returns an established control
// protocol connection.
// protocol connection.
//
//
// If Dial fails to connect using addr, it also tries to tunnel over
// If Dial fails to connect using HTTP, it also tries to tunnel over TLS to the
// TLS to host:https Port as a compatibility fallback.
// Dialer's Host:HTTPS Port as a compatibility fallback.
//
//
// The provided ctx is only used for the initial connection, until
// The provided ctx is only used for the initial connection, until
// Dial returns. It does not affect the connection once established.
// Dial returns. It does not affect the connection once established.
func Dial ( ctx context . Context , host string , httpPort string , httpsPort string , machineKey key . MachinePrivate , controlKey key . MachinePublic , protocolVersion uint16 , dialer dnscache . DialContextFunc ) ( * controlbase . Conn , error ) {
func ( a * Dialer ) Dial ( ctx context . Context ) ( * controlbase . Conn , error ) {
a := & dialParams {
if a . Hostname == "" {
host : host ,
return nil , errors . New ( "required Dialer.Hostname empty" )
httpPort : httpPort ,
httpsPort : httpsPort ,
machineKey : machineKey ,
controlKey : controlKey ,
version : protocolVersion ,
proxyFunc : tshttpproxy . ProxyFromEnvironment ,
dialer : dialer ,
}
}
return a . dial ( ctx )
return a . dial ( ctx )
}
}
type dialParams struct {
func ( a * Dialer ) logf ( format string , args ... any ) {
host string
if a . Logf != nil {
httpPort string
a . Logf ( format , args ... )
httpsPort string
}
machineKey key . MachinePrivate
}
controlKey key . MachinePublic
version uint16
proxyFunc func ( * http . Request ) ( * url . URL , error ) // or nil
dialer dnscache . DialContextFunc
// For tests only
func ( a * Dialer ) getProxyFunc ( ) func ( * http . Request ) ( * url . URL , error ) {
insecureTLS bool
if a . proxyFunc != nil {
testFallbackDelay time . Duration
return a . proxyFunc
}
return tshttpproxy . ProxyFromEnvironment
}
}
// httpsFallbackDelay is how long we'll wait for a. http Port to work before
// httpsFallbackDelay is how long we'll wait for a. HTTP Port to work before
// starting to try a. https Port.
// starting to try a. HTTPS Port.
func ( a * dialParams ) httpsFallbackDelay ( ) time . Duration {
func ( a * Dialer ) httpsFallbackDelay ( ) time . Duration {
if v := a . testFallbackDelay ; v != 0 {
if v := a . testFallbackDelay ; v != 0 {
return v
return v
}
}
return 500 * time . Millisecond
return 500 * time . Millisecond
}
}
func ( a * dialParams ) dial ( ctx context . Context ) ( * controlbase . Conn , error ) {
func ( a * Dialer ) dial ( ctx context . Context ) ( * controlbase . Conn , error ) {
// Create one shared context used by both port 80 and port 443 dials.
// Create one shared context used by both port 80 and port 443 dials.
// If port 80 is still in flight when 443 returns, this deferred cancel
// If port 80 is still in flight when 443 returns, this deferred cancel
// will stop the port 80 dial.
// will stop the port 80 dial.
@ -102,12 +94,12 @@ func (a *dialParams) dial(ctx context.Context) (*controlbase.Conn, error) {
// we'll speak Noise.
// we'll speak Noise.
u80 := & url . URL {
u80 := & url . URL {
Scheme : "http" ,
Scheme : "http" ,
Host : net . JoinHostPort ( a . host, a . httpPort ) ,
Host : net . JoinHostPort ( a . Hostname, strDef ( a . HTTPPort , "80" ) ) ,
Path : serverUpgradePath ,
Path : serverUpgradePath ,
}
}
u443 := & url . URL {
u443 := & url . URL {
Scheme : "https" ,
Scheme : "https" ,
Host : net . JoinHostPort ( a . host, a . httpsPort ) ,
Host : net . JoinHostPort ( a . Hostname, strDef ( a . HTTPSPort , "443" ) ) ,
Path : serverUpgradePath ,
Path : serverUpgradePath ,
}
}
@ -169,8 +161,8 @@ func (a *dialParams) dial(ctx context.Context) (*controlbase.Conn, error) {
}
}
// dialURL attempts to connect to the given URL.
// dialURL attempts to connect to the given URL.
func ( a * dialParams ) dialURL ( ctx context . Context , u * url . URL ) ( * controlbase . Conn , error ) {
func ( a * Dialer ) dialURL ( ctx context . Context , u * url . URL ) ( * controlbase . Conn , error ) {
init , cont , err := controlbase . ClientDeferred ( a . machineKey, a . controlKey , a . v ersion)
init , cont , err := controlbase . ClientDeferred ( a . MachineKey, a . ControlKey , a . ProtocolV ersion)
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
@ -189,26 +181,34 @@ func (a *dialParams) dialURL(ctx context.Context, u *url.URL) (*controlbase.Conn
// tryURLUpgrade connects to u, and tries to upgrade it to a net.Conn.
// tryURLUpgrade connects to u, and tries to upgrade it to a net.Conn.
//
//
// Only the provided ctx is used, not a.ctx.
// Only the provided ctx is used, not a.ctx.
func ( a * dialParams ) tryURLUpgrade ( ctx context . Context , u * url . URL , init [ ] byte ) ( net . Conn , error ) {
func ( a * Dialer ) tryURLUpgrade ( ctx context . Context , u * url . URL , init [ ] byte ) ( net . Conn , error ) {
dns := & dnscache . Resolver {
dns := & dnscache . Resolver {
Forward : dnscache . Get ( ) . Forward ,
Forward : dnscache . Get ( ) . Forward ,
LookupIPFallback : dnsfallback . Lookup ,
LookupIPFallback : dnsfallback . Lookup ,
UseLastGood : true ,
UseLastGood : true ,
}
}
var dialer dnscache . DialContextFunc
if a . Dialer != nil {
dialer = a . Dialer
} else {
dialer = stdDialer . DialContext
}
tr := http . DefaultTransport . ( * http . Transport ) . Clone ( )
tr := http . DefaultTransport . ( * http . Transport ) . Clone ( )
defer tr . CloseIdleConnections ( )
defer tr . CloseIdleConnections ( )
tr . Proxy = a . proxyFunc
tr . Proxy = a . getProxyFunc ( )
tshttpproxy . SetTransportGetProxyConnectHeader ( tr )
tshttpproxy . SetTransportGetProxyConnectHeader ( tr )
tr . DialContext = dnscache . Dialer ( a . dialer , dns )
tr . DialContext = dnscache . Dialer ( dialer, dns )
// Disable HTTP2, since h2 can't do protocol switching.
// Disable HTTP2, since h2 can't do protocol switching.
tr . TLSClientConfig . NextProtos = [ ] string { }
tr . TLSClientConfig . NextProtos = [ ] string { }
tr . TLSNextProto = map [ string ] func ( string , * tls . Conn ) http . RoundTripper { }
tr . TLSNextProto = map [ string ] func ( string , * tls . Conn ) http . RoundTripper { }
tr . TLSClientConfig = tlsdial . Config ( a . host , tr . TLSClientConfig )
tr . TLSClientConfig = tlsdial . Config ( a . Hostname , tr . TLSClientConfig )
if a . insecureTLS {
if a . insecureTLS {
tr . TLSClientConfig . InsecureSkipVerify = true
tr . TLSClientConfig . InsecureSkipVerify = true
tr . TLSClientConfig . VerifyConnection = nil
tr . TLSClientConfig . VerifyConnection = nil
}
}
tr . DialTLSContext = dnscache . TLSDialer ( a. dialer, dns , tr . TLSClientConfig )
tr . DialTLSContext = dnscache . TLSDialer ( dialer, dns , tr . TLSClientConfig )
tr . DisableCompression = true
tr . DisableCompression = true
// (mis)use httptrace to extract the underlying net.Conn from the
// (mis)use httptrace to extract the underlying net.Conn from the