#seccon ワークショップ@福岡でeBPF/XDPベースのファイアウォールとロードバランサーに入門してきた

2023.11.12

クラウドネイティブ界隈でよく耳にするCNI(Container Network Interface)・L4ロードバランサーのCilium、そしてランタイムセキュリティツールのFalcoでは、eBPF(Extended Berkeley Packet Filter)およびeBPFを活用したXDP(eXpress Data Path)の技術が非常に重要な役割を果たしています。

みんな大好き、『詳解 システム・パフォーマンス』"BPF Performance Tools" の著者である現IntelのBrendan Gregg ニキは、eBPFとLinuxカーネルの関係はJavaScriptとウェブサイトの関係と同じだと発言しています

近年、重要度が高まっているeBPF/XDPをワークショップ形式で学べるSECCONのイベントが地元福岡の九州大学で開催されましたので、レポートします。

ワークショップで利用したスライド、及び、サンプルコードは公開されているため、ワークショップに参加できなかった方も自分のペースで進めることが可能です。

ワークショップ概要

ワークショップ案内

「XDP で作って学ぶファイアウォールとロードバランサー」ワークショップ@福岡

概要

現在、ロードバランサーやファイアウォールはサービスを提供する上で欠かせないネットワークコンポーネントとなっており、ファイアウォールは機密性や堅牢性、ロードバランサーは可用性に非常に重要な役割を果たしています。ファイアウォールやロードバランサーは使ったことのある方は多いかもしれませんが、それらが実際にどのようにパケットを処理しているのかを知っている方はあまり多くないのではないでしょうか。普段なにげなく利用しているソフトウェアの仕組みを知ることは利用する上でもかなり役に立ちます。

また、XDP とは、近年流行している eBPF の一種で、高速なパケット処理に特化した機能を提供します。eBPF とは Linux カーネルの機能のひとつで、Linux カーネルの挙動を安全かつ動的に変更することのできる機能です。XDPはすでに様々なプロダクションで活用されていて非常にホットな技術です。本ワークショップでは XDP を利用して実際に簡単 なファイアウォールやロードバランサーを実装して動作させることで、それらの仕組みや、XDP によるパケット処理プログラムの書き方を学びます。本ワークショップを通して、パケット処理の面白さや普段利用しているネットワークコンポーネントの仕組みに興味を持って貰えれば幸いです。

講師

寺嶋友哉 (サイボウズ株式会社)

日時

2023年10月29日(日) 13:00 ~ 17:20(途中休憩あり)

会場

〒819-0395 福岡県福岡市西区元岡744 九州大学 伊都キャンパス 情報基盤研究開発センター 2階 

ワークショップ検証環境

ワークショップでは、参加者ごとにUbuntuのAmazon EC2 t2.microインスタンスが割り当てられました。

ワークショップ後、個人用AWSアカウントのUbuntu 22.04/t2.microでも動作することを確認しています。

XDP で作って学ぶファイアウォールとロードバランサー

それでは、当日のワークショップについて簡単にレポートします。

eBPF/XDP の概要

BPFはもともとBSD系OS向けのパケットフィルターとして開発され、1993年のUSENIXでも発表されているほどの歴史があります。

The BSD packet filter | Proceedings of the USENIX Winter 1993 Conference Proceedings on USENIX Winter 1993 Conference Proceedings

その後、Linuxに移植され、拡張されてより汎用的になったのが、eBPF(Extended Berkeley Packet Filter)です。eBPFは2014年リリースのカーネル4.4から導入されています。 BPF技術を利用すると、Linuxカーネルを変更したりカーネルモジュールをロードすることなく、安全かつ効率的にカーネルの機能を拡張できます。

eBPF以前の旧BPFはclassic BPF(cBPF)と呼ばれており、tcpdumpのフィルタールールなどでも利用されています。

※ 引用元 https://ebpf.io/what-is-ebpf/

XDP(eXpress Data Path)はeBPFプログラムをネットワークドライバ(NIC)などにアタッチして動作し、高速なパケット処理を実現します。

eBPF/XDPは以下のプロダクトなどで使われています。

  • Meta社のKatranCloudflare社のUnimogのようなL4ロードバランサー
  • CiliumのようなKubernetes等をターゲットとしたCloud Native Networking(CNI)
  • Falcoのようなランタイムセキュリティツール

hello worldプログラムでeBPFのお作法を学ぶ

パケットを受信するたびに hello from xdp! を出力するだけのCで書かれたeBPFプログラムが次のものです。

hello.bpf.c

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

SEC("xdp")
int hello(struct xdp_md *ctx) {
	bpf_printk("hello from xdp!");
	return XDP_PASS;
}

char LICENSE[] SEC("license") = "Dual BSD/GPL";

eBPFはカーネルスペースでサンドボックス化された仮想マシン上で実行されます。具体的な流れは以下の通りです。

  1. C言語でeBPFプログラムを記述
  2. ClangやGCCでeBPFバイトコードにコンパイル
  3. bpfシステムコールでカーネル空間にロード
  4. Verifierが安全性を検証
  5. アーキテクチャに合わせてネイティブコードにJITコンパイル
  6. フックポイントで実行

