├── .gitignore ├── Makefile ├── README.md ├── catchcats └── main.go ├── countpackets └── main.go ├── go.mod ├── go.sum ├── helloworld └── main.go ├── ministrace.c ├── ministrace └── main.go └── pkts.c /.gitignore: -------------------------------------------------------------------------------- 1 | elf/ 2 | vendor/ 3 | bin/ 4 | 5 | *.o -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: bin/countpackets bin/helloworld 2 | 3 | bin/countpackets: countpackets/main.go pkts.o 4 | @mkdir -p bin 5 | @unset GOFLAGS 6 | go build -o $@ $< 7 | 8 | bin/helloworld: helloworld/main.go vendor 9 | @mkdir -p bin 10 | @# use modules in vendor to get the iovisor/gobpf#202 fix 11 | GOFLAGS=-mod=vendor go build -o $@ $< 12 | 13 | # bin/catchcats: catchcats/main.go vendor 14 | # @mkdir -p bin 15 | # @# use modules in vendor to get the iovisor/gobpf#202 fix 16 | # GOFLAGS=-mod=vendor go build -o $@ $< 17 | 18 | setup: elf/include/bpf_helpers.h elf/include/bpf_map.h 19 | 20 | elf/include/bpf_helpers.h: elf/include 21 | @curl -s -o $@ -LO https://git.archlinux.org/linux.git/plain/tools/testing/selftests/bpf/bpf_helpers.h?h=v5.0-arch1 22 | @sed -i '/\/\* a helper/{:a;N;/\};/!ba};/struct bpf_map_def/d' elf/include/bpf_helpers.h 23 | 24 | elf/include/bpf_map.h: elf/include 25 | @curl -s -o $@ -LO https://raw.githubusercontent.com/iovisor/gobpf/master/elf/include/bpf_map.h 26 | 27 | elf/include: 28 | @mkdir -p $@ 29 | 30 | %.o: %.c elf/include/bpf_helpers.h elf/include/bpf_map.h 31 | clang -O2 -target bpf -c $< -o $@ 32 | 33 | .PHONY: vendor 34 | vendor: 35 | go mod vendor 36 | @# fix iovisor/gobpf#202 (not in pair with a BCC's bpf_module_create_c_from_string signature) 37 | @# remove following as soon https://github.com/iovisor/gobpf/pull/210 is merged in 38 | @sed -i 's/\(bpf_module_create_c_from_string.*\))/\1, nil)/' vendor/github.com/iovisor/gobpf/bcc/module.go 39 | 40 | clean: 41 | @rm -rf elf 42 | @rm -rf bin 43 | @rm -rf vendor 44 | @rm -rf *.o -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go eBPF examples 2 | 3 | > Experimenting eBPF with Go 4 | 5 | ## Prerequisites 6 | 7 | These examples have been created on an Archlinux box with kernel v5.0. 8 | 9 | The main prerequisites are: 10 | 11 | - linux headers 12 | - clang 13 | - bcc ([how to install](https://github.com/iovisor/bcc/blob/master/INSTALL.md)) 14 | 15 | ## Working examples 16 | 17 | - helloworld 18 | - `make bin/helloworld` 19 | - kprobe(clone) + trace_pipe + iovisor/gobpf/bcc (helloworld/main.go) 20 | 21 | - countpackets 22 | - `make bin/countpackets` 23 | - socket filter + map (pkts.c) + iovisor/gobpf/elf (countpackets/main.go) 24 | 25 | - catchcats 26 | 27 | - ministrace -------------------------------------------------------------------------------- /catchcats/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "os" 8 | "os/signal" 9 | "strconv" 10 | 11 | bpf "github.com/iovisor/gobpf/bcc" 12 | ) 13 | 14 | const source string = ` 15 | #include 16 | 17 | #include "elf/include/bpf_helpers.h" 18 | 19 | typedef struct { 20 | u32 pid; 21 | u32 uid; 22 | u32 tid; 23 | char str[80]; 24 | } userspace_event_t; 25 | 26 | BPF_PERF_OUTPUT(userspace_events); 27 | 28 | int get_return_value(struct pt_regs *ctx) { 29 | if (!PT_REGS_RC(ctx)) 30 | return 0; 31 | 32 | u64 id = bpf_get_current_pid_tgid(); 33 | u32 uid = bpf_get_current_uid_gid(); 34 | userspace_event_t evt = { 35 | .pid = id >> 32, 36 | .tid = id, 37 | .uid = uid, 38 | }; 39 | 40 | int ret = bpf_probe_read(&evt.str, sizeof(evt.str), (void *)PT_REGS_RC(ctx)); 41 | // https://github.com/iovisor/bcc/issues/2534 42 | bpf_trace_printk("bpf_probe_read() return: %d\\n", ret); 43 | bpf_trace_printk("bpf_probe_read() result: %s\\n", evt.str); 44 | 45 | userspace_events.perf_submit(ctx, &evt, sizeof(evt)); 46 | return 0; 47 | } 48 | ` 49 | 50 | type userspaceEvent struct { 51 | Pid uint32 52 | Uid uint32 53 | Tid uint32 54 | Str [80]byte 55 | } 56 | 57 | func main() { 58 | if len(os.Args) < 1 { 59 | fmt.Fprintf(os.Stderr, "missing PID") 60 | os.Exit(1) 61 | } 62 | m := bpf.NewModule(source, []string{}) 63 | defer m.Close() 64 | 65 | uretprobe, err := m.LoadUprobe("get_return_value") 66 | if err != nil { 67 | fmt.Fprintf(os.Stderr, "failed to load get_return_value: %s\n", err) 68 | os.Exit(1) 69 | } 70 | 71 | pid, err := strconv.Atoi(os.Args[1]) 72 | if err != nil { 73 | fmt.Fprintf(os.Stderr, "malformed PID: %s\n", os.Args[1]) 74 | os.Exit(1) 75 | } 76 | 77 | // proc/pid/exe 78 | err = m.AttachUretprobe("/caturday", "main.counterValue", uretprobe, pid) 79 | if err != nil { 80 | fmt.Fprintf(os.Stderr, "failed to attach get_return_value: %s\n", err) 81 | os.Exit(1) 82 | } 83 | 84 | table := bpf.NewTable(m.TableId("userspace_events"), m) 85 | 86 | channel := make(chan []byte) 87 | 88 | perfMap, err := bpf.InitPerfMap(table, channel) 89 | if err != nil { 90 | fmt.Fprintf(os.Stderr, "failed to init perf map: %s\n", err) 91 | os.Exit(1) 92 | } 93 | 94 | sig := make(chan os.Signal, 1) 95 | signal.Notify(sig, os.Interrupt, os.Kill) 96 | 97 | fmt.Printf("%10s\t%10s\t%10s\t%s\n", "PID", "UID", "TID", "COUNT") 98 | go func() { 99 | var event userspaceEvent 100 | for { 101 | data := <-channel 102 | err := binary.Read(bytes.NewBuffer(data), binary.LittleEndian, &event) 103 | 104 | if err != nil { 105 | fmt.Printf("failed to decode received data: %s\n", err) 106 | continue 107 | } 108 | comm := string(event.Str[:bytes.IndexByte(event.Str[:], 0)]) 109 | // fmt.Println(event.Str[:]) 110 | fmt.Printf("%10d\t%10d\t%10d\t%s\n", event.Pid, event.Uid, event.Tid, comm) 111 | } 112 | }() 113 | 114 | perfMap.Start() 115 | <-sig 116 | perfMap.Stop() 117 | } 118 | -------------------------------------------------------------------------------- /countpackets/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "syscall" 8 | "time" 9 | "unsafe" 10 | 11 | bpflib "github.com/iovisor/gobpf/elf" 12 | "github.com/skydive-project/skydive/common" 13 | "github.com/vishvananda/netlink" 14 | ) 15 | 16 | const howlong = 10 * time.Second 17 | const interval = 1 * time.Second 18 | 19 | const eBPFFileName = "pkts.o" 20 | 21 | func getBPF() (string, error) { 22 | dir, err := os.Getwd() 23 | if err != nil { 24 | return "", fmt.Errorf("unable to find %q eBPF file", eBPFFileName) 25 | } 26 | return path.Join(dir, eBPFFileName), nil 27 | } 28 | 29 | func main() { 30 | bpf, err := getBPF() 31 | if err != nil { 32 | fmt.Fprintln(os.Stderr, err) 33 | os.Exit(1) 34 | } 35 | 36 | m := bpflib.NewModule(bpf) 37 | if err := m.Load(nil); err != nil { 38 | fmt.Fprintf(os.Stderr, "error loading %q: %s\n", bpf, err) 39 | os.Exit(1) 40 | } 41 | 42 | countpacketsMap := m.Map("packets") 43 | if countpacketsMap == nil { 44 | fmt.Fprintln(os.Stderr, "unable to find `packets` eBPF map") 45 | os.Exit(1) 46 | } 47 | 48 | countpacketsProgram := m.SocketFilter("socket/countpackets") 49 | if countpacketsProgram == nil { 50 | fmt.Fprintln(os.Stderr, "unable to find `countpackets` socket filter eBPF program") 51 | os.Exit(1) 52 | } 53 | 54 | links, err := netlink.LinkList() 55 | if err != nil { 56 | fmt.Fprintln(os.Stderr, err) 57 | os.Exit(1) 58 | } 59 | for _, link := range links { 60 | // open a socket in the network namespace for interface 61 | rs, err := common.NewRawSocketInNs("/proc/1/ns/net", link.Attrs().Name, syscall.ETH_P_ALL) 62 | if err != nil { 63 | fmt.Fprintln(os.Stderr, err) 64 | os.Exit(1) 65 | } 66 | if err := bpflib.AttachSocketFilter(countpacketsProgram, rs.GetFd()); err != nil { 67 | fmt.Fprintln(os.Stderr, "unable to attach `countpackets` socket filter eBPF program") 68 | os.Exit(1) 69 | } 70 | } 71 | 72 | tick := time.NewTicker(interval) 73 | quit := make(chan struct{}) 74 | 75 | go func() { 76 | for { 77 | select { 78 | case <-tick.C: 79 | poll(m, countpacketsMap) 80 | case <-quit: 81 | fmt.Fprintln(os.Stdout, "quit") 82 | return 83 | } 84 | } 85 | }() 86 | 87 | time.Sleep(howlong) 88 | tick.Stop() 89 | close(quit) 90 | time.Sleep(time.Millisecond * 500) 91 | } 92 | 93 | func poll(bpf *bpflib.Module, m *bpflib.Map) { 94 | var err error 95 | var k, v, n uint64 96 | still := true 97 | for still { 98 | still, err = bpf.LookupNextElement(m, unsafe.Pointer(&k), unsafe.Pointer(&n), unsafe.Pointer(&v)) 99 | if err != nil { 100 | fmt.Fprintln(os.Stderr, err) 101 | break 102 | } 103 | 104 | fmt.Println(k, v) 105 | k = n 106 | } 107 | fmt.Println("-----") 108 | } 109 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/leodido/go-ebpf-examples 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/fatih/structs v1.1.0 // indirect 7 | github.com/iovisor/gobpf v0.0.0-20191017091429-c3024dcc6881 8 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect 9 | github.com/safchain/insanelock v0.0.0-20180509135444-33bca4586648 // indirect 10 | github.com/skydive-project/skydive v0.25.0 11 | github.com/vishvananda/netlink v1.0.0 12 | github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect 13 | golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= 2 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 3 | github.com/iovisor/gobpf v0.0.0-20191017091429-c3024dcc6881 h1:DRdqUzrTOOIlh9Fzmf59XZBLoTrUdiSN3Z0ThUkVfqM= 4 | github.com/iovisor/gobpf v0.0.0-20191017091429-c3024dcc6881/go.mod h1:+5U5qu5UOu8YJ5oHVLvWKH7/Dr5QNHU7mZ2RfPEeXg8= 5 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= 6 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= 7 | github.com/safchain/insanelock v0.0.0-20180509135444-33bca4586648 h1:qwumKB12zaV9SrtbrgWiMJW4Gpsu4VrcKCZhsLMTvwI= 8 | github.com/safchain/insanelock v0.0.0-20180509135444-33bca4586648/go.mod h1:HDoS2Pz2kUgD2zcmyJkXmhGH+R1dc8P8aeo2Gsf6dss= 9 | github.com/skydive-project/skydive v0.25.0 h1:pPt9Mhi31Ni8apfGG9L485dgDL57eR4gk3zyaWqcywo= 10 | github.com/skydive-project/skydive v0.25.0/go.mod h1:20E2myY4JHGJSg+96sqOfmMVCsDX2wHgzCNUdyQTKxg= 11 | github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM= 12 | github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= 13 | github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I= 14 | github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= 15 | golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= 16 | golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 17 | -------------------------------------------------------------------------------- /helloworld/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | bpflib "github.com/iovisor/gobpf/bcc" 8 | "github.com/iovisor/gobpf/pkg/tracepipe" 9 | ) 10 | 11 | import "C" 12 | 13 | const source string = ` 14 | #include "elf/include/bpf_helpers.h" 15 | 16 | int trace_clone(void *ctx) 17 | { 18 | u64 id = bpf_get_current_pid_tgid(); 19 | u32 uid = bpf_get_current_uid_gid(); 20 | u32 pid = id >> 32; // PID is the higher part 21 | u32 tid = id; // Cast to get the lower part 22 | bpf_trace_printk("pid<%d> uid<%d> tid<%d> hello clone\\n", pid, uid, tid); 23 | return 0; 24 | } 25 | ` 26 | 27 | func main() { 28 | cflags := []string{} 29 | m := bpflib.NewModule(source, cflags) 30 | defer m.Close() 31 | 32 | syscallName := bpflib.GetSyscallFnName("clone") 33 | fmt.Printf("Say hello at each %q syscall ...\n", syscallName) 34 | 35 | pipe, err := tracepipe.New() 36 | if err != nil { 37 | fmt.Fprintf(os.Stderr, "unable to get trace pipe: %s\n", err) 38 | os.Exit(1) 39 | } 40 | defer pipe.Close() 41 | 42 | kprobeFd, err := m.LoadKprobe("trace_clone") 43 | if err != nil { 44 | fmt.Fprintf(os.Stderr, "unable to load kprobe: %s\n", err) 45 | os.Exit(1) 46 | } 47 | 48 | // -1 -> use the default according to the kernel kprobe documentation 49 | if err := m.AttachKprobe(syscallName, kprobeFd, -1); err != nil { 50 | fmt.Fprintf(os.Stderr, "unable to attach kprobe: %s\n", err) 51 | os.Exit(1) 52 | } 53 | 54 | evtCh, errCh := pipe.Channel() 55 | for { 56 | select { 57 | case evt := <-evtCh: 58 | fmt.Println(evt) 59 | case err := <-errCh: 60 | fmt.Println(err) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ministrace.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "elf/include/bpf_helpers.h" 4 | #include "elf/include/bpf_map.h" 5 | 6 | struct bpf_map_def SEC("maps/syscalls") syscallsmap = { 7 | .type = BPF_MAP_TYPE_QUEUE, 8 | .key_size = 0, 9 | .value_size = sizeof(int), 10 | .max_entries = 32, 11 | }; 12 | 13 | SEC("tracepoint/raw_syscalls/sys_enter") 14 | int tracepoint__raw_syscalls__sys_enter() 15 | { 16 | int two = 2; 17 | // bpf_map_update_elem(&syscallsmap, 0, &two, 0); 18 | bpf_map_push_elem(&syscallsmap, &two, 0); 19 | return 0; 20 | } 21 | 22 | char _license[] SEC("license") = "GPL"; 23 | 24 | unsigned int _version SEC("version") = 0xFFFFFFFE; -------------------------------------------------------------------------------- /ministrace/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | 8 | bpflib "github.com/iovisor/gobpf/elf" 9 | ) 10 | 11 | const eBPFFileName = "ministrace.o" 12 | 13 | func getBPF() (string, error) { 14 | dir, err := os.Getwd() 15 | if err != nil { 16 | return "", fmt.Errorf("unable to find %q eBPF file", eBPFFileName) 17 | } 18 | return path.Join(dir, eBPFFileName), nil 19 | } 20 | 21 | func main() { 22 | bpf, err := getBPF() 23 | if err != nil { 24 | fmt.Fprintln(os.Stderr, err) 25 | os.Exit(1) 26 | } 27 | 28 | m := bpflib.NewModule(bpf) 29 | if err := m.Load(nil); err != nil { 30 | fmt.Fprintf(os.Stderr, "error loading %q: %s\n", bpf, err) 31 | os.Exit(1) 32 | } 33 | 34 | syscallsMap := m.Map("syscalls") 35 | if syscallsMap == nil { 36 | fmt.Fprintln(os.Stderr, "unable to find `syscalls` eBPF map") 37 | os.Exit(1) 38 | } 39 | fmt.Println(syscallsMap) 40 | } 41 | -------------------------------------------------------------------------------- /pkts.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "elf/include/bpf_helpers.h" 7 | #include "elf/include/bpf_map.h" 8 | 9 | #ifndef offsetof 10 | #define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER) 11 | #endif 12 | 13 | // map containing pairs 14 | // https://en.wikipedia.org/wiki/List_of_IP_protocol_numbers 15 | struct bpf_map_def SEC("maps/packets") countmap = { 16 | .type = BPF_MAP_TYPE_HASH, 17 | .key_size = sizeof(int), 18 | .value_size = sizeof(int), 19 | .max_entries = 256, 20 | }; 21 | 22 | SEC("socket/countpackets") 23 | int socket_prog(struct __sk_buff *skb) 24 | { 25 | int proto = load_byte(skb, ETH_HLEN + offsetof(struct iphdr, protocol)); 26 | int one = 1; 27 | int *el = bpf_map_lookup_elem(&countmap, &proto); 28 | if (el) 29 | { 30 | (*el)++; 31 | } 32 | else 33 | { 34 | el = &one; 35 | } 36 | bpf_map_update_elem(&countmap, &proto, el, BPF_ANY); 37 | return 0; 38 | } 39 | 40 | char _license[] SEC("license") = "GPL"; 41 | 42 | // this tells to the ELF loader to set the current running kernel version 43 | unsigned int _version SEC("version") = 0xFFFFFFFE; --------------------------------------------------------------------------------