-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathmagefile-bpf.go
212 lines (165 loc) · 4.98 KB
/
magefile-bpf.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// +build mage
package main
import (
"fmt"
"os"
"os/exec"
"path"
"strings"
"golang.org/x/sync/errgroup"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
"github.com/magefile/mage/target"
"github.com/ti-mo/conntracct/pkg/kernel"
)
const (
bpfBuildPath = "build/bpf/"
bpfAcctBuildPath = bpfBuildPath + "acct/"
bpfAcctProbe = "bpf/acct.c"
)
// Bpf is the namespace for all BPF-related build tasks.
type Bpf mg.Namespace
// Clean removes the BPF build directory and kernel configurations.
func (Bpf) Clean() error {
fmt.Println("Removing directory", bpfBuildPath, "..")
if err := os.RemoveAll(bpfBuildPath); err != nil {
return err
}
fmt.Println("Removing kernel configurations ..")
for _, k := range kernel.Builds {
p := path.Join(k.Directory(), ".config")
if mg.Verbose() {
fmt.Println("Removing", p, "..")
}
if err := os.RemoveAll(p); err != nil {
return err
}
}
return nil
}
// Build builds all BPF programs against all defined kernels.
func (Bpf) Build() error {
// Download and extract all kernels first.
mg.Deps(Bpf.Kernels)
// Basic check for build dependencies to avoid ugly errors.
buildTools := []string{"clang", "llc", "statik"}
for _, t := range buildTools {
if _, err := exec.LookPath(t); err != nil {
return fmt.Errorf("conntracct needs the following tools to build the eBPF probe: %s. %s",
strings.Join(buildTools, ", "), err)
}
}
// Create build target directory.
if err := os.MkdirAll(bpfAcctBuildPath, os.ModePerm); err != nil {
return err
}
fmt.Println("Building eBPF programs ..")
// Build the acct probe against all Kernels defined in the kernel package.
for _, k := range kernel.Builds {
// Name of the resulting BPF object file.
bpfObjectName := fmt.Sprintf("%s.o", k.Version)
// Target path for the compiled BPF object.
bpfObjectPath := path.Join(bpfAcctBuildPath, bpfObjectName)
// Check if the acct probe source is newer than the probe's object in the build directory.
run, err := target.Path(bpfObjectPath, bpfAcctProbe)
if err != nil {
return err
}
// Skip this build if the object is newer than the source.
if !run {
fmt.Println("Acct probe is up-to-date:", bpfObjectPath)
continue
}
if err := buildProbe(bpfAcctProbe, bpfObjectPath, k.Directory()); err != nil {
fmt.Println("Failed to build probe against kernel", k.Version)
return err
}
fmt.Println("Built acct probe", bpfObjectName)
}
// Bundle the BPF objects into the binary using statik.
// Provide empty -c argument so statik doesn't write a package description.
if err := sh.Run("statik", "-f", "-c", "", "-src", bpfBuildPath, "-dest", "pkg/", "-p", "bpf"); err != nil {
return err
}
return nil
}
// Kernels downloads and extracts a list of kernels to a temporary directory.
func (Bpf) Kernels() error {
fmt.Println("Fetching and configuring kernels ..")
var eg errgroup.Group
for _, k := range kernel.Builds {
// https://golang.org/doc/faq#closures_and_goroutines
k := k
eg.Go(func() error {
// Get and unarchive the kernel.
if err := k.Fetch(); err != nil {
return err
}
// Configure the kernel with its specified parameters.
if err := k.Configure(nil); err != nil {
return err
}
return nil
})
}
if err := eg.Wait(); err != nil {
return err
}
return nil
}
// buildProbe builds a BPF program given its source file, destination object file
// and directory of the kernel source tree the program is to be built against.
func buildProbe(srcFile, dstObj, kernelDir string) error {
clangParams := []string{
"-D__KERNEL__", "-D__BPF_TRACING__",
"-D__TARGET_ARCH_x86",
"-fno-stack-protector",
"-Wno-pointer-sign",
"-Wno-gnu-variable-sized-type-not-at-end",
"-Wno-address-of-packed-member",
"-Wunused", "-Wall", "-Werror",
"-O2", "-emit-llvm", "-ferror-limit=1",
"-S",
"-c", srcFile,
"-o", "-",
}
// Specify all include dirs to prevent clang from falling back to includes
// on the machine in /usr/include during cross-compilation.
kdirs := []string{
"-I%s/include",
"-I%s/include/uapi",
"-I%s/arch/x86/include",
"-I%s/arch/x86/include/uapi",
"-I%s/arch/x86/include/generated",
"-I%s/arch/x86/include/generated/uapi",
}
// Resolve kernel directories in all include paths and append to clang params.
for _, d := range kdirs {
clangParams = append(clangParams, fmt.Sprintf(d, kernelDir))
}
llcParams := []string{
"-march=bpf",
"-filetype=obj",
"-o", dstObj,
}
clang := exec.Command("clang", clangParams...)
llc := exec.Command("llc", llcParams...)
// Redirect stderr of the builds to the terminal's stderr.
clang.Stderr = os.Stderr
llc.Stderr = os.Stderr
llc.Stdin, _ = clang.StdoutPipe()
llc.Stdout = os.Stdout
if err := llc.Start(); err != nil {
return err
}
// Run clang and wait for it to finish.
if err := clang.Run(); err != nil {
fmt.Println("Error running clang with args:", clang.Args)
return err
}
if err := llc.Wait(); err != nil {
fmt.Println("Error running llc with args:", llc.Args)
return err
}
return nil
}