Просмотр исходного кода

control/controlclient: add more Screen Time blocking detection

Updates #9658
Updates #12545

Change-Id: Iec1dad354a75f145567b4055d77b1c1db27c89e2
Signed-off-by: Brad Fitzpatrick <[email protected]>
Co-authored-by: Andrea Gottardo <[email protected]>
Brad Fitzpatrick 1 год назад
Родитель
Сommit
fd3efd9bad
2 измененных файлов с 60 добавлено и 4 удалено
  1. 57 2
      control/controlclient/direct.go
  2. 3 2
      control/controlhttp/client.go

+ 57 - 2
control/controlclient/direct.go

@@ -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)
-		tr.DialTLSContext = dnscache.TLSDialer(opts.Dialer.SystemDial, dnsCache, tr.TLSClientConfig)
+		var dialFunc dialFunc
+		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")
 
 

+ 3 - 2
control/controlhttp/client.go

@@ -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",
-	Title: "Tailscale blocked by Screen Time",
+	Code:     "macos-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."
 	},
 	},