bumblebeeは、起動していくつかの質問に答えると、目的に合致したeBPFのソースコードのテンプレートを生成してくれます。そのテンプレートを基にすることで、比較的容易にeBPFのアプリケーションを開発し、Bumblebeeでビルドすることが可能です。
目次
インストール
bumblebeeをUbuntu20.04環境にインストールします。installには、installスクリプトをダウンロードして実行するか、git repositoryをcloneしgoスクリプトを実行するかの2通りがあります。ここではinstallスクリプトを使用します。ホームディレクトリの.bumblebee以下にディレクトリができるので、パスと特権を設定します。
takayuki@ubuntu2:~$ curl -sL https://run.solo.io/bee/install | sh
Attempting to download bee version v0.0.14
Downloading bee-linux-amd64...
Download complete!, validating checksum...
Checksum valid.
bee was successfully installed
takayuki@ubuntu2:~$ export PATH=$HOME/.bumblebee/bin:$PATH
takayuki@ubuntu2:~$ sudo setcap cap_sys_resource,cap_sys_admin+eip $(which bee)
ビルド、パッケージ化、実行
サンプルプグラムをビルド・パッケージ化して、実行します。
ビルド時にはdockerを使うので、事前インストールが必要です。
ビルドはbee build、パッケージ化はbee package、実行はbee runを使用します。
takayuki@ubuntu2:~/repos$ git clone https://github.com/solo-io/bumblebee.git
takayuki@ubuntu2:~/repos/bumblebee$ bee build examples/tcpconnect/tcpconnect.c tcpconnect
takayuki@ubuntu2:~/repos/bumblebee$ bee package tcpconnect bee-tcpconnect:latest
SUCCESS Packaged image built and tagged at bee-tcpconnect:latest
takayuki@ubuntu2:~/repos/bumblebee$ bee run tcpconnect
テンプレート作成
bumblebeeは、対話形式でテンプレートを自動生成する機能があります。言語(C)、Prgoram Type (Network, FileSystem)、Map Type(RingBuffer, HashMap)、出力方法(print, counter, gauge)を指定することでeBPFプログラムのひな形が自動生成することができます。
takayuki@ubuntu2:~/repos/bumblebee$ bee init
INFO Selected Language: C
INFO Selected Program Type: Network
INFO Selected Map Type: RingBuffer
INFO Selected Output Type: print
INFO Selected Output Type: BPF Program File Location /tmp/test.c
SUCCESS Successfully wrote skeleton BPF program
#include "vmlinux.h"
#include "bpf/bpf_helpers.h"
#include "bpf/bpf_core_read.h"
#include "bpf/bpf_tracing.h"
#include "solo_types.h"
// 1. Change the license if necessary
char __license[] SEC("license") = "Dual MIT/GPL";
struct event_t {
// 2. Add ringbuf struct data here.
} __attribute__((packed));
// This is the definition for the global map which both our
// bpf program and user space program can access.
// More info and map types can be found here: https://www.man7.org/linux/man-pages/man2/bpf.2.html
struct {
__uint(max_entries, 1 << 24);
__uint(type, BPF_MAP_TYPE_RINGBUF);
__type(value, struct event_t);
} events SEC(".maps.print");
SEC("kprobe/tcp_v4_connect")
int BPF_KPROBE(tcp_v4_connect, struct sock *sk)
{
// Init event pointer
struct event_t *event;
// Reserve a spot in the ringbuffer for our event
event = bpf_ringbuf_reserve(&events, sizeof(struct event_t), 0);
if (!event) {
return 0;
}
// 3. set data for our event,
// For example:
// event->pid = bpf_get_current_pid_tgid();
bpf_ringbuf_submit(event, 0);
return 0;
}
生成されるコードはNetworkの場合tcp_v4_connectへのHook、FileSystemのopenatシステムコールへのHookと決まっており、Hook箇所は自由に選ぶことはできません。Hookしたあとの処理は自製する必要があり、あくまでひな形作成のための機能になっています。
実装
自動生成処理
自動性制処理の本体はinitialize/init.goのfunc initialize関数です。initializeはGoのtext/templateパッケージを内部的に使用して自動生成を行います。text/templateパッケージはテンプレートを用いた自動コード生成を行うパッケージで、Mustacheに似た機能を提供します。text/templateパッケージについてはGo text/template で文字列作成 – Qiitaが参考になります。
initialize関数はユーザの指定内容に基づきテンプレート構成(templateData)を作成し、text/templateを用いてコードを自動生成します。例えばFileSystemが指定された場合には以下のようなtemplateDataを作成します。
import (
(...)
"text/template"
)
(...)
type templateData struct {
StructData string
MapData MapData
FunctionBody string
RenderedMap string
}
(...)
func initialize(opts *InitOptions) error {
(...)
} else if programType == fileSystem {
if opts.MapType != "" || opts.OutputType != "" {
return errors.New("initializing a file system type program with custom map types or output type is not currently supported. Please remove overrides for map type and output type to continue")
}
mapTemplate = &templateData{
StructData: openAtStruct,
FunctionBody: openAtBody,
RenderedMap: openAtMap,
}
} } else {
return fmt.Errorf("%s is not a valid program type", programType)
}
tmpl := template.Must(template.New("c-file-template").Parse(fileTemplate))
fileBuf := &bytes.Buffer{}
if err := tmpl.Execute(fileBuf, mapTemplate); err != nil {
return err
} else {
return fmt.Errorf("%s is not a valid program type", programType)
}
tmpl := template.Must(template.New("c-file-template").Parse(fileTemplate))
templateDataのStructData、FunctionBody、RenreedMapにはinitialize/template.goに定義したconst文字列を使用します。例えばopenAtStructは次のようなconst変数を使用します。
const openAtStruct = `// This struct represents the data we will gather from the tracepoint to send to our ring buffer map
// The 'bee' runner will watch for entries to our ring buffer and print them out for us
struct event {
// In this example, we have a single field, the filename being opened
char fname[255];
};`
また、text/templateのテンプレートは、同じくinitialize/template.goに定義したfileTemplateを使用します。
const fileTemplate = `#include "vmlinux.h"
#include "bpf/bpf_helpers.h"
#include "bpf/bpf_core_read.h"
#include "bpf/bpf_tracing.h"
#include "solo_types.h"
// 1. Change the license if necessary
char __license[] SEC("license") = "Dual MIT/GPL";
{{ .StructData }}
// This is the definition for the global map which both our
// bpf program and user space program can access.
// More info and map types can be found here: https://www.man7.org/linux/man-pages/man2/bpf.2.html
{{ .RenderedMap }}
{{ .FunctionBody }}
`