@ -16,43 +16,46 @@ import (
"inet.af/netaddr"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/types/logger"
"tailscale.com/types/preftype"
)
// geese is a collection of gooses. It need not be complete.
// But it should include anything handled specially (e.g. linux, windows)
// and at least one thing that's not (darwin, freebsd).
var geese = [ ] string { "linux" , "darwin" , "windows" , "freebsd" }
// Test that checkForAccidentalSettingReverts's updateMaskedPrefsFromUpFlag can handle
// all flags. This will panic if a new flag creeps in that's unhandled.
//
// Also, issue 1880: advertise-exit-node was being ignored. Verify that all flags cause an edit.
func TestUpdateMaskedPrefsFromUpFlag ( t * testing . T ) {
mp := new ( ipn . MaskedPrefs )
upFlagSet . VisitAll ( func ( f * flag . Flag ) {
updateMaskedPrefsFromUpFlag ( mp , f . Name )
} )
for _ , goos := range geese {
var upArgs upArgsT
fs := newUpFlagSet ( goos , & upArgs )
fs . VisitAll ( func ( f * flag . Flag ) {
mp := new ( ipn . MaskedPrefs )
updateMaskedPrefsFromUpFlag ( mp , f . Name )
got := mp . Pretty ( )
wantEmpty := preflessFlag ( f . Name )
isEmpty := got == "MaskedPrefs{}"
if isEmpty != wantEmpty {
t . Errorf ( "flag %q created MaskedPrefs %s; want empty=%v" , f . Name , got , wantEmpty )
}
} )
}
}
func TestCheckForAccidentalSettingReverts ( t * testing . T ) {
f := func ( flags ... string ) map [ string ] bool {
m := make ( map [ string ] bool )
for _ , f := range flags {
m [ f ] = true
}
return m
}
tests := [ ] struct {
name string
// flags, if non-nil populates flagSet and mp.
flags [ ] string // if non-nil,
name string
flags [ ] string // argv to be parsed by FlagSet
curPrefs * ipn . Prefs
// flagSet and mp are required if flags is nil.
// flagSet is the set of flags that were explicitly set in flags.
// mp is the mutation to apply to server.
flagSet map [ string ] bool
mp * ipn . MaskedPrefs
curExitNodeIP netaddr . IP
curUser string // os.Getenv("USER") on the client side
goos string // empty means "linux"
curPrefs * ipn . Prefs
curUser string // os.Getenv("USER") on the client side
goos string // empty means "linux"
want string
want string
} {
{
name : "bare_up_means_up" ,
@ -78,58 +81,26 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
want : accidentalUpPrefix + " --accept-dns --hostname=foo" ,
} ,
{
name : "hostname_changing_explicitly" ,
flag Set: f ( "hostname" ) ,
name : "hostname_changing_explicitly" ,
flag s: [ ] string { "--hostname=bar" } ,
curPrefs : & ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
WantRunning : false ,
Hostname : "foo" ,
} ,
mp : & ipn . MaskedPrefs {
Prefs : ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
WantRunning : true ,
Hostname : "bar" ,
} ,
ControlURLSet : true ,
WantRunningSet : true ,
HostnameSet : true ,
} ,
want : "" ,
} ,
{
name : "hostname_changing_empty_explicitly" ,
flagSet : f ( "hostname" ) ,
curPrefs : & ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
WantRunning : false ,
Hostname : "foo" ,
} ,
mp : & ipn . MaskedPrefs {
Prefs : ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
WantRunning : true ,
Hostname : "" ,
} ,
ControlURLSet : true ,
WantRunningSet : true ,
HostnameSet : true ,
ControlURL : ipn . DefaultControlURL ,
CorpDNS : true ,
NetfilterMode : preftype . NetfilterOn ,
AllowSingleHosts : true ,
Hostname : "foo" ,
} ,
want : "" ,
} ,
{
name : "empty_slice_equals_nil_slice ",
flag Set: f ( "hostname" ) ,
name : "hostname_changing_empty_explicitly" ,
flags : [ ] string { "--hostname=" } ,
curPrefs : & ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
AdvertiseRoutes : [ ] netaddr . IPPrefix { } ,
} ,
mp : & ipn . MaskedPrefs {
Prefs : ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
AdvertiseRoutes : nil ,
} ,
ControlURLSet : true ,
ControlURL : ipn . DefaultControlURL ,
CorpDNS : true ,
NetfilterMode : preftype . NetfilterOn ,
AllowSingleHosts : true ,
Hostname : "foo" ,
} ,
want : "" ,
} ,
@ -137,45 +108,35 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
// Issue 1725: "tailscale up --authkey=..." (or other non-empty flags) works from
// a fresh server's initial prefs.
name : "up_with_default_prefs" ,
flag Set: f ( "authkey" ) ,
flag s: [ ] string { "--authkey=foosdlkfjskdljf" } ,
curPrefs : ipn . NewPrefs ( ) ,
mp : & ipn . MaskedPrefs {
Prefs : * defaultPrefsFromUpArgs ( t , "linux" ) ,
WantRunningSet : true ,
} ,
want : "" ,
want : "" ,
} ,
{
name : "implicit_operator_change" ,
flag Set: f ( "hostname" ) ,
name : "implicit_operator_change" ,
flags : [ ] string { "--hostname=foo" } ,
curPrefs : & ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
OperatorUser : "alice" ,
ControlURL : ipn . DefaultControlURL ,
OperatorUser : "alice" ,
AllowSingleHosts : true ,
CorpDNS : true ,
NetfilterMode : preftype . NetfilterOn ,
} ,
curUser : "eve" ,
mp : & ipn . MaskedPrefs {
Prefs : ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
} ,
ControlURLSet : true ,
} ,
want : accidentalUpPrefix + " --hostname= --operator=alice" ,
want : accidentalUpPrefix + " --hostname=foo --operator=alice" ,
} ,
{
name : "implicit_operator_matches_shell_user" ,
flag Set: f ( "hostname" ) ,
name : "implicit_operator_matches_shell_user" ,
flags : [ ] string { "--hostname=foo" } ,
curPrefs : & ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
OperatorUser : "alice" ,
ControlURL : ipn . DefaultControlURL ,
AllowSingleHosts : true ,
CorpDNS : true ,
NetfilterMode : preftype . NetfilterOn ,
OperatorUser : "alice" ,
} ,
curUser : "alice" ,
mp : & ipn . MaskedPrefs {
Prefs : ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
} ,
ControlURLSet : true ,
} ,
want : "" ,
want : "" ,
} ,
{
name : "error_advertised_routes_exit_node_removed" ,
@ -194,7 +155,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
want : accidentalUpPrefix + " --advertise-routes=10.0.42.0/24 --advertise-exit-node" ,
} ,
{
name : "advertised_routes_exit_node_removed ",
name : "advertised_routes_exit_node_removed _explicit ",
flags : [ ] string { "--advertise-routes=10.0.42.0/24" , "--advertise-exit-node=false" } ,
curPrefs : & ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
@ -210,27 +171,19 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
want : "" ,
} ,
{
name : "advertised_routes_includes_the_0_routes" , // but no --advertise-exit-node
flag Set: f ( "advertise-routes" ) ,
name : "advertised_routes_includes_the_0_routes" , // but no --advertise-exit-node
flag s: [ ] string { "--advertise-routes=11.1.43.0/24,0.0.0.0/0,::/0" } ,
curPrefs : & ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
ControlURL : ipn . DefaultControlURL ,
AllowSingleHosts : true ,
CorpDNS : true ,
NetfilterMode : preftype . NetfilterOn ,
AdvertiseRoutes : [ ] netaddr . IPPrefix {
netaddr . MustParseIPPrefix ( "10.0.42.0/24" ) ,
netaddr . MustParseIPPrefix ( "0.0.0.0/0" ) ,
netaddr . MustParseIPPrefix ( "::/0" ) ,
} ,
} ,
mp : & ipn . MaskedPrefs {
Prefs : ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
AdvertiseRoutes : [ ] netaddr . IPPrefix {
netaddr . MustParseIPPrefix ( "11.1.43.0/24" ) ,
netaddr . MustParseIPPrefix ( "0.0.0.0/0" ) ,
netaddr . MustParseIPPrefix ( "::/0" ) ,
} ,
} ,
AdvertiseRoutesSet : true ,
} ,
want : "" ,
} ,
{
@ -252,6 +205,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
AllowSingleHosts : true ,
CorpDNS : true ,
NetfilterMode : preftype . NetfilterOn ,
AdvertiseRoutes : [ ] netaddr . IPPrefix {
netaddr . MustParseIPPrefix ( "1.2.0.0/16" ) ,
} ,
@ -275,19 +229,16 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
want : accidentalUpPrefix + " --advertise-exit-node --advertise-routes=1.2.0.0/16" ,
} ,
{
name : "exit_node_clearing" , // Issue 1777
flag Set: f ( "exit-node" ) ,
name : "exit_node_clearing" , // Issue 1777
flag s: [ ] string { "--exit-node=" } ,
curPrefs : & ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
ControlURL : ipn . DefaultControlURL ,
AllowSingleHosts : true ,
CorpDNS : true ,
NetfilterMode : preftype . NetfilterOn ,
ExitNodeID : "fooID" ,
} ,
mp : & ipn . MaskedPrefs {
Prefs : ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
ExitNodeIP : netaddr . IP { } ,
} ,
ExitNodeIPSet : true ,
} ,
want : "" ,
} ,
{
@ -306,12 +257,14 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
ForceDaemon : true ,
AdvertiseRoutes : [ ] netaddr . IPPrefix {
netaddr . MustParseIPPrefix ( "10.0.0.0/16" ) ,
netaddr . MustParseIPPrefix ( "0.0.0.0/0" ) ,
netaddr . MustParseIPPrefix ( "::/0" ) ,
} ,
NetfilterMode : preftype . NetfilterNoDivert ,
OperatorUser : "alice" ,
} ,
curUser : "eve" ,
want : accidentalUpPrefix + " --force-reauth --accept- routes --host-routes=false --exit-node=100.64.5.6 --accept-dns=false --shields-up --advertise-tags=tag:foo,tag:bar --hostname=myhostname --unattended --advertise-routes=10.0.0.0/16 --netfilter-mode=nodivert --operator=alice ",
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 --host-routes=false --hostname=myhostname --netfilter-mode=nodivert --operator=alice --shields-up ",
} ,
{
name : "remove_all_implicit_except_hostname" ,
@ -334,63 +287,46 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
OperatorUser : "alice" ,
} ,
curUser : "eve" ,
want : accidentalUpPrefix + " --hostname=newhostname --accept- routes --host-routes=false --exit-node=100.64.5.6 --accept-dns=false --shields-up --advertise-tags=tag:foo,tag:bar --unattended --advertise-routes=10.0.0.0/16 --netfilter-mode=nodivert --operator=alice ",
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 --host-routes=false --netfilter-mode=nodivert --operator=alice --shields-up ",
} ,
{
name : "loggedout_is_implicit" ,
flag Set: f ( "advertise-exit-node" ) ,
name : "loggedout_is_implicit" ,
flag s: [ ] string { "--hostname=foo" } ,
curPrefs : & ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
LoggedOut : true ,
} ,
mp : & ipn . MaskedPrefs {
Prefs : ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
AdvertiseRoutes : [ ] netaddr . IPPrefix {
netaddr . MustParseIPPrefix ( "0.0.0.0/0" ) ,
} ,
} ,
AdvertiseRoutesSet : true ,
ControlURL : ipn . DefaultControlURL ,
LoggedOut : true ,
AllowSingleHosts : true ,
CorpDNS : true ,
NetfilterMode : preftype . NetfilterOn ,
} ,
// not an error. LoggedOut is implicit.
want : "" ,
want : "" , // not an error. LoggedOut is implicit.
} ,
{
// Test that a pre-1.8 version of Tailscale with bogus NoSNAT pref
// values is able to enable exit nodes without warnings.
name : "make_windows_exit_node" ,
flag Set: f ( "advertise-exit-node" ) ,
name : "make_windows_exit_node" ,
flag s: [ ] string { "--advertise-exit-node" } ,
curPrefs : & ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
NoSNAT : true , // assume this no-op accidental pre-1.8 value
ControlURL : ipn . DefaultControlURL ,
AllowSingleHosts : true ,
CorpDNS : true ,
// And assume this no-op accidental pre-1.8 value:
NoSNAT : true ,
} ,
goos : "windows" ,
mp : & ipn . MaskedPrefs {
Prefs : ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
AdvertiseRoutes : [ ] netaddr . IPPrefix {
netaddr . MustParseIPPrefix ( "192.168.0.0/16" ) ,
} ,
} ,
AdvertiseRoutesSet : true ,
} ,
want : "" , // not an error
} ,
{
name : "ignore_netfilter_change_non_linux" ,
flag Set: f ( "accept-dns" ) ,
name : "ignore_netfilter_change_non_linux" ,
flags : [ ] string { "--accept-dns" } ,
curPrefs : & ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
ControlURL : ipn . DefaultControlURL ,
AllowSingleHosts : true ,
NetfilterMode : preftype . NetfilterNoDivert , // we never had this bug, but pretend it got set non-zero on Windows somehow
} ,
goos : "windows" ,
mp : & ipn . MaskedPrefs {
Prefs : ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
CorpDNS : false ,
} ,
CorpDNSSet : true ,
} ,
want : "" , // not an error
} ,
{
@ -407,7 +343,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
netaddr . MustParseIPPrefix ( "1.2.0.0/16" ) ,
} ,
} ,
want : accidentalUpPrefix + " --operator=expbits --advertise- routes=1.2.0.0/16 --advertise-exit-node ",
want : accidentalUpPrefix + " --operator=expbits --advertise- exit-node --advertise-routes=1.2.0.0/16 ",
} ,
{
name : "operator_losing_routes_step2" , // https://twitter.com/EXPbits/status/1390418145047887877
@ -425,6 +361,47 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
} ,
want : accidentalUpPrefix + " --advertise-routes=1.2.0.0/16 --operator=expbits --advertise-exit-node" ,
} ,
{
name : "errors_preserve_explicit_flags" ,
flags : [ ] string { "--reset" , "--force-reauth=false" , "--authkey=secretrand" } ,
curPrefs : & ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
WantRunning : false ,
CorpDNS : true ,
NetfilterMode : preftype . NetfilterOn ,
AllowSingleHosts : true ,
Hostname : "foo" ,
} ,
want : accidentalUpPrefix + " --authkey=secretrand --force-reauth=false --reset --hostname=foo" ,
} ,
{
name : "error_exit_node_omit_with_ip_pref" ,
flags : [ ] string { "--hostname=foo" } ,
curPrefs : & ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
AllowSingleHosts : true ,
CorpDNS : true ,
NetfilterMode : preftype . NetfilterOn ,
ExitNodeIP : netaddr . MustParseIP ( "100.64.5.4" ) ,
} ,
want : accidentalUpPrefix + " --hostname=foo --exit-node=100.64.5.4" ,
} ,
{
name : "error_exit_node_omit_with_id_pref" ,
flags : [ ] string { "--hostname=foo" } ,
curExitNodeIP : netaddr . MustParseIP ( "100.64.5.7" ) ,
curPrefs : & ipn . Prefs {
ControlURL : ipn . DefaultControlURL ,
AllowSingleHosts : true ,
CorpDNS : true ,
NetfilterMode : preftype . NetfilterOn ,
ExitNodeID : "some_stable_id" ,
} ,
want : accidentalUpPrefix + " --hostname=foo --exit-node=100.64.5.7" ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
@ -432,23 +409,19 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
if tt . goos != "" {
goos = tt . goos
}
flagSet := tt . flagSet
mp := tt . mp
if tt . flags != nil {
if tt . flagSet != nil || tt . mp != nil {
t . Fatal ( "flags and flagSet/mp are mutually exclusive" )
}
var upArgs upArgsT
fs := newUpFlagSet ( goos , & upArgs )
fs . Parse ( tt . flags )
prefs , err := prefsFromUpArgs ( upArgs , t . Logf , new ( ipnstate . Status ) , goos )
if err != nil {
t . Fatal ( err )
}
flagSet , mp = flagSetAndMaskedPrefs ( prefs , fs )
var upArgs upArgsT
flagSet := newUpFlagSet ( goos , & upArgs )
flagSet . Parse ( tt . flags )
newPrefs , err := prefsFromUpArgs ( upArgs , t . Logf , new ( ipnstate . Status ) , goos )
if err != nil {
t . Fatal ( err )
}
applyImplicitPrefs ( newPrefs , tt . curPrefs , tt . curUser )
var got string
if err := checkForAccidentalSettingReverts ( flagSet , tt . curPrefs , mp , goos , tt . curUser ) ; err != nil {
if err := checkForAccidentalSettingReverts ( flagSet , tt . curPrefs , newPrefs , upCheckEnv {
goos : goos ,
curExitNodeIP : tt . curExitNodeIP ,
} ) ; err != nil {
got = err . Error ( )
}
if strings . TrimSpace ( got ) != tt . want {
@ -458,16 +431,6 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
}
}
func defaultPrefsFromUpArgs ( t testing . TB , goos string ) * ipn . Prefs {
upArgs := upArgsFromOSArgs ( goos )
prefs , err := prefsFromUpArgs ( upArgs , logger . Discard , new ( ipnstate . Status ) , "linux" )
if err != nil {
t . Fatalf ( "defaultPrefsFromUpArgs: %v" , err )
}
prefs . WantRunning = true
return prefs
}
func upArgsFromOSArgs ( goos string , flagArgs ... string ) ( args upArgsT ) {
fs := newUpFlagSet ( goos , & args )
fs . Parse ( flagArgs ) // populates args
@ -663,10 +626,17 @@ func TestPrefsFromUpArgs(t *testing.T) {
}
func TestPrefFlagMapping ( t * testing . T ) {
prefHasFlag := map [ string ] bool { }
for _ , pv := range prefsOfFlag {
for _ , pref := range pv {
prefHasFlag [ pref ] = true
}
}
prefType := reflect . TypeOf ( ipn . Prefs { } )
for i := 0 ; i < prefType . NumField ( ) ; i ++ {
prefName := prefType . Field ( i ) . Name
if _ , ok := flagForPref [ prefName ] ; ok {
if prefHasFlag[ prefName ] {
continue
}
switch prefName {
@ -684,3 +654,15 @@ func TestPrefFlagMapping(t *testing.T) {
t . Errorf ( "unexpected new ipn.Pref field %q is not handled by up.go (see addPrefFlagMapping and checkForAccidentalSettingReverts)" , prefName )
}
}
func TestFlagAppliesToOS ( t * testing . T ) {
for _ , goos := range geese {
var upArgs upArgsT
fs := newUpFlagSet ( goos , & upArgs )
fs . VisitAll ( func ( f * flag . Flag ) {
if ! flagAppliesToOS ( f . Name , goos ) {
t . Errorf ( "flagAppliesToOS(%q, %q) = false but found in %s set" , f . Name , goos , goos )
}
} )
}
}