bridge

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: Apache-2.0 Imports: 30 Imported by: 0

Documentation

Overview

Package bridge provides bridge mode functionality including user access integration.

Index

Constants

View Source
const (
	DefaultRelayListenPort  = 51821
	DefaultMaxRelaySessions = 100
	DefaultSessionTTL       = 5 * time.Minute

	DefaultUserAccessInterfaceName = "wg-access"
	DefaultUserAccessListenPort    = 51822
	DefaultMaxAccessPeers          = 50

	DefaultMaxIngressRules    = 20
	DefaultIngressDialTimeout = 10 * time.Second

	DefaultSiteToSiteInterfacePrefix = "wg-s2s-"
	DefaultSiteToSiteListenPort      = 51823
	DefaultMaxSiteToSiteTunnels      = 10
)
View Source
const DefaultTunnelConfigDir = "/run/plexd/tunnels"

DefaultTunnelConfigDir is the default directory used for tunnel config files. It is isolated from /tmp to prevent other users from reading PSK material.

Variables

This section is empty.

Functions

func BoolPtr

func BoolPtr(v bool) *bool

BoolPtr returns a pointer to the given bool value.

func HandleBridgeConfigUpdated

func HandleBridgeConfigUpdated(trigger ReconcileTrigger) api.EventHandler

HandleBridgeConfigUpdated returns an api.EventHandler that triggers reconciliation when a bridge_config_updated SSE event is received. Follows the HandlePolicyUpdated pattern: payload is ignored, reconcile cycle will fetch the full desired state.

func HandleIngressConfigUpdated

func HandleIngressConfigUpdated(trigger ReconcileTrigger) api.EventHandler

HandleIngressConfigUpdated returns an api.EventHandler that triggers reconciliation when an ingress_config_updated SSE event is received. Follows the HandleBridgeConfigUpdated pattern: payload is ignored, reconcile cycle will fetch the full desired state.

func HandleIngressRuleAssigned

func HandleIngressRuleAssigned(mgr *IngressManager, logger *slog.Logger) api.EventHandler

HandleIngressRuleAssigned returns an api.EventHandler that adds an ingress rule when an ingress_rule_assigned SSE event is received.

func HandleIngressRuleRevoked

func HandleIngressRuleRevoked(mgr *IngressManager, logger *slog.Logger) api.EventHandler

HandleIngressRuleRevoked returns an api.EventHandler that removes an ingress rule when an ingress_rule_revoked SSE event is received.

func HandleRelaySessionAssigned

func HandleRelaySessionAssigned(relay *Relay, logger *slog.Logger) api.EventHandler

HandleRelaySessionAssigned returns an api.EventHandler that adds a relay session when a relay_session_assigned SSE event is received.

func HandleRelaySessionRevoked

func HandleRelaySessionRevoked(relay *Relay, logger *slog.Logger) api.EventHandler

HandleRelaySessionRevoked returns an api.EventHandler that removes a relay session when a relay_session_revoked SSE event is received.

func HandleSiteToSiteConfigUpdated

func HandleSiteToSiteConfigUpdated(trigger ReconcileTrigger) api.EventHandler

HandleSiteToSiteConfigUpdated returns an api.EventHandler that triggers reconciliation when a site_to_site_config_updated SSE event is received. Follows the HandleBridgeConfigUpdated pattern: payload is ignored, reconcile cycle will fetch the full desired state.

func HandleSiteToSiteTunnelAssigned

func HandleSiteToSiteTunnelAssigned(mgr *SiteToSiteManager, logger *slog.Logger) api.EventHandler

HandleSiteToSiteTunnelAssigned returns an api.EventHandler that adds a site-to-site tunnel when a site_to_site_tunnel_assigned SSE event is received.

func HandleSiteToSiteTunnelRevoked

func HandleSiteToSiteTunnelRevoked(mgr *SiteToSiteManager, logger *slog.Logger) api.EventHandler

HandleSiteToSiteTunnelRevoked returns an api.EventHandler that removes a site-to-site tunnel when a site_to_site_tunnel_revoked SSE event is received.

func HandleUserAccessConfigUpdated

func HandleUserAccessConfigUpdated(trigger ReconcileTrigger) api.EventHandler

HandleUserAccessConfigUpdated returns an api.EventHandler that triggers reconciliation when a user_access_config_updated SSE event is received. Follows the HandleBridgeConfigUpdated pattern: payload is ignored, reconcile cycle will fetch the full desired state.

func HandleUserAccessPeerAssigned

func HandleUserAccessPeerAssigned(mgr *UserAccessManager, logger *slog.Logger) api.EventHandler

HandleUserAccessPeerAssigned returns an api.EventHandler that adds a user access peer when a user_access_peer_assigned SSE event is received.

func HandleUserAccessPeerRevoked

func HandleUserAccessPeerRevoked(mgr *UserAccessManager, logger *slog.Logger) api.EventHandler

HandleUserAccessPeerRevoked returns an api.EventHandler that removes a user access peer when a user_access_peer_revoked SSE event is received.

func IngressReconcileHandler

func IngressReconcileHandler(mgr *IngressManager, logger *slog.Logger) reconcile.ReconcileHandler

IngressReconcileHandler returns a reconcile.ReconcileHandler that updates ingress rules when the desired IngressConfig changes. It diffs the desired rules against the currently active rules: adding missing rules, removing stale rules, and restarting changed rules (same ID, different config).

func ReconcileHandler

func ReconcileHandler(mgr *Manager) reconcile.ReconcileHandler

ReconcileHandler returns a reconcile.ReconcileHandler that updates bridge routes when the desired BridgeConfig changes. If BridgeConfig is nil in the desired state, the handler is a no-op.

func RelayReconcileHandler

func RelayReconcileHandler(relay *Relay, logger *slog.Logger) reconcile.ReconcileHandler

