@ -5,6 +5,7 @@ package controlclient
import (
import (
"bytes"
"bytes"
"cmp"
"context"
"context"
"encoding/json"
"encoding/json"
"errors"
"errors"
@ -16,6 +17,7 @@ import (
"golang.org/x/net/http2"
"golang.org/x/net/http2"
"tailscale.com/control/controlhttp"
"tailscale.com/control/controlhttp"
"tailscale.com/envknob"
"tailscale.com/health"
"tailscale.com/health"
"tailscale.com/internal/noiseconn"
"tailscale.com/internal/noiseconn"
"tailscale.com/net/dnscache"
"tailscale.com/net/dnscache"
@ -28,6 +30,7 @@ import (
"tailscale.com/util/mak"
"tailscale.com/util/mak"
"tailscale.com/util/multierr"
"tailscale.com/util/multierr"
"tailscale.com/util/singleflight"
"tailscale.com/util/singleflight"
"tailscale.com/util/testenv"
)
)
// NoiseClient provides a http.Client to connect to tailcontrol over
// NoiseClient provides a http.Client to connect to tailcontrol over
@ -56,8 +59,8 @@ type NoiseClient struct {
privKey key . MachinePrivate
privKey key . MachinePrivate
serverPubKey key . MachinePublic
serverPubKey key . MachinePublic
host string // the host part of serverURL
host string // the host part of serverURL
httpPort string // the default port to cal l
httpPort string // the default port to dia l
httpsPort string // the fallback Noise-over-https port
httpsPort string // the fallback Noise-over-https port or empty if none
// dialPlan optionally returns a ControlDialPlan previously received
// dialPlan optionally returns a ControlDialPlan previously received
// from the control server; either the function or the return value can
// from the control server; either the function or the return value can
@ -104,6 +107,11 @@ type NoiseOpts struct {
DialPlan func ( ) * tailcfg . ControlDialPlan
DialPlan func ( ) * tailcfg . ControlDialPlan
}
}
// controlIsPlaintext is whether we should assume that the controlplane is only accessible
// over plaintext HTTP (as the first hop, before the ts2021 encryption begins).
// This is used by some tests which don't have a real TLS certificate.
var controlIsPlaintext = envknob . RegisterBool ( "TS_CONTROL_IS_PLAINTEXT_HTTP" )
// NewNoiseClient returns a new noiseClient for the provided server and machine key.
// NewNoiseClient returns a new noiseClient for the provided server and machine key.
// serverURL is of the form https://<host>:<port> (no trailing slash).
// serverURL is of the form https://<host>:<port> (no trailing slash).
//
//
@ -116,14 +124,17 @@ func NewNoiseClient(opts NoiseOpts) (*NoiseClient, error) {
}
}
var httpPort string
var httpPort string
var httpsPort string
var httpsPort string
if u. Port ( ) != "" {
if port := u. Port ( ) ; port != "" {
// If there is an explicit port specified, trust the scheme and hope for the best
// If there is an explicit port specified, trust the scheme and hope for the best
if u . Scheme == "http" {
if u . Scheme == "http" {
httpPort = u. Port ( )
httpPort = port
httpsPort = "443"
httpsPort = "443"
if ( testenv . InTest ( ) || controlIsPlaintext ( ) ) && ( u . Hostname ( ) == "127.0.0.1" || u . Hostname ( ) == "localhost" ) {
httpsPort = ""
}
} else {
} else {
httpPort = "80"
httpPort = "80"
httpsPort = u . Port ( )
httpsPort = port
}
}
} else {
} else {
// Otherwise, use the standard ports
// Otherwise, use the standard ports
@ -340,7 +351,7 @@ func (nc *NoiseClient) dial(ctx context.Context) (*noiseconn.Conn, error) {
clientConn , err := ( & controlhttp . Dialer {
clientConn , err := ( & controlhttp . Dialer {
Hostname : nc . host ,
Hostname : nc . host ,
HTTPPort : nc . httpPort ,
HTTPPort : nc . httpPort ,
HTTPSPort : nc. httpsPort ,
HTTPSPort : cmp. Or ( nc. httpsPort , controlhttp . NoPort ) ,
MachineKey : nc . privKey ,
MachineKey : nc . privKey ,
ControlKey : nc . serverPubKey ,
ControlKey : nc . serverPubKey ,
ProtocolVersion : uint16 ( tailcfg . CurrentCapabilityVersion ) ,
ProtocolVersion : uint16 ( tailcfg . CurrentCapabilityVersion ) ,