| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- package ipn
- import (
- "encoding/json"
- "errors"
- "fmt"
- "net/netip"
- "os"
- "reflect"
- "strings"
- "testing"
- "time"
- "go4.org/mem"
- "tailscale.com/ipn/ipnstate"
- "tailscale.com/net/netaddr"
- "tailscale.com/tailcfg"
- "tailscale.com/tstest"
- "tailscale.com/types/key"
- "tailscale.com/types/opt"
- "tailscale.com/types/persist"
- "tailscale.com/types/preftype"
- "tailscale.com/util/syspolicy/policyclient"
- )
- func fieldsOf(t reflect.Type) (fields []string) {
- for i := range t.NumField() {
- fields = append(fields, t.Field(i).Name)
- }
- return
- }
- func TestPrefsEqual(t *testing.T) {
- tstest.PanicOnLog()
- prefsHandles := []string{
- "ControlURL",
- "RouteAll",
- "ExitNodeID",
- "ExitNodeIP",
- "AutoExitNode",
- "InternalExitNodePrior",
- "ExitNodeAllowLANAccess",
- "CorpDNS",
- "RunSSH",
- "RunWebClient",
- "WantRunning",
- "LoggedOut",
- "ShieldsUp",
- "AdvertiseTags",
- "Hostname",
- "NotepadURLs",
- "ForceDaemon",
- "Egg",
- "AdvertiseRoutes",
- "AdvertiseServices",
- "Sync",
- "NoSNAT",
- "NoStatefulFiltering",
- "NetfilterMode",
- "OperatorUser",
- "ProfileName",
- "AutoUpdate",
- "AppConnector",
- "PostureChecking",
- "NetfilterKind",
- "DriveShares",
- "RelayServerPort",
- "RelayServerStaticEndpoints",
- "AllowSingleHosts",
- "Persist",
- }
- if have := fieldsOf(reflect.TypeFor[Prefs]()); !reflect.DeepEqual(have, prefsHandles) {
- t.Errorf("Prefs.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
- have, prefsHandles)
- }
- relayServerPort := func(port uint16) *uint16 {
- return &port
- }
- nets := func(strs ...string) (ns []netip.Prefix) {
- for _, s := range strs {
- n, err := netip.ParsePrefix(s)
- if err != nil {
- panic(err)
- }
- ns = append(ns, n)
- }
- return ns
- }
- aps := func(strs ...string) (ret []netip.AddrPort) {
- for _, s := range strs {
- n, err := netip.ParseAddrPort(s)
- if err != nil {
- panic(err)
- }
- ret = append(ret, n)
- }
- return ret
- }
- tests := []struct {
- a, b *Prefs
- want bool
- }{
- {
- &Prefs{},
- nil,
- false,
- },
- {
- nil,
- &Prefs{},
- false,
- },
- {
- &Prefs{},
- &Prefs{},
- true,
- },
- {
- &Prefs{ControlURL: "https://controlplane.tailscale.com"},
- &Prefs{ControlURL: "https://login.private.co"},
- false,
- },
- {
- &Prefs{ControlURL: "https://controlplane.tailscale.com"},
- &Prefs{ControlURL: "https://controlplane.tailscale.com"},
- true,
- },
- {
- &Prefs{RouteAll: true},
- &Prefs{RouteAll: false},
- false,
- },
- {
- &Prefs{RouteAll: true},
- &Prefs{RouteAll: true},
- true,
- },
- {
- &Prefs{ExitNodeID: "n1234"},
- &Prefs{},
- false,
- },
- {
- &Prefs{ExitNodeID: "n1234"},
- &Prefs{ExitNodeID: "n1234"},
- true,
- },
- {
- &Prefs{ExitNodeIP: netip.MustParseAddr("1.2.3.4")},
- &Prefs{},
- false,
- },
- {
- &Prefs{ExitNodeIP: netip.MustParseAddr("1.2.3.4")},
- &Prefs{ExitNodeIP: netip.MustParseAddr("1.2.3.4")},
- true,
- },
- {
- &Prefs{AutoExitNode: ""},
- &Prefs{AutoExitNode: "auto:any"},
- false,
- },
- {
- &Prefs{AutoExitNode: "auto:any"},
- &Prefs{AutoExitNode: "auto:any"},
- true,
- },
- {
- &Prefs{},
- &Prefs{ExitNodeAllowLANAccess: true},
- false,
- },
- {
- &Prefs{ExitNodeAllowLANAccess: true},
- &Prefs{ExitNodeAllowLANAccess: true},
- true,
- },
- {
- &Prefs{CorpDNS: true},
- &Prefs{CorpDNS: false},
- false,
- },
- {
- &Prefs{CorpDNS: true},
- &Prefs{CorpDNS: true},
- true,
- },
- {
- &Prefs{WantRunning: true},
- &Prefs{WantRunning: false},
- false,
- },
- {
- &Prefs{WantRunning: true},
- &Prefs{WantRunning: true},
- true,
- },
- {
- &Prefs{NoSNAT: true},
- &Prefs{NoSNAT: false},
- false,
- },
- {
- &Prefs{NoSNAT: true},
- &Prefs{NoSNAT: true},
- true,
- },
- {
- &Prefs{Hostname: "android-host01"},
- &Prefs{Hostname: "android-host02"},
- false,
- },
- {
- &Prefs{Hostname: ""},
- &Prefs{Hostname: ""},
- true,
- },
- {
- &Prefs{NotepadURLs: true},
- &Prefs{NotepadURLs: false},
- false,
- },
- {
- &Prefs{NotepadURLs: true},
- &Prefs{NotepadURLs: true},
- true,
- },
- {
- &Prefs{ShieldsUp: true},
- &Prefs{ShieldsUp: false},
- false,
- },
- {
- &Prefs{ShieldsUp: true},
- &Prefs{ShieldsUp: true},
- true,
- },
- {
- &Prefs{AdvertiseRoutes: nil},
- &Prefs{AdvertiseRoutes: []netip.Prefix{}},
- true,
- },
- {
- &Prefs{AdvertiseRoutes: []netip.Prefix{}},
- &Prefs{AdvertiseRoutes: []netip.Prefix{}},
- true,
- },
- {
- &Prefs{AdvertiseRoutes: nets("192.168.0.0/24", "10.1.0.0/16")},
- &Prefs{AdvertiseRoutes: nets("192.168.1.0/24", "10.2.0.0/16")},
- false,
- },
- {
- &Prefs{AdvertiseRoutes: nets("192.168.0.0/24", "10.1.0.0/16")},
- &Prefs{AdvertiseRoutes: nets("192.168.0.0/24", "10.2.0.0/16")},
- false,
- },
- {
- &Prefs{AdvertiseRoutes: nets("192.168.0.0/24", "10.1.0.0/16")},
- &Prefs{AdvertiseRoutes: nets("192.168.0.0/24", "10.1.0.0/16")},
- true,
- },
- {
- &Prefs{NetfilterMode: preftype.NetfilterOff},
- &Prefs{NetfilterMode: preftype.NetfilterOn},
- false,
- },
- {
- &Prefs{NetfilterMode: preftype.NetfilterOn},
- &Prefs{NetfilterMode: preftype.NetfilterOn},
- true,
- },
- {
- &Prefs{Persist: &persist.Persist{}},
- &Prefs{Persist: &persist.Persist{
- UserProfile: tailcfg.UserProfile{LoginName: "dave"},
- }},
- false,
- },
- {
- &Prefs{Persist: &persist.Persist{
- UserProfile: tailcfg.UserProfile{LoginName: "dave"},
- }},
- &Prefs{Persist: &persist.Persist{
- UserProfile: tailcfg.UserProfile{LoginName: "dave"},
- }},
- true,
- },
- {
- &Prefs{ProfileName: "work"},
- &Prefs{ProfileName: "work"},
- true,
- },
- {
- &Prefs{ProfileName: "work"},
- &Prefs{ProfileName: "home"},
- false,
- },
- {
- &Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: opt.NewBool(false)}},
- &Prefs{AutoUpdate: AutoUpdatePrefs{Check: false, Apply: opt.NewBool(false)}},
- false,
- },
- {
- &Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: opt.NewBool(true)}},
- &Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: opt.NewBool(false)}},
- false,
- },
- {
- &Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: opt.NewBool(false)}},
- &Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: opt.NewBool(false)}},
- true,
- },
- {
- &Prefs{AppConnector: AppConnectorPrefs{Advertise: true}},
- &Prefs{AppConnector: AppConnectorPrefs{Advertise: true}},
- true,
- },
- {
- &Prefs{AppConnector: AppConnectorPrefs{Advertise: true}},
- &Prefs{AppConnector: AppConnectorPrefs{Advertise: false}},
- false,
- },
- {
- &Prefs{PostureChecking: true},
- &Prefs{PostureChecking: true},
- true,
- },
- {
- &Prefs{PostureChecking: true},
- &Prefs{PostureChecking: false},
- false,
- },
- {
- &Prefs{NetfilterKind: "iptables"},
- &Prefs{NetfilterKind: "iptables"},
- true,
- },
- {
- &Prefs{NetfilterKind: "nftables"},
- &Prefs{NetfilterKind: ""},
- false,
- },
- {
- &Prefs{AdvertiseServices: []string{"svc:tux", "svc:xenia"}},
- &Prefs{AdvertiseServices: []string{"svc:tux", "svc:xenia"}},
- true,
- },
- {
- &Prefs{AdvertiseServices: []string{"svc:tux", "svc:xenia"}},
- &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,
- },
- {
- &Prefs{RelayServerStaticEndpoints: aps("[2001:db8::1]:40000", "192.0.2.1:40000")},
- &Prefs{RelayServerStaticEndpoints: aps("[2001:db8::1]:40000", "192.0.2.1:40000")},
- true,
- },
- {
- &Prefs{RelayServerStaticEndpoints: aps("[2001:db8::1]:40000", "192.0.2.2:40000")},
- &Prefs{RelayServerStaticEndpoints: aps("[2001:db8::1]:40000", "192.0.2.1:40000")},
- false,
- },
- }
- for i, tt := range tests {
- got := tt.a.Equals(tt.b)
- if got != tt.want {
- t.Errorf("%d. Equal = %v; want %v", i, got, tt.want)
- }
- }
- }
- func checkPrefs(t *testing.T, p Prefs) {
- var err error
- var p2, p2c *Prefs
- var p2b *Prefs
- pp := p.Pretty()
- if pp == "" {
- t.Fatalf("default p.Pretty() failed\n")
- }
- t.Logf("\npp: %#v\n", pp)
- b := p.ToBytes()
- if len(b) == 0 {
- t.Fatalf("default p.ToBytes() failed\n")
- }
- if !p.Equals(&p) {
- t.Fatalf("p != p\n")
- }
- p2 = p.Clone()
- p2.RouteAll = true
- if p.Equals(p2) {
- t.Fatalf("p == p2\n")
- }
- p2b = new(Prefs)
- err = PrefsFromBytes(p2.ToBytes(), p2b)
- 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)
- if p2p != p2bp {
- t.Fatalf("p2p != p2bp\n%#v\n%#v\n", p2p, p2bp)
- }
- if !p2.Equals(p2b) {
- t.Fatalf("p2 != p2b\n%#v\n%#v\n", p2, p2b)
- }
- p2c = p2.Clone()
- if !p2b.Equals(p2c) {
- t.Fatalf("p2b != p2c\n")
- }
- }
- // 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()
- p := Prefs{
- ControlURL: "https://controlplane.tailscale.com",
- }
- checkPrefs(t, p)
- }
- func TestPrefsPersist(t *testing.T) {
- tstest.PanicOnLog()
- c := persist.Persist{
- UserProfile: tailcfg.UserProfile{
- LoginName: "[email protected]",
- },
- }
- p := Prefs{
- ControlURL: "https://controlplane.tailscale.com",
- CorpDNS: true,
- Persist: &c,
- }
- checkPrefs(t, p)
- }
- func TestPrefsPretty(t *testing.T) {
- tests := []struct {
- p Prefs
- os string
- want string
- }{
- {
- Prefs{},
- "linux",
- "Prefs{ra=false dns=false want=false routes=[] nf=off update=off Persist=nil}",
- },
- {
- Prefs{},
- "windows",
- "Prefs{ra=false dns=false want=false update=off Persist=nil}",
- },
- {
- Prefs{ShieldsUp: true},
- "windows",
- "Prefs{ra=false dns=false want=false shields=true update=off Persist=nil}",
- },
- {
- Prefs{},
- "windows",
- "Prefs{ra=false dns=false want=false update=off Persist=nil}",
- },
- {
- Prefs{
- NotepadURLs: true,
- },
- "windows",
- "Prefs{ra=false dns=false want=false notepad=true update=off Persist=nil}",
- },
- {
- Prefs{
- WantRunning: true,
- ForceDaemon: true, // server mode
- },
- "windows",
- "Prefs{ra=false dns=false want=true server=true update=off Persist=nil}",
- },
- {
- Prefs{
- WantRunning: true,
- ControlURL: "http://localhost:1234",
- AdvertiseTags: []string{"tag:foo", "tag:bar"},
- },
- "darwin",
- `Prefs{ra=false dns=false want=true tags=tag:foo,tag:bar url="http://localhost:1234" update=off Persist=nil}`,
- },
- {
- Prefs{
- Persist: &persist.Persist{
- PrivateNodeKey: key.NodePrivateFromRaw32(mem.B([]byte{1: 1, 31: 0})),
- },
- },
- "linux",
- `Prefs{ra=false dns=false want=false routes=[] nf=off update=off Persist{o=, n=[B1VKl] u="" ak=-}}`,
- },
- {
- Prefs{
- ExitNodeIP: netip.MustParseAddr("1.2.3.4"),
- },
- "linux",
- `Prefs{ra=false dns=false want=false exit=1.2.3.4 lan=false routes=[] nf=off update=off Persist=nil}`,
- },
- {
- Prefs{
- ExitNodeID: tailcfg.StableNodeID("myNodeABC"),
- },
- "linux",
- `Prefs{ra=false dns=false want=false exit=myNodeABC lan=false routes=[] nf=off update=off Persist=nil}`,
- },
- {
- Prefs{
- ExitNodeID: tailcfg.StableNodeID("myNodeABC"),
- ExitNodeAllowLANAccess: true,
- },
- "linux",
- `Prefs{ra=false dns=false want=false exit=myNodeABC lan=true routes=[] nf=off update=off Persist=nil}`,
- },
- {
- Prefs{
- ExitNodeAllowLANAccess: true,
- },
- "linux",
- `Prefs{ra=false dns=false want=false routes=[] nf=off update=off Persist=nil}`,
- },
- {
- Prefs{
- Hostname: "foo",
- },
- "linux",
- `Prefs{ra=false dns=false want=false routes=[] nf=off host="foo" update=off Persist=nil}`,
- },
- {
- Prefs{
- AutoUpdate: AutoUpdatePrefs{
- Check: true,
- Apply: opt.NewBool(false),
- },
- },
- "linux",
- `Prefs{ra=false dns=false want=false routes=[] nf=off update=check Persist=nil}`,
- },
- {
- Prefs{
- AutoUpdate: AutoUpdatePrefs{
- Check: true,
- Apply: opt.NewBool(true),
- },
- },
- "linux",
- `Prefs{ra=false dns=false want=false routes=[] nf=off update=on Persist=nil}`,
- },
- {
- Prefs{
- AppConnector: AppConnectorPrefs{
- Advertise: true,
- },
- },
- "linux",
- `Prefs{ra=false dns=false want=false routes=[] nf=off update=off appconnector=advertise Persist=nil}`,
- },
- {
- Prefs{
- AppConnector: AppConnectorPrefs{
- Advertise: false,
- },
- },
- "linux",
- `Prefs{ra=false dns=false want=false routes=[] nf=off update=off Persist=nil}`,
- },
- {
- Prefs{
- NetfilterKind: "iptables",
- },
- "linux",
- `Prefs{ra=false dns=false want=false routes=[] nf=off netfilterKind=iptables update=off Persist=nil}`,
- },
- {
- Prefs{
- NetfilterKind: "",
- },
- "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)
- if got != tt.want {
- t.Errorf("%d. wrong String:\n got: %s\nwant: %s\n", i, got, tt.want)
- }
- }
- }
- func TestLoadPrefsNotExist(t *testing.T) {
- bogusFile := fmt.Sprintf("/tmp/not-exist-%d", time.Now().UnixNano())
- p, err := LoadPrefsWindows(bogusFile)
- if errors.Is(err, os.ErrNotExist) {
- // expected.
- return
- }
- t.Fatalf("unexpected prefs=%#v, err=%v", p, err)
- }
- // TestLoadPrefsFileWithZeroInIt verifies that LoadPrefs handles corrupted input files.
- // See issue #954 for details.
- func TestLoadPrefsFileWithZeroInIt(t *testing.T) {
- f, err := os.CreateTemp("", "TestLoadPrefsFileWithZeroInIt")
- if err != nil {
- t.Fatal(err)
- }
- path := f.Name()
- if _, err := f.Write(jsonEscapedZero); err != nil {
- t.Fatal(err)
- }
- f.Close()
- defer os.Remove(path)
- p, err := LoadPrefsWindows(path)
- if errors.Is(err, os.ErrNotExist) {
- // expected.
- return
- }
- t.Fatalf("unexpected prefs=%#v, err=%v", p, err)
- }
- func TestMaskedPrefsSetsInternal(t *testing.T) {
- for _, f := range fieldsOf(reflect.TypeFor[MaskedPrefs]()) {
- if !strings.HasSuffix(f, "Set") || !strings.HasPrefix(f, "Internal") {
- continue
- }
- mp := new(MaskedPrefs)
- reflect.ValueOf(mp).Elem().FieldByName(f).SetBool(true)
- if !mp.SetsInternal() {
- t.Errorf("MaskedPrefs.%sSet=true but SetsInternal=false", f)
- }
- }
- }
- func TestMaskedPrefsFields(t *testing.T) {
- have := map[string]bool{}
- for _, f := range fieldsOf(reflect.TypeFor[Prefs]()) {
- switch f {
- case "Persist", "AllowSingleHosts":
- // These can't be edited.
- continue
- }
- have[f] = true
- }
- for _, f := range fieldsOf(reflect.TypeFor[MaskedPrefs]()) {
- if f == "Prefs" {
- continue
- }
- if !strings.HasSuffix(f, "Set") {
- t.Errorf("unexpected non-/Set$/ field %q", f)
- continue
- }
- bare := strings.TrimSuffix(f, "Set")
- _, ok := have[bare]
- if !ok {
- t.Errorf("no corresponding Prefs.%s field for MaskedPrefs.%s", bare, f)
- continue
- }
- delete(have, bare)
- }
- for f := range have {
- t.Errorf("missing MaskedPrefs.%sSet for Prefs.%s", f, f)
- }
- // And also make sure they line up in the right order, which
- // ApplyEdits assumes.
- pt := reflect.TypeFor[Prefs]()
- mt := reflect.TypeFor[MaskedPrefs]()
- for i := range mt.NumField() {
- name := mt.Field(i).Name
- if i == 0 {
- if name != "Prefs" {
- t.Errorf("first field of MaskedPrefs should be Prefs")
- }
- continue
- }
- prefName := pt.Field(i - 1).Name
- if prefName+"Set" != name {
- t.Errorf("MaskedField[%d] = %s; want %sSet", i-1, name, prefName)
- }
- }
- }
- func TestPrefsApplyEdits(t *testing.T) {
- tests := []struct {
- name string
- prefs *Prefs
- edit *MaskedPrefs
- want *Prefs
- }{
- {
- name: "no_change",
- prefs: &Prefs{
- Hostname: "foo",
- },
- edit: &MaskedPrefs{},
- want: &Prefs{
- Hostname: "foo",
- },
- },
- {
- name: "set1_decoy1",
- prefs: &Prefs{
- Hostname: "foo",
- },
- edit: &MaskedPrefs{
- Prefs: Prefs{
- Hostname: "bar",
- OperatorUser: "ignore-this", // not set
- },
- HostnameSet: true,
- },
- want: &Prefs{
- Hostname: "bar",
- },
- },
- {
- name: "set_several",
- prefs: &Prefs{},
- edit: &MaskedPrefs{
- Prefs: Prefs{
- Hostname: "bar",
- OperatorUser: "galaxybrain",
- },
- HostnameSet: true,
- OperatorUserSet: true,
- },
- want: &Prefs{
- Hostname: "bar",
- OperatorUser: "galaxybrain",
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := tt.prefs.Clone()
- got.ApplyEdits(tt.edit)
- if !got.Equals(tt.want) {
- gotj, _ := json.Marshal(got)
- wantj, _ := json.Marshal(tt.want)
- t.Errorf("fail.\n got: %s\nwant: %s\n", gotj, wantj)
- }
- })
- }
- }
- func TestMaskedPrefsPretty(t *testing.T) {
- tests := []struct {
- m *MaskedPrefs
- want string
- }{
- {
- m: &MaskedPrefs{},
- want: "MaskedPrefs{}",
- },
- {
- m: &MaskedPrefs{
- Prefs: Prefs{
- Hostname: "bar",
- OperatorUser: "galaxybrain",
- RouteAll: false,
- ExitNodeID: "foo",
- AdvertiseTags: []string{"tag:foo", "tag:bar"},
- NetfilterMode: preftype.NetfilterNoDivert,
- },
- RouteAllSet: true,
- HostnameSet: true,
- OperatorUserSet: true,
- ExitNodeIDSet: true,
- AdvertiseTagsSet: true,
- NetfilterModeSet: true,
- },
- want: `MaskedPrefs{RouteAll=false ExitNodeID="foo" AdvertiseTags=["tag:foo" "tag:bar"] Hostname="bar" NetfilterMode=nodivert OperatorUser="galaxybrain"}`,
- },
- {
- m: &MaskedPrefs{
- Prefs: Prefs{
- ExitNodeIP: netaddr.IPv4(100, 102, 104, 105),
- },
- ExitNodeIPSet: true,
- },
- want: `MaskedPrefs{ExitNodeIP=100.102.104.105}`,
- },
- {
- m: &MaskedPrefs{
- Prefs: Prefs{
- AutoUpdate: AutoUpdatePrefs{Check: true, Apply: opt.NewBool(false)},
- },
- AutoUpdateSet: AutoUpdatePrefsMask{CheckSet: true, ApplySet: false},
- },
- want: `MaskedPrefs{AutoUpdate={Check=true}}`,
- },
- {
- m: &MaskedPrefs{
- Prefs: Prefs{
- AutoUpdate: AutoUpdatePrefs{Check: true, Apply: opt.NewBool(true)},
- },
- AutoUpdateSet: AutoUpdatePrefsMask{CheckSet: true, ApplySet: true},
- },
- want: `MaskedPrefs{AutoUpdate={Check=true Apply=true}}`,
- },
- {
- m: &MaskedPrefs{
- Prefs: Prefs{
- AutoUpdate: AutoUpdatePrefs{Check: true, Apply: opt.NewBool(false)},
- },
- AutoUpdateSet: AutoUpdatePrefsMask{CheckSet: false, ApplySet: true},
- },
- want: `MaskedPrefs{AutoUpdate={Apply=false}}`,
- },
- {
- m: &MaskedPrefs{
- Prefs: Prefs{
- AutoUpdate: AutoUpdatePrefs{Check: true, Apply: opt.NewBool(true)},
- },
- AutoUpdateSet: AutoUpdatePrefsMask{CheckSet: false, ApplySet: false},
- },
- want: `MaskedPrefs{}`,
- },
- }
- for i, tt := range tests {
- got := tt.m.Pretty()
- if got != tt.want {
- t.Errorf("%d.\n got: %#q\nwant: %#q\n", i, got, tt.want)
- }
- }
- }
- func TestPrefsExitNode(t *testing.T) {
- var p *Prefs
- if p.AdvertisesExitNode() {
- t.Errorf("nil shouldn't advertise exit node")
- }
- p = NewPrefs()
- if p.AdvertisesExitNode() {
- t.Errorf("default shouldn't advertise exit node")
- }
- p.AdvertiseRoutes = []netip.Prefix{
- netip.MustParsePrefix("10.0.0.0/16"),
- }
- p.SetAdvertiseExitNode(true)
- if got, want := len(p.AdvertiseRoutes), 3; got != want {
- t.Errorf("routes = %d; want %d", got, want)
- }
- p.SetAdvertiseExitNode(true)
- if got, want := len(p.AdvertiseRoutes), 3; got != want {
- t.Errorf("routes = %d; want %d", got, want)
- }
- if !p.AdvertisesExitNode() {
- t.Errorf("not advertising after enable")
- }
- p.SetAdvertiseExitNode(false)
- if p.AdvertisesExitNode() {
- t.Errorf("advertising after disable")
- }
- if got, want := len(p.AdvertiseRoutes), 1; got != want {
- t.Errorf("routes = %d; want %d", got, want)
- }
- }
- func TestExitNodeIPOfArg(t *testing.T) {
- mustIP := netip.MustParseAddr
- tests := []struct {
- name string
- arg string
- st *ipnstate.Status
- want netip.Addr
- wantErr string
- }{
- {
- name: "ip_while_stopped_okay",
- arg: "1.2.3.4",
- st: &ipnstate.Status{
- BackendState: "Stopped",
- },
- want: mustIP("1.2.3.4"),
- },
- {
- name: "ip_not_found",
- arg: "1.2.3.4",
- st: &ipnstate.Status{
- BackendState: "Running",
- },
- wantErr: `no node found in netmap with IP 1.2.3.4`,
- },
- {
- name: "ip_is_self",
- arg: "1.2.3.4",
- st: &ipnstate.Status{
- TailscaleIPs: []netip.Addr{mustIP("1.2.3.4")},
- },
- wantErr: "cannot use 1.2.3.4 as an exit node as it is a local IP address to this machine",
- },
- {
- name: "ip_is_self_when_backend_running",
- arg: "1.2.3.4",
- st: &ipnstate.Status{
- BackendState: "Running",
- TailscaleIPs: []netip.Addr{mustIP("1.2.3.4")},
- },
- wantErr: "cannot use 1.2.3.4 as an exit node as it is a local IP address to this machine",
- },
- {
- name: "ip_not_exit",
- arg: "1.2.3.4",
- st: &ipnstate.Status{
- BackendState: "Running",
- Peer: map[key.NodePublic]*ipnstate.PeerStatus{
- key.NewNode().Public(): {
- TailscaleIPs: []netip.Addr{mustIP("1.2.3.4")},
- },
- },
- },
- wantErr: `node 1.2.3.4 is not advertising an exit node`,
- },
- {
- name: "ip",
- arg: "1.2.3.4",
- st: &ipnstate.Status{
- BackendState: "Running",
- Peer: map[key.NodePublic]*ipnstate.PeerStatus{
- key.NewNode().Public(): {
- TailscaleIPs: []netip.Addr{mustIP("1.2.3.4")},
- ExitNodeOption: true,
- },
- },
- },
- want: mustIP("1.2.3.4"),
- },
- {
- name: "no_match",
- arg: "unknown",
- st: &ipnstate.Status{MagicDNSSuffix: ".foo"},
- wantErr: `invalid value "unknown" for --exit-node; must be IP or unique node name`,
- },
- {
- name: "name",
- arg: "skippy",
- st: &ipnstate.Status{
- MagicDNSSuffix: ".foo",
- Peer: map[key.NodePublic]*ipnstate.PeerStatus{
- key.NewNode().Public(): {
- DNSName: "skippy.foo.",
- TailscaleIPs: []netip.Addr{mustIP("1.0.0.2")},
- ExitNodeOption: true,
- },
- },
- },
- want: mustIP("1.0.0.2"),
- },
- {
- name: "name_fqdn",
- arg: "skippy.foo.",
- st: &ipnstate.Status{
- MagicDNSSuffix: ".foo",
- Peer: map[key.NodePublic]*ipnstate.PeerStatus{
- key.NewNode().Public(): {
- DNSName: "skippy.foo.",
- TailscaleIPs: []netip.Addr{mustIP("1.0.0.2")},
- ExitNodeOption: true,
- },
- },
- },
- want: mustIP("1.0.0.2"),
- },
- {
- name: "name_not_exit",
- arg: "skippy",
- st: &ipnstate.Status{
- MagicDNSSuffix: ".foo",
- Peer: map[key.NodePublic]*ipnstate.PeerStatus{
- key.NewNode().Public(): {
- DNSName: "skippy.foo.",
- TailscaleIPs: []netip.Addr{mustIP("1.0.0.2")},
- },
- },
- },
- wantErr: `node "skippy" is not advertising an exit node`,
- },
- {
- name: "name_wrong_fqdn",
- arg: "skippy.bar.",
- st: &ipnstate.Status{
- MagicDNSSuffix: ".foo",
- Peer: map[key.NodePublic]*ipnstate.PeerStatus{
- key.NewNode().Public(): {
- DNSName: "skippy.foo.",
- TailscaleIPs: []netip.Addr{mustIP("1.0.0.2")},
- },
- },
- },
- wantErr: `invalid value "skippy.bar." for --exit-node; must be IP or unique node name`,
- },
- {
- name: "ambiguous",
- arg: "skippy",
- st: &ipnstate.Status{
- MagicDNSSuffix: ".foo",
- Peer: map[key.NodePublic]*ipnstate.PeerStatus{
- key.NewNode().Public(): {
- DNSName: "skippy.foo.",
- TailscaleIPs: []netip.Addr{mustIP("1.0.0.2")},
- ExitNodeOption: true,
- },
- key.NewNode().Public(): {
- DNSName: "SKIPPY.foo.",
- TailscaleIPs: []netip.Addr{mustIP("1.0.0.2")},
- ExitNodeOption: true,
- },
- },
- },
- wantErr: `ambiguous exit node name "skippy"`,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := exitNodeIPOfArg(tt.arg, tt.st)
- if err != nil {
- if err.Error() == tt.wantErr {
- return
- }
- if tt.wantErr == "" {
- t.Fatal(err)
- }
- t.Fatalf("error = %#q; want %#q", err, tt.wantErr)
- }
- if tt.wantErr != "" {
- t.Fatalf("got %v; want error %#q", got, tt.wantErr)
- }
- if got != tt.want {
- t.Fatalf("got %v; want %v", got, tt.want)
- }
- })
- }
- }
- func TestControlURLOrDefault(t *testing.T) {
- var p Prefs
- polc := policyclient.NoPolicyClient{}
- if got, want := p.ControlURLOrDefault(polc), DefaultControlURL; got != want {
- t.Errorf("got %q; want %q", got, want)
- }
- p.ControlURL = "http://foo.bar"
- if got, want := p.ControlURLOrDefault(polc), "http://foo.bar"; got != want {
- t.Errorf("got %q; want %q", got, want)
- }
- p.ControlURL = "https://login.tailscale.com"
- if got, want := p.ControlURLOrDefault(polc), DefaultControlURL; got != want {
- t.Errorf("got %q; want %q", got, want)
- }
- }
- func TestMaskedPrefsIsEmpty(t *testing.T) {
- tests := []struct {
- name string
- mp *MaskedPrefs
- wantEmpty bool
- }{
- {
- name: "nil",
- wantEmpty: true,
- },
- {
- name: "empty",
- wantEmpty: true,
- mp: &MaskedPrefs{},
- },
- {
- name: "no-masks",
- wantEmpty: true,
- mp: &MaskedPrefs{
- Prefs: Prefs{
- WantRunning: true,
- },
- },
- },
- {
- name: "with-mask",
- wantEmpty: false,
- mp: &MaskedPrefs{
- Prefs: Prefs{
- WantRunning: true,
- },
- WantRunningSet: true,
- },
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- got := tc.mp.IsEmpty()
- if got != tc.wantEmpty {
- t.Fatalf("mp.IsEmpty = %t; want %t", got, tc.wantEmpty)
- }
- })
- }
- }
- func TestNotifyPrefsJSONRoundtrip(t *testing.T) {
- var n Notify
- if n.Prefs != nil && n.Prefs.Valid() {
- t.Fatal("Prefs should not be valid at start")
- }
- b, err := json.Marshal(n)
- if err != nil {
- t.Fatal(err)
- }
- var n2 Notify
- if err := json.Unmarshal(b, &n2); err != nil {
- t.Fatal(err)
- }
- if n2.Prefs != nil && n2.Prefs.Valid() {
- t.Fatal("Prefs should not be valid after deserialization")
- }
- }
- // Verify that our Prefs type writes out an AllowSingleHosts field so we can
- // downgrade to older versions that require it.
- func TestPrefsDowngrade(t *testing.T) {
- var p Prefs
- j, err := json.Marshal(p)
- if err != nil {
- t.Fatal(err)
- }
- type oldPrefs struct {
- AllowSingleHosts bool
- }
- var op oldPrefs
- if err := json.Unmarshal(j, &op); err != nil {
- t.Fatal(err)
- }
- if !op.AllowSingleHosts {
- t.Fatal("AllowSingleHosts should be true")
- }
- }
- func TestParseAutoExitNodeString(t *testing.T) {
- tests := []struct {
- name string
- exitNodeID string
- wantOk bool
- wantExpr ExitNodeExpression
- }{
- {
- name: "empty expr",
- exitNodeID: "",
- wantOk: false,
- wantExpr: "",
- },
- {
- name: "no auto prefix",
- exitNodeID: "foo",
- wantOk: false,
- wantExpr: "",
- },
- {
- name: "auto:any",
- exitNodeID: "auto:any",
- wantOk: true,
- wantExpr: AnyExitNode,
- },
- {
- name: "auto:foo",
- exitNodeID: "auto:foo",
- wantOk: true,
- wantExpr: "foo",
- },
- {
- name: "auto prefix but empty suffix",
- exitNodeID: "auto:",
- wantOk: false,
- wantExpr: "",
- },
- {
- name: "auto prefix no colon",
- exitNodeID: "auto",
- wantOk: false,
- wantExpr: "",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- gotExpr, gotOk := ParseAutoExitNodeString(tt.exitNodeID)
- if gotOk != tt.wantOk || gotExpr != tt.wantExpr {
- if tt.wantOk {
- t.Fatalf("got %v (%q); want %v (%q)", gotOk, gotExpr, tt.wantOk, tt.wantExpr)
- } else {
- t.Fatalf("got %v (%q); want false", gotOk, gotExpr)
- }
- }
- })
- }
- }
|