Skip to content

Commit

Permalink
logger: introduce logger callbacks
Browse files Browse the repository at this point in the history
loggerCallback() calls callbacks, log() and []logFnFilters(),
which can be set by the libbpfgo consumer via SetLoggerCbs().

This moves output filtering from C libbpf_print_fn() to
Go filterOutput() which can be set as one of the slice of filter funcs.

This also introduces LogWarnLevel, LogInfoLevel and LogDebugLevel
constants.
  • Loading branch information
geyslan committed Jan 31, 2023
1 parent 8b1513e commit 024fa7d
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 29 deletions.
42 changes: 13 additions & 29 deletions libbpfgo.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,46 +15,30 @@
#include <bpf/bpf.h>
#include <bpf/libbpf.h>

extern void loggerCallback(enum libbpf_print_level level, char *output);

int libbpf_print_fn(enum libbpf_print_level level, // libbpf print level
const char *format, // format used for the msg
va_list args) { // args used by format

if (level != LIBBPF_WARN)
return 0;

int ret;
char str[300];
int ret = 0;
char *out;
va_list check;

out = (char *)calloc(1, 300);
if (!out)
return -ENOMEM;

va_copy(check, args);
ret = vsnprintf(str, sizeof(str), format, check);
ret = vsnprintf(out, 300, format, check);
va_end(check);

if (ret <= 0) {
goto done;
}
if (ret > 0)
loggerCallback(level, out);

// BUG: https:/github.com/aquasecurity/tracee/issues/1676
if (strstr(str, "Exclusivity flag on") != NULL) {
return 0;
}

// BUG: https://github.com/aquasecurity/tracee/issues/2446
if (strstr(str, "failed to create kprobe") != NULL) {
if (strstr(str, "trace_check_map_func_compatibility") != NULL)
return 0;
}

// AttachCgroupLegacy() will first try AttachCgroup() and it might fail. This
// is not an error and is the best way of probing for eBPF cgroup attachment
// link existence.
if (strstr(str, "cgroup") != NULL) {
if (strstr(str, "Invalid argument") != NULL)
return 0;
}
free(out);

done:
return vfprintf(stderr, format, args);
return ret;
}

void set_print_fn() { libbpf_set_print(libbpf_print_fn); }
Expand Down
157 changes: 157 additions & 0 deletions logger_cb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package libbpfgo

/*
#include <bpf/libbpf.h>
*/
import "C"

import (
"fmt"
"os"
"regexp"
"strings"
)

// This callback definition needs to be in a different file from where it is declared in C
// Otherwise, multiple definition compilation error will occur

// loggerCallback is called by libbpf_print_fn() which in turn is called by libbpf
//
//export loggerCallback
func loggerCallback(libbpfPrintLevel int, libbpfOutput *C.char) {
var (
level int
goOutput string
)

goOutput = C.GoString(libbpfOutput)
goOutput = strings.TrimSuffix(goOutput, "\n")

for _, fnFilterOut := range logFnFilters {
if fnFilterOut != nil {
if fnFilterOut(libbpfPrintLevel, goOutput) {
return
}
}
}

log(level, goOutput)
}

const (
LogWarnLevel = int(C.LIBBPF_WARN)
LogInfoLevel = int(C.LIBBPF_INFO)
LogDebugLevel = int(C.LIBBPF_DEBUG)
)

var log = logFallback
var logFnFilters = []func(libLevel int, msg string) bool{
LogFilterLevel,
LogFilterOutput,
}

// SetLoggerCbs receives a function to be used to log libbpf outputs and
// a list of functions to filter this output out
// If fnLog is nil, logFallback will continue to be used
// If fnFilters is nil, logFnFilters will continue to be used
func SetLoggerCbs(
fnLog func(level int, msg string, keyValues ...interface{}), // log callback
fnFilters []func(libLevel int, msg string) bool, // Filter out callbacks
) {
if fnLog != nil {
log = fnLog
}
if fnFilters != nil {
logFnFilters = fnFilters
}
}

// logFallback:
// - level is ignored in this stage
// - type coercion only takes care of string types
// - keyValues is not required to contain pairs
// - outputs all to stderr
func logFallback(level int, msg string, keyValues ...interface{}) {
var (
args = make([]string, 0)
outMsg = msg
)

for _, v := range keyValues {
if s, ok := v.(string); ok {
outMsg += " [%s]"
args = append(args, s)
}
}

outMsg += "\n"
if len(keyValues) > 0 {
fmt.Fprintf(os.Stderr, outMsg, args)
} else {
fmt.Fprint(os.Stderr, outMsg)
}
}

// LogFilterLevel filters by checking its print level
// In case the consumer defines its own filters functions via SetLoggerCbs, this can also be passed
func LogFilterLevel(libbpfPrintLevel int, output string) bool {
return libbpfPrintLevel != LogWarnLevel
}

// LogFilterOutput filters out some errors by using regex
// In case the consumer defines its own filters functions via SetLoggerCbs, this can also be passed
func LogFilterOutput(libbpfPrintLevel int, output string) bool {
var (
matched bool
err error
)

// BUG: https:/github.com/aquasecurity/tracee/issues/1676

// triggered by: libbpf/src/nlattr.c->libbpf_nla_dump_errormsg()
// "libbpf: Kernel error message: %s\n"
// 1. %s = "Exclusivity flag on"

matched, err = regexp.MatchString(`libbpf:.*Kernel error message:.*Exclusivity flag on`, output)
if err != nil {
log(LogDebugLevel, "Filtering libbpf print output", "error", err)
} else if matched {
return true
}

// BUG: https://github.com/aquasecurity/tracee/issues/2446

// triggered by: libbpf/src/libbpf.c->bpf_program__attach_kprobe_opts()
// "libbpf: prog '%s': failed to create %s '%s+0x%zx' perf event: %s\n"
// 1. %s = trace_check_map_func_compatibility
// 2. %s = kretprobe or kprobe
// 3. %s = check_map_func_compatibility (function name)
// 4. %x = offset (ignored in this check)
// 5. %s = No such file or directory

matched, err = regexp.MatchString(`libbpf:.*prog 'trace_check_map_func_compatibility'.*failed to create kprobe.*perf event: No such file or directory`, output)
if err != nil {
log(LogDebugLevel, "Filtering libbpf print output", "error", err)
} else if matched {
return true
}

// AttachCgroupLegacy() will first try AttachCgroup() and it might fail. This
// is not an error and is the best way of probing for eBPF cgroup attachment
// link existence.

// triggered by: libbpf/src/libbpf.c->bpf_program__attach_fd()
// "libbpf: prog '%s': failed to attach to %s: %s\n"
// 1. %s = cgroup_skb_ingress or cgroup_skb_egress
// 2. %s = cgroup
// 3. %s = Invalid argument

matched, err = regexp.MatchString(`libbpf:.*prog 'cgroup_skb_ingress|cgroup_skb_egress'.*failed to attach to cgroup.*Invalid argument`, output)
if err != nil {
log(LogDebugLevel, "Filtering libbpf print output", "error", err)
} else if matched {
return true
}

return false
}

0 comments on commit 024fa7d

Please sign in to comment.