mirror of
https://github.com/safing/portmaster.git
synced 2026-05-20 20:40:36 +00:00
ee8cde31f6
Implement initial proof-of-concept for split tunnel functionality on Windows, allowing applications to route traffic through a designated network interface while bypassing default system routing. Features: - Split tunnel module with TCP/UDP proxy infrastructure - Firewall integration with split tunnel verdict handling - SplitTunneling context attached to connections - Configuration options: enable toggle, interface selection, and policy rules - UI display of split tunnel connection details in connection info panel - Subsystem configuration for user-level access Windows-specific implementation: - Uses proxy-based interface routing on Windows - Automatic or manual interface detection and binding - Support for IPv4 and IPv6 traffic Note: Linux implementation is under development. SPN takes precedence over split tunnel when both are enabled, ensuring SPN connections bypass this feature.
809 lines
22 KiB
Go
809 lines
22 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/safing/portmaster/base/api"
|
|
"github.com/safing/portmaster/base/config"
|
|
"github.com/safing/portmaster/base/database/dbmodule"
|
|
"github.com/safing/portmaster/base/metrics"
|
|
"github.com/safing/portmaster/base/notifications"
|
|
"github.com/safing/portmaster/base/rng"
|
|
"github.com/safing/portmaster/base/runtime"
|
|
"github.com/safing/portmaster/base/utils"
|
|
"github.com/safing/portmaster/service/broadcasts"
|
|
"github.com/safing/portmaster/service/compat"
|
|
"github.com/safing/portmaster/service/control"
|
|
"github.com/safing/portmaster/service/core"
|
|
"github.com/safing/portmaster/service/core/base"
|
|
"github.com/safing/portmaster/service/firewall"
|
|
"github.com/safing/portmaster/service/firewall/interception"
|
|
"github.com/safing/portmaster/service/firewall/interception/dnsmonitor"
|
|
"github.com/safing/portmaster/service/integration"
|
|
"github.com/safing/portmaster/service/intel/customlists"
|
|
"github.com/safing/portmaster/service/intel/filterlists"
|
|
"github.com/safing/portmaster/service/intel/geoip"
|
|
"github.com/safing/portmaster/service/interop"
|
|
"github.com/safing/portmaster/service/mgr"
|
|
"github.com/safing/portmaster/service/nameserver"
|
|
"github.com/safing/portmaster/service/netenv"
|
|
"github.com/safing/portmaster/service/netquery"
|
|
"github.com/safing/portmaster/service/network"
|
|
"github.com/safing/portmaster/service/process"
|
|
"github.com/safing/portmaster/service/profile"
|
|
"github.com/safing/portmaster/service/resolver"
|
|
"github.com/safing/portmaster/service/splittun"
|
|
"github.com/safing/portmaster/service/status"
|
|
"github.com/safing/portmaster/service/sync"
|
|
"github.com/safing/portmaster/service/ui"
|
|
"github.com/safing/portmaster/service/updates"
|
|
"github.com/safing/portmaster/spn/access"
|
|
"github.com/safing/portmaster/spn/cabin"
|
|
"github.com/safing/portmaster/spn/captain"
|
|
"github.com/safing/portmaster/spn/crew"
|
|
"github.com/safing/portmaster/spn/docks"
|
|
"github.com/safing/portmaster/spn/hub"
|
|
"github.com/safing/portmaster/spn/navigator"
|
|
"github.com/safing/portmaster/spn/patrol"
|
|
"github.com/safing/portmaster/spn/ships"
|
|
"github.com/safing/portmaster/spn/sluice"
|
|
"github.com/safing/portmaster/spn/terminal"
|
|
)
|
|
|
|
// Instance is an instance of a Portmaster service.
|
|
type Instance struct {
|
|
ctx context.Context
|
|
cancelCtx context.CancelFunc
|
|
|
|
shutdownCtx context.Context
|
|
cancelShutdownCtx context.CancelFunc
|
|
|
|
serviceGroup *mgr.Group
|
|
serviceGroupInterception *mgr.GroupModule
|
|
|
|
binDir string
|
|
dataDir string
|
|
|
|
exitCode atomic.Int32
|
|
|
|
database *dbmodule.DBModule
|
|
config *config.Config
|
|
api *api.API
|
|
metrics *metrics.Metrics
|
|
runtime *runtime.Runtime
|
|
notifications *notifications.Notifications
|
|
rng *rng.Rng
|
|
base *base.Base
|
|
|
|
core *core.Core
|
|
binaryUpdates *updates.Updater
|
|
intelUpdates *updates.Updater
|
|
integration *integration.OSIntegration
|
|
geoip *geoip.GeoIP
|
|
netenv *netenv.NetEnv
|
|
ui *ui.UI
|
|
profile *profile.ProfileModule
|
|
network *network.Network
|
|
netquery *netquery.NetQuery
|
|
firewall *firewall.Firewall
|
|
filterLists *filterlists.FilterLists
|
|
interception *interception.Interception
|
|
dnsmonitor *dnsmonitor.DNSMonitor
|
|
customlist *customlists.CustomList
|
|
status *status.Status
|
|
broadcasts *broadcasts.Broadcasts
|
|
compat *compat.Compat
|
|
nameserver *nameserver.NameServer
|
|
process *process.ProcessModule
|
|
resolver *resolver.ResolverModule
|
|
sync *sync.Sync
|
|
control *control.Control
|
|
interop *interop.Interoperability
|
|
|
|
splittun *splittun.SplitTunModule
|
|
|
|
access *access.Access
|
|
|
|
// SPN modules
|
|
SpnGroup *mgr.ExtendedGroup
|
|
cabin *cabin.Cabin
|
|
navigator *navigator.Navigator
|
|
captain *captain.Captain
|
|
crew *crew.Crew
|
|
docks *docks.Docks
|
|
patrol *patrol.Patrol
|
|
ships *ships.Ships
|
|
sluice *sluice.SluiceModule
|
|
terminal *terminal.TerminalModule
|
|
|
|
CommandLineOperation func() error
|
|
ShouldRestart bool
|
|
}
|
|
|
|
// New returns a new Portmaster service instance.
|
|
func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
|
|
// Initialize config.
|
|
err := svcCfg.Init()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("internal service config error: %w", err)
|
|
}
|
|
|
|
// Make sure data dir exists, so that child directories don't dictate the permissions.
|
|
err = utils.EnsureDirectory(svcCfg.DataDir, utils.PublicReadExecPermission)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("data directory %s is not accessible: %w", svcCfg.DataDir, err)
|
|
}
|
|
|
|
// Create instance to pass it to modules.
|
|
instance := &Instance{
|
|
binDir: svcCfg.BinDir,
|
|
dataDir: svcCfg.DataDir,
|
|
}
|
|
instance.ctx, instance.cancelCtx = context.WithCancel(context.Background())
|
|
instance.shutdownCtx, instance.cancelShutdownCtx = context.WithCancel(context.Background())
|
|
|
|
// Base modules
|
|
instance.base, err = base.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create base module: %w", err)
|
|
}
|
|
instance.database, err = dbmodule.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create database module: %w", err)
|
|
}
|
|
instance.config, err = config.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create config module: %w", err)
|
|
}
|
|
instance.api, err = api.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create api module: %w", err)
|
|
}
|
|
instance.metrics, err = metrics.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create metrics module: %w", err)
|
|
}
|
|
instance.runtime, err = runtime.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create runtime module: %w", err)
|
|
}
|
|
instance.notifications, err = notifications.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create runtime module: %w", err)
|
|
}
|
|
instance.rng, err = rng.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create rng module: %w", err)
|
|
}
|
|
|
|
// Service modules
|
|
binaryUpdateConfig, intelUpdateConfig, err := MakeUpdateConfigs(svcCfg)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create updates config: %w", err)
|
|
}
|
|
instance.binaryUpdates, err = updates.New(instance, "Binary Updater", *binaryUpdateConfig)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create updates module: %w", err)
|
|
}
|
|
instance.intelUpdates, err = updates.New(instance, "Intel Updater", *intelUpdateConfig)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create updates module: %w", err)
|
|
}
|
|
instance.core, err = core.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create core module: %w", err)
|
|
}
|
|
instance.integration, err = integration.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create integration module: %w", err)
|
|
}
|
|
instance.geoip, err = geoip.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create customlist module: %w", err)
|
|
}
|
|
instance.netenv, err = netenv.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create netenv module: %w", err)
|
|
}
|
|
instance.ui, err = ui.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create ui module: %w", err)
|
|
}
|
|
instance.profile, err = profile.NewModule(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create profile module: %w", err)
|
|
}
|
|
instance.network, err = network.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create network module: %w", err)
|
|
}
|
|
instance.netquery, err = netquery.NewModule(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create netquery module: %w", err)
|
|
}
|
|
instance.firewall, err = firewall.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create firewall module: %w", err)
|
|
}
|
|
instance.filterLists, err = filterlists.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create filterLists module: %w", err)
|
|
}
|
|
instance.interception, err = interception.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create interception module: %w", err)
|
|
}
|
|
instance.dnsmonitor, err = dnsmonitor.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create dns-listener module: %w", err)
|
|
}
|
|
instance.customlist, err = customlists.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create customlist module: %w", err)
|
|
}
|
|
|
|
instance.status, err = status.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create status module: %w", err)
|
|
}
|
|
instance.broadcasts, err = broadcasts.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create broadcasts module: %w", err)
|
|
}
|
|
instance.compat, err = compat.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create compat module: %w", err)
|
|
}
|
|
instance.nameserver, err = nameserver.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create nameserver module: %w", err)
|
|
}
|
|
instance.process, err = process.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create process module: %w", err)
|
|
}
|
|
instance.resolver, err = resolver.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create resolver module: %w", err)
|
|
}
|
|
instance.sync, err = sync.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create sync module: %w", err)
|
|
}
|
|
instance.control, err = control.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create control module: %w", err)
|
|
}
|
|
instance.interop, err = interop.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create interop module: %w", err)
|
|
}
|
|
instance.access, err = access.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create access module: %w", err)
|
|
}
|
|
|
|
instance.splittun, err = splittun.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create splittun module: %w", err)
|
|
}
|
|
|
|
// SPN modules
|
|
instance.cabin, err = cabin.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create cabin module: %w", err)
|
|
}
|
|
instance.navigator, err = navigator.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create navigator module: %w", err)
|
|
}
|
|
instance.captain, err = captain.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create captain module: %w", err)
|
|
}
|
|
instance.crew, err = crew.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create crew module: %w", err)
|
|
}
|
|
instance.docks, err = docks.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create docks module: %w", err)
|
|
}
|
|
instance.patrol, err = patrol.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create patrol module: %w", err)
|
|
}
|
|
instance.ships, err = ships.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create ships module: %w", err)
|
|
}
|
|
instance.sluice, err = sluice.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create sluice module: %w", err)
|
|
}
|
|
instance.terminal, err = terminal.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create terminal module: %w", err)
|
|
}
|
|
|
|
// Grouped interception modules that can be paused/resumed together.
|
|
instance.serviceGroupInterception = mgr.NewGroupModule("Interception Group",
|
|
instance.interception,
|
|
instance.dnsmonitor,
|
|
instance.compat)
|
|
|
|
// Add all modules to instance group.
|
|
instance.serviceGroup = mgr.NewGroup(
|
|
instance.base,
|
|
instance.rng,
|
|
instance.database,
|
|
instance.config,
|
|
instance.api,
|
|
instance.metrics,
|
|
instance.runtime,
|
|
instance.notifications,
|
|
|
|
instance.core,
|
|
instance.binaryUpdates,
|
|
instance.intelUpdates,
|
|
instance.integration,
|
|
instance.geoip,
|
|
instance.netenv,
|
|
|
|
instance.process,
|
|
instance.profile,
|
|
instance.network,
|
|
instance.netquery,
|
|
instance.firewall,
|
|
instance.nameserver,
|
|
instance.resolver,
|
|
instance.filterLists,
|
|
instance.customlist,
|
|
|
|
instance.splittun,
|
|
|
|
instance.interop, // required to start before interception
|
|
|
|
// Grouped pausable interception modules:
|
|
// instance.interception,
|
|
// instance.dnsmonitor,
|
|
// instance.compat
|
|
instance.serviceGroupInterception,
|
|
|
|
instance.status,
|
|
instance.broadcasts,
|
|
instance.sync,
|
|
instance.ui,
|
|
instance.control,
|
|
|
|
instance.access,
|
|
)
|
|
|
|
// SPN Group
|
|
instance.SpnGroup = mgr.NewExtendedGroup(
|
|
instance.cabin,
|
|
instance.navigator,
|
|
instance.captain,
|
|
instance.crew,
|
|
instance.docks,
|
|
instance.patrol,
|
|
instance.ships,
|
|
instance.sluice,
|
|
instance.terminal,
|
|
)
|
|
|
|
return instance, nil
|
|
}
|
|
|
|
// SleepyModule is an interface for modules that can enter some sort of sleep mode.
|
|
type SleepyModule interface {
|
|
SetSleep(enabled bool)
|
|
}
|
|
|
|
// SetSleep sets sleep mode on all modules that satisfy the SleepyModule interface.
|
|
func (i *Instance) SetSleep(enabled bool) {
|
|
for _, module := range i.serviceGroup.Modules() {
|
|
if sm, ok := module.(SleepyModule); ok {
|
|
sm.SetSleep(enabled)
|
|
}
|
|
}
|
|
for _, module := range i.SpnGroup.Modules() {
|
|
if sm, ok := module.(SleepyModule); ok {
|
|
sm.SetSleep(enabled)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BinDir returns the directory for binaries.
|
|
// This directory may be read-only.
|
|
func (i *Instance) BinDir() string {
|
|
return i.binDir
|
|
}
|
|
|
|
// DataDir returns the directory for variable data.
|
|
// This directory is expected to be read/writeable.
|
|
func (i *Instance) DataDir() string {
|
|
return i.dataDir
|
|
}
|
|
|
|
// Database returns the database module.
|
|
func (i *Instance) Database() *dbmodule.DBModule {
|
|
return i.database
|
|
}
|
|
|
|
// Config returns the config module.
|
|
func (i *Instance) Config() *config.Config {
|
|
return i.config
|
|
}
|
|
|
|
// API returns the api module.
|
|
func (i *Instance) API() *api.API {
|
|
return i.api
|
|
}
|
|
|
|
// Metrics returns the metrics module.
|
|
func (i *Instance) Metrics() *metrics.Metrics {
|
|
return i.metrics
|
|
}
|
|
|
|
// Runtime returns the runtime module.
|
|
func (i *Instance) Runtime() *runtime.Runtime {
|
|
return i.runtime
|
|
}
|
|
|
|
// Notifications returns the notifications module.
|
|
func (i *Instance) Notifications() *notifications.Notifications {
|
|
return i.notifications
|
|
}
|
|
|
|
// Rng returns the rng module.
|
|
func (i *Instance) Rng() *rng.Rng {
|
|
return i.rng
|
|
}
|
|
|
|
// Base returns the base module.
|
|
func (i *Instance) Base() *base.Base {
|
|
return i.base
|
|
}
|
|
|
|
// BinaryUpdates returns the updates module.
|
|
func (i *Instance) BinaryUpdates() *updates.Updater {
|
|
return i.binaryUpdates
|
|
}
|
|
|
|
// GetBinaryUpdateFile returns the file path of a binary update file.
|
|
func (i *Instance) GetBinaryUpdateFile(name string) (path string, err error) {
|
|
file, err := i.binaryUpdates.GetFile(name)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return file.Path(), nil
|
|
}
|
|
|
|
// IntelUpdates returns the updates module.
|
|
func (i *Instance) IntelUpdates() *updates.Updater {
|
|
return i.intelUpdates
|
|
}
|
|
|
|
// OSIntegration returns the integration module.
|
|
func (i *Instance) OSIntegration() *integration.OSIntegration {
|
|
return i.integration
|
|
}
|
|
|
|
// GeoIP returns the geoip module.
|
|
func (i *Instance) GeoIP() *geoip.GeoIP {
|
|
return i.geoip
|
|
}
|
|
|
|
// NetEnv returns the netenv module.
|
|
func (i *Instance) NetEnv() *netenv.NetEnv {
|
|
return i.netenv
|
|
}
|
|
|
|
// Access returns the access module.
|
|
func (i *Instance) Access() *access.Access {
|
|
return i.access
|
|
}
|
|
|
|
// Cabin returns the cabin module.
|
|
func (i *Instance) Cabin() *cabin.Cabin {
|
|
return i.cabin
|
|
}
|
|
|
|
// Captain returns the captain module.
|
|
func (i *Instance) Captain() *captain.Captain {
|
|
return i.captain
|
|
}
|
|
|
|
// Crew returns the crew module.
|
|
func (i *Instance) Crew() *crew.Crew {
|
|
return i.crew
|
|
}
|
|
|
|
// Docks returns the crew module.
|
|
func (i *Instance) Docks() *docks.Docks {
|
|
return i.docks
|
|
}
|
|
|
|
// Navigator returns the navigator module.
|
|
func (i *Instance) Navigator() *navigator.Navigator {
|
|
return i.navigator
|
|
}
|
|
|
|
// Patrol returns the patrol module.
|
|
func (i *Instance) Patrol() *patrol.Patrol {
|
|
return i.patrol
|
|
}
|
|
|
|
// Ships returns the ships module.
|
|
func (i *Instance) Ships() *ships.Ships {
|
|
return i.ships
|
|
}
|
|
|
|
// Sluice returns the ships module.
|
|
func (i *Instance) Sluice() *sluice.SluiceModule {
|
|
return i.sluice
|
|
}
|
|
|
|
// Terminal returns the terminal module.
|
|
func (i *Instance) Terminal() *terminal.TerminalModule {
|
|
return i.terminal
|
|
}
|
|
|
|
// UI returns the ui module.
|
|
func (i *Instance) UI() *ui.UI {
|
|
return i.ui
|
|
}
|
|
|
|
// Profile returns the profile module.
|
|
func (i *Instance) Profile() *profile.ProfileModule {
|
|
return i.profile
|
|
}
|
|
|
|
// Firewall returns the firewall module.
|
|
func (i *Instance) Firewall() *firewall.Firewall {
|
|
return i.firewall
|
|
}
|
|
|
|
// FilterLists returns the filterLists module.
|
|
func (i *Instance) FilterLists() *filterlists.FilterLists {
|
|
return i.filterLists
|
|
}
|
|
|
|
// Interception returns the interception module.
|
|
func (i *Instance) Interception() *interception.Interception {
|
|
return i.interception
|
|
}
|
|
|
|
// InterceptionGroup returns the grouped interception modules that can be paused together.
|
|
func (i *Instance) InterceptionGroup() *mgr.GroupModule {
|
|
return i.serviceGroupInterception
|
|
}
|
|
|
|
// DNSMonitor returns the dns-listener module.
|
|
func (i *Instance) DNSMonitor() *dnsmonitor.DNSMonitor {
|
|
return i.dnsmonitor
|
|
}
|
|
|
|
// CustomList returns the customlist module.
|
|
func (i *Instance) CustomList() *customlists.CustomList {
|
|
return i.customlist
|
|
}
|
|
|
|
// Status returns the status module.
|
|
func (i *Instance) Status() *status.Status {
|
|
return i.status
|
|
}
|
|
|
|
// Broadcasts returns the broadcast module.
|
|
func (i *Instance) Broadcasts() *broadcasts.Broadcasts {
|
|
return i.broadcasts
|
|
}
|
|
|
|
// Compat returns the compat module.
|
|
func (i *Instance) Compat() *compat.Compat {
|
|
return i.compat
|
|
}
|
|
|
|
// NameServer returns the nameserver module.
|
|
func (i *Instance) NameServer() *nameserver.NameServer {
|
|
return i.nameserver
|
|
}
|
|
|
|
// NetQuery returns the netquery module.
|
|
func (i *Instance) NetQuery() *netquery.NetQuery {
|
|
return i.netquery
|
|
}
|
|
|
|
// Network returns the network module.
|
|
func (i *Instance) Network() *network.Network {
|
|
return i.network
|
|
}
|
|
|
|
// Process returns the process module.
|
|
func (i *Instance) Process() *process.ProcessModule {
|
|
return i.process
|
|
}
|
|
|
|
// Resolver returns the resolver module.
|
|
func (i *Instance) Resolver() *resolver.ResolverModule {
|
|
return i.resolver
|
|
}
|
|
|
|
// Sync returns the sync module.
|
|
func (i *Instance) Sync() *sync.Sync {
|
|
return i.sync
|
|
}
|
|
|
|
// Core returns the core module.
|
|
func (i *Instance) Core() *core.Core {
|
|
return i.core
|
|
}
|
|
|
|
// SPNGroup returns the group of all SPN modules.
|
|
func (i *Instance) SPNGroup() *mgr.ExtendedGroup {
|
|
return i.SpnGroup
|
|
}
|
|
|
|
// Events
|
|
|
|
// GetEventSPNConnected return the event manager for the SPN connected event.
|
|
func (i *Instance) GetEventSPNConnected() *mgr.EventMgr[struct{}] {
|
|
return i.captain.EventSPNConnected
|
|
}
|
|
|
|
func (i *Instance) GetHookSPNConnecting() *mgr.HookMgr[hub.Announcement] {
|
|
return i.captain.HookSPNConnecting
|
|
}
|
|
|
|
// Special functions
|
|
|
|
// SetCmdLineOperation sets a command line operation to be executed instead of starting the system. This is useful when functions need all modules to be prepared for a special operation.
|
|
func (i *Instance) SetCmdLineOperation(f func() error) {
|
|
i.CommandLineOperation = f
|
|
}
|
|
|
|
// GetStates returns the current states of all group modules.
|
|
func (i *Instance) GetStates() []mgr.StateUpdate {
|
|
mainStates := i.serviceGroup.GetStates()
|
|
spnStates := i.SpnGroup.GetStates()
|
|
|
|
updates := make([]mgr.StateUpdate, 0, len(mainStates)+len(spnStates))
|
|
updates = append(updates, mainStates...)
|
|
updates = append(updates, spnStates...)
|
|
|
|
return updates
|
|
}
|
|
|
|
// AddStatesCallback adds the given callback function to all group modules that
|
|
// expose a state manager at States().
|
|
func (i *Instance) AddStatesCallback(callbackName string, callback mgr.EventCallbackFunc[mgr.StateUpdate]) {
|
|
i.serviceGroup.AddStatesCallback(callbackName, callback)
|
|
i.SpnGroup.AddStatesCallback(callbackName, callback)
|
|
}
|
|
|
|
// Ready returns whether all modules in the main service module group have been started and are still running.
|
|
func (i *Instance) Ready() bool {
|
|
return i.serviceGroup.Ready()
|
|
}
|
|
|
|
// Start starts the instance modules.
|
|
func (i *Instance) Start() error {
|
|
return i.serviceGroup.Start()
|
|
}
|
|
|
|
// Stop stops the instance modules.
|
|
func (i *Instance) Stop() error {
|
|
return i.serviceGroup.Stop()
|
|
}
|
|
|
|
// RestartExitCode will instruct portmaster-start to restart the process immediately, potentially with a new version.
|
|
const RestartExitCode = 23
|
|
|
|
// Restart asynchronously restarts the instance.
|
|
// This only works if the underlying system/process supports this.
|
|
func (i *Instance) Restart() {
|
|
// Send a restart event, give it 10ms extra to propagate.
|
|
i.core.EventRestart.Submit(struct{}{})
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Set the restart flag and shutdown.
|
|
i.ShouldRestart = true
|
|
i.shutdown(RestartExitCode)
|
|
}
|
|
|
|
// Shutdown asynchronously stops the instance.
|
|
func (i *Instance) Shutdown() {
|
|
// Send a shutdown event, give it 10ms extra to propagate.
|
|
i.core.EventShutdown.Submit(struct{}{})
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
i.shutdown(0)
|
|
}
|
|
|
|
func (i *Instance) shutdown(exitCode int) {
|
|
// Only shutdown once.
|
|
if i.IsShuttingDown() {
|
|
return
|
|
}
|
|
|
|
// Cancel main context.
|
|
i.cancelCtx()
|
|
|
|
// Set given exit code.
|
|
i.exitCode.Store(int32(exitCode))
|
|
|
|
// Start shutdown asynchronously in a separate manager.
|
|
m := mgr.New("instance")
|
|
m.Go("shutdown", func(w *mgr.WorkerCtx) error {
|
|
// Stop all modules.
|
|
if err := i.Stop(); err != nil {
|
|
w.Error("failed to shutdown", "err", err)
|
|
}
|
|
|
|
// Cancel shutdown process context.
|
|
i.cancelShutdownCtx()
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Ctx returns the instance context.
|
|
// It is canceled when shutdown is started.
|
|
func (i *Instance) Ctx() context.Context {
|
|
return i.ctx
|
|
}
|
|
|
|
// IsShuttingDown returns whether the instance is shutting down.
|
|
func (i *Instance) IsShuttingDown() bool {
|
|
return i.ctx.Err() != nil
|
|
}
|
|
|
|
// ShuttingDown returns a channel that is triggered when the instance starts shutting down.
|
|
func (i *Instance) ShuttingDown() <-chan struct{} {
|
|
return i.ctx.Done()
|
|
}
|
|
|
|
// ShutdownCtx returns the instance shutdown context.
|
|
// It is canceled when shutdown is complete.
|
|
func (i *Instance) ShutdownCtx() context.Context {
|
|
return i.shutdownCtx
|
|
}
|
|
|
|
// IsShutDown returns whether the instance has stopped.
|
|
func (i *Instance) IsShutDown() bool {
|
|
return i.shutdownCtx.Err() != nil
|
|
}
|
|
|
|
// ShutDownComplete returns a channel that is triggered when the instance has shut down.
|
|
func (i *Instance) ShutdownComplete() <-chan struct{} {
|
|
return i.shutdownCtx.Done()
|
|
}
|
|
|
|
// ExitCode returns the set exit code of the instance.
|
|
func (i *Instance) ExitCode() int {
|
|
return int(i.exitCode.Load())
|
|
}
|
|
|
|
// ShouldRestartIsSet returns whether the service/instance should be restarted.
|
|
func (i *Instance) ShouldRestartIsSet() bool {
|
|
return i.ShouldRestart
|
|
}
|
|
|
|
// CommandLineOperationIsSet returns whether the command line option is set.
|
|
func (i *Instance) CommandLineOperationIsSet() bool {
|
|
return i.CommandLineOperation != nil
|
|
}
|
|
|
|
// CommandLineOperationExecute executes the set command line option.
|
|
func (i *Instance) CommandLineOperationExecute() error {
|
|
return i.CommandLineOperation()
|
|
}
|
|
|
|
// AddModule adds a module to the service group.
|
|
func (i *Instance) AddModule(m mgr.Module) {
|
|
i.serviceGroup.Add(m)
|
|
}
|