summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJordan Whited <jordan@tailscale.com>2025-04-09 10:25:57 -0700
committerGitHub <noreply@github.com>2025-04-09 10:25:57 -0700
commite17abbf461c0c014da0b38ec4bf44b00f2bc7ee4 (patch)
tree7bdd4dfc4d1ea949bbb75bf73784ddb658056006
parent7e296923ab57736a13602645d6eb3f793d798778 (diff)
downloadtailscale-e17abbf461c0c014da0b38ec4bf44b00f2bc7ee4.tar.xz
tailscale-e17abbf461c0c014da0b38ec4bf44b00f2bc7ee4.zip
cmd/tailscale,ipn: add relay-server-port "tailscale set" flag and Prefs field (#15594)
This flag is currently no-op and hidden. The flag does round trip through the related pref. Subsequent commits will tie them to net/udprelay.Server. There is no corresponding "tailscale up" flag, enabling/disabling of the relay server will only be supported via "tailscale set". This is a string flag in order to support disablement via empty string as a port value of 0 means "enable the server and listen on a random unused port". Disablement via empty string also follows existing flag convention, e.g. advertise-routes. Early internal discussions settled on "tailscale set --relay="<port>", but the author felt this was too ambiguous around client vs server, and may cause confusion in the future if we add related flags. Updates tailscale/corp#27502 Signed-off-by: Jordan Whited <jordan@tailscale.com>
-rw-r--r--cmd/tailscale/cli/set.go13
-rw-r--r--cmd/tailscale/cli/up.go1
-rw-r--r--ipn/ipn_clone.go4
-rw-r--r--ipn/ipn_view.go5
-rw-r--r--ipn/prefs.go25
-rw-r--r--ipn/prefs_test.go14
6 files changed, 61 insertions, 1 deletions
diff --git a/cmd/tailscale/cli/set.go b/cmd/tailscale/cli/set.go
index 07b3fe9ce..37db252ad 100644
--- a/cmd/tailscale/cli/set.go
+++ b/cmd/tailscale/cli/set.go
@@ -11,6 +11,7 @@ import (
"net/netip"
"os/exec"
"runtime"
+ "strconv"
"strings"
"github.com/peterbourgon/ff/v3/ffcli"
@@ -22,6 +23,7 @@ import (
"tailscale.com/net/tsaddr"
"tailscale.com/safesocket"
"tailscale.com/types/opt"
+ "tailscale.com/types/ptr"
"tailscale.com/types/views"
"tailscale.com/version"
)
@@ -62,6 +64,7 @@ type setArgsT struct {
snat bool
statefulFiltering bool
netfilterMode string
+ relayServerPort string
}
func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
@@ -82,6 +85,7 @@ func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
setf.BoolVar(&setArgs.updateApply, "auto-update", false, "automatically update to the latest available version")
setf.BoolVar(&setArgs.postureChecking, "posture-checking", false, hidden+"allow management plane to gather device posture information")
setf.BoolVar(&setArgs.runWebClient, "webclient", false, "expose the web interface for managing this node over Tailscale at port 5252")
+ setf.StringVar(&setArgs.relayServerPort, "relay-server-port", "", hidden+"UDP port number (0 will pick a random unused port) for the relay server to bind to, on all interfaces, or empty string to disable relay server functionality")
ffcomplete.Flag(setf, "exit-node", func(args []string) ([]string, ffcomplete.ShellCompDirective, error) {
st, err := localClient.Status(context.Background())
@@ -233,6 +237,15 @@ func runSet(ctx context.Context, args []string) (retErr error) {
}
}
}
+
+ if setArgs.relayServerPort != "" {
+ uport, err := strconv.ParseUint(setArgs.relayServerPort, 10, 16)
+ if err != nil {
+ return fmt.Errorf("failed to set relay server port: %v", err)
+ }
+ maskedPrefs.Prefs.RelayServerPort = ptr.To(int(uport))
+ }
+
checkPrefs := curPrefs.Clone()
checkPrefs.ApplyEdits(maskedPrefs)
if err := localClient.CheckPrefs(ctx, checkPrefs); err != nil {
diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go
index 26db85f13..d1e813b95 100644
--- a/cmd/tailscale/cli/up.go
+++ b/cmd/tailscale/cli/up.go
@@ -773,6 +773,7 @@ func init() {
addPrefFlagMapping("auto-update", "AutoUpdate.Apply")
addPrefFlagMapping("advertise-connector", "AppConnector")
addPrefFlagMapping("posture-checking", "PostureChecking")
+ addPrefFlagMapping("relay-server-port", "RelayServerPort")
}
func addPrefFlagMapping(flagName string, prefNames ...string) {
diff --git a/ipn/ipn_clone.go b/ipn/ipn_clone.go
index 4050fec46..65438444e 100644
--- a/ipn/ipn_clone.go
+++ b/ipn/ipn_clone.go
@@ -61,6 +61,9 @@ func (src *Prefs) Clone() *Prefs {
}
}
}
+ if dst.RelayServerPort != nil {
+ dst.RelayServerPort = ptr.To(*src.RelayServerPort)
+ }
dst.Persist = src.Persist.Clone()
return dst
}
@@ -96,6 +99,7 @@ var _PrefsCloneNeedsRegeneration = Prefs(struct {
PostureChecking bool
NetfilterKind string
DriveShares []*drive.Share
+ RelayServerPort *int
AllowSingleHosts marshalAsTrueInJSON
Persist *persist.Persist
}{})
diff --git a/ipn/ipn_view.go b/ipn/ipn_view.go
index e633a2633..871270b85 100644
--- a/ipn/ipn_view.go
+++ b/ipn/ipn_view.go
@@ -166,6 +166,10 @@ func (v PrefsView) NetfilterKind() string { return v.ж.Netfilte
func (v PrefsView) DriveShares() views.SliceView[*drive.Share, drive.ShareView] {
return views.SliceOfViews[*drive.Share, drive.ShareView](v.ж.DriveShares)
}
+func (v PrefsView) RelayServerPort() views.ValuePointer[int] {
+ return views.ValuePointerOf(v.ж.RelayServerPort)
+}
+
func (v PrefsView) AllowSingleHosts() marshalAsTrueInJSON { return v.ж.AllowSingleHosts }
func (v PrefsView) Persist() persist.PersistView { return v.ж.Persist.View() }
@@ -200,6 +204,7 @@ var _PrefsViewNeedsRegeneration = Prefs(struct {
PostureChecking bool
NetfilterKind string
DriveShares []*drive.Share
+ RelayServerPort *int
AllowSingleHosts marshalAsTrueInJSON
Persist *persist.Persist
}{})
diff --git a/ipn/prefs.go b/ipn/prefs.go
index 5b3e95b33..9d6008de1 100644
--- a/ipn/prefs.go
+++ b/ipn/prefs.go
@@ -246,6 +246,14 @@ type Prefs struct {
// by name.
DriveShares []*drive.Share
+ // RelayServerPort is the UDP port number for the relay server to bind to,
+ // on all interfaces. A non-nil zero value signifies a random unused port
+ // should be used. A nil value signifies relay server functionality
+ // should be disabled. This field is currently experimental, and therefore
+ // no guarantees are made about its current naming and functionality when
+ // non-nil/enabled.
+ RelayServerPort *int `json:",omitempty"`
+
// AllowSingleHosts was a legacy field that was always true
// for the past 4.5 years. It controlled whether Tailscale
// peers got /32 or /127 routes for each other.
@@ -337,6 +345,7 @@ type MaskedPrefs struct {
PostureCheckingSet bool `json:",omitempty"`
NetfilterKindSet bool `json:",omitempty"`
DriveSharesSet bool `json:",omitempty"`
+ RelayServerPortSet bool `json:",omitempty"`
}
// SetsInternal reports whether mp has any of the Internal*Set field bools set
@@ -555,6 +564,9 @@ func (p *Prefs) pretty(goos string) string {
}
sb.WriteString(p.AutoUpdate.Pretty())
sb.WriteString(p.AppConnector.Pretty())
+ if p.RelayServerPort != nil {
+ fmt.Fprintf(&sb, "relayServerPort=%d ", *p.RelayServerPort)
+ }
if p.Persist != nil {
sb.WriteString(p.Persist.Pretty())
} else {
@@ -616,7 +628,8 @@ func (p *Prefs) Equals(p2 *Prefs) bool {
p.AppConnector == p2.AppConnector &&
p.PostureChecking == p2.PostureChecking &&
slices.EqualFunc(p.DriveShares, p2.DriveShares, drive.SharesEqual) &&
- p.NetfilterKind == p2.NetfilterKind
+ p.NetfilterKind == p2.NetfilterKind &&
+ compareIntPtrs(p.RelayServerPort, p2.RelayServerPort)
}
func (au AutoUpdatePrefs) Pretty() string {
@@ -636,6 +649,16 @@ func (ap AppConnectorPrefs) Pretty() string {
return ""
}
+func compareIntPtrs(a, b *int) bool {
+ if (a == nil) != (b == nil) {
+ return false
+ }
+ if a == nil {
+ return true
+ }
+ return *a == *b
+}
+
// NewPrefs returns the default preferences to use.
func NewPrefs() *Prefs {
// Provide default values for options which might be missing
diff --git a/ipn/prefs_test.go b/ipn/prefs_test.go
index 91b835e3e..d28d161db 100644
--- a/ipn/prefs_test.go
+++ b/ipn/prefs_test.go
@@ -65,6 +65,7 @@ func TestPrefsEqual(t *testing.T) {
"PostureChecking",
"NetfilterKind",
"DriveShares",
+ "RelayServerPort",
"AllowSingleHosts",
"Persist",
}
@@ -73,6 +74,9 @@ func TestPrefsEqual(t *testing.T) {
have, prefsHandles)
}
+ relayServerPort := func(port int) *int {
+ return &port
+ }
nets := func(strs ...string) (ns []netip.Prefix) {
for _, s := range strs {
n, err := netip.ParsePrefix(s)
@@ -341,6 +345,16 @@ func TestPrefsEqual(t *testing.T) {
&Prefs{AdvertiseServices: []string{"svc:tux", "svc:amelie"}},
false,
},
+ {
+ &Prefs{RelayServerPort: relayServerPort(0)},
+ &Prefs{RelayServerPort: nil},
+ false,
+ },
+ {
+ &Prefs{RelayServerPort: relayServerPort(0)},
+ &Prefs{RelayServerPort: relayServerPort(1)},
+ false,
+ },
}
for i, tt := range tests {
got := tt.a.Equals(tt.b)