Header file not found in XDP source compiled in Go program - c

My XDP module compiles by itself, but compilation within the context of a Go program fails because it can't find bpf/bpf_helpers.h file. Here's the code leading up the problem:
package main
import (
"fmt"
bpf "github.com/iovisor/gobpf/bcc"
log "github.com/sirupsen/logrus"
"io/ioutil"
"os"
)
/*
#cgo CFLAGS: -I/usr/include/bcc/compat
#cgo LDFLAGS: -lbcc
#include <bcc/bcc_common.h>
#include <bcc/libbpf.h>
void perf_reader_free(void *ptr);
*/
import "C"
func main() {
// Get the source code from disk
source, err := ioutil.ReadFile("xdp/collect_ips.c")
if err != nil {
log.Fatalln("Cannot read collect_ips.c")
}
// Compile module
module := bpf.NewModule(string(source), []string{
"-Wall",
"-O2",
})
defer module.Close()
// Load module
fn, err := module.Load("collect_ips", C.BPF_PROG_TYPE_XDP, 1, 65536) // Problem happens here
// ...
The Go program compiles fine, but I when I run the program, I get this:
/virtual/main.c:2:10: fatal error: 'bpf/bpf_helpers.h' file not found
#include <bpf/bpf_helpers.h>
^~~~~~~~~~~~~~~~~~~
9 warnings and 1 error generated.
panic: runtime error: invalid memory address or nil pointer dereference
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4b23d4]
goroutine 1 [running]:
github.com/iovisor/gobpf/bcc.(*Module).Close.func1(0x203000)
/home/me/go/pkg/mod/github.com/iovisor/gobpf#v0.2.0/bcc/module.go:132 +0x14
github.com/iovisor/gobpf/bcc.(*Module).Close(0x0)
/home/me/go/pkg/mod/github.com/iovisor/gobpf#v0.2.0/bcc/module.go:132 +0x36
panic({0x4da720, 0x59ec60})
/usr/lib/go-1.17/src/runtime/panic.go:1038 +0x215
github.com/iovisor/gobpf/bcc.(*Module).Load(0x0, {0x4ef9f2, 0xb}, 0x2, 0x2, 0x8001)
/home/me/go/pkg/mod/github.com/iovisor/gobpf#v0.2.0/bcc/module.go:202 +0x36
main.main()
/home/me/go/src/code.squarespace.net/net/prism-stream/cmd/server/main.go:35 +0x1b7
This problem is happening because of my XDP module because if I comment out this header file in the C source, the error moves to a different header file.
I think this is happening because of bpf_helpers.h does not exist here https://github.com/iovisor/gobpf/tree/master/elf/include. If this is the issue, is there a way to use the header file from /usr/include/bpf?
If I take out bpf_helpers.h from the XDP code, I get an error complaining about the use of SEC in my code:
struct bpf_map_def SEC("maps") addr_map = {
.type = BPF_MAP_TYPE_LRU_HASH,
.key_size = sizeof(struct addr_desc_struct),
.value_size = sizeof(long),
.max_entries = 4096
};
I copied the macro for SEC from bpf_helpers.h to my code, but I then get error: variable has incomplete type 'struct bpf_map_def'. I also use bpf_map_lookup_elem() and bpf_map_update_elem(), which are defined in the bpf/ directory.

