Linuxにおける利用が急速に増えている「Berkeley Packet Filter(BPF)」について、基礎から応用まで幅広く紹介する連載。初回は、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を利用した近年のプロジェクトには、以下のようなものがあります。
このように、Linuxにおける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.