mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-16 18:08:40 +00:00
control/controlclient: add more Screen Time blocking detection
Updates #9658 Updates #12545 Change-Id: Iec1dad354a75f145567b4055d77b1c1db27c89e2 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com> Co-authored-by: Andrea Gottardo <andrea@gottardo.me>
This commit is contained in:
parent
bd50a3457d
commit
fd3efd9bad
@ -15,6 +15,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@ -25,6 +26,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go4.org/mem"
|
"go4.org/mem"
|
||||||
@ -62,6 +64,7 @@ import (
|
|||||||
// Direct is the client that connects to a tailcontrol server for a node.
|
// Direct is the client that connects to a tailcontrol server for a node.
|
||||||
type Direct struct {
|
type Direct struct {
|
||||||
httpc *http.Client // HTTP client used to talk to tailcontrol
|
httpc *http.Client // HTTP client used to talk to tailcontrol
|
||||||
|
interceptedDial *atomic.Bool // if non-nil, pointer to bool whether ScreenTime intercepted our dial
|
||||||
dialer *tsdial.Dialer
|
dialer *tsdial.Dialer
|
||||||
dnsCache *dnscache.Resolver
|
dnsCache *dnscache.Resolver
|
||||||
controlKnobs *controlknobs.Knobs // always non-nil
|
controlKnobs *controlknobs.Knobs // always non-nil
|
||||||
@ -258,23 +261,28 @@ func NewDirect(opts Options) (*Direct, error) {
|
|||||||
// etc set).
|
// etc set).
|
||||||
httpc = http.DefaultClient
|
httpc = http.DefaultClient
|
||||||
}
|
}
|
||||||
|
var interceptedDial *atomic.Bool
|
||||||
if httpc == nil {
|
if httpc == nil {
|
||||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
tr.Proxy = tshttpproxy.ProxyFromEnvironment
|
tr.Proxy = tshttpproxy.ProxyFromEnvironment
|
||||||
tshttpproxy.SetTransportGetProxyConnectHeader(tr)
|
tshttpproxy.SetTransportGetProxyConnectHeader(tr)
|
||||||
tr.TLSClientConfig = tlsdial.Config(serverURL.Hostname(), opts.HealthTracker, tr.TLSClientConfig)
|
tr.TLSClientConfig = tlsdial.Config(serverURL.Hostname(), opts.HealthTracker, tr.TLSClientConfig)
|
||||||
tr.DialContext = dnscache.Dialer(opts.Dialer.SystemDial, dnsCache)
|
var dialFunc dialFunc
|
||||||
tr.DialTLSContext = dnscache.TLSDialer(opts.Dialer.SystemDial, dnsCache, tr.TLSClientConfig)
|
dialFunc, interceptedDial = makeScreenTimeDetectingDialFunc(opts.Dialer.SystemDial)
|
||||||
|
tr.DialContext = dnscache.Dialer(dialFunc, dnsCache)
|
||||||
|
tr.DialTLSContext = dnscache.TLSDialer(dialFunc, dnsCache, tr.TLSClientConfig)
|
||||||
tr.ForceAttemptHTTP2 = true
|
tr.ForceAttemptHTTP2 = true
|
||||||
// Disable implicit gzip compression; the various
|
// Disable implicit gzip compression; the various
|
||||||
// handlers (register, map, set-dns, etc) do their own
|
// handlers (register, map, set-dns, etc) do their own
|
||||||
// zstd compression per naclbox.
|
// zstd compression per naclbox.
|
||||||
tr.DisableCompression = true
|
tr.DisableCompression = true
|
||||||
|
|
||||||
httpc = &http.Client{Transport: tr}
|
httpc = &http.Client{Transport: tr}
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &Direct{
|
c := &Direct{
|
||||||
httpc: httpc,
|
httpc: httpc,
|
||||||
|
interceptedDial: interceptedDial,
|
||||||
controlKnobs: opts.ControlKnobs,
|
controlKnobs: opts.ControlKnobs,
|
||||||
getMachinePrivKey: opts.GetMachinePrivateKey,
|
getMachinePrivKey: opts.GetMachinePrivateKey,
|
||||||
serverURL: opts.ServerURL,
|
serverURL: opts.ServerURL,
|
||||||
@ -464,6 +472,16 @@ func (c *Direct) hostInfoLocked() *tailcfg.Hostinfo {
|
|||||||
return hi
|
return hi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var macOSScreenTime = health.Register(&health.Warnable{
|
||||||
|
Code: "macos-screen-time-controlclient",
|
||||||
|
Severity: health.SeverityHigh,
|
||||||
|
Title: "Tailscale blocked by Screen Time",
|
||||||
|
Text: func(args health.Args) string {
|
||||||
|
return "macOS Screen Time seems to be blocking Tailscale. Try disabling Screen Time in System Settings > Screen Time > Content & Privacy > Access to Web Content."
|
||||||
|
},
|
||||||
|
ImpactsConnectivity: true,
|
||||||
|
})
|
||||||
|
|
||||||
func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, newURL string, nks tkatype.MarshaledSignature, err error) {
|
func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, newURL string, nks tkatype.MarshaledSignature, err error) {
|
||||||
if c.panicOnUse {
|
if c.panicOnUse {
|
||||||
panic("tainted client")
|
panic("tainted client")
|
||||||
@ -505,6 +523,11 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
|
|||||||
c.logf("doLogin(regen=%v, hasUrl=%v)", regen, opt.URL != "")
|
c.logf("doLogin(regen=%v, hasUrl=%v)", regen, opt.URL != "")
|
||||||
if serverKey.IsZero() {
|
if serverKey.IsZero() {
|
||||||
keys, err := loadServerPubKeys(ctx, c.httpc, c.serverURL)
|
keys, err := loadServerPubKeys(ctx, c.httpc, c.serverURL)
|
||||||
|
if err != nil && c.interceptedDial != nil && c.interceptedDial.Load() {
|
||||||
|
c.health.SetUnhealthy(macOSScreenTime, nil)
|
||||||
|
} else {
|
||||||
|
c.health.SetHealthy(macOSScreenTime)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return regen, opt.URL, nil, err
|
return regen, opt.URL, nil, err
|
||||||
}
|
}
|
||||||
@ -1664,6 +1687,38 @@ func addLBHeader(req *http.Request, nodeKey key.NodePublic) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dialFunc = func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
// makeScreenTimeDetectingDialFunc returns dialFunc, optionally wrapped (on
|
||||||
|
// Apple systems) with a func that sets the returned atomic.Bool for whether
|
||||||
|
// Screen Time seemed to intercept the connection.
|
||||||
|
//
|
||||||
|
// The returned *atomic.Bool is nil on non-Apple systems.
|
||||||
|
func makeScreenTimeDetectingDialFunc(dial dialFunc) (dialFunc, *atomic.Bool) {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin", "ios":
|
||||||
|
// Continue below.
|
||||||
|
default:
|
||||||
|
return dial, nil
|
||||||
|
}
|
||||||
|
ab := new(atomic.Bool)
|
||||||
|
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
c, err := dial(ctx, network, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ab.Store(isTCPLoopback(c.LocalAddr()) && isTCPLoopback(c.RemoteAddr()))
|
||||||
|
return c, nil
|
||||||
|
}, ab
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTCPLoopback(a net.Addr) bool {
|
||||||
|
if ta, ok := a.(*net.TCPAddr); ok {
|
||||||
|
return ta.IP.IsLoopback()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
metricMapRequestsActive = clientmetric.NewGauge("controlclient_map_requests_active")
|
metricMapRequestsActive = clientmetric.NewGauge("controlclient_map_requests_active")
|
||||||
|
|
||||||
|
@ -406,8 +406,9 @@ func isLoopback(a net.Addr) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var macOSScreenTime = health.Register(&health.Warnable{
|
var macOSScreenTime = health.Register(&health.Warnable{
|
||||||
Code: "macos-screen-time",
|
Code: "macos-screen-time",
|
||||||
Title: "Tailscale blocked by Screen Time",
|
Severity: health.SeverityHigh,
|
||||||
|
Title: "Tailscale blocked by Screen Time",
|
||||||
Text: func(args health.Args) string {
|
Text: func(args health.Args) string {
|
||||||
return "macOS Screen Time seems to be blocking Tailscale. Try disabling Screen Time in System Settings > Screen Time > Content & Privacy > Access to Web Content."
|
return "macOS Screen Time seems to be blocking Tailscale. Try disabling Screen Time in System Settings > Screen Time > Content & Privacy > Access to Web Content."
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user