introducing daemon tasks

daemon tasks are actions that are executed in background by the daemon.

They're started from the GUI (server) via a Notification (protobuf),
with the type TASK_START (protobuf).

Once received in the daemon, the TaskManager starts the task in
background.

Tasks may run at interval times (every 5s, 2days, etc), until they
finish an operation, until a timeout, etc.

Each task has each own configuration options, which will customize the
behaviour of its operations.

In this version, if the GUI is closed, the daemon will stop all the
running tasks.
Each Task has a flag to ignore this behaviour, for example if they need
to run until they finish and only send a notification to the GUI,
instead of streaming data continuously to the GUI (server).

 - Up until now we only had one task that could be initiated from the GUI:
   the process monitor dialog. It has been migrated to a Task{}.
 - go.mod bumped to v1.20, to use unsafe string functions.
 - go.sum updated accordingly.
This commit is contained in:
Gustavo Iñiguez Goia 2024-09-25 01:00:38 +02:00
parent 05eed4ef04
commit 9e0f3a4797
Failed to generate hash of commit
11 changed files with 456 additions and 95 deletions

View file

@ -1,6 +1,6 @@
module github.com/evilsocket/opensnitch/daemon
go 1.17
go 1.20
require (
github.com/fsnotify/fsnotify v1.4.7

View file

@ -78,18 +78,13 @@ github.com/varlink/go v0.4.0 h1:+/BQoUO9eJK/+MTSHwFcJch7TMsb6N6Dqp6g0qaXXRo=
github.com/varlink/go v0.4.0/go.mod h1:DKg9Y2ctoNkesREGAEak58l+jOC6JU2aqZvUYs5DynU=
github.com/vishvananda/netlink v1.1.1-0.20220115184804-dd687eb2f2d4 h1:fB26rIBlWTVJyEB6ONHdoEvUbvwoudH0/cMEXHiD1RU=
github.com/vishvananda/netlink v1.1.1-0.20220115184804-dd687eb2f2d4/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -99,7 +94,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -121,13 +115,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -136,9 +125,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -162,31 +149,17 @@ golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -198,8 +171,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

55
daemon/tasks/base.go Normal file
View file

@ -0,0 +1,55 @@
package tasks
import (
"context"
)
// TaskBase holds the common fields of every task.
// Warning: don't define fields in tasks with these names.
type TaskBase struct {
Ctx context.Context
Cancel context.CancelFunc
Results chan interface{}
Errors chan error
StopChan chan struct{}
// Stop the task if the daemon is disconnected from the GUI (server).
// Some tasks don't need to run if the daemon is not connected to the GUI,
// like PIDMonitor, SocketsMonitor,etc.
// There might be other tasks that will perform some actions, and they
// may send a notification on finish.
StopOnDisconnect bool
}
// Task defines the interface for tasks that the task manager will execute.
type Task interface {
// Start starts the task, potentially running it asynchronously.
Start(ctx context.Context, cancel context.CancelFunc) error
// Stop stops the task.
Stop() error
Pause() error
Resume() error
// Results returns a channel that can be used to receive task results.
Results() <-chan interface{}
// channel used to send errors
Errors() <-chan error
}
// TaskNotification is the data we receive when a new task is started from
// the GUI (server).
// The notification.data field will contain a string like:
// '{"name": "...", "data": {"interval": "3s", "...": ...} }'
//
// where Name is the task to start, sa defined by the Name var of each task,
// and Data is the configuration of each task (a map[string]string, converted by the json package).
type TaskNotification struct {
// Data of the task.
Data map[string]string
// Name of the task.
Name string
}

20
daemon/tasks/doc.go Normal file
View file

@ -0,0 +1,20 @@
package tasks
// Copyright 2024 The OpenSnitch Authors. All rights reserved.
// Use of this source code is governed by the GPLv3
// license that can be found in the LICENSE file.
/*
Package tasks manages actions launched by/to the daemon.
These tasks are handled by the TaskManager, which is in charge of start new
task, update and stop them.
The name of each task serves as the unique key inside the task manager.
Some tasks will be unique, like SocketsMonitor, and others might have more than one instance, like "pid-monitor-123", "pid-monitor-987".
Tasks run in background.
Some tasks may run periodically (every 5s, every 2 days, ...), others will run until stop and others until a timeout.
*/

126
daemon/tasks/main.go Normal file
View file

@ -0,0 +1,126 @@
package tasks
import (
"context"
"fmt"
"sync"
"github.com/evilsocket/opensnitch/daemon/log"
)
// TaskManager manages a collection of tasks.
type TaskManager struct {
tasks map[string]Task
stopChan chan struct{}
mu sync.Mutex
}
// NewTaskManager creates a new task manager.
func NewTaskManager() *TaskManager {
return &TaskManager{
tasks: make(map[string]Task),
stopChan: make(chan struct{}),
}
}
// AddTask adds a new task to the task manager.
func (tm *TaskManager) AddTask(name string, task Task) (context.Context, error) {
tm.mu.Lock()
defer tm.mu.Unlock()
if _, ok := tm.tasks[name]; ok {
return nil, fmt.Errorf("task with name %s already exists", name)
}
tm.tasks[name] = task
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context, cancel context.CancelFunc) {
defer cancel()
err := task.Start(ctx, cancel)
if err != nil {
log.Debug("Failed to start task %s: %v\n", name, err)
return
}
select {
case <-tm.stopChan:
task.Stop()
case <-ctx.Done():
return
}
}(ctx, cancel)
return ctx, nil
}
// PauseAll pauses all tasks that don't need to run while the daemon is
// disconnected from the GUI (server).
// Things to take into account:
// - The GUI may have been closed, therefore, the GUI won't have the id of the
// paused notifications. So when we resume the tasks, the GUI won't know
// about those notifications.
func (tm *TaskManager) PauseAll() {
for name, task := range tm.tasks {
log.Debug("taskManager. Pausing task %s", name)
task.Pause()
}
}
// ResumeAll resumes paused tasks
func (tm *TaskManager) ResumeAll() {
for name, task := range tm.tasks {
log.Debug("taskManager. Resuming task %s", name)
task.Resume()
}
}
// StopAll stops all running tasks
// TODO: stop only tasks with a TTL while it's connected to the GUI (server)
// example of such tasks are: monitor PID, monitor node details, monitor listening/established connections, on-demand scan, ...
func (tm *TaskManager) StopAll() {
for name := range tm.tasks {
log.Debug("taskManager. Stopping task %s", name)
tm.RemoveTask(name)
}
}
// GetTask ...
func (tm *TaskManager) GetTask(name string) (tk Task, found bool) {
tk, found = tm.tasks[name]
return
}
// UpdateTask replaces and existing task, with a new one.
func (tm *TaskManager) UpdateTask(name string, task Task) (context.Context, error) {
if _, found := tm.GetTask(name); !found {
return nil, fmt.Errorf("task %s not found", name)
}
if err := tm.RemoveTask(name); err != nil {
return nil, fmt.Errorf("Error updating task %s (remove)", name)
}
if err, ctx := tm.AddTask(name, task); err != nil {
return err, ctx
}
return nil, fmt.Errorf("Error updating task %s", name)
}
// RemoveTask removes a task from the task manager.
func (tm *TaskManager) RemoveTask(name string) error {
tm.mu.Lock()
defer tm.mu.Unlock()
tk, ok := tm.tasks[name]
if !ok {
return fmt.Errorf("task with name %s does not exist", name)
}
tk.Stop()
delete(tm.tasks, name)
return nil
}
// Stop stops all running tasks.
func (tm *TaskManager) Stop() {
close(tm.stopChan)
}

