ipnext

package
v1.88.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2025 License: BSD-3-Clause Imports: 14 Imported by: 0

Documentation

Overview

Package ipnext defines types and interfaces used for extending the core LocalBackend functionality with additional features and services.

Index

Constants

This section is empty.

Variables

View Source
var SkipExtension = errors.New("skipping extension")

SkipExtension is an error returned by NewExtensionFn to indicate that the extension should be skipped rather than prevent the LocalBackend from starting.

Skipping an extension should be reserved for cases where the extension is not supported on the current platform or configuration, or depends on a feature that is not available, or otherwise should be disabled permanently rather than temporarily.

Specifically, it must not be returned if the extension is not required right now based on user preferences, policy settings, the current tailnet, or other factors that may change throughout the LocalBackend's lifetime.

Functions

func Extensions

func Extensions() iter.Seq[*Definition]

Extensions iterates over the extensions in the order they were registered via RegisterExtension.

func RegisterExtension

func RegisterExtension(name string, newExt NewExtensionFn)

RegisterExtension registers a function that instantiates an Extension. The name must be the same as returned by the extension's [Extension.Name].

It must be called on the main goroutine before LocalBackend is created, such as from an init function of the package implementing the extension.

It panics if newExt is nil or if an extension with the same name has already been registered.

Types

type AuditLogProvider

type AuditLogProvider func() ipnauth.AuditLogFunc

AuditLogProvider is a function that returns an ipnauth.AuditLogFunc for logging auditable actions.

type Definition

type Definition struct {
	// contains filtered or unexported fields
}

Definition describes a registered Extension.

func DefinitionForTest

func DefinitionForTest(ext Extension) *Definition

DefinitionForTest returns a Definition for the specified Extension. It is primarily used for testing where the test code needs to instantiate and use an extension without registering it.

func DefinitionWithErrForTest

func DefinitionWithErrForTest(name string, err error) *Definition

DefinitionWithErrForTest returns a Definition with the specified extension name whose Definition.MakeExtension method returns the specified error. It is used for testing.

func (*Definition) MakeExtension

func (d *Definition) MakeExtension(logf logger.Logf, sb SafeBackend) (Extension, error)

MakeExtension instantiates the extension.

func (*Definition) Name

func (d *Definition) Name() string

Name returns the name of the extension.

type Extension

type Extension interface {
	// Name is a unique name of the extension.
	// It must be the same as the name used to register the extension.
	Name() string

	// Init is called to initialize the extension when LocalBackend's
	// Start method is called. Extensions are created but not initialized
	// unless LocalBackend is started.
	//
	// If the extension cannot be initialized, it must return an error,
	// and its Shutdown method will not be called on the host's shutdown.
	// Returned errors are not fatal; they are used for logging.
	// A [SkipExtension] error indicates an intentional decision rather than a failure.
	Init(Host) error

	// Shutdown is called when LocalBackend is shutting down,
	// provided the extension was initialized. For multiple extensions,
	// Shutdown is called in the reverse order of Init.
	// Returned errors are not fatal; they are used for logging.
	// After a call to Shutdown, the extension will not be called again.
	Shutdown() error
}

Extension augments LocalBackend with additional functionality.

An extension uses the provided Host to register callbacks and interact with the backend in a controlled, well-defined and thread-safe manner.

Extensions are registered using RegisterExtension.

They must be safe for concurrent use.

type ExtensionServices

type ExtensionServices interface {
	// FindExtensionByName returns an active extension with the given name,
	// or nil if no such extension exists.
	FindExtensionByName(name string) any

	// FindMatchingExtension finds the first active extension that matches target,
	// and if one is found, sets target to that extension and returns true.
	// Otherwise, it returns false.
	//
	// It panics if target is not a non-nil pointer to either a type
	// that implements [ipnext.Extension], or to any interface type.
	FindMatchingExtension(target any) bool
}

ExtensionServices provides access to the Host's extension management services, such as fetching active extensions.

type Hooks

