Skip to content

Commit f3c6cbf

Browse files
authored
[tc-bpf] add support for tc-bpf program types (dropbox#51)
1 parent d54e462 commit f3c6cbf

File tree

7 files changed

+259
-1
lines changed

7 files changed

+259
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ A nice and convenient way to work with `eBPF` programs / perf events from Go.
1414
- `SocketFilter`
1515
- `XDP`
1616
- `Kprobe` / `Kretprobe`
17+
- `tc-cls` / `tc-act`
1718
- Perf Events
1819

1920
Support for other program types / features can be added in future.

bpf_helpers.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,30 @@ struct bpf_map_def {
8181
#define BPF_MAP_OFFSET_PERSISTENT offsetof(struct bpf_map_def, persistent_path)
8282
#define BPF_MAP_OFFSET_INNER_MAP offsetof(struct bpf_map_def, inner_map_def)
8383

84+
/* Generic BPF return codes which all BPF program types may support.
85+
* The values are binary compatible with their TC_ACT_* counter-part to
86+
* provide backwards compatibility with existing SCHED_CLS and SCHED_ACT
87+
* programs.
88+
*
89+
* XDP is handled seprately, see XDP_*.
90+
*/
91+
enum bpf_ret_code {
92+
BPF_OK = 0,
93+
/* 1 reserved */
94+
BPF_DROP = 2,
95+
/* 3-6 reserved */
96+
BPF_REDIRECT = 7,
97+
/* >127 are reserved for prog type specific return codes.
98+
*
99+
* BPF_LWT_REROUTE: used by BPF_PROG_TYPE_LWT_IN and
100+
* BPF_PROG_TYPE_LWT_XMIT to indicate that skb had been
101+
* changed and should be routed based on its new L3 header.
102+
* (This is an L3 redirect, as opposed to L2 redirect
103+
* represented by BPF_REDIRECT above).
104+
*/
105+
BPF_LWT_REROUTE = 128,
106+
};
107+
84108
// XDP related constants
85109
enum xdp_action {
86110
XDP_ABORTED = 0,
@@ -458,6 +482,9 @@ static int (*bpf_perf_event_output)(void *ctx, void *map, __u64 index, void *dat
458482
static int (*bpf_skb_load_bytes)(void *ctx, int offset, void *to, __u32 len) = (void*) // NOLINT
459483
BPF_FUNC_skb_load_bytes;
460484

485+
static int (*bpf_skb_store_bytes)(void *ctx, int offset, const void *from, __u32 len, __u64 flags) = (void *) // NOLINT
486+
BPF_FUNC_skb_store_bytes;
487+
461488
static int (*bpf_perf_event_read_value)(void *map, __u64 flags, void *buf, __u32 buf_size) = (void*) // NOLINT
462489
BPF_FUNC_perf_event_read_value;
463490

itest/Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ EBPF_XDP_SOURCE := ebpf_prog/xdp1.c
1414
EBPF_XDP_BINARY := ebpf_prog/xdp1.elf
1515
EBPF_KPROBE_SOURCE := ebpf_prog/kprobe1.c
1616
EBPF_KPROBE_BINARY := ebpf_prog/kprobe1.elf
17+
EBPF_TC_SOURCE := ebpf_prog/tc1.c
18+
EBPF_TC_BINARY := ebpf_prog/tc1.elf
1719

1820
EUID := $(shell id -u -r)
1921

@@ -25,11 +27,14 @@ $(EBPF_XDP_BINARY): $(EBPF_XDP_SOURCE)
2527
$(EBPF_KPROBE_BINARY): $(EBPF_KPROBE_SOURCE)
2628
clang -I.. -O2 -target bpf -c $^ -o $@
2729

30+
$(EBPF_TC_BINARY): $(EBPF_TC_SOURCE)
31+
clang -I.. -O2 -target bpf -c $^ -o $@
32+
2833
$(TEST_BINARY): $(TEST_SOURCE)
2934
$(GOTEST) -c -v -o $@
3035

3136
build_test: $(TEST_BINARY)
32-
build_bpf: $(EBPF_XDP_BINARY) $(EBPF_KPROBE_BINARY)
37+
build_bpf: $(EBPF_XDP_BINARY) $(EBPF_KPROBE_BINARY) $(EBPF_TC_BINARY)
3338

3439
check_root:
3540
ifneq ($(EUID),0)
@@ -42,6 +47,7 @@ clean:
4247
rm -f $(TEST_BINARY)
4348
rm -f $(EBPF_XDP_BINARY)
4449
rm -f $(EBPF_KPROBE_BINARY)
50+
rm -f $(EBPF_TC_BINARY)
4551

4652
test: check_root build_bpf build_test
4753
@ulimit -l unlimited

itest/ebpf_prog/tc1.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) 2019 Dropbox, Inc.
2+
// Full license can be found in the LICENSE file.
3+
4+
// Set of simple TC programs for eBPF library integration tests
5+
6+
#include "bpf_helpers.h"
7+
8+
9+
SEC("tc_cls")
10+
int tc1(struct __sk_buff *skb)
11+
{
12+
return BPF_OK;
13+
}
14+
15+
SEC("tc_act")
16+
int tc2(struct __sk_buff *skb)
17+
{
18+
return BPF_DROP;
19+
}
20+
21+
SEC("tc_act")
22+
int tc3(struct __sk_buff *skb)
23+
{
24+
return bpf_redirect(1, 0);
25+
}
26+
27+
char _license[] SEC("license") = "GPL";

itest/tc_test.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright (c) 2019 Dropbox, Inc.
2+
// Full license can be found in the LICENSE file.
3+
4+
package itest
5+
6+
import (
7+
"os"
8+
"testing"
9+
"time"
10+
11+
"github.com/dropbox/goebpf"
12+
"github.com/stretchr/testify/suite"
13+
)
14+
15+
const (
16+
tcProgramFilename = "ebpf_prog/tc1.elf"
17+
)
18+
19+
type tcTestSuite struct {
20+
suite.Suite
21+
programFilename string
22+
programsCount int
23+
}
24+
25+
// Basic sanity test of BPF core functionality like
26+
// ReadElf, create maps, load / attach programs
27+
func (ts *tcTestSuite) TestElfLoad() {
28+
// This compile ELF file contains 2 BPF(TC type) programs
29+
eb := goebpf.NewDefaultEbpfSystem()
30+
err := eb.LoadElf(ts.programFilename)
31+
ts.NoError(err)
32+
if err != nil {
33+
// ELF read error.
34+
ts.FailNowf("Unable to read %s", ts.programFilename)
35+
}
36+
37+
// There should be 0 BPF maps recognized by loader
38+
maps := eb.GetMaps()
39+
ts.Require().Equal(0, len(maps))
40+
41+
// Non existing map
42+
ts.Nil(eb.GetMapByName("something"))
43+
44+
// Also there should few TC eBPF programs recognized
45+
ts.Require().Equal(ts.programsCount, len(eb.GetPrograms()))
46+
47+
// Check loaded programs and try to pin them
48+
tc1 := eb.GetProgramByName("tc1")
49+
tc1.Load()
50+
path := bpfPath + "/tc1_pin_test"
51+
err = tc1.Pin(path)
52+
ts.NoError(err)
53+
ts.FileExists(path)
54+
os.Remove(path)
55+
ts.Equal(goebpf.ProgramTypeSchedCls, tc1.GetType())
56+
57+
// Check loaded programs and try to pin them
58+
tc2 := eb.GetProgramByName("tc2")
59+
tc2.Load()
60+
path = bpfPath + "/tc2_pin_test"
61+
err = tc2.Pin(path)
62+
ts.NoError(err)
63+
ts.FileExists(path)
64+
os.Remove(path)
65+
ts.Equal(goebpf.ProgramTypeSchedAct, tc2.GetType())
66+
67+
// Check loaded programs and try to pin them
68+
tc3 := eb.GetProgramByName("tc3")
69+
tc3.Load()
70+
path = bpfPath + "/tc3_pin_test"
71+
err = tc3.Pin(path)
72+
ts.NoError(err)
73+
ts.FileExists(path)
74+
os.Remove(path)
75+
ts.Equal(goebpf.ProgramTypeSchedAct, tc3.GetType())
76+
77+
// Non existing program
78+
ts.Nil(eb.GetProgramByName("something"))
79+
80+
//Run attach and detach, they shouldn't fail as these methods are not implemented for TC
81+
err = tc1.Attach(0)
82+
ts.Error(err)
83+
err = tc1.Detach()
84+
ts.Error(err)
85+
err = tc2.Attach(0)
86+
ts.Error(err)
87+
err = tc2.Detach()
88+
ts.Error(err)
89+
err = tc3.Attach(0)
90+
ts.Error(err)
91+
err = tc3.Detach()
92+
ts.Error(err)
93+
94+
// Unload programs (not required for real use case)
95+
for _, program := range eb.GetPrograms() {
96+
err = program.Close()
97+
ts.NoError(err)
98+
}
99+
100+
// Negative: close already closed program
101+
err = tc1.Close()
102+
ts.Error(err)
103+
104+
}
105+
106+
func (ts *tcTestSuite) TestProgramInfo() {
107+
// Load test program, don't attach (not required to get info)
108+
eb := goebpf.NewDefaultEbpfSystem()
109+
err := eb.LoadElf(ts.programFilename)
110+
ts.Require().NoError(err)
111+
prog := eb.GetProgramByName("tc1")
112+
err = prog.Load()
113+
ts.Require().NoError(err)
114+
115+
// Get program info by FD (NOT ID, since this program is ours)
116+
info, err := goebpf.GetProgramInfoByFd(prog.GetFd())
117+
ts.NoError(err)
118+
119+
// Check base info
120+
ts.Equal(prog.GetName(), info.Name)
121+
ts.Equal(prog.GetFd(), info.Fd)
122+
ts.Equal(goebpf.ProgramTypeSchedCls, info.Type)
123+
// Check loaded time
124+
now := time.Now()
125+
ts.True(now.Sub(info.LoadTime) < time.Second*10)
126+
127+
}
128+
129+
// Run suite
130+
func TestTcSuite(t *testing.T) {
131+
suite.Run(t, &tcTestSuite{
132+
programFilename: tcProgramFilename,
133+
programsCount: 3,
134+
})
135+
}

loader.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ var sectionNameToProgramType = map[string]programCreator{
3838
"socket_filter": newSocketFilterProgram,
3939
"kprobe": newKprobeProgram,
4040
"kretprobe": newKretprobeProgram,
41+
"tc_cls": newTcSchedClsProgram,
42+
"tc_act": newTcSchedActProgram,
4143
}
4244

4345
// BPF instruction //

program_tc.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) 2019 Dropbox, Inc.
2+
// Full license can be found in the LICENSE file.
3+
4+
package goebpf
5+
6+
import (
7+
"fmt"
8+
)
9+
10+
// TC eBPF program (implements Program interface)
11+
type tcProgram struct {
12+
BaseProgram
13+
progType TcProgramType
14+
}
15+
16+
// TcProgramType selects a way how TC program will be attached
17+
// it can either be BPF_PROG_TYPE_SCHED_CLS or BPF_PROG_TYPE_SCHED_ACT
18+
type TcProgramType int
19+
20+
const (
21+
// TcProgramTypeCls is the `tc filter program type
22+
TcProgramTypeCls TcProgramType = iota
23+
// TcProgramTypeAct is the `tc action`program type
24+
TcProgramTypeAct
25+
)
26+
27+
func (t TcProgramType) String() string {
28+
switch t {
29+
case TcProgramTypeCls:
30+
return "tc_cls"
31+
case TcProgramTypeAct:
32+
return "tc_act"
33+
default:
34+
return "tc_unknown"
35+
}
36+
}
37+
38+
func newTcSchedClsProgram(bp BaseProgram) Program {
39+
bp.programType = ProgramTypeSchedCls
40+
return &tcProgram{
41+
BaseProgram: bp,
42+
progType: TcProgramTypeCls,
43+
}
44+
}
45+
46+
func newTcSchedActProgram(bp BaseProgram) Program {
47+
bp.programType = ProgramTypeSchedAct
48+
return &tcProgram{
49+
BaseProgram: bp,
50+
progType: TcProgramTypeAct,
51+
}
52+
}
53+
54+
func (p *tcProgram) Attach(data interface{}) error {
55+
return fmt.Errorf("Attach() not implemented for program type %s", p.BaseProgram.GetType())
56+
}
57+
58+
func (p *tcProgram) Detach() error {
59+
return fmt.Errorf("Detach() not implemented for program type %s", p.BaseProgram.GetType())
60+
}

0 commit comments

Comments
 (0)