View file

@ -0,0 +1,134 @@
package pidmonitor
import (
"context"
"encoding/json"
"fmt"
"time"
"unsafe"
"github.com/evilsocket/opensnitch/daemon/log"
"github.com/evilsocket/opensnitch/daemon/procmon"
"github.com/evilsocket/opensnitch/daemon/tasks"
)
// Name s the base name of this task.
// When adding a new task, it'll be created as "pid-monitor-" + <pid>
var Name = "pid-monitor"
// Config of this task
type Config struct {
Interval string
Pid int
}
// PIDMonitor monitors a process ID.
type PIDMonitor struct {
tasks.TaskBase
Ticker *time.Ticker
Interval string
Pid int
}
// New returns a new PIDMonitor
func New(pid int, interval string, stopOnDisconnect bool) (string, *PIDMonitor) {
return fmt.Sprint(Name, "-", pid), &PIDMonitor{
TaskBase: tasks.TaskBase{
Results: make(chan interface{}),
Errors: make(chan error),
StopChan: make(chan struct{}),
},
Pid: pid,
Interval: interval,
}
}
// Start ...
func (pm *PIDMonitor) Start(ctx context.Context, cancel context.CancelFunc) error {
pm.Ctx = ctx
pm.Cancel = cancel
p := &procmon.Process{}
item, found := procmon.EventsCache.IsInStoreByPID(pm.Pid)
if found {
newProc := item.Proc
p = &newProc
if len(p.Tree) == 0 {
p.GetParent()
p.BuildTree()
}
} else {
p = procmon.NewProcess(pm.Pid, "")
}
if pm.Interval == "" {
pm.Interval = "5s"
}
interval, err := time.ParseDuration(pm.Interval)
if err != nil {
return err
}
pm.Ticker = time.NewTicker(interval)
go func(ctx context.Context) {
for {
select {
case <-pm.StopChan:
goto Exit
case <-ctx.Done():
goto Exit
case <-pm.Ticker.C:
// TODO: errors counter, and exit on errors > X
if err := p.GetExtraInfo(); err != nil {
pm.TaskBase.Errors <- err
goto Exit
}
pJSON, err := json.Marshal(p)
if err != nil {
pm.TaskBase.Errors <- err
continue
}
// ~200µs (string()) vs ~60ns
pm.TaskBase.Results <- unsafe.String(unsafe.SliceData(pJSON), len(pJSON))
}
}
Exit:
log.Debug("[tasks.PIDMonitor] stopped (%d)", pm.Pid)
}(ctx)
return err
}
// Pause stops temporarily the task. For example it might be paused when the
// connection with the GUI (server) is closed.
func (pm *PIDMonitor) Pause() error {
// TODO
return nil
}
// Resume stopped tasks.
func (pm *PIDMonitor) Resume() error {
// TODO
return nil
}
// Stop ...
func (pm *PIDMonitor) Stop() error {
if pm.StopOnDisconnect {
log.Debug("[task.PIDMonitor] ignoring Stop()")
return nil
}
log.Debug("[task.PIDMonitor] Stop()")
pm.StopChan <- struct{}{}
pm.Cancel()
close(pm.TaskBase.Results)
close(pm.TaskBase.Errors)
return nil
}
// Results ...
func (pm *PIDMonitor) Results() <-chan interface{} {
return pm.TaskBase.Results
}
// Errors ...
func (pm *PIDMonitor) Errors() <-chan error {
return pm.TaskBase.Errors
}

