From 2a32ed1f301e49efc340ff8f829d68c1802b6d0d Mon Sep 17 00:00:00 2001 From: Nick Khyl <1761190+nickkhyl@users.noreply.github.com> Date: Wed, 7 Aug 2024 11:45:05 -0500 Subject: [PATCH] libtailscale, mdm: allow syspolicy to subscribe to policy change notifications (#462) In preparation for upcoming syspolicy improvements, we'd like to allow subscriptions to policy change notifications via the syspolicyHandler.RegisterChangeCallback. The registered callbacks are invoked whenever MDMSettings.update is called. Updates tailscale/tailscale#12687 Signed-off-by: Nick Khyl --- .../src/main/java/com/tailscale/ipn/App.kt | 4 +++ .../java/com/tailscale/ipn/mdm/MDMSettings.kt | 1 + libtailscale/backend.go | 1 + libtailscale/interfaces.go | 4 +++ libtailscale/localapi.go | 4 +++ libtailscale/syspolicy_handler.go | 33 ++++++++++++++++--- libtailscale/tailscale.go | 3 +- 7 files changed, 44 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/tailscale/ipn/App.kt b/android/src/main/java/com/tailscale/ipn/App.kt index eae0168..caae4ac 100644 --- a/android/src/main/java/com/tailscale/ipn/App.kt +++ b/android/src/main/java/com/tailscale/ipn/App.kt @@ -338,6 +338,10 @@ class App : UninitializedApp(), libtailscale.AppContext { throw MDMSettings.NoSuchKeyException() } } + + fun notifyPolicyChanged() { + app.notifyPolicyChanged() + } } /** diff --git a/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt b/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt index aa7a899..4285732 100644 --- a/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt +++ b/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt @@ -107,5 +107,6 @@ object MDMSettings { val bundle = restrictionsManager?.applicationRestrictions val preferences = lazy { app.getEncryptedPrefs() } allSettings.forEach { it.setFrom(bundle, preferences) } + app.notifyPolicyChanged() } } diff --git a/libtailscale/backend.go b/libtailscale/backend.go index 20aa3d1..f108f10 100644 --- a/libtailscale/backend.go +++ b/libtailscale/backend.go @@ -45,6 +45,7 @@ type App struct { appCtx AppContext store *stateStore + policyStore *syspolicyHandler logIDPublicAtomic atomic.Pointer[logid.PublicID] localAPIHandler http.Handler diff --git a/libtailscale/interfaces.go b/libtailscale/interfaces.go index 317d05e..c6ebf51 100644 --- a/libtailscale/interfaces.go +++ b/libtailscale/interfaces.go @@ -102,6 +102,10 @@ type Application interface { // it accepts multiple FileParts that get encoded as multipart/form-data. CallLocalAPIMultipart(timeoutMillis int, method, endpoint string, parts FileParts) (LocalAPIResponse, error) + // NotifyPolicyChanged notifies the backend about a changed MDM policy, + // so it can re-read it via the [syspolicyHandler]. + NotifyPolicyChanged() + // WatchNotifications provides a mechanism for subscribing to ipn.Notify // updates. The given NotificationCallback's OnNotify function is invoked // on every new ipn.Notify message. The returned NotificationManager diff --git a/libtailscale/localapi.go b/libtailscale/localapi.go index bed54b7..678d44c 100644 --- a/libtailscale/localapi.go +++ b/libtailscale/localapi.go @@ -107,6 +107,10 @@ func (app *App) CallLocalAPIMultipart(timeoutMillis int, method, endpoint string } } +func (app *App) NotifyPolicyChanged() { + app.policyStore.notifyChanged() +} + func (app *App) EditPrefs(prefs ipn.MaskedPrefs) (LocalAPIResponse, error) { r, w := io.Pipe() go func() { diff --git a/libtailscale/syspolicy_handler.go b/libtailscale/syspolicy_handler.go index 767f3b9..c7fc68c 100644 --- a/libtailscale/syspolicy_handler.go +++ b/libtailscale/syspolicy_handler.go @@ -6,17 +6,21 @@ package libtailscale import ( "encoding/json" "errors" + "sync" + "tailscale.com/util/set" "tailscale.com/util/syspolicy" ) // syspolicyHandler is a syspolicy handler for the Android version of the Tailscale client, // which lets the main networking code read values set via the Android RestrictionsManager. type syspolicyHandler struct { - a *App + a *App + mu sync.RWMutex + cbs set.HandleSet[func()] } -func (h syspolicyHandler) ReadString(key string) (string, error) { +func (h *syspolicyHandler) ReadString(key string) (string, error) { if key == "" { return "", syspolicy.ErrNoSuchKey } @@ -24,7 +28,7 @@ func (h syspolicyHandler) ReadString(key string) (string, error) { return retVal, translateHandlerError(err) } -func (h syspolicyHandler) ReadBoolean(key string) (bool, error) { +func (h *syspolicyHandler) ReadBoolean(key string) (bool, error) { if key == "" { return false, syspolicy.ErrNoSuchKey } @@ -32,7 +36,7 @@ func (h syspolicyHandler) ReadBoolean(key string) (bool, error) { return retVal, translateHandlerError(err) } -func (h syspolicyHandler) ReadUInt64(key string) (uint64, error) { +func (h *syspolicyHandler) ReadUInt64(key string) (uint64, error) { if key == "" { return 0, syspolicy.ErrNoSuchKey } @@ -40,7 +44,7 @@ func (h syspolicyHandler) ReadUInt64(key string) (uint64, error) { return 0, errors.New("ReadUInt64 is not implemented on Android") } -func (h syspolicyHandler) ReadStringArray(key string) ([]string, error) { +func (h *syspolicyHandler) ReadStringArray(key string) ([]string, error) { if key == "" { return nil, syspolicy.ErrNoSuchKey } @@ -59,6 +63,25 @@ func (h syspolicyHandler) ReadStringArray(key string) ([]string, error) { return arr, err } +func (h *syspolicyHandler) RegisterChangeCallback(cb func()) (unregister func(), err error) { + h.mu.Lock() + handle := h.cbs.Add(cb) + h.mu.Unlock() + return func() { + h.mu.Lock() + delete(h.cbs, handle) + h.mu.Unlock() + }, nil +} + +func (h *syspolicyHandler) notifyChanged() { + h.mu.RLock() + for _, cb := range h.cbs { + go cb() + } + h.mu.RUnlock() +} + func translateHandlerError(err error) error { if err != nil && !errors.Is(err, syspolicy.ErrNoSuchKey) && err.Error() == syspolicy.ErrNoSuchKey.Error() { return syspolicy.ErrNoSuchKey diff --git a/libtailscale/tailscale.go b/libtailscale/tailscale.go index df63aec..7370726 100644 --- a/libtailscale/tailscale.go +++ b/libtailscale/tailscale.go @@ -39,8 +39,9 @@ func newApp(dataDir, directFileRoot string, appCtx AppContext) Application { a.ready.Add(2) a.store = newStateStore(a.appCtx) + a.policyStore = &syspolicyHandler{a: a} netmon.RegisterInterfaceGetter(a.getInterfaces) - syspolicy.RegisterHandler(syspolicyHandler{a: a}) + syspolicy.RegisterHandler(a.policyStore) go func() { defer func() { if p := recover(); p != nil {