RelayReconcileHandler returns a reconcile.ReconcileHandler that reconciles relay sessions to match the desired RelayConfig. Sessions not in the desired state are removed; missing sessions are added.

func SiteToSiteReconcileHandler

func SiteToSiteReconcileHandler(mgr *SiteToSiteManager, logger *slog.Logger) reconcile.ReconcileHandler

SiteToSiteReconcileHandler returns a reconcile.ReconcileHandler that updates site-to-site tunnels when the desired SiteToSiteConfig changes. It diffs the desired tunnels against the currently active tunnels: adding missing tunnels, removing stale tunnels, and restarting changed tunnels (same ID, different config).

func UserAccessReconcileHandler

func UserAccessReconcileHandler(mgr *UserAccessManager, logger *slog.Logger) reconcile.ReconcileHandler

UserAccessReconcileHandler returns a reconcile.ReconcileHandler that updates user access peers when the desired UserAccessConfig changes. It diffs the desired peers against the currently active peers, adding missing and removing stale peers.

func ValidateTunnelConfig

func ValidateTunnelConfig(cfg TunnelConfig) error

ValidateTunnelConfig validates all fields of a TunnelConfig before they are interpolated into config files. This prevents newline injection and config injection in strongSwan/OpenVPN configurations.

func ValidateTunnelID

func ValidateTunnelID(id string) error

ValidateTunnelID checks that a tunnel ID is safe for use in file paths and shell arguments. Returns an error if the ID is invalid.

Types

type ACMEConfig

type ACMEConfig struct {
	// Enabled controls whether ACME certificate management is active.
	Enabled bool
	// CacheDir is the directory for caching certificates on disk.
	// Used by autocert.DirCache.
	CacheDir string
	// AllowedHosts is the whitelist of hostnames for which certificates may be obtained.
	AllowedHosts []string
	// Email is the contact email for the ACME account (optional, used in registration).
	Email string
	// ACMEDirectoryURL overrides the default ACME directory (e.g., for Let's Encrypt staging).
	// Empty means use the default (Let's Encrypt production).
	ACMEDirectoryURL string
}

