Skip to content

Commit

Permalink
cmd/tailscale,ipn: add auto-update flag and pref
Browse files Browse the repository at this point in the history
The flag is hidden and a noop for now. Adding propagation to tailscaled
and persistence only. The prefs field is wrapped in a struct to allow
for future expansion (like update schedule).

Updates #6907

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
  • Loading branch information
awly committed Aug 30, 2023
1 parent 37eab31 commit cb729de
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 16 deletions.
24 changes: 24 additions & 0 deletions cmd/tailscale/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,10 @@ func TestPrefsFromUpArgs(t *testing.T) {
NetfilterMode: preftype.NetfilterOn,
CorpDNS: true,
AllowSingleHosts: true,
AutoUpdate: ipn.AutoUpdatePrefs{
Check: true,
Apply: false,
},
},
},
{
Expand All @@ -569,6 +573,10 @@ func TestPrefsFromUpArgs(t *testing.T) {
AllowSingleHosts: true,
RouteAll: true,
NetfilterMode: preftype.NetfilterOn,
AutoUpdate: ipn.AutoUpdatePrefs{
Check: true,
Apply: false,
},
},
},
{
Expand All @@ -584,6 +592,10 @@ func TestPrefsFromUpArgs(t *testing.T) {
netip.MustParsePrefix("::/0"),
},
NetfilterMode: preftype.NetfilterOn,
AutoUpdate: ipn.AutoUpdatePrefs{
Check: true,
Apply: false,
},
},
},
{
Expand Down Expand Up @@ -670,6 +682,10 @@ func TestPrefsFromUpArgs(t *testing.T) {
WantRunning: true,
NetfilterMode: preftype.NetfilterNoDivert,
NoSNAT: true,
AutoUpdate: ipn.AutoUpdatePrefs{
Check: true,
Apply: false,
},
},
},
{
Expand All @@ -683,6 +699,10 @@ func TestPrefsFromUpArgs(t *testing.T) {
WantRunning: true,
NetfilterMode: preftype.NetfilterOff,
NoSNAT: true,
AutoUpdate: ipn.AutoUpdatePrefs{
Check: true,
Apply: false,
},
},
},
{
Expand All @@ -698,6 +718,10 @@ func TestPrefsFromUpArgs(t *testing.T) {
AdvertiseRoutes: []netip.Prefix{
netip.MustParsePrefix("fd7a:115c:a1e0:b1a::bb:10.0.0.0/112"),
},
AutoUpdate: ipn.AutoUpdatePrefs{
Check: true,
Apply: false,
},
},
},
{
Expand Down
8 changes: 8 additions & 0 deletions cmd/tailscale/cli/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ type setArgsT struct {
acceptedRisks string
profileName string
forceDaemon bool
updateCheck bool
updateApply bool
}

func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
Expand All @@ -61,6 +63,8 @@ func newSetFlagSet(goos string, setArgs *setArgsT) *flag.FlagSet {
setf.StringVar(&setArgs.hostname, "hostname", "", "hostname to use instead of the one provided by the OS")
setf.StringVar(&setArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. \"10.0.0.0/8,192.168.0.0/24\") or empty string to not advertise routes")
setf.BoolVar(&setArgs.advertiseDefaultRoute, "advertise-exit-node", false, "offer to be an exit node for internet traffic for the tailnet")
setf.BoolVar(&setArgs.updateCheck, "update-check", true, "HIDDEN: automatically check for available Tailscale updates")
setf.BoolVar(&setArgs.updateApply, "update-apply", false, "HIDDEN: automatically update to the latest available version as soon as it's available")
if safesocket.GOOSUsesPeerCreds(goos) {
setf.StringVar(&setArgs.opUser, "operator", "", "Unix username to allow to operate on tailscaled without sudo")
}
Expand Down Expand Up @@ -99,6 +103,10 @@ func runSet(ctx context.Context, args []string) (retErr error) {
Hostname: setArgs.hostname,
OperatorUser: setArgs.opUser,
ForceDaemon: setArgs.forceDaemon,
AutoUpdate: ipn.AutoUpdatePrefs{
Check: setArgs.updateCheck,
Apply: setArgs.updateApply,
},
},
}

Expand Down
3 changes: 3 additions & 0 deletions cmd/tailscale/cli/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ func newUpFlagSet(goos string, upArgs *upArgsT, cmd string) *flag.FlagSet {
}
upf := newFlagSet(cmd)

// When adding new flags, prefer to put them under "tailscale set" instead
// of here. Setting preferences via "tailscale up" is deprecated.
upf.BoolVar(&upArgs.qr, "qr", false, "show QR code for login URLs")
upf.StringVar(&upArgs.authKeyOrFile, "auth-key", "", `node authorization key; if it begins with "file:", then it's a path to a file containing the authkey`)

Expand Down Expand Up @@ -712,6 +714,7 @@ func init() {
addPrefFlagMapping("operator", "OperatorUser")
addPrefFlagMapping("ssh", "RunSSH")
addPrefFlagMapping("nickname", "ProfileName")
addPrefFlagMapping("auto-update", "AutoUpdate")
}

func addPrefFlagMapping(flagName string, prefNames ...string) {
Expand Down
1 change: 1 addition & 0 deletions ipn/ipn_clone.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions ipn/ipn_view.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion ipn/ipnlocal/c2n.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ func (b *LocalBackend) handleC2NUpdate(w http.ResponseWriter, r *http.Request) {
// updates, or if one happened in the past 5 minutes, or something.

var res tailcfg.C2NUpdateResponse
res.Enabled = envknob.AllowsRemoteUpdate()
prefs := b.Prefs().AutoUpdate()
res.Enabled = envknob.AllowsRemoteUpdate() || prefs.Apply || prefs.Check
res.Supported = runtime.GOOS == "windows" || (runtime.GOOS == "linux" && distro.Get() == distro.Debian)

switch r.Method {
Expand Down Expand Up @@ -144,6 +145,11 @@ func (b *LocalBackend) handleC2NUpdate(w http.ResponseWriter, r *http.Request) {
res.Err = "not supported"
return
}
if prefs.Check && !prefs.Apply {
// Client opted in to update notifications but not automatic updates.
return
}

cmdTS, err := findCmdTailscale()
if err != nil {
res.Err = fmt.Sprintf("failed to find cmd/tailscale binary: %v", err)
Expand Down
40 changes: 39 additions & 1 deletion ipn/prefs.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ type Prefs struct {
// and CLI.
ProfileName string `json:",omitempty"`

// AutoUpdate sets the auto-update preferences for the node agent. See
// AutoUpdatePrefs docs for more details.
AutoUpdate AutoUpdatePrefs

// The Persist field is named 'Config' in the file for backward
// compatibility with earlier versions.
// TODO(apenwarr): We should move this out of here, it's not a pref.
Expand All @@ -204,6 +208,18 @@ type Prefs struct {
Persist *persist.Persist `json:"Config"`
}

// AutoUpdatePrefs are the auto update settings for the node agent.
type AutoUpdatePrefs struct {
// Check specifies whether background checks for updates are enabled. When
// enabled, tailscaled will periodically check for available and notify the
// user about them.
Check bool
// Apply specifies whether background auto-updates are enabled. When
// enabled, tailscaled will apply available updates in the background as
// soon as they are available. Check must also be set when Apply is set.
Apply bool
}

// MaskedPrefs is a Prefs with an associated bitmask of which fields are set.
type MaskedPrefs struct {
Prefs
Expand All @@ -229,6 +245,7 @@ type MaskedPrefs struct {
NetfilterModeSet bool `json:",omitempty"`
OperatorUserSet bool `json:",omitempty"`
ProfileNameSet bool `json:",omitempty"`
AutoUpdateSet bool `json:",omitempty"`
}

// ApplyEdits mutates p, assigning fields from m.Prefs for each MaskedPrefs
Expand Down Expand Up @@ -284,6 +301,12 @@ func (m *MaskedPrefs) Pretty() string {
if v.Type().Elem().Kind() == reflect.String {
return "%s=%q"
}
case reflect.Struct:
return "%s=%+v"
case reflect.Pointer:
if v.Type().Elem().Kind() == reflect.Struct {
return "%s=%+v"
}
}
return "%s=%v"
}
Expand Down Expand Up @@ -360,6 +383,7 @@ func (p *Prefs) pretty(goos string) string {
if p.OperatorUser != "" {
fmt.Fprintf(&sb, "op=%q ", p.OperatorUser)
}
sb.WriteString(p.AutoUpdate.Pretty())
if p.Persist != nil {
sb.WriteString(p.Persist.Pretty())
} else {
Expand Down Expand Up @@ -414,7 +438,17 @@ func (p *Prefs) Equals(p2 *Prefs) bool {
compareIPNets(p.AdvertiseRoutes, p2.AdvertiseRoutes) &&
compareStrings(p.AdvertiseTags, p2.AdvertiseTags) &&
p.Persist.Equals(p2.Persist) &&
p.ProfileName == p2.ProfileName
p.ProfileName == p2.ProfileName &&
p.AutoUpdate.Equals(p2.AutoUpdate)
}

func (au AutoUpdatePrefs) Equals(au2 AutoUpdatePrefs) bool {
return au == au2

}

func (au AutoUpdatePrefs) Pretty() string {
return fmt.Sprintf("AutoUpdatePrefs{check=%v apply=%v} ", au.Check, au.Apply)
}

func compareIPNets(a, b []netip.Prefix) bool {
Expand Down Expand Up @@ -459,6 +493,10 @@ func NewPrefs() *Prefs {
CorpDNS: true,
WantRunning: false,
NetfilterMode: preftype.NetfilterOn,
AutoUpdate: AutoUpdatePrefs{
Check: true,
Apply: false,
},
}
}

Expand Down
54 changes: 40 additions & 14 deletions ipn/prefs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func TestPrefsEqual(t *testing.T) {
"NetfilterMode",
"OperatorUser",
"ProfileName",
"AutoUpdate",
"Persist",
}
if have := fieldsOf(reflect.TypeOf(Prefs{})); !reflect.DeepEqual(have, prefsHandles) {
Expand Down Expand Up @@ -288,6 +289,21 @@ func TestPrefsEqual(t *testing.T) {
&Prefs{ProfileName: "home"},
false,
},
{
&Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: false}},
&Prefs{AutoUpdate: AutoUpdatePrefs{Check: false, Apply: false}},
false,
},
{
&Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: true}},
&Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: false}},
false,
},
{
&Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: false}},
&Prefs{AutoUpdate: AutoUpdatePrefs{Check: true, Apply: false}},
true,
},
}
for i, tt := range tests {
got := tt.a.Equals(tt.b)
Expand Down Expand Up @@ -372,30 +388,30 @@ func TestPrefsPretty(t *testing.T) {
{
Prefs{},
"linux",
"Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off Persist=nil}",
"Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off AutoUpdatePrefs{check=false apply=false} Persist=nil}",
},
{
Prefs{},
"windows",
"Prefs{ra=false mesh=false dns=false want=false Persist=nil}",
"Prefs{ra=false mesh=false dns=false want=false AutoUpdatePrefs{check=false apply=false} Persist=nil}",
},
{
Prefs{ShieldsUp: true},
"windows",
"Prefs{ra=false mesh=false dns=false want=false shields=true Persist=nil}",
"Prefs{ra=false mesh=false dns=false want=false shields=true AutoUpdatePrefs{check=false apply=false} Persist=nil}",
},
{
Prefs{AllowSingleHosts: true},
"windows",
"Prefs{ra=false dns=false want=false Persist=nil}",
"Prefs{ra=false dns=false want=false AutoUpdatePrefs{check=false apply=false} Persist=nil}",
},
{
Prefs{
NotepadURLs: true,
AllowSingleHosts: true,
},
"windows",
"Prefs{ra=false dns=false want=false notepad=true Persist=nil}",
"Prefs{ra=false dns=false want=false notepad=true AutoUpdatePrefs{check=false apply=false} Persist=nil}",
},
{
Prefs{
Expand All @@ -404,7 +420,7 @@ func TestPrefsPretty(t *testing.T) {
ForceDaemon: true, // server mode
},
"windows",
"Prefs{ra=false dns=false want=true server=true Persist=nil}",
"Prefs{ra=false dns=false want=true server=true AutoUpdatePrefs{check=false apply=false} Persist=nil}",
},
{
Prefs{
Expand All @@ -414,14 +430,14 @@ func TestPrefsPretty(t *testing.T) {
AdvertiseTags: []string{"tag:foo", "tag:bar"},
},
"darwin",
`Prefs{ra=false dns=false want=true tags=tag:foo,tag:bar url="http://localhost:1234" Persist=nil}`,
`Prefs{ra=false dns=false want=true tags=tag:foo,tag:bar url="http://localhost:1234" AutoUpdatePrefs{check=false apply=false} Persist=nil}`,
},
{
Prefs{
Persist: &persist.Persist{},
},
"linux",
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off Persist{lm=, o=, n= u=""}}`,
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off AutoUpdatePrefs{check=false apply=false} Persist{lm=, o=, n= u=""}}`,
},
{
Prefs{
Expand All @@ -430,43 +446,53 @@ func TestPrefsPretty(t *testing.T) {
},
},
"linux",
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off Persist{lm=, o=, n=[B1VKl] u=""}}`,
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off AutoUpdatePrefs{check=false apply=false} Persist{lm=, o=, n=[B1VKl] u=""}}`,
},
{
Prefs{
ExitNodeIP: netip.MustParseAddr("1.2.3.4"),
},
"linux",
`Prefs{ra=false mesh=false dns=false want=false exit=1.2.3.4 lan=false routes=[] nf=off Persist=nil}`,
`Prefs{ra=false mesh=false dns=false want=false exit=1.2.3.4 lan=false routes=[] nf=off AutoUpdatePrefs{check=false apply=false} Persist=nil}`,
},
{
Prefs{
ExitNodeID: tailcfg.StableNodeID("myNodeABC"),
},
"linux",
`Prefs{ra=false mesh=false dns=false want=false exit=myNodeABC lan=false routes=[] nf=off Persist=nil}`,
`Prefs{ra=false mesh=false dns=false want=false exit=myNodeABC lan=false routes=[] nf=off AutoUpdatePrefs{check=false apply=false} Persist=nil}`,
},
{
Prefs{
ExitNodeID: tailcfg.StableNodeID("myNodeABC"),
ExitNodeAllowLANAccess: true,
},
"linux",
`Prefs{ra=false mesh=false dns=false want=false exit=myNodeABC lan=true routes=[] nf=off Persist=nil}`,
`Prefs{ra=false mesh=false dns=false want=false exit=myNodeABC lan=true routes=[] nf=off AutoUpdatePrefs{check=false apply=false} Persist=nil}`,
},
{
Prefs{
ExitNodeAllowLANAccess: true,
},
"linux",
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off Persist=nil}`,
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off AutoUpdatePrefs{check=false apply=false} Persist=nil}`,
},
{
Prefs{
Hostname: "foo",
},
"linux",
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off host="foo" Persist=nil}`,
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off host="foo" AutoUpdatePrefs{check=false apply=false} Persist=nil}`,
},
{
Prefs{
AutoUpdate: AutoUpdatePrefs{
Check: true,
Apply: true,
},
},
"linux",
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off AutoUpdatePrefs{check=true apply=true} Persist=nil}`,
},
}
for i, tt := range tests {
Expand Down

0 comments on commit cb729de

Please sign in to comment.