I used the eBPF library from Cilium. Here's an example program that loads .o file to an interface, waits for 10 seconds, and prints whatever is in the BPF map.
You have to make sure your XDP function has SEC("xdp") before it. Whereas if you load the program using xdp-loader, you can pass whatever you want to SEC as long as it's different from the name of the function.
package main
import (
"bytes"
"fmt"
"github.com/cilium/ebpf"
log "github.com/sirupsen/logrus"
"github.com/vishvananda/netlink"
"time"
)
func main() {
var objects struct {
XCProg *ebpf.Program `ebpf:"collect_ips"` // Name of function containing the XDP program
XCMap *ebpf.Map `ebpf:"addr_map"` // Name of the map
}
spec, err := ebpf.LoadCollectionSpec("dist/collect_ips.o")
if err != nil {
log.Fatalln("ebpf.LoadCollectionSpec", err)
}
if err := spec.LoadAndAssign(&objects, nil); err != nil {
log.Fatalln("ebpf.LoadAndAssign", err)
}
link, err := netlink.LinkByName("enp0s8")
if err != nil {
log.Fatalln("netlink.LinkByName", err)
}
err = netlink.LinkSetXdpFdWithFlags(link, objects.XCProg.FD(), 2)
if err != nil {
log.Fatalln("netlink.LinkSetXdpFdWithFlags")
}
time.Sleep(time.Second*10)
var keyBytes, valueBytes []byte
iterator := objects.XCMap.Iterate()
if iterator == nil {
log.Fatalln("nil iterator")
}
for iterator.Next(&keyBytes, &valueBytes) != false {
fmt.Printf("%x: %x", keyBytes, valueBytes)
}
defer objects.XCProg.Close()
defer objects.XCMap.Close()
}

Related

Golang, CGO, double pointers and macos Foundation framework

First, I have some basic C experience, but not too fluent with CGO and macos programming
I write a function, that removes a file in a system Trash. I have a working code, that I try to improve
The code first:
package trash
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation
#import "trash.h"
*/
import "C"
import (
"errors"
"unsafe"
)
// TrashItem puts items into system trash
func TrashItem(filePath string) error {
cString := C.CString(filePath)
defer C.free(unsafe.Pointer(cString))
var cErr = new(C.Error)
res := C.TrashItem(cString, cErr)
if res == 0 {
// convert cErr to go error
return errors.New("...")
}
return nil
}
#import <Foundation/Foundation.h>
typedef struct _Error {
long C;
const char *E;
} Error;
const BOOL TrashItem(const char *filePath, Error *error);
(I googled a lot to write this code, not sure, how good it is)
#import "trash.h"
const BOOL TrashItem(const char *filePath, Error *error) {
NSFileManager *manager = [NSFileManager defaultManager];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:#(filePath)];
NSError *e;
BOOL success = [manager trashItemAtURL:fileURL resultingItemURL:nil error:&e];
if (!success) {
error->C = e.code;
error->E = [e.localizedDescription UTF8String];
}
return success;
}
First issue:
I don't wont to always initialize var cErr = new(C.Error). I think I could make trash function accept Error **error, but I struggle to pass double pointer from go to C (I have go pointer have go pointer error)
Second issue:
I would like to avoid custom Error struct and pass NSError * and access fields from go side.
I had Incompatible pointer types passing 'struct NSError *' to parameter of type 'NSError *, but https://stackoverflow.com/a/60715306/889530 solved it for me
But I think I still need NSError **, so I can read the data back, and again I struggle with void and double pointers here.
Can somebody explain me how to do that properly

Pointer from C is removed from memory