View file

@ -13,6 +13,7 @@ import (
"github.com/evilsocket/opensnitch/daemon/procmon"
"github.com/evilsocket/opensnitch/daemon/rule"
"github.com/evilsocket/opensnitch/daemon/statistics"
"github.com/evilsocket/opensnitch/daemon/tasks"
"github.com/evilsocket/opensnitch/daemon/ui/auth"
"github.com/evilsocket/opensnitch/daemon/ui/config"
"github.com/evilsocket/opensnitch/daemon/ui/protocol"
@ -33,6 +34,8 @@ var (
clientErrorRule = rule.Create("ui.client.error", "", true, false, false, rule.Allow, rule.Once, dummyOperator)
maxQueuedAlerts = 1024
TaskMgr = tasks.NewTaskManager()
)
// Client holds the connection information of a client.

View file

@ -12,16 +12,15 @@ import (
"github.com/evilsocket/opensnitch/daemon/core"
"github.com/evilsocket/opensnitch/daemon/firewall"
"github.com/evilsocket/opensnitch/daemon/log"
"github.com/evilsocket/opensnitch/daemon/procmon"
"github.com/evilsocket/opensnitch/daemon/procmon/monitor"
"github.com/evilsocket/opensnitch/daemon/rule"
"github.com/evilsocket/opensnitch/daemon/tasks"
"github.com/evilsocket/opensnitch/daemon/tasks/pidmonitor"
"github.com/evilsocket/opensnitch/daemon/ui/config"
"github.com/evilsocket/opensnitch/daemon/ui/protocol"
"golang.org/x/net/context"
)
var stopMonitoringProcess = make(chan int)
// NewReply constructs a new protocol notification reply
func NewReply(rID uint64, replyCode protocol.NotificationReplyCode, data string) *protocol.NotificationReply {
return &protocol.NotificationReply{
@ -59,44 +58,37 @@ func (c *Client) getClientConfig() *protocol.ClientConfig {
}
}
func (c *Client) monitorProcessDetails(pid int, stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
p := &procmon.Process{}
item, found := procmon.EventsCache.IsInStoreByPID(pid)
if found {
newProc := item.Proc
p = &newProc
if len(p.Tree) == 0 {
p.GetParent()
p.BuildTree()
}
} else {
p = procmon.NewProcess(pid, "")
}
ticker := time.NewTicker(2 * time.Second)
for {
select {
case _pid := <-stopMonitoringProcess:
if _pid != pid {
continue
}
goto Exit
case <-ticker.C:
if err := p.GetExtraInfo(); err != nil {
c.sendNotificationReply(stream, notification.Id, notification.Data, err)
goto Exit
}
pJSON, err := json.Marshal(p)
notification.Data = string(pJSON)
if errs := c.sendNotificationReply(stream, notification.Id, notification.Data, err); errs != nil {
goto Exit
}
}
func (c *Client) monitorProcessDetails(pid int, interval string, stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
if !core.Exists(fmt.Sprint("/proc/", pid)) {
c.sendNotificationReply(stream, notification.Id, "", fmt.Errorf("The process is no longer running"))
return
}
Exit:
ticker.Stop()
taskName, pidMonTask := pidmonitor.New(pid, interval, true)
ctx, err := TaskMgr.AddTask(taskName, pidMonTask)
if err != nil {
c.sendNotificationReply(stream, notification.Id, "", err)
return
}
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
goto Exit
case err := <-pidMonTask.Errors():
c.sendNotificationReply(stream, notification.Id, "", err)
case temp := <-pidMonTask.Results():
data, ok := temp.(string)
if !ok {
goto Exit
}
c.sendNotificationReply(stream, notification.Id, data, nil)
}
}
Exit:
TaskMgr.RemoveTask(taskName)
}(ctx)
}
func (c *Client) handleActionChangeConfig(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
@ -176,28 +168,50 @@ func (c *Client) handleActionDeleteRule(stream protocol.UI_NotificationsClient,
c.sendNotificationReply(stream, notification.Id, "", err)
}
func (c *Client) handleActionMonitorProcess(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
pid, err := strconv.Atoi(notification.Data)
func (c *Client) handleActionTaskStart(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
var taskConf tasks.TaskNotification
err := json.Unmarshal([]byte(notification.Data), &taskConf)
if err != nil {
log.Error("parsing PID to monitor: %d, err: %s", pid, err)
log.Error("parsing TaskStart, err: %s, %s", err, notification.Data)
c.sendNotificationReply(stream, notification.Id, "", err)
return
}
if !core.Exists(fmt.Sprint("/proc/", pid)) {
c.sendNotificationReply(stream, notification.Id, "", fmt.Errorf("The process is no longer running"))
return
switch taskConf.Name {
case pidmonitor.Name:
pid, err := strconv.Atoi(taskConf.Data["pid"])
if err != nil {
log.Error("TaskStart.Data, PID err: %s, %v", err, taskConf)
c.sendNotificationReply(stream, notification.Id, "", err)
return
}
c.monitorProcessDetails(pid, taskConf.Data["interval"], stream, notification)
default:
log.Debug("TaskStart, unknown task: %v", taskConf)
//c.sendNotificationReply(stream, notification.Id, "", err)
}
go c.monitorProcessDetails(pid, stream, notification)
}
func (c *Client) handleActionStopMonitorProcess(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
pid, err := strconv.Atoi(notification.Data)
func (c *Client) handleActionTaskStop(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
var taskConf tasks.TaskNotification
err := json.Unmarshal([]byte(notification.Data), &taskConf)
if err != nil {
log.Error("parsing PID to stop monitor: %d, err: %s", pid, err)
c.sendNotificationReply(stream, notification.Id, "", fmt.Errorf("Error stopping monitor: %s", notification.Data))
log.Error("parsing TaskStop, err: %s, %s", err, notification.Data)
c.sendNotificationReply(stream, notification.Id, "", fmt.Errorf("Error stopping task: %s", notification.Data))
return
}
stopMonitoringProcess <- pid
c.sendNotificationReply(stream, notification.Id, "", nil)
switch taskConf.Name {
case pidmonitor.Name:
pid, err := strconv.Atoi(taskConf.Data["pid"])
if err != nil {
log.Error("TaskStop.Data, err: %s, %s, %v+, %q", err, notification.Data, taskConf.Data, taskConf.Data)
c.sendNotificationReply(stream, notification.Id, "", err)
return
}
TaskMgr.RemoveTask(fmt.Sprint(taskConf.Name, "-", pid))
default:
log.Debug("TaskStop, unknown task: %v", taskConf)
//c.sendNotificationReply(stream, notification.Id, "", err)
}
}
func (c *Client) handleActionEnableInterception(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
@ -269,11 +283,11 @@ func (c *Client) handleActionReloadFw(stream protocol.UI_NotificationsClient, no
func (c *Client) handleNotification(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
switch {
case notification.Type == protocol.Action_MONITOR_PROCESS:
c.handleActionMonitorProcess(stream, notification)
case notification.Type == protocol.Action_TASK_START:
c.handleActionTaskStart(stream, notification)
case notification.Type == protocol.Action_STOP_MONITOR_PROCESS:
c.handleActionStopMonitorProcess(stream, notification)
case notification.Type == protocol.Action_TASK_STOP:
c.handleActionTaskStop(stream, notification)
case notification.Type == protocol.Action_CHANGE_CONFIG:
c.handleActionChangeConfig(stream, notification)
@ -384,4 +398,5 @@ Exit:
notisStream.CloseSend()
log.Info("Stop receiving notifications")
c.disconnect()
TaskMgr.StopAll()
}

View file

@ -164,6 +164,8 @@ message Rule {
Operator operator = 9;
}
/* Action is the list of actions sent or received via the Notifications channel.
*/
enum Action {
NONE = 0;
ENABLE_INTERCEPTION = 1;
@ -178,8 +180,18 @@ enum Action {
CHANGE_RULE = 10;
LOG_LEVEL = 11;
STOP = 12;
MONITOR_PROCESS = 13;
STOP_MONITOR_PROCESS = 14;
/* Notifications of type TASK_START or TASK_STOP expect a JSON in the
* Notification.data field.
* It's parsed to a struct with format
* TaskNotification { Name string, Data interface{} }
* where Data is translated to a map of values (map[string]string), with
* the configuration for each task:
* PidMonitor: {"interval": "5s", "pid": "1234"}
* SocketsMonitor: {"interval": "5s", "states": "1,10"}
*/
TASK_START = 13;
TASK_STOP = 14;
}
message StatementValues {
@ -248,7 +260,16 @@ message ClientConfig {
SysFirewall systemFirewall = 8;
}
// notification sent to the clients (daemons)
/* Notification message is sent to the clients (daemons) from the GUI (server)
* for several purposes:
* - Start tasks.
* - Change configurations (rules, firewall, configuration)
* - Start / Stop interception or firewall.
* - Sent back the status of each task (errors, ok)
*
* Notifications are sent via an always open streaming channel.
* It's also used indirectly to maintain the connection status with the GUI (server).
*/
message Notification {
uint64 id = 1;
string clientName = 2;

View file

@ -132,10 +132,14 @@ class ProcessDetailsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0])
self._delete_notification(reply.id)
return
if noti.type == ui_pb2.MONITOR_PROCESS and reply.data != "":
if noti.type != ui_pb2.TASK_START:
print("proc-details, invalid notification type?", noti)
return
if noti.type == ui_pb2.TASK_START and reply.data != "":
self._load_data(reply.data)
elif noti.type == ui_pb2.STOP_MONITOR_PROCESS:
elif noti.type == ui_pb2.TASK_STOP:
if reply.data != "":
self._show_message(
QtCore.QCoreApplication.translate(
@ -233,7 +237,12 @@ class ProcessDetailsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0])
return
self._set_button_running(True)
noti = ui_pb2.Notification(clientName="", serverName="", type=ui_pb2.MONITOR_PROCESS, data=self._pid, rules=[])
noti = ui_pb2.Notification(
clientName="",
serverName="",
type=ui_pb2.TASK_START,
data=self._build_notification_message(self._pid),
rules=[])
self._nid = self._nodes.send_notification(self._pids[self._pid], noti, self._notification_callback)
self._notifications_sent[self._nid] = noti
except Exception as e:
@ -244,7 +253,12 @@ class ProcessDetailsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0])
return
self._set_button_running(False)
noti = ui_pb2.Notification(clientName="", serverName="", type=ui_pb2.STOP_MONITOR_PROCESS, data=str(self._pid), rules=[])
noti = ui_pb2.Notification(
clientName="",
serverName="",
type=ui_pb2.TASK_STOP,
data=self._build_notification_message(self._pid),
rules=[])
self._nid = self._nodes.send_notification(self._pids[self._pid], noti, self._notification_callback)
self._notifications_sent[self._nid] = noti
self._pid = ""
@ -379,4 +393,6 @@ class ProcessDetailsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0])
self._set_tab_text(self.TAB_ENVS, text)
def _build_notification_message(self, pid):
# TODO: make interval configurable
return '{"name": "pid-monitor", "data": { "interval": "5s", "pid": "%s" }}' % pid

View file

@ -334,7 +334,7 @@ class Nodes(QObject):
# delete only one-time notifications
# we need the ID of streaming notifications from the server
# (monitor_process for example) to keep track of the data sent to us.
if self._notifications_sent[reply.id]['type'] != ui_pb2.MONITOR_PROCESS:
if self._notifications_sent[reply.id]['type'] != ui_pb2.TASK_START:
del self._notifications_sent[reply.id]
except Exception as e:
print(self.LOG_TAG, "notification exception:", e)