eBPFは、ユーザが定義したプログラムをカーネルランドで実行するための仕組みです。計測、セキュリティ、ネットワーク処理などの利用用途があります。
BPF Compiler Collection (BCC)という、eBPFプログラムの作成や実行を簡易的に行うためのツールセットがあります。
カーネルランドで動かすBPFプログラムはCで書く必要がありますが、BPFプログラムの読み込みや出力のフォーマットなどはPython(もしくはlua)のバインディングを利用して書くことができます。
サンプルプログラム
ループバックインターフェイスに対するICMPパケットのみをドロップするプログラムを書いてみました。
BPFプログラムは次のとおりです。不正なメモリアクセスなどが発生しないように、データアクセスの前にそのデータ長を事前チェックするなど、堅牢なプログラムにしておかないとverifierで弾かれるので注意。
prog.c
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/ip.h>
int prog(struct xdp_md *ctx) {
int ipsize = 0;
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
struct iphdr *ip;
ipsize = sizeof(*eth);
ip = data + ipsize;
ipsize = sizeof(struct iphdr);
if (data + ipsize > data_end) {
return XDP_PASS;
}
if ((void *)&ip[1] > data_end) {
return XDP_PASS;
}
if (ip->protocol == IPPROTO_ICMP) {
bpf_trace_printk("drop!\\n");
return XDP_DROP;
}
return XDP_PASS;
}
ユーザランドで実行するPythonプログラムは次のとおりです。
example.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from bcc import BPF
def main():
b = BPF(src_file='program.c')
fn = b.load_func('prog', BPF.XDP)
device = 'lo'
b.attach_xdp(device, fn, 0)
try:
b.trace_print()
finally:
b.remove_xdp(device, 0)
if __name__ == '__main__':
main()