Skip to content

Commit

Permalink
Merge pull request #2 from odigos-io/pid_ns
Browse files Browse the repository at this point in the history
pass a pid ns to the ebpf program
  • Loading branch information
RonFed authored Oct 13, 2024
2 parents a97dfd1 + 2ea6226 commit 238adb8
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 73 deletions.
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ RUN --mount=type=cache,target=/go/pkg \
go mod download && go mod verify

COPY . .
ARG TARGETARCH
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
make build
GOOS=linux GOARCH=$TARGETARCH make build

CMD [ "/app/runtime-detector" ]
5 changes: 0 additions & 5 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import (
detector "github.com/odigos-io/runtime-detector"
)

const envProcFS = "PROC_FS_PATH"

func newLogger() *slog.Logger {
return slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
AddSource: true,
Expand All @@ -26,9 +24,6 @@ func main() {
detector.WithLogger(l),
detector.WithEnvironments("NODE_OPTIONS", "PYTHONPATH", "NODE_VERSION", "PYTHON_VERSION", "JAVA_VERSION", "ODIGOS_POD_NAME", "ODIGOS_CONTAINER_NAME", "ODIGOS_WORKLOAD_NAMESPACE"),
}
if p := os.Getenv(envProcFS); p != "" {
opts = append(opts, detector.WithProcFSPath(p))
}

details := make(chan *detector.Details)
done := make(chan struct{})
Expand Down
19 changes: 2 additions & 17 deletions daemonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ spec:
app: runtime-detect
spec:
containers:
- image: dev/runtime-detector:test
imagePullPolicy: IfNotPresent
- image: public.ecr.aws/y2v0v6s7/dev/runtime-detector:test
imagePullPolicy: Always
name: runtime-detector
resources: {}
securityContext:
Expand All @@ -27,17 +27,6 @@ spec:
volumeMounts:
- mountPath: /sys/kernel/debug
name: kernel-debug
- mountPath: /procHost
name: host-proc
env:
# when deploying in a kind cluster, the /proc filesystem is not the same as the host's
# this environment variable is used to tell the runtime-detector container where to find the host's /proc filesystem
- name: PROC_FS_PATH
value: "/procHost"
# used as a dummy HTTP server to test the runtime-detector configuration
ports:
- containerPort: 8080
protocol: TCP
dnsPolicy: ClusterFirst
hostPID: true
restartPolicy: Always
Expand All @@ -49,7 +38,3 @@ spec:
hostPath:
path: /sys/kernel/debug
type: ""
- name: host-proc
hostPath:
path: /procHost
type: Directory
18 changes: 8 additions & 10 deletions detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ func NewDetector(ctx context.Context, output chan<- *Details, opts ...DetectorOp

pids := make(chan int)

// the following steps are used to create the filters chain
// 1. ebpf probe generating events and doing basic filtering
// 2. duration filter to filter out short-lived processes
// 3. k8s filter to check if the process is running in a k8s pod
k8s := k8sfilter.NewK8sFilter(c.logger, pids)
durationFilter := duration.NewDurationFilter(c.logger, c.minDuration, k8s)
p := probe.New(c.logger, durationFilter)
Expand Down Expand Up @@ -130,6 +134,7 @@ func (d *Detector) Run(ctx context.Context) error {
return err
}

// read pid events from the filters chain and pass them to the client
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
Expand Down Expand Up @@ -240,16 +245,9 @@ func WithMinDuration(d time.Duration) DetectorOption {
})
}

// WithProcFSPath returns a [DetectorOption] that configures a [Detector] to use the specified path to the 'proc' filesystem,
// the default is /proc. In some cases, the 'proc' filesystem is mounted in a different location.
// For example when using Kind, the 'proc' filesystem is that of the container running kind.
func WithProcFSPath(p string) DetectorOption {
return fnOpt(func(_ context.Context, c detectorConfig) (detectorConfig, error) {
err := proc.SetProcFS(p)
return c, err
})
}

