mirror of
https://github.com/tailscale/tailscale.git
synced 2025-10-15 10:49:18 +00:00
feature/featuretags: add features for c2n, peerapi, advertise/use routes/exit nodes
Saves 262 KB so far. I'm sure I missed some places, but shotizam says these were the low hanging fruit. Updates #12614 Change-Id: Ia31c01b454f627e6d0470229aae4e19d615e45e3 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:

committed by
Brad Fitzpatrick

parent
2cd518a8b6
commit
a208cb9fd5
@@ -1409,6 +1409,9 @@ func (c *Direct) answerPing(pr *tailcfg.PingRequest) {
|
||||
answerHeadPing(c.logf, httpc, pr)
|
||||
return
|
||||
case "c2n":
|
||||
if !buildfeatures.HasC2N {
|
||||
return
|
||||
}
|
||||
if !useNoise && !envknob.Bool("TS_DEBUG_PERMIT_HTTP_C2N") {
|
||||
c.logf("refusing to answer c2n ping without noise")
|
||||
return
|
||||
|
13
feature/buildfeatures/feature_advertiseexitnode_disabled.go
Normal file
13
feature/buildfeatures/feature_advertiseexitnode_disabled.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build ts_omit_advertiseexitnode
|
||||
|
||||
package buildfeatures
|
||||
|
||||
// HasAdvertiseExitNode is whether the binary was built with support for modular feature "Run an exit node".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_advertiseexitnode" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasAdvertiseExitNode = false
|
13
feature/buildfeatures/feature_advertiseexitnode_enabled.go
Normal file
13
feature/buildfeatures/feature_advertiseexitnode_enabled.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build !ts_omit_advertiseexitnode
|
||||
|
||||
package buildfeatures
|
||||
|
||||
// HasAdvertiseExitNode is whether the binary was built with support for modular feature "Run an exit node".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_advertiseexitnode" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasAdvertiseExitNode = true
|
13
feature/buildfeatures/feature_advertiseroutes_disabled.go
Normal file
13
feature/buildfeatures/feature_advertiseroutes_disabled.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build ts_omit_advertiseroutes
|
||||
|
||||
package buildfeatures
|
||||
|
||||
// HasAdvertiseRoutes is whether the binary was built with support for modular feature "Advertise routes for other nodes to use".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_advertiseroutes" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasAdvertiseRoutes = false
|
13
feature/buildfeatures/feature_advertiseroutes_enabled.go
Normal file
13
feature/buildfeatures/feature_advertiseroutes_enabled.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build !ts_omit_advertiseroutes
|
||||
|
||||
package buildfeatures
|
||||
|
||||
// HasAdvertiseRoutes is whether the binary was built with support for modular feature "Advertise routes for other nodes to use".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_advertiseroutes" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasAdvertiseRoutes = true
|
13
feature/buildfeatures/feature_c2n_disabled.go
Normal file
13
feature/buildfeatures/feature_c2n_disabled.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build ts_omit_c2n
|
||||
|
||||
package buildfeatures
|
||||
|
||||
// HasC2N is whether the binary was built with support for modular feature "Control-to-node (C2N) support".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_c2n" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasC2N = false
|
13
feature/buildfeatures/feature_c2n_enabled.go
Normal file
13
feature/buildfeatures/feature_c2n_enabled.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build !ts_omit_c2n
|
||||
|
||||
package buildfeatures
|
||||
|
||||
// HasC2N is whether the binary was built with support for modular feature "Control-to-node (C2N) support".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_c2n" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasC2N = true
|
13
feature/buildfeatures/feature_peerapiclient_disabled.go
Normal file
13
feature/buildfeatures/feature_peerapiclient_disabled.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build ts_omit_peerapiclient
|
||||
|
||||
package buildfeatures
|
||||
|
||||
// HasPeerAPIClient is whether the binary was built with support for modular feature "PeerAPI client support".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_peerapiclient" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasPeerAPIClient = false
|
13
feature/buildfeatures/feature_peerapiclient_enabled.go
Normal file
13
feature/buildfeatures/feature_peerapiclient_enabled.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build !ts_omit_peerapiclient
|
||||
|
||||
package buildfeatures
|
||||
|
||||
// HasPeerAPIClient is whether the binary was built with support for modular feature "PeerAPI client support".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_peerapiclient" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasPeerAPIClient = true
|
13
feature/buildfeatures/feature_peerapiserver_disabled.go
Normal file
13
feature/buildfeatures/feature_peerapiserver_disabled.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build ts_omit_peerapiserver
|
||||
|
||||
package buildfeatures
|
||||
|
||||
// HasPeerAPIServer is whether the binary was built with support for modular feature "PeerAPI server support".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_peerapiserver" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasPeerAPIServer = false
|
13
feature/buildfeatures/feature_peerapiserver_enabled.go
Normal file
13
feature/buildfeatures/feature_peerapiserver_enabled.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build !ts_omit_peerapiserver
|
||||
|
||||
package buildfeatures
|
||||
|
||||
// HasPeerAPIServer is whether the binary was built with support for modular feature "PeerAPI server support".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_peerapiserver" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasPeerAPIServer = true
|
13
feature/buildfeatures/feature_useexitnode_disabled.go
Normal file
13
feature/buildfeatures/feature_useexitnode_disabled.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build ts_omit_useexitnode
|
||||
|
||||
package buildfeatures
|
||||
|
||||
// HasUseExitNode is whether the binary was built with support for modular feature "Use exit nodes".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_useexitnode" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasUseExitNode = false
|
13
feature/buildfeatures/feature_useexitnode_enabled.go
Normal file
13
feature/buildfeatures/feature_useexitnode_enabled.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build !ts_omit_useexitnode
|
||||
|
||||
package buildfeatures
|
||||
|
||||
// HasUseExitNode is whether the binary was built with support for modular feature "Use exit nodes".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_useexitnode" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasUseExitNode = true
|
13
feature/buildfeatures/feature_useroutes_disabled.go
Normal file
13
feature/buildfeatures/feature_useroutes_disabled.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build ts_omit_useroutes
|
||||
|
||||
package buildfeatures
|
||||
|
||||
// HasUseRoutes is whether the binary was built with support for modular feature "Use routes advertised by other nodes".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_useroutes" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasUseRoutes = false
|
13
feature/buildfeatures/feature_useroutes_enabled.go
Normal file
13
feature/buildfeatures/feature_useroutes_enabled.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build !ts_omit_useroutes
|
||||
|
||||
package buildfeatures
|
||||
|
||||
// HasUseRoutes is whether the binary was built with support for modular feature "Use routes advertised by other nodes".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_useroutes" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasUseRoutes = true
|
@@ -82,6 +82,12 @@ type FeatureMeta struct {
|
||||
Sym string // exported Go symbol for boolean const
|
||||
Desc string // human-readable description
|
||||
Deps []FeatureTag // other features this feature requires
|
||||
|
||||
// ImplementationDetail is whether the feature is an internal implementation
|
||||
// detail. That is, it's not something a user wuold care about having or not
|
||||
// having, but we'd like to able to omit from builds if no other
|
||||
// user-visible features depend on it.
|
||||
ImplementationDetail bool
|
||||
}
|
||||
|
||||
// Features are the known Tailscale features that can be selectively included or
|
||||
@@ -90,17 +96,45 @@ var Features = map[FeatureTag]FeatureMeta{
|
||||
"acme": {Sym: "ACME", Desc: "ACME TLS certificate management"},
|
||||
"appconnectors": {Sym: "AppConnectors", Desc: "App Connectors support"},
|
||||
"aws": {Sym: "AWS", Desc: "AWS integration"},
|
||||
"bakedroots": {Sym: "BakedRoots", Desc: "Embed CA (LetsEncrypt) x509 roots to use as fallback"},
|
||||
"bird": {Sym: "Bird", Desc: "Bird BGP integration"},
|
||||
"advertiseexitnode": {
|
||||
Sym: "AdvertiseExitNode",
|
||||
Desc: "Run an exit node",
|
||||
Deps: []FeatureTag{
|
||||
"peerapiserver", // to run the ExitDNS server
|
||||
"advertiseroutes",
|
||||
},
|
||||
},
|
||||
"advertiseroutes": {
|
||||
Sym: "AdvertiseRoutes",
|
||||
Desc: "Advertise routes for other nodes to use",
|
||||
Deps: []FeatureTag{
|
||||
"c2n", // for control plane to probe health for HA subnet router leader election
|
||||
},
|
||||
},
|
||||
"bakedroots": {Sym: "BakedRoots", Desc: "Embed CA (LetsEncrypt) x509 roots to use as fallback"},
|
||||
"bird": {Sym: "Bird", Desc: "Bird BGP integration"},
|
||||
"c2n": {
|
||||
Sym: "C2N",
|
||||
Desc: "Control-to-node (C2N) support",
|
||||
ImplementationDetail: true,
|
||||
},
|
||||
"captiveportal": {Sym: "CaptivePortal", Desc: "Captive portal detection"},
|
||||
"capture": {Sym: "Capture", Desc: "Packet capture"},
|
||||
"cloud": {Sym: "Cloud", Desc: "detect cloud environment to learn instances IPs and DNS servers"},
|
||||
"cli": {Sym: "CLI", Desc: "embed the CLI into the tailscaled binary"},
|
||||
"cliconndiag": {Sym: "CLIConnDiag", Desc: "CLI connection error diagnostics"},
|
||||
"clientmetrics": {Sym: "ClientMetrics", Desc: "Client metrics support"},
|
||||
"clientupdate": {Sym: "ClientUpdate", Desc: "Client auto-update support"},
|
||||
"completion": {Sym: "Completion", Desc: "CLI shell completion"},
|
||||
"dbus": {Sym: "DBus", Desc: "Linux DBus support"},
|
||||
"clientupdate": {
|
||||
Sym: "ClientUpdate",
|
||||
Desc: "Client auto-update support",
|
||||
Deps: []FeatureTag{"c2n"},
|
||||
},
|
||||
"completion": {Sym: "Completion", Desc: "CLI shell completion"},
|
||||
"cloud": {Sym: "Cloud", Desc: "detect cloud environment to learn instances IPs and DNS servers"},
|
||||
"dbus": {
|
||||
Sym: "DBus",
|
||||
Desc: "Linux DBus support",
|
||||
ImplementationDetail: true,
|
||||
},
|
||||
"debug": {Sym: "Debug", Desc: "various debug support, for things that don't have or need their own more specific feature"},
|
||||
"debugeventbus": {Sym: "DebugEventBus", Desc: "eventbus debug support"},
|
||||
"debugportmapper": {
|
||||
@@ -144,6 +178,16 @@ var Features = map[FeatureTag]FeatureMeta{
|
||||
// by some other feature are missing, then it's an error by default unless you accept
|
||||
// that it's okay to proceed without that meta feature.
|
||||
},
|
||||
"peerapiclient": {
|
||||
Sym: "PeerAPIClient",
|
||||
Desc: "PeerAPI client support",
|
||||
ImplementationDetail: true,
|
||||
},
|
||||
"peerapiserver": {
|
||||
Sym: "PeerAPIServer",
|
||||
Desc: "PeerAPI server support",
|
||||
ImplementationDetail: true,
|
||||
},
|
||||
"portlist": {Sym: "PortList", Desc: "Optionally advertise listening service ports"},
|
||||
"portmapper": {Sym: "PortMapper", Desc: "NAT-PMP/PCP/UPnP port mapping support"},
|
||||
"posture": {Sym: "Posture", Desc: "Device posture checking support"},
|
||||
@@ -180,7 +224,7 @@ var Features = map[FeatureTag]FeatureMeta{
|
||||
"ssh": {
|
||||
Sym: "SSH",
|
||||
Desc: "Tailscale SSH support",
|
||||
Deps: []FeatureTag{"dbus", "netstack"},
|
||||
Deps: []FeatureTag{"c2n", "dbus", "netstack"},
|
||||
},
|
||||
"synology": {
|
||||
Sym: "Synology",
|
||||
@@ -192,7 +236,13 @@ var Features = map[FeatureTag]FeatureMeta{
|
||||
Desc: "Linux system tray",
|
||||
Deps: []FeatureTag{"dbus"},
|
||||
},
|
||||
"taildrop": {Sym: "Taildrop", Desc: "Taildrop (file sending) support"},
|
||||
"taildrop": {
|
||||
Sym: "Taildrop",
|
||||
Desc: "Taildrop (file sending) support",
|
||||
Deps: []FeatureTag{
|
||||
"peerapiclient", "peerapiserver", // assume Taildrop is both sides for now
|
||||
},
|
||||
},
|
||||
"tailnetlock": {Sym: "TailnetLock", Desc: "Tailnet Lock support"},
|
||||
"tap": {Sym: "Tap", Desc: "Experimental Layer 2 (ethernet) support"},
|
||||
"tpm": {Sym: "TPM", Desc: "TPM support"},
|
||||
@@ -200,6 +250,15 @@ var Features = map[FeatureTag]FeatureMeta{
|
||||
Sym: "UnixSocketIdentity",
|
||||
Desc: "differentiate between users accessing the LocalAPI over unix sockets (if omitted, all users have full access)",
|
||||
},
|
||||
"useroutes": {
|
||||
Sym: "UseRoutes",
|
||||
Desc: "Use routes advertised by other nodes",
|
||||
},
|
||||
"useexitnode": {
|
||||
Sym: "UseExitNode",
|
||||
Desc: "Use exit nodes",
|
||||
Deps: []FeatureTag{"peerapiclient", "useroutes"},
|
||||
},
|
||||
"useproxy": {
|
||||
Sym: "UseProxy",
|
||||
Desc: "Support using system proxies as specified by env vars or the system configuration to reach Tailscale servers.",
|
||||
|
@@ -32,12 +32,17 @@ import (
|
||||
// c2nHandlers maps an HTTP method and URI path (without query parameters) to
|
||||
// its handler. The exact method+path match is preferred, but if no entry
|
||||
// exists for that, a map entry with an empty method is used as a fallback.
|
||||
var c2nHandlers = map[methodAndPath]c2nHandler{
|
||||
// Debug.
|
||||
req("/echo"): handleC2NEcho,
|
||||
}
|
||||
var c2nHandlers map[methodAndPath]c2nHandler
|
||||
|
||||
func init() {
|
||||
c2nHandlers = map[methodAndPath]c2nHandler{}
|
||||
if buildfeatures.HasC2N {
|
||||
// Echo is the basic "ping" handler as used by the control plane to probe
|
||||
// whether a node is reachable. In particular, it's important for
|
||||
// high-availability subnet routers for the control plane to probe which of
|
||||
// several candidate nodes is reachable and actually alive.
|
||||
RegisterC2N("/echo", handleC2NEcho)
|
||||
}
|
||||
if buildfeatures.HasSSH {
|
||||
RegisterC2N("/ssh/usernames", handleC2NSSHUsernames)
|
||||
}
|
||||
@@ -69,6 +74,9 @@ func init() {
|
||||
// A pattern is like "GET /foo" (specific to an HTTP method) or "/foo" (all
|
||||
// methods). It panics if the pattern is already registered.
|
||||
func RegisterC2N(pattern string, h func(*LocalBackend, http.ResponseWriter, *http.Request)) {
|
||||
if !buildfeatures.HasC2N {
|
||||
return
|
||||
}
|
||||
k := req(pattern)
|
||||
if _, ok := c2nHandlers[k]; ok {
|
||||
panic(fmt.Sprintf("c2n: duplicate handler for %q", pattern))
|
||||
|
@@ -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
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
@@ -530,6 +530,9 @@ func (nb *nodeBackend) dnsConfigForNetmap(prefs ipn.PrefsView, selfExpired bool,
|
||||
}
|
||||
|
||||
func (nb *nodeBackend) exitNodeCanProxyDNS(exitNodeID tailcfg.StableNodeID) (dohURL string, ok bool) {
|
||||
if !buildfeatures.HasUseExitNode {
|
||||
return "", false
|
||||
}
|
||||
nb.mu.Lock()
|
||||
defer nb.mu.Unlock()
|
||||
return exitNodeCanProxyDNS(nb.netMap, nb.peers, exitNodeID)
|
||||
@@ -769,18 +772,20 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.
|
||||
// If we're using an exit node and that exit node is new enough (1.19.x+)
|
||||
// to run a DoH DNS proxy, then send all our DNS traffic through it,
|
||||
// unless we find resolvers with UseWithExitNode set, in which case we use that.
|
||||
if dohURL, ok := exitNodeCanProxyDNS(nm, peers, prefs.ExitNodeID()); ok {
|
||||
filtered := useWithExitNodeResolvers(nm.DNS.Resolvers)
|
||||
if len(filtered) > 0 {
|
||||
addDefault(filtered)
|
||||
} else {
|
||||
// If no default global resolvers with the override
|
||||
// are configured, configure the exit node's resolver.
|
||||
addDefault([]*dnstype.Resolver{{Addr: dohURL}})
|
||||
}
|
||||
if buildfeatures.HasUseExitNode {
|
||||
if dohURL, ok := exitNodeCanProxyDNS(nm, peers, prefs.ExitNodeID()); ok {
|
||||
filtered := useWithExitNodeResolvers(nm.DNS.Resolvers)
|
||||
if len(filtered) > 0 {
|
||||
addDefault(filtered)
|
||||
} else {
|
||||
// If no default global resolvers with the override
|
||||
// are configured, configure the exit node's resolver.
|
||||
addDefault([]*dnstype.Resolver{{Addr: dohURL}})
|
||||
}
|
||||
|
||||
addSplitDNSRoutes(useWithExitNodeRoutes(nm.DNS.Routes))
|
||||
return dcfg
|
||||
addSplitDNSRoutes(useWithExitNodeRoutes(nm.DNS.Routes))
|
||||
return dcfg
|
||||
}
|
||||
}
|
||||
|
||||
// If the user has set default resolvers ("override local DNS"), prefer to
|
||||
@@ -788,7 +793,7 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.
|
||||
// node resolvers, use those as the default.
|
||||
if len(nm.DNS.Resolvers) > 0 {
|
||||
addDefault(nm.DNS.Resolvers)
|
||||
} else {
|
||||
} else if buildfeatures.HasUseExitNode {
|
||||
if resolvers, ok := wireguardExitNodeDNSResolvers(nm, peers, prefs.ExitNodeID()); ok {
|
||||
addDefault(resolvers)
|
||||
}
|
||||
|
@@ -26,6 +26,7 @@ import (
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"golang.org/x/net/http/httpguts"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/hostinfo"
|
||||
@@ -131,6 +132,9 @@ type peerAPIListener struct {
|
||||
}
|
||||
|
||||
func (pln *peerAPIListener) Close() error {
|
||||
if !buildfeatures.HasPeerAPIServer {
|
||||
return nil
|
||||
}
|
||||
if pln.ln != nil {
|
||||
return pln.ln.Close()
|
||||
}
|
||||
@@ -138,6 +142,9 @@ func (pln *peerAPIListener) Close() error {
|
||||
}
|
||||
|
||||
func (pln *peerAPIListener) serve() {
|
||||
if !buildfeatures.HasPeerAPIServer {
|
||||
return
|
||||
}
|
||||
if pln.ln == nil {
|
||||
return
|
||||
}
|
||||
@@ -319,6 +326,9 @@ func peerAPIRequestShouldGetSecurityHeaders(r *http.Request) bool {
|
||||
//
|
||||
// It panics if the path is already registered.
|
||||
func RegisterPeerAPIHandler(path string, f func(PeerAPIHandler, http.ResponseWriter, *http.Request)) {
|
||||
if !buildfeatures.HasPeerAPIServer {
|
||||
return
|
||||
}
|
||||
if _, ok := peerAPIHandlers[path]; ok {
|
||||
panic(fmt.Sprintf("duplicate PeerAPI handler %q", path))
|
||||
}
|
||||
@@ -337,6 +347,10 @@ var (
|
||||
)
|
||||
|
||||
func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if !buildfeatures.HasPeerAPIServer {
|
||||
http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
if err := h.validatePeerAPIRequest(r); err != nil {
|
||||
metricInvalidRequests.Add(1)
|
||||
h.logf("invalid request from %v: %v", h.remoteAddr, err)
|
||||
|
@@ -6,6 +6,7 @@ package ipnlocal
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/util/clientmetric"
|
||||
@@ -85,6 +86,9 @@ func (e *prefsMetricsEditEvent) record() error {
|
||||
// false otherwise. The caller is responsible for ensuring that the id belongs to
|
||||
// an exit node.
|
||||
func (e *prefsMetricsEditEvent) exitNodeType(id tailcfg.StableNodeID) (props []exitNodeProperty, isNode bool) {
|
||||
if !buildfeatures.HasUseExitNode {
|
||||
return nil, false
|
||||
}
|
||||
var peer tailcfg.NodeView
|
||||
|
||||
if peer, isNode = e.node.PeerByStableID(id); isNode {
|
||||
|
@@ -72,7 +72,6 @@ var handler = map[string]LocalAPIHandler{
|
||||
// The other /localapi/v0/NAME handlers are exact matches and contain only NAME
|
||||
// without a trailing slash:
|
||||
"alpha-set-device-attrs": (*Handler).serveSetDeviceAttrs, // see tailscale/corp#24690
|
||||
"bugreport": (*Handler).serveBugReport,
|
||||
"check-ip-forwarding": (*Handler).serveCheckIPForwarding,
|
||||
"check-prefs": (*Handler).serveCheckPrefs,
|
||||
"check-reverse-path-filtering": (*Handler).serveCheckReversePathFiltering,
|
||||
@@ -90,21 +89,17 @@ var handler = map[string]LocalAPIHandler{
|
||||
"logtap": (*Handler).serveLogTap,
|
||||
"metrics": (*Handler).serveMetrics,
|
||||
"ping": (*Handler).servePing,
|
||||
"pprof": (*Handler).servePprof,
|
||||
"prefs": (*Handler).servePrefs,
|
||||
"query-feature": (*Handler).serveQueryFeature,
|
||||
"reload-config": (*Handler).reloadConfig,
|
||||
"reset-auth": (*Handler).serveResetAuth,
|
||||
"set-dns": (*Handler).serveSetDNS,
|
||||
"set-expiry-sooner": (*Handler).serveSetExpirySooner,
|
||||
"set-gui-visible": (*Handler).serveSetGUIVisible,
|
||||
"set-push-device-token": (*Handler).serveSetPushDeviceToken,
|
||||
"set-udp-gro-forwarding": (*Handler).serveSetUDPGROForwarding,
|
||||
"set-use-exit-node-enabled": (*Handler).serveSetUseExitNodeEnabled,
|
||||
"shutdown": (*Handler).serveShutdown,
|
||||
"start": (*Handler).serveStart,
|
||||
"status": (*Handler).serveStatus,
|
||||
"suggest-exit-node": (*Handler).serveSuggestExitNode,
|
||||
"update/check": (*Handler).serveUpdateCheck,
|
||||
"upload-client-metrics": (*Handler).serveUploadClientMetrics,
|
||||
"usermetrics": (*Handler).serveUserMetrics,
|
||||
@@ -116,6 +111,17 @@ func init() {
|
||||
if buildfeatures.HasAppConnectors {
|
||||
Register("appc-route-info", (*Handler).serveGetAppcRouteInfo)
|
||||
}
|
||||
if buildfeatures.HasUseExitNode {
|
||||
Register("suggest-exit-node", (*Handler).serveSuggestExitNode)
|
||||
Register("set-use-exit-node-enabled", (*Handler).serveSetUseExitNodeEnabled)
|
||||
}
|
||||
if buildfeatures.HasACME {
|
||||
Register("set-dns", (*Handler).serveSetDNS)
|
||||
}
|
||||
if buildfeatures.HasDebug {
|
||||
Register("bugreport", (*Handler).serveBugReport)
|
||||
Register("pprof", (*Handler).servePprof)
|
||||
}
|
||||
}
|
||||
|
||||
// Register registers a new LocalAPI handler for the given name.
|
||||
@@ -1291,6 +1297,10 @@ func (h *Handler) serveSetGUIVisible(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (h *Handler) serveSetUseExitNodeEnabled(w http.ResponseWriter, r *http.Request) {
|
||||
if !buildfeatures.HasUseExitNode {
|
||||
http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
if r.Method != httpm.POST {
|
||||
http.Error(w, "use POST", http.StatusMethodNotAllowed)
|
||||
return
|
||||
@@ -1629,6 +1639,10 @@ func dnsMessageTypeForString(s string) (t dnsmessage.Type, err error) {
|
||||
|
||||
// serveSuggestExitNode serves a POST endpoint for returning a suggested exit node.
|
||||
func (h *Handler) serveSuggestExitNode(w http.ResponseWriter, r *http.Request) {
|
||||
if !buildfeatures.HasUseExitNode {
|
||||
http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
if r.Method != httpm.GET {
|
||||
http.Error(w, "only GET allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
|
@@ -20,6 +20,7 @@ import (
|
||||
|
||||
"tailscale.com/atomicfile"
|
||||
"tailscale.com/drive"
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/tsaddr"
|
||||
@@ -787,6 +788,9 @@ func (p *Prefs) AdvertisesExitNode() bool {
|
||||
// SetAdvertiseExitNode mutates p (if non-nil) to add or remove the two
|
||||
// /0 exit node routes.
|
||||
func (p *Prefs) SetAdvertiseExitNode(runExit bool) {
|
||||
if !buildfeatures.HasAdvertiseExitNode {
|
||||
return
|
||||
}
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
|
@@ -27,6 +27,7 @@ import (
|
||||
dns "golang.org/x/net/dns/dnsmessage"
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/net/dns/publicdns"
|
||||
@@ -530,6 +531,9 @@ func (f *forwarder) send(ctx context.Context, fq *forwardQuery, rr resolverAndDe
|
||||
}()
|
||||
}
|
||||
if strings.HasPrefix(rr.name.Addr, "http://") {
|
||||
if !buildfeatures.HasPeerAPIClient {
|
||||
return nil, feature.ErrUnavailable
|
||||
}
|
||||
return f.sendDoH(ctx, rr.name.Addr, f.dialer.PeerAPIHTTPClient(), fq.packet)
|
||||
}
|
||||
if strings.HasPrefix(rr.name.Addr, "https://") {
|
||||
|
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/mdlayher/netlink"
|
||||
"go4.org/mem"
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/util/lineiter"
|
||||
)
|
||||
@@ -41,6 +42,9 @@ ens18 00000000 0100000A 0003 0 0 0 00000000
|
||||
ens18 0000000A 00000000 0001 0 0 0 0000FFFF 0 0 0
|
||||
*/
|
||||
func likelyHomeRouterIPLinux() (ret netip.Addr, myIP netip.Addr, ok bool) {
|
||||
if !buildfeatures.HasPortMapper {
|
||||
return
|
||||
}
|
||||
if procNetRouteErr.Load() {
|
||||
// If we failed to read /proc/net/route previously, don't keep trying.
|
||||
return ret, myIP, false
|
||||
|
@@ -14,6 +14,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/eventbus"
|
||||
@@ -181,6 +182,9 @@ func (m *Monitor) SetTailscaleInterfaceName(ifName string) {
|
||||
// It's the same as interfaces.LikelyHomeRouterIP, but it caches the
|
||||
// result until the monitor detects a network change.
|
||||
func (m *Monitor) GatewayAndSelfIP() (gw, myIP netip.Addr, ok bool) {
|
||||
if !buildfeatures.HasPortMapper {
|
||||
return
|
||||
}
|
||||
if m.static {
|
||||
return
|
||||
}
|
||||
|
@@ -573,6 +573,9 @@ var disableLikelyHomeRouterIPSelf = envknob.RegisterBool("TS_DEBUG_DISABLE_LIKEL
|
||||
// the LAN using that gateway.
|
||||
// This is used as the destination for UPnP, NAT-PMP, PCP, etc queries.
|
||||
func LikelyHomeRouterIP() (gateway, myIP netip.Addr, ok bool) {
|
||||
if !buildfeatures.HasPortMapper {
|
||||
return
|
||||
}
|
||||
// If we don't have a way to get the home router IP, then we can't do
|
||||
// anything; just return.
|
||||
if likelyHomeRouterIP == nil {
|
||||
|
@@ -20,6 +20,7 @@ import (
|
||||
|
||||
"go4.org/mem"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/neterror"
|
||||
"tailscale.com/net/netmon"
|
||||
@@ -262,10 +263,13 @@ func NewClient(c Config) *Client {
|
||||
panic("nil EventBus")
|
||||
}
|
||||
ret := &Client{
|
||||
logf: c.Logf,
|
||||
netMon: c.NetMon,
|
||||
ipAndGateway: netmon.LikelyHomeRouterIP, // TODO(bradfitz): move this to method on netMon
|
||||
onChange: c.OnChange,
|
||||
logf: c.Logf,
|
||||
netMon: c.NetMon,
|
||||
onChange: c.OnChange,
|
||||
}
|
||||
if buildfeatures.HasPortMapper {
|
||||
// TODO(bradfitz): move this to method on netMon
|
||||
ret.ipAndGateway = netmon.LikelyHomeRouterIP
|
||||
}
|
||||
ret.pubClient = c.EventBus.Client("portmapper")
|
||||
ret.updates = eventbus.Publish[portmappertype.Mapping](ret.pubClient)
|
||||
|
@@ -19,6 +19,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/net/dnscache"
|
||||
"tailscale.com/net/netknob"
|
||||
"tailscale.com/net/netmon"
|
||||
@@ -135,6 +137,9 @@ func (d *Dialer) TUNName() string {
|
||||
//
|
||||
// For example, "http://100.68.82.120:47830/dns-query".
|
||||
func (d *Dialer) SetExitDNSDoH(doh string) {
|
||||
if !buildfeatures.HasUseExitNode {
|
||||
return
|
||||
}
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if d.exitDNSDoHBase == doh {
|
||||
@@ -372,7 +377,7 @@ func (d *Dialer) userDialResolve(ctx context.Context, network, addr string) (net
|
||||
}
|
||||
|
||||
var r net.Resolver
|
||||
if exitDNSDoH != "" {
|
||||
if buildfeatures.HasUseExitNode && buildfeatures.HasPeerAPIClient && exitDNSDoH != "" {
|
||||
r.PreferGo = true
|
||||
r.Dial = func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return &dohConn{
|
||||
@@ -509,6 +514,9 @@ func (d *Dialer) UserDial(ctx context.Context, network, addr string) (net.Conn,
|
||||
// network must a "tcp" type, and addr must be an ip:port. Name resolution
|
||||
// is not supported.
|
||||
func (d *Dialer) dialPeerAPI(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if !buildfeatures.HasPeerAPIClient {
|
||||
return nil, feature.ErrUnavailable
|
||||
}
|
||||
switch network {
|
||||
case "tcp", "tcp6", "tcp4":
|
||||
default:
|
||||
@@ -551,6 +559,9 @@ func (d *Dialer) getPeerDialer() *net.Dialer {
|
||||
// The returned Client must not be mutated; it's owned by the Dialer
|
||||
// and shared by callers.
|
||||
func (d *Dialer) PeerAPIHTTPClient() *http.Client {
|
||||
if !buildfeatures.HasPeerAPIClient {
|
||||
panic("unreachable")
|
||||
}
|
||||
d.peerClientOnce.Do(func() {
|
||||
t := http.DefaultTransport.(*http.Transport).Clone()
|
||||
t.Dial = nil
|
||||
|
Reference in New Issue
Block a user