mirror of
https://github.com/safing/portmaster
synced 2025-04-07 12:39:09 +00:00
* [service] Subscribe to systemd-resolver events * [service] Add disabled state to the resolver * [service] Add ETW DNS event listener * [service] DNS listener refactoring * [service] Add windows core dll project * [service] DNSListener refactoring, small bugfixes * [service] Change dns bypass rule * [service] Update gitignore * [service] Remove shim from integration module * [service] Add DNS packet analyzer * [service] Add self-check in dns monitor * [service] Fix go linter errors * [CI] Add github workflow for the windows core dll * [service] Minor fixes to the dns monitor
656 lines
18 KiB
Go
656 lines
18 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/service/broadcasts"
|
|
"github.com/safing/portmaster/service/compat"
|
|
"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/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/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/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
|
|
serviceGroup *mgr.Group
|
|
|
|
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
|
|
updates *updates.Updates
|
|
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
|
|
|
|
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
|
|
}
|
|
|
|
// New returns a new Portmaster service instance.
|
|
func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
|
|
// Create instance to pass it to modules.
|
|
instance := &Instance{}
|
|
instance.ctx, instance.cancelCtx = context.WithCancel(context.Background())
|
|
|
|
var err error
|
|
// 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
|
|
instance.core, err = core.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create core module: %w", err)
|
|
}
|
|
instance.updates, err = updates.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create updates 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.access, err = access.New(instance)
|
|
if err != nil {
|
|
return instance, fmt.Errorf("create access 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)
|
|
}
|
|
|
|
// 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.updates,
|
|
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.interception,
|
|
instance.dnsmonitor,
|
|
|
|
instance.compat,
|
|
instance.status,
|
|
instance.broadcasts,
|
|
instance.sync,
|
|
instance.ui,
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// Updates returns the updates module.
|
|
func (i *Instance) Updates() *updates.Updates {
|
|
return i.updates
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
|
|
// Ctx returns the instance context.
|
|
// It is only canceled on shutdown.
|
|
func (i *Instance) Ctx() context.Context {
|
|
return i.ctx
|
|
}
|
|
|
|
// Start starts the instance.
|
|
func (i *Instance) Start() error {
|
|
return i.serviceGroup.Start()
|
|
}
|
|
|
|
// Stop stops the instance and cancels the instance context when done.
|
|
func (i *Instance) Stop() error {
|
|
defer i.cancelCtx()
|
|
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)
|
|
|
|
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) {
|
|
// Set given exit code.
|
|
i.exitCode.Store(int32(exitCode))
|
|
|
|
m := mgr.New("instance")
|
|
m.Go("shutdown", func(w *mgr.WorkerCtx) error {
|
|
for {
|
|
if err := i.Stop(); err != nil {
|
|
w.Error("failed to shutdown", "err", err, "retry", "1s")
|
|
time.Sleep(1 * time.Second)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// Stopping returns whether the instance is shutting down.
|
|
func (i *Instance) Stopping() bool {
|
|
return i.ctx.Err() != nil
|
|
}
|
|
|
|
// Stopped returns a channel that is triggered when the instance has shut down.
|
|
func (i *Instance) Stopped() <-chan struct{} {
|
|
return i.ctx.Done()
|
|
}
|
|
|
|
// ExitCode returns the set exit code of the instance.
|
|
func (i *Instance) ExitCode() int {
|
|
return int(i.exitCode.Load())
|
|
}
|