パケットフィルターでトレーシング? Linuxで活用が進む「Berkeley Packet Filter(BPF)」とは何か:Berkeley Packet Filter(BPF)入門(1)(1/3 ページ)
Linuxにおける利用が急速に増えている「Berkeley Packet Filter(BPF)」について、基礎から応用まで幅広く紹介する連載。初回は、BPFの歴史や概要について。
Berkeley Packet Filter(BPF)とは
読者の皆さんは「Berkeley Packet Filter(BPF)」というものをご存じでしょうか?
Berkeley Packet Filter(BPF)は、独自の命令セットを持つ仮想マシンの一つ。ざっくりと言ってしまえばBPFはユーザーランドからのコードをカーネル内で安全に実行するための枠組みです。
「Packet Filter」という名称を含んでいる通り、BPFは当初パケットフィルタリング専用に設計されたものですが、近年のLinuxでは独自の進化を遂げ、汎用カーネル内仮想マシンとしての利用が進んでいます。パケットフィルタリング以外にも、トレーシングやセキュリティなどの用途で利用されています。
今のLinuxでは、さまざまなフックポイント(イベント)に対して、BPFのプログラムをアタッチする仕組みが整備されています。ユーザーは目的のBPFプログラムを作成することで、カーネルの再コンパイルなしにそのイベントに対するカーネルの動作を変更したり、ログを記録したりすることが可能です。
具体的には、以下のような場面でBPFが利用可能になっています。
- ネットワーキング
- パケットフィルタリング
- パケットフォワーディング
- トンネリング
- トラフィック制御
- トレーシング
- カーネルトレーシング
- ユーザープログラムトレーシング
- パフォーマンスカウンタモニタリング
- セキュリティ
- システムコールフィルタリング
これ以外にも、さまざまな用途でBPFの応用が進んでいます。
もともとLinuxはカーネルモジュールをサポートしており、C言語によってLinuxの一部機能を拡張することが可能です。しかし、カーネルモジュールの表現力が高過ぎる故にその安全性を一般に保証することは困難です。BPFでは、一部機能を制限し、BPFプログラムで許可されていない動作を行わないことを、事前に検証器が検証することで安全なプログラム実行を実現します。
BPFがカーネルモジュールと比べ制限されているといっても、BPF自体は汎用的な命令セットを持ち、それと合わせてカーネル内のヘルパー関数呼び出しを利用することでさまざまな処理が実行可能です。またBPFは、効率的な実行を考慮して設計されており、JITコンパイルもサポートされています。
BPFを利用したプロジェクト
BPFを利用した近年のプロジェクトには、以下のようなものがあります。
- BCC(BPF Compiler Collection)
BPFを利用してトレーシングやフィルタリングを行うためのツール群。本連載でも紹介予定 - bpftrace、ply
dtraceに似た言語を利用するダイナミックトレーサー - XDP(eXpress Data Path)
Linuxカーネル内で、ネットワークスタック処理前にパケットのフィルタリングや転送を実現する機構 - Katran
Facebookが開発しているXDPを活用したL4ロードバランサー。 - Cilium
コンテナ間のルーティングやロードバランシングなどを透過的にBPF(XDP)を用いて実現するプロジェクト。L7(HTTP)レベルのポリシーが定義可能 - Landlock
BPFによるプログラマブルなLSM(Linux Security Module)。LinuxKitプロジェクトの一つ - bpfilter
BPFによるiptableのようなファイアウォール実装(Linux 4.18時点で開発中)
本連載について
このように、LinuxにおけるBPFの利用は急速に増えており、またBPFを利用したツールの開発も活発です。一方、依然としてBPFに関するドキュメント、特に内部動作に関するものは少ないように思います。
本連載ではそんなBPFについて、基礎から応用まで幅広く紹介していきます。以下のような内容を取り扱う予定です。
- BPFの仕組み、内部構造
- 実践的なBPFプログラミング手法
- BPFの応用事例
- BPFの今後の方向性
第1回となる今回は、BPFの歴史や概要について簡単に説明します。
簡単な歴史
BPFの歴史は1990年代初頭にまでさかのぼります。BPFは効率的なパケットフィルタリング手法の一つとして登場しました(オリジナルのBPFに関する論文)。本来のBPFはパケットをキャプチャーする機構および、仮想マシンによるパケットフィルタリング機構を併せた名称ですが、後者のパケットフィルタリング手法のみを指すこともよくあり、ここでもBPFはパケットフィルタリング機構のことを意味します。
特定のパケット(例えば、特定の送信アドレスを持つパケット)のみをアプリケーション側でキャプチャーしたい場合、単純にはNICが受信したパケットを全てキャプチャープログラムへ渡し、そこでフィルタリングする方法があります。しかし、この方法ではカーネル空間、ユーザー空間で多量のシステムコールおよびデータコピーが発生し、良いパフォーマンスを得ることができません。
そこでカーネル内部でパケットフィルタリングを実施すれば、データコピーやカーネル/ユーザー空間切り替えに伴うオーバーヘッドを削減できます。しかし、カーネル内でパケットフィルタリングを実施する場合、ユーザーアプリケーションが要求するパケットフィルタリングのルールは無数にある可能性があり、どうフィルタリングを実施するかが問題となります。
BPFでは、フィルタリング用の仮想的なレジスタマシンを持ち、その仮想マシン用のプログラム(BPFプログラム)を実行することでパケットフィルタリングを行います。
仮想マシンは十分汎用的かつ効率的なフィルタリングが行えるように設計されており、ユーザーアプリケーションはBPFプログラムを変更することで、フィルタリングルールを変更できます。
さらにBPFプログラムの安全性を保証するために、実行前には検証器がプログラムを静的に検査します。例えば、検証器はBPFプログラムがプログラム外へのジャンプを実行しないことや、BPFプログラムのサイズが閾値以下(=一定時間以内の終了)であることをチェックします。
BPFは当初、BSD(Berkeley Software Distribution)に実装されましたが、数年後にLinuxにも移植され、カーネル内パケットフィルタリング機構として利用されてきました。LinuxにおいてBPFがパケットフィルタリング以外に利用されるようになった最初の例は、Linux 3.4(2012年)に導入されたseccomp(mode 2)です。
seccompはプロセスのサンドボックス化を実現するために、プロセスが発行するシステムコールを制限する手法です。seccompではBPFの安全かつ効率的にユーザーが作成したプログラムを実行できる点に着目し、システムコールのフィルタリングにBPFと同じ機構を採用しました。
さらに、Linux 3.14(2014年)から、BPFをより汎用的なカーネル内仮想マシンとして利用するため、従来のBPFを大きく拡張する変更が入り、現在に至るまで活発に開発が継続されています。
今のLinuxのBPFは、パケットフィルタリングのみならずさまざまな箇所でカーネル内の操作をフックしてプログラマブルにするための手法として用いられています。この拡張されたBPFは「eBPF(extended BPF)」と呼ばれます。それに対して、従来のBPFは「cBPF(classic BPF)」と呼ばれることがあります。現在Linuxにおいて一般にBPFと言うと多くの場合eBPFを指します。
本連載でもこれ以降、基本的にBPFはeBPFのことを意味し、classic BPFはcBPFと記載します。
eBPFでは命令セットが一新され、使用可能なレジスタ数も増えた他、ヘルパー関数呼び出しの機能が追加されたことで、BPFプログラムが行える処理が大幅に増えました。
ヘルパー関数経由で可能な操作の一つに外部のデータ構造(eBPF map)へのアクセスがあります。これにより、BPFとユーザープログラム間でデータのやりとりができるようになりました。
またeBPFのプログラム作成をサポートするために、LLVM 3.7からバックエンドにeBPFが追加されました。これにより、Clangを利用してC言語からeBPFへコンパイルすることが可能となり、eBPFで複雑なプログラムが作成しやすくなりました。
カーネル内でBPFのプログラムがアタッチできる箇所は続々と増えており、まだまだBPFの利用が拡大していくと予想されます。BPFを利用したツール群の開発も活発に行われています。
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- 2018年上半期に話題になったSpectreとその変異、Linuxカーネルでの対応まとめ
連載「OSS脆弱性ウォッチ」では、さまざまなオープンソースソフトウェアの脆弱性に関する情報を取り上げ、解説していく。2018年の上半期は、「Meltdown」「Spectre」とその変異(Variant)の脆弱性に悩まされた。今回はいつもとは異なり、上半期のまとめも兼ねて、Meltdown/Spectreの各変異をバージョンを追いかけながら整理する。 - 2017年版Linuxカーネル開発レポート公開――支援している企業トップ10とは?
The Linux Foundationは2017年版Linuxカーネル開発レポートを公開した。Linuxカーネル4.8から4.13までの開発に焦点を当て、カーネル開発に携わった開発者や変更数などについて言及した。 - Linuxカーネルのソースコードを読んで、システムコールを探る
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。前回まで、printf()内の中身をさまざまな方法で探り、write()やint $0x80の呼び出しまでたどり着いた。今回は、さらにその先にあるLinuxカーネル側のシステムコールを見ていく。