mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-20 11:58:39 +00:00

We've seen a bunch of cases where a captive portal or network with a firewall will allow a connection to the control server, successfully perform the Noise upgrade, but then fail immediately after trying to send any data on that now-upgraded connection. In many of these cases, we only see this behaviour on the plaintext connection over port 80. This interacts poorly with our controlhttp.Dialer's logic, which first tries to dial over port 80 (to avoid the overhead of double-encrypting, Noise and TLS), and only falls back to port 443/TLS if the connnection cannot be established or upgraded. In such cases, we'd essentially fail to connect to the control server entirely since we'd never fall back to port 443 (since the Noise upgrade succeeded) but we'd get an EOF when trying to do anything with that connection. This could be solved with the TS_FORCE_NOISE_443 envknob, but that's not a great experience for our users. Updates #13597 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: I223dec0ae11b8f2946e3fb78dc49fcffc62470f3
116 lines
3.1 KiB
Go
116 lines
3.1 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package controlhttp
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
|
|
"tailscale.com/health"
|
|
"tailscale.com/net/dnscache"
|
|
"tailscale.com/net/netmon"
|
|
"tailscale.com/tailcfg"
|
|
"tailscale.com/tstime"
|
|
"tailscale.com/types/key"
|
|
"tailscale.com/types/logger"
|
|
)
|
|
|
|
const (
|
|
// upgradeHeader is the value of the Upgrade HTTP header used to
|
|
// indicate the Tailscale control protocol.
|
|
upgradeHeaderValue = "tailscale-control-protocol"
|
|
|
|
// handshakeHeaderName is the HTTP request header that can
|
|
// optionally contain base64-encoded initial handshake
|
|
// payload, to save an RTT.
|
|
handshakeHeaderName = "X-Tailscale-Handshake"
|
|
|
|
// serverUpgradePath is where the server-side HTTP handler to
|
|
// to do the protocol switch is located.
|
|
serverUpgradePath = "/ts2021"
|
|
)
|
|
|
|
// Dialer contains configuration on how to dial the Tailscale control server.
|
|
type Dialer struct {
|
|
// Hostname is the hostname to connect to, with no port number.
|
|
//
|
|
// This field is required.
|
|
Hostname string
|
|
|
|
// MachineKey contains the current machine's private key.
|
|
//
|
|
// This field is required.
|
|
MachineKey key.MachinePrivate
|
|
|
|
// ControlKey contains the expected public key for the control server.
|
|
//
|
|
// This field is required.
|
|
ControlKey key.MachinePublic
|
|
|
|
// ProtocolVersion is the expected protocol version to negotiate.
|
|
//
|
|
// This field is required.
|
|
ProtocolVersion uint16
|
|
|
|
// HTTPPort is the port number to use when making a HTTP connection.
|
|
//
|
|
// If not specified, this defaults to port 80.
|
|
HTTPPort string
|
|
|
|
// HTTPSPort is the port number to use when making a HTTPS connection.
|
|
//
|
|
// If not specified, this defaults to port 443.
|
|
HTTPSPort string
|
|
|
|
// Dialer is the dialer used to make outbound connections.
|
|
//
|
|
// If not specified, this defaults to net.Dialer.DialContext.
|
|
Dialer dnscache.DialContextFunc
|
|
|
|
// DNSCache is the caching Resolver used by this Dialer.
|
|
//
|
|
// If not specified, a new Resolver is created per attempt.
|
|
DNSCache *dnscache.Resolver
|
|
|
|
// Logf, if set, is a logging function to use; if unset, logs are
|
|
// dropped.
|
|
Logf logger.Logf
|
|
|
|
NetMon *netmon.Monitor
|
|
|
|
// HealthTracker, if non-nil, is the health tracker to use.
|
|
HealthTracker *health.Tracker
|
|
|
|
// DialPlan, if set, contains instructions from the control server on
|
|
// how to connect to it. If present, we will try the methods in this
|
|
// plan before falling back to DNS.
|
|
DialPlan *tailcfg.ControlDialPlan
|
|
|
|
// TestConn, if non-nil, is called with a dialed connection to verify
|
|
// that it's ready to serve real requests. If this function returns an
|
|
// error, the connection is closed and not used. If this function
|
|
// returns an error for all dialed connections, an error is returned
|
|
// from Dial.
|
|
TestConn func(*ClientConn) error
|
|
|
|
proxyFunc func(*http.Request) (*url.URL, error) // or nil
|
|
|
|
// For tests only
|
|
drainFinished chan struct{}
|
|
omitCertErrorLogging bool
|
|
testFallbackDelay time.Duration
|
|
|
|
// tstime.Clock is used instead of time package for methods such as time.Now.
|
|
// If not specified, will default to tstime.StdClock{}.
|
|
Clock tstime.Clock
|
|
}
|
|
|
|
func strDef(v1, v2 string) string {
|
|
if v1 != "" {
|
|
return v1
|
|
}
|
|
return v2
|
|
}
|