Skip to content

Commit

Permalink
collect and display bytes sent/recv per process
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
gustavo-iniguez-goya committed Apr 23, 2024
1 parent 2ec37ed commit 4cdf709
Show file tree
Hide file tree
Showing 22 changed files with 1,328 additions and 242 deletions.
52 changes: 27 additions & 25 deletions daemon/procmon/details.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func (p *Process) GetExtraInfo() error {
// ReadPPID obtains the pid of the parent process
func (p *Process) ReadPPID() {
// ReadFile + parse = ~40us
data, err := ioutil.ReadFile(p.pathStat)
data, err := ioutil.ReadFile(p.procPath[Stat])
if err != nil {
p.PPID = 0
return
Expand All @@ -143,7 +143,7 @@ func (p *Process) ReadComm() error {
if p.Comm != "" {
return nil
}
data, err := ioutil.ReadFile(p.pathComm)
data, err := ioutil.ReadFile(p.procPath[Comm])
if err != nil {
return err
}
Expand All @@ -156,7 +156,7 @@ func (p *Process) ReadCwd() error {
if p.CWD != "" {
return nil
}
link, err := os.Readlink(p.pathCwd)
link, err := os.Readlink(p.procPath[Cwd])
if err != nil {
return err
}
Expand All @@ -166,7 +166,7 @@ func (p *Process) ReadCwd() error {

// ReadEnv reads and parses the environment variables of a process.
func (p *Process) ReadEnv() {
data, err := ioutil.ReadFile(p.pathEnviron)
data, err := ioutil.ReadFile(p.procPath[Environ])
if err != nil {
return
}
Expand Down Expand Up @@ -200,7 +200,7 @@ func (p *Process) ReadPath() error {
defer func() {
if p.Path == "" {
// determine if this process might be of a kernel task.
if data, err := ioutil.ReadFile(p.pathMaps); err == nil && len(data) == 0 {
if data, err := ioutil.ReadFile(p.procPath[Maps]); err == nil && len(data) == 0 {
p.Path = KernelConnection
p.Args = append(p.Args, p.Comm)
return
Expand All @@ -209,12 +209,12 @@ func (p *Process) ReadPath() error {
}
}()

if _, err := os.Lstat(p.pathExe); err != nil {
if _, err := os.Lstat(p.procPath[Exe]); err != nil {
return err
}

// FIXME: this reading can give error: file name too long
link, err := os.Readlink(p.pathExe)
link, err := os.Readlink(p.procPath[Exe])
if err != nil {
return err
}
Expand All @@ -226,7 +226,7 @@ func (p *Process) ReadPath() error {
func (p *Process) SetPath(path string) {
p.Path = path
p.CleanPath()
p.RealPath = core.ConcatStrings(p.pathRoot, "/", p.Path)
p.RealPath = core.ConcatStrings(p.procPath[Root], "/", p.Path)
if core.Exists(p.RealPath) == false {
p.RealPath = p.Path
// p.CleanPath() ?
Expand All @@ -236,13 +236,13 @@ func (p *Process) SetPath(path string) {
// ReadCmdline reads the cmdline of the process from ProcFS /proc/<pid>/cmdline
// This file may be empty if the process is of a kernel task.
// It can also be empty for short-lived processes.
func (p *Process) ReadCmdline() {
func (p *Process) ReadCmdline() error {
if len(p.Args) > 0 {
return
return nil
}
data, err := ioutil.ReadFile(p.pathCmdline)
data, err := ioutil.ReadFile(p.procPath[Cmdline])
if err != nil || len(data) == 0 {
return
return fmt.Errorf("%s empty", p.procPath[Cmdline])
}
// XXX: remove this loop, and split by "\x00"
for i, b := range data {
Expand All @@ -259,6 +259,8 @@ func (p *Process) ReadCmdline() {
}
}
p.CleanArgs()

return nil
}

// CleanArgs applies fixes on the cmdline arguments.
Expand All @@ -271,7 +273,7 @@ func (p *Process) CleanArgs() {
}

func (p *Process) readDescriptors() {
f, err := os.Open(p.pathFd)
f, err := os.Open(p.procPath[Fd])
if err != nil {
return
}
Expand All @@ -283,7 +285,7 @@ func (p *Process) readDescriptors() {
tempFd := &procDescriptors{
Name: fd.Name(),
}
link, err := os.Readlink(core.ConcatStrings(p.pathFd, fd.Name()))
link, err := os.Readlink(core.ConcatStrings(p.procPath[Fd], fd.Name()))
if err != nil {
continue
}
Expand Down Expand Up @@ -311,7 +313,7 @@ func (p *Process) readDescriptors() {
}

func (p *Process) readIOStats() (err error) {
f, err := os.Open(p.pathIO)
f, err := os.Open(p.procPath[IO])
if err != nil {
return err
}
Expand Down Expand Up @@ -342,19 +344,19 @@ func (p *Process) readIOStats() (err error) {
}

func (p *Process) readStatus() {
if data, err := ioutil.ReadFile(p.pathStatus); err == nil {
if data, err := ioutil.ReadFile(p.procPath[Status]); err == nil {
p.Status = string(data)
}
if data, err := ioutil.ReadFile(p.pathStat); err == nil {
if data, err := ioutil.ReadFile(p.procPath[Stat]); err == nil {
p.Stat = string(data)
}
if data, err := ioutil.ReadFile(core.ConcatStrings("/proc/", strconv.Itoa(p.ID), "/stack")); err == nil {
p.Stack = string(data)
}
if data, err := ioutil.ReadFile(p.pathMaps); err == nil {
if data, err := ioutil.ReadFile(p.procPath[Maps]); err == nil {
p.Maps = string(data)
}
if data, err := ioutil.ReadFile(p.pathStatm); err == nil {
if data, err := ioutil.ReadFile(p.procPath[Statm]); err == nil {
p.Statm = &procStatm{}
fmt.Sscanf(string(data), "%d %d %d %d %d %d %d", &p.Statm.Size, &p.Statm.Resident, &p.Statm.Shared, &p.Statm.Text, &p.Statm.Lib, &p.Statm.Data, &p.Statm.Dt)
}
Expand All @@ -372,7 +374,7 @@ func (p *Process) CleanPath() {
// to any process.
// Therefore we cannot use /proc/self/exe directly, because it resolves to our own process.
if strings.HasPrefix(p.Path, ProcSelf) {
if link, err := os.Readlink(p.pathExe); err == nil {
if link, err := os.Readlink(p.procPath[Exe]); err == nil {
p.Path = link
return
}
Expand All @@ -390,7 +392,7 @@ func (p *Process) CleanPath() {
}

// We may receive relative paths from kernel, but the path of a process must be absolute
if core.IsAbsPath(p.Path) == false {
if pathLen > 0 && core.IsAbsPath(p.Path) == false {
if err := p.ReadPath(); err != nil {
log.Debug("ClenPath() error reading process path%s", err)
return
Expand All @@ -401,7 +403,7 @@ func (p *Process) CleanPath() {

// IsAlive checks if the process is still running
func (p *Process) IsAlive() bool {
return core.Exists(p.pathProc)
return core.Exists(p.procPath[ProcID])
}

// IsChild determines if this process is child of its parent
Expand Down Expand Up @@ -459,7 +461,7 @@ func (p *Process) ComputeChecksum(algo string) {
// Path cannot be trusted, because multiple processes with the same path
// can coexist in different namespaces.
// The real path is /proc/<pid>/root/<path-to-the-binary>
paths := []string{p.pathExe, p.RealPath, p.Path}
paths := []string{p.procPath[Exe], p.RealPath, p.Path}

var h hash.Hash
if algo == HashMD5 {
Expand Down Expand Up @@ -530,7 +532,7 @@ func (p *Process) dumpFileImage(filePath string) ([]byte, error) {
var mappings []MemoryMapping

// read memory mappings
mapsFile, err := os.Open(p.pathMaps)
mapsFile, err := os.Open(p.procPath[Maps])
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -593,7 +595,7 @@ func (p *Process) dumpFileImage(filePath string) ([]byte, error) {
// given a range of addrs, read it from mem and return the content
func (p *Process) readMem(mappings []MemoryMapping) ([]byte, error) {
var elfCode []byte
memFile, err := os.Open(p.pathMem)
memFile, err := os.Open(p.procPath[Mem])
if err != nil {
return nil, err
}
Expand Down
82 changes: 70 additions & 12 deletions daemon/procmon/ebpf/ebpf.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,28 @@ import (
"fmt"
"sync"
"syscall"
"time"
"unsafe"

"github.com/evilsocket/opensnitch/daemon/core"
"github.com/evilsocket/opensnitch/daemon/log"
daemonNetlink "github.com/evilsocket/opensnitch/daemon/netlink"
"github.com/evilsocket/opensnitch/daemon/procmon"
"github.com/evilsocket/opensnitch/daemon/ui/protocol"
elf "github.com/iovisor/gobpf/elf"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
)

//contains pointers to ebpf maps for a given protocol (tcp/udp/v6)
// contains pointers to ebpf maps for a given protocol (tcp/udp/v6)
type ebpfMapsForProto struct {
bpfmap *elf.Map
}

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

//mimics union bpf_attr's anonymous struct used by BPF_MAP_*_ELEM commands
//from <linux_headers>/include/uapi/linux/bpf.h
// mimics union bpf_attr's anonymous struct used by BPF_MAP_*_ELEM commands
// from <linux_headers>/include/uapi/linux/bpf.h
type bpf_lookup_elem_t struct {
map_fd uint64 //even though in bpf.h its type is __u32, we must make it 8 bytes long
//because "key" is of type __aligned_u64, i.e. "key" must be aligned on an 8-byte boundary
Expand All @@ -47,8 +50,8 @@ const (

// Error returns the error type and a message with the explanation
type Error struct {
What int // 1 global error, 2 events error, 3 ...
Msg error
What int // 1 global error, 2 events error, 3 ...
}

var (
Expand Down Expand Up @@ -76,22 +79,22 @@ var (
hostByteOrder binary.ByteOrder
)

//Start installs ebpf kprobes
// Start installs ebpf kprobes
func Start(modPath string) *Error {
modulesPath = modPath

setRunning(false)
if err := mountDebugFS(); err != nil {
return &Error{
NotAvailable,
fmt.Errorf("ebpf.Start: mount debugfs error. Report on github please: %s", err),
NotAvailable,
}
}
var err error
m, err = core.LoadEbpfModule("opensnitch.o", modulesPath)
if err != nil {
dispatchErrorEvent(fmt.Sprint("[eBPF]: ", err.Error()))
return &Error{NotAvailable, fmt.Errorf("[eBPF] Error loading opensnitch.o: %s", err.Error())}
return &Error{fmt.Errorf("[eBPF] Error loading opensnitch.o: %s", err.Error()), NotAvailable}
}
m.EnableOptionCompatProbe()

Expand All @@ -101,10 +104,10 @@ func Start(modPath string) *Error {
if err := m.EnableKprobes(0); err != nil {
m.Close()
if err := m.Load(nil); err != nil {
return &Error{NotAvailable, fmt.Errorf("eBPF failed to load /etc/opensnitchd/opensnitch.o (2): %v", err)}
return &Error{fmt.Errorf("eBPF failed to load /etc/opensnitchd/opensnitch.o (2): %v", err), NotAvailable}
}
if err := m.EnableKprobes(0); err != nil {
return &Error{NotAvailable, fmt.Errorf("eBPF error when enabling kprobes: %v", err)}
return &Error{fmt.Errorf("eBPF error when enabling kprobes: %v", err), NotAvailable}
}
}
determineHostByteOrder()
Expand All @@ -121,7 +124,7 @@ func Start(modPath string) *Error {
}
for prot, mfp := range ebpfMaps {
if mfp.bpfmap == nil {
return &Error{NotAvailable, fmt.Errorf("eBPF module opensnitch.o malformed, bpfmap[%s] nil", prot)}
return &Error{fmt.Errorf("eBPF module opensnitch.o malformed, bpfmap[%s] nil", prot), NotAvailable}
}
}

Expand Down Expand Up @@ -200,7 +203,7 @@ func Stop() {
}
}

//make bpf() syscall with bpf_lookup prepared by the caller
// make bpf() syscall with bpf_lookup prepared by the caller
func makeBpfSyscall(bpf_lookup *bpf_lookup_elem_t) uintptr {
BPF_MAP_LOOKUP_ELEM := 1 //cmd number
syscall_BPF := 321 //syscall number
Expand All @@ -211,9 +214,64 @@ func makeBpfSyscall(bpf_lookup *bpf_lookup_elem_t) uintptr {
return r1
}

// dispatch a rx/tx event to the server
func dispatchRxTxEvent(proc *procmon.Process, proto uint32, fam uint8, bsent, brecv uint64) {
protoProc := proc.Serialize()
protoStr := "tcp"
if proto == unix.IPPROTO_UDP {
protoStr = "udp"
}
family := ""
if fam == unix.AF_INET6 {
family = "6"
}
// send only the bytes received of the packet(s), not the totals of the process.
protoProc.BytesSent = map[string]uint64{protoStr + family: bsent}
protoProc.BytesRecv = map[string]uint64{protoStr + family: brecv}
log.Debug("dispatchProcExit, proto: %s, sent: %d, recv: %d", protoStr, bsent, brecv)

dispatchEvent(
&protocol.Alert{
Id: uint64(time.Now().UnixNano()),
Type: protocol.Alert_INFO,
Action: protocol.Alert_SAVE_TO_DB,
What: protocol.Alert_KERNEL_NET_RXTX,
// TODO: send a KernelEvent{ KernelNetEvent }
Data: &protocol.Alert_Proc{
protoProc,
},
},
)
}

func dispatchProcExitEvent(proc *procmon.Process, proto uint32, fam uint8, bsent, brecv uint64) {
protoProc := proc.Serialize()
log.Debug("dispatchProcExit: %s", proc.Path)

dispatchEvent(
&protocol.Alert{
Id: uint64(time.Now().UnixNano()),
Type: protocol.Alert_INFO,
Action: protocol.Alert_SAVE_TO_DB,
What: protocol.Alert_KERNEL_PROC_EXIT,
Data: &protocol.Alert_Proc{
protoProc,
},
},
)
}

func dispatchErrorEvent(what string) {
log.Error(what)
dispatchEvent(what)
dispatchEvent(
&protocol.Alert{
Id: uint64(time.Now().UnixNano()),
Type: protocol.Alert_ERROR,
Action: protocol.Alert_SHOW_ALERT,
What: protocol.Alert_GENERIC,
Data: &protocol.Alert_Text{what},
},
)
}

func dispatchEvent(data interface{}) {
Expand Down
Loading

0 comments on commit 4cdf709

Please sign in to comment.