193 lines
4.6 KiB
Go
193 lines
4.6 KiB
Go
package control
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/safing/portmaster/base/api"
|
|
"github.com/safing/portmaster/base/config"
|
|
"github.com/safing/portmaster/service/mgr"
|
|
)
|
|
|
|
func (c *Control) handlePause(r *api.Request) (msg string, err error) {
|
|
params := pauseRequestParams{}
|
|
if r.InputData != nil {
|
|
if err := json.Unmarshal(r.InputData, ¶ms); err != nil {
|
|
return "Bad Request: invalid input data", err
|
|
}
|
|
}
|
|
|
|
if params.OnlySPN {
|
|
c.mgr.Info(fmt.Sprintf("Received SPN PAUSE(%v) action request ", params.Duration))
|
|
} else {
|
|
c.mgr.Info(fmt.Sprintf("Received PAUSE(%v) action request ", params.Duration))
|
|
}
|
|
|
|
if err := c.impl_pause(time.Duration(params.Duration)*time.Second, params.OnlySPN); err != nil {
|
|
return "Failed to pause", err
|
|
}
|
|
return "Pause initiated", nil
|
|
}
|
|
|
|
func (c *Control) handleResume(_ *api.Request) (msg string, err error) {
|
|
c.mgr.Info("Received RESUME action request")
|
|
if err := c.impl_resume(); err != nil {
|
|
return "Failed to resume", err
|
|
}
|
|
return "Resume initiated", nil
|
|
}
|
|
|
|
func (c *Control) impl_pause(duration time.Duration, onlySPN bool) (retErr error) {
|
|
c.locker.Lock()
|
|
defer c.locker.Unlock()
|
|
|
|
if duration <= 0 {
|
|
return errors.New(logPrefix + "invalid pause duration")
|
|
}
|
|
|
|
if onlySPN {
|
|
if c.isPaused {
|
|
return errors.New(logPrefix + "cannot pause SPN separately when core is paused")
|
|
}
|
|
if !c.isPausedSPN && !c.instance.SPNGroup().Ready() {
|
|
return errors.New(logPrefix + "cannot pause SPN when it is not running")
|
|
}
|
|
}
|
|
|
|
c.stopResumeWorker()
|
|
defer func() {
|
|
if retErr == nil {
|
|
// start new resume worker (with new duration) if no error
|
|
c.startResumeWorker(duration)
|
|
}
|
|
}()
|
|
|
|
if !c.isPausedSPN {
|
|
if c.instance.SPNGroup().Ready() {
|
|
|
|
// TODO: the 'pause' state must not make permanent config changes.
|
|
// Consider possibility to not store permanent config changes.
|
|
// E.g. SPN enabled -> pause SPN -> restart PC/Portmaster -> SPN should be enabled again.
|
|
enabled := config.GetAsBool("spn/enable", false)
|
|
if enabled() {
|
|
config.SetConfigOption("spn/enable", false)
|
|
}
|
|
|
|
// Alternatively, we could directly stop SPN here:
|
|
// if c.instance.IsShuttingDown() {
|
|
// c.mgr.Warn("Skipping pause during shutdown")
|
|
// return nil
|
|
// }
|
|
// err := c.instance.SPNGroup().Stop()
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// c.mgr.Info("SPN paused")
|
|
|
|
c.isPausedSPN = true
|
|
}
|
|
}
|
|
|
|
if onlySPN {
|
|
return nil
|
|
}
|
|
if c.isPaused {
|
|
return nil
|
|
}
|
|
|
|
modulesToResume := []mgr.Module{
|
|
c.instance.Compat(),
|
|
c.instance.Interception(),
|
|
}
|
|
for _, m := range modulesToResume {
|
|
if err := m.Stop(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
c.mgr.Info("interception paused")
|
|
c.isPaused = true
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Control) impl_resume() error {
|
|
c.locker.Lock()
|
|
defer c.locker.Unlock()
|
|
|
|
c.stopResumeWorker()
|
|
|
|
if c.isPausedSPN {
|
|
|
|
// TODO: consider using event to handle "spn/enable" changes:
|
|
// module.instance.Config().EventConfigChange
|
|
enabled := config.GetAsBool("spn/enable", false)
|
|
if !enabled() {
|
|
config.SetConfigOption("spn/enable", true)
|
|
}
|
|
|
|
// Alternatively, we could directly start SPN here:
|
|
// if c.instance.IsShuttingDown() {
|
|
// c.mgr.Warn("Skipping resume during shutdown")
|
|
// return nil
|
|
// }
|
|
// if !c.instance.SPNGroup().Ready() {
|
|
// err := c.instance.SPNGroup().Start()
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// c.mgr.Info("SPN resumed")
|
|
// }
|
|
|
|
c.isPausedSPN = false
|
|
}
|
|
|
|
if c.isPaused {
|
|
modulesToResume := []mgr.Module{
|
|
c.instance.Interception(),
|
|
c.instance.Compat(),
|
|
}
|
|
|
|
for _, m := range modulesToResume {
|
|
if err := m.Start(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
c.mgr.Info("interception resumed")
|
|
c.isPaused = false
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// stopResumeWorker stops any existing resume worker.
|
|
// No thread safety, caller must hold c.locker.
|
|
func (c *Control) stopResumeWorker() {
|
|
c.pauseStartTime = time.Time{}
|
|
c.pauseDuration = 0
|
|
|
|
if c.pauseWorker != nil {
|
|
c.pauseWorker.Stop()
|
|
c.pauseWorker = nil
|
|
}
|
|
}
|
|
|
|
// startResumeWorker starts a worker that will resume normal operation after the specified duration.
|
|
// No thread safety, caller must hold c.locker.
|
|
func (c *Control) startResumeWorker(duration time.Duration) {
|
|
c.pauseStartTime = time.Now()
|
|
c.pauseDuration = duration
|
|
|
|
c.mgr.Info(fmt.Sprintf("Scheduling resume in %v", duration))
|
|
|
|
c.pauseWorker = c.mgr.NewWorkerMgr(
|
|
fmt.Sprintf("resume in %v", duration),
|
|
func(wc *mgr.WorkerCtx) error {
|
|
wc.Info("Resuming...")
|
|
return c.impl_resume()
|
|
},
|
|
nil).Delay(duration)
|
|
}
|