Skip to content

Commit eede54c

Browse files
allow to customize ebpf options
Allow to customize: - EventsWorkers: number of goroutines to handle kernel events. Default 8. - QueueEventsSize: max number of events in the queue. By default 0, meaning that it'll relay on the available goroutines to process the events. If it's > 0, and the daemon can't process the events fast enough, they'll be queued. Once the queue is full, it'll behave as it was of size 0. If there're lost events, a message will be logged: "Lost ebpf events..."
1 parent 8436144 commit eede54c

File tree

8 files changed

+78
-47
lines changed

8 files changed

+78
-47
lines changed

daemon/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ func main() {
614614
// overwrite monitor method from configuration if the user has passed
615615
// the option via command line.
616616
if procmonMethod != "" {
617-
if err := monitor.ReconfigureMonitorMethod(procmonMethod, cfg.Ebpf.ModulesPath); err != nil {
617+
if err := monitor.ReconfigureMonitorMethod(procmonMethod, cfg.Ebpf); err != nil {
618618
msg := fmt.Sprintf("Unable to set process monitor method via parameter: %v", err)
619619
uiClient.SendWarningAlert(msg)
620620
log.Warning(msg)

daemon/procmon/ebpf/config.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package ebpf
2+
3+
import "github.com/evilsocket/opensnitch/daemon/log"
4+
5+
// Config holds the configuration to customize ebpf module behaviour.
6+
type Config struct {
7+
ModulesPath string `json:"ModulesPath"`
8+
9+
// system default value is 8, but it's not enough to handle "high" loads such
10+
// http downloads, torrent traffic, etc. (just regular desktop usage)
11+
// We set it to 64 by default (* PAGE_SIZE, which is usually 4a).
12+
RingBuffSize int `json:"RingBuffSize"`
13+
14+
// number of workers to handle events from kernel
15+
EventsWorkers int `json:"EventsWorkers"`
16+
17+
// max number of events in the queue received from the kernel.
18+
// 0 - Default behaviour. Each goroutine will wait for incoming messages, to
19+
// dispatch them one at a time.
20+
// > 0 - same as above, but if the daemon is not fast enough to dispatch the
21+
// events, they'll be queued. Once the daemon queue is full, kernel ebpf program
22+
// will have to wait/discard new events. (XXX: citation/testing needed).
23+
QueueEventsSize int `json:"QueueEventsSize"`
24+
}
25+
26+
func setConfig(ebpfOpts Config) {
27+
ebpfCfg = ebpfOpts
28+
29+
// ModulesPath defined in core.ebpf
30+
// QueueEventsSize defaults to 0
31+
32+
if ebpfCfg.EventsWorkers == 0 {
33+
ebpfCfg.EventsWorkers = 8
34+
}
35+
36+
if ebpfCfg.RingBuffSize == 0 {
37+
ebpfCfg.RingBuffSize = 64
38+
}
39+
40+
log.Debug("[eBPF] config loaded: %v", ebpfCfg)
41+
}

daemon/procmon/ebpf/ebpf.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ import (
1616
"github.com/vishvananda/netlink"
1717
)
1818

19-
//contains pointers to ebpf maps for a given protocol (tcp/udp/v6)
19+
// contains pointers to ebpf maps for a given protocol (tcp/udp/v6)
2020
type ebpfMapsForProto struct {
2121
bpfmap *elf.Map
2222
}
2323

2424
//Not in use, ~4usec faster lookup compared to m.LookupElement()
2525

26-
//mimics union bpf_attr's anonymous struct used by BPF_MAP_*_ELEM commands
27-
//from <linux_headers>/include/uapi/linux/bpf.h
26+
// mimics union bpf_attr's anonymous struct used by BPF_MAP_*_ELEM commands
27+
// from <linux_headers>/include/uapi/linux/bpf.h
2828
type bpf_lookup_elem_t struct {
2929
map_fd uint64 //even though in bpf.h its type is __u32, we must make it 8 bytes long
3030
//because "key" is of type __aligned_u64, i.e. "key" must be aligned on an 8-byte boundary
@@ -52,11 +52,11 @@ type Error struct {
5252
}
5353

5454
var (
55-
m, perfMod *elf.Module
56-
lock = sync.RWMutex{}
57-
mapSize = uint(12000)
58-
ebpfMaps map[string]*ebpfMapsForProto
59-
modulesPath string
55+
m, perfMod *elf.Module
56+
ebpfCfg Config
57+
lock = sync.RWMutex{}
58+
mapSize = uint(12000)
59+
ebpfMaps map[string]*ebpfMapsForProto
6060

6161
//connections which were established at the time when opensnitch started
6262
alreadyEstablished = alreadyEstablishedConns{
@@ -76,9 +76,9 @@ var (
7676
hostByteOrder binary.ByteOrder
7777
)
7878

79-
//Start installs ebpf kprobes
80-
func Start(modPath string) *Error {
81-
modulesPath = modPath
79+
// Start installs ebpf kprobes
80+
func Start(ebpfOpts Config) *Error {
81+
setConfig(ebpfOpts)
8282

8383
setRunning(false)
8484
if err := mountDebugFS(); err != nil {
@@ -88,7 +88,7 @@ func Start(modPath string) *Error {
8888
}
8989
}
9090
var err error
91-
m, err = core.LoadEbpfModule("opensnitch.o", modulesPath)
91+
m, err = core.LoadEbpfModule("opensnitch.o", ebpfCfg.ModulesPath)
9292
if err != nil {
9393
dispatchErrorEvent(fmt.Sprint("[eBPF]: ", err.Error()))
9494
return &Error{NotAvailable, fmt.Errorf("[eBPF] Error loading opensnitch.o: %s", err.Error())}
@@ -180,10 +180,6 @@ func Stop() {
180180
cancelTasks()
181181
ebpfCache.clear()
182182

183-
if m != nil {
184-
m.Close()
185-
}
186-
187183
for pm := range perfMapList {
188184
if pm != nil {
189185
pm.PollStop()
@@ -195,12 +191,16 @@ func Stop() {
195191
delete(perfMapList, k)
196192
}
197193
}
194+
if m != nil {
195+
m.Close()
196+
}
197+
198198
if perfMod != nil {
199199
perfMod.Close()
200200
}
201201
}
202202

203-
//make bpf() syscall with bpf_lookup prepared by the caller
203+
// make bpf() syscall with bpf_lookup prepared by the caller
204204
func makeBpfSyscall(bpf_lookup *bpf_lookup_elem_t) uintptr {
205205
BPF_MAP_LOOKUP_ELEM := 1 //cmd number
206206
syscall_BPF := 321 //syscall number

daemon/procmon/ebpf/events.go

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -64,21 +64,14 @@ const (
6464

6565
var (
6666
perfMapList = make(map[*elf.PerfMap]*elf.Module)
67-
// total workers spawned by the different events PerfMaps
68-
eventWorkers = 0
69-
perfMapName = "proc-events"
70-
71-
// default value is 8.
72-
// Not enough to handle high loads such http downloads, torent traffic, etc.
73-
// (regular desktop usage)
74-
ringBuffSize = 64 // * PAGE_SIZE (4k usually)
67+
perfMapName = "proc-events"
7568
)
7669

7770
func initEventsStreamer() *Error {
7871
elfOpts := make(map[string]elf.SectionParams)
79-
elfOpts["maps/"+perfMapName] = elf.SectionParams{PerfRingBufferPageCount: ringBuffSize}
72+
elfOpts["maps/"+perfMapName] = elf.SectionParams{PerfRingBufferPageCount: ebpfCfg.RingBuffSize}
8073
var err error
81-
perfMod, err = core.LoadEbpfModule("opensnitch-procs.o", modulesPath)
74+
perfMod, err = core.LoadEbpfModule("opensnitch-procs.o", ebpfCfg.ModulesPath)
8275
if err != nil {
8376
dispatchErrorEvent(fmt.Sprint("[eBPF events]: ", err))
8477
return &Error{EventsNotAvailable, err}
@@ -110,6 +103,7 @@ Verify that your kernel has support for tracepoints (opensnitchd -check-requirem
110103
}
111104

112105
if err = perfMod.EnableKprobes(0); err != nil {
106+
log.Error("Error enabling kprobe: %s", err)
113107
// if previous shutdown was unclean, then we must remove the dangling kprobe
114108
// and install it again (close the module and load it again)
115109
perfMod.Close()
@@ -128,7 +122,6 @@ Verify that your kernel has support for tracepoints (opensnitchd -check-requirem
128122
<-sig
129123
}(sig)
130124

131-
eventWorkers = 0
132125
if err := initPerfMap(perfMod); err != nil {
133126
return &Error{EventsNotAvailable, err}
134127
}
@@ -137,7 +130,7 @@ Verify that your kernel has support for tracepoints (opensnitchd -check-requirem
137130
}
138131

139132
func initPerfMap(mod *elf.Module) error {
140-
perfChan := make(chan []byte)
133+
perfChan := make(chan []byte, ebpfCfg.QueueEventsSize)
141134
lostEvents := make(chan uint64, 1)
142135
var err error
143136
perfMap, err := elf.InitPerfMap(mod, perfMapName, perfChan, lostEvents)
@@ -147,8 +140,7 @@ func initPerfMap(mod *elf.Module) error {
147140
}
148141
perfMapList[perfMap] = mod
149142

150-
eventWorkers += 8
151-
for i := 0; i < eventWorkers; i++ {
143+
for i := 0; i < ebpfCfg.EventsWorkers; i++ {
152144
go streamEventsWorker(i, perfChan, lostEvents, kernelEvents)
153145
}
154146
perfMap.PollStart()
@@ -174,7 +166,7 @@ func streamEventsWorker(id int, chn chan []byte, lost chan uint64, kernelEvents
174166
case <-ctxTasks.Done():
175167
goto Exit
176168
case l := <-lost:
177-
log.Debug("Lost ebpf events: %d", l)
169+
log.Debug("Lost ebpf events (try increasing QueueEventsSize/EventsWorkers): %d", l)
178170
case d := <-chn:
179171
if err := binary.Read(bytes.NewBuffer(d), hostByteOrder, &event); err != nil {
180172
log.Debug("[eBPF events #%d] error: %s", id, err)

daemon/procmon/monitor/init.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func stopProcMonitors() {
5050
}
5151

5252
// ReconfigureMonitorMethod configures a new method for parsing connections.
53-
func ReconfigureMonitorMethod(newMonitorMethod, ebpfModulesPath string) *Error {
53+
func ReconfigureMonitorMethod(newMonitorMethod string, ebpfCfg ebpf.Config) *Error {
5454
oldMethod := procmon.GetMonitorMethod()
5555
if oldMethod == "" {
5656
oldMethod = procmon.MethodProc
@@ -60,7 +60,7 @@ func ReconfigureMonitorMethod(newMonitorMethod, ebpfModulesPath string) *Error {
6060
// if the new monitor method fails to start, rollback the change and exit
6161
// without saving the configuration. Otherwise we can end up with the wrong
6262
// monitor method configured and saved to file.
63-
err := Init(ebpfModulesPath)
63+
err := Init(ebpfCfg)
6464
if err.What > NoError {
6565
log.Error("Reconf() -> Init() error: %v", err)
6666
procmon.SetMonitorMethod(oldMethod)
@@ -72,6 +72,7 @@ func ReconfigureMonitorMethod(newMonitorMethod, ebpfModulesPath string) *Error {
7272

7373
// End stops the way of parsing new connections.
7474
func End() {
75+
log.Debug("monitor.End()")
7576
stopProcMonitors()
7677
if procmon.MethodIsAudit() {
7778
audit.Stop()
@@ -81,7 +82,7 @@ func End() {
8182
}
8283

8384
// Init starts parsing connections using the method specified.
84-
func Init(ebpfModulesPath string) (errm *Error) {
85+
func Init(ebpfCfg ebpf.Config) (errm *Error) {
8586
errm = &Error{}
8687

8788
if cacheMonitorsRunning == false {
@@ -90,7 +91,7 @@ func Init(ebpfModulesPath string) (errm *Error) {
9091
}
9192

9293
if procmon.MethodIsEbpf() {
93-
err := ebpf.Start(ebpfModulesPath)
94+
err := ebpf.Start(ebpfCfg)
9495
if err == nil {
9596
log.Info("Process monitor method ebpf")
9697
return errm

daemon/ui/config/config.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/evilsocket/opensnitch/daemon/log"
1111
"github.com/evilsocket/opensnitch/daemon/log/loggers"
12+
"github.com/evilsocket/opensnitch/daemon/procmon/ebpf"
1213
"github.com/evilsocket/opensnitch/daemon/statistics"
1314
)
1415

@@ -61,11 +62,6 @@ type (
6162
QueueNum uint16 `json:"QueueNum"`
6263
}
6364

64-
// EbpfOptions struct
65-
EbpfOptions struct {
66-
ModulesPath string `json:"ModulesPath"`
67-
}
68-
6965
// InternalOptions struct
7066
InternalOptions struct {
7167
GCPercent int `json:"GCPercent"`
@@ -81,7 +77,7 @@ type Config struct {
8177
DefaultDuration string `json:"DefaultDuration"`
8278
ProcMonitorMethod string `json:"ProcMonitorMethod"`
8379
FwOptions FwOptions `json:"FwOptions"`
84-
Ebpf EbpfOptions `json:"Ebpf"`
80+
Ebpf ebpf.Config `json:"Ebpf"`
8581
Server ServerConfig `json:"Server"`
8682
Rules RulesOptions `json:"Rules"`
8783
Internal InternalOptions `json:"Internal"`

daemon/ui/config_utils.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,14 +197,15 @@ func (c *Client) reloadConfiguration(reload bool, newConfig config.Config) *moni
197197
log.Debug("[config] config.ProcMonMethod not changed")
198198
}
199199

200-
if reload && procmon.MethodIsEbpf() && newConfig.Ebpf.ModulesPath != "" && c.config.Ebpf.ModulesPath != newConfig.Ebpf.ModulesPath {
201-
log.Debug("[config] reloading config.Ebpf.ModulesPath: %s", newConfig.Ebpf.ModulesPath)
200+
if reload && procmon.MethodIsEbpf() &&
201+
!reflect.DeepEqual(newConfig.Ebpf, c.config.Ebpf) {
202+
log.Debug("[config] reloading config.Ebpf: %v", newConfig.Ebpf)
202203
reloadProc = true
203204
} else {
204205
log.Debug("[config] config.Ebpf.ModulesPath not changed")
205206
}
206207
if reloadProc {
207-
err := monitor.ReconfigureMonitorMethod(newConfig.ProcMonitorMethod, newConfig.Ebpf.ModulesPath)
208+
err := monitor.ReconfigureMonitorMethod(newConfig.ProcMonitorMethod, newConfig.Ebpf)
208209
if err != nil && err.What > monitor.NoError {
209210
return err
210211
}

daemon/ui/notifications.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ func (c *Client) handleActionStopMonitorProcess(stream protocol.UI_Notifications
202202

203203
func (c *Client) handleActionEnableInterception(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
204204
log.Info("[notification] starting interception")
205-
if err := monitor.ReconfigureMonitorMethod(c.config.ProcMonitorMethod, c.config.Ebpf.ModulesPath); err != nil && err.What > monitor.NoError {
205+
if err := monitor.ReconfigureMonitorMethod(c.config.ProcMonitorMethod, c.config.Ebpf); err != nil && err.What > monitor.NoError {
206206
log.Warning("[notification] error enabling monitor (%s): %s", c.config.ProcMonitorMethod, err.Msg)
207207
c.sendNotificationReply(stream, notification.Id, "", err.Msg)
208208
return

0 commit comments

Comments
 (0)