Restructure modules (#1572)

* Move portbase into monorepo

* Add new simple module mgr

* [WIP] Switch to new simple module mgr

* Add StateMgr and more worker variants

* [WIP] Switch more modules

* [WIP] Switch more modules

* [WIP] swtich more modules

* [WIP] switch all SPN modules

* [WIP] switch all service modules

* [WIP] Convert all workers to the new module system

* [WIP] add new task system to module manager

* [WIP] Add second take for scheduling workers

* [WIP] Add FIXME for bugs in new scheduler

* [WIP] Add minor improvements to scheduler

* [WIP] Add new worker scheduler

* [WIP] Fix more bug related to new module system

* [WIP] Fix start handing of the new module system

* [WIP] Improve startup process

* [WIP] Fix minor issues

* [WIP] Fix missing subsystem in settings

* [WIP] Initialize managers in constructor

* [WIP] Move module event initialization to constrictors

* [WIP] Fix setting for enabling and disabling the SPN module

* [WIP] Move API registeration into module construction

* [WIP] Update states mgr for all modules

* [WIP] Add CmdLine operation support

* Add state helper methods to module group and instance

* Add notification and module status handling to status package

* Fix starting issues

* Remove pilot widget and update security lock to new status data

* Remove debug logs

* Improve http server shutdown

* Add workaround for cleanly shutting down firewall+netquery

* Improve logging

* Add syncing states with notifications for new module system

* Improve starting, stopping, shutdown; resolve FIXMEs/TODOs

* [WIP] Fix most unit tests

* Review new module system and fix minor issues

* Push shutdown and restart events again via API

* Set sleep mode via interface

* Update example/template module

* [WIP] Fix spn/cabin unit test

* Remove deprecated UI elements

* Make log output more similar for the logging transition phase

* Switch spn hub and observer cmds to new module system

* Fix log sources

* Make worker mgr less error prone

* Fix tests and minor issues

* Fix observation hub

* Improve shutdown and restart handling

* Split up big connection.go source file

* Move varint and dsd packages to structures repo

* Improve expansion test

* Fix linter warnings

* Fix interception module on windows

* Fix linter errors

---------

Co-authored-by: Vladimir Stoilov <vladimir@safing.io>
This commit is contained in:
Daniel Hååvi
2024-08-09 17:15:48 +02:00
committed by GitHub
parent 10a77498f4
commit 80664d1a27
647 changed files with 37690 additions and 3366 deletions

View File

@@ -7,8 +7,8 @@ import (
"sync/atomic"
"time"
"github.com/safing/portbase/formats/varint"
"github.com/safing/portbase/modules"
"github.com/safing/portmaster/service/mgr"
"github.com/safing/structures/varint"
)
// FlowControl defines the flow control interface.
@@ -18,7 +18,7 @@ type FlowControl interface {
Send(msg *Msg, timeout time.Duration) *Error
ReadyToSend() <-chan struct{}
Flush(timeout time.Duration)
StartWorkers(m *modules.Module, terminalName string)
StartWorkers(m *mgr.Manager, terminalName string)
RecvQueueLen() int
SendQueueLen() int
}
@@ -121,8 +121,8 @@ func NewDuplexFlowQueue(
}
// StartWorkers starts the necessary workers to operate the flow queue.
func (dfq *DuplexFlowQueue) StartWorkers(m *modules.Module, terminalName string) {
m.StartWorker(terminalName+" flow queue", dfq.FlowHandler)
func (dfq *DuplexFlowQueue) StartWorkers(m *mgr.Manager, terminalName string) {
m.Go(terminalName+" flow queue", dfq.FlowHandler)
}
// shouldReportRecvSpace returns whether the receive space should be reported.
@@ -187,7 +187,7 @@ func (dfq *DuplexFlowQueue) reportableRecvSpace() int32 {
// FlowHandler handles all flow queue internals and must be started as a worker
// in the module where it is used.
func (dfq *DuplexFlowQueue) FlowHandler(_ context.Context) error {
func (dfq *DuplexFlowQueue) FlowHandler(_ *mgr.WorkerCtx) error {
// The upstreamSender is started by the terminal module, but is tied to the
// flow owner instead. Make sure that the flow owner's module depends on the
// terminal module so that it is shut down earlier.

View File

@@ -5,7 +5,7 @@ import (
"errors"
"fmt"
"github.com/safing/portbase/formats/varint"
"github.com/safing/structures/varint"
)
// Error is a terminal error.

View File

@@ -4,11 +4,11 @@ import (
"context"
"github.com/safing/jess"
"github.com/safing/portbase/container"
"github.com/safing/portbase/formats/dsd"
"github.com/safing/portbase/formats/varint"
"github.com/safing/portmaster/spn/cabin"
"github.com/safing/portmaster/spn/hub"
"github.com/safing/structures/container"
"github.com/safing/structures/dsd"
"github.com/safing/structures/varint"
)
/*

View File

@@ -5,8 +5,8 @@ import (
"github.com/tevino/abool"
"github.com/safing/portbase/api"
"github.com/safing/portbase/metrics"
"github.com/safing/portmaster/base/api"
"github.com/safing/portmaster/base/metrics"
)
var metricsRegistered = abool.New()

View File

@@ -1,18 +1,40 @@
package terminal
import (
"errors"
"flag"
"sync/atomic"
"time"
"github.com/safing/portbase/modules"
"github.com/safing/portbase/rng"
"github.com/safing/portmaster/base/rng"
"github.com/safing/portmaster/service/mgr"
"github.com/safing/portmaster/spn/conf"
"github.com/safing/portmaster/spn/unit"
)
// TerminalModule is the command multiplexing module.
type TerminalModule struct { //nolint:golint
mgr *mgr.Manager
instance instance
}
// Manager returns the module manager.
func (s *TerminalModule) Manager() *mgr.Manager {
return s.mgr
}
// Start starts the module.
func (s *TerminalModule) Start() error {
return start()
}
// Stop stops the module.
func (s *TerminalModule) Stop() error {
return nil
}
var (
module *modules.Module
rngFeeder *rng.Feeder = rng.NewFeeder()
rngFeeder *rng.Feeder = nil
scheduler *unit.Scheduler
@@ -21,8 +43,6 @@ var (
func init() {
flag.BoolVar(&debugUnitScheduling, "debug-unit-scheduling", false, "enable debug logs of the SPN unit scheduler")
module = modules.Register("terminal", nil, start, nil, "base")
}
func start() error {
@@ -33,7 +53,7 @@ func start() error {
// Debug unit leaks.
scheduler.StartDebugLog()
}
module.StartServiceWorker("msg unit scheduler", 0, scheduler.SlotScheduler)
module.mgr.Go("msg unit scheduler", scheduler.SlotScheduler)
lockOpRegistry()
@@ -78,3 +98,23 @@ func getSchedulerConfig() *unit.SchedulerConfig {
StatCycleDuration: 1 * time.Minute, // Match metrics report cycle.
}
}
var (
module *TerminalModule
shimLoaded atomic.Bool
)
// New returns a new Config module.
func New(instance instance) (*TerminalModule, error) {
if !shimLoaded.CompareAndSwap(false, true) {
return nil, errors.New("only one instance allowed")
}
m := mgr.New("TerminalModule")
module = &TerminalModule{
mgr: m,
instance: instance,
}
return module, nil
}
type instance interface{}

View File

@@ -1,13 +1,121 @@
package terminal
import (
"fmt"
"os"
"testing"
"github.com/safing/portmaster/service/core/pmtesting"
"github.com/safing/portmaster/base/config"
"github.com/safing/portmaster/base/database/dbmodule"
"github.com/safing/portmaster/base/metrics"
"github.com/safing/portmaster/base/rng"
"github.com/safing/portmaster/service/core/base"
"github.com/safing/portmaster/service/mgr"
"github.com/safing/portmaster/spn/cabin"
"github.com/safing/portmaster/spn/conf"
)
func TestMain(m *testing.M) {
conf.EnablePublicHub(true)
pmtesting.TestMain(m, module)
type testInstance struct {
db *dbmodule.DBModule
config *config.Config
metrics *metrics.Metrics
rng *rng.Rng
base *base.Base
cabin *cabin.Cabin
}
func (stub *testInstance) Config() *config.Config {
return stub.config
}
func (stub *testInstance) Metrics() *metrics.Metrics {
return stub.metrics
}
func (stub *testInstance) SPNGroup() *mgr.ExtendedGroup {
return nil
}
func (stub *testInstance) Stopping() bool {
return false
}
func (stub *testInstance) SetCmdLineOperation(f func() error) {}
func runTest(m *testing.M) error {
ds, err := config.InitializeUnitTestDataroot("test-terminal")
if err != nil {
return fmt.Errorf("failed to initialize dataroot: %w", err)
}
defer func() { _ = os.RemoveAll(ds) }()
conf.EnablePublicHub(true) // Make hub config available.
instance := &testInstance{}
instance.db, err = dbmodule.New(instance)
if err != nil {
return fmt.Errorf("failed to create database module: %w", err)
}
instance.config, err = config.New(instance)
if err != nil {
return fmt.Errorf("failed to create config module: %w", err)
}
instance.metrics, err = metrics.New(instance)
if err != nil {
return fmt.Errorf("failed to create metrics module: %w", err)
}
instance.rng, err = rng.New(instance)
if err != nil {
return fmt.Errorf("failed to create rng module: %w", err)
}
instance.base, err = base.New(instance)
if err != nil {
return fmt.Errorf("failed to create base module: %w", err)
}
instance.cabin, err = cabin.New(instance)
if err != nil {
return fmt.Errorf("failed to create cabin module: %w", err)
}
_, err = New(instance)
if err != nil {
return fmt.Errorf("failed to create module: %w", err)
}
// Start
err = instance.db.Start()
if err != nil {
return fmt.Errorf("failed to start db module: %w", err)
}
err = instance.config.Start()
if err != nil {
return fmt.Errorf("failed to start config module: %w", err)
}
err = instance.metrics.Start()
if err != nil {
return fmt.Errorf("failed to start metrics module: %w", err)
}
err = instance.rng.Start()
if err != nil {
return fmt.Errorf("failed to start rng module: %w", err)
}
err = instance.base.Start()
if err != nil {
return fmt.Errorf("failed to start base module: %w", err)
}
err = instance.cabin.Start()
if err != nil {
return fmt.Errorf("failed to start cabin module: %w", err)
}
err = module.Start()
if err != nil {
return fmt.Errorf("failed to start docks module: %w", err)
}
m.Run()
return nil
}
func TestMain(m *testing.M) {
if err := runTest(m); err != nil {
os.Exit(1)
}
}

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"runtime"
"github.com/safing/portbase/container"
"github.com/safing/portmaster/spn/unit"
"github.com/safing/structures/container"
)
// Msg is a message within the SPN network stack.

View File

@@ -1,8 +1,8 @@
package terminal
import (
"github.com/safing/portbase/container"
"github.com/safing/portbase/formats/varint"
"github.com/safing/structures/container"
"github.com/safing/structures/varint"
)
/*

View File

@@ -1,16 +1,16 @@
package terminal
import (
"context"
"sync"
"sync/atomic"
"time"
"github.com/tevino/abool"
"github.com/safing/portbase/container"
"github.com/safing/portbase/log"
"github.com/safing/portbase/utils"
"github.com/safing/portmaster/base/log"
"github.com/safing/portmaster/base/utils"
"github.com/safing/portmaster/service/mgr"
"github.com/safing/structures/container"
)
// Operation is an interface for all operations.
@@ -245,7 +245,7 @@ func (t *TerminalBase) StopOperation(op Operation, err *Error) {
log.Warningf("spn/terminal: operation %s %s failed: %s", op.Type(), fmtOperationID(t.parentID, t.id, op.ID()), err)
}
module.StartWorker("stop operation", func(_ context.Context) error {
module.mgr.Go("stop operation", func(_ *mgr.WorkerCtx) error {
// Call operation stop handle function for proper shutdown cleaning up.
err = op.HandleStop(err)

View File

@@ -1,15 +1,15 @@
package terminal
import (
"context"
"fmt"
"sync"
"time"
"github.com/safing/portbase/container"
"github.com/safing/portbase/formats/dsd"
"github.com/safing/portbase/formats/varint"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/base/log"
"github.com/safing/portmaster/service/mgr"
"github.com/safing/structures/container"
"github.com/safing/structures/dsd"
"github.com/safing/structures/varint"
)
// CounterOpType is the type ID for the Counter Operation.
@@ -68,7 +68,7 @@ func NewCounterOp(t Terminal, opts CounterOpts) (*CounterOp, *Error) {
// Start worker if needed.
if op.getRemoteCounterTarget() > 0 && !op.opts.suppressWorker {
module.StartWorker("counter sender", op.CounterWorker)
module.mgr.Go("counter sender", op.CounterWorker)
}
return op, nil
}
@@ -91,7 +91,7 @@ func startCounterOp(t Terminal, opID uint32, data *container.Container) (Operati
// Start worker if needed.
if op.getRemoteCounterTarget() > 0 {
module.StartWorker("counter sender", op.CounterWorker)
module.mgr.Go("counter sender", op.CounterWorker)
}
return op, nil
@@ -219,7 +219,7 @@ func (op *CounterOp) Wait() {
}
// CounterWorker is a worker that sends counters.
func (op *CounterOp) CounterWorker(ctx context.Context) error {
func (op *CounterOp) CounterWorker(ctx *mgr.WorkerCtx) error {
for {
// Send counter msg.
err := op.SendCounter()

View File

@@ -7,7 +7,7 @@ import (
"sync/atomic"
"time"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/base/log"
)
const (

View File

@@ -16,7 +16,7 @@ func TestRateLimit(t *testing.T) {
s := NewSession()
// Everything should be okay within the min limit.
for i := 0; i < rateLimitMinOps; i++ {
for range rateLimitMinOps {
tErr = s.RateLimit()
if tErr != nil {
t.Error("should not rate limit within min limit")
@@ -24,7 +24,7 @@ func TestRateLimit(t *testing.T) {
}
// Somewhere here we should rate limiting.
for i := 0; i < rateLimitMaxOpsPerSecond; i++ {
for range rateLimitMaxOpsPerSecond {
tErr = s.RateLimit()
}
assert.ErrorIs(t, tErr, ErrRateLimited, "should rate limit")
@@ -37,7 +37,7 @@ func TestSuspicionLimit(t *testing.T) {
s := NewSession()
// Everything should be okay within the min limit.
for i := 0; i < rateLimitMinSuspicion; i++ {
for range rateLimitMinSuspicion {
tErr = s.RateLimit()
if tErr != nil {
t.Error("should not rate limit within min limit")
@@ -46,7 +46,7 @@ func TestSuspicionLimit(t *testing.T) {
}
// Somewhere here we should rate limiting.
for i := 0; i < rateLimitMaxSuspicionPerSecond; i++ {
for range rateLimitMaxSuspicionPerSecond {
s.ReportSuspiciousActivity(SusFactorCommon)
tErr = s.RateLimit()
}
@@ -66,8 +66,7 @@ func TestConcurrencyLimit(t *testing.T) {
// Start many workers to test concurrency.
wg.Add(workers)
for i := 0; i < workers; i++ {
workerNum := i
for workerNum := range workers {
go func() {
defer func() {
_ = recover()

View File

@@ -9,12 +9,12 @@ import (
"github.com/tevino/abool"
"github.com/safing/jess"
"github.com/safing/portbase/container"
"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
"github.com/safing/portbase/rng"
"github.com/safing/portmaster/base/log"
"github.com/safing/portmaster/base/rng"
"github.com/safing/portmaster/service/mgr"
"github.com/safing/portmaster/spn/cabin"
"github.com/safing/portmaster/spn/conf"
"github.com/safing/structures/container"
)
const (
@@ -231,10 +231,10 @@ func (t *TerminalBase) Deliver(msg *Msg) *Error {
}
// StartWorkers starts the necessary workers to operate the Terminal.
func (t *TerminalBase) StartWorkers(m *modules.Module, terminalName string) {
func (t *TerminalBase) StartWorkers(m *mgr.Manager, terminalName string) {
// Start terminal workers.
m.StartWorker(terminalName+" handler", t.Handler)
m.StartWorker(terminalName+" sender", t.Sender)
m.Go(terminalName+" handler", t.Handler)
m.Go(terminalName+" sender", t.Sender)
// Start any flow control workers.
if t.flowControl != nil {
@@ -250,7 +250,7 @@ const (
// Handler receives and handles messages and must be started as a worker in the
// module where the Terminal is used.
func (t *TerminalBase) Handler(_ context.Context) error {
func (t *TerminalBase) Handler(_ *mgr.WorkerCtx) error {
defer t.Abandon(ErrInternalError.With("handler died"))
var msg *Msg
@@ -322,7 +322,7 @@ func (t *TerminalBase) submitToUpstream(msg *Msg, timeout time.Duration) {
// Sender handles sending messages and must be started as a worker in the
// module where the Terminal is used.
func (t *TerminalBase) Sender(_ context.Context) error {
func (t *TerminalBase) Sender(_ *mgr.WorkerCtx) error {
// Don't send messages, if the encryption is net yet set up.
// The server encryption session is only initialized with the first
// operative message, not on Terminal creation.
@@ -782,7 +782,7 @@ func (t *TerminalBase) sendOpMsgs(msg *Msg) *Error {
// Should not be overridden by implementations.
func (t *TerminalBase) Abandon(err *Error) {
if t.Abandoning.SetToIf(false, true) {
module.StartWorker("terminal abandon procedure", func(_ context.Context) error {
module.mgr.Go("terminal abandon procedure", func(_ *mgr.WorkerCtx) error {
t.handleAbandonProcedure(err)
return nil
})

View File

@@ -8,15 +8,15 @@ import (
"testing"
"time"
"github.com/safing/portbase/container"
"github.com/safing/portmaster/spn/cabin"
"github.com/safing/portmaster/spn/hub"
"github.com/safing/structures/container"
)
func TestTerminals(t *testing.T) {
t.Parallel()
identity, erro := cabin.CreateIdentity(module.Ctx, "test")
identity, erro := cabin.CreateIdentity(module.mgr.Ctx(), "test")
if erro != nil {
t.Fatalf("failed to create identity: %s", erro)
}
@@ -65,7 +65,7 @@ func testTerminals(t *testing.T, identity *cabin.Identity, terminalOpts *Termina
var initData *container.Container
var err *Error
term1, initData, err = NewLocalTestTerminal(
module.Ctx, 127, "c1", dstHub, terminalOpts, createForwardingUpstream(
module.mgr.Ctx(), 127, "c1", dstHub, terminalOpts, createForwardingUpstream(
t, "c1", "c2", func(msg *Msg) *Error {
return term2.Deliver(msg)
},
@@ -75,7 +75,7 @@ func testTerminals(t *testing.T, identity *cabin.Identity, terminalOpts *Termina
t.Fatalf("failed to create local terminal: %s", err)
}
term2, _, err = NewRemoteTestTerminal(
module.Ctx, 127, "c2", identity, initData, createForwardingUpstream(
module.mgr.Ctx(), 127, "c2", identity, initData, createForwardingUpstream(
t, "c2", "c1", func(msg *Msg) *Error {
return term1.Deliver(msg)
},

View File

@@ -4,10 +4,10 @@ import (
"context"
"time"
"github.com/safing/portbase/container"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/base/log"
"github.com/safing/portmaster/spn/cabin"
"github.com/safing/portmaster/spn/hub"
"github.com/safing/structures/container"
)
const (
@@ -35,7 +35,7 @@ func NewLocalTestTerminal(
if err != nil {
return nil, nil, err
}
t.StartWorkers(module, "test terminal")
t.StartWorkers(module.mgr, "test terminal")
return &TestTerminal{t}, initData, nil
}
@@ -54,7 +54,7 @@ func NewRemoteTestTerminal(
if err != nil {
return nil, nil, err
}
t.StartWorkers(module, "test terminal")
t.StartWorkers(module.mgr, "test terminal")
return &TestTerminal{t}, initMsg, nil
}
@@ -136,7 +136,7 @@ func (t *TestTerminal) HandleAbandon(err *Error) (errorToSend *Error) {
return
}
// NewSimpleTestTerminalPair provides a simple conntected terminal pair for tests.
// NewSimpleTestTerminalPair provides a simple connected terminal pair for tests.
func NewSimpleTestTerminalPair(delay time.Duration, delayQueueSize int, opts *TerminalOpts) (a, b *TestTerminal, err error) {
if opts == nil {
opts = &TerminalOpts{
@@ -149,7 +149,7 @@ func NewSimpleTestTerminalPair(delay time.Duration, delayQueueSize int, opts *Te
var initData *container.Container
var tErr *Error
a, initData, tErr = NewLocalTestTerminal(
module.Ctx, 127, "a", nil, opts, UpstreamSendFunc(createDelayingTestForwardingFunc(
module.mgr.Ctx(), 127, "a", nil, opts, UpstreamSendFunc(createDelayingTestForwardingFunc(
"a", "b", delay, delayQueueSize, func(msg *Msg, timeout time.Duration) *Error {
return b.Deliver(msg)
},
@@ -159,7 +159,7 @@ func NewSimpleTestTerminalPair(delay time.Duration, delayQueueSize int, opts *Te
return nil, nil, tErr.Wrap("failed to create local test terminal")
}
b, _, tErr = NewRemoteTestTerminal(
module.Ctx, 127, "b", nil, initData, UpstreamSendFunc(createDelayingTestForwardingFunc(
module.mgr.Ctx(), 127, "b", nil, initData, UpstreamSendFunc(createDelayingTestForwardingFunc(
"b", "a", delay, delayQueueSize, func(msg *Msg, timeout time.Duration) *Error {
return a.Deliver(msg)
},