type Hooks struct {
	// BackendStateChange is called when the backend state changes.
	BackendStateChange feature.Hooks[func(ipn.State)]

	// ProfileStateChange contains callbacks that are invoked when the current login profile
	// or its [ipn.Prefs] change, after those changes have been made. The current login profile
	// may be changed either because of a profile switch, or because the profile information
	// was updated by [LocalBackend.SetControlClientStatus], including when the profile
	// is first populated and persisted.
	ProfileStateChange feature.Hooks[ProfileStateChangeCallback]

	// BackgroundProfileResolvers are registered background profile resolvers.
	// They're used to determine the profile to use when no GUI/CLI client is connected.
	//
	// TODO(nickkhyl): allow specifying some kind of priority/altitude for the resolver.
	// TODO(nickkhyl): make it a "profile resolver" instead of a "background profile resolver".
	// The concepts of the "current user", "foreground profile" and "background profile"
	// only exist on Windows, and we're moving away from them anyway.
	BackgroundProfileResolvers feature.Hooks[ProfileResolver]

	// AuditLoggers are registered [AuditLogProvider]s.
	// Each provider is called to get an [ipnauth.AuditLogFunc] when an auditable action
	// is about to be performed. If an audit logger returns an error, the action is denied.
	AuditLoggers feature.Hooks[AuditLogProvider]

	// NewControlClient are the functions to be called when a new control client
	// is created. It is called with the LocalBackend locked.
	NewControlClient feature.Hooks[NewControlClientCallback]

	// OnSelfChange is called (with LocalBackend.mu held) when the self node
	// changes, including changing to nothing (an invalid view).
	OnSelfChange feature.Hooks[func(tailcfg.NodeView)]

	// MutateNotifyLocked is called to optionally mutate the provided Notify
	// before sending it to the IPN bus. It is called with LocalBackend.mu held.
	MutateNotifyLocked feature.Hooks[func(*ipn.Notify)]

	// SetPeerStatus is called to mutate PeerStatus.
	// Callers must only use NodeBackend to read data.
	SetPeerStatus feature.Hooks[func(*ipnstate.PeerStatus, tailcfg.NodeView, NodeBackend)]
}

Hooks is a collection of hooks that extensions can add to (non-concurrently) during program initialization and can be called by LocalBackend and others at runtime.

Each hook has its own rules about when it's called and what environment it has access to and what it's allowed to do.

type Host

type Host interface {
	// Extensions returns the host's [ExtensionServices].
	Extensions() ExtensionServices

	// Profiles returns the host's [ProfileServices].
	Profiles() ProfileServices

	// AuditLogger returns a function that calls all currently registered audit loggers.
	// The function fails if any logger returns an error, indicating that the action
	// cannot be logged and must not be performed.
	//
	// The returned function captures the current state (e.g., the current profile) at
	// the time of the call and must not be persisted.
	AuditLogger() ipnauth.AuditLogFunc

	// Hooks returns a non-nil pointer to a [Hooks] struct.
	// Hooks must not be modified concurrently or after Tailscale has started.
	Hooks() *Hooks

	// SendNotifyAsync sends a notification to the IPN bus,
	// typically to the GUI client.
	SendNotifyAsync(ipn.Notify)

	// NodeBackend returns the [NodeBackend] for the currently active node
	// (which is approximately the same as the current profile).
	NodeBackend() NodeBackend
}

Host is the API surface used by [Extension]s to interact with LocalBackend in a controlled manner.

Extensions can register callbacks, request information, or perform actions via the Host interface.

Typically, the host invokes registered callbacks when one of the following occurs:

  • LocalBackend notifies it of an event or state change that may be of interest to extensions, such as when switching ipn.LoginProfile.
  • LocalBackend needs to consult extensions for information, for example, determining the most appropriate profile for the current state of the system.
  • LocalBackend performs an extensible action, such as logging an auditable event, and delegates its execution to the extension.

The callbacks are invoked synchronously, and the LocalBackend's state remains unchanged while callbacks execute.

In contrast, actions initiated by extensions are generally asynchronous, as indicated by the "Async" suffix in their names. Performing actions may result in callbacks being invoked as described above.

To prevent conflicts between extensions competing for shared state, such as the current profile or prefs, the host must not expose methods that directly modify that state. For example, instead of allowing extensions to switch profiles at-will, the host's ProfileServices provides a method to switch to the "best" profile. The host can then consult extensions to determine the appropriate profile to use and resolve any conflicts in a controlled manner.

A host must be safe for concurrent use.

type NewControlClientCallback

type NewControlClientCallback func(controlclient.Client, ipn.LoginProfileView) (cleanup func())

NewControlClientCallback is a function to be called when a new controlclient.Client is created and before it is first used. The specified profile represents the node for which the cc is created and is always valid. Its ipn.LoginProfileView.ID returns "" if it is a new node whose profile has never been persisted.

If the controlclient.Client is created due to a profile switch, any registered [ProfileStateChangeCallback]s are called first.

It returns a function to be called when the cc is being shut down, or nil if no cleanup is needed.

type NewExtensionFn

type NewExtensionFn func(logger.Logf, SafeBackend) (Extension, error)

NewExtensionFn is a function that instantiates an Extension. If a registered extension cannot be instantiated, the function must return an error. If the extension should be skipped at runtime, it must return either SkipExtension or a wrapped SkipExtension. Any other error returned is fatal and will prevent the LocalBackend from starting.

type NodeBackend

