559 lines
25 KiB
Go
559 lines
25 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
||
// SPDX-License-Identifier: BSD-3-Clause
|
||
|
||
// Package ipnext defines types and interfaces used for extending the core LocalBackend
|
||
// functionality with additional features and services.
|
||
package ipnext
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"iter"
|
||
"net/netip"
|
||
|
||
"tailscale.com/control/controlclient"
|
||
"tailscale.com/feature"
|
||
"tailscale.com/ipn"
|
||
"tailscale.com/ipn/ipnauth"
|
||
"tailscale.com/ipn/ipnstate"
|
||
"tailscale.com/tailcfg"
|
||
"tailscale.com/tailcfg/peercap"
|
||
"tailscale.com/tsd"
|
||
"tailscale.com/tstime"
|
||
"tailscale.com/types/key"
|
||
"tailscale.com/types/logger"
|
||
"tailscale.com/types/mapx"
|
||
"tailscale.com/types/netmap"
|
||
"tailscale.com/types/views"
|
||
"tailscale.com/wgengine/filter"
|
||
)
|
||
|
||
// 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 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
|
||
}
|
||
|
||
// 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 NewExtensionFn func(logger.Logf, SafeBackend) (Extension, error)
|
||
|
||
// 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.
|
||
var SkipExtension = errors.New("skipping extension")
|
||
|
||
// Definition describes a registered [Extension].
|
||
type Definition struct {
|
||
name string // name under which the extension is registered
|
||
newFn NewExtensionFn // function that creates a new instance of the extension
|
||
}
|
||
|
||
// Name returns the name of the extension.
|
||
func (d *Definition) Name() string {
|
||
return d.name
|
||
}
|
||
|
||
// MakeExtension instantiates the extension.
|
||
func (d *Definition) MakeExtension(logf logger.Logf, sb SafeBackend) (Extension, error) {
|
||
ext, err := d.newFn(logf, sb)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if ext.Name() != d.name {
|
||
return nil, fmt.Errorf("extension name mismatch: registered %q; actual %q", d.name, ext.Name())
|
||
}
|
||
return ext, nil
|
||
}
|
||
|
||
// extensions is a map of registered extensions,
|
||
// where the key is the name of the extension.
|
||
var extensions mapx.OrderedMap[string, *Definition]
|
||
|
||
// 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.
|
||
//
|
||
// As a backstop for feature packages that forget to consult
|
||
// feature.Register, it does nothing if the named feature was disabled
|
||
// via the TS_DISABLE_FEATURE environment variable.
|
||
func RegisterExtension(name string, newExt NewExtensionFn) {
|
||
if feature.Disabled(name) {
|
||
return
|
||
}
|
||
if newExt == nil {
|
||
panic(fmt.Sprintf("ipnext: newExt is nil: %q", name))
|
||
}
|
||
if extensions.Contains(name) {
|
||
panic(fmt.Sprintf("ipnext: duplicate extension name %q", name))
|
||
}
|
||
extensions.Set(name, &Definition{name, newExt})
|
||
}
|
||
|
||
// Extensions iterates over the extensions in the order they were registered
|
||
// via [RegisterExtension].
|
||
func Extensions() iter.Seq[*Definition] {
|
||
return extensions.Values()
|
||
}
|
||
|
||
// 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 DefinitionForTest(ext Extension) *Definition {
|
||
return &Definition{
|
||
name: ext.Name(),
|
||
newFn: func(logger.Logf, SafeBackend) (Extension, error) { return ext, nil },
|
||
}
|
||
}
|
||
|
||
// DefinitionWithErrForTest returns a [Definition] with the specified extension name
|
||
// whose [Definition.MakeExtension] method returns the specified error.
|
||
// It is used for testing.
|
||
func DefinitionWithErrForTest(name string, err error) *Definition {
|
||
return &Definition{
|
||
name: name,
|
||
newFn: func(logger.Logf, SafeBackend) (Extension, error) { return nil, err },
|
||
}
|
||
}
|
||
|
||
// 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 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
|
||
|
||
// AuthReconfigAsync asynchronously pushes a new configuration into wgengine,
|
||
// if engine updates are not currently blocked, based on the cached netmap and
|
||
// user prefs. The reconfiguration is applied to [ipnlocal.LocalBackend]'s currently
|
||
// active node at the time of execution.
|
||
//
|
||
// AuthReconfigAsync should not be called at a high rate (i.e., more often
|
||
// than prefs and netmap changes), except in experimental or proof-of-concept
|
||
// contexts, since reconfiguration is known to be slow.
|
||
AuthReconfigAsync()
|
||
}
|
||
|
||
// 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).
|
||
type SafeBackend interface {
|
||
Sys() *tsd.System
|
||
Clock() tstime.Clock
|
||
TailscaleVarRoot() string
|
||
}
|
||
|
||
// NotifyWatcher is a subset of [tailscale.com/ipn/ipnlocal.LocalBackend]
|
||
// for extensions that subscribe to the IPN notification bus from within tailscaled.
|
||
//
|
||
// Unlike [SafeBackend], its methods acquire LocalBackend’s internal mutex
|
||
// and must not be called from extension hooks,
|
||
// instead call them from a goroutine started by [Extension.Init].
|
||
type NotifyWatcher interface {
|
||
// WatchNotifications subscribes to the ipn.Notify message bus notification
|
||
// messages.
|
||
//
|
||
// WatchNotifications blocks until ctx is done.
|
||
//
|
||
// The provided onWatchAdded, if non-nil, will be called once the watcher
|
||
// is installed.
|
||
//
|
||
// The provided fn will be called for each notification. It will only be
|
||
// called with non-nil pointers. The caller must not modify roNotify. If
|
||
// fn returns false, the watch also stops.
|
||
//
|
||
// Failure to consume many notifications in a row will result in one final
|
||
// notification with ErrMessage set, followed by the watch closing, unless mask
|
||
// includes ipn.NotifyInProcessNoDisconnect. Watchers using
|
||
// NotifyInProcessNoDisconnect must not call back into LocalBackend from fn or
|
||
// wait on work that might call back into LocalBackend.
|
||
WatchNotifications(ctx context.Context, mask ipn.NotifyWatchOpt, onWatchAdded func(), fn func(roNotify *ipn.Notify) (keepGoing bool))
|
||
}
|
||
|
||
// ExtensionServices provides access to the [Host]'s extension management services,
|
||
// such as fetching active extensions.
|
||
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
|
||
}
|
||
|
||
// ProfileServices provides access to the [Host]'s profile management services,
|
||
// such as switching profiles and registering profile change callbacks.
|
||
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)
|
||
}
|
||
|
||
// 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 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
|
||
}
|
||
|
||
// AuditLogProvider is a function that returns an [ipnauth.AuditLogFunc] for
|
||
// logging auditable actions.
|
||
type AuditLogProvider func() ipnauth.AuditLogFunc
|
||
|
||
// 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 ProfileResolver func(ProfileStore) ipn.LoginProfileView
|
||
|
||
// 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:
|
||
// - Only the profile's [ipn.Prefs] or metadata (e.g., [tailcfg.UserProfile]) have changed,
|
||
// but the node ID and [ipn.ProfileID] remain the same.
|
||
// - The profile has been persisted and assigned an [ipn.ProfileID] for the first time,
|
||
// so while its node ID and [ipn.ProfileID] have changed, it is still the same profile.
|
||
//
|
||
// 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 ProfileStateChangeCallback func(_ ipn.LoginProfileView, _ ipn.PrefsView, sameNode bool)
|
||
|
||
// 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. That cleanup function should not call
|
||
// back into LocalBackend, which may be locked during shutdown.
|
||
type NewControlClientCallback func(controlclient.Client, ipn.LoginProfileView) (cleanup func())
|
||
|
||
// 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 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]
|
||
|
||
// OnNetMapToggle is called (with LocalBackend.mu held) when the network map
|
||
// is toggled from nil to non-nil, or non-nil to nil. This usually happens
|
||
// when the client connects to the control plane and receives the initial MapResponse,
|
||
// or when the client disconnects and the network map is cleared.
|
||
OnNetMapToggle feature.Hooks[func(*netmap.NetworkMap)]
|
||
|
||
// 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)]
|
||
|
||
// ShouldUploadServices reports whether this node should include services
|
||
// in Hostinfo from the portlist extension.
|
||
ShouldUploadServices feature.Hook[func() bool]
|
||
|
||
// Filter contains hooks for the packet filter.
|
||
// See [filter.Filter] for details on how these hooks are invoked.
|
||
Filter FilterHooks
|
||
|
||
// ExtraWireGuardAllowedIPs is called with a sequence of peers whose
|
||
// extra AllowedIPs the caller wants (re)computed, and returns
|
||
// prefixes to append to those peers' AllowedIPs, keyed by node ID.
|
||
//
|
||
// The sequence is not necessarily all peers: callers may pass any
|
||
// subset (such as only the peers changed by a netmap delta), and
|
||
// the returned map's meaning is scoped to the peers presented. A
|
||
// peer absent from the returned map has no extra AllowedIPs. As of
|
||
// 2026-07-15 the only caller passes all current peers on each
|
||
// reconfig, but extensions must not rely on that.
|
||
//
|
||
// An extension with nothing to add should return nil without
|
||
// iterating peers; that keeps steady-state netmap deltas free of
|
||
// per-peer work when the extension is idle. The peers sequence is
|
||
// only valid during the call.
|
||
//
|
||
// The extra AllowedIPs are given to WireGuard, but not the OS
|
||
// routing table.
|
||
//
|
||
// The returned prefixes should not contain duplicates, either
|
||
// internally, or with netmap peer prefixes. They should only
|
||
// contain host routes, and not contain default or subnet routes.
|
||
// Subsequent calls that return an unchanged set of prefixes for a
|
||
// given peer should return the prefixes in the same order for that
|
||
// peer, to prevent configuration churn.
|
||
//
|
||
// The returned map and slices should not be mutated by the
|
||
// extension after they are returned.
|
||
//
|
||
// The hook is called with LocalBackend's mutex locked.
|
||
//
|
||
// TODO(#17858): This hook may not be needed and can possibly be replaced by
|
||
// new hooks that fit into the new architecture that make use of new
|
||
// WireGuard APIs.
|
||
ExtraWireGuardAllowedIPs feature.Hook[func(peers iter.Seq2[tailcfg.NodeID, key.NodePublic]) map[tailcfg.NodeID][]netip.Prefix]
|
||
|
||
// ExtraRouterConfigRoutes returns a view of prefixes to append to [router.Config.Routes].
|
||
//
|
||
// Routes goes through the WireGuard engine which makes efforts to avoid
|
||
// unnecessary reconfiguration by checking that things have actually changed.
|
||
// So implementors should make sure that the order of the prefixes is stable
|
||
// and that we don't have duplicate entries.
|
||
//
|
||
// The returned slice should not be mutated by the extension after it is returned.
|
||
//
|
||
// The hook is called with LocalBackend's mutex locked.
|
||
ExtraRouterConfigRoutes feature.Hook[func() views.Slice[netip.Prefix]]
|
||
}
|
||
|
||
// FilterHooks contains hooks that extensions can use to customize the packet
|
||
// filter. Field names match the corresponding fields in filter.Filter.
|
||
type FilterHooks struct {
|
||
// IngressAllowHooks are hooks that allow extensions to accept inbound
|
||
// packets beyond the standard filter rules. Packets that are not dropped
|
||
// by the direction-agnostic pre-check, but would be not accepted by the
|
||
// main filter rules, including the check for destinations in the node's
|
||
// local IP set, will be accepted if they match one of these hooks.
|
||
// As of 2026-02-24, the ingress filter does not implement explicit drop
|
||
// rules, but if it does, an explicitly dropped packet will be dropped,
|
||
// and these hooks will not be evaluated.
|
||
//
|
||
// Processing of hooks stop after the first one that returns true.
|
||
// The returned why string of the first match is used in logging.
|
||
// Returning false does not drop the packet.
|
||
// See also [filter.Filter.IngressAllowHooks].
|
||
IngressAllowHooks feature.Hooks[filter.PacketMatch]
|
||
|
||
// LinkLocalAllowHooks are hooks that provide exceptions to the default
|
||
// policy of dropping link-local unicast packets. They run inside the
|
||
// direction-agnostic pre-checks for both ingress and egress.
|
||
//
|
||
// A hook can allow a link-local packet to pass the link-local check,
|
||
// but the packet is still subject to all other filter rules, and could be
|
||
// dropped elsewhere. Matching link-local packets are not logged.
|
||
// See also [filter.Filter.LinkLocalAllowHooks].
|
||
LinkLocalAllowHooks feature.Hooks[filter.PacketMatch]
|
||
}
|
||
|
||
// 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 NodeBackend interface {
|
||
// Self returns the current node.
|
||
Self() tailcfg.NodeView
|
||
|
||
// 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
|
||
|
||
// Peers returns all the current peers.
|
||
Peers() []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 peercap.Cap) 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
|
||
|
||
// CollectServices reports whether the control plane is telling this
|
||
// node that the portlist service collection is desirable, should it
|
||
// choose to report them.
|
||
CollectServices() bool
|
||
|
||
// DERPMap returns the current DERP map from the current netmap,
|
||
// or nil if there is no netmap.
|
||
DERPMap() *tailcfg.DERPMap
|
||
}
|