ネットワークはBPFが最も多く利用されています。ネットワークスタック内のどこでBPFプログラムが実行されるか、簡単に示すと下図のようになります。
以降、具体的に見ていきます。
BPFプログラムを利用して、ソケットに対して下記のような操作が可能です。
「BPF_PROG_TYPE_SOCKET_FILTER」がいわゆる昔から利用されてきたパケットフィルターです。カーネル内のネットワークスタックを通過したソケットのデータに対して、BPFプログラムを利用してフィルタリングすることが可能です。BPFプログラムの戻り値が有効なパケット長を意味します(0ならドロップ)。
「BPF_PROG_TYPE_SOCK_OPS」というプログラムタイプを利用すると、ソケットのコネクション確立やタイムアウトのときにBPFプログラムを呼び出せます。これと、「bpf_setsockopt()」という、「setsockopt()」を実行するヘルパー関数を組み合わせることで、接続状況に応じてソケットの属性を変更できます。
「BPF_PROG_TYPE_SK_SKB」では、「sockmap」という特別なBPFマップを利用してパケットを他のソケットにリダイレクトすることができます。これにより、ポート番号やパケットのデータに基づいたパケットのロードバランシングが行えます。
LinuxのL3ネットワークスタック内には「lightweight tunneling」というトンネリングを実施するための機能があり、これにBPFプログラムを利用することができます。
BPFプログラム内でパケット(skb)のカプセル化や脱カプセル化が行えます。これには「BPF_PROG_TYPE_LWT_IN」「BPF_PROG_TYPE_LWT_OUT」といったBPFプログラムタイプが関連します。また、最近ではIPv6のSegment Routing(SRv6)の実現にも利用されるようになっています(参考)。
Linuxには「tc」と呼ばれる帯域制御機構があります。この処理はL3スタック実行前に実施されます。この部分にもBPFプログラムをアタッチでき、パケットのドロップやリダイレクトをBPFプログラムで制御可能です。「BPF_PROG_TYPE_SCHED_CLS」「BPF_PROG_TYPE_SCHED_ACT」といったプログラムタイプが関連します。
XDPは最もNICに近い段階でパケットデータに対してBPFプログラムを実行します。デバイスドライバに強く結び付いており、カーネル内部のパケット管理データ構造(skb)を割り当てるよりも前にBPFプログラムを実行することで、高速なパケットのフィルタリングやフォワーディングを実現します。
このため、XDPの利用にはデバイスドライバの対応が必要になります。デバイスに非依存な「Generic XDP」と呼ばれる機能も存在しますが、ネイティブな対応よりも性能は劣ります。
代表的なユースケースとしてはロードバランシング(例:Facebookの「kataran」)やDDoS攻撃対策(CloudFlareでの事例)があります。
「kprobe」「tracepoint」「perf」といった、Linuxの従来のトレーシング機能が、BPFを利用することで、より柔軟に扱えるようになっています。「BPF_PROG_TYPE_PERF_EVENT」「BPF_PROG_TYPE_KPROBE」「BPF_PROG_TYPE_TRACEPOINT」のプログラムタイプが関連します。
これらを利用すると、下記のようなことが可能になります。
BPFによるトレーシングの概要図を下に示します。
Linuxの「perf_event」という機能でパフォーマンスカウンタやkprobe、tracepointなどのイベントが抽象化されており、そのイベントにBPFプログラムをひも付けることが可能です。BPFプログラムはBPFマップにデータを記録できる他、ユーザー空間と共有のリングバッファーを利用することも可能です。
なお、他のBPFプログラムタイプであっても、BPFマップにはアクセス可能です。これを利用してトレーシングを実施できます。例えば、「BPF_PROG_TYPE_SOCKET_FILTER」でパケットフィルタリングをするBPFプログラムであっても、そのプログラム内でBPFマップにパケットの統計情報を記録することが可能です。
Linux 4.18から、「LIRC」という赤外線を送受信するための機構において、プロトコルのデコードにBPFプログラムが利用できるようになっています。これには「BPF_PROG_TYPE_LIRC_MODE2」というプログラムタイプが利用されます。
このように、ネットワークやトレーシング以外でもBPFが利用されるようになっています。
勘の良い方はseccompに関するプログラムタイプがないことに気付いたかもしれません。seccompはBPFを利用したシステムコールフィルタリング機能です。
seccompのBPFプログラムタイプがない理由は、seccompは歴史的いきさつから「eBPF」ではなく、「cBPF」を利用しているからです。seccompにおいて、BPFマップを利用してシステムコールの統計を取るといったことはできません。
seccompのBPFプログラムのロードは専用のシステムコール(seccomp(2))を利用します(なお、内部でcBPFプログラムはeBPFプログラムに変換され実行されています)。
システムコールの統計を取りたいのであれば、kprobeやtracepointでシステムコール関数をフックすることができます。「seccompでeBPFを利用するようにしよう」という話は前からあるので、将来的に変わる可能性はあります。
本稿ではLinuxで利用されるBPFについて、主にBPFプログラムタイプの観点から説明しました。
次回はネットワークに関して、BPFプログラムを作成してさまざまな機能を試します。
東京大学 大学院 情報理工学系研究科 博士課程
オペレーティングシステムや仮想化技術の研究に従事。
Copyright © ITmedia, Inc. All Rights Reserved.