ACMEConfig holds configuration for automatic TLS certificate management via the ACME protocol (e.g., Let's Encrypt).

type ACMEManager

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

ACMEManager manages automatic TLS certificate acquisition and renewal via the ACME protocol. It wraps autocert.Manager with DirCache, HostWhitelist, and certificate lifecycle management. ACMEManager is concurrent-safe via mu.

func NewACMEManager

func NewACMEManager(cfg ACMEConfig, logger *slog.Logger) *ACMEManager

NewACMEManager creates a new ACMEManager.

func (*ACMEManager) Active

func (m *ACMEManager) Active() bool

Active reports whether the ACME manager is currently active.

func (*ACMEManager) AllowedHosts

func (m *ACMEManager) AllowedHosts() []string

AllowedHosts returns a copy of the allowed hosts slice.

func (*ACMEManager) GetCertificate

func (m *ACMEManager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)

GetCertificate delegates to the underlying autocert.Manager's GetCertificate. Returns an error if the manager is not active.

func (*ACMEManager) Setup

func (m *ACMEManager) Setup() error

Setup initializes the ACME manager and creates the underlying autocert.Manager. When ACME is disabled this is a no-op.

func (*ACMEManager) TLSConfig

func (m *ACMEManager) TLSConfig() *tls.Config

TLSConfig returns a *tls.Config with GetCertificate set to the autocert manager's GetCertificate. Returns nil if the manager is not active. The returned config sets MinVersion to TLS 1.2.

func (*ACMEManager) Teardown

func (m *ACMEManager) Teardown() error

Teardown performs idempotent cleanup of the ACME manager. Calling Teardown when inactive returns nil.

type AccessController

type AccessController interface {
	// CreateInterface creates a WireGuard interface with the given name and listen port.
	// Idempotent: creating an already-existing interface with the same config returns nil.
	CreateInterface(name string, listenPort int) error

	// RemoveInterface removes the WireGuard interface with the given name.
	// Idempotent: removing a non-existent interface returns nil.
	RemoveInterface(name string) error

	// ConfigurePeer adds or updates a peer on the given WireGuard interface.
	// Idempotent: re-applying the same peer config returns nil.
	ConfigurePeer(iface string, publicKey string, allowedIPs []string, psk string) error

	// RemovePeer removes a peer from the given WireGuard interface by public key.
	// Idempotent: removing a non-existent peer returns nil.
	RemovePeer(iface string, publicKey string) error
}

AccessController abstracts WireGuard interface operations for user access testability. All methods must be idempotent: repeating an operation that is already applied returns nil.

type CommandExecutor

type CommandExecutor interface {
	// Run executes a command and returns its combined output.
	Run(ctx context.Context, name string, args ...string) ([]byte, error)
	// Start starts a command without waiting for it to complete.
	// Returns a CommandHandle that can be used to stop the process.
	Start(ctx context.Context, name string, args ...string) (CommandHandle, error)
}

CommandExecutor abstracts command execution for testability.

type CommandHandle

type CommandHandle interface {
	// Stop terminates the process.
	Stop() error
}

CommandHandle represents a running process.

type Config

type Config struct {
	// Enabled controls whether bridge mode is active.
	// Default: false
	Enabled bool `yaml:"enabled"`

	// AccessInterface is the name of the access-side network interface.
	AccessInterface string `yaml:"access_interface"`

	// AccessSubnets are the CIDR subnets reachable via the access-side interface.
	AccessSubnets []string `yaml:"access_subnets"`

	// EnableNAT controls whether NAT masquerading is applied on the access-side interface.
	// nil means use default (true); explicit false disables NAT.
	EnableNAT *bool `yaml:"enable_nat"`

	// RelayEnabled controls whether the bridge node serves as a relay.
	// Default: false. Requires Enabled=true.
	RelayEnabled bool `yaml:"relay_enabled"`

	// RelayListenPort is the UDP port for relay traffic.
	// Default: 51821
	RelayListenPort int `yaml:"relay_listen_port"`

	// MaxRelaySessions is the maximum number of concurrent relay sessions.
	// Default: 100
	MaxRelaySessions int `yaml:"max_relay_sessions"`

	// SessionTTL is the time-to-live for relay sessions.
	// Default: 5m. Minimum: 30s.
	SessionTTL time.Duration `yaml:"session_ttl"`

	// UserAccessEnabled controls whether user access integration is active.
	// Default: false. Requires Enabled=true.
	UserAccessEnabled bool `yaml:"user_access_enabled"`

	// UserAccessInterfaceName is the name of the WireGuard interface for user access.
	// Default: "wg-access"
	UserAccessInterfaceName string `yaml:"user_access_interface_name"`

	// UserAccessListenPort is the UDP port for the user access WireGuard interface.
	// Default: 51822
	UserAccessListenPort int `yaml:"user_access_listen_port"`

	// MaxAccessPeers is the maximum number of concurrent user access peers.
	// Default: 50
	MaxAccessPeers int `yaml:"max_access_peers"`

	// UserAccessProviderType selects the external VPN provider for user access.
	// Supported values: "tailscale", "netbird", or "" (disabled).
	UserAccessProviderType string `yaml:"user_access_provider_type"`

	// AuthKeyEnv is the environment variable name containing the auth key
	// for the user access provider (e.g. "PLEXD_TAILSCALE_AUTH_KEY").
	AuthKeyEnv string `yaml:"auth_key_env"`

	// SetupKeyEnv is the environment variable name containing the setup key
	// for the user access provider (e.g. "PLEXD_NETBIRD_SETUP_KEY").
	SetupKeyEnv string `yaml:"setup_key_env"`

	// IngressEnabled controls whether public ingress is active.
	// Default: false. Requires Enabled=true.
	IngressEnabled bool `yaml:"ingress_enabled"`

	// MaxIngressRules is the maximum number of concurrent ingress rules.
	// Default: 20
	MaxIngressRules int `yaml:"max_ingress_rules"`

	// IngressDialTimeout is the timeout for dialing target mesh peers.
	// Default: 10s. Minimum: 1s.
	IngressDialTimeout time.Duration `yaml:"ingress_dial_timeout"`

	// SiteToSiteEnabled controls whether site-to-site VPN connectivity is active.
	// Default: false. Requires Enabled=true.
	SiteToSiteEnabled bool `yaml:"site_to_site_enabled"`

	// SiteToSiteInterfacePrefix is the prefix for WireGuard interfaces used by site-to-site tunnels.
	// Each tunnel gets an interface named {prefix}{index}.
	// Default: "wg-s2s-"
	SiteToSiteInterfacePrefix string `yaml:"site_to_site_interface_prefix"`

	// SiteToSiteListenPort is the base UDP port for site-to-site WireGuard interfaces.
	// Each tunnel uses this port + tunnel index offset.
	// Default: 51823
	SiteToSiteListenPort int `yaml:"site_to_site_listen_port"`

	// MaxSiteToSiteTunnels is the maximum number of concurrent site-to-site tunnels.
	// Default: 10
	MaxSiteToSiteTunnels int `yaml:"max_site_to_site_tunnels"`

	// TunnelProviders lists the tunnel provider types to enable for site-to-site connectivity.
	// Supported values: "ipsec", "openvpn". Empty means WireGuard-only.
	TunnelProviders []string `yaml:"tunnel_providers"`

	// ACMEEnabled controls whether ACME certificate management is active for ingress.
	// Default: false. Requires IngressEnabled=true.
	ACMEEnabled bool `yaml:"acme_enabled"`

	// ACMECacheDir is the directory for caching ACME certificates.
	ACMECacheDir string `yaml:"acme_cache_dir"`

	// ACMEAllowedHosts is the whitelist of hostnames for ACME certificate issuance.
	ACMEAllowedHosts []string `yaml:"acme_allowed_hosts"`

	// ACMEEmail is the contact email for the ACME account.
	ACMEEmail string `yaml:"acme_email"`

	// ACMEDirectoryURL overrides the default ACME directory URL.
	// Empty means the default (Let's Encrypt production).
	ACMEDirectoryURL string `yaml:"acme_directory_url"`
}

Config holds the configuration for bridge mode. Config is passed as a constructor argument — no file I/O in this package.

func (*Config) ApplyDefaults

func (c *Config) ApplyDefaults()

ApplyDefaults sets default values for zero-valued fields.

func (*Config) Validate

func (c *Config) Validate() error

Validate checks that configuration values are acceptable. When bridge mode is disabled, validation is skipped.

type IPsecProvider

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

IPsecProvider wraps strongSwan (swanctl) to manage IPsec tunnels for site-to-site connectivity. All methods are safe for concurrent use.

func NewIPsecProvider

func NewIPsecProvider(exec CommandExecutor, routes RouteController, logger *slog.Logger) *IPsecProvider

NewIPsecProvider creates a new IPsecProvider.

func (*IPsecProvider) ActiveTunnels

func (p *IPsecProvider) ActiveTunnels() []string

ActiveTunnels returns the IDs of all active tunnels.

func (*IPsecProvider) CreateTunnel

func (p *IPsecProvider) CreateTunnel(ctx context.Context, cfg TunnelConfig) error

CreateTunnel establishes an IPsec tunnel via swanctl.

func (*IPsecProvider) Name

func (p *IPsecProvider) Name() string

Name returns the provider identifier.

func (*IPsecProvider) RemoveTunnel

func (p *IPsecProvider) RemoveTunnel(tunnelID string) error

RemoveTunnel tears down an IPsec tunnel. Idempotent.

func (*IPsecProvider) Stop

func (p *IPsecProvider) Stop() error

Stop gracefully shuts down all managed IPsec tunnels. Idempotent.

func (*IPsecProvider) TunnelStatus

func (p *IPsecProvider) TunnelStatus(tunnelID string) TunnelStatus

TunnelStatus returns the status of the given tunnel.

type IngressController

type IngressController interface {
	// Listen creates a TCP listener on the given address.
	// If tlsCfg is non-nil, the listener will accept TLS connections.
	// Returns the listener and any error.
	Listen(addr string, tlsCfg *tls.Config) (net.Listener, error)

	// Close closes the given listener.
	// Idempotent: closing an already-closed listener returns nil.
	Close(listener net.Listener) error
}

IngressController abstracts TCP listener operations for ingress testability. All methods must be idempotent where applicable.

type IngressManager

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

IngressManager manages public ingress — TCP listeners that proxy traffic to mesh-internal services via a bridge node. IngressManager is concurrent-safe via mu.

func NewIngressManager

func NewIngressManager(ctrl IngressController, cfg Config, logger *slog.Logger, acme *ACMEManager) *IngressManager

NewIngressManager creates a new IngressManager. The acme parameter is optional and may be nil.

func (*IngressManager) AddRule

func (m *IngressManager) AddRule(rule api.IngressRule) error

AddRule adds an ingress rule and starts a TCP listener for it. Returns an error if the manager is inactive, the rule ID already exists, or the maximum rule count is reached.

func (*IngressManager) GetRule

func (m *IngressManager) GetRule(ruleID string) (api.IngressRule, bool)

GetRule returns the IngressRule for the given ID and true if it exists, or a zero value and false otherwise.

func (*IngressManager) IngressCapabilities

func (m *IngressManager) IngressCapabilities() map[string]string

IngressCapabilities returns capability metadata for registration. Returns nil when ingress is not enabled.

func (*IngressManager) IngressStatus

func (m *IngressManager) IngressStatus() *api.IngressInfo

IngressStatus returns ingress status for heartbeat reporting. Returns nil when ingress is not active.

func (*IngressManager) RemoveRule

func (m *IngressManager) RemoveRule(ruleID string)

RemoveRule stops the listener for the given rule ID and removes it. Removing a non-existent rule or calling on an inactive manager is a no-op.

func (*IngressManager) RuleIDs

func (m *IngressManager) RuleIDs() []string

RuleIDs returns the IDs of all active rules.

func (*IngressManager) Setup

func (m *IngressManager) Setup() error

Setup initializes the ingress manager. When ingress is disabled this is a no-op.

func (*IngressManager) Teardown

func (m *IngressManager) Teardown() error

Teardown closes all active listeners and cancels proxy connections. Errors are aggregated — cleanup continues even when individual operations fail. Idempotent: calling Teardown when inactive returns nil.

type Manager

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

Manager manages bridge mode routing between the mesh and access-side interfaces. Manager is not concurrent-safe; it relies on serial invocation from the reconcile loop.

func NewManager

func NewManager(ctrl RouteController, cfg Config, logger *slog.Logger) *Manager

NewManager creates a new Manager. Config defaults are applied automatically.

func (*Manager) BridgeCapabilities

func (m *Manager) BridgeCapabilities() map[string]string

BridgeCapabilities returns bridge capability metadata for registration. Returns nil when bridge mode is disabled.

func (*Manager) BridgeStatus

func (m *Manager) BridgeStatus() *api.BridgeInfo

BridgeStatus returns bridge status for heartbeat reporting. Returns nil when bridge mode is not active.

func (*Manager) Relay

func (m *Manager) Relay() *Relay

Relay returns the relay instance, or nil if relay is not configured.

func (*Manager) Setup

func (m *Manager) Setup(meshIface string) error

Setup configures bridge mode routing: enables forwarding, adds routes, and optionally configures NAT masquerading. When bridge mode is disabled this is a no-op. On partial route failure, previously added routes in this call are rolled back.

func (*Manager) StartRelay

func (m *Manager) StartRelay(ctx context.Context) error

StartRelay starts the relay UDP listener. No-op if relay is not configured.

func (*Manager) StopRelay

func (m *Manager) StopRelay() error

StopRelay stops the relay. No-op if relay is not configured.

func (*Manager) Teardown

func (m *Manager) Teardown() error

Teardown removes all bridge-specific routing rules, NAT masquerading, and disables forwarding. Idempotent: calling Teardown when inactive returns nil. Errors are aggregated via errors.Join; cleanup continues even on failure.

func (*Manager) UpdateRoutes

func (m *Manager) UpdateRoutes(subnets []string) error

UpdateRoutes computes the diff between current active routes and the desired subnets, adding new and removing stale routes.

type NetbirdProvider

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

NetbirdProvider implements UserAccessProvider by wrapping the Netbird CLI. It joins a Netbird network using a setup key, enabling Netbird users to access mesh resources through the bridge node.

func NewNetbirdProvider

func NewNetbirdProvider(exec CommandExecutor, logger *slog.Logger) *NetbirdProvider

NewNetbirdProvider returns a new NetbirdProvider that uses the given CommandExecutor to invoke the netbird CLI.

func (*NetbirdProvider) Name

func (n *NetbirdProvider) Name() string

Name returns the provider identifier.

func (*NetbirdProvider) Start

func (n *NetbirdProvider) Start(ctx context.Context) error

Start launches the Netbird daemon by running `netbird up` with the setup key from the PLEXD_NETBIRD_SETUP_KEY environment variable, then queries `netbird status --json` to discover the assigned IP and interface name.

func (*NetbirdProvider) Status

func (n *NetbirdProvider) Status() ProviderStatus

Status returns the current provider status.

func (*NetbirdProvider) Stop

func (n *NetbirdProvider) Stop() error

Stop gracefully shuts down Netbird by running `netbird down`. Idempotent: calling Stop when not running returns nil.

type NetlinkAccessController

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

NetlinkAccessController implements AccessController using Linux netlink and wgctrl for managing WireGuard user-access interfaces.

func NewNetlinkAccessController

func NewNetlinkAccessController(logger *slog.Logger) *NetlinkAccessController

NewNetlinkAccessController returns a new NetlinkAccessController.

func (*NetlinkAccessController) ConfigurePeer

func (c *NetlinkAccessController) ConfigurePeer(iface, publicKey string, allowedIPs []string, psk string) error

ConfigurePeer adds or updates a peer on the access interface.

func (*NetlinkAccessController) CreateInterface

func (c *NetlinkAccessController) CreateInterface(name string, listenPort int) error

CreateInterface creates a WireGuard interface for user access.

func (*NetlinkAccessController) RemoveInterface

func (c *NetlinkAccessController) RemoveInterface(name string) error

RemoveInterface deletes the named WireGuard access interface. It is idempotent: removing a non-existent interface returns nil.

func (*NetlinkAccessController) RemovePeer

func (c *NetlinkAccessController) RemovePeer(iface, publicKey string) error

RemovePeer removes a peer from the access interface by public key.

type NetlinkRouteController

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

NetlinkRouteController implements RouteController using Linux netlink for route management, sysctl for IP forwarding, and nftables for NAT masquerade.

func NewNetlinkRouteController

func NewNetlinkRouteController(logger *slog.Logger) *NetlinkRouteController

NewNetlinkRouteController returns a new NetlinkRouteController.

func (*NetlinkRouteController) AddNATMasquerade

func (c *NetlinkRouteController) AddNATMasquerade(iface string) error

AddNATMasquerade configures NAT masquerading on the given interface using nftables. Creates a postrouting chain with a masquerade rule matching traffic on the interface. Idempotent: re-adding an existing masquerade is a no-op (table/chain are re-added atomically).

func (*NetlinkRouteController) AddRoute

func (c *NetlinkRouteController) AddRoute(subnet, iface string) error

AddRoute adds a route for the given CIDR subnet via the given interface. Idempotent: adding an existing route returns nil.

func (*NetlinkRouteController) DisableForwarding

func (c *NetlinkRouteController) DisableForwarding(meshIface, accessIface string) error

DisableForwarding disables IPv4 forwarding for the given interfaces via sysctl.

func (*NetlinkRouteController) EnableForwarding

func (c *NetlinkRouteController) EnableForwarding(meshIface, accessIface string) error

EnableForwarding enables IPv4 forwarding for the given interfaces via sysctl.

func (*NetlinkRouteController) RemoveNATMasquerade

func (c *NetlinkRouteController) RemoveNATMasquerade(iface string) error

RemoveNATMasquerade removes NAT masquerading from the given interface by deleting the plexd-nat nftables table. Idempotent: removing a non-existent table returns nil.

func (*NetlinkRouteController) RemoveRoute

func (c *NetlinkRouteController) RemoveRoute(subnet, iface string) error

RemoveRoute removes the route for the given CIDR subnet via the given interface. Idempotent: removing a non-existent route returns nil.

type NetlinkVPNController

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

NetlinkVPNController implements VPNController using Linux netlink and wgctrl for managing WireGuard site-to-site tunnel interfaces.

func NewNetlinkVPNController

func NewNetlinkVPNController(logger *slog.Logger) *NetlinkVPNController

NewNetlinkVPNController returns a new NetlinkVPNController.

func (*NetlinkVPNController) ConfigureTunnelPeer

func (c *NetlinkVPNController) ConfigureTunnelPeer(iface, publicKey string, allowedIPs []string, endpoint, psk string) error

ConfigureTunnelPeer adds or updates a peer on the tunnel interface.

func (*NetlinkVPNController) CreateTunnelInterface

func (c *NetlinkVPNController) CreateTunnelInterface(name string, listenPort int) error

CreateTunnelInterface creates a WireGuard interface for a site-to-site tunnel.

func (*NetlinkVPNController) RemoveTunnelInterface

func (c *NetlinkVPNController) RemoveTunnelInterface(name string) error

RemoveTunnelInterface deletes the named WireGuard tunnel interface. It is idempotent: removing a non-existent interface returns nil.

func (*NetlinkVPNController) RemoveTunnelPeer

func (c *NetlinkVPNController) RemoveTunnelPeer(iface, publicKey string) error

RemoveTunnelPeer removes a peer from the tunnel interface by public key.

type OpenVPNProvider

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

OpenVPNProvider wraps the OpenVPN CLI to manage OpenVPN tunnels for site-to-site connectivity. All methods are safe for concurrent use.

func NewOpenVPNProvider

func NewOpenVPNProvider(exec CommandExecutor, routes RouteController, logger *slog.Logger) *OpenVPNProvider

NewOpenVPNProvider creates a new OpenVPNProvider.

func (*OpenVPNProvider) ActiveTunnels

func (p *OpenVPNProvider) ActiveTunnels() []string

ActiveTunnels returns the IDs of all active tunnels.

func (*OpenVPNProvider) CreateTunnel

func (p *OpenVPNProvider) CreateTunnel(ctx context.Context, cfg TunnelConfig) error

CreateTunnel establishes an OpenVPN tunnel.

func (*OpenVPNProvider) Name

func (p *OpenVPNProvider) Name() string

Name returns the provider identifier.

func (*OpenVPNProvider) RemoveTunnel

func (p *OpenVPNProvider) RemoveTunnel(tunnelID string) error

RemoveTunnel tears down an OpenVPN tunnel. Idempotent.

func (*OpenVPNProvider) Stop

func (p *OpenVPNProvider) Stop() error

Stop gracefully shuts down all managed OpenVPN tunnels. Idempotent.

func (*OpenVPNProvider) TunnelStatus

func (p *OpenVPNProvider) TunnelStatus(tunnelID string) TunnelStatus

TunnelStatus returns the status of the given tunnel.

type ProviderStatus

type ProviderStatus struct {
	// Running indicates whether the provider process is active.
	Running bool `json:"running"`

	// InterfaceName is the network interface created by the provider.
	InterfaceName string `json:"interface_name,omitempty"`

	// IP is the IP address assigned by the provider.
	IP string `json:"ip,omitempty"`

	// Error holds the last error message, if any.
	Error string `json:"error,omitempty"`
}

ProviderStatus represents the current status of a user access VPN provider.

type ReconcileTrigger

type ReconcileTrigger interface {
	TriggerReconcile()
}

ReconcileTrigger is satisfied by *reconcile.Reconciler.

type Relay

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

Relay manages a UDP listener and relay sessions.

func NewRelay

func NewRelay(listenPort, maxSessions int, sessionTTL time.Duration, logger *slog.Logger) *Relay

NewRelay creates a new Relay.

func (*Relay) ActiveCount

func (r *Relay) ActiveCount() int

ActiveCount returns the number of active relay sessions.

func (*Relay) AddSession

func (r *Relay) AddSession(assignment api.RelaySessionAssignment) error

AddSession creates and registers a new relay session.

func (*Relay) ListenAddr

func (r *Relay) ListenAddr() net.Addr

ListenAddr returns the local address of the relay UDP listener. Returns nil if not started.

func (*Relay) RemoveSession

func (r *Relay) RemoveSession(sessionID string)

RemoveSession closes and removes a session by ID. No-op if not found.

func (*Relay) SessionIDs

func (r *Relay) SessionIDs() []string

SessionIDs returns the IDs of all active sessions.

func (*Relay) Start

func (r *Relay) Start(ctx context.Context) error

Start opens a UDP socket and begins the dispatch loop.

func (*Relay) Stop

func (r *Relay) Stop() error

Stop closes all sessions and the UDP listener. Idempotent.

type RelaySession

type RelaySession struct {
	SessionID string
	PeerAAddr *net.UDPAddr
	PeerBAddr *net.UDPAddr
	// contains filtered or unexported fields
}

RelaySession represents a relay session forwarding UDP packets between two peers.

func (*RelaySession) Close

func (s *RelaySession) Close() error

Close marks the session as closed. Idempotent.

func (*RelaySession) Forward

func (s *RelaySession) Forward(srcAddr *net.UDPAddr, data []byte)

Forward sends data to the peer that is NOT the source. If srcAddr matches PeerA, forward to PeerB and vice versa. Packets from unknown sources are dropped.

type RouteController

type RouteController interface {
	// EnableForwarding enables IP forwarding between the mesh and access interfaces.
	EnableForwarding(meshIface, accessIface string) error

	// DisableForwarding reverses the forwarding setup.
	DisableForwarding(meshIface, accessIface string) error

	// AddRoute adds a route for the given CIDR subnet via the given interface.
	// Idempotent: adding an existing route returns nil.
	AddRoute(subnet, iface string) error

	// RemoveRoute removes the route for the given CIDR subnet via the given interface.
	// Idempotent: removing a non-existent route returns nil.
	RemoveRoute(subnet, iface string) error

	// AddNATMasquerade configures NAT masquerading on the given interface.
	AddNATMasquerade(iface string) error

	// RemoveNATMasquerade removes NAT masquerading from the given interface.
	// Idempotent: removing non-existent masquerade returns nil.
	RemoveNATMasquerade(iface string) error
}

RouteController abstracts OS-level routing and forwarding operations for testability. All methods must be idempotent: repeating an operation that is already applied returns nil.

type SNIRoute

type SNIRoute struct {
	Hostname    string // SNI hostname to match (exact match, lowercase)
	BackendAddr string // TCP address to forward to (host:port)
}

SNIRoute maps an SNI hostname to a backend address.

type SNIRouter

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

SNIRouter accepts TCP connections, peeks at the TLS ClientHello to extract the SNI hostname, and routes connections to the appropriate backend. SNIRouter is concurrent-safe via mu.

func NewSNIRouter

func NewSNIRouter(dialTimeout time.Duration, logger *slog.Logger) *SNIRouter

NewSNIRouter creates a new SNIRouter.

func (*SNIRouter) Active

func (r *SNIRouter) Active() bool

Active returns whether the router is currently active.

func (*SNIRouter) AddRoute

func (r *SNIRouter) AddRoute(route SNIRoute) error

AddRoute adds an SNI route. If a route for the hostname already exists, it is overwritten.

func (*SNIRouter) ConnCount

func (r *SNIRouter) ConnCount() int64

ConnCount returns the number of active proxy connections.

func (*SNIRouter) RemoveRoute

func (r *SNIRouter) RemoveRoute(hostname string) error

RemoveRoute removes the route for the given hostname. Idempotent: removing a non-existent route returns nil.

func (*SNIRouter) Routes

func (r *SNIRouter) Routes() []SNIRoute

Routes returns a copy of all routes, sorted by hostname.

func (*SNIRouter) Start

func (r *SNIRouter) Start(ln net.Listener) error

Start begins accepting connections on the given listener.

func (*SNIRouter) Stop

func (r *SNIRouter) Stop() error

Stop stops the router. Idempotent: if not active, returns nil.

type SiteToSiteManager

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

SiteToSiteManager manages site-to-site VPN tunnels — WireGuard interfaces that establish VPN connections to external networks via a bridge node. SiteToSiteManager is concurrent-safe via mu.

func NewSiteToSiteManager

func NewSiteToSiteManager(ctrl VPNController, routes RouteController, cfg Config, logger *slog.Logger, tunnelProviders map[string]TunnelProvider) *SiteToSiteManager

NewSiteToSiteManager creates a new SiteToSiteManager. tunnelProviders can be nil (meaning WireGuard-only mode).

func (*SiteToSiteManager) AddTunnel

func (m *SiteToSiteManager) AddTunnel(tunnel api.SiteToSiteTunnel) error

AddTunnel establishes a site-to-site tunnel: creates a WireGuard interface, configures the remote peer, enables forwarding, and adds routes for remote subnets. Returns an error if the manager is inactive, the tunnel ID already exists, or the maximum tunnel count is reached.

func (*SiteToSiteManager) GetTunnel

func (m *SiteToSiteManager) GetTunnel(tunnelID string) (api.SiteToSiteTunnel, bool)

GetTunnel returns the SiteToSiteTunnel for the given ID and true if it exists, or a zero value and false otherwise.

func (*SiteToSiteManager) RemoveTunnel

func (m *SiteToSiteManager) RemoveTunnel(tunnelID string)

RemoveTunnel removes a site-to-site tunnel: removes routes, disables forwarding, removes the peer, and removes the interface. Removing a non-existent tunnel or calling on an inactive manager is a no-op.

func (*SiteToSiteManager) Setup

func (m *SiteToSiteManager) Setup(meshIface string) error

Setup initializes the site-to-site manager with the given mesh interface. When site-to-site is disabled this is a no-op.

func (*SiteToSiteManager) SiteToSiteCapabilities

func (m *SiteToSiteManager) SiteToSiteCapabilities() map[string]string

SiteToSiteCapabilities returns capability metadata for registration. Returns nil when site-to-site is not enabled.

func (*SiteToSiteManager) SiteToSiteStatus

func (m *SiteToSiteManager) SiteToSiteStatus() *api.SiteToSiteInfo

SiteToSiteStatus returns site-to-site status for heartbeat reporting. Returns nil when site-to-site is not active.

func (*SiteToSiteManager) Teardown

func (m *SiteToSiteManager) Teardown() error

Teardown removes all active tunnels, their routes, forwarding, and interfaces. Errors are aggregated — cleanup continues even when individual operations fail. Idempotent: calling Teardown when inactive returns nil.

func (*SiteToSiteManager) TunnelIDs

func (m *SiteToSiteManager) TunnelIDs() []string

TunnelIDs returns the IDs of all active tunnels.

type StdCommandExecutor

type StdCommandExecutor struct{}

StdCommandExecutor implements CommandExecutor using os/exec.

func NewStdCommandExecutor

func NewStdCommandExecutor() *StdCommandExecutor

NewStdCommandExecutor returns a new StdCommandExecutor.

func (*StdCommandExecutor) Run

func (e *StdCommandExecutor) Run(ctx context.Context, name string, args ...string) ([]byte, error)

Run executes the named command with the given arguments and returns its combined stdout/stderr output.

func (*StdCommandExecutor) Start

func (e *StdCommandExecutor) Start(ctx context.Context, name string, args ...string) (CommandHandle, error)

Start launches the named command asynchronously and returns a handle that can be used to stop it.

type StdIngressController

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

StdIngressController implements IngressController using the standard library net and crypto/tls packages. It is cross-platform.

func NewStdIngressController

func NewStdIngressController(logger *slog.Logger) *StdIngressController

NewStdIngressController returns a new StdIngressController.

func (*StdIngressController) Close

func (c *StdIngressController) Close(listener net.Listener) error

Close closes the given listener.

func (*StdIngressController) Listen

func (c *StdIngressController) Listen(addr string, tlsCfg *tls.Config) (net.Listener, error)

Listen opens a TCP listener on the given address. If tlsCfg is non-nil, the listener is wrapped with TLS.

type TailscaleProvider

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

TailscaleProvider wraps the Tailscale CLI (tailscale/tailscaled) to join a Tailscale network on a bridge node, enabling Tailscale users to access mesh resources. TailscaleProvider is concurrent-safe via mu.

func NewTailscaleProvider

func NewTailscaleProvider(exec CommandExecutor, logger *slog.Logger) *TailscaleProvider

NewTailscaleProvider creates a new TailscaleProvider.

func (*TailscaleProvider) Name

func (p *TailscaleProvider) Name() string

Name returns the provider identifier.

func (*TailscaleProvider) Start

func (p *TailscaleProvider) Start(ctx context.Context) error

Start launches tailscaled and joins the Tailscale network using an auth key read from the PLEXD_TAILSCALE_AUTH_KEY environment variable.

func (*TailscaleProvider) Status

func (p *TailscaleProvider) Status() ProviderStatus

Status returns the current provider status.

func (*TailscaleProvider) Stop

func (p *TailscaleProvider) Stop() error

Stop gracefully shuts down Tailscale and the daemon process. Idempotent: calling Stop when not running returns nil.

type TunnelConfig

type TunnelConfig struct {
	// TunnelID is the unique identifier for this tunnel.
	TunnelID string `json:"tunnel_id"`

	// LocalSubnets are the local CIDR subnets to expose through the tunnel.
	LocalSubnets []string `json:"local_subnets"`

	// RemoteSubnets are the remote CIDR subnets reachable through the tunnel.
	RemoteSubnets []string `json:"remote_subnets"`

	// RemoteEndpoint is the remote tunnel peer address (host:port or IP).
	RemoteEndpoint string `json:"remote_endpoint"`

	// PSK is the pre-shared key for tunnel authentication.
	PSK string `json:"psk"`

	// Extra holds provider-specific configuration parameters.
	Extra map[string]string `json:"extra,omitempty"`
}

TunnelConfig holds the configuration for creating a tunnel via a TunnelProvider.

type TunnelProvider

type TunnelProvider interface {
	// Name returns the provider identifier (e.g. "ipsec", "openvpn").
	Name() string

	// CreateTunnel establishes a tunnel with the given configuration.
	// The context controls the tunnel lifetime.
	// Returns an error if the tunnel cannot be created or already exists.
	CreateTunnel(ctx context.Context, cfg TunnelConfig) error

	// RemoveTunnel tears down the tunnel with the given ID.
	// Idempotent: removing a non-existent tunnel returns nil.
	RemoveTunnel(tunnelID string) error

	// TunnelStatus returns the status of the tunnel with the given ID.
	// Returns a zero-value TunnelStatus with Running=false if the tunnel
	// does not exist.
	TunnelStatus(tunnelID string) TunnelStatus

	// ActiveTunnels returns the IDs of all active tunnels.
	ActiveTunnels() []string

	// Stop gracefully shuts down all tunnels managed by this provider.
	// Idempotent: calling Stop when no tunnels are active returns nil.
	Stop() error
}

TunnelProvider abstracts lifecycle management of non-WireGuard tunnel technologies (IPsec, OpenVPN, etc.) used for site-to-site connectivity via a bridge node.

Implementations invoke the tunnel daemon/CLI to establish encrypted tunnels to external networks. The bridge manager uses TunnelProvider alongside the existing VPNController (WireGuard) to support heterogeneous site-to-site connectivity.

All methods must be safe for concurrent use.

type TunnelStatus

type TunnelStatus struct {
	// TunnelID is the unique identifier for this tunnel.
	TunnelID string `json:"tunnel_id"`

	// Running indicates whether the tunnel is active.
	Running bool `json:"running"`

	// InterfaceName is the network interface used by the tunnel.
	InterfaceName string `json:"interface_name,omitempty"`

	// Error holds the last error message, if any.
	Error string `json:"error,omitempty"`
}

TunnelStatus represents the current status of a tunnel managed by a TunnelProvider.

type UserAccessManager

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

UserAccessManager manages user access integration — a WireGuard interface that allows external VPN clients to reach the mesh via a bridge node. UserAccessManager is concurrent-safe via mu.

func NewUserAccessManager

func NewUserAccessManager(ctrl AccessController, routes RouteController, cfg Config, logger *slog.Logger, provider UserAccessProvider) *UserAccessManager

NewUserAccessManager creates a new UserAccessManager. The provider parameter is optional — pass nil when no external VPN provider is used.

func (*UserAccessManager) AddPeer

func (m *UserAccessManager) AddPeer(peer api.UserAccessPeer) error

AddPeer adds a single user access peer. Returns an error if the maximum number of peers has been reached or the peer is already tracked.

func (*UserAccessManager) PeerPublicKeys

func (m *UserAccessManager) PeerPublicKeys() []string

PeerPublicKeys returns the public keys of all active peers.

func (*UserAccessManager) RemovePeer

func (m *UserAccessManager) RemovePeer(publicKey string)

RemovePeer removes a single user access peer by public key. Removing a non-existent peer is a no-op.

func (*UserAccessManager) Setup

func (m *UserAccessManager) Setup() error

Setup creates the WireGuard interface for user access and enables forwarding. When user access is disabled this is a no-op.

func (*UserAccessManager) Teardown

func (m *UserAccessManager) Teardown() error

Teardown removes all tracked peers, disables forwarding, and removes the WireGuard interface. Errors are aggregated — cleanup continues even when individual operations fail. Idempotent: calling Teardown when inactive returns nil.

func (*UserAccessManager) UserAccessCapabilities

func (m *UserAccessManager) UserAccessCapabilities() map[string]string

UserAccessCapabilities returns capability metadata for registration. Returns nil when user access is not enabled.

func (*UserAccessManager) UserAccessStatus

func (m *UserAccessManager) UserAccessStatus() *api.UserAccessInfo

UserAccessStatus returns user access status for heartbeat reporting. Returns nil when user access is not active.

type UserAccessProvider

type UserAccessProvider interface {
	// Name returns the provider identifier (e.g. "tailscale", "netbird").
	Name() string

	// Start launches the provider daemon/process. The auth credential
	// (auth key, setup key) is read from the environment by the implementation.
	// The context controls the provider lifetime — cancelling it should trigger
	// a graceful stop.
	Start(ctx context.Context) error

	// Stop gracefully shuts down the provider and removes its network interface.
	// Idempotent: calling Stop when not running returns nil.
	Stop() error

	// Status returns the current provider status.
	Status() ProviderStatus
}

UserAccessProvider abstracts lifecycle management of external VPN providers (Tailscale, Netbird, etc.) used for user access into the mesh via a bridge node.

Implementations invoke the provider's CLI or daemon to join an overlay network, then report status so the bridge can route traffic between the provider's interface and the mesh.

All methods must be safe for concurrent use.

type VPNController

type VPNController interface {
	// CreateTunnelInterface creates a WireGuard interface for a site-to-site tunnel.
	// name is the interface name, listenPort is the UDP port.
	// Idempotent: creating an already-existing interface with the same config returns nil.
	CreateTunnelInterface(name string, listenPort int) error

	// RemoveTunnelInterface removes the WireGuard interface with the given name.
	// Idempotent: removing a non-existent interface returns nil.
	RemoveTunnelInterface(name string) error

	// ConfigureTunnelPeer configures the remote peer on the given tunnel interface.
	// publicKey is base64-encoded, allowedIPs are the remote subnets, endpoint is host:port.
	// psk may be empty if not used.
	// Idempotent: re-applying the same config returns nil.
	ConfigureTunnelPeer(iface string, publicKey string, allowedIPs []string, endpoint string, psk string) error

	// RemoveTunnelPeer removes the remote peer from the given tunnel interface.
	// Idempotent: removing a non-existent peer returns nil.
	RemoveTunnelPeer(iface string, publicKey string) error
}

VPNController abstracts OS-level WireGuard tunnel operations for site-to-site testability. All methods must be idempotent: repeating an operation that is already applied returns nil.

Jump to

Keyboard shortcuts

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