|
|
@@ -550,10 +550,12 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
|
|
|
// Following changes are triggered via the eventbus.
|
|
|
b.linkChange(&netmon.ChangeDelta{New: netMon.InterfaceState()})
|
|
|
|
|
|
- if tunWrap, ok := b.sys.Tun.GetOK(); ok {
|
|
|
- tunWrap.PeerAPIPort = b.GetPeerAPIPort
|
|
|
- } else {
|
|
|
- b.logf("[unexpected] failed to wire up PeerAPI port for engine %T", e)
|
|
|
+ if buildfeatures.HasPeerAPIServer {
|
|
|
+ if tunWrap, ok := b.sys.Tun.GetOK(); ok {
|
|
|
+ tunWrap.PeerAPIPort = b.GetPeerAPIPort
|
|
|
+ } else {
|
|
|
+ b.logf("[unexpected] failed to wire up PeerAPI port for engine %T", e)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if buildfeatures.HasDebug {
|
|
|
@@ -972,15 +974,17 @@ func (b *LocalBackend) linkChange(delta *netmon.ChangeDelta) {
|
|
|
b.updateFilterLocked(prefs)
|
|
|
updateExitNodeUsageWarning(prefs, delta.New, b.health)
|
|
|
|
|
|
- cn := b.currentNode()
|
|
|
- nm := cn.NetMap()
|
|
|
- if peerAPIListenAsync && nm != nil && b.state == ipn.Running {
|
|
|
- want := nm.GetAddresses().Len()
|
|
|
- have := len(b.peerAPIListeners)
|
|
|
- b.logf("[v1] linkChange: have %d peerAPIListeners, want %d", have, want)
|
|
|
- if have < want {
|
|
|
- b.logf("linkChange: peerAPIListeners too low; trying again")
|
|
|
- b.goTracker.Go(b.initPeerAPIListener)
|
|
|
+ if buildfeatures.HasPeerAPIServer {
|
|
|
+ cn := b.currentNode()
|
|
|
+ nm := cn.NetMap()
|
|
|
+ if peerAPIListenAsync && nm != nil && b.state == ipn.Running {
|
|
|
+ want := nm.GetAddresses().Len()
|
|
|
+ have := len(b.peerAPIListeners)
|
|
|
+ b.logf("[v1] linkChange: have %d peerAPIListeners, want %d", have, want)
|
|
|
+ if have < want {
|
|
|
+ b.logf("linkChange: peerAPIListeners too low; trying again")
|
|
|
+ b.goTracker.Go(b.initPeerAPIListener)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -1368,7 +1372,7 @@ func peerStatusFromNode(ps *ipnstate.PeerStatus, n tailcfg.NodeView) {
|
|
|
ps.PublicKey = n.Key()
|
|
|
ps.ID = n.StableID()
|
|
|
ps.Created = n.Created()
|
|
|
- ps.ExitNodeOption = tsaddr.ContainsExitRoutes(n.AllowedIPs())
|
|
|
+ ps.ExitNodeOption = buildfeatures.HasUseExitNode && tsaddr.ContainsExitRoutes(n.AllowedIPs())
|
|
|
if n.Tags().Len() != 0 {
|
|
|
v := n.Tags()
|
|
|
ps.Tags = &v
|
|
|
@@ -1897,6 +1901,9 @@ func (b *LocalBackend) applySysPolicyLocked(prefs *ipn.Prefs) (anyChange bool) {
|
|
|
//
|
|
|
// b.mu must be held.
|
|
|
func (b *LocalBackend) applyExitNodeSysPolicyLocked(prefs *ipn.Prefs) (anyChange bool) {
|
|
|
+ if !buildfeatures.HasUseExitNode {
|
|
|
+ return false
|
|
|
+ }
|
|
|
if exitNodeIDStr, _ := b.polc.GetString(pkey.ExitNodeID, ""); exitNodeIDStr != "" {
|
|
|
exitNodeID := tailcfg.StableNodeID(exitNodeIDStr)
|
|
|
|
|
|
@@ -2002,7 +2009,7 @@ func (b *LocalBackend) sysPolicyChanged(policy policyclient.PolicyChange) {
|
|
|
b.mu.Unlock()
|
|
|
}
|
|
|
|
|
|
- if policy.HasChanged(pkey.AllowedSuggestedExitNodes) {
|
|
|
+ if buildfeatures.HasUseExitNode && policy.HasChanged(pkey.AllowedSuggestedExitNodes) {
|
|
|
b.refreshAllowedSuggestions()
|
|
|
// Re-evaluate exit node suggestion now that the policy setting has changed.
|
|
|
if _, err := b.SuggestExitNode(); err != nil && !errors.Is(err, ErrNoPreferredDERP) {
|
|
|
@@ -2073,6 +2080,9 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo
|
|
|
// mustationsAreWorthyOfRecalculatingSuggestedExitNode reports whether any mutation type in muts is
|
|
|
// worthy of recalculating the suggested exit node.
|
|
|
func mutationsAreWorthyOfRecalculatingSuggestedExitNode(muts []netmap.NodeMutation, cn *nodeBackend, sid tailcfg.StableNodeID) bool {
|
|
|
+ if !buildfeatures.HasUseExitNode {
|
|
|
+ return false
|
|
|
+ }
|
|
|
for _, m := range muts {
|
|
|
n, ok := cn.NodeByID(m.NodeIDBeingMutated())
|
|
|
if !ok {
|
|
|
@@ -2126,6 +2136,9 @@ func mutationsAreWorthyOfTellingIPNBus(muts []netmap.NodeMutation) bool {
|
|
|
//
|
|
|
// b.mu must be held.
|
|
|
func (b *LocalBackend) resolveAutoExitNodeLocked(prefs *ipn.Prefs) (prefsChanged bool) {
|
|
|
+ if !buildfeatures.HasUseExitNode {
|
|
|
+ return false
|
|
|
+ }
|
|
|
// As of 2025-07-08, the only supported auto exit node expression is [ipn.AnyExitNode].
|
|
|
//
|
|
|
// However, to maintain forward compatibility with future auto exit node expressions,
|
|
|
@@ -2170,6 +2183,9 @@ func (b *LocalBackend) resolveAutoExitNodeLocked(prefs *ipn.Prefs) (prefsChanged
|
|
|
//
|
|
|
// b.mu must be held.
|
|
|
func (b *LocalBackend) resolveExitNodeIPLocked(prefs *ipn.Prefs) (prefsChanged bool) {
|
|
|
+ if !buildfeatures.HasUseExitNode {
|
|
|
+ return false
|
|
|
+ }
|
|
|
// If we have a desired IP on file, try to find the corresponding node.
|
|
|
if !prefs.ExitNodeIP.IsValid() {
|
|
|
return false
|
|
|
@@ -2455,6 +2471,11 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ var c2nHandler http.Handler
|
|
|
+ if buildfeatures.HasC2N {
|
|
|
+ c2nHandler = http.HandlerFunc(b.handleC2N)
|
|
|
+ }
|
|
|
+
|
|
|
// TODO(apenwarr): The only way to change the ServerURL is to
|
|
|
// re-run b.Start, because this is the only place we create a
|
|
|
// new controlclient. EditPrefs allows you to overwrite ServerURL,
|
|
|
@@ -2475,7 +2496,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
|
|
PopBrowserURL: b.tellClientToBrowseToURL,
|
|
|
Dialer: b.Dialer(),
|
|
|
Observer: b,
|
|
|
- C2NHandler: http.HandlerFunc(b.handleC2N),
|
|
|
+ C2NHandler: c2nHandler,
|
|
|
DialPlan: &b.dialPlan, // pointer because it can't be copied
|
|
|
ControlKnobs: b.sys.ControlKnobs(),
|
|
|
Shutdown: ccShutdown,
|
|
|
@@ -2623,31 +2644,33 @@ func (b *LocalBackend) updateFilterLocked(prefs ipn.PrefsView) {
|
|
|
}
|
|
|
}
|
|
|
if prefs.Valid() {
|
|
|
- for _, r := range prefs.AdvertiseRoutes().All() {
|
|
|
- if r.Bits() == 0 {
|
|
|
- // When offering a default route to the world, we
|
|
|
- // filter out locally reachable LANs, so that the
|
|
|
- // default route effectively appears to be a "guest
|
|
|
- // wifi": you get internet access, but to additionally
|
|
|
- // get LAN access the LAN(s) need to be offered
|
|
|
- // explicitly as well.
|
|
|
- localInterfaceRoutes, hostIPs, err := interfaceRoutes()
|
|
|
- if err != nil {
|
|
|
- b.logf("getting local interface routes: %v", err)
|
|
|
- continue
|
|
|
- }
|
|
|
- s, err := shrinkDefaultRoute(r, localInterfaceRoutes, hostIPs)
|
|
|
- if err != nil {
|
|
|
- b.logf("computing default route filter: %v", err)
|
|
|
- continue
|
|
|
+ if buildfeatures.HasAdvertiseRoutes {
|
|
|
+ for _, r := range prefs.AdvertiseRoutes().All() {
|
|
|
+ if r.Bits() == 0 {
|
|
|
+ // When offering a default route to the world, we
|
|
|
+ // filter out locally reachable LANs, so that the
|
|
|
+ // default route effectively appears to be a "guest
|
|
|
+ // wifi": you get internet access, but to additionally
|
|
|
+ // get LAN access the LAN(s) need to be offered
|
|
|
+ // explicitly as well.
|
|
|
+ localInterfaceRoutes, hostIPs, err := interfaceRoutes()
|
|
|
+ if err != nil {
|
|
|
+ b.logf("getting local interface routes: %v", err)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ s, err := shrinkDefaultRoute(r, localInterfaceRoutes, hostIPs)
|
|
|
+ if err != nil {
|
|
|
+ b.logf("computing default route filter: %v", err)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ localNetsB.AddSet(s)
|
|
|
+ } else {
|
|
|
+ localNetsB.AddPrefix(r)
|
|
|
+ // When advertising a non-default route, we assume
|
|
|
+ // this is a corporate subnet that should be present
|
|
|
+ // in the audit logs.
|
|
|
+ logNetsB.AddPrefix(r)
|
|
|
}
|
|
|
- localNetsB.AddSet(s)
|
|
|
- } else {
|
|
|
- localNetsB.AddPrefix(r)
|
|
|
- // When advertising a non-default route, we assume
|
|
|
- // this is a corporate subnet that should be present
|
|
|
- // in the audit logs.
|
|
|
- logNetsB.AddPrefix(r)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -2658,7 +2681,7 @@ func (b *LocalBackend) updateFilterLocked(prefs ipn.PrefsView) {
|
|
|
// The correct filter rules are synthesized by the coordination server
|
|
|
// and sent down, but the address needs to be part of the 'local net' for the
|
|
|
// filter package to even bother checking the filter rules, so we set them here.
|
|
|
- if prefs.AppConnector().Advertise {
|
|
|
+ if buildfeatures.HasAppConnectors && prefs.AppConnector().Advertise {
|
|
|
localNetsB.Add(netip.MustParseAddr("0.0.0.0"))
|
|
|
localNetsB.Add(netip.MustParseAddr("::0"))
|
|
|
}
|
|
|
@@ -3712,6 +3735,9 @@ func (b *LocalBackend) Ping(ctx context.Context, ip netip.Addr, pingType tailcfg
|
|
|
}
|
|
|
|
|
|
func (b *LocalBackend) pingPeerAPI(ctx context.Context, ip netip.Addr) (peer tailcfg.NodeView, peerBase string, err error) {
|
|
|
+ if !buildfeatures.HasPeerAPIClient {
|
|
|
+ return peer, peerBase, feature.ErrUnavailable
|
|
|
+ }
|
|
|
var zero tailcfg.NodeView
|
|
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
|
defer cancel()
|
|
|
@@ -4051,6 +4077,9 @@ var exitNodeMisconfigurationWarnable = health.Register(&health.Warnable{
|
|
|
// updateExitNodeUsageWarning updates a warnable meant to notify users of
|
|
|
// configuration issues that could break exit node usage.
|
|
|
func updateExitNodeUsageWarning(p ipn.PrefsView, state *netmon.State, healthTracker *health.Tracker) {
|
|
|
+ if !buildfeatures.HasUseExitNode {
|
|
|
+ return
|
|
|
+ }
|
|
|
var msg string
|
|
|
if p.ExitNodeIP().IsValid() || p.ExitNodeID() != "" {
|
|
|
warn, _ := netutil.CheckReversePathFiltering(state)
|
|
|
@@ -4070,6 +4099,9 @@ func (b *LocalBackend) checkExitNodePrefsLocked(p *ipn.Prefs) error {
|
|
|
if !tryingToUseExitNode {
|
|
|
return nil
|
|
|
}
|
|
|
+ if !buildfeatures.HasUseExitNode {
|
|
|
+ return feature.ErrUnavailable
|
|
|
+ }
|
|
|
|
|
|
if err := featureknob.CanUseExitNode(); err != nil {
|
|
|
return err
|
|
|
@@ -4110,6 +4142,9 @@ func (b *LocalBackend) SetUseExitNodeEnabled(actor ipnauth.Actor, v bool) (ipn.P
|
|
|
defer unlock()
|
|
|
|
|
|
p0 := b.pm.CurrentPrefs()
|
|
|
+ if !buildfeatures.HasUseExitNode {
|
|
|
+ return p0, nil
|
|
|
+ }
|
|
|
if v && p0.ExitNodeID() != "" {
|
|
|
// Already on.
|
|
|
return p0, nil
|
|
|
@@ -4240,6 +4275,9 @@ func (b *LocalBackend) checkEditPrefsAccessLocked(actor ipnauth.Actor, prefs ipn
|
|
|
//
|
|
|
// b.mu must be held.
|
|
|
func (b *LocalBackend) changeDisablesExitNodeLocked(prefs ipn.PrefsView, change *ipn.MaskedPrefs) bool {
|
|
|
+ if !buildfeatures.HasUseExitNode {
|
|
|
+ return false
|
|
|
+ }
|
|
|
if !change.AutoExitNodeSet && !change.ExitNodeIDSet && !change.ExitNodeIPSet {
|
|
|
// The change does not affect exit node usage.
|
|
|
return false
|
|
|
@@ -4577,6 +4615,9 @@ func (b *LocalBackend) setPrefsLockedOnEntry(newp *ipn.Prefs, unlock unlockOnce)
|
|
|
// GetPeerAPIPort returns the port number for the peerapi server
|
|
|
// running on the provided IP.
|
|
|
func (b *LocalBackend) GetPeerAPIPort(ip netip.Addr) (port uint16, ok bool) {
|
|
|
+ if !buildfeatures.HasPeerAPIServer {
|
|
|
+ return 0, false
|
|
|
+ }
|
|
|
b.mu.Lock()
|
|
|
defer b.mu.Unlock()
|
|
|
for _, pln := range b.peerAPIListeners {
|
|
|
@@ -4936,10 +4977,12 @@ func (b *LocalBackend) authReconfig() {
|
|
|
// Keep the dialer updated about whether we're supposed to use
|
|
|
// an exit node's DNS server (so SOCKS5/HTTP outgoing dials
|
|
|
// can use it for name resolution)
|
|
|
- if dohURLOK {
|
|
|
- b.dialer.SetExitDNSDoH(dohURL)
|
|
|
- } else {
|
|
|
- b.dialer.SetExitDNSDoH("")
|
|
|
+ if buildfeatures.HasUseExitNode {
|
|
|
+ if dohURLOK {
|
|
|
+ b.dialer.SetExitDNSDoH(dohURL)
|
|
|
+ } else {
|
|
|
+ b.dialer.SetExitDNSDoH("")
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
cfg, err := nmcfg.WGCfg(nm, b.logf, flags, prefs.ExitNodeID())
|
|
|
@@ -5064,6 +5107,9 @@ func (b *LocalBackend) TailscaleVarRoot() string {
|
|
|
//
|
|
|
// b.mu must be held.
|
|
|
func (b *LocalBackend) closePeerAPIListenersLocked() {
|
|
|
+ if !buildfeatures.HasPeerAPIServer {
|
|
|
+ return
|
|
|
+ }
|
|
|
b.peerAPIServer = nil
|
|
|
for _, pln := range b.peerAPIListeners {
|
|
|
pln.Close()
|
|
|
@@ -5079,6 +5125,9 @@ func (b *LocalBackend) closePeerAPIListenersLocked() {
|
|
|
const peerAPIListenAsync = runtime.GOOS == "windows" || runtime.GOOS == "android"
|
|
|
|
|
|
func (b *LocalBackend) initPeerAPIListener() {
|
|
|
+ if !buildfeatures.HasPeerAPIServer {
|
|
|
+ return
|
|
|
+ }
|
|
|
b.logf("[v1] initPeerAPIListener: entered")
|
|
|
b.mu.Lock()
|
|
|
defer b.mu.Unlock()
|
|
|
@@ -5903,6 +5952,9 @@ func (b *LocalBackend) setNetInfo(ni *tailcfg.NetInfo) {
|
|
|
// RefreshExitNode determines which exit node to use based on the current
|
|
|
// prefs and netmap and switches to it if needed.
|
|
|
func (b *LocalBackend) RefreshExitNode() {
|
|
|
+ if !buildfeatures.HasUseExitNode {
|
|
|
+ return
|
|
|
+ }
|
|
|
if b.resolveExitNode() {
|
|
|
b.authReconfig()
|
|
|
}
|
|
|
@@ -5918,6 +5970,9 @@ func (b *LocalBackend) RefreshExitNode() {
|
|
|
//
|
|
|
// b.mu must not be held.
|
|
|
func (b *LocalBackend) resolveExitNode() (changed bool) {
|
|
|
+ if !buildfeatures.HasUseExitNode {
|
|
|
+ return false
|
|
|
+ }
|
|
|
b.mu.Lock()
|
|
|
defer b.mu.Unlock()
|
|
|
|
|
|
@@ -6468,6 +6523,9 @@ func (b *LocalBackend) SetDeviceAttrs(ctx context.Context, attrs tailcfg.AttrUpd
|
|
|
//
|
|
|
// If exitNodeID is the zero valid, it returns "", false.
|
|
|
func exitNodeCanProxyDNS(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.NodeView, exitNodeID tailcfg.StableNodeID) (dohURL string, ok bool) {
|
|
|
+ if !buildfeatures.HasUseExitNode {
|
|
|
+ return "", false
|
|
|
+ }
|
|
|
if exitNodeID.IsZero() {
|
|
|
return "", false
|
|
|
}
|
|
|
@@ -7084,6 +7142,9 @@ var ErrNoPreferredDERP = errors.New("no preferred DERP, try again later")
|
|
|
//
|
|
|
// b.mu.lock() must be held.
|
|
|
func (b *LocalBackend) suggestExitNodeLocked() (response apitype.ExitNodeSuggestionResponse, err error) {
|
|
|
+ if !buildfeatures.HasUseExitNode {
|
|
|
+ return response, feature.ErrUnavailable
|
|
|
+ }
|
|
|
lastReport := b.MagicConn().GetLastNetcheckReport(b.ctx)
|
|
|
prevSuggestion := b.lastSuggestedExitNode
|
|
|
|
|
|
@@ -7101,6 +7162,9 @@ func (b *LocalBackend) suggestExitNodeLocked() (response apitype.ExitNodeSuggest
|
|
|
}
|
|
|
|
|
|
func (b *LocalBackend) SuggestExitNode() (response apitype.ExitNodeSuggestionResponse, err error) {
|
|
|
+ if !buildfeatures.HasUseExitNode {
|
|
|
+ return response, feature.ErrUnavailable
|
|
|
+ }
|
|
|
b.mu.Lock()
|
|
|
defer b.mu.Unlock()
|
|
|
return b.suggestExitNodeLocked()
|
|
|
@@ -7117,6 +7181,9 @@ func (b *LocalBackend) getAllowedSuggestions() set.Set[tailcfg.StableNodeID] {
|
|
|
// refreshAllowedSuggestions rebuilds the set of permitted exit nodes
|
|
|
// from the current [pkey.AllowedSuggestedExitNodes] value.
|
|
|
func (b *LocalBackend) refreshAllowedSuggestions() {
|
|
|
+ if !buildfeatures.HasUseExitNode {
|
|
|
+ return
|
|
|
+ }
|
|
|
b.allowedSuggestedExitNodesMu.Lock()
|
|
|
defer b.allowedSuggestedExitNodesMu.Unlock()
|
|
|
b.allowedSuggestedExitNodes = fillAllowedSuggestions(b.polc)
|