// WithEnvironments returns a [DetectorOption] that configures a [Detector] to include the specified environment
// variables in the output (in case they are set for the process). If no environment keys are provided, no environment
// variables will be included in the output.
func WithEnvironments(envs ...string) DetectorOption {
return fnOpt(func(_ context.Context, c detectorConfig) (detectorConfig, error) {
envsMap := make(map[string]struct{})
Expand Down
7 changes: 5 additions & 2 deletions internal/probe/bpf_arm64_bpfel.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions internal/probe/bpf_x86_bpfel.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 74 additions & 9 deletions internal/probe/ebpf/detector.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,29 @@
#include "bpf_helpers.h"
#include "bpf_core_read.h"

char __license[] SEC("license") = "Dual MIT/GPL";

const char odigos_env_prefix[] = "ODIGOS_POD";
#define ODIGOS_PREFIX_LEN (10)

#define MAX_ENV_VARS (128)
#define MAX_NS_FOR_PID (8)

// This max is only for processes we track.
// Those which are filtered out are not counted in this limit.
#define MAX_CONCURRENT_PIDS (16384) // 2^14

struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
} events SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, u32); // the pid as return from bpf_get_current_pid_tgid()
__type(value, u32); // the pid as return from get_pid_for_configured_ns()
__uint(max_entries, MAX_CONCURRENT_PIDS);
} tracked_pids_to_ns_pids SEC(".maps");

typedef enum {
UNDEFINED = 0,
PROCESS_EXEC = 1,
Expand All @@ -17,12 +36,9 @@ typedef struct process_event {
u32 pid;
} process_event_t;

char __license[] SEC("license") = "Dual MIT/GPL";

const char odigos_env_prefix[] = "ODIGOS_POD";
#define ODIGOS_PREFIX_LEN (10)

#define MAX_ENV_VARS (128)
// This is the inode number of the PID namespace we are interested in.
// It is set by the userspace code.
volatile const u32 pid_ns_inode = 0;

static __always_inline bool is_odigos_env_prefix(char *env) {
// don't compare the null terminator
Expand All @@ -34,8 +50,38 @@ static __always_inline bool is_odigos_env_prefix(char *env) {
return true;
}

static __always_inline u32 get_pid_for_configured_ns(struct task_struct *task) {
struct upid upid = {0};
u32 inum = 0;
u32 selected_pid = 0;
unsigned int num_pids = BPF_CORE_READ(task, thread_pid, level);

if (num_pids > MAX_NS_FOR_PID) {
bpf_printk("Number of PIDs is greater than supported: %d", num_pids);
num_pids = MAX_NS_FOR_PID;
}

for (int i = 0; i < num_pids && i < MAX_NS_FOR_PID; i++) {
upid = BPF_CORE_READ(task, thread_pid, numbers[i]);
inum = BPF_CORE_READ(upid.ns, ns.inum);
if (inum == pid_ns_inode) {
selected_pid = upid.nr;
break;
}
}

return selected_pid;
}

SEC("tracepoint/syscalls/sys_enter_execve")
int tracepoint__syscalls__sys_enter_execve(struct syscall_trace_enter* ctx) {
/*
The format of the tracepoint args is:
args:
field:const char * filename; offset:16; size:8; signed:0;
field:const char *const * argv; offset:24; size:8; signed:0;
field:const char *const * envp; offset:32; size:8; signed:0;
*/
const char **args = (const char **)(ctx->args[2]);
const char *argp;
// save space for a terminating null byte
Expand Down Expand Up @@ -65,12 +111,24 @@ int tracepoint__syscalls__sys_enter_execve(struct syscall_trace_enter* ctx) {
return 0;
}

struct task_struct *task = (struct task_struct *)bpf_get_current_task();
u32 selected_pid = get_pid_for_configured_ns(task);
if (selected_pid == 0) {
bpf_printk("Could not find PID for task: 0x%llx", bpf_get_current_pid_tgid());
return 0;
}

u64 pid_tgid = bpf_get_current_pid_tgid();
u32 tgid = pid_tgid >> 32;
u32 pid = (u32)(pid_tgid & 0xFFFFFFFF);
ret = bpf_map_update_elem(&tracked_pids_to_ns_pids, &pid, &selected_pid, BPF_ANY);
if (ret != 0) {
bpf_printk("Failed to update PID to NS PID map: %d", ret);
return 0;
}

process_event_t event = {
.type = PROCESS_EXEC,
.pid = tgid,
.pid = selected_pid,
};

bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));
Expand All @@ -90,11 +148,18 @@ int tracepoint__sched__sched_process_exit(struct trace_event_raw_sched_process_e
return 0;
}

// look this pid in the map, avoid sending exit event for PIDs we didn't send exec event for.
u32 *selected_pid = bpf_map_lookup_elem(&tracked_pids_to_ns_pids, &pid);
if (selected_pid == NULL) {
return 0;
}

process_event_t event = {
.type = PROCESS_EXIT,
.pid = tgid,
.pid = *selected_pid,
};

bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));
bpf_map_delete_elem(&tracked_pids_to_ns_pids, &pid);
return 0;
}
Loading

0 comments on commit 238adb8

Please sign in to comment.