Skip to content

Instantly share code, notes, and snippets.

@teknoraver
Last active June 30, 2024 12:29
Show Gist options
  • Save teknoraver/ed341c5506027c7cdda9e759fdd30c21 to your computer and use it in GitHub Desktop.
Save teknoraver/ed341c5506027c7cdda9e759fdd30c21 to your computer and use it in GitHub Desktop.
*.o
sysctl_monitor
vmlinux.h
*.skel.h
LDLIBS += -lbpf
CFLAGS += -O2 -pipe -g -Wall
all:: sysctl_monitor
vmlinux.h:
bpftool btf dump file /sys/kernel/btf/vmlinux format c > $@
sysctl_monitor_bpf.skel.h: sysctl_monitor.bpf.o
bpftool gen skeleton $< > $@
sysctl_monitor.bpf.o: sysctl_monitor.bpf.c vmlinux.h
clang $(CFLAGS) -target bpf -c $< -o $@
sysctl_monitor: sysctl_monitor.c sysctl_monitor_bpf.skel.h
clean::
$(RM) *.o sysctl_monitor *.skel.h vmlinux.h
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include "vals.h"
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024);
} written_sysctls SEC(".maps");
static bool my_streq(const char *s1, const char *s2, int l)
{
for (int i = 0; i < l; i++) {
if (s1[i] != s2[i])
return false;
if (!s1[i])
return true;
}
return true;
}
struct str {
char *s;
int l;
};
static long cut_last(int i, struct str *str)
{
char *s;
i = str->l - i - 1;
s = str->s + i;
// Sanity checks for the preverifier
if (i < 0 || i >= str->l)
return 1;
if (*s == 0)
return 0;
if (*s == '\n' || *s == '\r' || *s == ' ' || *s == '\t') {
*s = 0;
return 0;
}
return 1;
}
// Cut off trailing whitespace and newlines
static void chop(char *s, int l)
{
struct str str = { s, l };
bpf_loop(l, cut_last, &str, 0);
}
SEC("cgroup/sysctl")
int sysctl_monitor(struct bpf_sysctl *ctx)
{
struct vals vals = { };
// Allow reads
if (!ctx->write)
return 1;
// Only monitor net/
bpf_sysctl_get_name(ctx, vals.name, sizeof(vals.name), 0);
if (bpf_strncmp(vals.name, 4, "net/"))
return 1;
bpf_get_current_comm(vals.comm, sizeof(vals.comm));
bpf_sysctl_get_current_value(ctx, vals.current, sizeof(vals.current));
bpf_sysctl_get_new_value(ctx, vals.newvalue, sizeof(vals.newvalue));
// Both the kernel and userspace applications add a newline at the end,
// remove it from both strings
chop(vals.current, sizeof(vals.current));
chop(vals.newvalue, sizeof(vals.newvalue));
// If new value is the same, ignore it
if (my_streq(vals.current, vals.newvalue, sizeof(vals.current)))
return 1;
bpf_ringbuf_output(&written_sysctls, &vals, sizeof(vals), 0);
return 0;
}
char _license[] SEC("license") = "GPL";
#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include "vals.h"
#include "sysctl_monitor_bpf.skel.h"
#define CGROUP_MOUNT_DFLT "/sys/fs/cgroup"
struct ring_buffer *rb;
static void int_exit(int sig)
{
int cgfd = open(CGROUP_MOUNT_DFLT, O_PATH | O_DIRECTORY | O_CLOEXEC);
if (cgfd >= 0) {
bpf_prog_detach(cgfd, BPF_CGROUP_SYSCTL);
close(cgfd);
}
}
static int log_sysctl_writes(void *ctx, void *data, size_t data_sz)
{
struct vals *vals = data;
printf("%s tried to update '%s' from '%s' to '%s'\n", vals->comm, vals->name, vals->current, vals->newvalue);
return 0;
}
static int attach_bpf(void)
{
struct sysctl_monitor_bpf *skel;
int progfd, cgfd;
int err;
cgfd = open(CGROUP_MOUNT_DFLT, O_PATH | O_DIRECTORY | O_CLOEXEC);
if (cgfd < 0) {
printf("failed to open cgroup mount point\n");
return 1;
}
skel = sysctl_monitor_bpf__open_and_load();
if (!skel) {
printf("failed to open and load BPF object\n");
return 1;
}
err = sysctl_monitor_bpf__attach(skel);
if (err) {
printf("failed to attach BPF program\n");
return 1;
}
rb = ring_buffer__new(bpf_map__fd(skel->maps.written_sysctls), log_sysctl_writes, NULL, NULL);
if (!rb) {
printf("failed to create ring buffer\n");
return 1;
}
progfd = bpf_program__fd(skel->progs.sysctl_monitor);
if (bpf_prog_attach(progfd, cgfd, BPF_CGROUP_SYSCTL, BPF_F_ALLOW_OVERRIDE) < 0) {
close(progfd);
return 1;
}
close(progfd);
return 0;
}
int main(int argc, char **argv)
{
int ret, cgfd;
signal(SIGINT, int_exit);
signal(SIGTERM, int_exit);
signal(SIGQUIT, int_exit);
if (attach_bpf())
return 1;
// In business
while (1) {
ret = ring_buffer__poll(rb, 1000);
if (ret < 0) {
if (errno == EINTR)
break;
printf("Error polling ring buffer\n");
break;
}
}
cgfd = open(CGROUP_MOUNT_DFLT, O_PATH | O_DIRECTORY | O_CLOEXEC);
if (cgfd >= 0) {
bpf_prog_detach(cgfd, BPF_CGROUP_SYSCTL);
close(cgfd);
}
return 0;
}
#pragma once
struct vals {
char comm[32];
char name[64];
char current[32];
char newvalue[32];
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment