diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index aee9a9f87396c..9697e4b2f84c1 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -156,30 +156,31 @@ type watchSession struct { // state machine generates events back out to zero or more components. type LocalBackend struct { // Elements that are thread-safe or constant after construction. - ctx context.Context // canceled by Close - ctxCancel context.CancelFunc // cancels ctx - logf logger.Logf // general logging - keyLogf logger.Logf // for printing list of peers on change - statsLogf logger.Logf // for printing peers stats on change - sys *tsd.System - e wgengine.Engine // non-nil; TODO(bradfitz): remove; use sys - store ipn.StateStore // non-nil; TODO(bradfitz): remove; use sys - dialer *tsdial.Dialer // non-nil; TODO(bradfitz): remove; use sys - pushDeviceToken syncs.AtomicValue[string] - backendLogID logid.PublicID - unregisterNetMon func() - unregisterHealthWatch func() - portpoll *portlist.Poller // may be nil - portpollOnce sync.Once // guards starting readPoller - gotPortPollRes chan struct{} // closed upon first readPoller result - varRoot string // or empty if SetVarRoot never called - logFlushFunc func() // or nil if SetLogFlusher wasn't called - em *expiryManager // non-nil - sshAtomicBool atomic.Bool - webClientAtomicBool atomic.Bool - shutdownCalled bool // if Shutdown has been called - debugSink *capture.Sink - sockstatLogger *sockstatlog.Logger + ctx context.Context // canceled by Close + ctxCancel context.CancelFunc // cancels ctx + logf logger.Logf // general logging + keyLogf logger.Logf // for printing list of peers on change + statsLogf logger.Logf // for printing peers stats on change + sys *tsd.System + e wgengine.Engine // non-nil; TODO(bradfitz): remove; use sys + store ipn.StateStore // non-nil; TODO(bradfitz): remove; use sys + dialer *tsdial.Dialer // non-nil; TODO(bradfitz): remove; use sys + pushDeviceToken syncs.AtomicValue[string] + backendLogID logid.PublicID + unregisterNetMon func() + unregisterHealthWatch func() + portpoll *portlist.Poller // may be nil + portpollOnce sync.Once // guards starting readPoller + gotPortPollRes chan struct{} // closed upon first readPoller result + varRoot string // or empty if SetVarRoot never called + logFlushFunc func() // or nil if SetLogFlusher wasn't called + em *expiryManager // non-nil + sshAtomicBool atomic.Bool + webClientAtomicBool atomic.Bool + exposeWebClientAtomicBool atomic.Bool + shutdownCalled bool // if Shutdown has been called + debugSink *capture.Sink + sockstatLogger *sockstatlog.Logger // getTCPHandlerForFunnelFlow returns a handler for an incoming TCP flow for // the provided srcAddr and dstPort if one exists. @@ -1160,7 +1161,7 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control // Perform all reconfiguration based on the netmap here. if st.NetMap != nil { b.capTailnetLock = hasCapability(st.NetMap, tailcfg.CapabilityTailnetLock) - b.setWebClientAtomicBoolLocked(st.NetMap, prefs.View()) + b.setWebClientAtomicBoolLocked(st.NetMap) b.mu.Unlock() // respect locking rules for tkaSyncIfNeeded if err := b.tkaSyncIfNeeded(st.NetMap, prefs.View()); err != nil { @@ -2719,11 +2720,12 @@ func (b *LocalBackend) setTCPPortsIntercepted(ports []uint16) { b.shouldInterceptTCPPortAtomic.Store(f) } -// setAtomicValuesFromPrefsLocked populates sshAtomicBool, containsViaIPFuncAtomic -// and shouldInterceptTCPPortAtomic from the prefs p, which may be !Valid(). +// setAtomicValuesFromPrefsLocked populates sshAtomicBool, containsViaIPFuncAtomic, +// shouldInterceptTCPPortAtomic, and exposeWebClientAtomicBool from the prefs p, +// which may be !Valid(). func (b *LocalBackend) setAtomicValuesFromPrefsLocked(p ipn.PrefsView) { b.sshAtomicBool.Store(p.Valid() && p.RunSSH() && envknob.CanSSHD()) - b.setWebClientAtomicBoolLocked(b.netMap, p) + b.setExposeWebClientAtomicBoolLocked(p) if !p.Valid() { b.containsViaIPFuncAtomic.Store(tsaddr.FalseContainsIPFunc()) @@ -3356,6 +3358,8 @@ func (b *LocalBackend) TCPHandlerForDst(src, dst netip.AddrPort) (handler func(c if hittingServiceIP { switch dst.Port() { case 80: + // TODO(mpminardi): do we want to show an error message if the web client + // has been disabled instead of the more "basic" web UI? if b.ShouldRunWebClient() { return b.handleWebClientConn, opts } @@ -3380,7 +3384,7 @@ func (b *LocalBackend) TCPHandlerForDst(src, dst netip.AddrPort) (handler func(c return b.handleSSHConn, opts } // TODO(will,sonia): allow customizing web client port ? - if dst.Port() == webClientPort && b.ShouldRunWebClient() { + if dst.Port() == webClientPort && b.ShouldExposeWebClient() { return b.handleWebClientConn, opts } if port, ok := b.GetPeerAPIPort(dst.Addr()); ok && dst.Port() == port { @@ -4508,19 +4512,34 @@ func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Load() && en // call regardless of whether b.mu is held or not. func (b *LocalBackend) ShouldRunWebClient() bool { return b.webClientAtomicBool.Load() } +// ShouldExposeWebClient reports whether the web client should accept +// connections via [tailscale IP]:5252 in addition to the default +// behaviour of accepting local connections over 100.100.100.100. +func (b *LocalBackend) ShouldExposeWebClient() bool { + return b.ShouldRunWebClient() && b.exposeWebClientAtomicBool.Load() +} + // setWebClientAtomicBoolLocked sets webClientAtomicBool based on whether -// the RunWebClient pref is set, and whether tailcfg.NodeAttrDisableWebClient -// has been set in the netmap.NetworkMap. +// tailcfg.NodeAttrDisableWebClient has been set in the netmap.NetworkMap. // // b.mu must be held. -func (b *LocalBackend) setWebClientAtomicBoolLocked(nm *netmap.NetworkMap, prefs ipn.PrefsView) { - shouldRun := prefs.Valid() && prefs.RunWebClient() && !hasCapability(nm, tailcfg.NodeAttrDisableWebClient) +func (b *LocalBackend) setWebClientAtomicBoolLocked(nm *netmap.NetworkMap) { + shouldRun := !hasCapability(nm, tailcfg.NodeAttrDisableWebClient) wasRunning := b.webClientAtomicBool.Swap(shouldRun) if wasRunning && !shouldRun { go b.webClientShutdown() // stop web client } } +// setExposeWebClientAtomicBoolLocked sets exposeWebClientAtomicBool based on +// whether the RunWebClient pref is set. +// +// b.mu must be held. +func (b *LocalBackend) setExposeWebClientAtomicBoolLocked(prefs ipn.PrefsView) { + shouldExpose := prefs.Valid() && prefs.RunWebClient() + b.exposeWebClientAtomicBool.Store(shouldExpose) +} + // ShouldHandleViaIP reports whether ip is an IPv6 address in the // Tailscale ULA's v6 "via" range embedding an IPv4 address to be forwarded to // by Tailscale. @@ -4808,7 +4827,7 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn. if prefs.Valid() && prefs.RunSSH() && envknob.CanSSHD() { handlePorts = append(handlePorts, 22) } - if b.ShouldRunWebClient() { + if b.ShouldExposeWebClient() { handlePorts = append(handlePorts, webClientPort) // don't listen on netmap addresses if we're in userspace mode