325 lines
8.7 KiB
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;
|
|
}
|