mirror of
https://github.com/evilsocket/opensnitch.git
synced 2026-06-08 20:27:34 +00:00
New feature to collect and display bytes sent/received per process.
- it only works with 'ebpf' monitor method.
- the information is collected on kernel space and sent to the daemon:
- when the connection socket is closed.
- every 2s on large transfers. On this case the bytes are
accumulated.
The daemon sends the events to the server (GUI), where the information
is added to the DB.
The information is displayed on the GUI:
- on the statusbar in real-time (based on the refresh interval defined).
- on the Applications tab.
By right clicking on the Applications tab headers, the user can reset
Tx/Rx stats, and grouping bytes per unit (default) or not.
Finally, the rx/tx stats are deleted based on the preferences options.
319 lines
6.9 KiB
Go
319 lines
6.9 KiB
Go
package procmon
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/evilsocket/opensnitch/daemon/core"
|
|
"github.com/evilsocket/opensnitch/daemon/ui/protocol"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
var (
|
|
lock = sync.RWMutex{}
|
|
monitorMethod = MethodProc
|
|
Ctx, CancelTasks = context.WithCancel(context.Background())
|
|
)
|
|
|
|
// monitor method supported types
|
|
const (
|
|
MethodProc = "proc"
|
|
MethodAudit = "audit"
|
|
MethodEbpf = "ebpf"
|
|
|
|
KernelConnection = "Kernel connection"
|
|
ProcSelf = "/proc/self/"
|
|
|
|
HashMD5 = "process.hash.md5"
|
|
HashSHA1 = "process.hash.sha1"
|
|
)
|
|
|
|
// ..
|
|
const (
|
|
ProcID = iota
|
|
Comm
|
|
Cmdline
|
|
Exe
|
|
Cwd
|
|
Environ
|
|
Root
|
|
Status
|
|
Statm
|
|
Stat
|
|
Mem
|
|
Maps
|
|
Fd
|
|
IO
|
|
)
|
|
|
|
// man 5 proc; man procfs
|
|
type procIOstats struct {
|
|
RChar int64
|
|
WChar int64
|
|
SyscallRead int64
|
|
SyscallWrite int64
|
|
ReadBytes int64
|
|
WriteBytes int64
|
|
}
|
|
|
|
type procNetStats struct {
|
|
ReadBytes uint64
|
|
WriteBytes uint64
|
|
}
|
|
|
|
type procDescriptors struct {
|
|
ModTime time.Time
|
|
Name string
|
|
SymLink string
|
|
Size int64
|
|
}
|
|
|
|
type procStatm struct {
|
|
Size int64
|
|
Resident int64
|
|
Shared int64
|
|
Text int64
|
|
Lib int64
|
|
Data int64 // data + stack
|
|
Dt int
|
|
}
|
|
|
|
type procBytes struct {
|
|
sent uint64
|
|
recv uint64
|
|
proto uint8
|
|
fam uint8
|
|
}
|
|
|
|
// Process holds the details of a process.
|
|
type Process struct {
|
|
mu *sync.RWMutex
|
|
Statm *procStatm
|
|
Parent *Process
|
|
IOStats *procIOstats
|
|
NetStats *procNetStats
|
|
Env map[string]string
|
|
BytesSent map[string]uint64
|
|
BytesRecv map[string]uint64
|
|
Checksums map[string]string
|
|
CWD string
|
|
Status string
|
|
Stat string
|
|
Stack string
|
|
Maps string
|
|
Comm string
|
|
|
|
// Path is the absolute path to the binary
|
|
Path string
|
|
|
|
// RealPath is the path to the binary taking into account its root fs.
|
|
// The simplest form of accessing the RealPath is by prepending /proc/<pid>/root/ to the path:
|
|
// /usr/bin/curl -> /proc/<pid>/root/usr/bin/curl
|
|
RealPath string
|
|
|
|
Tree []*protocol.StringInt
|
|
Descriptors []*procDescriptors
|
|
// Args is the command that the user typed. It MAY contain the absolute path
|
|
// of the binary:
|
|
// $ curl https://...
|
|
// -> Path: /usr/bin/curl
|
|
// -> Args: curl https://....
|
|
// $ /usr/bin/curl https://...
|
|
// -> Path: /usr/bin/curl
|
|
// -> Args: /usr/bin/curl https://....
|
|
Args []string
|
|
procPath []string
|
|
Starttime int64
|
|
ID int
|
|
PPID int
|
|
UID int
|
|
}
|
|
|
|
// NewProcessEmpty returns a new Process struct with no details.
|
|
func NewProcessEmpty(pid int, comm string) *Process {
|
|
p := &Process{
|
|
mu: &sync.RWMutex{},
|
|
Starttime: time.Now().UnixNano(),
|
|
ID: pid,
|
|
PPID: 0,
|
|
Comm: comm,
|
|
Args: make([]string, 0),
|
|
procPath: make([]string, 14),
|
|
Env: make(map[string]string),
|
|
BytesSent: make(map[string]uint64, 2),
|
|
BytesRecv: make(map[string]uint64, 2),
|
|
Tree: make([]*protocol.StringInt, 0),
|
|
IOStats: &procIOstats{},
|
|
NetStats: &procNetStats{},
|
|
Statm: &procStatm{},
|
|
Checksums: make(map[string]string),
|
|
}
|
|
|
|
p.procPath[ProcID] = core.ConcatStrings("/proc/", strconv.Itoa(p.ID))
|
|
p.procPath[Exe] = core.ConcatStrings(p.procPath[ProcID], "/exe")
|
|
p.procPath[Cwd] = core.ConcatStrings(p.procPath[ProcID], "/cwd")
|
|
p.procPath[Comm] = core.ConcatStrings(p.procPath[ProcID], "/comm")
|
|
p.procPath[Cmdline] = core.ConcatStrings(p.procPath[ProcID], "/cmdline")
|
|
p.procPath[Environ] = core.ConcatStrings(p.procPath[ProcID], "/environ")
|
|
p.procPath[Status] = core.ConcatStrings(p.procPath[ProcID], "/status")
|
|
p.procPath[Statm] = core.ConcatStrings(p.procPath[ProcID], "/statm")
|
|
p.procPath[Root] = core.ConcatStrings(p.procPath[ProcID], "/root")
|
|
p.procPath[Maps] = core.ConcatStrings(p.procPath[ProcID], "/maps")
|
|
p.procPath[Stat] = core.ConcatStrings(p.procPath[ProcID], "/stat")
|
|
p.procPath[Mem] = core.ConcatStrings(p.procPath[ProcID], "/mem")
|
|
p.procPath[Fd] = core.ConcatStrings(p.procPath[ProcID], "/fd/")
|
|
p.procPath[IO] = core.ConcatStrings(p.procPath[ProcID], "/io")
|
|
|
|
return p
|
|
}
|
|
|
|
// NewProcess returns a new Process structure.
|
|
func NewProcess(pid int, comm string) *Process {
|
|
p := NewProcessEmpty(pid, comm)
|
|
if pid <= 0 {
|
|
return p
|
|
}
|
|
p.GetDetails()
|
|
p.GetParent()
|
|
p.BuildTree()
|
|
|
|
return p
|
|
}
|
|
|
|
// NewProcessWithParent returns a new Process structure.
|
|
func NewProcessWithParent(pid, ppid int, comm string) *Process {
|
|
p := NewProcessEmpty(pid, comm)
|
|
if pid <= 0 {
|
|
return p
|
|
}
|
|
p.PPID = ppid
|
|
p.GetDetails()
|
|
p.Parent = NewProcess(ppid, comm)
|
|
|
|
return p
|
|
}
|
|
|
|
// Lock locks this process for w+r
|
|
func (p *Process) Lock() {
|
|
p.mu.Lock()
|
|
}
|
|
|
|
// Unlock unlocks reading from this process
|
|
func (p *Process) Unlock() {
|
|
p.mu.Unlock()
|
|
}
|
|
|
|
// RLock locks this process for r
|
|
func (p *Process) RLock() {
|
|
p.mu.RLock()
|
|
}
|
|
|
|
// RUnlock unlocks reading from this process
|
|
func (p *Process) RUnlock() {
|
|
p.mu.RUnlock()
|
|
}
|
|
|
|
// AddBytes accumulates the bytes sent by this process
|
|
func (p *Process) AddBytes(fam uint8, proto uint32, sent, recv uint64) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
protoStr := "tcp"
|
|
if proto == unix.IPPROTO_UDP {
|
|
protoStr = "udp"
|
|
}
|
|
family := ""
|
|
if fam == unix.AF_INET6 {
|
|
family = "6"
|
|
}
|
|
|
|
p.BytesSent[protoStr+family] += recv
|
|
p.BytesRecv[protoStr+family] += recv
|
|
}
|
|
|
|
// Serialize transforms a Process object to gRPC protocol object
|
|
func (p *Process) Serialize() *protocol.Process {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
ioStats := p.IOStats
|
|
netStats := p.NetStats
|
|
if ioStats == nil {
|
|
ioStats = &procIOstats{}
|
|
}
|
|
if netStats == nil {
|
|
netStats = &procNetStats{}
|
|
}
|
|
|
|
// maps are referenced data types, we cannot assign a map to another
|
|
// an expect to be a copy.
|
|
bsent := make(map[string]uint64, len(p.BytesSent))
|
|
brecv := make(map[string]uint64, len(p.BytesRecv))
|
|
for k, v := range p.BytesSent {
|
|
bsent[k] = v
|
|
}
|
|
for k, v := range p.BytesRecv {
|
|
brecv[k] = v
|
|
}
|
|
|
|
return &protocol.Process{
|
|
Pid: uint64(p.ID),
|
|
Ppid: uint64(p.PPID),
|
|
Uid: uint64(p.UID),
|
|
Comm: p.Comm,
|
|
Path: p.Path,
|
|
Args: p.Args,
|
|
Env: p.Env,
|
|
Cwd: p.CWD,
|
|
Checksums: p.Checksums,
|
|
IoReads: uint64(ioStats.RChar),
|
|
IoWrites: uint64(ioStats.WChar),
|
|
NetReads: netStats.ReadBytes,
|
|
NetWrites: netStats.WriteBytes,
|
|
BytesSent: bsent,
|
|
BytesRecv: brecv,
|
|
Tree: p.Tree,
|
|
}
|
|
}
|
|
|
|
// SetMonitorMethod configures a new method for parsing connections.
|
|
func SetMonitorMethod(newMonitorMethod string) {
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
|
|
monitorMethod = newMonitorMethod
|
|
}
|
|
|
|
// GetMonitorMethod configures a new method for parsing connections.
|
|
func GetMonitorMethod() string {
|
|
lock.RLock()
|
|
defer lock.RUnlock()
|
|
|
|
return monitorMethod
|
|
}
|
|
|
|
// MethodIsEbpf returns if the process monitor method is eBPF.
|
|
func MethodIsEbpf() bool {
|
|
lock.RLock()
|
|
defer lock.RUnlock()
|
|
|
|
return monitorMethod == MethodEbpf
|
|
}
|
|
|
|
// MethodIsAudit returns if the process monitor method is eBPF.
|
|
func MethodIsAudit() bool {
|
|
lock.RLock()
|
|
defer lock.RUnlock()
|
|
|
|
return monitorMethod == MethodAudit
|
|
}
|
|
|
|
func methodIsProc() bool {
|
|
lock.RLock()
|
|
defer lock.RUnlock()
|
|
|
|
return monitorMethod == MethodProc
|
|
}
|