cmd/tailscale/main: use localapi for edit prefs

-Use PrefsView instead of Prefs
-Replace SetPrefs calls with localapi using the prefs endpoint
-Follow-up will be to check if an exit node is being run from this node and stop before editing prefs to use an exit node; and to check if an exit node is being used and stop before editing prefs to be an exit node (right now there is no check and prefs are just being overriden directly)

Updates tailscale/tailscale#10992

Signed-off-by: kari-ts <kari@tailscale.com>
pull/172/head
kari-ts 8 months ago committed by kari-ts
parent 60b9884aa2
commit 051a0ecd62

@ -5,11 +5,13 @@
package main package main
import ( import (
"bytes"
"cmp" "cmp"
"context" "context"
"crypto/rand" "crypto/rand"
"crypto/sha1" "crypto/sha1"
"encoding/hex" "encoding/hex"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -66,15 +68,17 @@ type App struct {
// netStates receives the most recent network state. // netStates receives the most recent network state.
netStates chan BackendState netStates chan BackendState
// prefs receives new preferences from the backend. // prefs receives new preferences from the backend.
prefs chan *ipn.Prefs prefs chan ipn.PrefsView
// browseURLs receives URLs when the backend wants to browse. // browseURLs receives URLs when the backend wants to browse.
browseURLs chan string browseURLs chan string
// targetsLoaded receives lists of file targets. // targetsLoaded receives lists of file targets.
targetsLoaded chan FileTargets targetsLoaded chan FileTargets
// invalidates receives whenever the window should be refreshed. // invalidates receives whenever the window should be refreshed.
invalidates chan struct{} invalidates chan struct{}
// bugReport receives the bug report from the backend's localapi call // bugReport receives the bug report from the backend's localapi call.
bugReport chan string bugReport chan string
// editFns receives instructions for which preferences to edit and enforces the ordering of these edits.
editFns chan func(currPrefs ipn.PrefsView, updatedPrefs *ipn.MaskedPrefs)
} }
var ( var (
@ -153,7 +157,7 @@ type Peer struct {
} }
type BackendState struct { type BackendState struct {
Prefs *ipn.Prefs Prefs ipn.PrefsView
State ipn.State State ipn.State
NetworkMap *netmap.NetworkMap NetworkMap *netmap.NetworkMap
LostInternet bool LostInternet bool
@ -227,10 +231,11 @@ func main() {
appCtx: jni.Object(app.AppContext()), appCtx: jni.Object(app.AppContext()),
netStates: make(chan BackendState, 1), netStates: make(chan BackendState, 1),
browseURLs: make(chan string, 1), browseURLs: make(chan string, 1),
prefs: make(chan *ipn.Prefs, 1), prefs: make(chan ipn.PrefsView, 1),
targetsLoaded: make(chan FileTargets, 1), targetsLoaded: make(chan FileTargets, 1),
invalidates: make(chan struct{}, 1), invalidates: make(chan struct{}, 1),
bugReport: make(chan string, 1), bugReport: make(chan string, 1),
editFns: make(chan func(currPrefs ipn.PrefsView, updatedPrefs *ipn.MaskedPrefs), 1),
} }
err := jni.Do(a.jvm, func(env *jni.Env) error { err := jni.Do(a.jvm, func(env *jni.Env) error {
loader := jni.ClassLoaderFor(env, a.appCtx) loader := jni.ClassLoaderFor(env, a.appCtx)
@ -370,16 +375,19 @@ func (a *App) runBackend(ctx context.Context) error {
case n := <-notifications: case n := <-notifications:
exitWasOnline := state.ExitStatus == ExitOnline exitWasOnline := state.ExitStatus == ExitOnline
if p := n.Prefs; p != nil && n.Prefs.Valid() { if p := n.Prefs; p != nil && n.Prefs.Valid() {
state.Prefs = p.AsStruct() // make a copy of prefs and use the readonly view
state.Prefs = p.AsStruct().View()
state.updateExitNodes() state.updateExitNodes()
a.setPrefs(state.Prefs) a.prefs <- state.Prefs
} }
first := state.Prefs == nil first := !state.Prefs.Valid()
if first { if first {
state.Prefs = ipn.NewPrefs() newPrefs := ipn.NewPrefs()
state.Prefs.Hostname = a.hostname() newPrefs.Hostname = a.hostname()
go b.backend.SetPrefs(state.Prefs) go b.backend.SetPrefs(newPrefs)
a.setPrefs(state.Prefs) state.Prefs = newPrefs.View()
go a.editPrefs(ctx, state.Prefs)
} }
if s := n.State; s != nil { if s := n.State; s != nil {
oldState := state.State oldState := state.State
@ -451,11 +459,21 @@ func (a *App) runBackend(ctx context.Context) error {
case OAuth2Event: case OAuth2Event:
go b.backend.Login(e.Token) go b.backend.Login(e.Token)
case BeExitNodeEvent: case BeExitNodeEvent:
state.Prefs.SetAdvertiseExitNode(bool(e)) a.editFns <- func(currPrefs ipn.PrefsView, updatedPrefs *ipn.MaskedPrefs) {
go b.backend.SetPrefs(state.Prefs) routes := currPrefs.AdvertiseRoutes()
routes.AppendTo([]netip.Prefix{
netip.MustParsePrefix("0.0.0.0/0"),
netip.MustParsePrefix("::/0"),
})
updatedPrefs.AdvertiseRoutes = routes.AsSlice()
updatedPrefs.SetAdvertiseExitNode(bool(e))
updatedPrefs.AdvertiseRoutesSet = true
}
case ExitAllowLANEvent: case ExitAllowLANEvent:
state.Prefs.ExitNodeAllowLANAccess = bool(e) a.editFns <- func(currPrefs ipn.PrefsView, updatedPrefs *ipn.MaskedPrefs) {
go b.backend.SetPrefs(state.Prefs) updatedPrefs.ExitNodeAllowLANAccess = bool(e)
updatedPrefs.ExitNodeAllowLANAccessSet = true
}
case WebAuthEvent: case WebAuthEvent:
log.Printf("KARI WEBAUTHEVENT") log.Printf("KARI WEBAUTHEVENT")
if !signingIn { if !signingIn {
@ -463,17 +481,10 @@ func (a *App) runBackend(ctx context.Context) error {
signingIn = true signingIn = true
} }
case SetLoginServerEvent: case SetLoginServerEvent:
state.Prefs.ControlURL = e.URL a.editFns <- func(currPrefs ipn.PrefsView, updatedPrefs *ipn.MaskedPrefs) {
b.backend.SetPrefs(state.Prefs) updatedPrefs.ControlURL = e.URL
// Need to restart to force the login URL to be regenerated updatedPrefs.ControlURLSet = true
// with the new control URL. Start from a goroutine to avoid }
// deadlock.
go func() {
err := b.backend.Start(ipn.Options{})
if err != nil {
fatalErr(err)
}
}()
case LogoutEvent: case LogoutEvent:
go func() { go func() {
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute) ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
@ -481,11 +492,15 @@ func (a *App) runBackend(ctx context.Context) error {
b.backend.Logout(ctx) b.backend.Logout(ctx)
}() }()
case ConnectEvent: case ConnectEvent:
state.Prefs.WantRunning = e.Enable a.editFns <- func(currPrefs ipn.PrefsView, updatedPrefs *ipn.MaskedPrefs) {
go b.backend.SetPrefs(state.Prefs) updatedPrefs.WantRunning = e.Enable
updatedPrefs.WantRunningSet = true
}
case RouteAllEvent: case RouteAllEvent:
state.Prefs.ExitNodeID = e.ID a.editFns <- func(currPrefs ipn.PrefsView, updatedPrefs *ipn.MaskedPrefs) {
go b.backend.SetPrefs(state.Prefs) updatedPrefs.ExitNodeID = e.ID
updatedPrefs.ExitNodeIDSet = true
}
state.updateExitNodes() state.updateExitNodes()
a.notify(state) a.notify(state)
if service != 0 { if service != 0 {
@ -570,6 +585,46 @@ func (a *App) runBackend(ctx context.Context) error {
} }
} }
// editPrefs is called when prefs is created for the first time. It pulls
// from the edit function channel, and makes calls to localapi to edit the
//
// preferences. This ensures that edits will be made in the correct order.
func (a *App) editPrefs(ctx context.Context, prefs ipn.PrefsView) {
for {
select {
case fn := <-a.editFns:
mp := new(ipn.MaskedPrefs)
fn(prefs, mp)
jsonData, err := json.Marshal(mp)
if err != nil {
log.Printf("error marshaling MaskedPrefs %v", err)
continue
}
body := bytes.NewReader(jsonData)
r, err := a.localAPIClient.Call(ctx, "PATCH", "prefs", body)
if err != nil {
log.Printf("localapiclient error %v", err)
continue
}
data, err := io.ReadAll(r.Body())
r.Body().Close()
if err != nil {
log.Printf("error reading localapi response %v", err)
}
var newPrefs ipn.PrefsView
err = json.Unmarshal(data, &newPrefs)
if err != nil {
log.Printf("error unmarshaling localapi response %v", err)
}
prefs = newPrefs
default:
}
}
}
func (a *App) getBugReportID(ctx context.Context, bugReportChan chan<- string, fallbackLog string) { func (a *App) getBugReportID(ctx context.Context, bugReportChan chan<- string, fallbackLog string) {
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(2*time.Second)) ctx, cancel := context.WithDeadline(ctx, time.Now().Add(2*time.Second))
defer cancel() defer cancel()
@ -704,8 +759,8 @@ func (a *App) isChromeOS() bool {
func (s *BackendState) updateExitNodes() { func (s *BackendState) updateExitNodes() {
s.ExitStatus = ExitNone s.ExitStatus = ExitNone
var exitID tailcfg.StableNodeID var exitID tailcfg.StableNodeID
if p := s.Prefs; p != nil { if p := s.Prefs; p.Valid() {
exitID = p.ExitNodeID exitID = p.ExitNodeID()
if exitID != "" { if exitID != "" {
s.ExitStatus = ExitOffline s.ExitStatus = ExitOffline
} }
@ -978,18 +1033,6 @@ func (a *App) notify(state BackendState) {
} }
} }
func (a *App) setPrefs(prefs *ipn.Prefs) {
wantRunning := jni.Bool(prefs.WantRunning)
if err := a.callVoidMethod(a.appCtx, "setTileStatus", "(Z)V", jni.Value(wantRunning)); err != nil {
fatalErr(err)
}
select {
case <-a.prefs:
default:
}
a.prefs <- prefs
}
func (a *App) setURL(url string) { func (a *App) setURL(url string) {
select { select {
case <-a.browseURLs: case <-a.browseURLs:
@ -1045,9 +1088,10 @@ func (a *App) runUI() error {
} }
} }
case p := <-a.prefs: case p := <-a.prefs:
ui.enabled.Value = p.WantRunning ui.enabled.Value = p.WantRunning()
ui.runningExit = p.AdvertisesExitNode() ui.runningExit = p.AdvertisesExitNode()
ui.exitLAN.Value = p.ExitNodeAllowLANAccess ui.exitID = p.ExitNodeID()
ui.exitLAN.Value = p.ExitNodeAllowLANAccess()
w.Invalidate() w.Invalidate()
case url := <-a.browseURLs: case url := <-a.browseURLs:
ui.signinType = noSignin ui.signinType = noSignin
@ -1313,7 +1357,7 @@ func (a *App) processUIEvents(w *app.Window, events []UIEvent, act jni.Object, s
a.signOut() a.signOut()
requestBackend(e) requestBackend(e)
case ConnectEvent: case ConnectEvent:
if srv, _ := a.store.ReadString(customLoginServerPrefKey, ""); srv != state.backend.Prefs.ControlURL { if srv, _ := a.store.ReadString(customLoginServerPrefKey, ""); srv != state.backend.Prefs.ControlURL() {
requestBackend(SetLoginServerEvent{URL: srv}) requestBackend(SetLoginServerEvent{URL: srv})
// wait a moment for the backend to restart // wait a moment for the backend to restart
<-time.After(200 * time.Millisecond) <-time.After(200 * time.Millisecond)

@ -82,6 +82,8 @@ type UI struct {
runningExit bool // are we an exit node now? runningExit bool // are we an exit node now?
exitID tailcfg.StableNodeID
qr struct { qr struct {
show bool show bool
op paint.ImageOp op paint.ImageOp
@ -360,7 +362,6 @@ func (ui *UI) layout(gtx layout.Context, sysIns system.Insets, state *clientStat
localName, localAddr string localName, localAddr string
expiry time.Time expiry time.Time
userID tailcfg.UserID userID tailcfg.UserID
exitID tailcfg.StableNodeID
) )
if netmap != nil { if netmap != nil {
userID = netmap.User() userID = netmap.User()
@ -370,16 +371,13 @@ func (ui *UI) layout(gtx layout.Context, sysIns system.Insets, state *clientStat
localAddr = addrs.At(0).Addr().String() localAddr = addrs.At(0).Addr().String()
} }
} }
if p := state.backend.Prefs; p != nil {
exitID = p.ExitNodeID
}
if d := &ui.exitDialog; d.show { if d := &ui.exitDialog; d.show {
if newID := tailcfg.StableNodeID(d.exits.Value); newID != exitID { if newID := tailcfg.StableNodeID(d.exits.Value); newID != ui.exitID {
d.show = false d.show = false
events = append(events, RouteAllEvent{newID}) events = append(events, RouteAllEvent{newID})
} }
} else { } else {
d.exits.Value = string(exitID) d.exits.Value = string(ui.exitID)
} }
if ui.exitLAN.Changed() { if ui.exitLAN.Changed() {
events = append(events, ExitAllowLANEvent(ui.exitLAN.Value)) events = append(events, ExitAllowLANEvent(ui.exitLAN.Value))
@ -583,7 +581,7 @@ func (ui *UI) layout(gtx layout.Context, sysIns system.Insets, state *clientStat
// 3-dots menu. // 3-dots menu.
if ui.menu.show { if ui.menu.show {
ui.layoutMenu(gtx, sysIns, expiry, exitID != "" || len(state.backend.Exits) > 0, needsLogin) ui.layoutMenu(gtx, sysIns, expiry, ui.exitID != "" || len(state.backend.Exits) > 0, needsLogin)
} }
if ui.qr.show { if ui.qr.show {

@ -1,6 +1,6 @@
module github.com/tailscale/tailscale-android module github.com/tailscale/tailscale-android
go 1.22 go 1.22.0
require ( require (
eliasnaur.com/font v0.0.0-20220124212145-832bb8fc08c3 eliasnaur.com/font v0.0.0-20220124212145-832bb8fc08c3
@ -98,4 +98,4 @@ require (
gvisor.dev/gvisor v0.0.0-20240119233241-c9c1d4f9b186 // indirect gvisor.dev/gvisor v0.0.0-20240119233241-c9c1d4f9b186 // indirect
inet.af/peercred v0.0.0-20210906144145-0893ea02156a // indirect inet.af/peercred v0.0.0-20210906144145-0893ea02156a // indirect
nhooyr.io/websocket v1.8.10 // indirect nhooyr.io/websocket v1.8.10 // indirect
) )

Loading…
Cancel
Save