netdata/libnetdata/ebpf/ebpf.c

325 lines
8.7 KiB
C

// 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;
}