ipn{,/local},cmd/tailscale: add "sync" flag and pref to disable control map poll

For manual (human) testing, this lets the user disable control plane
map polls with "tailscale set --sync=false" (which survives restarts)
and "tailscale set --sync" to restore.

A high severity health warning is shown while this is active.

Updates #12639
Updates #17945

Change-Id: I83668fa5de3b5e5e25444df0815ec2a859153a6d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2025-11-17 08:06:16 -08:00
committed by Brad Fitzpatrick
parent 165a24744e
commit f1cddc6ecf
10 changed files with 136 additions and 6 deletions
+43
View File
@@ -57,6 +57,7 @@ func TestPrefsEqual(t *testing.T) {
"Egg",
"AdvertiseRoutes",
"AdvertiseServices",
"Sync",
"NoSNAT",
"NoStatefulFiltering",
"NetfilterMode",
@@ -404,6 +405,7 @@ func checkPrefs(t *testing.T, p Prefs) {
if err != nil {
t.Fatalf("PrefsFromBytes(p2) failed: bytes=%q; err=%v\n", p2.ToBytes(), err)
}
p2b.normalizeOptBools()
p2p := p2.Pretty()
p2bp := p2b.Pretty()
t.Logf("\np2p: %#v\np2bp: %#v\n", p2p, p2bp)
@@ -419,6 +421,42 @@ func checkPrefs(t *testing.T, p Prefs) {
}
}
// PrefsFromBytes documents that it preserves fields unset in the JSON.
// This verifies that stays true.
func TestPrefsFromBytesPreservesOldValues(t *testing.T) {
tests := []struct {
name string
old Prefs
json []byte
want Prefs
}{
{
name: "preserve-control-url",
old: Prefs{ControlURL: "https://foo"},
json: []byte(`{"RouteAll": true}`),
want: Prefs{ControlURL: "https://foo", RouteAll: true},
},
{
name: "opt.Bool", // test that we don't normalize it early
old: Prefs{Sync: "unset"},
json: []byte(`{}`),
want: Prefs{Sync: "unset"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
old := tt.old // shallow
err := PrefsFromBytes(tt.json, &old)
if err != nil {
t.Fatalf("PrefsFromBytes failed: %v", err)
}
if !old.Equals(&tt.want) {
t.Fatalf("got %+v; want %+v", old, tt.want)
}
})
}
}
func TestBasicPrefs(t *testing.T) {
tstest.PanicOnLog()
@@ -591,6 +629,11 @@ func TestPrefsPretty(t *testing.T) {
"linux",
`Prefs{ra=false dns=false want=false routes=[] nf=off update=off Persist=nil}`,
},
{
Prefs{Sync: "false"},
"linux",
"Prefs{ra=false dns=false want=false sync=false routes=[] nf=off update=off Persist=nil}",
},
}
for i, tt := range tests {
got := tt.p.pretty(tt.os)