In this situation, I pass a pointer from Go to a C function, the C function modifies that pointer value (fills in an array) and I use the same pointer again on the Go code, making sure to call C.free to release it after I am done.
I am sometimes getting a nil reference on that pointer, I am having a hard time understanding why.
Here, the object in Go is cpuTimesC, in C it is the cputicks.
I have also tried making the function return the pointer, with the same results. The weirdest thing is that if I put a printf statement at the end of the function, it takes longer before I eventually get the nil error.
package collector
/*
#cgo LDFLAGS: -lperfstat
#include <stdlib.h>
#include <stdio.h>
#include <libperfstat.h>
#include <string.h>
#include <time.h>
u_longlong_t **ref;
int getCPUTicks(uint64_t **cputicks, size_t *cpu_ticks_len) {
int i, ncpus, cputotal;
perfstat_id_t firstcpu;
perfstat_cpu_t *statp;
cputotal = perfstat_cpu(NULL, NULL, sizeof(perfstat_cpu_t), 0);
if (cputotal <= 0){
return -1;
}
statp = calloc(cputotal, sizeof(perfstat_cpu_t));
if(statp==NULL){
return -1;
}
ncpus = perfstat_cpu(&firstcpu, statp, sizeof(perfstat_cpu_t), cputotal);
*cpu_ticks_len = ncpus*4;
*cputicks = (uint64_t *) malloc(sizeof(uint64_t)*(*cpu_ticks_len));
for (i = 0; i < ncpus; i++) {
int offset = 4 * i;
(*cputicks)[offset] = statp[i].user;
(*cputicks)[offset+1] = statp[i].sys;
(*cputicks)[offset+2] = statp[i].wait;
(*cputicks)[offset+3] = statp[i].idle;
}
return 0;
}
*/
import "C"
import (
"errors"
"unsafe"
"fmt"
"github.com/prometheus/client_golang/prometheus"
)
const ClocksPerSec = float64(C.CLK_TCK)
const maxCPUTimesLen = 1024 * 4
type statCollector struct {
cpu *prometheus.Desc
}
func init() {
registerCollector("cpu", true, NewCPUCollector)
}
func NewCPUCollector() (Collector, error) {
return &statCollector{
cpu: nodeCPUSecondsDesc,
}, nil
}
func (c *statCollector) Update(ch chan<- prometheus.Metric) error {
var fieldsCount = 4
cpuFields := []string{"user", "sys", "wait", "idle"}
var (
cpuTimesC *C.uint64_t
cpuTimesLength C.size_t
)
if C.getCPUTicks(&cpuTimesC, &cpuTimesLength) == -1 {
return errors.New("could not retrieve CPU times")
}
defer C.free(unsafe.Pointer(cpuTimesC))
cput := (*[maxCPUTimesLen]C.u_longlong_t)(unsafe.Pointer(cpuTimesC))[:cpuTimesLength:cpuTimesLength]
cpuTicks := make([]float64, cpuTimesLength)
for i, value := range cput {
cpuTicks[i] = float64(value) / ClocksPerSec
}
for i, value := range cpuTicks {
cpux := fmt.Sprintf("CPU %d", i/fieldsCount)
ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, value, cpux, cpuFields[i%fieldsCount])
}
return nil
}
The error is:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x33 addr=0x0 pc=0x1003fcec0]
goroutine 940 [running]:
github.com/dlopes7/aix-prometheus-exporter/collector.(*statCollector).Update(0xa000100000d21b8, 0xa0001000028c480, 0x0, 0x0)
/home/david/go/src/github.com/dlopes7/aix-prometheus-exporter/collector/cpu_aix.go:81 +0xf0
github.com/dlopes7/aix-prometheus-exporter/collector.execute(0x10043e7e7, 0x3, 0x1101120e0, 0xa000100000d21b8, 0xa0001000028c480)
/home/david/go/src/github.com/dlopes7/aix-prometheus-exporter/collector/collector.go:95 +0x6c
github.com/dlopes7/aix-prometheus-exporter/collector.AIXCollector.Collect.func1(0xa0001000028c480, 0xa000100000cd440, 0x10043e7e7, 0x3, 0x1101120e0, 0xa000100000d21b8)
/home/david/go/src/github.com/dlopes7/aix-prometheus-exporter/collector/collector.go:115 +0x4c
created by github.com/dlopes7/aix-prometheus-exporter/collector.AIXCollector.Collect
/home/david/go/src/github.com/dlopes7/aix-prometheus-exporter/collector/collector.go:114 +0xf8
And I know it happens here, because for some reason cpuTimesC is nil:
cput := (*[maxCPUTimesLen]C.u_longlong_t)(unsafe.Pointer(cpuTimesC))[:cpuTimesLength:cpuTimesLength]
Why would this object be nil sometimes, and how do I make it remain in memory until I call that C.free?
This is on AIX PPC64 with cgo if that makes any difference.
This had nothing to do with pointers being removed from memory, the cputicks was NULL because ncpus was -1 so the malloc call returned NULL, ncpu was -1 because the first parameter of the perfstat_cpu call was not initialized properly, adding:
strcpy(firstcpu.name, FIRST_CPU);
Before the call to perfstat_cpu fixed the issue, as suggested by #kostik

Invalid argument (EINVAL) when trying to load a BPF program

