diff options
Diffstat (limited to 'libnetdata/ebpf/ebpf.c')
-rw-r--r-- | libnetdata/ebpf/ebpf.c | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/libnetdata/ebpf/ebpf.c b/libnetdata/ebpf/ebpf.c new file mode 100644 index 000000000..a9ff21f69 --- /dev/null +++ b/libnetdata/ebpf/ebpf.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dlfcn.h> +#include <sys/utsname.h> + +#include "../libnetdata.h" + +/* +static int clean_kprobe_event(FILE *out, char *filename, char *father_pid, netdata_ebpf_events_t *ptr) +{ + int fd = open(filename, O_WRONLY | O_APPEND, 0); + if (fd < 0) { + if (out) { + fprintf(out, "Cannot open %s : %s\n", filename, strerror(errno)); + } + return 1; + } + + char cmd[1024]; + int length = snprintf(cmd, 1023, "-:kprobes/%c_netdata_%s_%s", ptr->type, ptr->name, father_pid); + int ret = 0; + if (length > 0) { + ssize_t written = write(fd, cmd, strlen(cmd)); + if (written < 0) { + if (out) { + fprintf( + out, "Cannot remove the event (%d, %d) '%s' from %s : %s\n", getppid(), getpid(), cmd, filename, + strerror((int)errno)); + } + ret = 1; + } + } + + close(fd); + + return ret; +} + +int clean_kprobe_events(FILE *out, int pid, netdata_ebpf_events_t *ptr) +{ + debug(D_EXIT, "Cleaning parent process events."); + char filename[FILENAME_MAX + 1]; + snprintf(filename, FILENAME_MAX, "%s%s", NETDATA_DEBUGFS, "kprobe_events"); + + char removeme[16]; + snprintf(removeme, 15, "%d", pid); + + int i; + for (i = 0; ptr[i].name; i++) { + if (clean_kprobe_event(out, filename, removeme, &ptr[i])) { + break; + } + } + + return 0; +} +*/ + +//---------------------------------------------------------------------------------------------------------------------- + +int get_kernel_version(char *out, int size) +{ + char major[16], minor[16], patch[16]; + char ver[VERSION_STRING_LEN]; + char *version = ver; + + out[0] = '\0'; + int fd = open("/proc/sys/kernel/osrelease", O_RDONLY); + if (fd < 0) + return -1; + + ssize_t len = read(fd, ver, sizeof(ver)); + if (len < 0) { + close(fd); + return -1; + } + + close(fd); + + char *move = major; + while (*version && *version != '.') + *move++ = *version++; + *move = '\0'; + + version++; + move = minor; + while (*version && *version != '.') + *move++ = *version++; + *move = '\0'; + + if (*version) + version++; + else + return -1; + + move = patch; + while (*version && *version != '\n') + *move++ = *version++; + *move = '\0'; + + fd = snprintf(out, (size_t)size, "%s.%s.%s", major, minor, patch); + if (fd > size) + error("The buffer to store kernel version is not smaller than necessary."); + + return ((int)(str2l(major) * 65536) + (int)(str2l(minor) * 256) + (int)str2l(patch)); +} + +int get_redhat_release() +{ + char buffer[VERSION_STRING_LEN + 1]; + int major, minor; + FILE *fp = fopen("/etc/redhat-release", "r"); + + if (fp) { + major = 0; + minor = -1; + size_t length = fread(buffer, sizeof(char), VERSION_STRING_LEN, fp); + if (length > 4) { + buffer[length] = '\0'; + char *end = strchr(buffer, '.'); + char *start; + if (end) { + *end = 0x0; + + if (end > buffer) { + start = end - 1; + + major = strtol(start, NULL, 10); + start = ++end; + + end++; + if (end) { + end = 0x00; + minor = strtol(start, NULL, 10); + } else { + minor = -1; + } + } + } + } + + fclose(fp); + return ((major * 256) + minor); + } else { + return -1; + } +} + +/** + * Check if the kernel is in a list of rejected ones + * + * @return Returns 1 if the kernel is rejected, 0 otherwise. + */ +static int kernel_is_rejected() +{ + // Get kernel version from system + char version_string[VERSION_STRING_LEN + 1]; + int version_string_len = 0; + + if (read_file("/proc/version_signature", version_string, VERSION_STRING_LEN)) { + if (read_file("/proc/version", version_string, VERSION_STRING_LEN)) { + struct utsname uname_buf; + if (!uname(&uname_buf)) { + info("Cannot check kernel version"); + return 0; + } + version_string_len = + snprintfz(version_string, VERSION_STRING_LEN, "%s %s", uname_buf.release, uname_buf.version); + } + } + + if (!version_string_len) + version_string_len = strlen(version_string); + + // Open a file with a list of rejected kernels + char *config_dir = getenv("NETDATA_USER_CONFIG_DIR"); + if (config_dir == NULL) { + config_dir = CONFIG_DIR; + } + + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/%s", config_dir, EBPF_KERNEL_REJECT_LIST_FILE); + FILE *kernel_reject_list = fopen(filename, "r"); + + if (!kernel_reject_list) { + config_dir = getenv("NETDATA_STOCK_CONFIG_DIR"); + if (config_dir == NULL) { + config_dir = LIBCONFIG_DIR; + } + + snprintfz(filename, FILENAME_MAX, "%s/%s", config_dir, EBPF_KERNEL_REJECT_LIST_FILE); + kernel_reject_list = fopen(filename, "r"); + + if (!kernel_reject_list) + return 0; + } + + // Find if the kernel is in the reject list + char *reject_string = NULL; + size_t buf_len = 0; + ssize_t reject_string_len; + while ((reject_string_len = getline(&reject_string, &buf_len, kernel_reject_list) - 1) > 0) { + if (version_string_len >= reject_string_len) { + if (!strncmp(version_string, reject_string, reject_string_len)) { + info("A buggy kernel is detected"); + fclose(kernel_reject_list); + freez(reject_string); + return 1; + } + } + } + + fclose(kernel_reject_list); + freez(reject_string); + + return 0; +} + +static int has_ebpf_kernel_version(int version) +{ + if (kernel_is_rejected()) + return 0; + + // Kernel 4.11.0 or RH > 7.5 + return (version >= NETDATA_MINIMUM_EBPF_KERNEL || get_redhat_release() >= NETDATA_MINIMUM_RH_VERSION); +} + +int has_condition_to_run(int version) +{ + if (!has_ebpf_kernel_version(version)) + return 0; + + return 1; +} + +//---------------------------------------------------------------------------------------------------------------------- + +char *ebpf_kernel_suffix(int version, int isrh) +{ + if (isrh) { + if (version >= NETDATA_EBPF_KERNEL_4_11) + return "4.18"; + else + return "3.10"; + } else { + if (version >= NETDATA_EBPF_KERNEL_5_10) + return "5.10"; + else if (version >= NETDATA_EBPF_KERNEL_4_17) + return "5.4"; + else if (version >= NETDATA_EBPF_KERNEL_4_15) + return "4.16"; + else if (version >= NETDATA_EBPF_KERNEL_4_11) + return "4.14"; + } + + return NULL; +} + +//---------------------------------------------------------------------------------------------------------------------- + +int ebpf_update_kernel(ebpf_data_t *ed) +{ + char *kernel = ebpf_kernel_suffix(ed->running_on_kernel, (ed->isrh < 0) ? 0 : 1); + size_t length = strlen(kernel); + strncpyz(ed->kernel_string, kernel, length); + ed->kernel_string[length] = '\0'; + + return 0; +} + +static int select_file(char *name, const char *program, size_t length, int mode, char *kernel_string) +{ + int ret = -1; + if (!mode) + ret = snprintf(name, length, "rnetdata_ebpf_%s.%s.o", program, kernel_string); + else if (mode == 1) + ret = snprintf(name, length, "dnetdata_ebpf_%s.%s.o", program, kernel_string); + else if (mode == 2) + ret = snprintf(name, length, "pnetdata_ebpf_%s.%s.o", program, kernel_string); + + return ret; +} + +struct bpf_link **ebpf_load_program(char *plugins_dir, ebpf_module_t *em, char *kernel_string, struct bpf_object **obj, int *map_fd) +{ + char lpath[4096]; + char lname[128]; + int prog_fd; + + int test = select_file(lname, em->thread_name, (size_t)127, em->mode, kernel_string); + if (test < 0 || test > 127) + return NULL; + + snprintf(lpath, 4096, "%s/%s", plugins_dir, lname); + if (bpf_prog_load(lpath, BPF_PROG_TYPE_KPROBE, obj, &prog_fd)) { + em->enabled = CONFIG_BOOLEAN_NO; + info("Cannot load program: %s", lpath); + return NULL; + } else { + info("The eBPF program %s was loaded with success.", em->thread_name); + } + + struct bpf_map *map; + size_t i = 0; + bpf_map__for_each(map, *obj) + { + map_fd[i] = bpf_map__fd(map); + i++; + } + + struct bpf_program *prog; + struct bpf_link **links = callocz(NETDATA_MAX_PROBES , sizeof(struct bpf_link *)); + i = 0; + bpf_object__for_each_program(prog, *obj) + { + links[i] = bpf_program__attach(prog); + i++; + } + + return links; +} |