├── .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;
--------------------------------------------------------------------------------