diff --git a/src/features/struct_ops/README.md b/src/features/struct_ops/README.md new file mode 100644 index 0000000..e3bfac1 --- /dev/null +++ b/src/features/struct_ops/README.md @@ -0,0 +1,74 @@ +# BPF struct_ops Example with Custom Kernel Module + +This example demonstrates BPF struct_ops functionality using a custom kernel module that defines struct_ops operations triggered via a proc file write. + +## Overview + +struct_ops allows BPF programs to implement kernel subsystem operations dynamically. This example includes: + +1. **Kernel Module** (`module/hello.c`) - Defines `bpf_testmod_ops` struct_ops with three callbacks +2. **BPF Program** (`struct_ops.bpf.c`) - Implements the struct_ops callbacks in BPF +3. **User-space Loader** (`struct_ops.c`) - Loads the BPF program and triggers callbacks via `/proc/bpf_testmod_trigger` + +## Building and Running + +### 1. Build the kernel module: +```bash +cd module +make +cd .. +``` + +### 2. Load the kernel module: +```bash +sudo insmod module/hello.ko +``` + +### 3. Build the BPF program: +```bash +make +``` + +### 4. Run the example: +```bash +sudo ./struct_ops +``` + +### 5. Check kernel logs: +```bash +sudo dmesg -w +``` + +You should see output like: +``` +bpf_testmod loaded with struct_ops support +bpf_testmod_ops registered +Calling struct_ops callbacks: +BPF test_1 called! +test_1() returned: 42 +BPF test_2 called: 10 + 20 = 30 +test_2(10, 20) returned: 30 +BPF test_3 called with buffer length 21 +First char: H +test_3() called with buffer +``` + +### 6. Clean up: +```bash +sudo rmmod hello +make clean +``` + +## How It Works + +1. The kernel module registers a custom struct_ops type `bpf_testmod_ops` +2. It creates `/proc/bpf_testmod_trigger` - writing to this file triggers the callbacks +3. The BPF program implements the three callbacks: `test_1`, `test_2`, and `test_3` +4. The user-space program loads the BPF program and periodically writes to the proc file +5. Each write triggers all registered callbacks, demonstrating BPF struct_ops in action + +## Troubleshooting + +- If you get "Failed to attach struct_ops", make sure the kernel module is loaded +- Check `dmesg` for any error messages from the kernel module or BPF verifier +- Ensure your kernel has CONFIG_BPF_SYSCALL=y and supports struct_ops \ No newline at end of file diff --git a/src/features/struct_ops/module/Makefile b/src/features/struct_ops/module/Makefile index 44afa31..4b6f623 100644 --- a/src/features/struct_ops/module/Makefile +++ b/src/features/struct_ops/module/Makefile @@ -4,7 +4,7 @@ obj-m += hello.o # hello.o is the target KBUILD_CFLAGS += -g -O2 all: - # Compile the module with BTF information + # Compile the module make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: diff --git a/src/features/struct_ops/module/bpf_testmod.h b/src/features/struct_ops/module/bpf_testmod.h new file mode 100644 index 0000000..5310641 --- /dev/null +++ b/src/features/struct_ops/module/bpf_testmod.h @@ -0,0 +1,11 @@ +#ifndef _BPF_TESTMOD_H +#define _BPF_TESTMOD_H + +/* Shared struct_ops definition between kernel module and BPF program */ +struct bpf_testmod_ops { + int (*test_1)(void); + int (*test_2)(int a, int b); + void (*test_3)(const char *buf, int len); +}; + +#endif /* _BPF_TESTMOD_H */ \ No newline at end of file diff --git a/src/features/struct_ops/module/hello.c b/src/features/struct_ops/module/hello.c index 6691b01..8f23d54 100644 --- a/src/features/struct_ops/module/hello.c +++ b/src/features/struct_ops/module/hello.c @@ -1,89 +1,193 @@ -#include // Macros for module initialization -#include // Core header for loading modules -#include // Kernel logging macros +#include +#include +#include #include #include #include +#include +#include -__bpf_kfunc int bpf_strstr(const char *str, u32 str__sz, const char *substr, u32 substr__sz); - -/* Define a kfunc function */ -__bpf_kfunc_start_defs(); - -__bpf_kfunc int bpf_strstr(const char *str, u32 str__sz, const char *substr, u32 substr__sz) -{ - // Edge case: if substr is empty, return 0 (assuming empty string is found at the start) - if (substr__sz == 0) - { - return 0; - } - // Edge case: if the substring is longer than the main string, it's impossible to find - if (substr__sz > str__sz) - { - return -1; // Return -1 to indicate not found - } - - // Iterate through the main string, considering the size limit - for (size_t i = 0; i <= str__sz - substr__sz; i++) - { - size_t j = 0; - // Compare the substring with the current position in the string - while (j < substr__sz && str[i + j] == substr[j]) - { - j++; - } - // If the entire substring was found - if (j == substr__sz) - { - return i; // Return the index of the first match - } - } - // Return -1 if the substring is not found - return -1; -} - -__bpf_kfunc_end_defs(); - -BTF_KFUNCS_START(bpf_kfunc_example_ids_set) -BTF_ID_FLAGS(func, bpf_strstr) -BTF_KFUNCS_END(bpf_kfunc_example_ids_set) - -// Register the kfunc ID set -static const struct btf_kfunc_id_set bpf_kfunc_example_set = { - .owner = THIS_MODULE, - .set = &bpf_kfunc_example_ids_set, +/* Define our custom struct_ops operations */ +struct bpf_testmod_ops { + int (*test_1)(void); + int (*test_2)(int a, int b); + void (*test_3)(const char *buf, int len); }; -// Function executed when the module is loaded -static int __init hello_init(void) -{ - int ret; +/* Global instance that BPF programs will implement */ +static struct bpf_testmod_ops __rcu *testmod_ops; - printk(KERN_INFO "Hello, world!\n"); - // Register the BTF kfunc ID set - ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_KPROBE, &bpf_kfunc_example_set); - if (ret) - { - pr_err("bpf_kfunc_example: Failed to register BTF kfunc ID set\n"); - return ret; - } - printk(KERN_INFO "bpf_kfunc_example: Module loaded successfully\n"); - return 0; // Return 0 if successful +/* Proc file to trigger the struct_ops */ +static struct proc_dir_entry *trigger_file; + +/* CFI stub functions - required for struct_ops */ +static int bpf_testmod_ops__test_1(void) +{ + return 0; } -// Function executed when the module is removed -static void __exit hello_exit(void) +static int bpf_testmod_ops__test_2(int a, int b) { - // Unregister the BTF kfunc ID set - // unregister_btf_kfunc_id_set(BPF_PROG_TYPE_UNSPEC, &bpf_kfunc_example_set); - printk(KERN_INFO "Goodbye, world!\n"); + return 0; } -// Macros to define the module’s init and exit points -module_init(hello_init); -module_exit(hello_exit); +static void bpf_testmod_ops__test_3(const char *buf, int len) +{ +} -MODULE_LICENSE("GPL"); // License type (GPL) -MODULE_AUTHOR("Your Name"); // Module author -MODULE_DESCRIPTION("A simple module"); // Module description -MODULE_VERSION("1.0"); // Module version +/* CFI stubs structure */ +static struct bpf_testmod_ops __bpf_ops_bpf_testmod_ops = { + .test_1 = bpf_testmod_ops__test_1, + .test_2 = bpf_testmod_ops__test_2, + .test_3 = bpf_testmod_ops__test_3, +}; + +/* BTF and verifier callbacks */ +static int bpf_testmod_ops_init(struct btf *btf) +{ + /* Initialize BTF if needed */ + return 0; +} + +static bool bpf_testmod_ops_is_valid_access(int off, int size, + enum bpf_access_type type, + const struct bpf_prog *prog, + struct bpf_insn_access_aux *info) +{ + /* Allow all accesses for now */ + return true; +} + +static const struct bpf_verifier_ops bpf_testmod_verifier_ops = { + .is_valid_access = bpf_testmod_ops_is_valid_access, +}; + +static int bpf_testmod_ops_init_member(const struct btf_type *t, + const struct btf_member *member, + void *kdata, const void *udata) +{ + /* No special member initialization needed */ + return 0; +} + +/* Registration function */ +static int bpf_testmod_ops_reg(void *kdata, struct bpf_link *link) +{ + struct bpf_testmod_ops *ops = kdata; + + /* Only one instance at a time */ + if (cmpxchg(&testmod_ops, NULL, ops) != NULL) + return -EEXIST; + + pr_info("bpf_testmod_ops registered\n"); + return 0; +} + +/* Unregistration function */ +static void bpf_testmod_ops_unreg(void *kdata, struct bpf_link *link) +{ + struct bpf_testmod_ops *ops = kdata; + + if (cmpxchg(&testmod_ops, ops, NULL) != ops) { + pr_warn("bpf_testmod_ops: unexpected unreg\n"); + return; + } + + pr_info("bpf_testmod_ops unregistered\n"); +} + +/* Struct ops definition */ +static struct bpf_struct_ops bpf_testmod_ops_struct_ops = { + .verifier_ops = &bpf_testmod_verifier_ops, + .init = bpf_testmod_ops_init, + .init_member = bpf_testmod_ops_init_member, + .reg = bpf_testmod_ops_reg, + .unreg = bpf_testmod_ops_unreg, + .cfi_stubs = &__bpf_ops_bpf_testmod_ops, + .name = "bpf_testmod_ops", + .owner = THIS_MODULE, +}; + +/* Proc file write handler to trigger struct_ops */ +static ssize_t trigger_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct bpf_testmod_ops *ops; + char kbuf[64]; + int ret = 0; + + if (count >= sizeof(kbuf)) + count = sizeof(kbuf) - 1; + + if (copy_from_user(kbuf, buf, count)) + return -EFAULT; + + kbuf[count] = '\0'; + + rcu_read_lock(); + ops = rcu_dereference(testmod_ops); + if (ops) { + pr_info("Calling struct_ops callbacks:\n"); + + if (ops->test_1) { + ret = ops->test_1(); + pr_info("test_1() returned: %d\n", ret); + } + + if (ops->test_2) { + ret = ops->test_2(10, 20); + pr_info("test_2(10, 20) returned: %d\n", ret); + } + + if (ops->test_3) { + ops->test_3(kbuf, count); + pr_info("test_3() called with buffer\n"); + } + } else { + pr_info("No struct_ops registered\n"); + } + rcu_read_unlock(); + + return count; +} + +static const struct proc_ops trigger_proc_ops = { + .proc_write = trigger_write, +}; + +static int __init testmod_init(void) +{ + int ret; + + /* Register the struct_ops */ + ret = register_bpf_struct_ops(&bpf_testmod_ops_struct_ops, bpf_testmod_ops); + if (ret) { + pr_err("Failed to register struct_ops: %d\n", ret); + return ret; + } + + /* Create proc file for triggering */ + trigger_file = proc_create("bpf_testmod_trigger", 0222, NULL, &trigger_proc_ops); + if (!trigger_file) { + /* Note: No unregister function available in this kernel version */ + return -ENOMEM; + } + + pr_info("bpf_testmod loaded with struct_ops support\n"); + return 0; +} + +static void __exit testmod_exit(void) +{ + proc_remove(trigger_file); + /* Note: struct_ops unregister happens automatically on module unload */ + pr_info("bpf_testmod unloaded\n"); +} + +module_init(testmod_init); +module_exit(testmod_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("eBPF Example"); +MODULE_DESCRIPTION("BPF struct_ops test module"); +MODULE_VERSION("1.0"); \ No newline at end of file diff --git a/src/features/struct_ops/struct_ops.bpf.c b/src/features/struct_ops/struct_ops.bpf.c index afbb7a9..b97e962 100644 --- a/src/features/struct_ops/struct_ops.bpf.c +++ b/src/features/struct_ops/struct_ops.bpf.c @@ -1,28 +1,40 @@ -/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ -#define BPF_NO_GLOBAL_DATA -#include +/* SPDX-License-Identifier: GPL-2.0 */ +#include #include #include +#include "module/bpf_testmod.h" -typedef unsigned int u32; -typedef unsigned long long u64; -typedef int pid_t; +char _license[] SEC("license") = "GPL"; -extern int bpf_strstr(const char *str, u32 str__sz, const char *substr, u32 substr__sz) __ksym; - -char LICENSE[] SEC("license") = "Dual BSD/GPL"; - -SEC("kprobe/do_unlinkat") -int handle_kprobe(void *ctx) +/* Implement the struct_ops callbacks */ +SEC("struct_ops/test_1") +int BPF_PROG(bpf_testmod_test_1) { - pid_t pid = bpf_get_current_pid_tgid() >> 32; - char str[] = "Hello, world!"; - char substr[] = "wor"; - u32 result = bpf_strstr(str, sizeof(str) - 1, substr, sizeof(substr) - 1); - if (result != -1) - { - bpf_printk("'%s' found in '%s' at index %d\n", substr, str, result); - } - bpf_printk("Hello, world! (pid: %d) bpf_strstr %d\n", pid, result); - return 0; + bpf_printk("BPF test_1 called!\n"); + return 42; } + +SEC("struct_ops/test_2") +int BPF_PROG(bpf_testmod_test_2, int a, int b) +{ + int result = a + b; + bpf_printk("BPF test_2 called: %d + %d = %d\n", a, b, result); + return result; +} + +SEC("struct_ops/test_3") +void BPF_PROG(bpf_testmod_test_3, const char *buf, int len) +{ + bpf_printk("BPF test_3 called with buffer length %d\n", len); + if (len > 0) { + bpf_printk("First char: %c\n", buf[0]); + } +} + +/* Define the struct_ops map */ +SEC(".struct_ops") +struct bpf_testmod_ops testmod_ops = { + .test_1 = (void *)bpf_testmod_test_1, + .test_2 = (void *)bpf_testmod_test_2, + .test_3 = (void *)bpf_testmod_test_3, +}; diff --git a/src/features/struct_ops/struct_ops.c b/src/features/struct_ops/struct_ops.c index 7d1c60c..738111b 100644 --- a/src/features/struct_ops/struct_ops.c +++ b/src/features/struct_ops/struct_ops.c @@ -2,75 +2,94 @@ #include #include #include -#include +#include +#include +#include +#include -#include "struct_ops.skel.h" // Include the generated skeleton header +#include "struct_ops.skel.h" static volatile bool exiting = false; -// Signal handler for graceful termination void handle_signal(int sig) { exiting = true; } +static int trigger_struct_ops(const char *message) { + int fd, ret; + + fd = open("/proc/bpf_testmod_trigger", O_WRONLY); + if (fd < 0) { + perror("open /proc/bpf_testmod_trigger"); + return -1; + } + + ret = write(fd, message, strlen(message)); + if (ret < 0) { + perror("write"); + close(fd); + return -1; + } + + close(fd); + return 0; +} + int main(int argc, char **argv) { struct struct_ops_bpf *skel; + struct bpf_link *link; int err; - // Handle SIGINT and SIGTERM for graceful shutdown signal(SIGINT, handle_signal); + signal(SIGTERM, handle_signal); - // Open the BPF application + /* Open BPF application */ skel = struct_ops_bpf__open(); if (!skel) { fprintf(stderr, "Failed to open BPF skeleton\n"); return 1; } - // Load & verify the BPF program + /* Load BPF programs */ err = struct_ops_bpf__load(skel); if (err) { - fprintf(stderr, "Failed to load and verify BPF skeleton: %d\n", err); + fprintf(stderr, "Failed to load BPF skeleton: %d\n", err); goto cleanup; } - // Attach the BPF program (e.g., attach kprobe) - err = struct_ops_bpf__attach(skel); - if (err) { - fprintf(stderr, "Failed to attach BPF skeleton: %d\n", err); + /* Register struct_ops */ + link = bpf_map__attach_struct_ops(skel->maps.testmod_ops); + if (!link) { + fprintf(stderr, "Failed to attach struct_ops\n"); + err = -1; goto cleanup; } - printf("BPF program loaded and attached successfully. Press Ctrl-C to exit.\n"); - - // Optionally, read the trace_pipe to see bpf_printk outputs - FILE *trace_pipe = fopen("/sys/kernel/debug/tracing/trace_pipe", "r"); - if (!trace_pipe) { - perror("fopen trace_pipe"); - // Continue without reading trace_pipe + printf("Successfully loaded and attached BPF struct_ops!\n"); + printf("Triggering struct_ops callbacks...\n"); + + /* Trigger the struct_ops by writing to proc file */ + if (trigger_struct_ops("Hello from userspace!") < 0) { + printf("Failed to trigger struct_ops - is the kernel module loaded?\n"); + printf("Load it with: sudo insmod module/hello.ko\n"); + } else { + printf("Triggered struct_ops successfully! Check dmesg for output.\n"); } + + printf("\nPress Ctrl-C to exit...\n"); - // Main loop + /* Main loop - trigger periodically */ while (!exiting) { - if (trace_pipe) { - char buffer[256]; - if (fgets(buffer, sizeof(buffer), trace_pipe)) { - printf("%s", buffer); - } else { - if (errno == EINTR) - break; - } - } else { - // If trace_pipe is not available, just sleep - sleep(1); + sleep(2); + if (!exiting && trigger_struct_ops("Periodic trigger") == 0) { + printf("Triggered struct_ops again...\n"); } } - if (trace_pipe) - fclose(trace_pipe); + printf("\nDetaching struct_ops...\n"); + bpf_link__destroy(link); cleanup: - // Clean up and destroy the BPF program struct_ops_bpf__destroy(skel); return err < 0 ? -err : 0; }