mirror of
https://github.com/evilsocket/opensnitch.git
synced 2025-03-04 08:34:40 +01:00
Use ebpf program to find PID of new connections. (#397)
* Use ebpf program to find PID of new connections. before running the branch you have to compile ebpf_prog/opensnitch.c opensnitch.c is an eBPF program. Compilation requires getting kernel source. cd opensnitch wget https://github.com/torvalds/linux/archive/v5.8.tar.gz tar -xf v5.8.tar.gz patch linux-5.8/tools/lib/bpf/bpf_helpers.h < ebpf_prog/file.patch cp ebpf_prog/opensnitch.c ebpf_prog/Makefile linux-5.8/samples/bpf cd linux-5.8 && yes "" | make oldconfig && make prepare && make headers_install # (1 min) cd samples/bpf && make objdump -h opensnitch.o #you should see many section, number 1 should be called kprobe/tcp_v4_connect llvm-strip -g opensnitch.o #remove debug info sudo cp opensnitch.o /etc/opensnitchd cd ../../../daemon --opensnitchd expects to find opensnitch.o in /etc/opensnitchd/ --start opensnitchd with: opensnitchd -rules-path /etc/opensnitchd/rules -process-monitor-method ebpf Co-authored-by: themighty1 <you@example.com> Co-authored-by: Gustavo Iñiguez Goia <gooffy1@gmail.com>
This commit is contained in:
parent
148526e527
commit
9497cf8394
14 changed files with 1319 additions and 93 deletions
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/evilsocket/opensnitch/daemon/netlink"
|
"github.com/evilsocket/opensnitch/daemon/netlink"
|
||||||
"github.com/evilsocket/opensnitch/daemon/netstat"
|
"github.com/evilsocket/opensnitch/daemon/netstat"
|
||||||
"github.com/evilsocket/opensnitch/daemon/procmon"
|
"github.com/evilsocket/opensnitch/daemon/procmon"
|
||||||
|
"github.com/evilsocket/opensnitch/daemon/procmon/ebpf"
|
||||||
"github.com/evilsocket/opensnitch/daemon/ui/protocol"
|
"github.com/evilsocket/opensnitch/daemon/ui/protocol"
|
||||||
|
|
||||||
"github.com/google/gopacket/layers"
|
"github.com/google/gopacket/layers"
|
||||||
|
@ -81,24 +82,47 @@ func newConnectionImpl(nfp *netfilter.Packet, c *Connection, protoType string) (
|
||||||
INode: -1,
|
INode: -1,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0. lookup uid and inode via netlink. Can return several inodes.
|
pid := -1
|
||||||
// 1. lookup uid and inode using /proc/net/(udp|tcp|udplite)
|
var uid int
|
||||||
// 2. lookup pid by inode
|
if procmon.MethodIsEbpf() {
|
||||||
// 3. if this is coming from us, just accept
|
pid, uid, err = ebpf.GetPid(c.Protocol, c.SrcPort, c.SrcIP, c.DstIP, c.DstPort)
|
||||||
// 4. lookup process info by pid
|
if err != nil {
|
||||||
uid, inodeList := netlink.GetSocketInfo(c.Protocol, c.SrcIP, c.SrcPort, c.DstIP, c.DstPort)
|
log.Warning("ebpf warning: %v", err)
|
||||||
if len(inodeList) == 0 {
|
return nil, nil
|
||||||
if c.Entry = netstat.FindEntry(c.Protocol, c.SrcIP, c.SrcPort, c.DstIP, c.DstPort); c.Entry == nil {
|
|
||||||
return nil, fmt.Errorf("Could not find netstat entry for: %s", c)
|
|
||||||
}
|
}
|
||||||
if c.Entry.INode > 0 {
|
}
|
||||||
log.Debug("connection found in netstat: %v", c.Entry)
|
// sometimes when using eBPF the connection is not found, but falling back to legacy
|
||||||
inodeList = append([]int{c.Entry.INode}, inodeList...)
|
// methods helps to find it and avoid "unknown/kernel pop-ups". TODO: investigate
|
||||||
|
if pid == -1 {
|
||||||
|
// 0. lookup uid and inode via netlink. Can return several inodes.
|
||||||
|
// 1. lookup uid and inode using /proc/net/(udp|tcp|udplite)
|
||||||
|
// 2. lookup pid by inode
|
||||||
|
// 3. if this is coming from us, just accept
|
||||||
|
// 4. lookup process info by pid
|
||||||
|
var inodeList []int
|
||||||
|
uid, inodeList = netlink.GetSocketInfo(c.Protocol, c.SrcIP, c.SrcPort, c.DstIP, c.DstPort)
|
||||||
|
if len(inodeList) == 0 {
|
||||||
|
if c.Entry = netstat.FindEntry(c.Protocol, c.SrcIP, c.SrcPort, c.DstIP, c.DstPort); c.Entry == nil {
|
||||||
|
return nil, fmt.Errorf("Could not find netstat entry for: %s", c)
|
||||||
|
}
|
||||||
|
if c.Entry.INode > 0 {
|
||||||
|
log.Debug("connection found in netstat: %v", c.Entry)
|
||||||
|
inodeList = append([]int{c.Entry.INode}, inodeList...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(inodeList) == 0 {
|
||||||
|
log.Debug("<== no inodes found, applying default action.")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for n, inode := range inodeList {
|
||||||
|
pid = procmon.GetPIDFromINode(inode, fmt.Sprint(inode, c.SrcIP, c.SrcPort, c.DstIP, c.DstPort))
|
||||||
|
if pid != -1 {
|
||||||
|
log.Debug("[%d] PID found %d", n, pid)
|
||||||
|
c.Entry.INode = inode
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if len(inodeList) == 0 {
|
|
||||||
log.Debug("<== no inodes found, applying default action.")
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if uid != -1 {
|
if uid != -1 {
|
||||||
|
@ -107,26 +131,18 @@ func newConnectionImpl(nfp *netfilter.Packet, c *Connection, protoType string) (
|
||||||
c.Entry.UserId = int(nfp.UID)
|
c.Entry.UserId = int(nfp.UID)
|
||||||
}
|
}
|
||||||
|
|
||||||
pid := -1
|
if pid == os.Getpid() {
|
||||||
for n, inode := range inodeList {
|
// return a Process object with our PID, to be able to exclude our own connections
|
||||||
if pid = procmon.GetPIDFromINode(inode, fmt.Sprint(inode, c.SrcIP, c.SrcPort, c.DstIP, c.DstPort)); pid == os.Getpid() {
|
// (to the UI on a local socket for example)
|
||||||
// return a Process object with our PID, to be able to exclude our own connections
|
c.Process = procmon.NewProcess(pid, "")
|
||||||
// (to the UI on a local socket for example)
|
return c, nil
|
||||||
c.Process = procmon.NewProcess(pid, "")
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
if pid != -1 {
|
|
||||||
log.Debug("[%d] PID found %d", n, pid)
|
|
||||||
c.Entry.INode = inode
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Process = procmon.FindProcess(pid, showUnknownCons); c.Process == nil {
|
if c.Process = procmon.FindProcess(pid, showUnknownCons); c.Process == nil {
|
||||||
return nil, fmt.Errorf("Could not find process by its pid %d for: %s", pid, c)
|
return nil, fmt.Errorf("Could not find process by its pid %d for: %s", pid, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConnection creates a new Connection object, and returns the details of it.
|
// NewConnection creates a new Connection object, and returns the details of it.
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/evilsocket/opensnitch/daemon/log"
|
"github.com/evilsocket/opensnitch/daemon/log"
|
||||||
"github.com/evilsocket/opensnitch/daemon/netfilter"
|
"github.com/evilsocket/opensnitch/daemon/netfilter"
|
||||||
"github.com/evilsocket/opensnitch/daemon/procmon"
|
"github.com/evilsocket/opensnitch/daemon/procmon"
|
||||||
|
"github.com/evilsocket/opensnitch/daemon/procmon/monitor"
|
||||||
"github.com/evilsocket/opensnitch/daemon/rule"
|
"github.com/evilsocket/opensnitch/daemon/rule"
|
||||||
"github.com/evilsocket/opensnitch/daemon/statistics"
|
"github.com/evilsocket/opensnitch/daemon/statistics"
|
||||||
"github.com/evilsocket/opensnitch/daemon/ui"
|
"github.com/evilsocket/opensnitch/daemon/ui"
|
||||||
|
@ -62,7 +63,7 @@ var (
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&showVersion, "version", debug, "Show daemon version of this executable and exit.")
|
flag.BoolVar(&showVersion, "version", debug, "Show daemon version of this executable and exit.")
|
||||||
|
|
||||||
flag.StringVar(&procmonMethod, "process-monitor-method", procmonMethod, "How to search for processes path. Options: ftrace, audit (experimental), proc (default)")
|
flag.StringVar(&procmonMethod, "process-monitor-method", procmonMethod, "How to search for processes path. Options: ftrace, audit (experimental), ebpf (experimental), proc (default)")
|
||||||
flag.StringVar(&uiSocket, "ui-socket", uiSocket, "Path the UI gRPC service listener (https://github.com/grpc/grpc/blob/master/doc/naming.md).")
|
flag.StringVar(&uiSocket, "ui-socket", uiSocket, "Path the UI gRPC service listener (https://github.com/grpc/grpc/blob/master/doc/naming.md).")
|
||||||
flag.StringVar(&rulesPath, "rules-path", rulesPath, "Path to load JSON rules from.")
|
flag.StringVar(&rulesPath, "rules-path", rulesPath, "Path to load JSON rules from.")
|
||||||
flag.IntVar(&queueNum, "queue-num", queueNum, "Netfilter queue number.")
|
flag.IntVar(&queueNum, "queue-num", queueNum, "Netfilter queue number.")
|
||||||
|
@ -156,7 +157,7 @@ func setupWorkers() {
|
||||||
func doCleanup(queue, repeatQueue *netfilter.Queue) {
|
func doCleanup(queue, repeatQueue *netfilter.Queue) {
|
||||||
log.Info("Cleaning up ...")
|
log.Info("Cleaning up ...")
|
||||||
firewall.Stop(&queueNum)
|
firewall.Stop(&queueNum)
|
||||||
procmon.End()
|
monitor.End()
|
||||||
uiClient.Close()
|
uiClient.Close()
|
||||||
queue.Close()
|
queue.Close()
|
||||||
repeatQueue.Close()
|
repeatQueue.Close()
|
||||||
|
@ -387,7 +388,7 @@ func main() {
|
||||||
if procmonMethod != "" {
|
if procmonMethod != "" {
|
||||||
procmon.SetMonitorMethod(procmonMethod)
|
procmon.SetMonitorMethod(procmonMethod)
|
||||||
}
|
}
|
||||||
procmon.Init()
|
monitor.Init()
|
||||||
|
|
||||||
// queue is ready, run firewall rules
|
// queue is ready, run firewall rules
|
||||||
firewall.Init(&queueNum)
|
firewall.Init(&queueNum)
|
||||||
|
|
|
@ -23,10 +23,10 @@ var (
|
||||||
activePidsLock = sync.RWMutex{}
|
activePidsLock = sync.RWMutex{}
|
||||||
)
|
)
|
||||||
|
|
||||||
//monitorActivePids checks that each process in activePids
|
//MonitorActivePids checks that each process in activePids
|
||||||
//is still running and if not running (or another process with the same pid is running),
|
//is still running and if not running (or another process with the same pid is running),
|
||||||
//removes the pid from activePids
|
//removes the pid from activePids
|
||||||
func monitorActivePids() {
|
func MonitorActivePids() {
|
||||||
for {
|
for {
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
activePidsLock.Lock()
|
activePidsLock.Lock()
|
||||||
|
|
|
@ -110,7 +110,7 @@ func deleteInodeEntry(pid int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cacheCleanerTask() {
|
func CacheCleanerTask() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-cacheTicker.C:
|
case <-cacheTicker.C:
|
||||||
|
|
535
daemon/procmon/ebpf/ebpf.go
Normal file
535
daemon/procmon/ebpf/ebpf.go
Normal file
|
@ -0,0 +1,535 @@
|
||||||
|
package ebpf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/evilsocket/opensnitch/daemon/log"
|
||||||
|
"github.com/evilsocket/opensnitch/daemon/procmon"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
|
||||||
|
daemonNetlink "github.com/evilsocket/opensnitch/daemon/netlink"
|
||||||
|
elf "github.com/iovisor/gobpf/elf"
|
||||||
|
)
|
||||||
|
|
||||||
|
//contains pointers to ebpf maps for a given protocol (tcp/udp/v6)
|
||||||
|
type ebpfMapsForProto struct {
|
||||||
|
counterMap *elf.Map
|
||||||
|
bpfmap *elf.Map
|
||||||
|
lastPurgedMax uint64 // max counter value up to and including which the map was purged on the last purge
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
m *elf.Module
|
||||||
|
mapSize = 12000
|
||||||
|
ebpfMaps map[string]*ebpfMapsForProto
|
||||||
|
//connections which were established at the time when opensnitch started
|
||||||
|
alreadyEstablishedTCP = make(map[*daemonNetlink.Socket]int)
|
||||||
|
alreadyEstablishedTCPv6 = make(map[*daemonNetlink.Socket]int)
|
||||||
|
//stop == true is a signal for all goroutines to stop
|
||||||
|
stop = false
|
||||||
|
// list of local addresses of this machine
|
||||||
|
localAddresses []net.IP
|
||||||
|
localAddressesLock sync.RWMutex
|
||||||
|
hostByteOrder binary.ByteOrder
|
||||||
|
)
|
||||||
|
|
||||||
|
//Start installs ebpf kprobes
|
||||||
|
func Start() error {
|
||||||
|
m = elf.NewModule("/etc/opensnitchd/opensnitch.o")
|
||||||
|
if err := m.Load(nil); err != nil {
|
||||||
|
log.Error("Failed to load /etc/opensnitchd/opensnitch.o", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if previous shutdown was unclean, then we must remove the dangling kprobe
|
||||||
|
// and install it again (close the module and load it again)
|
||||||
|
if err := m.EnableKprobes(0); err != nil {
|
||||||
|
m.Close()
|
||||||
|
if err := m.Load(nil); err != nil {
|
||||||
|
log.Error("Failed to load /etc/opensnitchd/opensnitch.o", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := m.EnableKprobes(0); err != nil {
|
||||||
|
log.Error("Error when enabling kprobes", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// init all connection counters to 0
|
||||||
|
zeroKey := make([]byte, 4)
|
||||||
|
zeroValue := make([]byte, 8)
|
||||||
|
for _, name := range []string{"tcpcounter", "tcpv6counter", "udpcounter", "udpv6counter"} {
|
||||||
|
err := m.UpdateElement(m.Map(name), unsafe.Pointer(&zeroKey[0]), unsafe.Pointer(&zeroValue[0]), 0)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Could not init counters to zero", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//determine host byte order
|
||||||
|
buf := [2]byte{}
|
||||||
|
*(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD)
|
||||||
|
switch buf {
|
||||||
|
case [2]byte{0xCD, 0xAB}:
|
||||||
|
hostByteOrder = binary.LittleEndian
|
||||||
|
case [2]byte{0xAB, 0xCD}:
|
||||||
|
hostByteOrder = binary.BigEndian
|
||||||
|
default:
|
||||||
|
log.Error("Could not determine host byte order.")
|
||||||
|
}
|
||||||
|
|
||||||
|
ebpfMaps = map[string]*ebpfMapsForProto{
|
||||||
|
"tcp": {lastPurgedMax: 0,
|
||||||
|
counterMap: m.Map("tcpcounter"),
|
||||||
|
bpfmap: m.Map("tcpMap")},
|
||||||
|
"tcp6": {lastPurgedMax: 0,
|
||||||
|
counterMap: m.Map("tcpv6counter"),
|
||||||
|
bpfmap: m.Map("tcpv6Map")},
|
||||||
|
"udp": {lastPurgedMax: 0,
|
||||||
|
counterMap: m.Map("udpcounter"),
|
||||||
|
bpfmap: m.Map("udpMap")},
|
||||||
|
"udp6": {lastPurgedMax: 0,
|
||||||
|
counterMap: m.Map("udpv6counter"),
|
||||||
|
bpfmap: m.Map("udpv6Map")},
|
||||||
|
}
|
||||||
|
|
||||||
|
// save already established connections
|
||||||
|
socketListTCP, err := daemonNetlink.SocketsDump(uint8(syscall.AF_INET), uint8(syscall.IPPROTO_TCP))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Could not dump TCP sockets via netlink", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, sock := range socketListTCP {
|
||||||
|
inode := int((*sock).INode)
|
||||||
|
pid := procmon.GetPIDFromINode(inode, fmt.Sprint(inode,
|
||||||
|
(*sock).ID.Source, (*sock).ID.SourcePort, (*sock).ID.Destination, (*sock).ID.DestinationPort))
|
||||||
|
alreadyEstablishedTCP[sock] = pid
|
||||||
|
}
|
||||||
|
|
||||||
|
socketListTCPv6, err := daemonNetlink.SocketsDump(uint8(syscall.AF_INET6), uint8(syscall.IPPROTO_TCP))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Could not dump TCPv6 sockets via netlink", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, sock := range socketListTCPv6 {
|
||||||
|
inode := int((*sock).INode)
|
||||||
|
pid := procmon.GetPIDFromINode(inode, fmt.Sprint(inode,
|
||||||
|
(*sock).ID.Source, (*sock).ID.SourcePort, (*sock).ID.Destination, (*sock).ID.DestinationPort))
|
||||||
|
alreadyEstablishedTCPv6[sock] = pid
|
||||||
|
}
|
||||||
|
|
||||||
|
go monitorMaps()
|
||||||
|
go monitorLocalAddresses()
|
||||||
|
go monitorAlreadyEstablished()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Stop() {
|
||||||
|
stop = true
|
||||||
|
if m != nil {
|
||||||
|
m.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete all map's elements which have counter value <= maxToDelete
|
||||||
|
func deleteOld(bpfmap *elf.Map, isIPv6 bool, maxToDelete uint64) {
|
||||||
|
var lookupKey []byte
|
||||||
|
var nextKey []byte
|
||||||
|
var value []byte
|
||||||
|
if !isIPv6 {
|
||||||
|
lookupKey = make([]byte, 12)
|
||||||
|
nextKey = make([]byte, 12)
|
||||||
|
value = make([]byte, 24)
|
||||||
|
} else {
|
||||||
|
lookupKey = make([]byte, 36)
|
||||||
|
nextKey = make([]byte, 36)
|
||||||
|
value = make([]byte, 24)
|
||||||
|
}
|
||||||
|
firstrun := true
|
||||||
|
i := 0
|
||||||
|
for {
|
||||||
|
i++
|
||||||
|
if i > 12000 {
|
||||||
|
// there were more iterations than the max amount of elements in map
|
||||||
|
// TODO find out what causes the endless loop
|
||||||
|
// maybe because ebpf prog modified the map while we were iterating
|
||||||
|
log.Error("Breaking because endless loop was detected in deleteOld")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ok, err := m.LookupNextElement(bpfmap, unsafe.Pointer(&lookupKey[0]),
|
||||||
|
unsafe.Pointer(&nextKey[0]), unsafe.Pointer(&value[0]))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("LookupNextElement error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if firstrun {
|
||||||
|
// on first run lookupKey is a dummy, nothing to delete
|
||||||
|
firstrun = false
|
||||||
|
copy(lookupKey, nextKey)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// last 8 bytes of value is counter value
|
||||||
|
counterValue := hostByteOrder.Uint64(value[16:24])
|
||||||
|
if counterValue > maxToDelete {
|
||||||
|
copy(lookupKey, nextKey)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := m.DeleteElement(bpfmap, unsafe.Pointer(&lookupKey[0])); err != nil {
|
||||||
|
log.Error("DeleteElement error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok { //reached end of map
|
||||||
|
break
|
||||||
|
}
|
||||||
|
copy(lookupKey, nextKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to manually remove old connections from a bpf map
|
||||||
|
// since when a bpf map is full it doesn't allow any more insertions
|
||||||
|
func monitorMaps() {
|
||||||
|
zeroKey := make([]byte, 4)
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Second * 1)
|
||||||
|
if stop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for name, ebpfMap := range ebpfMaps {
|
||||||
|
value := make([]byte, 8)
|
||||||
|
if err := m.LookupElement(ebpfMap.counterMap,
|
||||||
|
unsafe.Pointer(&zeroKey[0]), unsafe.Pointer(&value[0])); err != nil {
|
||||||
|
log.Error("m.LookupElement", err)
|
||||||
|
}
|
||||||
|
counterValue := hostByteOrder.Uint64(value)
|
||||||
|
if counterValue-ebpfMap.lastPurgedMax > 10000 {
|
||||||
|
ebpfMap.lastPurgedMax = counterValue - 5000
|
||||||
|
deleteOld(ebpfMap.bpfmap, name == "tcp6" || name == "udp6", ebpfMap.lastPurgedMax)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPid looks up process pid in a bpf map. If not found there, then it searches
|
||||||
|
// already-established TCP connections.
|
||||||
|
func GetPid(proto string, srcPort uint, srcIP net.IP, dstIP net.IP, dstPort uint) (int, int, error) {
|
||||||
|
if hostByteOrder == nil {
|
||||||
|
return -1, -1, fmt.Errorf("eBPF monitoring method not initialized yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pid, uid := getPidFromEbpf(proto, srcPort, srcIP, dstIP, dstPort); pid != -1 {
|
||||||
|
return pid, uid, nil
|
||||||
|
}
|
||||||
|
//check if it comes from already established TCP
|
||||||
|
if proto == "tcp" || proto == "tcp6" {
|
||||||
|
if pid, uid, err := findInAlreadyEstablishedTCP(proto, srcPort, srcIP, dstIP, dstPort); err == nil {
|
||||||
|
return pid, uid, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//using netlink.GetSocketInfo to check if UID is 0 (in-kernel connection)
|
||||||
|
if uid, _ := daemonNetlink.GetSocketInfo(proto, srcIP, srcPort, dstIP, dstPort); uid == 0 {
|
||||||
|
return -100, -100, nil
|
||||||
|
}
|
||||||
|
if !findAddressInLocalAddresses(srcIP) {
|
||||||
|
// systemd-resolved sometimes makes a TCP Fast Open connection to a DNS server (8.8.8.8 on my machine)
|
||||||
|
// and we get a packet here with **source** (not detination!!!) IP 8.8.8.8
|
||||||
|
// Maybe it's an in-kernel response with spoofed IP because wireshark does not show neither
|
||||||
|
// resolved's TCP Fast Open packet, nor the response
|
||||||
|
// Until this is better understood, we simply do not allow this machine to make connections with
|
||||||
|
// arbitrary source IPs
|
||||||
|
return -1, -1, fmt.Errorf("Packet with unknown source IP: %s", srcIP)
|
||||||
|
}
|
||||||
|
return -1, -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPidFromEbpf looks up a connection in bpf map and returns PID if found
|
||||||
|
// the lookup keys and values are defined in opensnitch.c , e.g.
|
||||||
|
//
|
||||||
|
// struct tcp_key_t {
|
||||||
|
// u16 sport;
|
||||||
|
// u32 daddr;
|
||||||
|
// u16 dport;
|
||||||
|
// u32 saddr;
|
||||||
|
// }__attribute__((packed));
|
||||||
|
|
||||||
|
// struct tcp_value_t{
|
||||||
|
// u64 pid;
|
||||||
|
// u64 uid;
|
||||||
|
// u64 counter;
|
||||||
|
// }__attribute__((packed));;
|
||||||
|
|
||||||
|
func getPidFromEbpf(proto string, srcPort uint, srcIP net.IP, dstIP net.IP, dstPort uint) (pid int, uid int) {
|
||||||
|
var key []byte
|
||||||
|
var value []byte
|
||||||
|
var isIP4 bool = (proto == "tcp") || (proto == "udp") || (proto == "udplite")
|
||||||
|
|
||||||
|
if isIP4 {
|
||||||
|
key = make([]byte, 12)
|
||||||
|
value = make([]byte, 24)
|
||||||
|
copy(key[2:6], dstIP)
|
||||||
|
binary.BigEndian.PutUint16(key[6:8], uint16(dstPort))
|
||||||
|
copy(key[8:12], srcIP)
|
||||||
|
} else { // IPv6
|
||||||
|
key = make([]byte, 36)
|
||||||
|
value = make([]byte, 24)
|
||||||
|
copy(key[2:18], dstIP)
|
||||||
|
binary.BigEndian.PutUint16(key[18:20], uint16(dstPort))
|
||||||
|
copy(key[20:36], srcIP)
|
||||||
|
}
|
||||||
|
hostByteOrder.PutUint16(key[0:2], uint16(srcPort))
|
||||||
|
|
||||||
|
err := m.LookupElement(ebpfMaps[proto].bpfmap, unsafe.Pointer(&key[0]), unsafe.Pointer(&value[0]))
|
||||||
|
if err != nil {
|
||||||
|
// key not found
|
||||||
|
// maybe srcIP is 0.0.0.0 Happens especially with UDP sendto()
|
||||||
|
// TODO: can this happen with TCP?
|
||||||
|
if isIP4 {
|
||||||
|
zeroes := make([]byte, 4)
|
||||||
|
copy(key[8:12], zeroes)
|
||||||
|
} else {
|
||||||
|
zeroes := make([]byte, 16)
|
||||||
|
copy(key[20:36], zeroes)
|
||||||
|
}
|
||||||
|
err = m.LookupElement(ebpfMaps[proto].bpfmap, unsafe.Pointer(&key[0]), unsafe.Pointer(&value[0]))
|
||||||
|
}
|
||||||
|
if err != nil && proto == "udp" && srcIP.String() == dstIP.String() {
|
||||||
|
// very rarely I see this connection. It has srcIP and dstIP == 0.0.0.0 in ebpf map
|
||||||
|
// it is a localhost to localhost connection
|
||||||
|
// srcIP was already set to 0, set dstIP to zero also
|
||||||
|
// TODO try to reproduce it and look for srcIP/dstIP in other kernel structures
|
||||||
|
zeroes := make([]byte, 4)
|
||||||
|
copy(key[2:6], zeroes)
|
||||||
|
err = m.LookupElement(ebpfMaps[proto].bpfmap, unsafe.Pointer(&key[0]), unsafe.Pointer(&value[0]))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// key not found in bpf map
|
||||||
|
return -1, -1
|
||||||
|
}
|
||||||
|
pid = int(hostByteOrder.Uint32(value[0:4]))
|
||||||
|
uid = int(hostByteOrder.Uint32(value[8:12]))
|
||||||
|
return pid, uid
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindInAlreadyEstablishedTCP searches those TCP connections which were already established at the time
|
||||||
|
// when opensnitch started
|
||||||
|
func findInAlreadyEstablishedTCP(proto string, srcPort uint, srcIP net.IP, dstIP net.IP, dstPort uint) (int, int, error) {
|
||||||
|
var alreadyEstablished map[*daemonNetlink.Socket]int
|
||||||
|
if proto == "tcp" {
|
||||||
|
alreadyEstablished = alreadyEstablishedTCP
|
||||||
|
} else if proto == "tcp6" {
|
||||||
|
alreadyEstablished = alreadyEstablishedTCPv6
|
||||||
|
}
|
||||||
|
for sock, v := range alreadyEstablished {
|
||||||
|
if (*sock).ID.SourcePort == uint16(srcPort) && (*sock).ID.Source.Equal(srcIP) &&
|
||||||
|
(*sock).ID.Destination.Equal(dstIP) && (*sock).ID.DestinationPort == uint16(dstPort) {
|
||||||
|
return v, int((*sock).UID), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, -1, fmt.Errorf("Inode not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
//returns true if addr is in the list of this machine's addresses
|
||||||
|
func findAddressInLocalAddresses(addr net.IP) bool {
|
||||||
|
localAddressesLock.Lock()
|
||||||
|
defer localAddressesLock.Unlock()
|
||||||
|
for _, a := range localAddresses {
|
||||||
|
if addr.String() == a.String() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// maintains a list of this machine's local addresses
|
||||||
|
func monitorLocalAddresses() {
|
||||||
|
for {
|
||||||
|
addr, err := netlink.AddrList(nil, netlink.FAMILY_ALL)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error looking up this machine's addresses via netlink", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
localAddressesLock.Lock()
|
||||||
|
localAddresses = nil
|
||||||
|
for _, a := range addr {
|
||||||
|
localAddresses = append(localAddresses, a.IP)
|
||||||
|
}
|
||||||
|
localAddressesLock.Unlock()
|
||||||
|
time.Sleep(time.Second * 1)
|
||||||
|
if stop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// monitorAlreadyEstablished makes sure that when an already-established connection is closed
|
||||||
|
// it will be removed from alreadyEstablished. If we don't do this and keep the alreadyEstablished entry forever,
|
||||||
|
// then after the genuine process quits,a malicious process may reuse PID-srcPort-srcIP-dstPort-dstIP
|
||||||
|
func monitorAlreadyEstablished() {
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Second * 1)
|
||||||
|
if stop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
socketListTCP, err := daemonNetlink.SocketsDump(uint8(syscall.AF_INET), uint8(syscall.IPPROTO_TCP))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error in dumping TCP sockets via netlink")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for aesock := range alreadyEstablishedTCP {
|
||||||
|
found := false
|
||||||
|
for _, sock := range socketListTCP {
|
||||||
|
if (*aesock).INode == (*sock).INode &&
|
||||||
|
//inodes are unique enough, so the matches below will never have to be checked
|
||||||
|
(*aesock).ID.SourcePort == (*sock).ID.SourcePort &&
|
||||||
|
(*aesock).ID.Source.Equal((*sock).ID.Source) &&
|
||||||
|
(*aesock).ID.Destination.Equal((*sock).ID.Destination) &&
|
||||||
|
(*aesock).ID.DestinationPort == (*sock).ID.DestinationPort &&
|
||||||
|
(*aesock).UID == (*sock).UID {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
delete(alreadyEstablishedTCP, aesock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
socketListTCPv6, err := daemonNetlink.SocketsDump(uint8(syscall.AF_INET6), uint8(syscall.IPPROTO_TCP))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error in dumping TCPv6 sockets via netlink")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for aesock := range alreadyEstablishedTCPv6 {
|
||||||
|
found := false
|
||||||
|
for _, sock := range socketListTCPv6 {
|
||||||
|
if (*aesock).INode == (*sock).INode &&
|
||||||
|
//inodes are unique enough, so the matches below will never have to be checked
|
||||||
|
(*aesock).ID.SourcePort == (*sock).ID.SourcePort &&
|
||||||
|
(*aesock).ID.Source.Equal((*sock).ID.Source) &&
|
||||||
|
(*aesock).ID.Destination.Equal((*sock).ID.Destination) &&
|
||||||
|
(*aesock).ID.DestinationPort == (*sock).ID.DestinationPort &&
|
||||||
|
(*aesock).UID == (*sock).UID {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
delete(alreadyEstablishedTCPv6, aesock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//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
|
||||||
|
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
|
||||||
|
key uintptr
|
||||||
|
value uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
//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
|
||||||
|
sizeOfStruct := 24 //sizeof bpf_lookup_elem_t struct
|
||||||
|
|
||||||
|
r1, _, _ := syscall.Syscall(uintptr(syscall_BPF), uintptr(BPF_MAP_LOOKUP_ELEM),
|
||||||
|
uintptr(unsafe.Pointer(bpf_lookup)), uintptr(sizeOfStruct))
|
||||||
|
return r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// print map contents. used only for debugging
|
||||||
|
func dumpMap(bpfmap *elf.Map, isIPv6 bool) {
|
||||||
|
var lookupKey []byte
|
||||||
|
var nextKey []byte
|
||||||
|
var value []byte
|
||||||
|
if !isIPv6 {
|
||||||
|
lookupKey = make([]byte, 12)
|
||||||
|
nextKey = make([]byte, 12)
|
||||||
|
value = make([]byte, 24)
|
||||||
|
} else {
|
||||||
|
lookupKey = make([]byte, 36)
|
||||||
|
nextKey = make([]byte, 36)
|
||||||
|
value = make([]byte, 24)
|
||||||
|
}
|
||||||
|
firstrun := true
|
||||||
|
i := 0
|
||||||
|
for {
|
||||||
|
i++
|
||||||
|
ok, err := m.LookupNextElement(bpfmap, unsafe.Pointer(&lookupKey[0]),
|
||||||
|
unsafe.Pointer(&nextKey[0]), unsafe.Pointer(&value[0]))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("LookupNextElement error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if firstrun {
|
||||||
|
// on first run lookupKey is a dummy, nothing to delete
|
||||||
|
firstrun = false
|
||||||
|
copy(lookupKey, nextKey)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Println("key, value", lookupKey, value)
|
||||||
|
|
||||||
|
if !ok { //reached end of map
|
||||||
|
break
|
||||||
|
}
|
||||||
|
copy(lookupKey, nextKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//PrintEverything prints all the stats. used only for debugging
|
||||||
|
func PrintEverything() {
|
||||||
|
bash, _ := exec.LookPath("bash")
|
||||||
|
//get the number of the first map
|
||||||
|
out, err := exec.Command(bash, "-c", "bpftool map show | head -n 1 | cut -d ':' -f1").Output()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("bpftool map dump name tcpMap ", err)
|
||||||
|
}
|
||||||
|
i, _ := strconv.Atoi(string(out[:len(out)-1]))
|
||||||
|
fmt.Println("i is", i)
|
||||||
|
|
||||||
|
//dump all maps for analysis
|
||||||
|
for j := i; j < i+14; j++ {
|
||||||
|
_, _ = exec.Command(bash, "-c", "bpftool map dump id "+strconv.Itoa(j)+" > dump"+strconv.Itoa(j)).Output()
|
||||||
|
}
|
||||||
|
|
||||||
|
for sock1, v := range alreadyEstablishedTCP {
|
||||||
|
fmt.Println(*sock1, v)
|
||||||
|
}
|
||||||
|
fmt.Println("---------------------")
|
||||||
|
for sock1, v := range alreadyEstablishedTCPv6 {
|
||||||
|
fmt.Println(*sock1, v)
|
||||||
|
}
|
||||||
|
fmt.Println("---------------------")
|
||||||
|
sockets, _ := daemonNetlink.SocketsDump(syscall.AF_INET, syscall.IPPROTO_TCP)
|
||||||
|
for idx := range sockets {
|
||||||
|
fmt.Println("socket tcp: ", sockets[idx])
|
||||||
|
}
|
||||||
|
fmt.Println("---------------------")
|
||||||
|
sockets, _ = daemonNetlink.SocketsDump(syscall.AF_INET6, syscall.IPPROTO_TCP)
|
||||||
|
for idx := range sockets {
|
||||||
|
fmt.Println("socket tcp6: ", sockets[idx])
|
||||||
|
}
|
||||||
|
fmt.Println("---------------------")
|
||||||
|
sockets, _ = daemonNetlink.SocketsDump(syscall.AF_INET, syscall.IPPROTO_UDP)
|
||||||
|
for idx := range sockets {
|
||||||
|
fmt.Println("socket udp: ", sockets[idx])
|
||||||
|
}
|
||||||
|
fmt.Println("---------------------")
|
||||||
|
sockets, _ = daemonNetlink.SocketsDump(syscall.AF_INET6, syscall.IPPROTO_UDP)
|
||||||
|
for idx := range sockets {
|
||||||
|
fmt.Println("socket udp6: ", sockets[idx])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
77
daemon/procmon/monitor/init.go
Normal file
77
daemon/procmon/monitor/init.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package monitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/evilsocket/opensnitch/daemon/log"
|
||||||
|
"github.com/evilsocket/opensnitch/daemon/procmon"
|
||||||
|
"github.com/evilsocket/opensnitch/daemon/procmon/audit"
|
||||||
|
"github.com/evilsocket/opensnitch/daemon/procmon/ebpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cacheMonitorsRunning = false
|
||||||
|
)
|
||||||
|
|
||||||
|
// monitor method supported types
|
||||||
|
const (
|
||||||
|
MethodFtrace = "ftrace"
|
||||||
|
MethodProc = "proc"
|
||||||
|
MethodAudit = "audit"
|
||||||
|
MethodEbpf = "ebpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// End stops the way of parsing new connections.
|
||||||
|
func End() {
|
||||||
|
if procmon.MethodIsAudit() {
|
||||||
|
audit.Stop()
|
||||||
|
} else if procmon.MethodIsEbpf() {
|
||||||
|
ebpf.Stop()
|
||||||
|
} else if procmon.MethodIsFtrace() {
|
||||||
|
go func() {
|
||||||
|
if err := procmon.Stop(); err != nil {
|
||||||
|
log.Warning("procmon.End() stop ftrace error: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init starts parsing connections using the method specified.
|
||||||
|
func Init() (err error) {
|
||||||
|
if cacheMonitorsRunning == false {
|
||||||
|
go procmon.MonitorActivePids()
|
||||||
|
go procmon.CacheCleanerTask()
|
||||||
|
cacheMonitorsRunning = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if procmon.MethodIsEbpf() {
|
||||||
|
err = ebpf.Start()
|
||||||
|
if err == nil {
|
||||||
|
log.Info("Process monitor method ebpf")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Warning("error starting ebpf monitor method: %v", err)
|
||||||
|
} else if procmon.MethodIsFtrace() {
|
||||||
|
err = procmon.Start()
|
||||||
|
if err == nil {
|
||||||
|
log.Info("Process monitor method ftrace")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Warning("error starting ftrace monitor method: %v", err)
|
||||||
|
|
||||||
|
} else if procmon.MethodIsAudit() {
|
||||||
|
var auditConn net.Conn
|
||||||
|
auditConn, err = audit.Start()
|
||||||
|
if err == nil {
|
||||||
|
log.Info("Process monitor method audit")
|
||||||
|
go audit.Reader(auditConn, (chan<- audit.Event)(audit.EventChan))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Warning("error starting audit monitor method: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if any of the above methods have failed, fallback to proc
|
||||||
|
log.Info("Process monitor method /proc")
|
||||||
|
procmon.SetMonitorMethod(MethodProc)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -58,12 +58,12 @@ func GetPIDFromINode(inode int, inodeKey string) int {
|
||||||
return cachedPid
|
return cachedPid
|
||||||
}
|
}
|
||||||
|
|
||||||
if methodIsAudit() {
|
if MethodIsAudit() {
|
||||||
if aPid, pos := getPIDFromAuditEvents(inode, inodeKey, expect); aPid != -1 {
|
if aPid, pos := getPIDFromAuditEvents(inode, inodeKey, expect); aPid != -1 {
|
||||||
log.Debug("PID found via audit events: %v, position: %d", time.Since(start), pos)
|
log.Debug("PID found via audit events: %v, position: %d", time.Since(start), pos)
|
||||||
return aPid
|
return aPid
|
||||||
}
|
}
|
||||||
} else if methodIsFtrace() && IsWatcherAvailable() {
|
} else if MethodIsFtrace() && IsWatcherAvailable() {
|
||||||
forEachProcess(func(pid int, path string, args []string) bool {
|
forEachProcess(func(pid int, path string, args []string) bool {
|
||||||
if inodeFound("/proc/", expect, inodeKey, inode, pid) {
|
if inodeFound("/proc/", expect, inodeKey, inode, pid) {
|
||||||
found = pid
|
found = pid
|
||||||
|
@ -85,6 +85,9 @@ func GetPIDFromINode(inode int, inodeKey string) int {
|
||||||
// If it exists in /proc, a new Process{} object is returned with the details
|
// If it exists in /proc, a new Process{} object is returned with the details
|
||||||
// to identify a process (cmdline, name, environment variables, etc).
|
// to identify a process (cmdline, name, environment variables, etc).
|
||||||
func FindProcess(pid int, interceptUnknown bool) *Process {
|
func FindProcess(pid int, interceptUnknown bool) *Process {
|
||||||
|
if pid == -100 {
|
||||||
|
return NewProcess(-100, "Linux kernel")
|
||||||
|
}
|
||||||
if interceptUnknown && pid < 0 {
|
if interceptUnknown && pid < 0 {
|
||||||
return NewProcess(0, "")
|
return NewProcess(0, "")
|
||||||
}
|
}
|
||||||
|
@ -93,7 +96,7 @@ func FindProcess(pid int, interceptUnknown bool) *Process {
|
||||||
return proc
|
return proc
|
||||||
}
|
}
|
||||||
|
|
||||||
if methodIsAudit() {
|
if MethodIsAudit() {
|
||||||
if aevent := audit.GetEventByPid(pid); aevent != nil {
|
if aevent := audit.GetEventByPid(pid); aevent != nil {
|
||||||
audit.Lock.RLock()
|
audit.Lock.RLock()
|
||||||
proc := NewProcess(pid, aevent.ProcPath)
|
proc := NewProcess(pid, aevent.ProcPath)
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
package procmon
|
package procmon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/evilsocket/opensnitch/daemon/log"
|
|
||||||
"github.com/evilsocket/opensnitch/daemon/procmon/audit"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -73,14 +69,21 @@ func SetMonitorMethod(newMonitorMethod string) {
|
||||||
monitorMethod = newMonitorMethod
|
monitorMethod = newMonitorMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
func methodIsFtrace() bool {
|
func MethodIsEbpf() bool {
|
||||||
|
lock.RLock()
|
||||||
|
defer lock.RUnlock()
|
||||||
|
|
||||||
|
return monitorMethod == MethodEbpf
|
||||||
|
}
|
||||||
|
|
||||||
|
func MethodIsFtrace() bool {
|
||||||
lock.RLock()
|
lock.RLock()
|
||||||
defer lock.RUnlock()
|
defer lock.RUnlock()
|
||||||
|
|
||||||
return monitorMethod == MethodFtrace
|
return monitorMethod == MethodFtrace
|
||||||
}
|
}
|
||||||
|
|
||||||
func methodIsAudit() bool {
|
func MethodIsAudit() bool {
|
||||||
lock.RLock()
|
lock.RLock()
|
||||||
defer lock.RUnlock()
|
defer lock.RUnlock()
|
||||||
|
|
||||||
|
@ -93,49 +96,3 @@ func methodIsProc() bool {
|
||||||
|
|
||||||
return monitorMethod == MethodProc
|
return monitorMethod == MethodProc
|
||||||
}
|
}
|
||||||
|
|
||||||
// End stops the way of parsing new connections.
|
|
||||||
func End() {
|
|
||||||
if methodIsAudit() {
|
|
||||||
audit.Stop()
|
|
||||||
} else if methodIsFtrace() {
|
|
||||||
go func() {
|
|
||||||
if err := Stop(); err != nil {
|
|
||||||
log.Warning("procmon.End() stop ftrace error: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init starts parsing connections using the method specified.
|
|
||||||
func Init() (err error) {
|
|
||||||
if cacheMonitorsRunning == false {
|
|
||||||
go monitorActivePids()
|
|
||||||
go cacheCleanerTask()
|
|
||||||
cacheMonitorsRunning = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if methodIsFtrace() {
|
|
||||||
err = Start()
|
|
||||||
if err == nil {
|
|
||||||
log.Info("Process monitor method ftrace")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
log.Warning("error starting ftrace monitor method: %v", err)
|
|
||||||
|
|
||||||
} else if methodIsAudit() {
|
|
||||||
var auditConn net.Conn
|
|
||||||
auditConn, err = audit.Start()
|
|
||||||
if err == nil {
|
|
||||||
log.Info("Process monitor method audit")
|
|
||||||
go audit.Reader(auditConn, (chan<- audit.Event)(audit.EventChan))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
log.Warning("error starting audit monitor method: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if any of the above methods have failed, fallback to proc
|
|
||||||
log.Info("Process monitor method /proc")
|
|
||||||
SetMonitorMethod(MethodProc)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ const (
|
||||||
MethodFtrace = "ftrace"
|
MethodFtrace = "ftrace"
|
||||||
MethodProc = "proc"
|
MethodProc = "proc"
|
||||||
MethodAudit = "audit"
|
MethodAudit = "audit"
|
||||||
|
MethodEbpf = "ebpf"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/evilsocket/opensnitch/daemon/firewall"
|
"github.com/evilsocket/opensnitch/daemon/firewall"
|
||||||
"github.com/evilsocket/opensnitch/daemon/log"
|
"github.com/evilsocket/opensnitch/daemon/log"
|
||||||
"github.com/evilsocket/opensnitch/daemon/procmon"
|
"github.com/evilsocket/opensnitch/daemon/procmon"
|
||||||
|
"github.com/evilsocket/opensnitch/daemon/procmon/monitor"
|
||||||
"github.com/evilsocket/opensnitch/daemon/rule"
|
"github.com/evilsocket/opensnitch/daemon/rule"
|
||||||
"github.com/evilsocket/opensnitch/daemon/ui/protocol"
|
"github.com/evilsocket/opensnitch/daemon/ui/protocol"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -95,7 +96,7 @@ func (c *Client) handleActionChangeConfig(stream protocol.UI_NotificationsClient
|
||||||
// in such case close the current method, and start the new one.
|
// in such case close the current method, and start the new one.
|
||||||
procMonitorEqual := c.isProcMonitorEqual(newConf.ProcMonitorMethod)
|
procMonitorEqual := c.isProcMonitorEqual(newConf.ProcMonitorMethod)
|
||||||
if procMonitorEqual == false {
|
if procMonitorEqual == false {
|
||||||
procmon.End()
|
monitor.End()
|
||||||
}
|
}
|
||||||
|
|
||||||
// this save operation triggers a re-loadConfiguration()
|
// this save operation triggers a re-loadConfiguration()
|
||||||
|
@ -103,7 +104,7 @@ func (c *Client) handleActionChangeConfig(stream protocol.UI_NotificationsClient
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warning("[notification] CHANGE_CONFIG not applied %s", err)
|
log.Warning("[notification] CHANGE_CONFIG not applied %s", err)
|
||||||
} else if err == nil && procMonitorEqual == false {
|
} else if err == nil && procMonitorEqual == false {
|
||||||
if err := procmon.Init(); err != nil {
|
if err := monitor.Init(); err != nil {
|
||||||
c.sendNotificationReply(stream, notification.Id, "", err)
|
c.sendNotificationReply(stream, notification.Id, "", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
159
ebpf_prog/Makefile
Normal file
159
ebpf_prog/Makefile
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
#taken from /samples/bpf/Makefile and removed all targets
|
||||||
|
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
BPF_SAMPLES_PATH ?= $(abspath $(srctree)/$(src))
|
||||||
|
TOOLS_PATH := $(BPF_SAMPLES_PATH)/../../tools
|
||||||
|
|
||||||
|
# Libbpf dependencies
|
||||||
|
LIBBPF = $(TOOLS_PATH)/lib/bpf/libbpf.a
|
||||||
|
|
||||||
|
CGROUP_HELPERS := ../../tools/testing/selftests/bpf/cgroup_helpers.o
|
||||||
|
TRACE_HELPERS := ../../tools/testing/selftests/bpf/trace_helpers.o
|
||||||
|
|
||||||
|
always-y += opensnitch.o
|
||||||
|
|
||||||
|
ifeq ($(ARCH), arm)
|
||||||
|
# Strip all except -D__LINUX_ARM_ARCH__ option needed to handle linux
|
||||||
|
# headers when arm instruction set identification is requested.
|
||||||
|
ARM_ARCH_SELECTOR := $(filter -D__LINUX_ARM_ARCH__%, $(KBUILD_CFLAGS))
|
||||||
|
BPF_EXTRA_CFLAGS := $(ARM_ARCH_SELECTOR)
|
||||||
|
TPROGS_CFLAGS += $(ARM_ARCH_SELECTOR)
|
||||||
|
endif
|
||||||
|
|
||||||
|
TPROGS_CFLAGS += -Wall -O2
|
||||||
|
TPROGS_CFLAGS += -Wmissing-prototypes
|
||||||
|
TPROGS_CFLAGS += -Wstrict-prototypes
|
||||||
|
|
||||||
|
TPROGS_CFLAGS += -I$(objtree)/usr/include
|
||||||
|
TPROGS_CFLAGS += -I$(srctree)/tools/testing/selftests/bpf/
|
||||||
|
TPROGS_CFLAGS += -I$(srctree)/tools/lib/
|
||||||
|
TPROGS_CFLAGS += -I$(srctree)/tools/include
|
||||||
|
TPROGS_CFLAGS += -I$(srctree)/tools/perf
|
||||||
|
TPROGS_CFLAGS += -DHAVE_ATTR_TEST=0
|
||||||
|
|
||||||
|
ifdef SYSROOT
|
||||||
|
TPROGS_CFLAGS += --sysroot=$(SYSROOT)
|
||||||
|
TPROGS_LDFLAGS := -L$(SYSROOT)/usr/lib
|
||||||
|
endif
|
||||||
|
|
||||||
|
TPROGCFLAGS_bpf_load.o += -Wno-unused-variable
|
||||||
|
|
||||||
|
TPROGS_LDLIBS += $(LIBBPF) -lelf -lz
|
||||||
|
TPROGLDLIBS_tracex4 += -lrt
|
||||||
|
TPROGLDLIBS_trace_output += -lrt
|
||||||
|
TPROGLDLIBS_map_perf_test += -lrt
|
||||||
|
TPROGLDLIBS_test_overhead += -lrt
|
||||||
|
TPROGLDLIBS_xdpsock += -pthread
|
||||||
|
|
||||||
|
# Allows pointing LLC/CLANG to a LLVM backend with bpf support, redefine on cmdline:
|
||||||
|
# make M=samples/bpf/ LLC=~/git/llvm/build/bin/llc CLANG=~/git/llvm/build/bin/clang
|
||||||
|
LLC ?= llc
|
||||||
|
CLANG ?= clang
|
||||||
|
LLVM_OBJCOPY ?= llvm-objcopy
|
||||||
|
BTF_PAHOLE ?= pahole
|
||||||
|
|
||||||
|
# Detect that we're cross compiling and use the cross compiler
|
||||||
|
ifdef CROSS_COMPILE
|
||||||
|
CLANG_ARCH_ARGS = --target=$(notdir $(CROSS_COMPILE:%-=%))
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Don't evaluate probes and warnings if we need to run make recursively
|
||||||
|
ifneq ($(src),)
|
||||||
|
HDR_PROBE := $(shell printf "\#include <linux/types.h>\n struct list_head { int a; }; int main() { return 0; }" | \
|
||||||
|
$(CC) $(TPROGS_CFLAGS) $(TPROGS_LDFLAGS) -x c - \
|
||||||
|
-o /dev/null 2>/dev/null && echo okay)
|
||||||
|
|
||||||
|
ifeq ($(HDR_PROBE),)
|
||||||
|
$(warning WARNING: Detected possible issues with include path.)
|
||||||
|
$(warning WARNING: Please install kernel headers locally (make headers_install).)
|
||||||
|
endif
|
||||||
|
|
||||||
|
BTF_LLC_PROBE := $(shell $(LLC) -march=bpf -mattr=help 2>&1 | grep dwarfris)
|
||||||
|
BTF_PAHOLE_PROBE := $(shell $(BTF_PAHOLE) --help 2>&1 | grep BTF)
|
||||||
|
BTF_OBJCOPY_PROBE := $(shell $(LLVM_OBJCOPY) --help 2>&1 | grep -i 'usage.*llvm')
|
||||||
|
BTF_LLVM_PROBE := $(shell echo "int main() { return 0; }" | \
|
||||||
|
$(CLANG) -target bpf -O2 -g -c -x c - -o ./llvm_btf_verify.o; \
|
||||||
|
readelf -S ./llvm_btf_verify.o | grep BTF; \
|
||||||
|
/bin/rm -f ./llvm_btf_verify.o)
|
||||||
|
|
||||||
|
BPF_EXTRA_CFLAGS += -fno-stack-protector
|
||||||
|
ifneq ($(BTF_LLVM_PROBE),)
|
||||||
|
BPF_EXTRA_CFLAGS += -g
|
||||||
|
else
|
||||||
|
ifneq ($(and $(BTF_LLC_PROBE),$(BTF_PAHOLE_PROBE),$(BTF_OBJCOPY_PROBE)),)
|
||||||
|
BPF_EXTRA_CFLAGS += -g
|
||||||
|
LLC_FLAGS += -mattr=dwarfris
|
||||||
|
DWARF2BTF = y
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Trick to allow make to be run from this directory
|
||||||
|
all:
|
||||||
|
$(MAKE) -C ../../ M=$(CURDIR) BPF_SAMPLES_PATH=$(CURDIR)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(MAKE) -C ../../ M=$(CURDIR) clean
|
||||||
|
@find $(CURDIR) -type f -name '*~' -delete
|
||||||
|
|
||||||
|
$(LIBBPF): FORCE
|
||||||
|
# Fix up variables inherited from Kbuild that tools/ build system won't like
|
||||||
|
$(MAKE) -C $(dir $@) RM='rm -rf' EXTRA_CFLAGS="$(TPROGS_CFLAGS)" \
|
||||||
|
LDFLAGS=$(TPROGS_LDFLAGS) srctree=$(BPF_SAMPLES_PATH)/../../ O=
|
||||||
|
|
||||||
|
$(obj)/syscall_nrs.h: $(obj)/syscall_nrs.s FORCE
|
||||||
|
$(call filechk,offsets,__SYSCALL_NRS_H__)
|
||||||
|
|
||||||
|
targets += syscall_nrs.s
|
||||||
|
clean-files += syscall_nrs.h
|
||||||
|
|
||||||
|
FORCE:
|
||||||
|
|
||||||
|
|
||||||
|
# Verify LLVM compiler tools are available and bpf target is supported by llc
|
||||||
|
.PHONY: verify_cmds verify_target_bpf $(CLANG) $(LLC)
|
||||||
|
|
||||||
|
verify_cmds: $(CLANG) $(LLC)
|
||||||
|
@for TOOL in $^ ; do \
|
||||||
|
if ! (which -- "$${TOOL}" > /dev/null 2>&1); then \
|
||||||
|
echo "*** ERROR: Cannot find LLVM tool $${TOOL}" ;\
|
||||||
|
exit 1; \
|
||||||
|
else true; fi; \
|
||||||
|
done
|
||||||
|
|
||||||
|
verify_target_bpf: verify_cmds
|
||||||
|
@if ! (${LLC} -march=bpf -mattr=help > /dev/null 2>&1); then \
|
||||||
|
echo "*** ERROR: LLVM (${LLC}) does not support 'bpf' target" ;\
|
||||||
|
echo " NOTICE: LLVM version >= 3.7.1 required" ;\
|
||||||
|
exit 2; \
|
||||||
|
else true; fi
|
||||||
|
|
||||||
|
$(BPF_SAMPLES_PATH)/*.c: verify_target_bpf $(LIBBPF)
|
||||||
|
$(src)/*.c: verify_target_bpf $(LIBBPF)
|
||||||
|
|
||||||
|
$(obj)/tracex5_kern.o: $(obj)/syscall_nrs.h
|
||||||
|
$(obj)/hbm_out_kern.o: $(src)/hbm.h $(src)/hbm_kern.h
|
||||||
|
$(obj)/hbm.o: $(src)/hbm.h
|
||||||
|
$(obj)/hbm_edt_kern.o: $(src)/hbm.h $(src)/hbm_kern.h
|
||||||
|
|
||||||
|
-include $(BPF_SAMPLES_PATH)/Makefile.target
|
||||||
|
|
||||||
|
# asm/sysreg.h - inline assembly used by it is incompatible with llvm.
|
||||||
|
# But, there is no easy way to fix it, so just exclude it since it is
|
||||||
|
# useless for BPF samples.
|
||||||
|
$(obj)/%.o: $(src)/%.c
|
||||||
|
@echo " CLANG-bpf " $@
|
||||||
|
$(Q)$(CLANG) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) $(BPF_EXTRA_CFLAGS) \
|
||||||
|
-I$(obj) -I$(srctree)/tools/testing/selftests/bpf/ \
|
||||||
|
-I$(srctree)/tools/lib/ \
|
||||||
|
-D__KERNEL__ -D__BPF_TRACING__ -Wno-unused-value -Wno-pointer-sign \
|
||||||
|
-D__TARGET_ARCH_$(SRCARCH) -Wno-compare-distinct-pointer-types \
|
||||||
|
-Wno-gnu-variable-sized-type-not-at-end \
|
||||||
|
-Wno-address-of-packed-member -Wno-tautological-compare \
|
||||||
|
-Wno-unknown-warning-option $(CLANG_ARCH_ARGS) \
|
||||||
|
-I$(srctree)/samples/bpf/ -include asm_goto_workaround.h \
|
||||||
|
-O2 -emit-llvm -c $< -o -| $(LLC) -march=bpf $(LLC_FLAGS) -filetype=obj -o $@
|
||||||
|
ifeq ($(DWARF2BTF),y)
|
||||||
|
$(BTF_PAHOLE) -J $@
|
||||||
|
endif
|
19
ebpf_prog/README
Normal file
19
ebpf_prog/README
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
opensnitch.c is an eBPF program. Compilation requires getting kernel source.
|
||||||
|
|
||||||
|
sudo apt install clang llvm libelf-dev libzip-dev
|
||||||
|
cd opensnitch
|
||||||
|
wget https://github.com/torvalds/linux/archive/v5.8.tar.gz
|
||||||
|
tar -xf v5.8.tar.gz
|
||||||
|
patch linux-5.8/tools/lib/bpf/bpf_helpers.h < ebpf_prog/file.patch
|
||||||
|
cp ebpf_prog/opensnitch.c ebpf_prog/Makefile linux-5.8/samples/bpf
|
||||||
|
cd linux-5.8 && yes "" | make oldconfig && make prepare && make headers_install # (1 min)
|
||||||
|
cd samples/bpf && make
|
||||||
|
objdump -h opensnitch.o #you should see many section, number 1 should be called kprobe/tcp_v4_connect
|
||||||
|
llvm-strip -g opensnitch.o #remove debug info
|
||||||
|
sudo cp opensnitch.o /etc/opensnitchd/
|
||||||
|
cd ../../../daemon
|
||||||
|
|
||||||
|
--opensnitchd expects to find opensnitch.o in /etc/opensnitchd/
|
||||||
|
--start opensnitchd with:
|
||||||
|
|
||||||
|
opensnitchd -rules-path /etc/opensnitchd/rules -process-monitor-method ebpf
|
11
ebpf_prog/file.patch
Normal file
11
ebpf_prog/file.patch
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
--- linux-5.8/tools/lib/bpf/bpf_helpers.h 2020-08-03 00:21:45.000000000 +0300
|
||||||
|
+++ linux-5.8/tools/lib/bpf/bpf_helpersnew.h 2021-02-23 18:45:21.789624834 +0300
|
||||||
|
@@ -54,7 +54,7 @@
|
||||||
|
* Helper structure used by eBPF C program
|
||||||
|
* to describe BPF map attributes to libbpf loader
|
||||||
|
*/
|
||||||
|
-struct bpf_map_def {
|
||||||
|
+struct bpf_map_defold {
|
||||||
|
unsigned int type;
|
||||||
|
unsigned int key_size;
|
||||||
|
unsigned int value_size;
|
446
ebpf_prog/opensnitch.c
Normal file
446
ebpf_prog/opensnitch.c
Normal file
|
@ -0,0 +1,446 @@
|
||||||
|
#define KBUILD_MODNAME "dummy"
|
||||||
|
|
||||||
|
//uncomment if building on x86_32
|
||||||
|
//#define OPENSNITCH_x86_32
|
||||||
|
|
||||||
|
#include <linux/ptrace.h>
|
||||||
|
#include <linux/version.h>
|
||||||
|
#include <uapi/linux/bpf.h>
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
#include <bpf/bpf_tracing.h>
|
||||||
|
#include <net/sock.h>
|
||||||
|
#include <net/inet_sock.h>
|
||||||
|
|
||||||
|
#define MAPSIZE 12000
|
||||||
|
|
||||||
|
//-------------------------------map definitions
|
||||||
|
// which github.com/iovisor/gobpf/elf expects
|
||||||
|
#define BUF_SIZE_MAP_NS 256
|
||||||
|
|
||||||
|
typedef struct bpf_map_def {
|
||||||
|
unsigned int type;
|
||||||
|
unsigned int key_size;
|
||||||
|
unsigned int value_size;
|
||||||
|
unsigned int max_entries;
|
||||||
|
unsigned int map_flags;
|
||||||
|
unsigned int pinning;
|
||||||
|
char namespace[BUF_SIZE_MAP_NS];
|
||||||
|
} bpf_map_def;
|
||||||
|
|
||||||
|
enum bpf_pin_type {
|
||||||
|
PIN_NONE = 0,
|
||||||
|
PIN_OBJECT_NS,
|
||||||
|
PIN_GLOBAL_NS,
|
||||||
|
PIN_CUSTOM_NS,
|
||||||
|
};
|
||||||
|
//-----------------------------------
|
||||||
|
|
||||||
|
// even though we only need 32 bits of pid, on x86_32 ebpf verifier complained when pid type was set to u32
|
||||||
|
typedef u64 pid_size_t;
|
||||||
|
typedef u64 uid_size_t;
|
||||||
|
|
||||||
|
struct tcp_key_t {
|
||||||
|
u16 sport;
|
||||||
|
u32 daddr;
|
||||||
|
u16 dport;
|
||||||
|
u32 saddr;
|
||||||
|
}__attribute__((packed));
|
||||||
|
|
||||||
|
struct tcp_value_t{
|
||||||
|
pid_size_t pid;
|
||||||
|
uid_size_t uid;
|
||||||
|
u64 counter;
|
||||||
|
}__attribute__((packed));
|
||||||
|
|
||||||
|
// not using unsigned __int128 because it is not supported on x86_32
|
||||||
|
struct ipV6 {
|
||||||
|
u64 part1;
|
||||||
|
u64 part2;
|
||||||
|
}__attribute__((packed));
|
||||||
|
|
||||||
|
struct tcpv6_key_t {
|
||||||
|
u16 sport;
|
||||||
|
struct ipV6 daddr;
|
||||||
|
u16 dport;
|
||||||
|
struct ipV6 saddr;
|
||||||
|
}__attribute__((packed));
|
||||||
|
|
||||||
|
struct tcpv6_value_t{
|
||||||
|
pid_size_t pid;
|
||||||
|
uid_size_t uid;
|
||||||
|
u64 counter;
|
||||||
|
}__attribute__((packed));;
|
||||||
|
|
||||||
|
struct udp_key_t {
|
||||||
|
u16 sport;
|
||||||
|
u32 daddr;
|
||||||
|
u16 dport;
|
||||||
|
u32 saddr;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
struct udp_value_t{
|
||||||
|
pid_size_t pid;
|
||||||
|
uid_size_t uid;
|
||||||
|
u64 counter;
|
||||||
|
}__attribute__((packed));
|
||||||
|
|
||||||
|
struct udpv6_key_t {
|
||||||
|
u16 sport;
|
||||||
|
struct ipV6 daddr;
|
||||||
|
u16 dport;
|
||||||
|
struct ipV6 saddr;
|
||||||
|
}__attribute__((packed));
|
||||||
|
|
||||||
|
struct udpv6_value_t{
|
||||||
|
pid_size_t pid;
|
||||||
|
uid_size_t uid;
|
||||||
|
u64 counter;
|
||||||
|
}__attribute__((packed));
|
||||||
|
|
||||||
|
// on x86_32 "struct sock" is arranged differently from x86_64 (at least on Debian kernels).
|
||||||
|
// We hardcode offsets of IP addresses.
|
||||||
|
struct sock_on_x86_32_t {
|
||||||
|
u8 data_we_dont_care_about[40];
|
||||||
|
struct ipV6 daddr;
|
||||||
|
struct ipV6 saddr;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Add +1,+2,+3 etc. to map size helps to easier distinguish maps in bpftool's output
|
||||||
|
struct bpf_map_def SEC("maps/tcpMap") tcpMap = {
|
||||||
|
.type = BPF_MAP_TYPE_HASH,
|
||||||
|
.key_size = sizeof(struct tcp_key_t),
|
||||||
|
.value_size = sizeof(struct tcp_value_t),
|
||||||
|
.max_entries = MAPSIZE+1,
|
||||||
|
};
|
||||||
|
struct bpf_map_def SEC("maps/tcpv6Map") tcpv6Map = {
|
||||||
|
.type = BPF_MAP_TYPE_HASH,
|
||||||
|
.key_size = sizeof(struct tcpv6_key_t),
|
||||||
|
.value_size = sizeof(struct tcpv6_value_t),
|
||||||
|
.max_entries = MAPSIZE+2,
|
||||||
|
};
|
||||||
|
struct bpf_map_def SEC("maps/udpMap") udpMap = {
|
||||||
|
.type = BPF_MAP_TYPE_HASH,
|
||||||
|
.key_size = sizeof(struct udp_key_t),
|
||||||
|
.value_size = sizeof(struct udp_value_t),
|
||||||
|
.max_entries = MAPSIZE+3,
|
||||||
|
};
|
||||||
|
struct bpf_map_def SEC("maps/udpv6Map") udpv6Map = {
|
||||||
|
.type = BPF_MAP_TYPE_HASH,
|
||||||
|
.key_size = sizeof(struct udpv6_key_t),
|
||||||
|
.value_size = sizeof(struct udpv6_value_t),
|
||||||
|
.max_entries = MAPSIZE+4,
|
||||||
|
};
|
||||||
|
|
||||||
|
// for TCP the IP-tuple can be copied from "struct sock" only upon return from tcp_connect().
|
||||||
|
// We stash the socket here to look it up upon return.
|
||||||
|
struct bpf_map_def SEC("maps/tcpsock") tcpsock = {
|
||||||
|
.type = BPF_MAP_TYPE_HASH,
|
||||||
|
.key_size = sizeof(u64),
|
||||||
|
.value_size = sizeof(u64),// using u64 instead of sizeof(struct sock *)
|
||||||
|
// to avoid pointer size related quirks on x86_32
|
||||||
|
.max_entries = 100,
|
||||||
|
};
|
||||||
|
struct bpf_map_def SEC("maps/tcpv6sock") tcpv6sock = {
|
||||||
|
.type = BPF_MAP_TYPE_HASH,
|
||||||
|
.key_size = sizeof(u64),
|
||||||
|
.value_size = sizeof(u64),
|
||||||
|
.max_entries = 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
// //counts how many connections we've processed. Starts at 0.
|
||||||
|
struct bpf_map_def SEC("maps/tcpcounter") tcpcounter = {
|
||||||
|
.type = BPF_MAP_TYPE_ARRAY,
|
||||||
|
.key_size = sizeof(u32),
|
||||||
|
.value_size = sizeof(u64),
|
||||||
|
.max_entries = 1,
|
||||||
|
};
|
||||||
|
struct bpf_map_def SEC("maps/tcpv6counter") tcpv6counter = {
|
||||||
|
.type = BPF_MAP_TYPE_ARRAY,
|
||||||
|
.key_size = sizeof(u32),
|
||||||
|
.value_size = sizeof(u64),
|
||||||
|
.max_entries = 1,
|
||||||
|
};
|
||||||
|
struct bpf_map_def SEC("maps/udpcounter") udpcounter = {
|
||||||
|
.type = BPF_MAP_TYPE_ARRAY,
|
||||||
|
.key_size = sizeof(u32),
|
||||||
|
.value_size = sizeof(u64),
|
||||||
|
.max_entries = 1,
|
||||||
|
};
|
||||||
|
struct bpf_map_def SEC("maps/udpv6counter") udpv6counter = {
|
||||||
|
.type = BPF_MAP_TYPE_ARRAY,
|
||||||
|
.key_size = sizeof(u32),
|
||||||
|
.value_size = sizeof(u64),
|
||||||
|
.max_entries = 1,
|
||||||
|
};
|
||||||
|
struct bpf_map_def SEC("maps/debugcounter") debugcounter = {
|
||||||
|
.type = BPF_MAP_TYPE_ARRAY,
|
||||||
|
.key_size = sizeof(u32),
|
||||||
|
.value_size = sizeof(u64),
|
||||||
|
.max_entries = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// size 150 gave ebpf verifier errors for kernel 4.14, 100 is ok
|
||||||
|
// we can cast any struct into rawBytes_t to be able to access arbitrary bytes of the struct
|
||||||
|
struct rawBytes_t {
|
||||||
|
u8 bytes[100];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//used for debug purposes only
|
||||||
|
struct bpf_map_def SEC("maps/bytes") bytes = {
|
||||||
|
.type = BPF_MAP_TYPE_HASH,
|
||||||
|
.key_size = sizeof(u32),
|
||||||
|
.value_size = sizeof(u32),
|
||||||
|
.max_entries = 222,
|
||||||
|
};
|
||||||
|
|
||||||
|
//used for debug purposes only
|
||||||
|
struct bpf_map_def SEC("maps/debug") debug = {
|
||||||
|
.type = BPF_MAP_TYPE_HASH,
|
||||||
|
.key_size = sizeof(struct tcpv6_key_t),
|
||||||
|
.value_size = sizeof(struct rawBytes_t),
|
||||||
|
.max_entries = 555,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// initializing variables with __builtin_memset() is required
|
||||||
|
// for compatibility with bpf on kernel 4.4
|
||||||
|
|
||||||
|
SEC("kprobe/tcp_v4_connect")
|
||||||
|
int kprobe__tcp_v4_connect(struct pt_regs *ctx)
|
||||||
|
{
|
||||||
|
#ifdef OPENSNITCH_x86_32
|
||||||
|
// On x86_32 platforms I couldn't get function arguments using PT_REGS_PARM1
|
||||||
|
// that's why we are accessing registers directly
|
||||||
|
struct sock *sk = (struct sock *)((ctx)->ax);
|
||||||
|
#else
|
||||||
|
struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
u64 skp = (u64)sk;
|
||||||
|
u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||||
|
bpf_map_update_elem(&tcpsock, &pid_tgid, &skp, BPF_ANY);
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
SEC("kretprobe/tcp_v4_connect")
|
||||||
|
int kretprobe__tcp_v4_connect(struct pt_regs *ctx)
|
||||||
|
{
|
||||||
|
u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||||
|
u64 *skp = bpf_map_lookup_elem(&tcpsock, &pid_tgid);
|
||||||
|
if (skp == NULL) {return 0;}
|
||||||
|
|
||||||
|
struct sock *sk;
|
||||||
|
__builtin_memset(&sk, 0, sizeof(sk));
|
||||||
|
sk = (struct sock *)*skp;
|
||||||
|
|
||||||
|
struct tcp_key_t tcp_key;
|
||||||
|
__builtin_memset(&tcp_key, 0, sizeof(tcp_key));
|
||||||
|
bpf_probe_read(&tcp_key.dport, sizeof(tcp_key.dport), &sk->__sk_common.skc_dport);
|
||||||
|
bpf_probe_read(&tcp_key.sport, sizeof(tcp_key.sport), &sk->__sk_common.skc_num);
|
||||||
|
bpf_probe_read(&tcp_key.daddr, sizeof(tcp_key.daddr), &sk->__sk_common.skc_daddr);
|
||||||
|
bpf_probe_read(&tcp_key.saddr, sizeof(tcp_key.saddr), &sk->__sk_common.skc_rcv_saddr);
|
||||||
|
|
||||||
|
u32 zero_key = 0;
|
||||||
|
u64 *val = bpf_map_lookup_elem(&tcpcounter, &zero_key);
|
||||||
|
if (val == NULL){return 0;}
|
||||||
|
|
||||||
|
struct tcp_value_t tcp_value;
|
||||||
|
__builtin_memset(&tcp_value, 0, sizeof(tcp_value));
|
||||||
|
tcp_value.pid = pid_tgid >> 32;
|
||||||
|
tcp_value.uid = bpf_get_current_uid_gid() & 0xffffffff;
|
||||||
|
tcp_value.counter = *val;
|
||||||
|
bpf_map_update_elem(&tcpMap, &tcp_key, &tcp_value, BPF_ANY);
|
||||||
|
u64 newval = *val + 1;
|
||||||
|
bpf_map_update_elem(&tcpcounter, &zero_key, &newval, BPF_ANY);
|
||||||
|
bpf_map_delete_elem(&tcpsock, &pid_tgid);
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
SEC("kprobe/tcp_v6_connect")
|
||||||
|
int kprobe__tcp_v6_connect(struct pt_regs *ctx)
|
||||||
|
{
|
||||||
|
#ifdef OPENSNITCH_x86_32
|
||||||
|
struct sock *sk = (struct sock *)((ctx)->ax);
|
||||||
|
#else
|
||||||
|
struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
u64 skp = (u64)sk;
|
||||||
|
u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||||
|
bpf_map_update_elem(&tcpv6sock, &pid_tgid, &skp, BPF_ANY);
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
SEC("kretprobe/tcp_v6_connect")
|
||||||
|
int kretprobe__tcp_v6_connect(struct pt_regs *ctx)
|
||||||
|
{
|
||||||
|
u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||||
|
u64 *skp = bpf_map_lookup_elem(&tcpv6sock, &pid_tgid);
|
||||||
|
if (skp == NULL) {return 0;}
|
||||||
|
struct sock *sk;
|
||||||
|
__builtin_memset(&sk, 0, sizeof(sk));
|
||||||
|
sk = (struct sock *)*skp;
|
||||||
|
|
||||||
|
struct tcpv6_key_t tcpv6_key;
|
||||||
|
__builtin_memset(&tcpv6_key, 0, sizeof(tcpv6_key));
|
||||||
|
bpf_probe_read(&tcpv6_key.dport, sizeof(tcpv6_key.dport), &sk->__sk_common.skc_dport);
|
||||||
|
bpf_probe_read(&tcpv6_key.sport, sizeof(tcpv6_key.sport), &sk->__sk_common.skc_num);
|
||||||
|
#ifdef OPENSNITCH_x86_32
|
||||||
|
struct sock_on_x86_32_t sock;
|
||||||
|
__builtin_memset(&sock, 0, sizeof(sock));
|
||||||
|
bpf_probe_read(&sock, sizeof(sock), *(&sk));
|
||||||
|
tcpv6_key.daddr = sock.daddr;
|
||||||
|
tcpv6_key.saddr = sock.saddr;
|
||||||
|
#else
|
||||||
|
bpf_probe_read(&tcpv6_key.daddr, sizeof(tcpv6_key.daddr), &sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32);
|
||||||
|
bpf_probe_read(&tcpv6_key.saddr, sizeof(tcpv6_key.saddr), &sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
u32 zero_key = 0;
|
||||||
|
u64 *val = bpf_map_lookup_elem(&tcpv6counter, &zero_key);
|
||||||
|
if (val == NULL){return 0;}
|
||||||
|
|
||||||
|
struct tcpv6_value_t tcpv6_value;
|
||||||
|
__builtin_memset(&tcpv6_value, 0, sizeof(tcpv6_value));
|
||||||
|
tcpv6_value.pid = pid_tgid >> 32;
|
||||||
|
tcpv6_value.uid = bpf_get_current_uid_gid() & 0xffffffff;
|
||||||
|
tcpv6_value.counter = *val;
|
||||||
|
bpf_map_update_elem(&tcpv6Map, &tcpv6_key, &tcpv6_value, BPF_ANY);
|
||||||
|
u64 newval = *val + 1;
|
||||||
|
bpf_map_update_elem(&tcpv6counter, &zero_key, &newval, BPF_ANY);
|
||||||
|
bpf_map_delete_elem(&tcpv6sock, &pid_tgid);
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
SEC("kprobe/udp_sendmsg")
|
||||||
|
int kprobe__udp_sendmsg(struct pt_regs *ctx)
|
||||||
|
{
|
||||||
|
#ifdef OPENSNITCH_x86_32
|
||||||
|
struct sock *sk = (struct sock *)((ctx)->ax);
|
||||||
|
struct msghdr *msg = (struct msghdr *)((ctx)->dx);
|
||||||
|
#else
|
||||||
|
struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
|
||||||
|
struct msghdr *msg = (struct msghdr *)PT_REGS_PARM2(ctx);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
u64 msg_name; //pointer
|
||||||
|
__builtin_memset(&msg_name, 0, sizeof(msg_name));
|
||||||
|
bpf_probe_read(&msg_name, sizeof(msg_name), &msg->msg_name);
|
||||||
|
struct sockaddr_in * usin = (struct sockaddr_in *)msg_name;
|
||||||
|
|
||||||
|
struct udp_key_t udp_key;
|
||||||
|
__builtin_memset(&udp_key, 0, sizeof(udp_key));
|
||||||
|
bpf_probe_read(&udp_key.dport, sizeof(udp_key.dport), &usin->sin_port);
|
||||||
|
if (udp_key.dport != 0){ //likely
|
||||||
|
bpf_probe_read(&udp_key.daddr, sizeof(udp_key.daddr), &usin->sin_addr.s_addr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//very rarely dport can be found in skc_dport
|
||||||
|
bpf_probe_read(&udp_key.dport, sizeof(udp_key.dport), &sk->__sk_common.skc_dport);
|
||||||
|
bpf_probe_read(&udp_key.daddr, sizeof(udp_key.daddr), &sk->__sk_common.skc_daddr);
|
||||||
|
}
|
||||||
|
bpf_probe_read(&udp_key.sport, sizeof(udp_key.sport), &sk->__sk_common.skc_num);
|
||||||
|
bpf_probe_read(&udp_key.saddr, sizeof(udp_key.saddr), &sk->__sk_common.skc_rcv_saddr);
|
||||||
|
|
||||||
|
u32 zero_key = 0;
|
||||||
|
__builtin_memset(&zero_key, 0, sizeof(zero_key));
|
||||||
|
u64 *counterVal = bpf_map_lookup_elem(&udpcounter, &zero_key);
|
||||||
|
if (counterVal == NULL){return 0;}
|
||||||
|
struct udp_value_t *lookedupValue = bpf_map_lookup_elem(&udpMap, &udp_key);
|
||||||
|
u64 pid = bpf_get_current_pid_tgid() >> 32;
|
||||||
|
if ( lookedupValue == NULL || lookedupValue->pid != pid) {
|
||||||
|
struct udp_value_t udp_value;
|
||||||
|
__builtin_memset(&udp_value, 0, sizeof(udp_value));
|
||||||
|
udp_value.pid = pid;
|
||||||
|
udp_value.uid = bpf_get_current_uid_gid() & 0xffffffff;
|
||||||
|
udp_value.counter = *counterVal;
|
||||||
|
bpf_map_update_elem(&udpMap, &udp_key, &udp_value, BPF_ANY);
|
||||||
|
u64 newval = *counterVal + 1;
|
||||||
|
bpf_map_update_elem(&udpcounter, &zero_key, &newval, BPF_ANY);
|
||||||
|
}
|
||||||
|
//else nothing to do
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
SEC("kprobe/udpv6_sendmsg")
|
||||||
|
int kprobe__udpv6_sendmsg(struct pt_regs *ctx)
|
||||||
|
{
|
||||||
|
#ifdef OPENSNITCH_x86_32
|
||||||
|
struct sock *sk = (struct sock *)((ctx)->ax);
|
||||||
|
struct msghdr *msg = (struct msghdr *)((ctx)->dx);
|
||||||
|
#else
|
||||||
|
struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
|
||||||
|
struct msghdr *msg = (struct msghdr *)PT_REGS_PARM2(ctx);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
u64 msg_name; //a pointer
|
||||||
|
__builtin_memset(&msg_name, 0, sizeof(msg_name));
|
||||||
|
bpf_probe_read(&msg_name, sizeof(msg_name), &msg->msg_name);
|
||||||
|
|
||||||
|
struct udpv6_key_t udpv6_key;
|
||||||
|
__builtin_memset(&udpv6_key, 0, sizeof(udpv6_key));
|
||||||
|
bpf_probe_read(&udpv6_key.dport, sizeof(udpv6_key.dport), &sk->__sk_common.skc_dport);
|
||||||
|
if (udpv6_key.dport != 0){ //likely
|
||||||
|
bpf_probe_read(&udpv6_key.daddr, sizeof(udpv6_key.daddr), &sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
struct sockaddr_in6 * sin6 = (struct sockaddr_in6 *)msg_name;
|
||||||
|
bpf_probe_read(&udpv6_key.dport, sizeof(udpv6_key.dport), &sin6->sin6_port);
|
||||||
|
bpf_probe_read(&udpv6_key.daddr, sizeof(udpv6_key.daddr), &sin6->sin6_addr.in6_u.u6_addr32);
|
||||||
|
}
|
||||||
|
|
||||||
|
bpf_probe_read(&udpv6_key.sport, sizeof(udpv6_key.sport), &sk->__sk_common.skc_num);
|
||||||
|
bpf_probe_read(&udpv6_key.saddr, sizeof(udpv6_key.saddr), &sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef OPENSNITCH_x86_32
|
||||||
|
struct sock_on_x86_32_t sock;
|
||||||
|
__builtin_memset(&sock, 0, sizeof(sock));
|
||||||
|
bpf_probe_read(&sock, sizeof(sock), *(&sk));
|
||||||
|
udpv6_key.daddr = sock.daddr;
|
||||||
|
udpv6_key.saddr = sock.saddr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
u32 zero_key = 0;
|
||||||
|
u64 *counterVal = bpf_map_lookup_elem(&udpv6counter, &zero_key);
|
||||||
|
if (counterVal == NULL){return 0;}
|
||||||
|
struct udpv6_value_t *lookedupValue = bpf_map_lookup_elem(&udpv6Map, &udpv6_key);
|
||||||
|
u64 pid = bpf_get_current_pid_tgid() >> 32;
|
||||||
|
if ( lookedupValue == NULL || lookedupValue->pid != pid) {
|
||||||
|
struct udpv6_value_t udpv6_value;
|
||||||
|
__builtin_memset(&udpv6_value, 0, sizeof(udpv6_value));
|
||||||
|
udpv6_value.pid = pid;
|
||||||
|
udpv6_value.uid = bpf_get_current_uid_gid() & 0xffffffff;
|
||||||
|
udpv6_value.counter = *counterVal;
|
||||||
|
bpf_map_update_elem(&udpv6Map, &udpv6_key, &udpv6_value, BPF_ANY);
|
||||||
|
u64 newval = *counterVal + 1;
|
||||||
|
bpf_map_update_elem(&udpv6counter, &zero_key, &newval, BPF_ANY);
|
||||||
|
}
|
||||||
|
//else nothing to do
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// debug only: increment key's value by 1 in map "bytes"
|
||||||
|
void increment(u32 key){
|
||||||
|
u32 *lookedupValue = bpf_map_lookup_elem(&bytes, &key);
|
||||||
|
if (lookedupValue == NULL){
|
||||||
|
u32 zero = 0;
|
||||||
|
bpf_map_update_elem(&bytes, &key, &zero, BPF_ANY);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
u32 newval = *lookedupValue + 1;
|
||||||
|
bpf_map_update_elem(&bytes, &key, &newval, BPF_ANY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char _license[] SEC("license") = "GPL";
|
||||||
|
// this number will be interpreted by the elf loader
|
||||||
|
// to set the current running kernel version
|
||||||
|
u32 _version SEC("version") = 0xFFFFFFFE;
|
Loading…
Add table
Reference in a new issue