ipnext.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package ipnext defines types and interfaces used for extending the core LocalBackend
  4. // functionality with additional features and services.
  5. package ipnext
  6. import (
  7. "errors"
  8. "fmt"
  9. "iter"
  10. "net/netip"
  11. "tailscale.com/control/controlclient"
  12. "tailscale.com/feature"
  13. "tailscale.com/ipn"
  14. "tailscale.com/ipn/ipnauth"
  15. "tailscale.com/ipn/ipnstate"
  16. "tailscale.com/tailcfg"
  17. "tailscale.com/tsd"
  18. "tailscale.com/tstime"
  19. "tailscale.com/types/logger"
  20. "tailscale.com/types/mapx"
  21. )
  22. // Extension augments LocalBackend with additional functionality.
  23. //
  24. // An extension uses the provided [Host] to register callbacks
  25. // and interact with the backend in a controlled, well-defined
  26. // and thread-safe manner.
  27. //
  28. // Extensions are registered using [RegisterExtension].
  29. //
  30. // They must be safe for concurrent use.
  31. type Extension interface {
  32. // Name is a unique name of the extension.
  33. // It must be the same as the name used to register the extension.
  34. Name() string
  35. // Init is called to initialize the extension when LocalBackend's
  36. // Start method is called. Extensions are created but not initialized
  37. // unless LocalBackend is started.
  38. //
  39. // If the extension cannot be initialized, it must return an error,
  40. // and its Shutdown method will not be called on the host's shutdown.
  41. // Returned errors are not fatal; they are used for logging.
  42. // A [SkipExtension] error indicates an intentional decision rather than a failure.
  43. Init(Host) error
  44. // Shutdown is called when LocalBackend is shutting down,
  45. // provided the extension was initialized. For multiple extensions,
  46. // Shutdown is called in the reverse order of Init.
  47. // Returned errors are not fatal; they are used for logging.
  48. // After a call to Shutdown, the extension will not be called again.
  49. Shutdown() error
  50. }
  51. // NewExtensionFn is a function that instantiates an [Extension].
  52. // If a registered extension cannot be instantiated, the function must return an error.
  53. // If the extension should be skipped at runtime, it must return either [SkipExtension]
  54. // or a wrapped [SkipExtension]. Any other error returned is fatal and will prevent
  55. // the LocalBackend from starting.
  56. type NewExtensionFn func(logger.Logf, SafeBackend) (Extension, error)
  57. // SkipExtension is an error returned by [NewExtensionFn] to indicate that the extension
  58. // should be skipped rather than prevent the LocalBackend from starting.
  59. //
  60. // Skipping an extension should be reserved for cases where the extension is not supported
  61. // on the current platform or configuration, or depends on a feature that is not available,
  62. // or otherwise should be disabled permanently rather than temporarily.
  63. //
  64. // Specifically, it must not be returned if the extension is not required right now
  65. // based on user preferences, policy settings, the current tailnet, or other factors
  66. // that may change throughout the LocalBackend's lifetime.
  67. var SkipExtension = errors.New("skipping extension")
  68. // Definition describes a registered [Extension].
  69. type Definition struct {
  70. name string // name under which the extension is registered
  71. newFn NewExtensionFn // function that creates a new instance of the extension
  72. }
  73. // Name returns the name of the extension.
  74. func (d *Definition) Name() string {
  75. return d.name
  76. }
  77. // MakeExtension instantiates the extension.
  78. func (d *Definition) MakeExtension(logf logger.Logf, sb SafeBackend) (Extension, error) {
  79. ext, err := d.newFn(logf, sb)
  80. if err != nil {
  81. return nil, err
  82. }
  83. if ext.Name() != d.name {
  84. return nil, fmt.Errorf("extension name mismatch: registered %q; actual %q", d.name, ext.Name())
  85. }
  86. return ext, nil
  87. }
  88. // extensions is a map of registered extensions,
  89. // where the key is the name of the extension.
  90. var extensions mapx.OrderedMap[string, *Definition]
  91. // RegisterExtension registers a function that instantiates an [Extension].
  92. // The name must be the same as returned by the extension's [Extension.Name].
  93. //
  94. // It must be called on the main goroutine before LocalBackend is created,
  95. // such as from an init function of the package implementing the extension.
  96. //
  97. // It panics if newExt is nil or if an extension with the same name
  98. // has already been registered.
  99. func RegisterExtension(name string, newExt NewExtensionFn) {
  100. if newExt == nil {
  101. panic(fmt.Sprintf("ipnext: newExt is nil: %q", name))
  102. }
  103. if extensions.Contains(name) {
  104. panic(fmt.Sprintf("ipnext: duplicate extension name %q", name))
  105. }
  106. extensions.Set(name, &Definition{name, newExt})
  107. }
  108. // Extensions iterates over the extensions in the order they were registered
  109. // via [RegisterExtension].
  110. func Extensions() iter.Seq[*Definition] {
  111. return extensions.Values()
  112. }
  113. // DefinitionForTest returns a [Definition] for the specified [Extension].
  114. // It is primarily used for testing where the test code needs to instantiate
  115. // and use an extension without registering it.
  116. func DefinitionForTest(ext Extension) *Definition {
  117. return &Definition{
  118. name: ext.Name(),
  119. newFn: func(logger.Logf, SafeBackend) (Extension, error) { return ext, nil },
  120. }
  121. }
  122. // DefinitionWithErrForTest returns a [Definition] with the specified extension name
  123. // whose [Definition.MakeExtension] method returns the specified error.
  124. // It is used for testing.
  125. func DefinitionWithErrForTest(name string, err error) *Definition {
  126. return &Definition{
  127. name: name,
  128. newFn: func(logger.Logf, SafeBackend) (Extension, error) { return nil, err },
  129. }
  130. }
  131. // Host is the API surface used by [Extension]s to interact with LocalBackend
  132. // in a controlled manner.
  133. //
  134. // Extensions can register callbacks, request information, or perform actions
  135. // via the [Host] interface.
  136. //
  137. // Typically, the host invokes registered callbacks when one of the following occurs:
  138. // - LocalBackend notifies it of an event or state change that may be
  139. // of interest to extensions, such as when switching [ipn.LoginProfile].
  140. // - LocalBackend needs to consult extensions for information, for example,
  141. // determining the most appropriate profile for the current state of the system.
  142. // - LocalBackend performs an extensible action, such as logging an auditable event,
  143. // and delegates its execution to the extension.
  144. //
  145. // The callbacks are invoked synchronously, and the LocalBackend's state
  146. // remains unchanged while callbacks execute.
  147. //
  148. // In contrast, actions initiated by extensions are generally asynchronous,
  149. // as indicated by the "Async" suffix in their names.
  150. // Performing actions may result in callbacks being invoked as described above.
  151. //
  152. // To prevent conflicts between extensions competing for shared state,
  153. // such as the current profile or prefs, the host must not expose methods
  154. // that directly modify that state. For example, instead of allowing extensions
  155. // to switch profiles at-will, the host's [ProfileServices] provides a method
  156. // to switch to the "best" profile. The host can then consult extensions
  157. // to determine the appropriate profile to use and resolve any conflicts
  158. // in a controlled manner.
  159. //
  160. // A host must be safe for concurrent use.
  161. type Host interface {
  162. // Extensions returns the host's [ExtensionServices].
  163. Extensions() ExtensionServices
  164. // Profiles returns the host's [ProfileServices].
  165. Profiles() ProfileServices
  166. // AuditLogger returns a function that calls all currently registered audit loggers.
  167. // The function fails if any logger returns an error, indicating that the action
  168. // cannot be logged and must not be performed.
  169. //
  170. // The returned function captures the current state (e.g., the current profile) at
  171. // the time of the call and must not be persisted.
  172. AuditLogger() ipnauth.AuditLogFunc
  173. // Hooks returns a non-nil pointer to a [Hooks] struct.
  174. // Hooks must not be modified concurrently or after Tailscale has started.
  175. Hooks() *Hooks
  176. // SendNotifyAsync sends a notification to the IPN bus,
  177. // typically to the GUI client.
  178. SendNotifyAsync(ipn.Notify)
  179. // NodeBackend returns the [NodeBackend] for the currently active node
  180. // (which is approximately the same as the current profile).
  181. NodeBackend() NodeBackend
  182. }
  183. // SafeBackend is a subset of the [ipnlocal.LocalBackend] type's methods that
  184. // are safe to call from extension hooks at any time (even hooks called while
  185. // LocalBackend's internal mutex is held).
  186. type SafeBackend interface {
  187. Sys() *tsd.System
  188. Clock() tstime.Clock
  189. TailscaleVarRoot() string
  190. }
  191. // ExtensionServices provides access to the [Host]'s extension management services,
  192. // such as fetching active extensions.
  193. type ExtensionServices interface {
  194. // FindExtensionByName returns an active extension with the given name,
  195. // or nil if no such extension exists.
  196. FindExtensionByName(name string) any
  197. // FindMatchingExtension finds the first active extension that matches target,
  198. // and if one is found, sets target to that extension and returns true.
  199. // Otherwise, it returns false.
  200. //
  201. // It panics if target is not a non-nil pointer to either a type
  202. // that implements [ipnext.Extension], or to any interface type.
  203. FindMatchingExtension(target any) bool
  204. }
  205. // ProfileServices provides access to the [Host]'s profile management services,
  206. // such as switching profiles and registering profile change callbacks.
  207. type ProfileServices interface {
  208. // CurrentProfileState returns read-only views of the current profile
  209. // and its preferences. The returned views are always valid,
  210. // but the profile's [ipn.LoginProfileView.ID] returns ""
  211. // if the profile is new and has not been persisted yet.
  212. //
  213. // The returned views are immutable snapshots of the current profile
  214. // and prefs at the time of the call. The actual state is only guaranteed
  215. // to remain unchanged and match these views for the duration
  216. // of a callback invoked by the host, if used within that callback.
  217. //
  218. // Extensions that need the current profile or prefs at other times
  219. // should typically subscribe to [ProfileStateChangeCallback]
  220. // to be notified if the profile or prefs change after retrieval.
  221. // CurrentProfileState returns both the profile and prefs
  222. // to guarantee that they are consistent with each other.
  223. CurrentProfileState() (ipn.LoginProfileView, ipn.PrefsView)
  224. // CurrentPrefs is like [CurrentProfileState] but only returns prefs.
  225. CurrentPrefs() ipn.PrefsView
  226. // SwitchToBestProfileAsync asynchronously selects the best profile to use
  227. // and switches to it, unless it is already the current profile.
  228. //
  229. // If an extension needs to know when a profile switch occurs,
  230. // it must use [ProfileServices.RegisterProfileStateChangeCallback]
  231. // to register a [ProfileStateChangeCallback].
  232. //
  233. // The reason indicates why the profile is being switched, such as due
  234. // to a client connecting or disconnecting or a change in the desktop
  235. // session state. It is used for logging.
  236. SwitchToBestProfileAsync(reason string)
  237. }
  238. // ProfileStore provides read-only access to available login profiles and their preferences.
  239. // It is not safe for concurrent use and can only be used from the callback it is passed to.
  240. type ProfileStore interface {
  241. // CurrentUserID returns the current user ID. It is only non-empty on
  242. // Windows where we have a multi-user system.
  243. //
  244. // Deprecated: this method exists for compatibility with the current (as of 2024-08-27)
  245. // permission model and will be removed as we progress on tailscale/corp#18342.
  246. CurrentUserID() ipn.WindowsUserID
  247. // CurrentProfile returns a read-only [ipn.LoginProfileView] of the current profile.
  248. // The returned view is always valid, but the profile's [ipn.LoginProfileView.ID]
  249. // returns "" if the profile is new and has not been persisted yet.
  250. CurrentProfile() ipn.LoginProfileView
  251. // CurrentPrefs returns a read-only view of the current prefs.
  252. // The returned view is always valid.
  253. CurrentPrefs() ipn.PrefsView
  254. // DefaultUserProfile returns a read-only view of the default (last used) profile for the specified user.
  255. // It returns a read-only view of a new, non-persisted profile if the specified user does not have a default profile.
  256. DefaultUserProfile(uid ipn.WindowsUserID) ipn.LoginProfileView
  257. }
  258. // AuditLogProvider is a function that returns an [ipnauth.AuditLogFunc] for
  259. // logging auditable actions.
  260. type AuditLogProvider func() ipnauth.AuditLogFunc
  261. // ProfileResolver is a function that returns a read-only view of a login profile.
  262. // An invalid view indicates no profile. A valid profile view with an empty [ipn.ProfileID]
  263. // indicates that the profile is new and has not been persisted yet.
  264. // The provided [ProfileStore] can only be used for the duration of the callback.
  265. type ProfileResolver func(ProfileStore) ipn.LoginProfileView
  266. // ProfileStateChangeCallback is a function to be called when the current login profile
  267. // or its preferences change.
  268. //
  269. // The sameNode parameter indicates whether the profile represents the same node as before,
  270. // which is true when:
  271. // - Only the profile's [ipn.Prefs] or metadata (e.g., [tailcfg.UserProfile]) have changed,
  272. // but the node ID and [ipn.ProfileID] remain the same.
  273. // - The profile has been persisted and assigned an [ipn.ProfileID] for the first time,
  274. // so while its node ID and [ipn.ProfileID] have changed, it is still the same profile.
  275. //
  276. // It can be used to decide whether to reset state bound to the current profile or node identity.
  277. //
  278. // The profile and prefs are always valid, but the profile's [ipn.LoginProfileView.ID]
  279. // returns "" if the profile is new and has not been persisted yet.
  280. type ProfileStateChangeCallback func(_ ipn.LoginProfileView, _ ipn.PrefsView, sameNode bool)
  281. // NewControlClientCallback is a function to be called when a new [controlclient.Client]
  282. // is created and before it is first used. The specified profile represents the node
  283. // for which the cc is created and is always valid. Its [ipn.LoginProfileView.ID]
  284. // returns "" if it is a new node whose profile has never been persisted.
  285. //
  286. // If the [controlclient.Client] is created due to a profile switch, any registered
  287. // [ProfileStateChangeCallback]s are called first.
  288. //
  289. // It returns a function to be called when the cc is being shut down,
  290. // or nil if no cleanup is needed. That cleanup function should not call
  291. // back into LocalBackend, which may be locked during shutdown.
  292. type NewControlClientCallback func(controlclient.Client, ipn.LoginProfileView) (cleanup func())
  293. // Hooks is a collection of hooks that extensions can add to (non-concurrently)
  294. // during program initialization and can be called by LocalBackend and others at
  295. // runtime.
  296. //
  297. // Each hook has its own rules about when it's called and what environment it
  298. // has access to and what it's allowed to do.
  299. type Hooks struct {
  300. // BackendStateChange is called when the backend state changes.
  301. BackendStateChange feature.Hooks[func(ipn.State)]
  302. // ProfileStateChange contains callbacks that are invoked when the current login profile
  303. // or its [ipn.Prefs] change, after those changes have been made. The current login profile
  304. // may be changed either because of a profile switch, or because the profile information
  305. // was updated by [LocalBackend.SetControlClientStatus], including when the profile
  306. // is first populated and persisted.
  307. ProfileStateChange feature.Hooks[ProfileStateChangeCallback]
  308. // BackgroundProfileResolvers are registered background profile resolvers.
  309. // They're used to determine the profile to use when no GUI/CLI client is connected.
  310. //
  311. // TODO(nickkhyl): allow specifying some kind of priority/altitude for the resolver.
  312. // TODO(nickkhyl): make it a "profile resolver" instead of a "background profile resolver".
  313. // The concepts of the "current user", "foreground profile" and "background profile"
  314. // only exist on Windows, and we're moving away from them anyway.
  315. BackgroundProfileResolvers feature.Hooks[ProfileResolver]
  316. // AuditLoggers are registered [AuditLogProvider]s.
  317. // Each provider is called to get an [ipnauth.AuditLogFunc] when an auditable action
  318. // is about to be performed. If an audit logger returns an error, the action is denied.
  319. AuditLoggers feature.Hooks[AuditLogProvider]
  320. // NewControlClient are the functions to be called when a new control client
  321. // is created. It is called with the LocalBackend locked.
  322. NewControlClient feature.Hooks[NewControlClientCallback]
  323. // OnSelfChange is called (with LocalBackend.mu held) when the self node
  324. // changes, including changing to nothing (an invalid view).
  325. OnSelfChange feature.Hooks[func(tailcfg.NodeView)]
  326. // MutateNotifyLocked is called to optionally mutate the provided Notify
  327. // before sending it to the IPN bus. It is called with LocalBackend.mu held.
  328. MutateNotifyLocked feature.Hooks[func(*ipn.Notify)]
  329. // SetPeerStatus is called to mutate PeerStatus.
  330. // Callers must only use NodeBackend to read data.
  331. SetPeerStatus feature.Hooks[func(*ipnstate.PeerStatus, tailcfg.NodeView, NodeBackend)]
  332. // ShouldUploadServices reports whether this node should include services
  333. // in Hostinfo from the portlist extension.
  334. ShouldUploadServices feature.Hook[func() bool]
  335. }
  336. // NodeBackend is an interface to query the current node and its peers.
  337. //
  338. // It is not a snapshot in time but is locked to a particular node.
  339. type NodeBackend interface {
  340. // AppendMatchingPeers appends all peers that match the predicate
  341. // to the base slice and returns it.
  342. AppendMatchingPeers(base []tailcfg.NodeView, pred func(tailcfg.NodeView) bool) []tailcfg.NodeView
  343. // PeerCaps returns the capabilities that src has to this node.
  344. PeerCaps(src netip.Addr) tailcfg.PeerCapMap
  345. // PeerHasCap reports whether the peer has the specified peer capability.
  346. PeerHasCap(peer tailcfg.NodeView, cap tailcfg.PeerCapability) bool
  347. // PeerAPIBase returns the "http://ip:port" URL base to reach peer's
  348. // PeerAPI, or the empty string if the peer is invalid or doesn't support
  349. // PeerAPI.
  350. PeerAPIBase(tailcfg.NodeView) string
  351. // PeerHasPeerAPI whether the provided peer supports PeerAPI.
  352. //
  353. // It effectively just reports whether PeerAPIBase(node) is non-empty, but
  354. // potentially more efficiently.
  355. PeerHasPeerAPI(tailcfg.NodeView) bool
  356. // CollectServices reports whether the control plane is telling this
  357. // node that the portlist service collection is desirable, should it
  358. // choose to report them.
  359. CollectServices() bool
  360. }