I am trying to load a BPF program using the bpf syscall but I am receiving invalid argument (EINVAL) on return. From the man page, the possible reasons for this are:
EINVAL
For BPF_PROG_LOAD, indicates an attempt to load an invalid program.
eBPF programs can be deemed invalid due to unrecognized instructions,
the use of reserved fields, jumps out of range, infinite loops or calls
of unknown functions.
So it seems there is something wrong with my BPF program. My BPF program is as follows:
#include <uapi/linux/bpf.h>
int prog(struct pt_regs *ctx)
{
return 0;
}
Which surely cannot have anything wrong with it.
I am compiling with the Makefile here (I removed most of the code from test_overhead_kprobe_kern.c to give a very simple program for testing).
What could be wrong with my program that caused it to get rejected?
uname -a: Linux ubuntu1710 4.13.0-32-generic #35-Ubuntu SMP Thu Jan 25 09:13:46 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
My full userspace code (in Go) is as follows:
package main
/*
#include <stdio.h>
#include <stdlib.h>
void print(char* s) {
printf("%s\n", s);
}
*/
import "C"
import (
"unsafe"
"golang.org/x/sys/unix"
"github.com/cilium/cilium/pkg/bpf"
)
import (
"fmt"
"io/ioutil"
)
const (
bufferSize = 256
sessionIDHTTPHeader = "X-Session-ID"
defaultServerAddress = "localhost"
defaultPort = 5050
)
const (
BPF_PROG_TYPE_UNSPEC = 0
BPF_PROG_TYPE_SOCKET_FILTER = 1
BPF_PROG_TYPE_KPROBE = 2
BPF_PROG_TYPE_SCHED_CLS = 3
BPF_PROG_TYPE_SCHED_ACT = 4
)
type ttyWrite struct {
Count int32
Buf [bufferSize]byte
SessionID int32
}
func main() {
//for i := 0; i < 6; i++ {
//b, err := ioutil.ReadFile(fmt.Sprintf("bpf/test%d.o", i))
b, err := ioutil.ReadFile("bpf/bpf_tty.o")
if err != nil {
fmt.Print(err)
}
err = loadProgram(BPF_PROG_TYPE_KPROBE, unsafe.Pointer(&b), len(b))
if err != nil {
fmt.Printf("%s\n", err)
}
//}
}
func loadProgram(progType int, insns unsafe.Pointer, insnCnt int) error {
licenseBuf := "GPL"
licenseStr := C.CString(licenseBuf)
defer C.free(unsafe.Pointer(licenseStr))
logStr := C.CString("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
defer C.free(unsafe.Pointer(logStr))
lba := struct {
progType uint32
//pad0 [4]byte
insnCnt uint32
//pad1 [4]byte
insns uint64
license uint64
logLevel uint32
//pad2 [4]byte
logSize uint32
//pad3 [4]byte
logBuf uint64
kernVersion uint32
//pad4 [4]byte
}{
progType: uint32(progType),
insnCnt: uint32(insnCnt),
insns: uint64(uintptr(insns)),
license: uint64(uintptr(unsafe.Pointer(licenseStr))),
logLevel: uint32(1),
logSize: uint32(50),
logBuf: uint64(uintptr(unsafe.Pointer(logStr))),
//logBuf: uint64(uintptr(unsafe.Pointer(bufStr))),
// /usr/src/linux-headers-4.13.0-32-generic/include/generated/uapi/linux/version.h
kernVersion: uint32(265485),
}
ret, _, err := unix.Syscall(
unix.SYS_BPF,
bpf.BPF_PROG_LOAD,
uintptr(unsafe.Pointer(&lba)),
unsafe.Sizeof(lba),
)
//fmt.Printf("%s\n", logBuf)
//cs := C.CString("XXXXXXXXXX")
C.print(logStr)
//fmt.Printf("%c\n", *logStr)
if ret != 0 || err != 0 {
//fmt.Printf("%#v %d\n", logBuf, unsafe.Sizeof(lba))
return fmt.Errorf("Unable to load program: ret: %d: %s", int(ret), err)
}
return nil
}
As Qeole pointed out in your previous question, your userspace Go program needs to extract the BPF instructions (.text section) from the object file. Otherwise, the kernel will try to interpret the binary content as BPF instructions and inevitably fail.

How to cast bytes to struct(c struct) in go?

package main
/*
#define _GNU_SOURCE 1
#include <stdio.h>
#include <stdlib.h>
#include <utmpx.h>
#include <fcntl.h>
#include <unistd.h>
char *path_utmpx = _PATH_UTMPX;
typedef struct utmpx utmpx;
*/
import "C"
import (
"fmt"
"io/ioutil"
)
type Record C.utmpx
func main() {
path := C.GoString(C.path_utmpx)
content, err := ioutil.ReadFile(path)
handleError(err)
var records []Record
// now we have the bytes(content), the struct(Record/C.utmpx)
// how can I cast bytes to struct ?
}
func handleError(err error) {
if err != nil {
panic("bad")
}
}
I'm trying to read content into Record
I have asked a few related questions.
Cannot access c variables in cgo
Can not read utmpx file in go
I have read some articles and posts but still cannot figure out a way to do this.
I think you're going about this the wrong way. If you were wanting to use the C library, you would use the C library to read the file.
Don't use cgo purely to have struct definitions, you should create these in Go yourself. You could then write the appropriate marshal / unmarshal code to read from the raw bytes.
A quick Google shows that someone has already done the work required to convert a look of the relevant C library to Go. See the utmp repository.
A short example of how this could be used is:
package main
import (
"bytes"
"fmt"
"log"
"github.com/ericlagergren/go-gnulib/utmp"
)
func handleError(err error) {
if err != nil {
log.Fatal(err)
}
}
func byteToStr(b []byte) string {
i := bytes.IndexByte(b, 0)
if i == -1 {
i = len(b)
}
return string(b[:i])
}
func main() {
list, err := utmp.ReadUtmp(utmp.UtmpxFile, 0)
handleError(err)
for _, u := range list {
fmt.Println(byteToStr(u.User[:]))
}
}
You can view the GoDoc for the utmp package for more information.

Getting double free or corruption (out) when using free in cgo, golang

I am trying to understand and learn cgo, and as a part of that, I wrote a function that checks for filepermission using C.stat.
import (
"fmt"
"unsafe"
"os"
)
//#include <sys/stat.h>
//#include <stdlib.h>
import "C"
func CheckPerm(filename string) {
statt := C.stat //stat struct from C
path := C.CString(filename)
st := *(*C.struct_stat)(unsafe.Pointer(statt)) //Casting unsafe pointer to C.struct_stat
defer C.free(unsafe.Pointer(path)) //free
defer C.free(unsafe.Pointer(statt))
C.stat(path, &st)
if st.st_mode & C.S_IRGRP > 0 || st.st_mode & C.S_IWGRP > 0 || st.st_mode & C.S_IXGRP > 0 || st.st_mode & C.S_IROTH > 0 || st.st_mode & C.S_IWOTH > 0 || st.st_mode & C.S_IXOTH > 0 {
fmt.Println("file permission too broad, make it non-readable to groups and others.")
os.Exit(1)
}
fmt.Println("File permission looks fine")
}
Upon passing a filepath to the function, it errors out saying
*** Error in `/tmp/Build go-sync.go and run77go': double free or corruption (out): 0x0000000000609480 ***
SIGABRT: abort
PC=0x7fe4302f3267 m=0
signal arrived during cgo execution
goroutine 1 [syscall, locked to thread]:
runtime.cgocall(0x401930, 0xc820097808, 0xc800000000)
/usr/lib/go/src/runtime/cgocall.go:120 +0x11b fp=0xc8200977d8 sp=0xc8200977a8
gosyncmodules._Cfunc_free(0x609480)
==========snip============
Because of the last line pasted here gosyncmodules._Cfunc_free(0x609480), I removed the defer C.free(unsafe.Pointer(statt)) and it works fine now.
Why is it throwing error when I am trying to free the struct that was created, whereas it can be used to free the path variable?
Will a similar scenario cause a memory leak, if not in this case?
From https://golang.org/cmd/cgo/#hdr-Go_references_to_C :
// Go string to C string
// The C string is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CString(string) *C.char
Your
path := C.CString(filename)
makes a copy and you have to free it while statt is a normal Go-memory-managed variable.

Resources