mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-08 10:51:57 +01:00
control/controlclient: remove x/net/http2, use net/http
Saves 352 KB, removing one of our two HTTP/2 implementations linked into the binary. Fixes #17305 Updates #15015 Change-Id: I53a04b1f2687dca73c8541949465038b69aa6ade Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
c45f8813b4
commit
1d93bdce20
@ -687,7 +687,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
|||||||
tailscale.com/client/web from tailscale.com/ipn/ipnlocal
|
tailscale.com/client/web from tailscale.com/ipn/ipnlocal
|
||||||
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
||||||
tailscale.com/control/controlclient from tailscale.com/ipn/ipnlocal+
|
tailscale.com/control/controlclient from tailscale.com/ipn/ipnlocal+
|
||||||
tailscale.com/control/controlhttp from tailscale.com/control/controlclient
|
tailscale.com/control/controlhttp from tailscale.com/control/ts2021
|
||||||
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
||||||
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
|
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
|
||||||
tailscale.com/control/ts2021 from tailscale.com/control/controlclient
|
tailscale.com/control/ts2021 from tailscale.com/control/controlclient
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptrace"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -28,17 +29,18 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"golang.org/x/net/http2"
|
|
||||||
"tailscale.com/client/tailscale/apitype"
|
"tailscale.com/client/tailscale/apitype"
|
||||||
"tailscale.com/control/controlhttp"
|
|
||||||
"tailscale.com/control/ts2021"
|
"tailscale.com/control/ts2021"
|
||||||
"tailscale.com/feature"
|
"tailscale.com/feature"
|
||||||
_ "tailscale.com/feature/condregister/useproxy"
|
_ "tailscale.com/feature/condregister/useproxy"
|
||||||
|
"tailscale.com/health"
|
||||||
"tailscale.com/hostinfo"
|
"tailscale.com/hostinfo"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/net/ace"
|
"tailscale.com/net/ace"
|
||||||
|
"tailscale.com/net/dnscache"
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
|
"tailscale.com/net/tsdial"
|
||||||
"tailscale.com/paths"
|
"tailscale.com/paths"
|
||||||
"tailscale.com/safesocket"
|
"tailscale.com/safesocket"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
@ -1062,22 +1064,8 @@ func runTS2021(ctx context.Context, args []string) error {
|
|||||||
if err := json.Unmarshal(b, dialPlan); err != nil {
|
if err := json.Unmarshal(b, dialPlan); err != nil {
|
||||||
return fmt.Errorf("unmarshaling dial plan JSON file: %w", err)
|
return fmt.Errorf("unmarshaling dial plan JSON file: %w", err)
|
||||||
}
|
}
|
||||||
}
|
} else if ts2021Args.aceHost != "" {
|
||||||
|
dialPlan = &tailcfg.ControlDialPlan{
|
||||||
noiseDialer := &controlhttp.Dialer{
|
|
||||||
Hostname: ts2021Args.host,
|
|
||||||
HTTPPort: "80",
|
|
||||||
HTTPSPort: "443",
|
|
||||||
MachineKey: machinePrivate,
|
|
||||||
ControlKey: keys.PublicKey,
|
|
||||||
ProtocolVersion: uint16(ts2021Args.version),
|
|
||||||
DialPlan: dialPlan,
|
|
||||||
Dialer: dialFunc,
|
|
||||||
Logf: logf,
|
|
||||||
NetMon: netMon,
|
|
||||||
}
|
|
||||||
if ts2021Args.aceHost != "" {
|
|
||||||
noiseDialer.DialPlan = &tailcfg.ControlDialPlan{
|
|
||||||
Candidates: []tailcfg.ControlIPCandidate{
|
Candidates: []tailcfg.ControlIPCandidate{
|
||||||
{
|
{
|
||||||
ACEHost: ts2021Args.aceHost,
|
ACEHost: ts2021Args.aceHost,
|
||||||
@ -1086,9 +1074,25 @@ func runTS2021(ctx context.Context, args []string) error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opts := ts2021.ClientOpts{
|
||||||
|
ServerURL: "https://" + ts2021Args.host,
|
||||||
|
DialPlan: func() *tailcfg.ControlDialPlan {
|
||||||
|
return dialPlan
|
||||||
|
},
|
||||||
|
Logf: logf,
|
||||||
|
NetMon: netMon,
|
||||||
|
PrivKey: machinePrivate,
|
||||||
|
ServerPubKey: keys.PublicKey,
|
||||||
|
Dialer: tsdial.NewFromFuncForDebug(logf, dialFunc),
|
||||||
|
DNSCache: &dnscache.Resolver{},
|
||||||
|
HealthTracker: &health.Tracker{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: ProtocolVersion: uint16(ts2021Args.version),
|
||||||
const tries = 2
|
const tries = 2
|
||||||
for i := range tries {
|
for i := range tries {
|
||||||
err := tryConnect(ctx, keys.PublicKey, noiseDialer)
|
err := tryConnect(ctx, keys.PublicKey, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error on attempt %d/%d: %v", i+1, tries, err)
|
log.Printf("error on attempt %d/%d: %v", i+1, tries, err)
|
||||||
continue
|
continue
|
||||||
@ -1098,44 +1102,37 @@ func runTS2021(ctx context.Context, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryConnect(ctx context.Context, controlPublic key.MachinePublic, noiseDialer *controlhttp.Dialer) error {
|
func tryConnect(ctx context.Context, controlPublic key.MachinePublic, opts ts2021.ClientOpts) error {
|
||||||
conn, err := noiseDialer.Dial(ctx)
|
|
||||||
log.Printf("controlhttp.Dial = %p, %v", conn, err)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("did noise handshake")
|
|
||||||
|
|
||||||
gotPeer := conn.Peer()
|
ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
|
||||||
if gotPeer != controlPublic {
|
GotConn: func(ci httptrace.GotConnInfo) {
|
||||||
log.Printf("peer = %v, want %v", gotPeer, controlPublic)
|
log.Printf("GotConn: %T", ci.Conn)
|
||||||
return errors.New("key mismatch")
|
ncc, ok := ci.Conn.(*ts2021.Conn)
|
||||||
}
|
if !ok {
|
||||||
|
return
|
||||||
log.Printf("final underlying conn: %v / %v", conn.LocalAddr(), conn.RemoteAddr())
|
}
|
||||||
|
log.Printf("did noise handshake")
|
||||||
h2Transport, err := http2.ConfigureTransports(&http.Transport{
|
log.Printf("final underlying conn: %v / %v", ncc.LocalAddr(), ncc.RemoteAddr())
|
||||||
IdleConnTimeout: time.Second,
|
gotPeer := ncc.Peer()
|
||||||
|
if gotPeer != controlPublic {
|
||||||
|
log.Fatalf("peer = %v, want %v", gotPeer, controlPublic)
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("http2.ConfigureTransports: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, create a Noise conn over the existing conn.
|
nc, err := ts2021.NewClient(opts)
|
||||||
nc, err := ts2021.New(conn.Conn, h2Transport, 0, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("noiseconn.New: %w", err)
|
return fmt.Errorf("NewNoiseClient: %w", err)
|
||||||
}
|
}
|
||||||
defer nc.Close()
|
|
||||||
|
|
||||||
// Make a /whoami request to the server to verify that we can actually
|
// Make a /whoami request to the server to verify that we can actually
|
||||||
// communicate over the newly-established connection.
|
// communicate over the newly-established connection.
|
||||||
whoamiURL := "http://" + ts2021Args.host + "/machine/whoami"
|
whoamiURL := "https://" + ts2021Args.host + "/machine/whoami"
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", whoamiURL, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", whoamiURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
resp, err := nc.RoundTrip(req)
|
resp, err := nc.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("RoundTrip whoami request: %w", err)
|
return fmt.Errorf("RoundTrip whoami request: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
W 💣 github.com/dblohm7/wingoes/pe from tailscale.com/util/winutil/authenticode
|
W 💣 github.com/dblohm7/wingoes/pe from tailscale.com/util/winutil/authenticode
|
||||||
L github.com/fogleman/gg from tailscale.com/client/systray
|
L github.com/fogleman/gg from tailscale.com/client/systray
|
||||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||||
|
github.com/gaissmai/bart from tailscale.com/net/tsdial
|
||||||
|
github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+
|
||||||
|
github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart
|
||||||
github.com/go-json-experiment/json from tailscale.com/types/opt+
|
github.com/go-json-experiment/json from tailscale.com/types/opt+
|
||||||
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+
|
github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+
|
||||||
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+
|
github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+
|
||||||
@ -83,7 +86,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
tailscale.com/cmd/tailscale/cli/ffcomplete from tailscale.com/cmd/tailscale/cli
|
tailscale.com/cmd/tailscale/cli/ffcomplete from tailscale.com/cmd/tailscale/cli
|
||||||
tailscale.com/cmd/tailscale/cli/ffcomplete/internal from tailscale.com/cmd/tailscale/cli/ffcomplete
|
tailscale.com/cmd/tailscale/cli/ffcomplete/internal from tailscale.com/cmd/tailscale/cli/ffcomplete
|
||||||
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
||||||
tailscale.com/control/controlhttp from tailscale.com/cmd/tailscale/cli
|
tailscale.com/control/controlhttp from tailscale.com/control/ts2021
|
||||||
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
||||||
tailscale.com/control/ts2021 from tailscale.com/cmd/tailscale/cli
|
tailscale.com/control/ts2021 from tailscale.com/cmd/tailscale/cli
|
||||||
tailscale.com/derp from tailscale.com/derp/derphttp+
|
tailscale.com/derp from tailscale.com/derp/derphttp+
|
||||||
@ -119,7 +122,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
tailscale.com/net/netaddr from tailscale.com/ipn+
|
tailscale.com/net/netaddr from tailscale.com/ipn+
|
||||||
tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli
|
tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli
|
||||||
tailscale.com/net/neterror from tailscale.com/net/netcheck+
|
tailscale.com/net/neterror from tailscale.com/net/netcheck+
|
||||||
tailscale.com/net/netknob from tailscale.com/net/netns
|
tailscale.com/net/netknob from tailscale.com/net/netns+
|
||||||
💣 tailscale.com/net/netmon from tailscale.com/cmd/tailscale/cli+
|
💣 tailscale.com/net/netmon from tailscale.com/cmd/tailscale/cli+
|
||||||
💣 tailscale.com/net/netns from tailscale.com/derp/derphttp+
|
💣 tailscale.com/net/netns from tailscale.com/derp/derphttp+
|
||||||
tailscale.com/net/netutil from tailscale.com/client/local+
|
tailscale.com/net/netutil from tailscale.com/client/local+
|
||||||
@ -132,6 +135,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
tailscale.com/net/tlsdial from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/net/tlsdial from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/net/tlsdial/blockblame from tailscale.com/net/tlsdial
|
tailscale.com/net/tlsdial/blockblame from tailscale.com/net/tlsdial
|
||||||
tailscale.com/net/tsaddr from tailscale.com/client/web+
|
tailscale.com/net/tsaddr from tailscale.com/client/web+
|
||||||
|
tailscale.com/net/tsdial from tailscale.com/cmd/tailscale/cli+
|
||||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/feature/useproxy
|
💣 tailscale.com/net/tshttpproxy from tailscale.com/feature/useproxy
|
||||||
tailscale.com/net/udprelay/status from tailscale.com/client/local+
|
tailscale.com/net/udprelay/status from tailscale.com/client/local+
|
||||||
tailscale.com/paths from tailscale.com/client/local+
|
tailscale.com/paths from tailscale.com/client/local+
|
||||||
@ -229,13 +233,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
L golang.org/x/image/math/fixed from github.com/fogleman/gg+
|
L golang.org/x/image/math/fixed from github.com/fogleman/gg+
|
||||||
golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
||||||
golang.org/x/net/dns/dnsmessage from tailscale.com/appc+
|
golang.org/x/net/dns/dnsmessage from tailscale.com/appc+
|
||||||
golang.org/x/net/http/httpguts from golang.org/x/net/http2+
|
|
||||||
golang.org/x/net/http/httpproxy from tailscale.com/net/tshttpproxy
|
golang.org/x/net/http/httpproxy from tailscale.com/net/tshttpproxy
|
||||||
golang.org/x/net/http2 from tailscale.com/cmd/tailscale/cli+
|
|
||||||
golang.org/x/net/http2/hpack from golang.org/x/net/http2+
|
|
||||||
golang.org/x/net/icmp from tailscale.com/net/ping
|
golang.org/x/net/icmp from tailscale.com/net/ping
|
||||||
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
golang.org/x/net/idna from golang.org/x/net/http/httpproxy+
|
||||||
golang.org/x/net/internal/httpcommon from golang.org/x/net/http2
|
|
||||||
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
|
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
|
||||||
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
|
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
|
||||||
golang.org/x/net/internal/socks from golang.org/x/net/proxy
|
golang.org/x/net/internal/socks from golang.org/x/net/proxy
|
||||||
@ -342,7 +342,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
crypto/sha3 from crypto/internal/fips140hash
|
crypto/sha3 from crypto/internal/fips140hash
|
||||||
crypto/sha512 from crypto/ecdsa+
|
crypto/sha512 from crypto/ecdsa+
|
||||||
crypto/subtle from crypto/cipher+
|
crypto/subtle from crypto/cipher+
|
||||||
crypto/tls from golang.org/x/net/http2+
|
crypto/tls from net/http+
|
||||||
crypto/tls/internal/fips140tls from crypto/tls
|
crypto/tls/internal/fips140tls from crypto/tls
|
||||||
crypto/x509 from crypto/tls+
|
crypto/x509 from crypto/tls+
|
||||||
D crypto/x509/internal/macos from crypto/x509
|
D crypto/x509/internal/macos from crypto/x509
|
||||||
@ -441,14 +441,14 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
net from crypto/tls+
|
net from crypto/tls+
|
||||||
net/http from expvar+
|
net/http from expvar+
|
||||||
net/http/cgi from tailscale.com/cmd/tailscale/cli
|
net/http/cgi from tailscale.com/cmd/tailscale/cli
|
||||||
net/http/httptrace from golang.org/x/net/http2+
|
net/http/httptrace from net/http+
|
||||||
net/http/httputil from tailscale.com/client/web+
|
net/http/httputil from tailscale.com/client/web+
|
||||||
net/http/internal from net/http+
|
net/http/internal from net/http+
|
||||||
net/http/internal/ascii from net/http+
|
net/http/internal/ascii from net/http+
|
||||||
net/http/internal/httpcommon from net/http
|
net/http/internal/httpcommon from net/http
|
||||||
net/http/pprof from tailscale.com/tsweb
|
net/http/pprof from tailscale.com/tsweb
|
||||||
net/netip from go4.org/netipx+
|
net/netip from go4.org/netipx+
|
||||||
net/textproto from golang.org/x/net/http/httpguts+
|
net/textproto from github.com/coder/websocket+
|
||||||
net/url from crypto/x509+
|
net/url from crypto/x509+
|
||||||
os from crypto/internal/sysrand+
|
os from crypto/internal/sysrand+
|
||||||
os/exec from github.com/atotto/clipboard+
|
os/exec from github.com/atotto/clipboard+
|
||||||
|
|||||||
@ -19,7 +19,6 @@ func TestDeps(t *testing.T) {
|
|||||||
"gvisor.dev/gvisor/pkg/tcpip/header": "https://github.com/tailscale/tailscale/issues/9756",
|
"gvisor.dev/gvisor/pkg/tcpip/header": "https://github.com/tailscale/tailscale/issues/9756",
|
||||||
"tailscale.com/wgengine/filter": "brings in bart, etc",
|
"tailscale.com/wgengine/filter": "brings in bart, etc",
|
||||||
"github.com/bits-and-blooms/bitset": "unneeded in CLI",
|
"github.com/bits-and-blooms/bitset": "unneeded in CLI",
|
||||||
"github.com/gaissmai/bart": "unneeded in CLI",
|
|
||||||
"tailscale.com/net/ipset": "unneeded in CLI",
|
"tailscale.com/net/ipset": "unneeded in CLI",
|
||||||
},
|
},
|
||||||
}.Check(t)
|
}.Check(t)
|
||||||
|
|||||||
@ -41,7 +41,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
tailscale.com/cmd/tailscaled/childproc from tailscale.com/cmd/tailscaled
|
tailscale.com/cmd/tailscaled/childproc from tailscale.com/cmd/tailscaled
|
||||||
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
||||||
tailscale.com/control/controlclient from tailscale.com/cmd/tailscaled+
|
tailscale.com/control/controlclient from tailscale.com/cmd/tailscaled+
|
||||||
tailscale.com/control/controlhttp from tailscale.com/control/controlclient
|
tailscale.com/control/controlhttp from tailscale.com/control/ts2021
|
||||||
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
||||||
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
|
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
|
||||||
tailscale.com/control/ts2021 from tailscale.com/control/controlclient
|
tailscale.com/control/ts2021 from tailscale.com/control/controlclient
|
||||||
@ -212,12 +212,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
golang.org/x/exp/maps from tailscale.com/ipn/store/mem
|
golang.org/x/exp/maps from tailscale.com/ipn/store/mem
|
||||||
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
|
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
|
||||||
golang.org/x/net/dns/dnsmessage from tailscale.com/ipn/ipnlocal+
|
golang.org/x/net/dns/dnsmessage from tailscale.com/ipn/ipnlocal+
|
||||||
golang.org/x/net/http/httpguts from golang.org/x/net/http2+
|
golang.org/x/net/http/httpguts from tailscale.com/ipn/ipnlocal+
|
||||||
golang.org/x/net/http2 from tailscale.com/control/controlclient+
|
|
||||||
golang.org/x/net/http2/hpack from golang.org/x/net/http2+
|
|
||||||
golang.org/x/net/icmp from tailscale.com/net/ping
|
golang.org/x/net/icmp from tailscale.com/net/ping
|
||||||
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
golang.org/x/net/idna from golang.org/x/net/http/httpguts
|
||||||
golang.org/x/net/internal/httpcommon from golang.org/x/net/http2
|
|
||||||
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
|
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
|
||||||
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
|
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
|
||||||
golang.org/x/net/ipv4 from github.com/tailscale/wireguard-go/conn+
|
golang.org/x/net/ipv4 from github.com/tailscale/wireguard-go/conn+
|
||||||
@ -251,7 +248,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
bytes from bufio+
|
bytes from bufio+
|
||||||
cmp from encoding/json+
|
cmp from encoding/json+
|
||||||
compress/flate from compress/gzip
|
compress/flate from compress/gzip
|
||||||
compress/gzip from golang.org/x/net/http2+
|
compress/gzip from net/http
|
||||||
container/list from crypto/tls+
|
container/list from crypto/tls+
|
||||||
context from crypto/tls+
|
context from crypto/tls+
|
||||||
crypto from crypto/ecdh+
|
crypto from crypto/ecdh+
|
||||||
@ -313,7 +310,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
crypto/sha3 from crypto/internal/fips140hash
|
crypto/sha3 from crypto/internal/fips140hash
|
||||||
crypto/sha512 from crypto/ecdsa+
|
crypto/sha512 from crypto/ecdsa+
|
||||||
crypto/subtle from crypto/cipher+
|
crypto/subtle from crypto/cipher+
|
||||||
crypto/tls from golang.org/x/net/http2+
|
crypto/tls from net/http+
|
||||||
crypto/tls/internal/fips140tls from crypto/tls
|
crypto/tls/internal/fips140tls from crypto/tls
|
||||||
crypto/x509 from crypto/tls+
|
crypto/x509 from crypto/tls+
|
||||||
crypto/x509/pkix from crypto/x509
|
crypto/x509/pkix from crypto/x509
|
||||||
@ -391,7 +388,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
mime/quotedprintable from mime/multipart
|
mime/quotedprintable from mime/multipart
|
||||||
net from crypto/tls+
|
net from crypto/tls+
|
||||||
net/http from expvar+
|
net/http from expvar+
|
||||||
net/http/httptrace from golang.org/x/net/http2+
|
net/http/httptrace from net/http+
|
||||||
net/http/internal from net/http
|
net/http/internal from net/http
|
||||||
net/http/internal/ascii from net/http
|
net/http/internal/ascii from net/http
|
||||||
net/http/internal/httpcommon from net/http
|
net/http/internal/httpcommon from net/http
|
||||||
|
|||||||
@ -61,10 +61,10 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
tailscale.com/cmd/tailscaled/childproc from tailscale.com/cmd/tailscaled
|
tailscale.com/cmd/tailscaled/childproc from tailscale.com/cmd/tailscaled
|
||||||
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
||||||
tailscale.com/control/controlclient from tailscale.com/cmd/tailscaled+
|
tailscale.com/control/controlclient from tailscale.com/cmd/tailscaled+
|
||||||
tailscale.com/control/controlhttp from tailscale.com/control/controlclient+
|
tailscale.com/control/controlhttp from tailscale.com/control/ts2021
|
||||||
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
||||||
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
|
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
|
||||||
tailscale.com/control/ts2021 from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/control/ts2021 from tailscale.com/control/controlclient+
|
||||||
tailscale.com/derp from tailscale.com/derp/derphttp+
|
tailscale.com/derp from tailscale.com/derp/derphttp+
|
||||||
tailscale.com/derp/derpconst from tailscale.com/derp/derphttp+
|
tailscale.com/derp/derpconst from tailscale.com/derp/derphttp+
|
||||||
tailscale.com/derp/derphttp from tailscale.com/net/netcheck+
|
tailscale.com/derp/derphttp from tailscale.com/net/netcheck+
|
||||||
@ -239,12 +239,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
golang.org/x/exp/maps from tailscale.com/ipn/store/mem
|
golang.org/x/exp/maps from tailscale.com/ipn/store/mem
|
||||||
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
|
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
|
||||||
golang.org/x/net/dns/dnsmessage from tailscale.com/cmd/tailscale/cli+
|
golang.org/x/net/dns/dnsmessage from tailscale.com/cmd/tailscale/cli+
|
||||||
golang.org/x/net/http/httpguts from golang.org/x/net/http2+
|
golang.org/x/net/http/httpguts from tailscale.com/ipn/ipnlocal+
|
||||||
golang.org/x/net/http2 from tailscale.com/cmd/tailscale/cli+
|
|
||||||
golang.org/x/net/http2/hpack from golang.org/x/net/http2+
|
|
||||||
golang.org/x/net/icmp from tailscale.com/net/ping
|
golang.org/x/net/icmp from tailscale.com/net/ping
|
||||||
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
||||||
golang.org/x/net/internal/httpcommon from golang.org/x/net/http2
|
|
||||||
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
|
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
|
||||||
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
|
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
|
||||||
golang.org/x/net/ipv4 from github.com/tailscale/wireguard-go/conn+
|
golang.org/x/net/ipv4 from github.com/tailscale/wireguard-go/conn+
|
||||||
@ -279,7 +276,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
bytes from bufio+
|
bytes from bufio+
|
||||||
cmp from encoding/json+
|
cmp from encoding/json+
|
||||||
compress/flate from compress/gzip+
|
compress/flate from compress/gzip+
|
||||||
compress/gzip from golang.org/x/net/http2+
|
compress/gzip from net/http+
|
||||||
compress/zlib from image/png
|
compress/zlib from image/png
|
||||||
container/list from crypto/tls+
|
container/list from crypto/tls+
|
||||||
context from crypto/tls+
|
context from crypto/tls+
|
||||||
@ -342,7 +339,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
crypto/sha3 from crypto/internal/fips140hash
|
crypto/sha3 from crypto/internal/fips140hash
|
||||||
crypto/sha512 from crypto/ecdsa+
|
crypto/sha512 from crypto/ecdsa+
|
||||||
crypto/subtle from crypto/cipher+
|
crypto/subtle from crypto/cipher+
|
||||||
crypto/tls from golang.org/x/net/http2+
|
crypto/tls from net/http+
|
||||||
crypto/tls/internal/fips140tls from crypto/tls
|
crypto/tls/internal/fips140tls from crypto/tls
|
||||||
crypto/x509 from crypto/tls+
|
crypto/x509 from crypto/tls+
|
||||||
crypto/x509/pkix from crypto/x509
|
crypto/x509/pkix from crypto/x509
|
||||||
@ -425,7 +422,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
mime/quotedprintable from mime/multipart
|
mime/quotedprintable from mime/multipart
|
||||||
net from crypto/tls+
|
net from crypto/tls+
|
||||||
net/http from expvar+
|
net/http from expvar+
|
||||||
net/http/httptrace from golang.org/x/net/http2+
|
net/http/httptrace from net/http+
|
||||||
net/http/httputil from tailscale.com/cmd/tailscale/cli
|
net/http/httputil from tailscale.com/cmd/tailscale/cli
|
||||||
net/http/internal from net/http+
|
net/http/internal from net/http+
|
||||||
net/http/internal/ascii from net/http+
|
net/http/internal/ascii from net/http+
|
||||||
|
|||||||
@ -252,7 +252,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
tailscale.com/cmd/tailscaled/tailscaledhooks from tailscale.com/cmd/tailscaled+
|
tailscale.com/cmd/tailscaled/tailscaledhooks from tailscale.com/cmd/tailscaled+
|
||||||
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
||||||
tailscale.com/control/controlclient from tailscale.com/cmd/tailscaled+
|
tailscale.com/control/controlclient from tailscale.com/cmd/tailscaled+
|
||||||
tailscale.com/control/controlhttp from tailscale.com/control/controlclient
|
tailscale.com/control/controlhttp from tailscale.com/control/ts2021
|
||||||
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
||||||
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
|
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
|
||||||
tailscale.com/control/ts2021 from tailscale.com/control/controlclient
|
tailscale.com/control/ts2021 from tailscale.com/control/controlclient
|
||||||
@ -501,13 +501,10 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
golang.org/x/exp/maps from tailscale.com/ipn/store/mem+
|
golang.org/x/exp/maps from tailscale.com/ipn/store/mem+
|
||||||
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
|
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
|
||||||
golang.org/x/net/dns/dnsmessage from tailscale.com/appc+
|
golang.org/x/net/dns/dnsmessage from tailscale.com/appc+
|
||||||
golang.org/x/net/http/httpguts from golang.org/x/net/http2+
|
golang.org/x/net/http/httpguts from tailscale.com/ipn/ipnlocal+
|
||||||
golang.org/x/net/http/httpproxy from tailscale.com/net/tshttpproxy
|
golang.org/x/net/http/httpproxy from tailscale.com/net/tshttpproxy
|
||||||
golang.org/x/net/http2 from tailscale.com/control/controlclient+
|
|
||||||
golang.org/x/net/http2/hpack from golang.org/x/net/http2+
|
|
||||||
golang.org/x/net/icmp from tailscale.com/net/ping+
|
golang.org/x/net/icmp from tailscale.com/net/ping+
|
||||||
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
||||||
golang.org/x/net/internal/httpcommon from golang.org/x/net/http2
|
|
||||||
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
|
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
|
||||||
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
|
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
|
||||||
golang.org/x/net/internal/socks from golang.org/x/net/proxy
|
golang.org/x/net/internal/socks from golang.org/x/net/proxy
|
||||||
@ -551,7 +548,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
bytes from archive/tar+
|
bytes from archive/tar+
|
||||||
cmp from slices+
|
cmp from slices+
|
||||||
compress/flate from compress/gzip+
|
compress/flate from compress/gzip+
|
||||||
compress/gzip from golang.org/x/net/http2+
|
compress/gzip from github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding+
|
||||||
W compress/zlib from debug/pe
|
W compress/zlib from debug/pe
|
||||||
container/heap from github.com/jellydator/ttlcache/v3+
|
container/heap from github.com/jellydator/ttlcache/v3+
|
||||||
container/list from crypto/tls+
|
container/list from crypto/tls+
|
||||||
|
|||||||
@ -276,5 +276,8 @@ func TestMinTailscaledWithCLI(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
BadDeps: map[string]string{
|
||||||
|
"golang.org/x/net/http2": "unexpected x/net/http2 dep; tailscale/tailscale#17305",
|
||||||
|
},
|
||||||
}.Check(t)
|
}.Check(t)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -129,7 +129,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
|
|||||||
tailscale.com/client/web from tailscale.com/ipn/ipnlocal
|
tailscale.com/client/web from tailscale.com/ipn/ipnlocal
|
||||||
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
||||||
tailscale.com/control/controlclient from tailscale.com/ipn/ipnext+
|
tailscale.com/control/controlclient from tailscale.com/ipn/ipnext+
|
||||||
tailscale.com/control/controlhttp from tailscale.com/control/controlclient
|
tailscale.com/control/controlhttp from tailscale.com/control/ts2021
|
||||||
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
||||||
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
|
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
|
||||||
tailscale.com/control/ts2021 from tailscale.com/control/controlclient
|
tailscale.com/control/ts2021 from tailscale.com/control/controlclient
|
||||||
@ -335,13 +335,10 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
|
|||||||
golang.org/x/exp/maps from tailscale.com/ipn/store/mem+
|
golang.org/x/exp/maps from tailscale.com/ipn/store/mem+
|
||||||
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
|
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
|
||||||
golang.org/x/net/dns/dnsmessage from tailscale.com/appc+
|
golang.org/x/net/dns/dnsmessage from tailscale.com/appc+
|
||||||
golang.org/x/net/http/httpguts from golang.org/x/net/http2+
|
golang.org/x/net/http/httpguts from tailscale.com/ipn/ipnlocal+
|
||||||
golang.org/x/net/http/httpproxy from tailscale.com/net/tshttpproxy
|
golang.org/x/net/http/httpproxy from tailscale.com/net/tshttpproxy
|
||||||
golang.org/x/net/http2 from tailscale.com/control/controlclient+
|
|
||||||
golang.org/x/net/http2/hpack from golang.org/x/net/http2+
|
|
||||||
golang.org/x/net/icmp from github.com/prometheus-community/pro-bing+
|
golang.org/x/net/icmp from github.com/prometheus-community/pro-bing+
|
||||||
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
||||||
golang.org/x/net/internal/httpcommon from golang.org/x/net/http2
|
|
||||||
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
|
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
|
||||||
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
|
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
|
||||||
golang.org/x/net/internal/socks from golang.org/x/net/proxy
|
golang.org/x/net/internal/socks from golang.org/x/net/proxy
|
||||||
@ -385,7 +382,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
|
|||||||
bytes from bufio+
|
bytes from bufio+
|
||||||
cmp from encoding/json+
|
cmp from encoding/json+
|
||||||
compress/flate from compress/gzip+
|
compress/flate from compress/gzip+
|
||||||
compress/gzip from golang.org/x/net/http2+
|
compress/gzip from internal/profile+
|
||||||
W compress/zlib from debug/pe
|
W compress/zlib from debug/pe
|
||||||
container/heap from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
|
container/heap from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
|
||||||
container/list from crypto/tls+
|
container/list from crypto/tls+
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import (
|
|||||||
|
|
||||||
"go4.org/mem"
|
"go4.org/mem"
|
||||||
"tailscale.com/control/controlknobs"
|
"tailscale.com/control/controlknobs"
|
||||||
|
"tailscale.com/control/ts2021"
|
||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/feature"
|
"tailscale.com/feature"
|
||||||
"tailscale.com/feature/buildfeatures"
|
"tailscale.com/feature/buildfeatures"
|
||||||
@ -95,8 +96,8 @@ type Direct struct {
|
|||||||
serverLegacyKey key.MachinePublic // original ("legacy") nacl crypto_box-based public key; only used for signRegisterRequest on Windows now
|
serverLegacyKey key.MachinePublic // original ("legacy") nacl crypto_box-based public key; only used for signRegisterRequest on Windows now
|
||||||
serverNoiseKey key.MachinePublic
|
serverNoiseKey key.MachinePublic
|
||||||
|
|
||||||
sfGroup singleflight.Group[struct{}, *NoiseClient] // protects noiseClient creation.
|
sfGroup singleflight.Group[struct{}, *ts2021.Client] // protects noiseClient creation.
|
||||||
noiseClient *NoiseClient
|
noiseClient *ts2021.Client
|
||||||
|
|
||||||
persist persist.PersistView
|
persist persist.PersistView
|
||||||
authKey string
|
authKey string
|
||||||
@ -329,7 +330,7 @@ func NewDirect(opts Options) (*Direct, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if opts.NoiseTestClient != nil {
|
if opts.NoiseTestClient != nil {
|
||||||
c.noiseClient = &NoiseClient{
|
c.noiseClient = &ts2021.Client{
|
||||||
Client: opts.NoiseTestClient,
|
Client: opts.NoiseTestClient,
|
||||||
}
|
}
|
||||||
c.serverNoiseKey = key.NewMachine().Public() // prevent early error before hitting test client
|
c.serverNoiseKey = key.NewMachine().Public() // prevent early error before hitting test client
|
||||||
@ -359,9 +360,7 @@ func (c *Direct) Close() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.noiseClient = nil
|
c.noiseClient = nil
|
||||||
if tr, ok := c.httpc.Transport.(*http.Transport); ok {
|
c.httpc.CloseIdleConnections()
|
||||||
tr.CloseIdleConnections()
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -703,8 +702,8 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return regen, opt.URL, nil, err
|
return regen, opt.URL, nil, err
|
||||||
}
|
}
|
||||||
addLBHeader(req, request.OldNodeKey)
|
ts2021.AddLBHeader(req, request.OldNodeKey)
|
||||||
addLBHeader(req, request.NodeKey)
|
ts2021.AddLBHeader(req, request.NodeKey)
|
||||||
|
|
||||||
res, err := httpc.Do(req)
|
res, err := httpc.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1012,7 +1011,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
addLBHeader(req, nodeKey)
|
ts2021.AddLBHeader(req, nodeKey)
|
||||||
|
|
||||||
res, err := httpc.Do(req)
|
res, err := httpc.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1507,7 +1506,7 @@ func sleepAsRequested(ctx context.Context, logf logger.Logf, d time.Duration, cl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getNoiseClient returns the noise client, creating one if one doesn't exist.
|
// getNoiseClient returns the noise client, creating one if one doesn't exist.
|
||||||
func (c *Direct) getNoiseClient() (*NoiseClient, error) {
|
func (c *Direct) getNoiseClient() (*ts2021.Client, error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
serverNoiseKey := c.serverNoiseKey
|
serverNoiseKey := c.serverNoiseKey
|
||||||
nc := c.noiseClient
|
nc := c.noiseClient
|
||||||
@ -1522,13 +1521,13 @@ func (c *Direct) getNoiseClient() (*NoiseClient, error) {
|
|||||||
if c.dialPlan != nil {
|
if c.dialPlan != nil {
|
||||||
dp = c.dialPlan.Load
|
dp = c.dialPlan.Load
|
||||||
}
|
}
|
||||||
nc, err, _ := c.sfGroup.Do(struct{}{}, func() (*NoiseClient, error) {
|
nc, err, _ := c.sfGroup.Do(struct{}{}, func() (*ts2021.Client, error) {
|
||||||
k, err := c.getMachinePrivKey()
|
k, err := c.getMachinePrivKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.logf("[v1] creating new noise client")
|
c.logf("[v1] creating new noise client")
|
||||||
nc, err := NewNoiseClient(NoiseOpts{
|
nc, err := ts2021.NewClient(ts2021.ClientOpts{
|
||||||
PrivKey: k,
|
PrivKey: k,
|
||||||
ServerPubKey: serverNoiseKey,
|
ServerPubKey: serverNoiseKey,
|
||||||
ServerURL: c.serverURL,
|
ServerURL: c.serverURL,
|
||||||
@ -1562,7 +1561,7 @@ func (c *Direct) setDNSNoise(ctx context.Context, req *tailcfg.SetDNSRequest) er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
res, err := nc.post(ctx, "/machine/set-dns", newReq.NodeKey, &newReq)
|
res, err := nc.Post(ctx, "/machine/set-dns", newReq.NodeKey, &newReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1696,7 +1695,7 @@ func (c *Direct) ReportWarnableChange(w *health.Warnable, us *health.UnhealthySt
|
|||||||
// Best effort, no logging:
|
// Best effort, no logging:
|
||||||
ctx, cancel := context.WithTimeout(c.closedCtx, 5*time.Second)
|
ctx, cancel := context.WithTimeout(c.closedCtx, 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
res, err := np.post(ctx, "/machine/update-health", nodeKey, req)
|
res, err := np.Post(ctx, "/machine/update-health", nodeKey, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1741,7 +1740,7 @@ func (c *Direct) SetDeviceAttrs(ctx context.Context, attrs tailcfg.AttrUpdate) e
|
|||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
res, err := nc.doWithBody(ctx, "PATCH", "/machine/set-device-attr", nodeKey, req)
|
res, err := nc.DoWithBody(ctx, "PATCH", "/machine/set-device-attr", nodeKey, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1782,7 +1781,7 @@ func (c *Direct) sendAuditLog(ctx context.Context, auditLog tailcfg.AuditLogRequ
|
|||||||
panic("tainted client")
|
panic("tainted client")
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := nc.post(ctx, "/machine/audit-log", nodeKey, req)
|
res, err := nc.Post(ctx, "/machine/audit-log", nodeKey, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %w", errHTTPPostFailure, err)
|
return fmt.Errorf("%w: %w", errHTTPPostFailure, err)
|
||||||
}
|
}
|
||||||
@ -1794,12 +1793,6 @@ func (c *Direct) sendAuditLog(ctx context.Context, auditLog tailcfg.AuditLogRequ
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addLBHeader(req *http.Request, nodeKey key.NodePublic) {
|
|
||||||
if !nodeKey.IsZero() {
|
|
||||||
req.Header.Add(tailcfg.LBHeader, nodeKey.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeScreenTimeDetectingDialFunc returns dialFunc, optionally wrapped (on
|
// makeScreenTimeDetectingDialFunc returns dialFunc, optionally wrapped (on
|
||||||
// Apple systems) with a func that sets the returned atomic.Bool for whether
|
// Apple systems) with a func that sets the returned atomic.Bool for whether
|
||||||
// Screen Time seemed to intercept the connection.
|
// Screen Time seemed to intercept the connection.
|
||||||
|
|||||||
@ -1,394 +0,0 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
package controlclient
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"cmp"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"math"
|
|
||||||
"net/http"
|
|
||||||
"net/netip"
|
|
||||||
"net/url"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
|
||||||
"tailscale.com/control/controlhttp"
|
|
||||||
"tailscale.com/control/ts2021"
|
|
||||||
"tailscale.com/health"
|
|
||||||
"tailscale.com/net/dnscache"
|
|
||||||
"tailscale.com/net/netmon"
|
|
||||||
"tailscale.com/net/tsdial"
|
|
||||||
"tailscale.com/tailcfg"
|
|
||||||
"tailscale.com/tstime"
|
|
||||||
"tailscale.com/types/key"
|
|
||||||
"tailscale.com/types/logger"
|
|
||||||
"tailscale.com/util/mak"
|
|
||||||
"tailscale.com/util/singleflight"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NoiseClient provides a http.Client to connect to tailcontrol over
|
|
||||||
// the ts2021 protocol.
|
|
||||||
type NoiseClient struct {
|
|
||||||
// Client is an HTTP client to talk to the coordination server.
|
|
||||||
// It automatically makes a new Noise connection as needed.
|
|
||||||
// It does not support node key proofs. To do that, call
|
|
||||||
// noiseClient.getConn instead to make a connection.
|
|
||||||
*http.Client
|
|
||||||
|
|
||||||
// h2t is the HTTP/2 transport we use a bit to create new
|
|
||||||
// *http2.ClientConns. We don't use its connection pool and we don't use its
|
|
||||||
// dialing. We use it for exactly one reason: its idle timeout that can only
|
|
||||||
// be configured via the HTTP/1 config. And then we call NewClientConn (with
|
|
||||||
// an existing Noise connection) on the http2.Transport which sets up an
|
|
||||||
// http2.ClientConn using that idle timeout from an http1.Transport.
|
|
||||||
h2t *http2.Transport
|
|
||||||
|
|
||||||
// sfDial ensures that two concurrent requests for a noise connection only
|
|
||||||
// produce one shared one between the two callers.
|
|
||||||
sfDial singleflight.Group[struct{}, *ts2021.Conn]
|
|
||||||
|
|
||||||
dialer *tsdial.Dialer
|
|
||||||
dnsCache *dnscache.Resolver
|
|
||||||
privKey key.MachinePrivate
|
|
||||||
serverPubKey key.MachinePublic
|
|
||||||
host string // the host part of serverURL
|
|
||||||
httpPort string // the default port to dial
|
|
||||||
httpsPort string // the fallback Noise-over-https port or empty if none
|
|
||||||
|
|
||||||
// dialPlan optionally returns a ControlDialPlan previously received
|
|
||||||
// from the control server; either the function or the return value can
|
|
||||||
// be nil.
|
|
||||||
dialPlan func() *tailcfg.ControlDialPlan
|
|
||||||
|
|
||||||
logf logger.Logf
|
|
||||||
netMon *netmon.Monitor
|
|
||||||
health *health.Tracker
|
|
||||||
|
|
||||||
// mu only protects the following variables.
|
|
||||||
mu sync.Mutex
|
|
||||||
closed bool
|
|
||||||
last *ts2021.Conn // or nil
|
|
||||||
nextID int
|
|
||||||
connPool map[int]*ts2021.Conn // active connections not yet closed; see ts2021.Conn.Close
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoiseOpts contains options for the NewNoiseClient function. All fields are
|
|
||||||
// required unless otherwise specified.
|
|
||||||
type NoiseOpts struct {
|
|
||||||
// PrivKey is this node's private key.
|
|
||||||
PrivKey key.MachinePrivate
|
|
||||||
// ServerPubKey is the public key of the server.
|
|
||||||
ServerPubKey key.MachinePublic
|
|
||||||
// ServerURL is the URL of the server to connect to.
|
|
||||||
ServerURL string
|
|
||||||
// Dialer's SystemDial function is used to connect to the server.
|
|
||||||
Dialer *tsdial.Dialer
|
|
||||||
// DNSCache is the caching Resolver to use to connect to the server.
|
|
||||||
//
|
|
||||||
// This field can be nil.
|
|
||||||
DNSCache *dnscache.Resolver
|
|
||||||
// Logf is the log function to use. This field can be nil.
|
|
||||||
Logf logger.Logf
|
|
||||||
// NetMon is the network monitor that, if set, will be used to get the
|
|
||||||
// network interface state. This field can be nil; if so, the current
|
|
||||||
// state will be looked up dynamically.
|
|
||||||
NetMon *netmon.Monitor
|
|
||||||
// HealthTracker, if non-nil, is the health tracker to use.
|
|
||||||
HealthTracker *health.Tracker
|
|
||||||
// DialPlan, if set, is a function that should return an explicit plan
|
|
||||||
// on how to connect to the server.
|
|
||||||
DialPlan func() *tailcfg.ControlDialPlan
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNoiseClient returns a new noiseClient for the provided server and machine key.
|
|
||||||
// serverURL is of the form https://<host>:<port> (no trailing slash).
|
|
||||||
//
|
|
||||||
// netMon may be nil, if non-nil it's used to do faster interface lookups.
|
|
||||||
// dialPlan may be nil
|
|
||||||
func NewNoiseClient(opts NoiseOpts) (*NoiseClient, error) {
|
|
||||||
logf := opts.Logf
|
|
||||||
u, err := url.Parse(opts.ServerURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Scheme != "http" && u.Scheme != "https" {
|
|
||||||
return nil, errors.New("invalid ServerURL scheme, must be http or https")
|
|
||||||
}
|
|
||||||
|
|
||||||
var httpPort string
|
|
||||||
var httpsPort string
|
|
||||||
addr, _ := netip.ParseAddr(u.Hostname())
|
|
||||||
isPrivateHost := addr.IsPrivate() || addr.IsLoopback() || u.Hostname() == "localhost"
|
|
||||||
if port := u.Port(); port != "" {
|
|
||||||
// If there is an explicit port specified, entirely rely on the scheme,
|
|
||||||
// unless it's http with a private host in which case we never try using HTTPS.
|
|
||||||
if u.Scheme == "https" {
|
|
||||||
httpPort = ""
|
|
||||||
httpsPort = port
|
|
||||||
} else if u.Scheme == "http" {
|
|
||||||
httpPort = port
|
|
||||||
httpsPort = "443"
|
|
||||||
if isPrivateHost {
|
|
||||||
logf("setting empty HTTPS port with http scheme and private host %s", u.Hostname())
|
|
||||||
httpsPort = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if u.Scheme == "http" && isPrivateHost {
|
|
||||||
// Whenever the scheme is http and the hostname is an IP address, do not set the HTTPS port,
|
|
||||||
// as there cannot be a TLS certificate issued for an IP, unless it's a public IP.
|
|
||||||
httpPort = "80"
|
|
||||||
httpsPort = ""
|
|
||||||
} else {
|
|
||||||
// Otherwise, use the standard ports
|
|
||||||
httpPort = "80"
|
|
||||||
httpsPort = "443"
|
|
||||||
}
|
|
||||||
|
|
||||||
np := &NoiseClient{
|
|
||||||
serverPubKey: opts.ServerPubKey,
|
|
||||||
privKey: opts.PrivKey,
|
|
||||||
host: u.Hostname(),
|
|
||||||
httpPort: httpPort,
|
|
||||||
httpsPort: httpsPort,
|
|
||||||
dialer: opts.Dialer,
|
|
||||||
dnsCache: opts.DNSCache,
|
|
||||||
dialPlan: opts.DialPlan,
|
|
||||||
logf: opts.Logf,
|
|
||||||
netMon: opts.NetMon,
|
|
||||||
health: opts.HealthTracker,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the HTTP/2 Transport using a net/http.Transport
|
|
||||||
// (which only does HTTP/1) because it's the only way to
|
|
||||||
// configure certain properties on the http2.Transport. But we
|
|
||||||
// never actually use the net/http.Transport for any HTTP/1
|
|
||||||
// requests.
|
|
||||||
h2Transport, err := http2.ConfigureTransports(&http.Transport{
|
|
||||||
IdleConnTimeout: time.Minute,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
np.h2t = h2Transport
|
|
||||||
|
|
||||||
np.Client = &http.Client{Transport: np}
|
|
||||||
return np, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// contextErr is an error that wraps another error and is used to indicate that
|
|
||||||
// the error was because a context expired.
|
|
||||||
type contextErr struct {
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e contextErr) Error() string {
|
|
||||||
return e.err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e contextErr) Unwrap() error {
|
|
||||||
return e.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// getConn returns a ts2021.Conn that can be used to make requests to the
|
|
||||||
// coordination server. It may return a cached connection or create a new one.
|
|
||||||
// Dials are singleflighted, so concurrent calls to getConn may only dial once.
|
|
||||||
// As such, context values may not be respected as there are no guarantees that
|
|
||||||
// the context passed to getConn is the same as the context passed to dial.
|
|
||||||
func (nc *NoiseClient) getConn(ctx context.Context) (*ts2021.Conn, error) {
|
|
||||||
nc.mu.Lock()
|
|
||||||
if last := nc.last; last != nil && last.CanTakeNewRequest() {
|
|
||||||
nc.mu.Unlock()
|
|
||||||
return last, nil
|
|
||||||
}
|
|
||||||
nc.mu.Unlock()
|
|
||||||
|
|
||||||
for {
|
|
||||||
// We singeflight the dial to avoid making multiple connections, however
|
|
||||||
// that means that we can't simply cancel the dial if the context is
|
|
||||||
// canceled. Instead, we have to additionally check that the context
|
|
||||||
// which was canceled is our context and retry if our context is still
|
|
||||||
// valid.
|
|
||||||
conn, err, _ := nc.sfDial.Do(struct{}{}, func() (*ts2021.Conn, error) {
|
|
||||||
c, err := nc.dial(ctx)
|
|
||||||
if err != nil {
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return nil, contextErr{ctx.Err()}
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
})
|
|
||||||
var ce contextErr
|
|
||||||
if err == nil || !errors.As(err, &ce) {
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
if ctx.Err() == nil {
|
|
||||||
// The dial failed because of a context error, but our context
|
|
||||||
// is still valid. Retry.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// The dial failed because our context was canceled. Return the
|
|
||||||
// underlying error.
|
|
||||||
return nil, ce.Unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nc *NoiseClient) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
ctx := req.Context()
|
|
||||||
conn, err := nc.getConn(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return conn.RoundTrip(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// connClosed removes the connection with the provided ID from the pool
|
|
||||||
// of active connections.
|
|
||||||
func (nc *NoiseClient) connClosed(id int) {
|
|
||||||
nc.mu.Lock()
|
|
||||||
defer nc.mu.Unlock()
|
|
||||||
conn := nc.connPool[id]
|
|
||||||
if conn != nil {
|
|
||||||
delete(nc.connPool, id)
|
|
||||||
if nc.last == conn {
|
|
||||||
nc.last = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes all the underlying noise connections.
|
|
||||||
// It is a no-op and returns nil if the connection is already closed.
|
|
||||||
func (nc *NoiseClient) Close() error {
|
|
||||||
nc.mu.Lock()
|
|
||||||
nc.closed = true
|
|
||||||
conns := nc.connPool
|
|
||||||
nc.connPool = nil
|
|
||||||
nc.mu.Unlock()
|
|
||||||
|
|
||||||
var errs []error
|
|
||||||
for _, c := range conns {
|
|
||||||
if err := c.Close(); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.Join(errs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dial opens a new connection to tailcontrol, fetching the server noise key
|
|
||||||
// if not cached.
|
|
||||||
func (nc *NoiseClient) dial(ctx context.Context) (*ts2021.Conn, error) {
|
|
||||||
nc.mu.Lock()
|
|
||||||
connID := nc.nextID
|
|
||||||
nc.nextID++
|
|
||||||
nc.mu.Unlock()
|
|
||||||
|
|
||||||
if tailcfg.CurrentCapabilityVersion > math.MaxUint16 {
|
|
||||||
// Panic, because a test should have started failing several
|
|
||||||
// thousand version numbers before getting to this point.
|
|
||||||
panic("capability version is too high to fit in the wire protocol")
|
|
||||||
}
|
|
||||||
|
|
||||||
var dialPlan *tailcfg.ControlDialPlan
|
|
||||||
if nc.dialPlan != nil {
|
|
||||||
dialPlan = nc.dialPlan()
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a dial plan, then set our timeout as slightly longer than
|
|
||||||
// the maximum amount of time contained therein; we assume that
|
|
||||||
// explicit instructions on timeouts are more useful than a single
|
|
||||||
// hard-coded timeout.
|
|
||||||
//
|
|
||||||
// The default value of 5 is chosen so that, when there's no dial plan,
|
|
||||||
// we retain the previous behaviour of 10 seconds end-to-end timeout.
|
|
||||||
timeoutSec := 5.0
|
|
||||||
if dialPlan != nil {
|
|
||||||
for _, c := range dialPlan.Candidates {
|
|
||||||
if v := c.DialStartDelaySec + c.DialTimeoutSec; v > timeoutSec {
|
|
||||||
timeoutSec = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// After we establish a connection, we need some time to actually
|
|
||||||
// upgrade it into a Noise connection. With a ballpark worst-case RTT
|
|
||||||
// of 1000ms, give ourselves an extra 5 seconds to complete the
|
|
||||||
// handshake.
|
|
||||||
timeoutSec += 5
|
|
||||||
|
|
||||||
// Be extremely defensive and ensure that the timeout is in the range
|
|
||||||
// [5, 60] seconds (e.g. if we accidentally get a negative number).
|
|
||||||
if timeoutSec > 60 {
|
|
||||||
timeoutSec = 60
|
|
||||||
} else if timeoutSec < 5 {
|
|
||||||
timeoutSec = 5
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout := time.Duration(timeoutSec * float64(time.Second))
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
clientConn, err := (&controlhttp.Dialer{
|
|
||||||
Hostname: nc.host,
|
|
||||||
HTTPPort: nc.httpPort,
|
|
||||||
HTTPSPort: cmp.Or(nc.httpsPort, controlhttp.NoPort),
|
|
||||||
MachineKey: nc.privKey,
|
|
||||||
ControlKey: nc.serverPubKey,
|
|
||||||
ProtocolVersion: uint16(tailcfg.CurrentCapabilityVersion),
|
|
||||||
Dialer: nc.dialer.SystemDial,
|
|
||||||
DNSCache: nc.dnsCache,
|
|
||||||
DialPlan: dialPlan,
|
|
||||||
Logf: nc.logf,
|
|
||||||
NetMon: nc.netMon,
|
|
||||||
HealthTracker: nc.health,
|
|
||||||
Clock: tstime.StdClock{},
|
|
||||||
}).Dial(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ncc, err := ts2021.New(clientConn.Conn, nc.h2t, connID, nc.connClosed)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
nc.mu.Lock()
|
|
||||||
if nc.closed {
|
|
||||||
nc.mu.Unlock()
|
|
||||||
ncc.Close() // Needs to be called without holding the lock.
|
|
||||||
return nil, errors.New("noise client closed")
|
|
||||||
}
|
|
||||||
defer nc.mu.Unlock()
|
|
||||||
mak.Set(&nc.connPool, connID, ncc)
|
|
||||||
nc.last = ncc
|
|
||||||
return ncc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// post does a POST to the control server at the given path, JSON-encoding body.
|
|
||||||
// The provided nodeKey is an optional load balancing hint.
|
|
||||||
func (nc *NoiseClient) post(ctx context.Context, path string, nodeKey key.NodePublic, body any) (*http.Response, error) {
|
|
||||||
return nc.doWithBody(ctx, "POST", path, nodeKey, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nc *NoiseClient) doWithBody(ctx context.Context, method, path string, nodeKey key.NodePublic, body any) (*http.Response, error) {
|
|
||||||
jbody, err := json.Marshal(body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req, err := http.NewRequestWithContext(ctx, method, "https://"+nc.host+path, bytes.NewReader(jbody))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
addLBHeader(req, nodeKey)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
conn, err := nc.getConn(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return conn.RoundTrip(req)
|
|
||||||
}
|
|
||||||
@ -78,8 +78,8 @@ type Dialer struct {
|
|||||||
// dropped.
|
// dropped.
|
||||||
Logf logger.Logf
|
Logf logger.Logf
|
||||||
|
|
||||||
// NetMon is the [netmon.Monitor] to use for this Dialer. It must be
|
// NetMon is the [netmon.Monitor] to use for this Dialer.
|
||||||
// non-nil.
|
// It is optional.
|
||||||
NetMon *netmon.Monitor
|
NetMon *netmon.Monitor
|
||||||
|
|
||||||
// HealthTracker, if non-nil, is the health tracker to use.
|
// HealthTracker, if non-nil, is the health tracker to use.
|
||||||
|
|||||||
289
control/ts2021/client.go
Normal file
289
control/ts2021/client.go
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package ts2021
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"cmp"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tailscale.com/control/controlhttp"
|
||||||
|
"tailscale.com/health"
|
||||||
|
"tailscale.com/net/dnscache"
|
||||||
|
"tailscale.com/net/netmon"
|
||||||
|
"tailscale.com/net/tsdial"
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
|
"tailscale.com/tstime"
|
||||||
|
"tailscale.com/types/key"
|
||||||
|
"tailscale.com/types/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client provides a http.Client to connect to tailcontrol over
|
||||||
|
// the ts2021 protocol.
|
||||||
|
type Client struct {
|
||||||
|
// Client is an HTTP client to talk to the coordination server.
|
||||||
|
// It automatically makes a new Noise connection as needed.
|
||||||
|
*http.Client
|
||||||
|
|
||||||
|
logf logger.Logf // non-nil
|
||||||
|
opts ClientOpts
|
||||||
|
host string // the host part of serverURL
|
||||||
|
httpPort string // the default port to dial
|
||||||
|
httpsPort string // the fallback Noise-over-https port or empty if none
|
||||||
|
|
||||||
|
// mu protects the following
|
||||||
|
mu sync.Mutex
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientOpts contains options for the [NewClient] function. All fields are
|
||||||
|
// required unless otherwise specified.
|
||||||
|
type ClientOpts struct {
|
||||||
|
// ServerURL is the URL of the server to connect to.
|
||||||
|
ServerURL string
|
||||||
|
|
||||||
|
// PrivKey is this node's private key.
|
||||||
|
PrivKey key.MachinePrivate
|
||||||
|
|
||||||
|
// ServerPubKey is the public key of the server.
|
||||||
|
// It is of the form https://<host>:<port> (no trailing slash).
|
||||||
|
ServerPubKey key.MachinePublic
|
||||||
|
|
||||||
|
// Dialer's SystemDial function is used to connect to the server.
|
||||||
|
Dialer *tsdial.Dialer
|
||||||
|
|
||||||
|
// Optional fields follow
|
||||||
|
|
||||||
|
// Logf is the log function to use.
|
||||||
|
// If nil, log.Printf is used.
|
||||||
|
Logf logger.Logf
|
||||||
|
|
||||||
|
// NetMon is the network monitor that will be used to get the
|
||||||
|
// network interface state. This field can be nil; if so, the current
|
||||||
|
// state will be looked up dynamically.
|
||||||
|
NetMon *netmon.Monitor
|
||||||
|
|
||||||
|
// DNSCache is the caching Resolver to use to connect to the server.
|
||||||
|
//
|
||||||
|
// This field can be nil.
|
||||||
|
DNSCache *dnscache.Resolver
|
||||||
|
|
||||||
|
// HealthTracker, if non-nil, is the health tracker to use.
|
||||||
|
HealthTracker *health.Tracker
|
||||||
|
|
||||||
|
// DialPlan, if set, is a function that should return an explicit plan
|
||||||
|
// on how to connect to the server.
|
||||||
|
DialPlan func() *tailcfg.ControlDialPlan
|
||||||
|
|
||||||
|
// ProtocolVersion, if non-zero, specifies an alternate
|
||||||
|
// protocol version to use instead of the default,
|
||||||
|
// of [tailcfg.CurrentCapabilityVersion].
|
||||||
|
ProtocolVersion uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a new noiseClient for the provided server and machine key.
|
||||||
|
//
|
||||||
|
// netMon may be nil, if non-nil it's used to do faster interface lookups.
|
||||||
|
// dialPlan may be nil
|
||||||
|
func NewClient(opts ClientOpts) (*Client, error) {
|
||||||
|
logf := opts.Logf
|
||||||
|
if logf == nil {
|
||||||
|
logf = log.Printf
|
||||||
|
}
|
||||||
|
if opts.ServerURL == "" {
|
||||||
|
return nil, errors.New("ServerURL is required")
|
||||||
|
}
|
||||||
|
if opts.PrivKey.IsZero() {
|
||||||
|
return nil, errors.New("PrivKey is required")
|
||||||
|
}
|
||||||
|
if opts.ServerPubKey.IsZero() {
|
||||||
|
return nil, errors.New("ServerPubKey is required")
|
||||||
|
}
|
||||||
|
if opts.Dialer == nil {
|
||||||
|
return nil, errors.New("Dialer is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(opts.ServerURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid ClientOpts.ServerURL: %w", err)
|
||||||
|
}
|
||||||
|
if u.Scheme != "http" && u.Scheme != "https" {
|
||||||
|
return nil, errors.New("invalid ServerURL scheme, must be http or https")
|
||||||
|
}
|
||||||
|
|
||||||
|
httpPort, httpsPort := "80", "443"
|
||||||
|
addr, _ := netip.ParseAddr(u.Hostname())
|
||||||
|
isPrivateHost := addr.IsPrivate() || addr.IsLoopback() || u.Hostname() == "localhost"
|
||||||
|
if port := u.Port(); port != "" {
|
||||||
|
// If there is an explicit port specified, entirely rely on the scheme,
|
||||||
|
// unless it's http with a private host in which case we never try using HTTPS.
|
||||||
|
if u.Scheme == "https" {
|
||||||
|
httpPort = ""
|
||||||
|
httpsPort = port
|
||||||
|
} else if u.Scheme == "http" {
|
||||||
|
httpPort = port
|
||||||
|
httpsPort = "443"
|
||||||
|
if isPrivateHost {
|
||||||
|
logf("setting empty HTTPS port with http scheme and private host %s", u.Hostname())
|
||||||
|
httpsPort = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if u.Scheme == "http" && isPrivateHost {
|
||||||
|
// Whenever the scheme is http and the hostname is an IP address, do not set the HTTPS port,
|
||||||
|
// as there cannot be a TLS certificate issued for an IP, unless it's a public IP.
|
||||||
|
httpPort = "80"
|
||||||
|
httpsPort = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
np := &Client{
|
||||||
|
opts: opts,
|
||||||
|
host: u.Hostname(),
|
||||||
|
httpPort: httpPort,
|
||||||
|
httpsPort: httpsPort,
|
||||||
|
logf: logf,
|
||||||
|
}
|
||||||
|
|
||||||
|
tr := &http.Transport{
|
||||||
|
Protocols: new(http.Protocols),
|
||||||
|
MaxConnsPerHost: 1,
|
||||||
|
}
|
||||||
|
// We force only HTTP/2 for this transport, which is what the control server
|
||||||
|
// speaks inside the ts2021 Noise encryption. But Go doesn't know about that,
|
||||||
|
// so we use "SetUnencryptedHTTP2" even though it's actually encrypted.
|
||||||
|
tr.Protocols.SetUnencryptedHTTP2(true)
|
||||||
|
tr.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return np.dial(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
np.Client = &http.Client{Transport: tr}
|
||||||
|
return np, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes all the underlying noise connections.
|
||||||
|
// It is a no-op and returns nil if the connection is already closed.
|
||||||
|
func (nc *Client) Close() error {
|
||||||
|
nc.mu.Lock()
|
||||||
|
defer nc.mu.Unlock()
|
||||||
|
nc.closed = true
|
||||||
|
nc.Client.CloseIdleConnections()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dial opens a new connection to tailcontrol, fetching the server noise key
|
||||||
|
// if not cached.
|
||||||
|
func (nc *Client) dial(ctx context.Context) (*Conn, error) {
|
||||||
|
if tailcfg.CurrentCapabilityVersion > math.MaxUint16 {
|
||||||
|
// Panic, because a test should have started failing several
|
||||||
|
// thousand version numbers before getting to this point.
|
||||||
|
panic("capability version is too high to fit in the wire protocol")
|
||||||
|
}
|
||||||
|
|
||||||
|
var dialPlan *tailcfg.ControlDialPlan
|
||||||
|
if nc.opts.DialPlan != nil {
|
||||||
|
dialPlan = nc.opts.DialPlan()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a dial plan, then set our timeout as slightly longer than
|
||||||
|
// the maximum amount of time contained therein; we assume that
|
||||||
|
// explicit instructions on timeouts are more useful than a single
|
||||||
|
// hard-coded timeout.
|
||||||
|
//
|
||||||
|
// The default value of 5 is chosen so that, when there's no dial plan,
|
||||||
|
// we retain the previous behaviour of 10 seconds end-to-end timeout.
|
||||||
|
timeoutSec := 5.0
|
||||||
|
if dialPlan != nil {
|
||||||
|
for _, c := range dialPlan.Candidates {
|
||||||
|
if v := c.DialStartDelaySec + c.DialTimeoutSec; v > timeoutSec {
|
||||||
|
timeoutSec = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// After we establish a connection, we need some time to actually
|
||||||
|
// upgrade it into a Noise connection. With a ballpark worst-case RTT
|
||||||
|
// of 1000ms, give ourselves an extra 5 seconds to complete the
|
||||||
|
// handshake.
|
||||||
|
timeoutSec += 5
|
||||||
|
|
||||||
|
// Be extremely defensive and ensure that the timeout is in the range
|
||||||
|
// [5, 60] seconds (e.g. if we accidentally get a negative number).
|
||||||
|
if timeoutSec > 60 {
|
||||||
|
timeoutSec = 60
|
||||||
|
} else if timeoutSec < 5 {
|
||||||
|
timeoutSec = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := time.Duration(timeoutSec * float64(time.Second))
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
chd := &controlhttp.Dialer{
|
||||||
|
Hostname: nc.host,
|
||||||
|
HTTPPort: nc.httpPort,
|
||||||
|
HTTPSPort: cmp.Or(nc.httpsPort, controlhttp.NoPort),
|
||||||
|
MachineKey: nc.opts.PrivKey,
|
||||||
|
ControlKey: nc.opts.ServerPubKey,
|
||||||
|
ProtocolVersion: cmp.Or(nc.opts.ProtocolVersion, uint16(tailcfg.CurrentCapabilityVersion)),
|
||||||
|
Dialer: nc.opts.Dialer.SystemDial,
|
||||||
|
DNSCache: nc.opts.DNSCache,
|
||||||
|
DialPlan: dialPlan,
|
||||||
|
Logf: nc.logf,
|
||||||
|
NetMon: nc.opts.NetMon,
|
||||||
|
HealthTracker: nc.opts.HealthTracker,
|
||||||
|
Clock: tstime.StdClock{},
|
||||||
|
}
|
||||||
|
clientConn, err := chd.Dial(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ncc := NewConn(clientConn.Conn)
|
||||||
|
|
||||||
|
nc.mu.Lock()
|
||||||
|
if nc.closed {
|
||||||
|
nc.mu.Unlock()
|
||||||
|
ncc.Close() // Needs to be called without holding the lock.
|
||||||
|
return nil, errors.New("noise client closed")
|
||||||
|
}
|
||||||
|
defer nc.mu.Unlock()
|
||||||
|
return ncc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// post does a POST to the control server at the given path, JSON-encoding body.
|
||||||
|
// The provided nodeKey is an optional load balancing hint.
|
||||||
|
func (nc *Client) Post(ctx context.Context, path string, nodeKey key.NodePublic, body any) (*http.Response, error) {
|
||||||
|
return nc.DoWithBody(ctx, "POST", path, nodeKey, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *Client) DoWithBody(ctx context.Context, method, path string, nodeKey key.NodePublic, body any) (*http.Response, error) {
|
||||||
|
jbody, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequestWithContext(ctx, method, "https://"+nc.host+path, bytes.NewReader(jbody))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
AddLBHeader(req, nodeKey)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
return nc.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLBHeader adds the load balancer header to req if nodeKey is non-zero.
|
||||||
|
func AddLBHeader(req *http.Request, nodeKey key.NodePublic) {
|
||||||
|
if !nodeKey.IsZero() {
|
||||||
|
req.Header.Add(tailcfg.LBHeader, nodeKey.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
package controlclient
|
package ts2021
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -10,19 +10,20 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptrace"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
"tailscale.com/control/controlhttp/controlhttpserver"
|
"tailscale.com/control/controlhttp/controlhttpserver"
|
||||||
"tailscale.com/control/ts2021"
|
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/net/tsdial"
|
"tailscale.com/net/tsdial"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/tstest/nettest"
|
"tailscale.com/tstest/nettest"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/util/eventbus/eventbustest"
|
"tailscale.com/util/must"
|
||||||
)
|
)
|
||||||
|
|
||||||
// maxAllowedNoiseVersion is the highest we expect the Tailscale
|
// maxAllowedNoiseVersion is the highest we expect the Tailscale
|
||||||
@ -55,14 +56,23 @@ func TestNoiseClientHTTP2Upgrade_earlyPayload(t *testing.T) {
|
|||||||
}.run(t)
|
}.run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeClientWithURL(t *testing.T, url string) *NoiseClient {
|
var (
|
||||||
nc, err := NewNoiseClient(NoiseOpts{
|
testPrivKey = key.NewMachine()
|
||||||
Logf: t.Logf,
|
testServerPub = key.NewMachine().Public()
|
||||||
ServerURL: url,
|
)
|
||||||
|
|
||||||
|
func makeClientWithURL(t *testing.T, url string) *Client {
|
||||||
|
nc, err := NewClient(ClientOpts{
|
||||||
|
Logf: t.Logf,
|
||||||
|
PrivKey: testPrivKey,
|
||||||
|
ServerPubKey: testServerPub,
|
||||||
|
ServerURL: url,
|
||||||
|
Dialer: tsdial.NewDialer(netmon.NewStatic()),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
t.Cleanup(func() { nc.Close() })
|
||||||
return nc
|
return nc
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +186,6 @@ func (tt noiseClientTest) run(t *testing.T) {
|
|||||||
serverPrivate := key.NewMachine()
|
serverPrivate := key.NewMachine()
|
||||||
clientPrivate := key.NewMachine()
|
clientPrivate := key.NewMachine()
|
||||||
chalPrivate := key.NewChallenge()
|
chalPrivate := key.NewChallenge()
|
||||||
bus := eventbustest.NewBus(t)
|
|
||||||
|
|
||||||
const msg = "Hello, client"
|
const msg = "Hello, client"
|
||||||
h2 := &http2.Server{}
|
h2 := &http2.Server{}
|
||||||
@ -196,12 +205,11 @@ func (tt noiseClientTest) run(t *testing.T) {
|
|||||||
defer hs.Close()
|
defer hs.Close()
|
||||||
|
|
||||||
dialer := tsdial.NewDialer(netmon.NewStatic())
|
dialer := tsdial.NewDialer(netmon.NewStatic())
|
||||||
dialer.SetBus(bus)
|
|
||||||
if nettest.PreferMemNetwork() {
|
if nettest.PreferMemNetwork() {
|
||||||
dialer.SetSystemDialerForTest(nw.Dial)
|
dialer.SetSystemDialerForTest(nw.Dial)
|
||||||
}
|
}
|
||||||
|
|
||||||
nc, err := NewNoiseClient(NoiseOpts{
|
nc, err := NewClient(ClientOpts{
|
||||||
PrivKey: clientPrivate,
|
PrivKey: clientPrivate,
|
||||||
ServerPubKey: serverPrivate.Public(),
|
ServerPubKey: serverPrivate.Public(),
|
||||||
ServerURL: hs.URL,
|
ServerURL: hs.URL,
|
||||||
@ -212,28 +220,39 @@ func (tt noiseClientTest) run(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a conn and verify it read its early payload before the http/2
|
var sawConn atomic.Bool
|
||||||
// handshake.
|
trace := httptrace.WithClientTrace(t.Context(), &httptrace.ClientTrace{
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
GotConn: func(ci httptrace.GotConnInfo) {
|
||||||
defer cancel()
|
ncc, ok := ci.Conn.(*Conn)
|
||||||
c, err := nc.getConn(ctx)
|
if !ok {
|
||||||
if err != nil {
|
// This trace hook sees two dials: the lower-level controlhttp upgrade's
|
||||||
t.Fatal(err)
|
// dial (a tsdial.sysConn), and then the *ts2021.Conn we want.
|
||||||
}
|
// Ignore the first one.
|
||||||
payload, err := c.GetEarlyPayload(ctx)
|
return
|
||||||
if err != nil {
|
}
|
||||||
t.Fatal("timed out waiting for didReadHeaderCh")
|
sawConn.Store(true)
|
||||||
}
|
|
||||||
|
|
||||||
gotNonNil := payload != nil
|
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
|
||||||
if gotNonNil != tt.sendEarlyPayload {
|
defer cancel()
|
||||||
t.Errorf("sendEarlyPayload = %v but got earlyPayload = %T", tt.sendEarlyPayload, payload)
|
|
||||||
}
|
payload, err := ncc.GetEarlyPayload(ctx)
|
||||||
if payload != nil {
|
if err != nil {
|
||||||
if payload.NodeKeyChallenge != chalPrivate.Public() {
|
t.Errorf("GetEarlyPayload: %v", err)
|
||||||
t.Errorf("earlyPayload.NodeKeyChallenge = %v; want %v", payload.NodeKeyChallenge, chalPrivate.Public())
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
gotNonNil := payload != nil
|
||||||
|
if gotNonNil != tt.sendEarlyPayload {
|
||||||
|
t.Errorf("sendEarlyPayload = %v but got earlyPayload = %T", tt.sendEarlyPayload, payload)
|
||||||
|
}
|
||||||
|
if payload != nil {
|
||||||
|
if payload.NodeKeyChallenge != chalPrivate.Public() {
|
||||||
|
t.Errorf("earlyPayload.NodeKeyChallenge = %v; want %v", payload.NodeKeyChallenge, chalPrivate.Public())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
req := must.Get(http.NewRequestWithContext(trace, "GET", "https://unused.example/", nil))
|
||||||
|
|
||||||
checkRes := func(t *testing.T, res *http.Response) {
|
checkRes := func(t *testing.T, res *http.Response) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@ -247,15 +266,19 @@ func (tt noiseClientTest) run(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// And verify we can do HTTP/2 against that conn.
|
// Verify we can do HTTP/2 against that conn.
|
||||||
res, err := (&http.Client{Transport: c}).Get("https://unused.example/")
|
res, err := nc.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
checkRes(t, res)
|
checkRes(t, res)
|
||||||
|
|
||||||
|
if !sawConn.Load() {
|
||||||
|
t.Error("ClientTrace.GotConn never saw the *ts2021.Conn")
|
||||||
|
}
|
||||||
|
|
||||||
// And try using the high-level nc.post API as well.
|
// And try using the high-level nc.post API as well.
|
||||||
res, err = nc.post(context.Background(), "/", key.NodePublic{}, nil)
|
res, err = nc.Post(context.Background(), "/", key.NodePublic{}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -310,7 +333,7 @@ func (up *Upgrader) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
// https://httpwg.org/specs/rfc7540.html#rfc.section.4.1 (Especially not
|
// https://httpwg.org/specs/rfc7540.html#rfc.section.4.1 (Especially not
|
||||||
// an HTTP/2 settings frame, which isn't of type 'T')
|
// an HTTP/2 settings frame, which isn't of type 'T')
|
||||||
var notH2Frame [5]byte
|
var notH2Frame [5]byte
|
||||||
copy(notH2Frame[:], ts2021.EarlyPayloadMagic)
|
copy(notH2Frame[:], EarlyPayloadMagic)
|
||||||
var lenBuf [4]byte
|
var lenBuf [4]byte
|
||||||
binary.BigEndian.PutUint32(lenBuf[:], uint32(len(earlyJSON)))
|
binary.BigEndian.PutUint32(lenBuf[:], uint32(len(earlyJSON)))
|
||||||
// These writes are all buffered by caller, so fine to do them
|
// These writes are all buffered by caller, so fine to do them
|
||||||
@ -13,10 +13,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
|
||||||
"tailscale.com/control/controlbase"
|
"tailscale.com/control/controlbase"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
@ -27,11 +25,11 @@ import (
|
|||||||
// the pool when the connection is closed, properly handles an optional "early
|
// the pool when the connection is closed, properly handles an optional "early
|
||||||
// payload" that's sent prior to beginning the HTTP/2 session, and provides a
|
// payload" that's sent prior to beginning the HTTP/2 session, and provides a
|
||||||
// way to return a connection to a pool when the connection is closed.
|
// way to return a connection to a pool when the connection is closed.
|
||||||
|
//
|
||||||
|
// Use [NewConn] to build a new Conn if you want [Conn.GetEarlyPayload] to work.
|
||||||
|
// Otherwise making a Conn directly, only setting Conn, is fine.
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
*controlbase.Conn
|
*controlbase.Conn
|
||||||
id int
|
|
||||||
onClose func(int)
|
|
||||||
h2cc *http2.ClientConn
|
|
||||||
|
|
||||||
readHeaderOnce sync.Once // guards init of reader field
|
readHeaderOnce sync.Once // guards init of reader field
|
||||||
reader io.Reader // (effectively Conn.Reader after header)
|
reader io.Reader // (effectively Conn.Reader after header)
|
||||||
@ -40,31 +38,18 @@ type Conn struct {
|
|||||||
earlyPayloadErr error
|
earlyPayloadErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Conn that wraps the given controlbase.Conn.
|
// NewConn creates a new Conn that wraps the given controlbase.Conn.
|
||||||
//
|
//
|
||||||
// h2t is the HTTP/2 transport to use for the connection; a new
|
// h2t is the HTTP/2 transport to use for the connection; a new
|
||||||
// http2.ClientConn will be created that reads from the returned Conn.
|
// http2.ClientConn will be created that reads from the returned Conn.
|
||||||
//
|
//
|
||||||
// connID should be a unique ID for this connection. When the Conn is closed,
|
// connID should be a unique ID for this connection. When the Conn is closed,
|
||||||
// the onClose function will be called with the connID if it is non-nil.
|
// the onClose function will be called with the connID if it is non-nil.
|
||||||
func New(conn *controlbase.Conn, h2t *http2.Transport, connID int, onClose func(int)) (*Conn, error) {
|
func NewConn(conn *controlbase.Conn) *Conn {
|
||||||
ncc := &Conn{
|
return &Conn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
id: connID,
|
|
||||||
onClose: onClose,
|
|
||||||
earlyPayloadReady: make(chan struct{}),
|
earlyPayloadReady: make(chan struct{}),
|
||||||
}
|
}
|
||||||
h2cc, err := h2t.NewClientConn(ncc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ncc.h2cc = h2cc
|
|
||||||
return ncc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoundTrip implements the http.RoundTripper interface.
|
|
||||||
func (c *Conn) RoundTrip(r *http.Request) (*http.Response, error) {
|
|
||||||
return c.h2cc.RoundTrip(r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEarlyPayload waits for the early Noise payload to arrive.
|
// GetEarlyPayload waits for the early Noise payload to arrive.
|
||||||
@ -74,6 +59,15 @@ func (c *Conn) RoundTrip(r *http.Request) (*http.Response, error) {
|
|||||||
// early Noise payload is ready (if any) and will return the same result for
|
// early Noise payload is ready (if any) and will return the same result for
|
||||||
// the lifetime of the Conn.
|
// the lifetime of the Conn.
|
||||||
func (c *Conn) GetEarlyPayload(ctx context.Context) (*tailcfg.EarlyNoise, error) {
|
func (c *Conn) GetEarlyPayload(ctx context.Context) (*tailcfg.EarlyNoise, error) {
|
||||||
|
if c.earlyPayloadReady == nil {
|
||||||
|
return nil, errors.New("Conn was not created with NewConn; early payload not supported")
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-c.earlyPayloadReady:
|
||||||
|
return c.earlyPayload, c.earlyPayloadErr
|
||||||
|
default:
|
||||||
|
go c.readHeaderOnce.Do(c.readHeader)
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case <-c.earlyPayloadReady:
|
case <-c.earlyPayloadReady:
|
||||||
return c.earlyPayload, c.earlyPayloadErr
|
return c.earlyPayload, c.earlyPayloadErr
|
||||||
@ -82,12 +76,6 @@ func (c *Conn) GetEarlyPayload(ctx context.Context) (*tailcfg.EarlyNoise, error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanTakeNewRequest reports whether the underlying HTTP/2 connection can take
|
|
||||||
// a new request, meaning it has not been closed or received or sent a GOAWAY.
|
|
||||||
func (c *Conn) CanTakeNewRequest() bool {
|
|
||||||
return c.h2cc.CanTakeNewRequest()
|
|
||||||
}
|
|
||||||
|
|
||||||
// The first 9 bytes from the server to client over Noise are either an HTTP/2
|
// The first 9 bytes from the server to client over Noise are either an HTTP/2
|
||||||
// settings frame (a normal HTTP/2 setup) or, as we added later, an "early payload"
|
// settings frame (a normal HTTP/2 setup) or, as we added later, an "early payload"
|
||||||
// header that's also 9 bytes long: 5 bytes (EarlyPayloadMagic) followed by 4 bytes
|
// header that's also 9 bytes long: 5 bytes (EarlyPayloadMagic) followed by 4 bytes
|
||||||
@ -122,7 +110,9 @@ func (c *Conn) Read(p []byte) (n int, err error) {
|
|||||||
// c.earlyPayload, closing c.earlyPayloadReady, and initializing c.reader for
|
// c.earlyPayload, closing c.earlyPayloadReady, and initializing c.reader for
|
||||||
// future reads.
|
// future reads.
|
||||||
func (c *Conn) readHeader() {
|
func (c *Conn) readHeader() {
|
||||||
defer close(c.earlyPayloadReady)
|
if c.earlyPayloadReady != nil {
|
||||||
|
defer close(c.earlyPayloadReady)
|
||||||
|
}
|
||||||
|
|
||||||
setErr := func(err error) {
|
setErr := func(err error) {
|
||||||
c.reader = returnErrReader{err}
|
c.reader = returnErrReader{err}
|
||||||
@ -156,14 +146,3 @@ func (c *Conn) readHeader() {
|
|||||||
}
|
}
|
||||||
c.reader = c.Conn
|
c.reader = c.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the connection.
|
|
||||||
func (c *Conn) Close() error {
|
|
||||||
if err := c.Conn.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if c.onClose != nil {
|
|
||||||
c.onClose(c.id)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@ -45,6 +45,13 @@ func NewDialer(netMon *netmon.Monitor) *Dialer {
|
|||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewFromFuncForDebug is like NewDialer but takes a netx.DialFunc
|
||||||
|
// and no netMon. It's meant exclusively for the "tailscale debug ts2021"
|
||||||
|
// debug command, and perhaps tests.
|
||||||
|
func NewFromFuncForDebug(logf logger.Logf, dial netx.DialFunc) *Dialer {
|
||||||
|
return &Dialer{sysDialForTest: dial, Logf: logf}
|
||||||
|
}
|
||||||
|
|
||||||
// Dialer dials out of tailscaled, while taking care of details while
|
// Dialer dials out of tailscaled, while taking care of details while
|
||||||
// handling the dozens of edge cases depending on the server mode
|
// handling the dozens of edge cases depending on the server mode
|
||||||
// (TUN, netstack), the OS network sandboxing style (macOS/iOS
|
// (TUN, netstack), the OS network sandboxing style (macOS/iOS
|
||||||
@ -420,7 +427,7 @@ func (d *Dialer) SetSystemDialerForTest(fn netx.DialFunc) {
|
|||||||
// Control and (in the future, as of 2022-04-27) DERPs..
|
// Control and (in the future, as of 2022-04-27) DERPs..
|
||||||
func (d *Dialer) SystemDial(ctx context.Context, network, addr string) (net.Conn, error) {
|
func (d *Dialer) SystemDial(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
d.mu.Lock()
|
d.mu.Lock()
|
||||||
if d.netMon == nil {
|
if d.netMon == nil && d.sysDialForTest == nil {
|
||||||
d.mu.Unlock()
|
d.mu.Unlock()
|
||||||
if testenv.InTest() {
|
if testenv.InTest() {
|
||||||
panic("SystemDial requires a netmon.Monitor; call SetNetMon first")
|
panic("SystemDial requires a netmon.Monitor; call SetNetMon first")
|
||||||
|
|||||||
@ -125,7 +125,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
|
|||||||
LDW tailscale.com/client/web from tailscale.com/ipn/ipnlocal
|
LDW tailscale.com/client/web from tailscale.com/ipn/ipnlocal
|
||||||
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
||||||
tailscale.com/control/controlclient from tailscale.com/ipn/ipnext+
|
tailscale.com/control/controlclient from tailscale.com/ipn/ipnext+
|
||||||
tailscale.com/control/controlhttp from tailscale.com/control/controlclient
|
tailscale.com/control/controlhttp from tailscale.com/control/ts2021
|
||||||
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
||||||
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
|
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
|
||||||
tailscale.com/control/ts2021 from tailscale.com/control/controlclient
|
tailscale.com/control/ts2021 from tailscale.com/control/controlclient
|
||||||
@ -328,13 +328,10 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
|
|||||||
golang.org/x/exp/maps from tailscale.com/ipn/store/mem+
|
golang.org/x/exp/maps from tailscale.com/ipn/store/mem+
|
||||||
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
|
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
|
||||||
golang.org/x/net/dns/dnsmessage from tailscale.com/appc+
|
golang.org/x/net/dns/dnsmessage from tailscale.com/appc+
|
||||||
golang.org/x/net/http/httpguts from golang.org/x/net/http2+
|
golang.org/x/net/http/httpguts from tailscale.com/ipn/ipnlocal+
|
||||||
golang.org/x/net/http/httpproxy from tailscale.com/net/tshttpproxy
|
golang.org/x/net/http/httpproxy from tailscale.com/net/tshttpproxy
|
||||||
golang.org/x/net/http2 from tailscale.com/control/controlclient+
|
|
||||||
golang.org/x/net/http2/hpack from golang.org/x/net/http2+
|
|
||||||
golang.org/x/net/icmp from github.com/prometheus-community/pro-bing+
|
golang.org/x/net/icmp from github.com/prometheus-community/pro-bing+
|
||||||
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
||||||
golang.org/x/net/internal/httpcommon from golang.org/x/net/http2
|
|
||||||
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
|
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
|
||||||
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
|
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
|
||||||
LDW golang.org/x/net/internal/socks from golang.org/x/net/proxy
|
LDW golang.org/x/net/internal/socks from golang.org/x/net/proxy
|
||||||
@ -378,7 +375,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
|
|||||||
bytes from bufio+
|
bytes from bufio+
|
||||||
cmp from encoding/json+
|
cmp from encoding/json+
|
||||||
compress/flate from compress/gzip+
|
compress/flate from compress/gzip+
|
||||||
compress/gzip from golang.org/x/net/http2+
|
compress/gzip from internal/profile+
|
||||||
W compress/zlib from debug/pe
|
W compress/zlib from debug/pe
|
||||||
container/heap from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
|
container/heap from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
|
||||||
container/list from crypto/tls+
|
container/list from crypto/tls+
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user