diff options
author | Vladimir Kobal <vlad@prokk.net> | 2020-07-16 15:10:35 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-16 15:10:35 +0300 |
commit | 3e84239ff64ececa6bd54bbddbc9e3d22542dfa9 (patch) | |
tree | a86249faf962bec50715e925a432bb07efe3d102 /libnetdata/ebpf | |
parent | 1b606bc1b2a16dfbcc8b5c7ed780906b482eef11 (diff) |
Use the libbpf library for the eBPF plugin (#9490)
Diffstat (limited to 'libnetdata/ebpf')
-rw-r--r-- | libnetdata/ebpf/README.md | 1 | ||||
-rw-r--r-- | libnetdata/ebpf/ebpf.c | 165 | ||||
-rw-r--r-- | libnetdata/ebpf/ebpf.h | 43 | ||||
-rw-r--r-- | libnetdata/ebpf/libbpf.c.diff | 229 |
4 files changed, 319 insertions, 119 deletions
diff --git a/libnetdata/ebpf/README.md b/libnetdata/ebpf/README.md index e69de29bb2..72446eb5bd 100644 --- a/libnetdata/ebpf/README.md +++ b/libnetdata/ebpf/README.md @@ -0,0 +1 @@ +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Flibnetdata%2Febpf%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/libnetdata/ebpf/ebpf.c b/libnetdata/ebpf/ebpf.c index 75a9f5dcb3..0d7af7068a 100644 --- a/libnetdata/ebpf/ebpf.c +++ b/libnetdata/ebpf/ebpf.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> @@ -5,10 +7,11 @@ #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); +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) { + if (out) { fprintf(out, "Cannot open %s : %s\n", filename, strerror(errno)); } return 1; @@ -20,10 +23,10 @@ static int clean_kprobe_event(FILE *out, char *filename, char *father_pid, netda 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)); + if (out) { + fprintf( + out, "Cannot remove the event (%d, %d) '%s' from %s : %s\n", getppid(), getpid(), cmd, filename, + strerror((int)errno)); } ret = 1; } @@ -34,16 +37,17 @@ static int clean_kprobe_event(FILE *out, char *filename, char *father_pid, netda return ret; } -int clean_kprobe_events(FILE *out, int pid, netdata_ebpf_events_t *ptr) { +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]; + char filename[FILENAME_MAX + 1]; snprintf(filename, FILENAME_MAX, "%s%s", NETDATA_DEBUGFS, "kprobe_events"); char removeme[16]; - snprintf(removeme, 15,"%d", pid); + snprintf(removeme, 15, "%d", pid); int i; - for (i = 0 ; ptr[i].name ; i++) { + for (i = 0; ptr[i].name; i++) { if (clean_kprobe_event(out, filename, removeme, &ptr[i])) { break; } @@ -54,7 +58,8 @@ int clean_kprobe_events(FILE *out, int pid, netdata_ebpf_events_t *ptr) { //---------------------------------------------------------------------------------------------------------------------- -int get_kernel_version(char *out, int size) { +int get_kernel_version(char *out, int size) +{ char major[16], minor[16], patch[16]; char ver[256]; char *version = ver; @@ -73,12 +78,14 @@ int get_kernel_version(char *out, int size) { close(fd); char *move = major; - while (*version && *version != '.') *move++ = *version++; + while (*version && *version != '.') + *move++ = *version++; *move = '\0'; version++; move = minor; - while (*version && *version != '.') *move++ = *version++; + while (*version && *version != '.') + *move++ = *version++; *move = '\0'; if (*version) @@ -87,27 +94,28 @@ int get_kernel_version(char *out, int size) { return -1; move = patch; - while (*version && *version != '\n') *move++ = *version++; + 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)); + return ((int)(str2l(major) * 65536) + (int)(str2l(minor) * 256) + (int)str2l(patch)); } int get_redhat_release() { char buffer[256]; - int major,minor; + int major, minor; FILE *fp = fopen("/etc/redhat-release", "r"); if (fp) { major = 0; minor = -1; size_t length = fread(buffer, sizeof(char), 255, fp); - if (length > 4 ) { + if (length > 4) { buffer[length] = '\0'; char *end = strchr(buffer, '.'); char *start; @@ -117,13 +125,13 @@ int get_redhat_release() if (end > buffer) { start = end - 1; - major = strtol( start, NULL, 10); + major = strtol(start, NULL, 10); start = ++end; end++; - if(end) { + if (end) { end = 0x00; - minor = strtol( start, NULL, 10); + minor = strtol(start, NULL, 10); } else { minor = -1; } @@ -132,19 +140,21 @@ int get_redhat_release() } fclose(fp); - return ((major*256) + minor); + return ((major * 256) + minor); } else { return -1; } } -static int has_ebpf_kernel_version(int version) { - //Kernel 4.11.0 or RH > 7.5 - return (version >= NETDATA_MINIMUM_EBPF_KERNEL || get_redhat_release() >= NETDATA_MINIMUM_RH_VERSION); +static int has_ebpf_kernel_version(int version) +{ + // 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)) +int has_condition_to_run(int version) +{ + if (!has_ebpf_kernel_version(version)) return 0; return 1; @@ -152,7 +162,8 @@ int has_condition_to_run(int version) { //---------------------------------------------------------------------------------------------------------------------- -char *ebpf_library_suffix(int version, int isrh) { +char *ebpf_kernel_suffix(int version, int isrh) +{ if (isrh) { if (version >= NETDATA_EBPF_KERNEL_4_11) return "4.18.0"; @@ -172,101 +183,63 @@ char *ebpf_library_suffix(int version, int isrh) { //---------------------------------------------------------------------------------------------------------------------- -int ebpf_load_libraries(ebpf_functions_t *ef, char *libbase, char *pluginsdir) +int ebpf_update_kernel(ebpf_data_t *ed) { - char *err = NULL; - char lpath[4096]; - char netdatasl[128]; - void *libnetdata; - - snprintf(netdatasl, 127, "%s.%s", libbase, ef->kernel_string); - snprintf(lpath, 4095, "%s/%s", pluginsdir, netdatasl); - libnetdata = dlopen(lpath, RTLD_LAZY); - if (!libnetdata) { - info("Cannot load library %s for the current kernel.", lpath); - - //Update kernel - char *library = ebpf_library_suffix(ef->running_on_kernel, (ef->isrh < 0)?0:1); - size_t length = strlen(library); - strncpyz(ef->kernel_string, library, length); - ef->kernel_string[length] = '\0'; - - //Try to load the default version - snprintf(netdatasl, 127, "%s.%s", libbase, ef->kernel_string); - snprintf(lpath, 4095, "%s/%s", pluginsdir, netdatasl); - libnetdata = dlopen(lpath, RTLD_LAZY); - if (!libnetdata) { - error("Cannot load %s default library.", lpath); - return -1; - } else { - info("Default shared library %s loaded with success.", lpath); - ef->libnetdata = libnetdata; - } - } else { - info("Current shared library %s loaded with success.", lpath); - ef->libnetdata = libnetdata; - } - - ef->load_bpf_file = dlsym(libnetdata, "load_bpf_file"); - if ((err = dlerror()) != NULL) { - error("Cannot find load_bpf_file: %s", err); - return -1; - } - - ef->bpf_map_lookup_elem = dlsym(libnetdata, "bpf_map_lookup_elem"); - if ((err = dlerror()) != NULL) { - error("Cannot find bpf_map_lookup_elem: %s", err); - return -1; - } - - ef->bpf_map_delete_elem = dlsym(libnetdata, "bpf_map_delete_elem"); - if ((err = dlerror()) != NULL) { - error("Cannot find bpf_map_delete_elem: %s", err); - return -1; - } - - ef->bpf_map_get_next_key = dlsym(libnetdata, "bpf_map_get_next_key"); - if ((err = dlerror()) != NULL) { - error("Cannot find bpf_map_delete_elem: %s", err); - return -1; - } + 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) { +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) + else if (mode == 1) ret = snprintf(name, length, "dnetdata_ebpf_%s.%s.o", program, kernel_string); - else if(mode == 2) + else if (mode == 2) ret = snprintf(name, length, "pnetdata_ebpf_%s.%s.o", program, kernel_string); return ret; } -int ebpf_load_program(char *plugins_dir, - int event_id, int mode , - char *kernel_string, - const char *name, - int *map_fd, - int (*load_bpf_file)(int *, char *, int)) +int ebpf_load_program(char *plugins_dir, int event_id, int mode, char *kernel_string, const char *name, int *map_fd) { + UNUSED(event_id); + char lpath[4096]; char lname[128]; + struct bpf_object *obj; + int prog_fd; int test = select_file(lname, name, (size_t)127, mode, kernel_string); if (test < 0 || test > 127) return -1; - snprintf(lpath, 4096, "%s/%s", plugins_dir, lname); - if (load_bpf_file(map_fd, lpath, event_id)) { + snprintf(lpath, 4096, "%s/%s", plugins_dir, lname); + if (bpf_prog_load(lpath, BPF_PROG_TYPE_KPROBE, &obj, &prog_fd)) { info("Cannot load program: %s", lpath); return -1; } else { info("The eBPF program %s was loaded with success.", 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; + bpf_object__for_each_program(prog, obj) + { + bpf_program__attach(prog); + } + return 0; } diff --git a/libnetdata/ebpf/ebpf.h b/libnetdata/ebpf/ebpf.h index 6e01786c85..1003f9eda3 100644 --- a/libnetdata/ebpf/ebpf.h +++ b/libnetdata/ebpf/ebpf.h @@ -1,7 +1,12 @@ -#ifndef _NETDATA_EBPF_H_ -# define _NETDATA_EBPF_H_ 1 +// SPDX-License-Identifier: GPL-3.0-or-later -# define NETDATA_DEBUGFS "/sys/kernel/debug/tracing/" +#ifndef NETDATA_EBPF_H +#define NETDATA_EBPF_H 1 + +#include <bpf/bpf.h> +#include <bpf/libbpf.h> + +#define NETDATA_DEBUGFS "/sys/kernel/debug/tracing/" /** * The next magic number is got doing the following math: @@ -9,7 +14,7 @@ * * For more details, please, read /usr/include/linux/version.h */ -# define NETDATA_MINIMUM_EBPF_KERNEL 264960 +#define NETDATA_MINIMUM_EBPF_KERNEL 264960 /** * The RedHat magic number was got doing: @@ -19,65 +24,57 @@ * For more details, please, read /usr/include/linux/version.h * in any Red Hat installation. */ -# define NETDATA_MINIMUM_RH_VERSION 1797 +#define NETDATA_MINIMUM_RH_VERSION 1797 /** * 2048 = 8*256 + 0 */ -# define NETDATA_RH_8 2048 +#define NETDATA_RH_8 2048 /** * Kernel 4.17 * * 266496 = 4*65536 + 17*256 */ -# define NETDATA_EBPF_KERNEL_4_17 266496 +#define NETDATA_EBPF_KERNEL_4_17 266496 /** * Kernel 4.15 * * 265984 = 4*65536 + 15*256 */ -# define NETDATA_EBPF_KERNEL_4_15 265984 +#define NETDATA_EBPF_KERNEL_4_15 265984 /** * Kernel 4.11 * * 264960 = 4*65536 + 15*256 */ -# define NETDATA_EBPF_KERNEL_4_11 264960 +#define NETDATA_EBPF_KERNEL_4_11 264960 typedef struct netdata_ebpf_events { char type; char *name; } netdata_ebpf_events_t; -typedef struct ebpf_functions { - void *libnetdata; - int (*load_bpf_file)(int *, char *, int); - //Libbpf (It is necessary to have at least kernel 4.10) - int (*bpf_map_lookup_elem)(int, const void *, void *); - int (*bpf_map_delete_elem)(int fd, const void *key); - int (*bpf_map_get_next_key)(int fd, const void *key, void *next_key); - +typedef struct ebpf_data { int *map_fd; char *kernel_string; uint32_t running_on_kernel; int isrh; -} ebpf_functions_t; +} ebpf_data_t; extern int clean_kprobe_events(FILE *out, int pid, netdata_ebpf_events_t *ptr); extern int get_kernel_version(char *out, int size); extern int get_redhat_release(); extern int has_condition_to_run(int version); -extern char *ebpf_library_suffix(int version, int isrh); -extern int ebpf_load_libraries(ebpf_functions_t *ef, char *libbase, char *pluginsdir); +extern char *ebpf_kernel_suffix(int version, int isrh); +extern int ebpf_update_kernel(ebpf_data_t *ef); extern int ebpf_load_program(char *plugins_dir, int event_id, int mode, char *kernel_string, const char *name, - int *map_fd, - int (*load_bpf_file)(int *,char *, int)); + int *map_fd); -#endif +#endif /* NETDATA_EBPF_H */ diff --git a/libnetdata/ebpf/libbpf.c.diff b/libnetdata/ebpf/libbpf.c.diff new file mode 100644 index 0000000000..f014a7bc64 --- /dev/null +++ b/libnetdata/ebpf/libbpf.c.diff @@ -0,0 +1,229 @@ +--- a/src/libbpf.c ++++ b/src/libbpf.c +@@ -620,6 +620,93 @@ bpf_object__init_prog_names(struct bpf_object *obj) + return 0; + } + ++static __u32 choose_specific_version0(int fd, __u32 current) ++{ ++ char ver[256]; ++ __u32 v_major, v_minor, v_patch; ++ ssize_t len = read(fd, ver, sizeof(ver)); ++ if (len < 0) { ++ return 0; ++ } ++ ver[len] = '\0'; ++ ++ char *first = strchr(ver, ' '); ++ if (!first) { ++ return 0; ++ } ++ ++ first++; ++ char *version = strchr(first, ' '); ++ if (!version) { ++ return 0; ++ } ++ ++ version++; ++ if (sscanf(version, "%u.%u.%u", &v_major, &v_minor, &v_patch) != 3) ++ return current; ++ ++ return KERNEL_VERSION(v_major, v_minor, v_patch); ++} ++ ++static __u32 choose_kernel_version(__u32 current) ++{ ++ FILE *fp_d = fopen("/etc/debian_version","r"); ++ int fp_u = open("/proc/version_signature", O_RDONLY); ++ FILE *fp_rh = fopen("/etc/redhat-release","r"); ++ char tmp[32]; ++ int de = 0; ++ __u32 ret; ++ ++ if (!fp_d && !fp_rh && fp_u == -1) ++ return current; ++ ++ struct utsname u; ++ uname(&u); ++ ++ if (fp_d) { ++ fclose(fp_d); ++ de = 1; ++ } ++ ++ if (fp_rh) { ++ fclose(fp_rh); ++ de = 0; ++ } ++ ++ if ( fp_u > 0 ) { ++ ret = choose_specific_version0(fp_u, current); ++ close(fp_u); ++ return (!ret)?current:ret; ++ } ++ ++ __u32 v_kernel,v_major, v_minor, v_patch; ++ __u32 r_kernel,r_major, r_minor, r_patch; ++ ++ if (sscanf(u.release, "%u.%u.%u-%u", &v_kernel, &v_major, &v_minor, &v_patch) != 4) ++ return current; ++ ++ int length = snprintf(tmp, 31, "%u.%u", v_kernel, v_major); ++ tmp[length] = '\0'; ++ ++ char *parse = strstr(u.version, tmp); ++ if (!parse) { ++ return current; ++ } ++ ++ char *space = strchr(parse, ' '); ++ if(space) { ++ length = (int)(space - parse); ++ strncpy(tmp, parse, (size_t)length); ++ tmp[length] = '\0'; ++ } ++ ++ if (sscanf(tmp, "%u.%u.%u-%u", &r_kernel, &r_major, &r_minor, &r_patch) != 4) ++ return current; ++ ++ ret = (de)?KERNEL_VERSION(r_kernel, r_major, r_minor):KERNEL_VERSION(r_kernel, r_major, r_minor) + r_patch; ++ return (ret > current)?ret:current; ++} ++ + static __u32 get_kernel_version(void) + { + __u32 major, minor, patch; +@@ -628,7 +715,11 @@ static __u32 get_kernel_version(void) + uname(&info); + if (sscanf(info.release, "%u.%u.%u", &major, &minor, &patch) != 3) + return 0; +- return KERNEL_VERSION(major, minor, patch); ++ ++ if (major < 5) ++ return choose_kernel_version(KERNEL_VERSION(major, minor, patch)); ++ else ++ return KERNEL_VERSION(major, minor, patch); + } + + static const struct btf_member * +@@ -3239,6 +3330,10 @@ int bpf_map__resize(struct bpf_map *map, __u32 max_entries) + static int + bpf_object__probe_loading(struct bpf_object *obj) + { ++ // Don't probe loading for very old kernels. CentOS 7 can't load this probe. ++ if (obj->kern_version <= KERNEL_VERSION(3, 10, 0)) ++ return 0; ++ + struct bpf_load_program_attr attr; + char *cp, errmsg[STRERR_BUFSIZE]; + struct bpf_insn insns[] = { +@@ -7612,6 +7707,88 @@ static int perf_event_open_probe(bool uprobe, bool retprobe, const char *name, + return pfd; + } + ++static int perf_event_open_old_kprobe(bool retprobe, const char *name) ++{ ++#define DEBUG_FS "/sys/kernel/debug/tracing/" ++ struct perf_event_attr attr = {}; ++ char errmsg[STRERR_BUFSIZE]; ++ int kfd = -1, ret = -1, err; ++ char event_alias[256], buf[PATH_MAX]; ++ const char *event_prefix = ""; ++ bool event_was_written = false; ++ ++ kfd = open(DEBUG_FS "kprobe_events", O_WRONLY | O_APPEND, 0); ++ if (kfd < 0) { ++ pr_warn("failed to open '%s%s': %s\n", ++ DEBUG_FS, ++ "kprobe_events", ++ libbpf_strerror_r(kfd, errmsg, sizeof(errmsg))); ++ return kfd; ++ } ++ ++ char type = retprobe ? 'r' : 'p'; ++ snprintf(event_alias, sizeof(event_alias), "%c_netdata_%s_%d", type, name, getpid()); ++ ++#ifdef __x86_64__ ++ if (strncmp(name, "sys_", 4) == 0) { ++ snprintf(buf, sizeof(buf), "%c:__x64_%s __x64_%s", ++ type, event_alias, name); ++ ret = write(kfd, buf, strlen(buf)); ++ ++ if (ret >= 0) { ++ event_was_written = true; ++ event_prefix = "__x64_"; ++ } ++ } ++#endif ++ ++ if (!event_was_written) { ++ snprintf(buf, sizeof(buf), "%c:%s %s", ++ type, event_alias, name); ++ ret = write(kfd, buf, strlen(buf)); ++ if (ret < 0) { ++ pr_warn("failed to create kprobe '%s': %s\n", ++ name, ++ libbpf_strerror_r(ret, errmsg, sizeof(errmsg))); ++ close(kfd); ++ return ret; ++ } ++ } ++ ++ close(kfd); ++ ++ snprintf(buf, sizeof(buf), DEBUG_FS "events/kprobes/%s%s/id", ++ event_prefix, ++ event_alias); ++ ++ ret = parse_uint_from_file(buf, "%d\n"); ++ if (ret < 0) { ++ pr_warn("failed to determine event id: %s\n", ++ libbpf_strerror_r(ret, errmsg, sizeof(errmsg))); ++ return ret; ++ } ++ attr.config = ret; ++ ++ attr.size = sizeof(attr); ++ attr.type = PERF_TYPE_TRACEPOINT; ++ attr.config1 = ptr_to_u64(name); /* kprobe_func */ ++ attr.config2 = 0; /* kprobe_addr */ ++ ++ /* pid filter is meaningful only for uprobes */ ++ kfd = syscall(__NR_perf_event_open, &attr, ++ -1 /* pid */, ++ 0 /* cpu */, ++ -1 /* group_fd */, PERF_FLAG_FD_CLOEXEC); ++ if (kfd < 0) { ++ err = -errno; ++ pr_warn("%s perf_event_open_old_kprobe() failed: %s\n", ++ "kprobe", ++ libbpf_strerror_r(err, errmsg, sizeof(errmsg))); ++ return err; ++ } ++ return kfd; ++} ++ + struct bpf_link *bpf_program__attach_kprobe(struct bpf_program *prog, + bool retprobe, + const char *func_name) +@@ -7623,11 +7800,14 @@ struct bpf_link *bpf_program__attach_kprobe(struct bpf_program *prog, + pfd = perf_event_open_probe(false /* uprobe */, retprobe, func_name, + 0 /* offset */, -1 /* pid */); + if (pfd < 0) { +- pr_warn("program '%s': failed to create %s '%s' perf event: %s\n", +- bpf_program__title(prog, false), +- retprobe ? "kretprobe" : "kprobe", func_name, +- libbpf_strerror_r(pfd, errmsg, sizeof(errmsg))); +- return ERR_PTR(pfd); ++ pfd = perf_event_open_old_kprobe(retprobe, func_name); ++ if (pfd < 0) { ++ pr_warn("program '%s': failed to create %s '%s' perf event: %s\n", ++ bpf_program__title(prog, false), ++ retprobe ? "kretprobe" : "kprobe", func_name, ++ libbpf_strerror_r(pfd, errmsg, sizeof(errmsg))); ++ return ERR_PTR(pfd); ++ } + } + link = bpf_program__attach_perf_event(prog, pfd); + if (IS_ERR(link)) { |