安全性を検証する重要な機構がeBPF Verifierです。 DAG(Directed Acyclic Graph)を作成し、到達不可能な命令がないかなどをチェックします。

それでは、実際にeBPFをターゲット(-target bpf)にしてコンパイルし、カーネル空間にロードしてみましょう。

$ clang \
  -I./include \
  -target bpf \
  -Wall \
  -O2 \
  -g \
  -o hello.bpf.o -c hello.bpf.c

$ sudo bpftool prog load hello.bpf.o /sys/fs/bpf/hello

次にロードしたプログラムのIDを bpftool で確認し、デバイス(NIC)にアタッチします。

$ sudo bpftool prog list
3: cgroup_device  tag e3dbd137be8d6168  gpl
        loaded_at 2023-11-11T11:11:38+0000  uid 0
        xlated 504B  jited 310B  memlock 4096B
        pids systemd(1)
...
30: xdp  name hello  tag 744c65b05702d8c0  gpl
        loaded_at 2023-11-11T03:25:30+0000  uid 0
        xlated 96B  jited 71B  memlock 4096B  map_ids 3
        btf_id 40

$ sudo ip netns exec host0 bpftool net attach xdp id 30 dev h0

$ sudo ip netns exec host0 ip link show dev h0
3: h0@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 xdp qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether b6:94:06:0f:86:db brd ff:ff:ff:ff:ff:ff link-netns host1
    prog/xdp id 30 tag 744c65b05702d8c0 jited

手順は省略しますがが、本ワークショップでは、Network Namespaceを使ってテスト用ネットワークを作成します。

eBPFのライセンスに注意

eBPF プログラムのライセンスにも注意が必要です。BSD系OS経由のclassic BPF(cBPF)はBSDライセンスですが、extend された eBPFプログラムのライセンスはLinuxカーネルライセンスのGPLv2と互換性が必要です。

試しに、eBPFプログラムのライセンス行をコメントアウトしてビルドし、先程と同じ手順でデバイスにアタッチしてみましょう。

$ sudo bpftool prog load hello.bpf.o /sys/fs/bpf/hello
libbpf: prog 'hello': BPF program load failed: Invalid argument
libbpf: prog 'hello': -- BEGIN PROG LOAD LOG --
0: R1=ctx(off=0,imm=0) R10=fp0
; int hello(struct xdp_md *ctx) {
0: (18) r1 = 0x21706478206d6f         ; R1_w=9412251045883247
; bpf_printk("hello from xdp!");
2: (7b) *(u64 *)(r10 -8) = r1         ; R1_w=9412251045883247 R10=fp0 fp-8_w=9412251045883247
3: (18) r1 = 0x7266206f6c6c6568       ; R1_w=8243311830880773480
5: (7b) *(u64 *)(r10 -16) = r1        ; R1_w=8243311830880773480 R10=fp0 fp-16_w=8243311830880773480
6: (bf) r1 = r10                      ; R1_w=fp0 R10=fp0
;
7: (07) r1 += -16                     ; R1_w=fp-16
; bpf_printk("hello from xdp!");
8: (b7) r2 = 16                       ; R2_w=16
9: (85) call bpf_trace_printk#6
cannot call GPL-restricted function from non-GPL compatible program
processed 8 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
-- END PROG LOAD LOG --
libbpf: prog 'hello': failed to load: -22
libbpf: failed to load object 'hello.bpf.o'
Error: failed to load object file

コンパイルは成功しますが、ロード時に cannot call GPL-restricted function from non-GPL compatible program というライセンス起因のエラーが発生しました。

Verifierはライセンス情報もチェックしていることがわかります。

Verifierのエラーログはわかりにくいので、めげないようにしましょう。

より複雑なコードを拡張していく

Step 1のHello Worldプログラムは最初から完成形が提示されていますが、後続のパケットカウンタやロードバランシングなど機能では、ボイラープレートを元に参加者がメインロジックをコンパイラ・ Verifierに怒られながら実装していきます。

main ブランチで目指す動作を確認したあと、ワークショップ用の handson ブランチに切り替えてマイペースに作業を進めることも可能です。

ワークショップ用スライドとGitHubのサンプルコードレポジトリを参考にしてください。

講義のベースになっているスライドは約170ページもあリ、4時間のワークショップでは一部をとばしても100ページ程度まで進みました。

すべてをこなす場合、8時間程度は覚悟しましょう。

eBPF本の待望の邦訳が2023年12月に出版!

2023年4月に、Ciliumの開発を主導するIsovalent社のchief open source officerをつとめるLiz Rice"Learning eBPF"を出版しました。

本書の邦訳である『入門 eBPF Linuxカーネルの可視化と機能拡張』は、同年12月に刊行されます。

翻訳はLinux低レイヤーに強い武内 覚さんと近藤 宇智朗さんという強力なペアによって行われています。年末・年始の必読書と言えるでしょう。

最後に

普段はパブリッククラウドのマネージドサービスにおんぶにだっこなゆるふわ勢にとって、今回のワークショップはクラウドの裏側で動いているであろう仕組みをパケットの気持ちになって体感するいい機会となりました。

講師のテラシマさん、タジマさん、誘ってくれた同僚のべこみんに感謝します。