bumblebee: eBPF自動生成ツール

投稿者: | 2023年1月8日

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 }}
`

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です