type NodeBackend interface {
	// AppendMatchingPeers appends all peers that match the predicate
	// to the base slice and returns it.
	AppendMatchingPeers(base []tailcfg.NodeView, pred func(tailcfg.NodeView) bool) []tailcfg.NodeView

	// PeerCaps returns the capabilities that src has to this node.
	PeerCaps(src netip.Addr) tailcfg.PeerCapMap

	// PeerHasCap reports whether the peer has the specified peer capability.
	PeerHasCap(peer tailcfg.NodeView, cap tailcfg.PeerCapability) bool

	// PeerAPIBase returns the "http://ip:port" URL base to reach peer's
	// PeerAPI, or the empty string if the peer is invalid or doesn't support
	// PeerAPI.
	PeerAPIBase(tailcfg.NodeView) string

	// PeerHasPeerAPI whether the provided peer supports PeerAPI.
	//
	// It effectively just reports whether PeerAPIBase(node) is non-empty, but
	// potentially more efficiently.
	PeerHasPeerAPI(tailcfg.NodeView) bool
}

NodeBackend is an interface to query the current node and its peers.

It is not a snapshot in time but is locked to a particular node.

type ProfileResolver

type ProfileResolver func(ProfileStore) ipn.LoginProfileView

ProfileResolver is a function that returns a read-only view of a login profile. An invalid view indicates no profile. A valid profile view with an empty ipn.ProfileID indicates that the profile is new and has not been persisted yet. The provided ProfileStore can only be used for the duration of the callback.

type ProfileServices

type ProfileServices interface {
	// CurrentProfileState returns read-only views of the current profile
	// and its preferences. The returned views are always valid,
	// but the profile's [ipn.LoginProfileView.ID] returns ""
	// if the profile is new and has not been persisted yet.
	//
	// The returned views are immutable snapshots of the current profile
	// and prefs at the time of the call. The actual state is only guaranteed
	// to remain unchanged and match these views for the duration
	// of a callback invoked by the host, if used within that callback.
	//
	// Extensions that need the current profile or prefs at other times
	// should typically subscribe to [ProfileStateChangeCallback]
	// to be notified if the profile or prefs change after retrieval.
	// CurrentProfileState returns both the profile and prefs
	// to guarantee that they are consistent with each other.
	CurrentProfileState() (ipn.LoginProfileView, ipn.PrefsView)

	// CurrentPrefs is like [CurrentProfileState] but only returns prefs.
	CurrentPrefs() ipn.PrefsView

	// SwitchToBestProfileAsync asynchronously selects the best profile to use
	// and switches to it, unless it is already the current profile.
	//
	// If an extension needs to know when a profile switch occurs,
	// it must use [ProfileServices.RegisterProfileStateChangeCallback]
	// to register a [ProfileStateChangeCallback].
	//
	// The reason indicates why the profile is being switched, such as due
	// to a client connecting or disconnecting or a change in the desktop
	// session state. It is used for logging.
	SwitchToBestProfileAsync(reason string)
}

ProfileServices provides access to the Host's profile management services, such as switching profiles and registering profile change callbacks.

type ProfileStateChangeCallback

type ProfileStateChangeCallback func(_ ipn.LoginProfileView, _ ipn.PrefsView, sameNode bool)

ProfileStateChangeCallback is a function to be called when the current login profile or its preferences change.

The sameNode parameter indicates whether the profile represents the same node as before, which is true when:

It can be used to decide whether to reset state bound to the current profile or node identity.

The profile and prefs are always valid, but the profile's ipn.LoginProfileView.ID returns "" if the profile is new and has not been persisted yet.

type ProfileStore

type ProfileStore interface {
	// CurrentUserID returns the current user ID. It is only non-empty on
	// Windows where we have a multi-user system.
	//
	// Deprecated: this method exists for compatibility with the current (as of 2024-08-27)
	// permission model and will be removed as we progress on tailscale/corp#18342.
	CurrentUserID() ipn.WindowsUserID

	// CurrentProfile returns a read-only [ipn.LoginProfileView] of the current profile.
	// The returned view is always valid, but the profile's [ipn.LoginProfileView.ID]
	// returns "" if the profile is new and has not been persisted yet.
	CurrentProfile() ipn.LoginProfileView

	// CurrentPrefs returns a read-only view of the current prefs.
	// The returned view is always valid.
	CurrentPrefs() ipn.PrefsView

	// DefaultUserProfile returns a read-only view of the default (last used) profile for the specified user.
	// It returns a read-only view of a new, non-persisted profile if the specified user does not have a default profile.
	DefaultUserProfile(uid ipn.WindowsUserID) ipn.LoginProfileView
}

ProfileStore provides read-only access to available login profiles and their preferences. It is not safe for concurrent use and can only be used from the callback it is passed to.

type SafeBackend

type SafeBackend interface {
	Sys() *tsd.System
	Clock() tstime.Clock
	TailscaleVarRoot() string
}

SafeBackend is a subset of the [ipnlocal.LocalBackend] type's methods that are safe to call from extension hooks at any time (even hooks called while LocalBackend's internal mutex is held).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL