Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add clone and tcpconnect tracer #28

Merged
merged 13 commits into from
Aug 31, 2023
4 changes: 2 additions & 2 deletions duetector/collectors/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import Dict, NamedTuple, Optional
from typing import Any, Dict, NamedTuple, Optional

import pydantic

Expand All @@ -23,7 +23,7 @@ class Tracking(pydantic.BaseModel):
fname: Optional[str] = None
timestamp: Optional[int] = None

extended: Dict[str, str] = {}
extended: Dict[str, Any] = {}

@staticmethod
def from_namedtuple(tracer, data: NamedTuple) -> Tracking: # type: ignore
Expand Down
11 changes: 11 additions & 0 deletions duetector/static/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,23 @@ exclude_gid = [
[tracer]
disabled = false

[tracer.clonetracer]
disabled = false
attach_event = "__x64_sys_clone"
poll_timeout = 10

[tracer.tcpconnecttracer]
disabled = false
poll_timeout = 10

[tracer.unametracer]
disabled = false
enable_cache = true

[tracer.opentracer]
disabled = false
attach_event = "do_sys_openat2"
poll_timeout = 10

[collector]
disabled = false
Expand Down
4 changes: 2 additions & 2 deletions duetector/tracers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
__all__ = ["BccTracer"]

# Expose for plugin system
from . import openat2, uname
from . import clone, openat2, tcpconnect, uname

registers = [openat2, uname]
registers = [openat2, uname, tcpconnect, clone]
17 changes: 11 additions & 6 deletions duetector/tracers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class BccTracer(Tracer):
**Tracer.default_config,
}

attach_type: str
attach_type: Optional[str] = None
attatch_args: Dict[str, str] = {}
many_attatchs: List[Tuple[str, Dict[str, str]]] = []
poll_fn: str
Expand All @@ -80,12 +80,22 @@ def _convert_data(self, data) -> NamedTuple:
v = getattr(data, k)
if isinstance(v, bytes):
v = v.decode("utf-8")
elif k == "saddr" or k == "daddr":
dq = b""
for i in range(0, 4):
dq = dq + str(v & 0xFF).encode()
if i != 3:
dq = dq + b"."
v = v >> 8
v = dq
wunder957 marked this conversation as resolved.
Show resolved Hide resolved

args[k] = v

return self.data_t(**args) # type: ignore

def _attatch(self, host, attatch_type, attatch_args):
if not attatch_type:
return
attatcher = getattr(host, f"attach_{attatch_type}")
# Prevent AttributeError
attatch_args = attatch_args or {}
Expand All @@ -95,11 +105,6 @@ def attach(self, host):
if self.disabled:
raise TreacerDisabledError("Tracer is disabled")

if not self.attach_type:
# No need to attach, in this case, function name indicates
# More: https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md
return

attatch_list = [*self.many_attatchs, (self.attach_type, self.attatch_args)]

for attatch_type, attatch_args in attatch_list:
Expand Down
95 changes: 95 additions & 0 deletions duetector/tracers/clone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
from collections import namedtuple
from typing import Callable, NamedTuple

from duetector.extension.tracer import hookimpl
from duetector.tracers.base import BccTracer


class CloneTracer(BccTracer):
"""
A tracer for clone syscall
"""

default_config = {
**BccTracer.default_config,
"attach_event": "__x64_sys_clone",
"poll_timeout": 10,
}
attach_type = "kprobe"

@property
def attatch_args(self):
return {"fn_name": "do_trace", "event": self.config.attach_event}

poll_fn = "perf_buffer_poll"

@property
def poll_args(self):
return {"timeout": int(self.config.poll_timeout)}

data_t = namedtuple("CloneTracking", ["pid", "timestamp", "comm"])
prog = """
#include <linux/sched.h>

// define output data structure in C
struct data_t {
u32 pid;
u32 uid;
u32 gid;
u64 timestamp;
char comm[TASK_COMM_LEN];
};
BPF_PERF_OUTPUT(events);

int do_trace(struct pt_regs *ctx) {
struct data_t data = {};

data.pid = bpf_get_current_pid_tgid();
data.uid = bpf_get_current_uid_gid();
data.gid = bpf_get_current_uid_gid() >> 32;
data.timestamp = bpf_ktime_get_ns();
bpf_get_current_comm(&data.comm, sizeof(data.comm));

events.perf_submit(ctx, &data, sizeof(data));

return 0;
}
"""

def set_callback(self, host, callback: Callable[[NamedTuple], None]):
def _(ctx, data, size):
event = host["events"].event(data)
return callback(self._convert_data(event)) # type: ignore

host["events"].open_perf_buffer(_)


@hookimpl
def init_tracer(config):
return CloneTracer(config)


if __name__ == "__main__":
from bcc import BPF

b = BPF(text=CloneTracer.prog)

tracer = CloneTracer()
tracer.attach(b)
start = 0

def print_callback(data: NamedTuple):
global start
if start == 0:
print(f"[{data.comm} ({data.pid})] 0 ")
else:
print(f"[{data.comm} ({data.pid}) {data.gid} {data.uid}] {(data.timestamp-start)/1000000000}") # type: ignore
start = data.timestamp

tracer.set_callback(b, print_callback)
poller = tracer.get_poller(b)
while True:
try:
poller(**tracer.poll_args)
except KeyboardInterrupt:
exit()
19 changes: 17 additions & 2 deletions duetector/tracers/openat2.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,25 @@ class OpenTracer(BccTracer):
A tracer for openat2 syscall
"""

default_config = {
**BccTracer.default_config,
"attach_event": "do_sys_openat2",
"poll_timeout": 10,
}

attach_type = "kprobe"

@property
def attatch_args(self):
return {"fn_name": "do_trace", "event": self.config.attach_event}

attatch_args = {"fn_name": "trace_entry", "event": "do_sys_openat2"}
poll_fn = "ring_buffer_poll"
poll_args = {}

@property
def poll_args(self):
return {"timeout": int(self.config.poll_timeout)}

data_t = namedtuple("OpenTracking", ["pid", "uid", "gid", "comm", "fname", "timestamp"])

prog = """
Expand Down Expand Up @@ -72,6 +87,6 @@ def print_callback(data: NamedTuple):
poller = tracer.get_poller(b)
while True:
try:
poller()
poller(**tracer.poll_args)
except KeyboardInterrupt:
exit()
131 changes: 131 additions & 0 deletions duetector/tracers/tcpconnect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
from collections import namedtuple
from typing import Callable, NamedTuple

from duetector.extension.tracer import hookimpl
from duetector.tracers.base import BccTracer


class TcpconnectTracer(BccTracer):
"""
A tracer for tcpconnect syscall
"""

default_config = {
**BccTracer.default_config,
"poll_timeout": 10,
}

many_attatchs = [
("kprobe", {"fn_name": "do_trace", "event": "tcp_v4_connect"}),
("kretprobe", {"fn_name": "do_return", "event": "tcp_v4_connect"}),
]

poll_fn = "ring_buffer_poll"

@property
def poll_args(self):
return {"timeout": int(self.config.poll_timeout)}

data_t = namedtuple("TcpTracking", ["pid", "uid", "gid", "comm", "saddr", "daddr", "dport"])

# define BPF program
prog = """
#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <bcc/proto.h>
#define TASK_COMM_LEN 16

BPF_RINGBUF_OUTPUT(buffer, 1 << 4);
BPF_HASH(currsock, u32, struct sock *);

struct event {
u32 dport;
u32 saddr;
u32 daddr;
u32 pid;
u32 uid;
u32 gid;
char comm[TASK_COMM_LEN];
};
int do_trace(struct pt_regs *ctx, struct sock *sk)
{
u32 pid = bpf_get_current_pid_tgid();

// stash the sock ptr for lookup on return
currsock.update(&pid, &sk);

return 0;
}

int do_return(struct pt_regs *ctx)
{
int ret = PT_REGS_RC(ctx);
u32 pid = bpf_get_current_pid_tgid();

struct event event= {};

struct sock **skpp;
skpp = currsock.lookup(&pid);
if (skpp == 0) {
return 0; // missed entry
}

if (ret != 0) {
// failed to send SYNC packet, may not have populated
// socket __sk_common.{skc_rcv_saddr, ...}
currsock.delete(&pid);
return 0;
}

// pull in details
struct sock *skp = *skpp;
u32 saddr = skp->__sk_common.skc_rcv_saddr;
u32 daddr = skp->__sk_common.skc_daddr;
u16 dport = skp->__sk_common.skc_dport;
event.saddr = saddr;
event.daddr = daddr;
event.dport = dport;
event.pid = pid;
event.uid = bpf_get_current_uid_gid();
event.gid = bpf_get_current_uid_gid() >> 32;
bpf_get_current_comm(&event.comm, sizeof(event.comm));
// output
buffer.ringbuf_output(&event, sizeof(event), 0);
//bpf_trace_printk("trace_tcp4connect %x %x %d\\n", saddr, daddr, ntohs(dport));

currsock.delete(&pid);

return 0;
}
"""

def set_callback(self, host, callback: Callable[[NamedTuple], None]):
def _(ctx, data, size):
event = host["buffer"].event(data)
return callback(self._convert_data(event)) # type: ignore

host["buffer"].open_ring_buffer(_)

wunder957 marked this conversation as resolved.
Show resolved Hide resolved

@hookimpl
def init_tracer(config):
return TcpconnectTracer(config)


if __name__ == "__main__":
from bcc import BPF

b = BPF(text=TcpconnectTracer.prog)
tracer = TcpconnectTracer()
tracer.attach(b)

def print_callback(data: NamedTuple):
print(f"[{data.comm} ({data.pid}) {data.uid} {data.gid}] TCP_CONNECT SADDR:{data.saddr} DADDR: {data.daddr} DPORT:{data.dport}") # type: ignore

tracer.set_callback(b, print_callback)
poller = tracer.get_poller(b)
while True:
try:
poller(**tracer.poll_args)
except KeyboardInterrupt:
exit()