Add prefs.LoggedOut to fix several state machine bugs.

Fixes: tailscale/corp#1660

Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>
This commit is contained in:
Avery Pennarun 2021-04-30 03:56:11 -04:00
parent b0382ca167
commit 2a4d1cf9e2
5 changed files with 118 additions and 68 deletions

View File

@ -445,6 +445,10 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
// Auth completed, unblock the engine // Auth completed, unblock the engine
b.blockEngineUpdates(false) b.blockEngineUpdates(false)
b.authReconfig() b.authReconfig()
b.EditPrefs(&ipn.MaskedPrefs{
LoggedOutSet: true,
Prefs: ipn.Prefs{LoggedOut: false},
})
b.send(ipn.Notify{LoginFinished: &empty.Message{}}) b.send(ipn.Notify{LoginFinished: &empty.Message{}})
} }
@ -643,7 +647,6 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
return errors.New("no state key or prefs provided") return errors.New("no state key or prefs provided")
} }
defer b.stateMachine()
if opts.Prefs != nil { if opts.Prefs != nil {
b.logf("Start: %v", opts.Prefs.Pretty()) b.logf("Start: %v", opts.Prefs.Pretty())
} else { } else {
@ -707,6 +710,8 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
} }
} }
loggedOut := b.prefs.LoggedOut
b.inServerMode = b.prefs.ForceDaemon b.inServerMode = b.prefs.ForceDaemon
b.serverURL = b.prefs.ControlURLOrDefault() b.serverURL = b.prefs.ControlURLOrDefault()
hostinfo.RoutableIPs = append(hostinfo.RoutableIPs, b.prefs.AdvertiseRoutes...) hostinfo.RoutableIPs = append(hostinfo.RoutableIPs, b.prefs.AdvertiseRoutes...)
@ -804,8 +809,16 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
b.send(ipn.Notify{BackendLogID: &blid}) b.send(ipn.Notify{BackendLogID: &blid})
b.send(ipn.Notify{Prefs: prefs}) b.send(ipn.Notify{Prefs: prefs})
if wantRunning { if wantRunning && !loggedOut {
cc.Login(nil, controlclient.LoginDefault) cc.Login(nil, controlclient.LoginDefault)
b.mu.Lock()
b.state = ipn.Starting
b.mu.Unlock()
b.send(ipn.Notify{State: &b.state})
} else {
b.stateMachine()
} }
return nil return nil
} }
@ -2118,12 +2131,17 @@ func (b *LocalBackend) nextState() ipn.State {
netMap = b.netMap netMap = b.netMap
state = b.state state = b.state
wantRunning = b.prefs.WantRunning wantRunning = b.prefs.WantRunning
loggedOut = b.prefs.LoggedOut
hasNodeKey = b.prefs.Persist != nil &&
!b.prefs.Persist.PrivateNodeKey.IsZero()
) )
b.mu.Unlock() b.mu.Unlock()
switch { switch {
case !wantRunning && !loggedOut && hasNodeKey:
return ipn.Stopped
case netMap == nil: case netMap == nil:
if cc.AuthCantContinue() { if cc.AuthCantContinue() || loggedOut {
// Auth was interrupted or waiting for URL visit, // Auth was interrupted or waiting for URL visit,
// so it won't proceed without human help. // so it won't proceed without human help.
return ipn.NeedsLogin return ipn.NeedsLogin
@ -2238,7 +2256,8 @@ func (b *LocalBackend) logout(ctx context.Context, sync bool) error {
b.EditPrefs(&ipn.MaskedPrefs{ b.EditPrefs(&ipn.MaskedPrefs{
WantRunningSet: true, WantRunningSet: true,
Prefs: ipn.Prefs{WantRunning: false}, LoggedOutSet: true,
Prefs: ipn.Prefs{WantRunning: false, LoggedOut: true},
}) })
if cc == nil { if cc == nil {

View File

@ -321,6 +321,10 @@ func TestStateMachine(t *testing.T) {
assert.NotNil(nn[0].Prefs) assert.NotNil(nn[0].Prefs)
assert.NotNil(nn[1].State) assert.NotNil(nn[1].State)
prefs := *nn[0].Prefs prefs := *nn[0].Prefs
// Note: a totally fresh system has Prefs.LoggedOut=false by
// default. We are logged out, but not because the user asked
// for it, so it doesn't count as Prefs.LoggedOut==true.
assert.Equal(false, nn[0].Prefs.LoggedOut)
assert.Equal(false, prefs.WantRunning) assert.Equal(false, prefs.WantRunning)
assert.Equal(ipn.NeedsLogin, *nn[1].State) assert.Equal(ipn.NeedsLogin, *nn[1].State)
assert.Equal(ipn.NeedsLogin, b.State()) assert.Equal(ipn.NeedsLogin, b.State())
@ -343,6 +347,7 @@ func TestStateMachine(t *testing.T) {
assert.Equal([]string{}, cc.getCalls()) assert.Equal([]string{}, cc.getCalls())
assert.NotNil(nn[0].Prefs) assert.NotNil(nn[0].Prefs)
assert.NotNil(nn[1].State) assert.NotNil(nn[1].State)
assert.Equal(false, nn[0].Prefs.LoggedOut)
assert.Equal(false, nn[0].Prefs.WantRunning) assert.Equal(false, nn[0].Prefs.WantRunning)
assert.Equal(ipn.NeedsLogin, *nn[1].State) assert.Equal(ipn.NeedsLogin, *nn[1].State)
assert.Equal(ipn.NeedsLogin, b.State()) assert.Equal(ipn.NeedsLogin, b.State())
@ -383,6 +388,7 @@ func TestStateMachine(t *testing.T) {
// Trying to log in automatically sets WantRunning. // Trying to log in automatically sets WantRunning.
// BUG: that should have happened right after Login(). // BUG: that should have happened right after Login().
assert.NotNil(nn[0].Prefs) assert.NotNil(nn[0].Prefs)
assert.Equal(false, nn[0].Prefs.LoggedOut)
assert.Equal(true, nn[0].Prefs.WantRunning) assert.Equal(true, nn[0].Prefs.WantRunning)
} }
@ -555,16 +561,18 @@ func TestStateMachine(t *testing.T) {
// User wants to logout. // User wants to logout.
t.Logf("\n\nLogout (async)") t.Logf("\n\nLogout (async)")
notifies.expect(1) notifies.expect(2)
b.Logout() b.Logout()
{ {
nn := notifies.drain(1) nn := notifies.drain(2)
assert.Equal([]string{"StartLogout"}, cc.getCalls()) // BUG: now is not the time to unpause.
assert.NotNil(nn[0].Prefs) assert.Equal([]string{"unpause", "StartLogout"}, cc.getCalls())
// NOTE: WantRunning is false here, which is right. But assert.NotNil(nn[0].State)
// it changes to true in subsequent logout attempts, which is assert.NotNil(nn[1].Prefs)
// wrong in those attempts. assert.Equal(ipn.NeedsLogin, *nn[0].State)
assert.Equal(false, nn[0].Prefs.WantRunning) assert.Equal(true, nn[1].Prefs.LoggedOut)
assert.Equal(false, nn[1].Prefs.WantRunning)
assert.Equal(ipn.NeedsLogin, b.State())
} }
// Let's make the logout succeed. // Let's make the logout succeed.
@ -574,21 +582,27 @@ func TestStateMachine(t *testing.T) {
cc.send(nil, "", false, nil) cc.send(nil, "", false, nil)
{ {
nn := notifies.drain(1) nn := notifies.drain(1)
// BUG: now is not the time to unpause. assert.Equal([]string{}, cc.getCalls())
assert.Equal([]string{"unpause"}, cc.getCalls()) assert.NotNil(nn[0].Prefs)
assert.NotNil(nn[0].State) assert.Equal(true, nn[0].Prefs.LoggedOut)
assert.Equal(ipn.NeedsLogin, *nn[0].State) // BUG: WantRunning should be false after manual logout.
assert.Equal(true, nn[0].Prefs.WantRunning)
assert.Equal(ipn.NeedsLogin, b.State())
} }
// A second logout should do nothing, since the prefs haven't changed. // A second logout should do nothing, since the prefs haven't changed.
t.Logf("\n\nLogout2 (async)") t.Logf("\n\nLogout2 (async)")
notifies.expect(0) notifies.expect(1)
b.Logout() b.Logout()
{ {
notifies.drain(0) nn := notifies.drain(1)
// BUG: the backend has already called StartLogout, and we're // BUG: the backend has already called StartLogout, and we're
// still logged out. So it shouldn't call it again. // still logged out. So it shouldn't call it again.
assert.Equal([]string{"StartLogout"}, cc.getCalls()) assert.Equal([]string{"StartLogout"}, cc.getCalls())
// BUG: Prefs should not change here. Already logged out.
assert.NotNil(nn[0].Prefs)
assert.Equal(true, nn[0].Prefs.LoggedOut)
assert.Equal(false, nn[0].Prefs.WantRunning)
assert.Equal(ipn.NeedsLogin, b.State()) assert.Equal(ipn.NeedsLogin, b.State())
} }
@ -601,6 +615,7 @@ func TestStateMachine(t *testing.T) {
nn := notifies.drain(1) nn := notifies.drain(1)
assert.Equal([]string{}, cc.getCalls()) assert.Equal([]string{}, cc.getCalls())
assert.NotNil(nn[0].Prefs) assert.NotNil(nn[0].Prefs)
assert.Equal(true, nn[0].Prefs.LoggedOut)
// BUG: second logout shouldn't cause WantRunning->true !! // BUG: second logout shouldn't cause WantRunning->true !!
assert.Equal(true, nn[0].Prefs.WantRunning) assert.Equal(true, nn[0].Prefs.WantRunning)
assert.Equal(ipn.NeedsLogin, b.State()) assert.Equal(ipn.NeedsLogin, b.State())
@ -616,6 +631,7 @@ func TestStateMachine(t *testing.T) {
nn := notifies.drain(1) nn := notifies.drain(1)
assert.Equal([]string{"Logout"}, cc.getCalls()) assert.Equal([]string{"Logout"}, cc.getCalls())
assert.NotNil(nn[0].Prefs) assert.NotNil(nn[0].Prefs)
assert.Equal(true, nn[0].Prefs.LoggedOut)
assert.Equal(false, nn[0].Prefs.WantRunning) assert.Equal(false, nn[0].Prefs.WantRunning)
assert.Equal(ipn.NeedsLogin, b.State()) assert.Equal(ipn.NeedsLogin, b.State())
} }
@ -629,6 +645,7 @@ func TestStateMachine(t *testing.T) {
nn := notifies.drain(1) nn := notifies.drain(1)
assert.Equal([]string{}, cc.getCalls()) assert.Equal([]string{}, cc.getCalls())
assert.NotNil(nn[0].Prefs) assert.NotNil(nn[0].Prefs)
assert.Equal(true, nn[0].Prefs.LoggedOut)
// BUG: third logout shouldn't cause WantRunning->true !! // BUG: third logout shouldn't cause WantRunning->true !!
assert.Equal(true, nn[0].Prefs.WantRunning) assert.Equal(true, nn[0].Prefs.WantRunning)
assert.Equal(ipn.NeedsLogin, b.State()) assert.Equal(ipn.NeedsLogin, b.State())
@ -664,16 +681,14 @@ func TestStateMachine(t *testing.T) {
{ {
// BUG: We already called Shutdown(), no need to do it again. // BUG: We already called Shutdown(), no need to do it again.
// BUG: Way too soon for UpdateEndpoints. // BUG: Way too soon for UpdateEndpoints.
// BUG: We've forgotten that we logged out earlier. // BUG: don't unpause because we're not logged in.
// Shouldn't cc.Login() until the user engages, or else we assert.Equal([]string{"Shutdown", "New", "UpdateEndpoints", "unpause"}, cc.getCalls())
// defeat the "no traffic until `tailscale up`" logic.
// BUG: strictly, it should pause, not unpause, here.
assert.Equal([]string{"Shutdown", "New", "UpdateEndpoints", "Login", "unpause"}, cc.getCalls())
nn := notifies.drain(2) nn := notifies.drain(2)
assert.Equal([]string{}, cc.getCalls()) assert.Equal([]string{}, cc.getCalls())
assert.NotNil(nn[0].Prefs) assert.NotNil(nn[0].Prefs)
assert.NotNil(nn[1].State) assert.NotNil(nn[1].State)
assert.Equal(true, nn[0].Prefs.LoggedOut)
assert.Equal(true, nn[0].Prefs.WantRunning) assert.Equal(true, nn[0].Prefs.WantRunning)
assert.Equal(ipn.NeedsLogin, *nn[1].State) assert.Equal(ipn.NeedsLogin, *nn[1].State)
assert.Equal(ipn.NeedsLogin, b.State()) assert.Equal(ipn.NeedsLogin, b.State())
@ -684,17 +699,19 @@ func TestStateMachine(t *testing.T) {
// (This simulates an admin reviving a key that you previously // (This simulates an admin reviving a key that you previously
// disabled.) // disabled.)
t.Logf("\n\nLoginFinished3") t.Logf("\n\nLoginFinished3")
notifies.expect(2) notifies.expect(3)
cc.setAuthBlocked(false) cc.setAuthBlocked(false)
cc.send(nil, "", true, &netmap.NetworkMap{ cc.send(nil, "", true, &netmap.NetworkMap{
MachineStatus: tailcfg.MachineAuthorized, MachineStatus: tailcfg.MachineAuthorized,
}) })
{ {
nn := notifies.drain(2) nn := notifies.drain(3)
assert.Equal([]string{"unpause"}, cc.getCalls()) assert.Equal([]string{"unpause"}, cc.getCalls())
assert.NotNil(nn[0].LoginFinished) assert.NotNil(nn[0].Prefs)
assert.NotNil(nn[1].State) assert.NotNil(nn[1].LoginFinished)
assert.Equal(ipn.Starting, *nn[1].State) assert.NotNil(nn[2].State)
assert.Equal(false, nn[0].Prefs.LoggedOut)
assert.Equal(ipn.Starting, *nn[2].State)
} }
// Now we've logged in successfully. Let's disconnect. // Now we've logged in successfully. Let's disconnect.
@ -711,6 +728,7 @@ func TestStateMachine(t *testing.T) {
assert.NotNil(nn[0].State) assert.NotNil(nn[0].State)
assert.NotNil(nn[1].Prefs) assert.NotNil(nn[1].Prefs)
assert.Equal(ipn.Stopped, *nn[0].State) assert.Equal(ipn.Stopped, *nn[0].State)
assert.Equal(false, nn[1].Prefs.LoggedOut)
} }
// One more restart, this time with a valid key, but WantRunning=false. // One more restart, this time with a valid key, but WantRunning=false.
@ -722,20 +740,16 @@ func TestStateMachine(t *testing.T) {
{ {
// NOTE: cc.Shutdown() is correct here, since we didn't call // NOTE: cc.Shutdown() is correct here, since we didn't call
// b.Shutdown() explicitly ourselves. // b.Shutdown() explicitly ourselves.
assert.Equal([]string{"Shutdown", "New", "UpdateEndpoints", "unpause"}, cc.getCalls()) // BUG: UpdateEndpoints should be called here since we're not WantRunning.
assert.Equal([]string{"Shutdown", "New", "UpdateEndpoints", "pause"}, cc.getCalls())
nn := notifies.drain(2) nn := notifies.drain(2)
assert.Equal([]string{}, cc.getCalls()) assert.Equal([]string{}, cc.getCalls())
assert.NotNil(nn[0].Prefs) assert.NotNil(nn[0].Prefs)
assert.NotNil(nn[1].State) assert.NotNil(nn[1].State)
prefs := *nn[0].Prefs assert.Equal(false, nn[0].Prefs.WantRunning)
assert.Equal(false, prefs.WantRunning) assert.Equal(false, nn[0].Prefs.LoggedOut)
// BUG: NeedsLogin is incorrect. We are already logged in. assert.Equal(ipn.Stopped, *nn[1].State)
// This is the cause of bug tailscale/corp#1660.
// (Whenever we enter the NeedsLogin state, the UI will
// sent the user a notification that they should log in.)
assert.Equal(ipn.NeedsLogin, *nn[1].State)
assert.Equal(ipn.NeedsLogin, b.State())
} }
// This time, let's have the control server spontaneously // This time, let's have the control server spontaneously
@ -744,25 +758,35 @@ func TestStateMachine(t *testing.T) {
// it. (Normally we should call b.Login() first, but we already // it. (Normally we should call b.Login() first, but we already
// tested that up above.) // tested that up above.)
t.Logf("\n\nLoginFinished4") t.Logf("\n\nLoginFinished4")
notifies.expect(3) notifies.expect(1)
cc.setAuthBlocked(false) cc.setAuthBlocked(false)
cc.send(nil, "", true, &netmap.NetworkMap{ cc.send(nil, "", true, &netmap.NetworkMap{
MachineStatus: tailcfg.MachineAuthorized, MachineStatus: tailcfg.MachineAuthorized,
}) })
{ {
nn := notifies.drain(3) nn := notifies.drain(1)
assert.Equal([]string{"unpause"}, cc.getCalls()) // NOTE: No call to Login() here since !WantRunning at
// startup time.
assert.Equal([]string{}, cc.getCalls())
assert.NotNil(nn[0].LoginFinished) assert.NotNil(nn[0].LoginFinished)
// State is still ipn.Stopped, as expected.
}
// Request connection.
// The state machine didn't call Login() earlier, so now it needs to.
t.Logf("\n\nWantRunning4 -> true")
notifies.expect(2)
b.EditPrefs(&ipn.MaskedPrefs{
WantRunningSet: true,
Prefs: ipn.Prefs{WantRunning: true},
})
{
nn := notifies.drain(2)
assert.Equal([]string{"unpause", "Login"}, cc.getCalls())
// BUG: I would expect Prefs to change first, and state after.
assert.NotNil(nn[0].State)
assert.NotNil(nn[1].Prefs) assert.NotNil(nn[1].Prefs)
assert.NotNil(nn[2].State) assert.Equal(ipn.Starting, *nn[0].State)
// BUG: WantRunning spontaneously shifted to true.
// This should happen if a user calls b.Login(), but not if
// a login spontaneously finishes.
// This could lead to a bug where the control server could
// remotely cause a node to re-connect when it's supposed to
// be in the Stopped state.
assert.Equal(true, nn[1].Prefs.WantRunning)
assert.Equal(ipn.Starting, *nn[2].State)
} }
// The last test case is the most common one: restarting when both // The last test case is the most common one: restarting when both
@ -775,40 +799,32 @@ func TestStateMachine(t *testing.T) {
{ {
// NOTE: cc.Shutdown() is correct here, since we didn't call // NOTE: cc.Shutdown() is correct here, since we didn't call
// b.Shutdown() ourselves. // b.Shutdown() ourselves.
assert.Equal([]string{"Shutdown", "New", "UpdateEndpoints", "Login", "unpause"}, cc.getCalls()) assert.Equal([]string{"Shutdown", "New", "UpdateEndpoints", "Login"}, cc.getCalls())
nn := notifies.drain(2) nn := notifies.drain(2)
assert.Equal([]string{}, cc.getCalls()) assert.Equal([]string{}, cc.getCalls())
assert.NotNil(nn[0].Prefs) assert.NotNil(nn[0].Prefs)
assert.NotNil(nn[1].State) assert.NotNil(nn[1].State)
prefs := *nn[0].Prefs assert.Equal(false, nn[0].Prefs.LoggedOut)
assert.Equal(true, prefs.WantRunning) assert.Equal(true, nn[0].Prefs.WantRunning)
// BUG: NeedsLogin is very wrong, it could trigger a login assert.Equal(ipn.Starting, *nn[1].State)
// notification. (It goes away quickly once the control assert.Equal(ipn.Starting, b.State())
// server gets back to us, but this race condition is
// enough to probably cause spurious notifications. I think
// I've seen this on Windows.)
//
// Probably we should go to Starting right away here, since
// WantRunning==true and we believe (so far) that our key
// is valid.
assert.Equal(ipn.NeedsLogin, *nn[1].State)
assert.Equal(ipn.NeedsLogin, b.State())
} }
// Control server accepts our valid key from before. // Control server accepts our valid key from before.
t.Logf("\n\nLoginFinished5") t.Logf("\n\nLoginFinished5")
notifies.expect(2) notifies.expect(1)
cc.setAuthBlocked(false) cc.setAuthBlocked(false)
cc.send(nil, "", true, &netmap.NetworkMap{ cc.send(nil, "", true, &netmap.NetworkMap{
MachineStatus: tailcfg.MachineAuthorized, MachineStatus: tailcfg.MachineAuthorized,
}) })
{ {
nn := notifies.drain(2) nn := notifies.drain(1)
assert.Equal([]string{"unpause"}, cc.getCalls()) assert.Equal([]string{}, cc.getCalls())
assert.NotNil(nn[0].LoginFinished) assert.NotNil(nn[0].LoginFinished)
// NOTE: No prefs change this time. WantRunning stays true. // NOTE: No prefs change this time. WantRunning stays true.
assert.NotNil(nn[1].State) // We were in Starting in the first place, so that doesn't
assert.Equal(ipn.Starting, *nn[1].State) // change either.
assert.Equal(ipn.Starting, b.State())
} }
} }

View File

@ -87,6 +87,14 @@ type Prefs struct {
// this node. // this node.
WantRunning bool WantRunning bool
// LoggedOut indicates whether the user intends to be logged out.
// There are other reasons we may be logged out, including no valid
// keys.
// We need to remember this state so that, on next startup, we can
// generate the "Login" vs "Connect" buttons correctly, without having
// to contact the server to confirm our nodekey status first.
LoggedOut bool
// ShieldsUp indicates whether to block all incoming connections, // ShieldsUp indicates whether to block all incoming connections,
// regardless of the control-provided packet filter. If false, we // regardless of the control-provided packet filter. If false, we
// use the packet filter as provided. If true, we block incoming // use the packet filter as provided. If true, we block incoming
@ -177,6 +185,7 @@ type MaskedPrefs struct {
ExitNodeAllowLANAccessSet bool `json:",omitempty"` ExitNodeAllowLANAccessSet bool `json:",omitempty"`
CorpDNSSet bool `json:",omitempty"` CorpDNSSet bool `json:",omitempty"`
WantRunningSet bool `json:",omitempty"` WantRunningSet bool `json:",omitempty"`
LoggedOutSet bool `json:",omitempty"`
ShieldsUpSet bool `json:",omitempty"` ShieldsUpSet bool `json:",omitempty"`
AdvertiseTagsSet bool `json:",omitempty"` AdvertiseTagsSet bool `json:",omitempty"`
HostnameSet bool `json:",omitempty"` HostnameSet bool `json:",omitempty"`
@ -246,6 +255,9 @@ func (p *Prefs) pretty(goos string) string {
sb.WriteString("mesh=false ") sb.WriteString("mesh=false ")
} }
fmt.Fprintf(&sb, "dns=%v want=%v ", p.CorpDNS, p.WantRunning) fmt.Fprintf(&sb, "dns=%v want=%v ", p.CorpDNS, p.WantRunning)
if p.LoggedOut {
sb.WriteString("loggedout=true ")
}
if p.ForceDaemon { if p.ForceDaemon {
sb.WriteString("server=true ") sb.WriteString("server=true ")
} }
@ -315,6 +327,7 @@ func (p *Prefs) Equals(p2 *Prefs) bool {
p.ExitNodeAllowLANAccess == p2.ExitNodeAllowLANAccess && p.ExitNodeAllowLANAccess == p2.ExitNodeAllowLANAccess &&
p.CorpDNS == p2.CorpDNS && p.CorpDNS == p2.CorpDNS &&
p.WantRunning == p2.WantRunning && p.WantRunning == p2.WantRunning &&
p.LoggedOut == p2.LoggedOut &&
p.NotepadURLs == p2.NotepadURLs && p.NotepadURLs == p2.NotepadURLs &&
p.ShieldsUp == p2.ShieldsUp && p.ShieldsUp == p2.ShieldsUp &&
p.NoSNAT == p2.NoSNAT && p.NoSNAT == p2.NoSNAT &&

View File

@ -41,6 +41,7 @@ func (src *Prefs) Clone() *Prefs {
ExitNodeAllowLANAccess bool ExitNodeAllowLANAccess bool
CorpDNS bool CorpDNS bool
WantRunning bool WantRunning bool
LoggedOut bool
ShieldsUp bool ShieldsUp bool
AdvertiseTags []string AdvertiseTags []string
Hostname string Hostname string

View File

@ -42,6 +42,7 @@ func TestPrefsEqual(t *testing.T) {
"ExitNodeAllowLANAccess", "ExitNodeAllowLANAccess",
"CorpDNS", "CorpDNS",
"WantRunning", "WantRunning",
"LoggedOut",
"ShieldsUp", "ShieldsUp",
"AdvertiseTags", "AdvertiseTags",
"Hostname", "Hostname",