various: disable stateful filtering by default ()

After some analysis, stateful filtering is only necessary in tailnets
that use `autogroup:danger-all` in `src` in ACLs. And in those cases
users explicitly specify that hosts outside of the tailnet should be
able to reach their nodes. To fix local DNS breakage in containers, we
disable stateful filtering by default.

Updates 

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
This commit is contained in:
Andrew Lytvynov 2024-05-20 11:44:29 -07:00 committed by GitHub
parent 6db1219185
commit c9179bc261
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 159 additions and 246 deletions

@ -24,6 +24,7 @@ import (
"tailscale.com/tka" "tailscale.com/tka"
"tailscale.com/tstest" "tailscale.com/tstest"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/opt"
"tailscale.com/types/persist" "tailscale.com/types/persist"
"tailscale.com/types/preftype" "tailscale.com/types/preftype"
"tailscale.com/version/distro" "tailscale.com/version/distro"
@ -176,9 +177,10 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
name: "bare_up_means_up", name: "bare_up_means_up",
flags: []string{}, flags: []string{},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL, ControlURL: ipn.DefaultControlURL,
WantRunning: false, WantRunning: false,
Hostname: "foo", Hostname: "foo",
NoStatefulFiltering: opt.NewBool(true),
}, },
want: "", want: "",
}, },
@ -186,11 +188,12 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
name: "losing_hostname", name: "losing_hostname",
flags: []string{"--accept-dns"}, flags: []string{"--accept-dns"},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL, ControlURL: ipn.DefaultControlURL,
WantRunning: false, WantRunning: false,
Hostname: "foo", Hostname: "foo",
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
want: accidentalUpPrefix + " --accept-dns --hostname=foo", want: accidentalUpPrefix + " --accept-dns --hostname=foo",
}, },
@ -198,10 +201,11 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
name: "hostname_changing_explicitly", name: "hostname_changing_explicitly",
flags: []string{"--hostname=bar"}, flags: []string{"--hostname=bar"},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL, ControlURL: ipn.DefaultControlURL,
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
Hostname: "foo", Hostname: "foo",
NoStatefulFiltering: opt.NewBool(true),
}, },
want: "", want: "",
}, },
@ -209,10 +213,11 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
name: "hostname_changing_empty_explicitly", name: "hostname_changing_empty_explicitly",
flags: []string{"--hostname="}, flags: []string{"--hostname="},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL, ControlURL: ipn.DefaultControlURL,
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
Hostname: "foo", Hostname: "foo",
NoStatefulFiltering: opt.NewBool(true),
}, },
want: "", want: "",
}, },
@ -228,10 +233,11 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
name: "implicit_operator_change", name: "implicit_operator_change",
flags: []string{"--hostname=foo"}, flags: []string{"--hostname=foo"},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL, ControlURL: ipn.DefaultControlURL,
OperatorUser: "alice", OperatorUser: "alice",
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
curUser: "eve", curUser: "eve",
want: accidentalUpPrefix + " --hostname=foo --operator=alice", want: accidentalUpPrefix + " --hostname=foo --operator=alice",
@ -240,10 +246,11 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
name: "implicit_operator_matches_shell_user", name: "implicit_operator_matches_shell_user",
flags: []string{"--hostname=foo"}, flags: []string{"--hostname=foo"},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL, ControlURL: ipn.DefaultControlURL,
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
OperatorUser: "alice", OperatorUser: "alice",
NoStatefulFiltering: opt.NewBool(true),
}, },
curUser: "alice", curUser: "alice",
want: "", want: "",
@ -260,6 +267,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("0.0.0.0/0"),
netip.MustParsePrefix("::/0"), netip.MustParsePrefix("::/0"),
}, },
NoStatefulFiltering: opt.NewBool(true),
}, },
want: accidentalUpPrefix + " --advertise-routes=10.0.42.0/24 --advertise-exit-node", want: accidentalUpPrefix + " --advertise-routes=10.0.42.0/24 --advertise-exit-node",
}, },
@ -275,6 +283,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("0.0.0.0/0"),
netip.MustParsePrefix("::/0"), netip.MustParsePrefix("::/0"),
}, },
NoStatefulFiltering: opt.NewBool(true),
}, },
want: "", want: "",
}, },
@ -290,6 +299,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("0.0.0.0/0"),
netip.MustParsePrefix("::/0"), netip.MustParsePrefix("::/0"),
}, },
NoStatefulFiltering: opt.NewBool(true),
}, },
want: "", want: "",
}, },
@ -297,9 +307,10 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
name: "advertise_exit_node", // Issue 1859 name: "advertise_exit_node", // Issue 1859
flags: []string{"--advertise-exit-node"}, flags: []string{"--advertise-exit-node"},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL, ControlURL: ipn.DefaultControlURL,
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
want: "", want: "",
}, },
@ -314,6 +325,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
AdvertiseRoutes: []netip.Prefix{ AdvertiseRoutes: []netip.Prefix{
netip.MustParsePrefix("1.2.0.0/16"), netip.MustParsePrefix("1.2.0.0/16"),
}, },
NoStatefulFiltering: opt.NewBool(true),
}, },
want: accidentalUpPrefix + " --advertise-exit-node --advertise-routes=1.2.0.0/16", want: accidentalUpPrefix + " --advertise-exit-node --advertise-routes=1.2.0.0/16",
}, },
@ -329,6 +341,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
netip.MustParsePrefix("::/0"), netip.MustParsePrefix("::/0"),
netip.MustParsePrefix("1.2.0.0/16"), netip.MustParsePrefix("1.2.0.0/16"),
}, },
NoStatefulFiltering: opt.NewBool(true),
}, },
want: accidentalUpPrefix + " --advertise-exit-node --advertise-routes=1.2.0.0/16", want: accidentalUpPrefix + " --advertise-exit-node --advertise-routes=1.2.0.0/16",
}, },
@ -340,7 +353,8 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
ExitNodeID: "fooID", ExitNodeID: "fooID",
NoStatefulFiltering: opt.NewBool(true),
}, },
want: "", want: "",
}, },
@ -362,8 +376,9 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("0.0.0.0/0"),
netip.MustParsePrefix("::/0"), netip.MustParsePrefix("::/0"),
}, },
NetfilterMode: preftype.NetfilterNoDivert, NetfilterMode: preftype.NetfilterNoDivert,
OperatorUser: "alice", OperatorUser: "alice",
NoStatefulFiltering: opt.NewBool(true),
}, },
curUser: "eve", curUser: "eve",
want: accidentalUpPrefix + " --force-reauth --accept-dns=false --accept-routes --advertise-exit-node --advertise-routes=10.0.0.0/16 --advertise-tags=tag:foo,tag:bar --exit-node=100.64.5.6 --hostname=myhostname --netfilter-mode=nodivert --operator=alice --shields-up", want: accidentalUpPrefix + " --force-reauth --accept-dns=false --accept-routes --advertise-exit-node --advertise-routes=10.0.0.0/16 --advertise-tags=tag:foo,tag:bar --exit-node=100.64.5.6 --hostname=myhostname --netfilter-mode=nodivert --operator=alice --shields-up",
@ -384,8 +399,9 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
AdvertiseRoutes: []netip.Prefix{ AdvertiseRoutes: []netip.Prefix{
netip.MustParsePrefix("10.0.0.0/16"), netip.MustParsePrefix("10.0.0.0/16"),
}, },
NetfilterMode: preftype.NetfilterNoDivert, NetfilterMode: preftype.NetfilterNoDivert,
OperatorUser: "alice", OperatorUser: "alice",
NoStatefulFiltering: opt.NewBool(true),
}, },
curUser: "eve", curUser: "eve",
want: accidentalUpPrefix + " --hostname=newhostname --accept-dns=false --accept-routes --advertise-routes=10.0.0.0/16 --advertise-tags=tag:foo,tag:bar --exit-node=100.64.5.6 --netfilter-mode=nodivert --operator=alice --shields-up", want: accidentalUpPrefix + " --hostname=newhostname --accept-dns=false --accept-routes --advertise-routes=10.0.0.0/16 --advertise-tags=tag:foo,tag:bar --exit-node=100.64.5.6 --netfilter-mode=nodivert --operator=alice --shields-up",
@ -394,10 +410,11 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
name: "loggedout_is_implicit", name: "loggedout_is_implicit",
flags: []string{"--hostname=foo"}, flags: []string{"--hostname=foo"},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL, ControlURL: ipn.DefaultControlURL,
LoggedOut: true, LoggedOut: true,
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
want: "", // not an error. LoggedOut is implicit. want: "", // not an error. LoggedOut is implicit.
}, },
@ -440,6 +457,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
netip.MustParsePrefix("::/0"), netip.MustParsePrefix("::/0"),
netip.MustParsePrefix("1.2.0.0/16"), netip.MustParsePrefix("1.2.0.0/16"),
}, },
NoStatefulFiltering: opt.NewBool(true),
}, },
want: accidentalUpPrefix + " --operator=expbits --advertise-exit-node --advertise-routes=1.2.0.0/16", want: accidentalUpPrefix + " --operator=expbits --advertise-exit-node --advertise-routes=1.2.0.0/16",
}, },
@ -455,6 +473,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
netip.MustParsePrefix("::/0"), netip.MustParsePrefix("::/0"),
netip.MustParsePrefix("1.2.0.0/16"), netip.MustParsePrefix("1.2.0.0/16"),
}, },
NoStatefulFiltering: opt.NewBool(true),
}, },
want: accidentalUpPrefix + " --advertise-routes=1.2.0.0/16 --operator=expbits --advertise-exit-node", want: accidentalUpPrefix + " --advertise-routes=1.2.0.0/16 --operator=expbits --advertise-exit-node",
}, },
@ -467,7 +486,8 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
Hostname: "foo", Hostname: "foo",
NoStatefulFiltering: opt.NewBool(true),
}, },
want: accidentalUpPrefix + " --auth-key=secretrand --force-reauth=false --reset --hostname=foo", want: accidentalUpPrefix + " --auth-key=secretrand --force-reauth=false --reset --hostname=foo",
}, },
@ -479,7 +499,8 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
ExitNodeIP: netip.MustParseAddr("100.64.5.4"), ExitNodeIP: netip.MustParseAddr("100.64.5.4"),
NoStatefulFiltering: opt.NewBool(true),
}, },
want: accidentalUpPrefix + " --hostname=foo --exit-node=100.64.5.4", want: accidentalUpPrefix + " --hostname=foo --exit-node=100.64.5.4",
}, },
@ -492,7 +513,8 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
ExitNodeID: "some_stable_id", ExitNodeID: "some_stable_id",
NoStatefulFiltering: opt.NewBool(true),
}, },
want: accidentalUpPrefix + " --hostname=foo --exit-node=100.64.5.7", want: accidentalUpPrefix + " --hostname=foo --exit-node=100.64.5.7",
}, },
@ -507,6 +529,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
ExitNodeAllowLANAccess: true, ExitNodeAllowLANAccess: true,
ExitNodeID: "some_stable_id", ExitNodeID: "some_stable_id",
NoStatefulFiltering: opt.NewBool(true),
}, },
want: accidentalUpPrefix + " --hostname=foo --exit-node-allow-lan-access --exit-node=100.2.3.4", want: accidentalUpPrefix + " --hostname=foo --exit-node-allow-lan-access --exit-node=100.2.3.4",
}, },
@ -514,9 +537,10 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
name: "ignore_login_server_synonym", name: "ignore_login_server_synonym",
flags: []string{"--login-server=https://controlplane.tailscale.com"}, flags: []string{"--login-server=https://controlplane.tailscale.com"},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com", ControlURL: "https://login.tailscale.com",
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
want: "", // not an error want: "", // not an error
}, },
@ -524,9 +548,10 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
name: "ignore_login_server_synonym_on_other_change", name: "ignore_login_server_synonym_on_other_change",
flags: []string{"--netfilter-mode=off"}, flags: []string{"--netfilter-mode=off"},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com", ControlURL: "https://login.tailscale.com",
CorpDNS: false, CorpDNS: false,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
want: accidentalUpPrefix + " --netfilter-mode=off --accept-dns=false", want: accidentalUpPrefix + " --netfilter-mode=off --accept-dns=false",
}, },
@ -536,10 +561,11 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
name: "synology_permit_omit_accept_routes", name: "synology_permit_omit_accept_routes",
flags: []string{"--hostname=foo"}, flags: []string{"--hostname=foo"},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com", ControlURL: "https://login.tailscale.com",
CorpDNS: true, CorpDNS: true,
RouteAll: true, RouteAll: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
goos: "linux", goos: "linux",
distro: distro.Synology, distro: distro.Synology,
@ -551,10 +577,11 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
name: "not_synology_dont_permit_omit_accept_routes", name: "not_synology_dont_permit_omit_accept_routes",
flags: []string{"--hostname=foo"}, flags: []string{"--hostname=foo"},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com", ControlURL: "https://login.tailscale.com",
CorpDNS: true, CorpDNS: true,
RouteAll: true, RouteAll: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
goos: "linux", goos: "linux",
distro: "", // not Synology distro: "", // not Synology
@ -564,10 +591,11 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
name: "profile_name_ignored_in_up", name: "profile_name_ignored_in_up",
flags: []string{"--hostname=foo"}, flags: []string{"--hostname=foo"},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com", ControlURL: "https://login.tailscale.com",
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
ProfileName: "foo", ProfileName: "foo",
NoStatefulFiltering: opt.NewBool(true),
}, },
goos: "linux", goos: "linux",
want: "", want: "",
@ -630,7 +658,7 @@ func TestPrefsFromUpArgs(t *testing.T) {
ControlURL: ipn.DefaultControlURL, ControlURL: ipn.DefaultControlURL,
WantRunning: true, WantRunning: true,
NoSNAT: false, NoSNAT: false,
NoStatefulFiltering: "false", NoStatefulFiltering: "true",
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
CorpDNS: true, CorpDNS: true,
AutoUpdate: ipn.AutoUpdatePrefs{ AutoUpdate: ipn.AutoUpdatePrefs{
@ -648,7 +676,7 @@ func TestPrefsFromUpArgs(t *testing.T) {
CorpDNS: true, CorpDNS: true,
RouteAll: true, RouteAll: true,
NoSNAT: false, NoSNAT: false,
NoStatefulFiltering: "false", NoStatefulFiltering: "true",
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
AutoUpdate: ipn.AutoUpdatePrefs{ AutoUpdate: ipn.AutoUpdatePrefs{
Check: true, Check: true,
@ -666,7 +694,7 @@ func TestPrefsFromUpArgs(t *testing.T) {
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("0.0.0.0/0"),
netip.MustParsePrefix("::/0"), netip.MustParsePrefix("::/0"),
}, },
NoStatefulFiltering: "false", NoStatefulFiltering: "true",
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
AutoUpdate: ipn.AutoUpdatePrefs{ AutoUpdate: ipn.AutoUpdatePrefs{
Check: true, Check: true,
@ -1033,10 +1061,11 @@ func TestUpdatePrefs(t *testing.T) {
name: "change_login_server", name: "change_login_server",
flags: []string{"--login-server=https://localhost:1000"}, flags: []string{"--login-server=https://localhost:1000"},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com", ControlURL: "https://login.tailscale.com",
Persist: &persist.Persist{UserProfile: tailcfg.UserProfile{LoginName: "crawshaw.github"}}, Persist: &persist.Persist{UserProfile: tailcfg.UserProfile{LoginName: "crawshaw.github"}},
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
env: upCheckEnv{backendState: "Running"}, env: upCheckEnv{backendState: "Running"},
wantSimpleUp: true, wantSimpleUp: true,
@ -1047,10 +1076,11 @@ func TestUpdatePrefs(t *testing.T) {
name: "change_tags", name: "change_tags",
flags: []string{"--advertise-tags=tag:foo"}, flags: []string{"--advertise-tags=tag:foo"},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com", ControlURL: "https://login.tailscale.com",
Persist: &persist.Persist{UserProfile: tailcfg.UserProfile{LoginName: "crawshaw.github"}}, Persist: &persist.Persist{UserProfile: tailcfg.UserProfile{LoginName: "crawshaw.github"}},
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
env: upCheckEnv{backendState: "Running"}, env: upCheckEnv{backendState: "Running"},
}, },
@ -1059,10 +1089,11 @@ func TestUpdatePrefs(t *testing.T) {
name: "explicit_empty_operator", name: "explicit_empty_operator",
flags: []string{"--operator="}, flags: []string{"--operator="},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com", ControlURL: "https://login.tailscale.com",
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
OperatorUser: "somebody", OperatorUser: "somebody",
NoStatefulFiltering: opt.NewBool(true),
}, },
env: upCheckEnv{user: "somebody", backendState: "Running"}, env: upCheckEnv{user: "somebody", backendState: "Running"},
wantJustEditMP: &ipn.MaskedPrefs{ wantJustEditMP: &ipn.MaskedPrefs{
@ -1079,10 +1110,11 @@ func TestUpdatePrefs(t *testing.T) {
name: "enable_ssh", name: "enable_ssh",
flags: []string{"--ssh"}, flags: []string{"--ssh"},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com", ControlURL: "https://login.tailscale.com",
Persist: &persist.Persist{UserProfile: tailcfg.UserProfile{LoginName: "crawshaw.github"}}, Persist: &persist.Persist{UserProfile: tailcfg.UserProfile{LoginName: "crawshaw.github"}},
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
wantJustEditMP: &ipn.MaskedPrefs{ wantJustEditMP: &ipn.MaskedPrefs{
RunSSHSet: true, RunSSHSet: true,
@ -1099,11 +1131,12 @@ func TestUpdatePrefs(t *testing.T) {
name: "disable_ssh", name: "disable_ssh",
flags: []string{"--ssh=false"}, flags: []string{"--ssh=false"},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com", ControlURL: "https://login.tailscale.com",
Persist: &persist.Persist{UserProfile: tailcfg.UserProfile{LoginName: "crawshaw.github"}}, Persist: &persist.Persist{UserProfile: tailcfg.UserProfile{LoginName: "crawshaw.github"}},
CorpDNS: true, CorpDNS: true,
RunSSH: true, RunSSH: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
wantJustEditMP: &ipn.MaskedPrefs{ wantJustEditMP: &ipn.MaskedPrefs{
RunSSHSet: true, RunSSHSet: true,
@ -1123,11 +1156,12 @@ func TestUpdatePrefs(t *testing.T) {
flags: []string{"--ssh=false"}, flags: []string{"--ssh=false"},
sshOverTailscale: true, sshOverTailscale: true,
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com", ControlURL: "https://login.tailscale.com",
Persist: &persist.Persist{UserProfile: tailcfg.UserProfile{LoginName: "crawshaw.github"}}, Persist: &persist.Persist{UserProfile: tailcfg.UserProfile{LoginName: "crawshaw.github"}},
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
RunSSH: true, RunSSH: true,
NoStatefulFiltering: opt.NewBool(true),
}, },
wantJustEditMP: &ipn.MaskedPrefs{ wantJustEditMP: &ipn.MaskedPrefs{
RunSSHSet: true, RunSSHSet: true,
@ -1146,10 +1180,11 @@ func TestUpdatePrefs(t *testing.T) {
flags: []string{"--ssh=true"}, flags: []string{"--ssh=true"},
sshOverTailscale: true, sshOverTailscale: true,
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com", ControlURL: "https://login.tailscale.com",
Persist: &persist.Persist{UserProfile: tailcfg.UserProfile{LoginName: "crawshaw.github"}}, Persist: &persist.Persist{UserProfile: tailcfg.UserProfile{LoginName: "crawshaw.github"}},
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
wantJustEditMP: &ipn.MaskedPrefs{ wantJustEditMP: &ipn.MaskedPrefs{
RunSSHSet: true, RunSSHSet: true,
@ -1168,10 +1203,11 @@ func TestUpdatePrefs(t *testing.T) {
flags: []string{"--ssh=true", "--accept-risk=lose-ssh"}, flags: []string{"--ssh=true", "--accept-risk=lose-ssh"},
sshOverTailscale: true, sshOverTailscale: true,
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com", ControlURL: "https://login.tailscale.com",
Persist: &persist.Persist{UserProfile: tailcfg.UserProfile{LoginName: "crawshaw.github"}}, Persist: &persist.Persist{UserProfile: tailcfg.UserProfile{LoginName: "crawshaw.github"}},
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
wantJustEditMP: &ipn.MaskedPrefs{ wantJustEditMP: &ipn.MaskedPrefs{
RunSSHSet: true, RunSSHSet: true,
@ -1189,11 +1225,12 @@ func TestUpdatePrefs(t *testing.T) {
flags: []string{"--ssh=false", "--accept-risk=lose-ssh"}, flags: []string{"--ssh=false", "--accept-risk=lose-ssh"},
sshOverTailscale: true, sshOverTailscale: true,
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com", ControlURL: "https://login.tailscale.com",
Persist: &persist.Persist{UserProfile: tailcfg.UserProfile{LoginName: "crawshaw.github"}}, Persist: &persist.Persist{UserProfile: tailcfg.UserProfile{LoginName: "crawshaw.github"}},
CorpDNS: true, CorpDNS: true,
RunSSH: true, RunSSH: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
wantJustEditMP: &ipn.MaskedPrefs{ wantJustEditMP: &ipn.MaskedPrefs{
RunSSHSet: true, RunSSHSet: true,
@ -1211,9 +1248,10 @@ func TestUpdatePrefs(t *testing.T) {
flags: []string{"--force-reauth"}, flags: []string{"--force-reauth"},
sshOverTailscale: true, sshOverTailscale: true,
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com", ControlURL: "https://login.tailscale.com",
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
env: upCheckEnv{backendState: "Running"}, env: upCheckEnv{backendState: "Running"},
wantErrSubtr: "aborted, no changes made", wantErrSubtr: "aborted, no changes made",
@ -1223,9 +1261,10 @@ func TestUpdatePrefs(t *testing.T) {
flags: []string{"--force-reauth", "--accept-risk=lose-ssh"}, flags: []string{"--force-reauth", "--accept-risk=lose-ssh"},
sshOverTailscale: true, sshOverTailscale: true,
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com", ControlURL: "https://login.tailscale.com",
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
wantJustEditMP: nil, wantJustEditMP: nil,
env: upCheckEnv{backendState: "Running"}, env: upCheckEnv{backendState: "Running"},
@ -1234,9 +1273,10 @@ func TestUpdatePrefs(t *testing.T) {
name: "advertise_connector", name: "advertise_connector",
flags: []string{"--advertise-connector"}, flags: []string{"--advertise-connector"},
curPrefs: &ipn.Prefs{ curPrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL, ControlURL: ipn.DefaultControlURL,
CorpDNS: true, CorpDNS: true,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(true),
}, },
wantJustEditMP: &ipn.MaskedPrefs{ wantJustEditMP: &ipn.MaskedPrefs{
AppConnectorSet: true, AppConnectorSet: true,
@ -1259,6 +1299,7 @@ func TestUpdatePrefs(t *testing.T) {
AppConnector: ipn.AppConnectorPrefs{ AppConnector: ipn.AppConnectorPrefs{
Advertise: true, Advertise: true,
}, },
NoStatefulFiltering: opt.NewBool(true),
}, },
wantJustEditMP: &ipn.MaskedPrefs{ wantJustEditMP: &ipn.MaskedPrefs{
AppConnectorSet: true, AppConnectorSet: true,

@ -103,7 +103,7 @@ func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
switch goos { switch goos {
case "linux": case "linux":
setf.BoolVar(&setArgs.snat, "snat-subnet-routes", true, "source NAT traffic to local routes advertised with --advertise-routes") setf.BoolVar(&setArgs.snat, "snat-subnet-routes", true, "source NAT traffic to local routes advertised with --advertise-routes")
setf.BoolVar(&setArgs.statefulFiltering, "stateful-filtering", true, "apply stateful filtering to forwarded packets (subnet routers, exit nodes, etc.)") setf.BoolVar(&setArgs.statefulFiltering, "stateful-filtering", false, "apply stateful filtering to forwarded packets (subnet routers, exit nodes, etc.)")
setf.StringVar(&setArgs.netfilterMode, "netfilter-mode", defaultNetfilterMode(), "netfilter mode (one of on, nodivert, off)") setf.StringVar(&setArgs.netfilterMode, "netfilter-mode", defaultNetfilterMode(), "netfilter mode (one of on, nodivert, off)")
case "windows": case "windows":
setf.BoolVar(&setArgs.forceDaemon, "unattended", false, "run in \"Unattended Mode\" where Tailscale keeps running even after the current GUI user logs out (Windows-only)") setf.BoolVar(&setArgs.forceDaemon, "unattended", false, "run in \"Unattended Mode\" where Tailscale keeps running even after the current GUI user logs out (Windows-only)")

@ -121,7 +121,7 @@ func newUpFlagSet(goos string, upArgs *upArgsT, cmd string) *flag.FlagSet {
switch goos { switch goos {
case "linux": case "linux":
upf.BoolVar(&upArgs.snat, "snat-subnet-routes", true, "source NAT traffic to local routes advertised with --advertise-routes") upf.BoolVar(&upArgs.snat, "snat-subnet-routes", true, "source NAT traffic to local routes advertised with --advertise-routes")
upf.BoolVar(&upArgs.statefulFiltering, "stateful-filtering", true, "apply stateful filtering to forwarded packets (subnet routers, exit nodes, etc.)") upf.BoolVar(&upArgs.statefulFiltering, "stateful-filtering", false, "apply stateful filtering to forwarded packets (subnet routers, exit nodes, etc.)")
upf.StringVar(&upArgs.netfilterMode, "netfilter-mode", defaultNetfilterMode(), "netfilter mode (one of on, nodivert, off)") upf.StringVar(&upArgs.netfilterMode, "netfilter-mode", defaultNetfilterMode(), "netfilter mode (one of on, nodivert, off)")
case "windows": case "windows":
upf.BoolVar(&upArgs.forceDaemon, "unattended", false, "run in \"Unattended Mode\" where Tailscale keeps running even after the current GUI user logs out (Windows-only)") upf.BoolVar(&upArgs.forceDaemon, "unattended", false, "run in \"Unattended Mode\" where Tailscale keeps running even after the current GUI user logs out (Windows-only)")

@ -4186,18 +4186,7 @@ func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs ipn.PrefsView, oneC
} }
var doStatefulFiltering bool var doStatefulFiltering bool
if v, ok := prefs.NoStatefulFiltering().Get(); !ok { if v, ok := prefs.NoStatefulFiltering().Get(); ok && !v {
// The stateful filtering preference isn't explicitly set; this is
// unexpected since we expect it to be set during the profile
// backfill, but to be safe let's enable stateful filtering
// absent further information.
doStatefulFiltering = true
b.logf("[unexpected] NoStatefulFiltering preference not set; enabling stateful filtering")
} else if v {
// The preferences explicitly say "no stateful filtering", so
// we don't do it.
doStatefulFiltering = false
} else {
// The preferences explicitly "do stateful filtering" is turned // The preferences explicitly "do stateful filtering" is turned
// off, or to expand the double negative, to do stateful // off, or to expand the double negative, to do stateful
// filtering. Do so. // filtering. Do so.

@ -354,10 +354,6 @@ func (pm *profileManager) loadSavedPrefs(key ipn.StateKey) (ipn.PrefsView, error
return ipn.PrefsView{}, err return ipn.PrefsView{}, err
} }
savedPrefs := ipn.NewPrefs() savedPrefs := ipn.NewPrefs()
// NewPrefs sets a default NoStatefulFiltering, but we want to actually see
// if the saved state had an empty value. The empty value gets migrated
// based on NoSNAT, while a default "false" does not.
savedPrefs.NoStatefulFiltering = ""
if err := ipn.PrefsFromBytes(bs, savedPrefs); err != nil { if err := ipn.PrefsFromBytes(bs, savedPrefs); err != nil {
return ipn.PrefsView{}, fmt.Errorf("parsing saved prefs: %v", err) return ipn.PrefsView{}, fmt.Errorf("parsing saved prefs: %v", err)
} }
@ -382,32 +378,6 @@ func (pm *profileManager) loadSavedPrefs(key ipn.StateKey) (ipn.PrefsView, error
savedPrefs.AutoUpdate.Apply.Clear() savedPrefs.AutoUpdate.Apply.Clear()
} }
// Backfill a missing NoStatefulFiltering field based on the value of
// the NoSNAT field; we want to apply stateful filtering in all cases
// *except* where the user has disabled SNAT.
//
// Only backfill if the user hasn't set a value for
// NoStatefulFiltering, however.
_, haveNoStateful := savedPrefs.NoStatefulFiltering.Get()
if !haveNoStateful {
if savedPrefs.NoSNAT {
pm.logf("backfilling NoStatefulFiltering field to true because NoSNAT is set")
// No SNAT: no stateful filtering
savedPrefs.NoStatefulFiltering.Set(true)
} else {
pm.logf("backfilling NoStatefulFiltering field to false because NoSNAT is not set")
// SNAT (default): apply stateful filtering
savedPrefs.NoStatefulFiltering.Set(false)
}
// Write back to the preferences store now that we've updated it.
if err := pm.writePrefsToStore(key, savedPrefs.View()); err != nil {
return ipn.PrefsView{}, err
}
}
return savedPrefs.View(), nil return savedPrefs.View(), nil
} }

@ -4,7 +4,6 @@
package ipnlocal package ipnlocal
import ( import (
"encoding/json"
"fmt" "fmt"
"os/user" "os/user"
"strconv" "strconv"
@ -13,14 +12,12 @@ import (
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-cmp/cmp/cmpopts"
"tailscale.com/clientupdate" "tailscale.com/clientupdate"
"tailscale.com/envknob"
"tailscale.com/health" "tailscale.com/health"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/ipn/store/mem" "tailscale.com/ipn/store/mem"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/opt"
"tailscale.com/types/persist" "tailscale.com/types/persist"
"tailscale.com/util/must" "tailscale.com/util/must"
) )
@ -604,89 +601,6 @@ func TestProfileManagementWindows(t *testing.T) {
} }
} }
func TestProfileBackfillStatefulFiltering(t *testing.T) {
envknob.Setenv("TS_DEBUG_PROFILES", "true")
tests := []struct {
noSNAT bool
noStateful opt.Bool
want bool
}{
// Default: NoSNAT is false, NoStatefulFiltering is false, so
// we want it to stay false.
{false, "false", false},
// NoSNAT being set to true and NoStatefulFiltering being false
// should result in NoStatefulFiltering still being false,
// since it was explicitly set.
{true, "false", false},
// If NoSNAT is false, and NoStatefulFiltering is unset, we
// backfill it to 'false'.
{false, "", false},
// If NoSNAT is true, and NoStatefulFiltering is unset, we
// backfill to 'true' to not break users of NoSNAT.
//
// In other words: if the user is not using SNAT, they almost
// certainly also don't want to use stateful filtering.
{true, "", true},
// However, if the user specifies both NoSNAT and stateful
// filtering, don't change that.
{true, "true", true},
{false, "true", true},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("noSNAT=%v,noStateful=%q", tt.noSNAT, tt.noStateful), func(t *testing.T) {
prefs := ipn.NewPrefs()
prefs.Persist = &persist.Persist{
NodeID: tailcfg.StableNodeID("node1"),
UserProfile: tailcfg.UserProfile{
ID: tailcfg.UserID(1),
LoginName: "user1@example.com",
},
}
prefs.NoSNAT = tt.noSNAT
prefs.NoStatefulFiltering = tt.noStateful
// Make enough of a state store to load the prefs.
const profileName = "profile1"
bn := must.Get(json.Marshal(map[string]any{
string(ipn.CurrentProfileStateKey): []byte(profileName),
string(ipn.KnownProfilesStateKey): must.Get(json.Marshal(map[ipn.ProfileID]*ipn.LoginProfile{
profileName: {
ID: "profile1-id",
Key: profileName,
},
})),
profileName: prefs.ToBytes(),
}))
store := new(mem.Store)
err := store.LoadFromJSON([]byte(bn))
if err != nil {
t.Fatal(err)
}
ht := new(health.Tracker)
pm, err := newProfileManagerWithGOOS(store, t.Logf, ht, "linux")
if err != nil {
t.Fatal(err)
}
// Get the current profile and verify that we backfilled our
// StatefulFiltering boolean.
pf := pm.CurrentPrefs()
if !pf.NoStatefulFiltering().EqualBool(tt.want) {
t.Fatalf("got NoStatefulFiltering=%q, want %v", pf.NoStatefulFiltering(), tt.want)
}
})
}
}
// TestDefaultPrefs tests that defaultPrefs is just NewPrefs with // TestDefaultPrefs tests that defaultPrefs is just NewPrefs with
// LoggedOut=true (the Prefs we use before connecting to control). We shouldn't // LoggedOut=true (the Prefs we use before connecting to control). We shouldn't
// be putting any defaulting there, and instead put all defaults in NewPrefs. // be putting any defaulting there, and instead put all defaults in NewPrefs.

@ -191,17 +191,16 @@ type Prefs struct {
// Linux-only. // Linux-only.
NoSNAT bool NoSNAT bool
// NoStatefulFiltering specifies whether to apply stateful filtering // NoStatefulFiltering specifies whether to apply stateful filtering when
// when advertising routes in AdvertiseRoutes. The default is to apply // advertising routes in AdvertiseRoutes. The default is to not apply
// stateful filtering. // stateful filtering.
// //
// To allow inbound connections from advertised routes, both NoSNAT and // To allow inbound connections from advertised routes, both NoSNAT and
// NoStatefulFiltering must be true. // NoStatefulFiltering must be true.
// //
// This is an opt.Bool because it was added after NoSNAT, but is backfilled // This is an opt.Bool because it was first added after NoSNAT, with a
// based on the value of that parameter. We need to treat it as a tristate: // backfill based on the value of that parameter. The backfill has been
// true, false, or unset, and backfill based on that value. See // removed since then, but the field remains an opt.Bool.
// ipn/ipnlocal for more details on the backfill.
// //
// Linux-only. // Linux-only.
NoStatefulFiltering opt.Bool `json:",omitempty"` NoStatefulFiltering opt.Bool `json:",omitempty"`
@ -666,7 +665,7 @@ func NewPrefs() *Prefs {
CorpDNS: true, CorpDNS: true,
WantRunning: false, WantRunning: false,
NetfilterMode: preftype.NetfilterOn, NetfilterMode: preftype.NetfilterOn,
NoStatefulFiltering: opt.NewBool(false), NoStatefulFiltering: opt.NewBool(true),
AutoUpdate: AutoUpdatePrefs{ AutoUpdate: AutoUpdatePrefs{
Check: true, Check: true,
Apply: opt.Bool("unset"), Apply: opt.Bool("unset"),

@ -469,7 +469,7 @@ func (r *linuxRouter) updateStatefulFilteringWithDockerWarning(cfg *Config) {
if _, found := ifstate.Interface["docker0"]; found { if _, found := ifstate.Interface["docker0"]; found {
r.health.SetWarnable(warnStatefulFilteringWithDocker, fmt.Errorf(""+ r.health.SetWarnable(warnStatefulFilteringWithDocker, fmt.Errorf(""+
"Stateful filtering is enabled and Docker was detected; this may prevent Docker containers "+ "Stateful filtering is enabled and Docker was detected; this may prevent Docker containers "+
"on this host from connecting to Tailscale nodes. "+ "on this host from resolving DNS and connecting to Tailscale nodes. "+
"See https://tailscale.com/s/stateful-docker", "See https://tailscale.com/s/stateful-docker",
)) ))
return return