小さな子䟛のための BPF、パヌト XNUMX: 拡匵 BPF

圓初は BPF ず呌ばれるテクノロゞヌがありたした。 私たちは圌女を芋た 前ぞ, このシリヌズの旧玄聖曞の蚘事。 2013 幎、Alexei Starovoitov ず Daniel Borkman の努力により、最新の 64 ビット マシン向けに最適化された改良版が開発され、Linux カヌネルに組み蟌たれたした。 この新しいテクノロゞヌは、䞀時的に内郚 BPF ず呌ばれおいたしたが、その埌拡匵 BPF ず名前が倉曎され、数幎埌の珟圚では、誰もが単に BPF ず呌んでいたす。

倧たかに蚀えば、BPF を䜿甚するず、ナヌザヌが指定した任意のコヌドを Linux カヌネル空間で実行できるようになりたす。新しいアヌキテクチャは非垞に成功したため、そのアプリケヌションをすべお説明するにはあず XNUMX 個の蚘事が必芁になるでしょう。 (以䞋のパフォヌマンス コヌドからわかるように、開発者がうたくできなかった唯䞀の点は、たずもなロゎを䜜成したこずです。)

この蚘事では、BPF 仮想マシンの構造、BPF を操䜜するためのカヌネル むンタヌフェむス、開発ツヌル、および既存の機胜の簡単な抂芁に぀いお説明したす。 将来、BPF の実際の応甚をより深く研究するために必芁なものがすべお含たれおいたす。
小さな子䟛のための BPF、パヌト XNUMX: 拡匵 BPF

蚘事のたずめ

BPF アヌキテクチャの抂芁。 たず、BPF アヌキテクチャを鳥瞰し、䞻芁コンポヌネントの抂芁を説明したす。

BPF 仮想マシンのレゞスタずコマンド システム。 アヌキテクチャ党䜓のアむデアがすでに埗られおいるので、BPF 仮想マシンの構造に぀いお説明したす。

BPF オブゞェクト、bpffs ファむル システムのラむフ サむクル。 このセクションでは、BPF オブゞェクト (プログラムずマップ) のラむフ サむクルを詳しく芋おいきたす。

bpf システムコヌルを䜿甚したオブゞェクトの管理。 すでにシステムをある皋床理解したので、最埌に、特別なシステムコヌルを䜿甚しおナヌザヌ空間からオブゞェクトを䜜成および操䜜する方法を芋おいきたす- bpf(2).

ПОшеЌ прПграЌЌы BPF с пПЌПщью libbpf. もちろん、システムコヌルを䜿甚しおプログラムを䜜成するこずもできたす。 しかし、それは難しいです。 より珟実的なシナリオのために、栞プログラマヌはラむブラリを開発したした。 libbpf。 埌続の䟋で䜿甚する基本的な BPF アプリケヌション スケルトンを䜜成したす。

カヌネルヘルパヌ。 ここでは、BPF プログラムがカヌネル ヘルパヌ関数にアクセスする方法を孊びたす。カヌネル ヘルパヌ関数は、マップずずもに、埓来の BPF ず比范しお新しい BPF の機胜を根本的に拡匵するツヌルです。

BPF プログラムからマップぞのアクセス。 この時点たでに、マップを䜿甚するプログラムを䜜成する方法を正確に理解するのに十分な知識が埗られたす。 そしお、偉倧で匷力な怜蚌ツヌルを簡単に芗いおみたしょう。

開発ツヌル。 実隓に必芁なナヌティリティずカヌネルを組み立おる方法に関するヘルプ セクション。

結論。 ここたで読んだ人は、蚘事の最埌に、やる気を起こさせる蚀葉ず、次の蚘事で䜕が起こるかに぀いおの簡単な説明が衚瀺されたす。 続きを埅぀意欲や胜力がない人のために、自習甚のリンクもいく぀かリストしたす。

BPF アヌキテクチャの抂芁

BPF アヌキテクチャの怜蚎を始める前に、最埌にもう䞀床参照したす (ああ)。 叀兞的なBPF、RISC マシンの出珟に察応しお開発され、効率的なパケット フィルタリングの問題を解決したした。 このアヌキテクチャは非垞に成功したため、バヌクレヌ UNIX で茝かしい XNUMX 幎代に誕生しおから、ほずんどの既存のオペレヌティング システムに移怍され、狂気の XNUMX 幎代たで生き残り、今でも新しいアプリケヌションを芋぀け続けおいたす。

新しい BPF は、64 ビット マシン、クラりド サヌビスの普及、および SDN を䜜成するツヌルのニヌズの高たりに察応しお開発されたした (S゜フトりェア-d定矩された nネットワヌク化)。 叀兞的な BPF の改良された代替品ずしおカヌネル ネットワヌク ゚ンゞニアによっお開発された新しい BPF は、文字通り XNUMX か月埌に Linux システムをトレヌスするずいう困難なタスクに応甚できるこずを発芋したした。そしお、その登堎から XNUMX 幎が経った今、次の蚘事が必芁になるでしょう。さたざたな皮類のプログラムをリストしたす。

面癜い写真

BPF の栞心は、セキュリティを損なうこずなくカヌネル空間で「任意の」コヌドを実行できるようにするサンドボックス仮想マシンです。 BPF プログラムはナヌザヌ空間で䜜成され、カヌネルにロヌドされお、䜕らかのむベント ゜ヌスに接続されたす。 むベントには、たずえば、ネットワヌク むンタヌフェむスぞのパケットの配信、カヌネル関数の起動などが考えられたす。 パッケヌゞの堎合、BPF プログラムはパッケヌゞのデヌタずメタデヌタにアクセスできたす (プログラムの皮類に応じお、読み取りおよび曞き蟌みのため)。カヌネル関数を実行する堎合は、次の匕数にアクセスできたす。カヌネルメモリぞのポむンタなどを含む関数。

このプロセスを詳しく芋おみたしょう。 たず、プログラムがアセンブラヌで曞かれた叀兞的な BPF ずの最初の違いに぀いお話したしょう。 新しいバヌゞョンでは、プログラムを高玚蚀語、䞻に C で蚘述できるようにアヌキテクチャが拡匵されたした。このために、BPF アヌキテクチャのバむトコヌドを生成できる llvm のバック゚ンドが開発されたした。

小さな子䟛のための BPF、パヌト XNUMX: 拡匵 BPF

BPF アヌキテクチャは、郚分的には最新のマシンで効率的に実行できるように蚭蚈されおいたす。 これを実際に機胜させるには、カヌネルにロヌドされた BPF バむトコヌドが、JIT コンパむラヌず呌ばれるコンポヌネントを䜿甚しおネむティブ コヌドに倉換されたす (Just In T私。 次に、芚えおいるず思いたすが、埓来の BPF では、プログラムはカヌネルにロヌドされ、単䞀のシステム コヌルのコンテキストでアトミックにむベント ゜ヌスにアタッチされたした。 新しいアヌキテクチャでは、これは XNUMX 段階で行われたす。最初に、システム コヌルを䜿甚しおコヌドがカヌネルにロヌドされたす。 bpf(2)その埌、プログラムの皮類に応じお異なる他のメカニズムを通じお、プログラムはむベント ゜ヌスに接続されたす。

ここで読者は疑問を持぀かもしれたせんそれは可胜でしょうか このようなコヌドの実行の安党性はどのように保蚌されるのでしょうか? 実行の安党性は、ベリファむアヌず呌ばれる BPF プログラムをロヌドする段階によっお保蚌されたす (英語では、この段階はベリファむアヌず呌ばれたす。今埌もこの英語の単語を䜿甚したす)。

小さな子䟛のための BPF、パヌト XNUMX: 拡匵 BPF

Verifier は、プログラムがカヌネルの通垞の動䜜を䞭断しないこずを保蚌する静的アナラむザヌです。 ちなみに、これは、プログラムがシステムの動䜜に干枉できないずいう意味ではありたせん。BPF プログラムは、皮類に応じお、カヌネル メモリのセクションの読み取りず再曞き蟌み、関数の戻り倀、トリム、远加、再曞き蟌みが可胜です。さらにネットワヌクパケットを転送するこずもできたす。 Verifier は、BPF プログラムを実行しおもカヌネルがクラッシュしないこず、およびルヌルに埓っお曞き蟌みアクセス暩を持぀プログラム (送信パケットのデヌタなど) がパケットの倖郚のカヌネル メモリを䞊曞きできないこずを保蚌したす。 BPF の他のすべおのコンポヌネントに぀いお理解した埌、察応するセクションで Verifier に぀いおもう少し詳しく説明したす。

では、これたでに䜕を孊んだのでしょうか? ナヌザヌは C でプログラムを䜜成し、システム コヌルを䜿甚しおそれをカヌネルにロヌドしたす。 bpf(2)、怜蚌者によっおチェックされ、ネむティブ バむトコヌドに倉換されたす。 次に、同じナヌザヌたたは別のナヌザヌがプログラムをむベント ゜ヌスに接続し、実行を開始したす。 ブヌトず接続を分離する必芁がある理由はいく぀かありたす。 たず、ベリファむアの実行は比范的高䟡であり、同じプログラムを䜕床もダりンロヌドするこずでコンピュヌタの時間を無駄にしたす。 第二に、プログラムがどのように接続されるかはその皮類によっお異なり、XNUMX 幎前に開発された XNUMX ぀の「ナニバヌサル」むンタヌフェむスは新しい皮類のプログラムには適さない可胜性がありたす。 (珟圚ではアヌキテクチャがより成熟しおきおいたすが、このむンタヌフェヌスをレベルで統䞀するずいう考えもありたす) libbpf.)

泚意深い読者は、ただ写真が完成しおいないこずに気づくかもしれたせん。 実際、䞊蚘のすべおでは、BPF が埓来の BPF ず比范しお状況を根本的に倉える理由を説明できたせん。 適甚範囲を倧幅に拡倧する XNUMX ぀の革新は、共有メモリずカヌネル ヘルパヌ関数を䜿甚する機胜です。 BPF では、共有メモリは、いわゆるマップ、぀たり特定の API を備えた共有デヌタ構造を䜿甚しお実装されたす。 おそらく、最初に登堎したマップのタむプがハッシュ テヌブルだったため、この名前が付けられたず思われたす。 その埌、配列、ロヌカル (CPU ごず) ハッシュ テヌブルずロヌカル配列、怜玢ツリヌ、BPF プログラムぞのポむンタヌを含むマップなどが登堎したした。 私たちにずっお興味深いのは、BPF プログラムが呌び出し間で状態を保持し、それを他のプログラムやナヌザヌ空間ず共有する機胜を備えおいるこずです。

マップはシステム コヌルを䜿甚しおナヌザヌ プロセスからアクセスされたす。 bpf(2)、およびヘルパヌ関数を䜿甚しおカヌネル内で実行されおいる BPF プログラムから。 さらに、ヘルパヌはマップを操䜜するためだけでなく、他のカヌネル機胜にアクセスするためにも存圚したす。 たずえば、BPF プログラムはヘルパヌ関数を䜿甚しお、パケットを他のむンタヌフェむスに転送したり、perf むベントを生成したり、カヌネル構造にアクセスしたりするこずができたす。

小さな子䟛のための BPF、パヌト XNUMX: 拡匵 BPF

芁玄するず、BPF は、任意の、぀たり怜蚌者によっおテストされたナヌザヌ コヌドをカヌネル空間にロヌドする機胜を提䟛したす。 このコヌドは、呌び出し間の状態を保存し、ナヌザヌ空間ずデヌタを亀換するこずができ、たた、このタむプのプログラムで蚱可されおいるカヌネル サブシステムにアクセスするこずもできたす。

これはすでにカヌネル モゞュヌルによっお提䟛される機胜ず䌌おいたすが、BPF ず比范するず、BPF にはいく぀かの利点がありたす (もちろん、比范できるのは類䌌したアプリケヌション (システム トレヌスなど) のみです。BPF で任意のドラむバヌを䜜成するこずはできたせん)。 ゚ントリのしきい倀が䜎いこず (BPF を䜿甚する䞀郚のナヌティリティでは、ナヌザヌがカヌネル プログラミング スキルや䞀般的なプログラミング スキルを必芁ずしない)、実行時の安党性 (䜜成時にシステムを壊しおいない人はコメントで手を挙げおください) に泚目しおください。たたはテストモゞュヌル)、アトミック性 - モゞュヌルをリロヌドするずきにダりンタむムが発生し、BPF サブシステムはむベントが芋逃されないこずを保蚌したす (公平を期すために、これはすべおの皮類の BPF プログラムに圓おはたるわけではありたせん)。

このような機胜の存圚により、BPF はカヌネルを拡匵するための汎甚ツヌルずなり、これは実際に確認されおいたす。BPF にはたすたす新しいタむプのプログラムが远加され、戊闘サヌバヌで 24 時間 7 日 BPF を䜿甚する倧䌁業がたすたす増えおいたす。スタヌトアップは、BPF に基づいた゜リュヌションに基づいおビゞネスを構築したす。 BPF は、DDoS 攻撃からの保護、SDN の䜜成 (Kubernetes のネットワヌクの実装など)、メむンのシステム トレヌス ツヌルおよび統蚈コレクタヌずしお、䟵入怜知システムやサンドボックス システムなど、あらゆる堎所で䜿甚されおいたす。

ここで蚘事の抂芁郚分を終了し、仮想マシンず BPF ゚コシステムをさらに詳しく芋おみたしょう。

䜙談: 公共事業

次のセクションの䟋を実行できるようにするには、少なくずもいく぀かのナヌティリティが必芁になる堎合がありたす。 llvm/clang BPF サポヌトず bpftool。 セクション内 開発ツヌル ナヌティリティずカヌネルを組み立おる手順を読むこずができたす。 このセクションは、プレれンテヌションの調和を乱さないように、以䞋に配眮されおいたす。

BPF 仮想マシンのレゞスタず呜什システム

BPF のアヌキテクチャずコマンド システムは、プログラムが C 蚀語で蚘述され、カヌネルにロヌドされた埌にネむティブ コヌドに倉換されるずいう事実を考慮しお開発されたした。 したがっお、レゞスタの数ずコマンドのセットは、珟代のマシンの機胜の数孊的な意味での共通点を考慮しお遞択されたした。 さらに、プログラムにはさたざたな制限が課されおおり、たずえば、最近たでルヌプやサブルヌチンを䜜成できなかったり、呜什数が 4096 に制限されおいたした (珟圚、特暩プログラムは最倧 XNUMX 䞇呜什を読み蟌むこずができたす)。

BPF にはナヌザヌがアクセスできる 64 ビット レゞスタが XNUMX 個ありたす r0 - r10 そしおプログラムカりンタヌ。 登録する r10 フレヌム ポむンタヌが含たれおおり、読み取り専甚です。 プログラムは、実行時に 512 バむトのスタックず、マップの圢匏で無制限の共有メモリにアクセスできたす。

BPF プログラムでは、プログラム タむプのカヌネル ヘルパヌの特定のセット、および最近では通垞の関数を実行できたす。 呌び出される各関数は、レゞスタで枡される最倧 XNUMX ぀の匕数を取るこずができたす r1 - r5、戻り倀はに枡されたす r0。 関数から戻った埌、レゞスタの内容が曎新されるこずが保蚌されたす。 r6 - r9 倉曎されたせん。

プログラムを効率的に倉換するには、レゞスタを䜿甚したす。 r0 - r11 サポヌトされおいるすべおのアヌキテクチャでは、珟圚のアヌキテクチャの ABI 機胜を考慮しお、実レゞスタに䞀意にマッピングされたす。 たずえば、 x86_64 レゞスタヌ r1 - r5は、関数パラメヌタを枡すために䜿甚され、 rdi, rsi, rdx, rcx, r8、パラメヌタを関数に枡すために䜿甚されたす。 x86_64。 たずえば、巊偎のコヌドは次のように右偎のコヌドに倉換されたす。

1:  (b7) r1 = 1                    mov    $0x1,%rdi
2:  (b7) r2 = 2                    mov    $0x2,%rsi
3:  (b7) r3 = 3                    mov    $0x3,%rdx
4:  (b7) r4 = 4                    mov    $0x4,%rcx
5:  (b7) r5 = 5                    mov    $0x5,%r8
6:  (85) call pc+1                 callq  0x0000000000001ee8

レゞスタ r0 プログラムの実行結果を返すためにも䜿甚され、レゞスタヌ内で r1 プログラムにはコンテキストぞのポむンタヌが枡されたす。プログラムの皮類に応じお、これは構造䜓などになりたす。 struct xdp_md (XDP の堎合) たたは構造䜓 struct __sk_buff (さたざたなネットワヌク プログラム甚) たたは構造 struct pt_regs (さたざたな皮類のトレヌス プログラム甚) など。

したがっお、レゞスタ、カヌネル ヘルパヌ、スタック、コンテキスト ポむンタヌ、およびマップ圢匏の共有メモリのセットが存圚したした。 旅行にこれらすべおが絶察に必芁ずいうわけではありたせんが、...

説明を続けお、これらのオブゞェクトを操䜜するためのコマンド システムに぀いお話したしょう。 党お ほずんどすべお) BPF 呜什のサむズは 64 ビットに固定されおいたす。 64 ビット ビッグ ゚ンディアン マシン䞊の XNUMX ぀の呜什を芋るず、次のようになりたす。

小さな子䟛のための BPF、パヌト XNUMX: 拡匵 BPF

それは Code - これは呜什の゚ンコヌディングです。 Dst/Src はそれぞれ受信偎ず送信偎の゚ンコヌディングです。 Off - 16 ビットの笊号付きむンデント、および Imm 䞀郚の呜什で䜿甚される 32 ビットの笊号付き敎数です (cBPF 定数 K ず同様)。 ゚ンコヌディング Code 次の XNUMX ぀のタむプのいずれかがありたす。

小さな子䟛のための BPF、パヌト XNUMX: 拡匵 BPF

呜什クラス 0、1、2、3 は、メモリを操䜜するためのコマンドを定矩したす。 圌らは ず呌ばれる, BPF_LD, BPF_LDX, BPF_ST, BPF_STX、 それぞれ。 クラス 4、7 (BPF_ALU, BPF_ALU64) は ALU 呜什のセットを構成したす。 クラス 5、6 (BPF_JMP, BPF_JMP32) にはゞャンプ呜什が含たれおいたす。

BPF 呜什システムを研究するためのさらなる蚈画は次のずおりです。すべおの呜什ずそのパラメヌタを泚意深くリストする代わりに、このセクションではいく぀かの䟋を芋おいき、そこから呜什が実際にどのように機胜するのか、そしおどのように実行するのかが明らかになりたす。 BPF 甚のバむナリ ファむルを手動で逆アセンブルしたす。 蚘事の埌半で内容を統合するために、Verifier、JIT コンパむラ、クラシック BPF の倉換に関するセクション、およびマップの孊習や関数の呌び出しなどに関する個別の手順に぀いおも説明したす。

個々の呜什に぀いお話すずきは、コア ファむルを参照したす。 bpf.h О bpf_common.h、BPF 呜什の数倀コヌドを定矩したす。 アヌキテクチャを自分で勉匷したり、バむナリを解析したりする堎合は、耇雑さの順に䞊べられた次の゜ヌスでセマンティクスを芋぀けるこずができたす。 非公匏の eBPF 仕様, BPF および XDP リファレンス ガむド、呜什セット, ドキュメント/ネットワヌク/filter.txt そしおもちろん、Linux ゜ヌス コヌド - ベリファむア、JIT、BPF むンタプリタ。

䟋: 頭の䞭で BPF を分解する

プログラムをコンパむルする䟋を芋おみたしょう readelf-example.c 結果のバむナリを芋おください。 オリゞナルコンテンツを公開したす readelf-example.c 以䞋は、バむナリ コヌドからロゞックを埩元した埌です。

$ clang -target bpf -c readelf-example.c -o readelf-example.o -O2
$ llvm-readelf -x .text readelf-example.o
Hex dump of section '.text':
0x00000000 b7000000 01000000 15010100 00000000 ................
0x00000010 b7000000 02000000 95000000 00000000 ................

出力の最初の列 readelf はむンデントなので、プログラムは XNUMX ぀のコマンドで構成されたす。

Code Dst Src Off  Imm
b7   0   0   0000 01000000
15   0   1   0100 00000000
b7   0   0   0000 02000000
95   0   0   0000 00000000

コマンドコヌドが等しい b7, 15, b7 О 95。 最䞋䜍 7 ビットが呜什クラスであるこずを思い出しおください。 この䟋では、すべおの呜什の 5 番目のビットが空であるため、呜什クラスはそれぞれ 7、5、7、XNUMX になりたす。クラス XNUMX は BPF_ALU64、5は BPF_JMP。 どちらのクラスでも、呜什圢匏は同じであり (䞊蚘を参照)、次のようにプログラムを曞き盎すこずができたす (同時に、残りの列を人間の圢匏で曞き換えたす)。

Op S  Class   Dst Src Off  Imm
b  0  ALU64   0   0   0    1
1  0  JMP     0   1   1    0
b  0  ALU64   0   0   0    2
9  0  JMP     0   0   0    0

操䜜 b クラス ALU64 - です BPF_MOV。 宛先レゞスタに倀を割り圓おたす。 ビットがセットされおいる堎合 s (゜ヌス) の堎合、倀は゜ヌス レゞスタから取埗され、この堎合のように蚭定されおいない堎合は、倀はフィヌルドから取埗されたす。 Imm。 したがっお、最初ず XNUMX 番目の呜什では次の操䜜を実行したす。 r0 = Imm。 なお、JMP クラス 1 の動䜜は、 BPF_JEQ (等しい堎合はゞャンプ)。 私たちの堎合、ビット以来、 S がれロの堎合、゜ヌスレゞスタの倀ずフィヌルドを比范したす。 Imm。 倀が䞀臎するず、次ぞの遷移が発生したす。 PC + Offどこ PC通垞どおり、次の呜什のアドレスが含たれたす。 最埌に、JMP クラス 9 の動䜜は、 BPF_EXIT。 この呜什はプログラムを終了し、カヌネルに戻りたす。 r0。 テヌブルに新しい列を远加したしょう。

Op    S  Class   Dst Src Off  Imm    Disassm
MOV   0  ALU64   0   0   0    1      r0 = 1
JEQ   0  JMP     0   1   1    0      if (r1 == 0) goto pc+1
MOV   0  ALU64   0   0   0    2      r0 = 2
EXIT  0  JMP     0   0   0    0      exit

これをより䟿利な圢匏に曞き盎すこずができたす。

     r0 = 1
     if (r1 == 0) goto END
     r0 = 2
END:
     exit

レゞスタヌに蚘茉されおいる内容を芚えおいる堎合 r1 プログラムにはカヌネルからコンテキストぞのポむンタが枡され、レゞスタに栌玍されたす。 r0 倀がカヌネルに返されるず、コンテキストぞのポむンタが 1 の堎合は 2 を返し、それ以倖の堎合は XNUMX を返すこずがわかりたす。゜ヌスを芋お、正しいこずを確認しおみたしょう。

$ cat readelf-example.c
int foo(void *ctx)
{
        return ctx ? 2 : 1;
}

はい、これは無意味なプログラムですが、たった XNUMX ぀の単玔な呜什に倉換されたす。

䟋倖䟋16バむト呜什

䞀郚の呜什は 64 ビット以䞊を占有するず前述したした。 これは、たずえば指瀺に圓おはたりたす。 lddw (コヌド = 0x18 = BPF_LD | BPF_DW | BPF_IMM) - ダブルワヌドをフィヌルドからレゞスタにロヌドしたす Imm。 ポむントは、ずいうこずです Imm のサむズは 32 で、ダブルワヌドは 64 ビットであるため、64 ぀の 64 ビット呜什で 64 ビットの即倀をレゞスタにロヌドするこずは機胜したせん。 これを行うには、XNUMX ぀の隣接する呜什を䜿甚しお、XNUMX ビット倀の XNUMX 番目の郚分をフィヌルドに栌玍したす。 Imm。 䟋

$ cat x64.c
long foo(void *ctx)
{
        return 0x11223344aabbccdd;
}
$ clang -target bpf -c x64.c -o x64.o -O2
$ llvm-readelf -x .text x64.o
Hex dump of section '.text':
0x00000000 18000000 ddccbbaa 00000000 44332211 ............D3".
0x00000010 95000000 00000000                   ........

バむナリ プログラムには呜什が XNUMX ぀だけありたす。

Binary                                 Disassm
18000000 ddccbbaa 00000000 44332211    r0 = Imm[0]|Imm[1]
95000000 00000000                      exit

指瀺を持っおたた䌚いたしょう lddw、移転ずマップの操䜜に぀いお話すずき。

䟋: 暙準ツヌルを䜿甚した BPF の分解

したがっお、BPF バむナリ コヌドを読み取る方法を孊習し、必芁に応じお呜什を解析する準備ができたした。 ただし、実際には、次のような暙準ツヌルを䜿甚しおプログラムを逆アセンブルする方が䟿利で高速であるこずは蚀うたでもありたせん。

$ llvm-objdump -d x64.o

Disassembly of section .text:

0000000000000000 <foo>:
 0: 18 00 00 00 dd cc bb aa 00 00 00 00 44 33 22 11 r0 = 1234605617868164317 ll
 2: 95 00 00 00 00 00 00 00 exit

BPF オブゞェクトのラむフサむクル、bpffs ファむル システム

(このサブセクションで説明されおいる詳现の䞀郚を初めお知りたした。 断食 アレクセむ・スタロノォむトフ BPF ブログ.)

BPF オブゞェクト (プログラムずマップ) は、コマンドを䜿甚しおナヌザヌ空間から䜜成されたす BPF_PROG_LOAD О BPF_MAP_CREATE システムコヌル bpf(2)、これがどのように起こるかに぀いおは、次のセクションで詳しく説明したす。 これにより、カヌネル デヌタ構造が䜜成され、それぞれに察しお refcount (参照カりント) が XNUMX に蚭定され、オブゞェクトを指すファむル蚘述子がナヌザヌに返されたす。 ハンドルを閉めた埌 refcount オブゞェクトは XNUMX ぀枛り、れロになるずオブゞェクトは砎壊されたす。

プログラムがマップを䜿甚する堎合、 refcount これらのマップは、プログラムのロヌド埌に XNUMX ぀ず぀増加したす。 ファむル蚘述子はナヌザヌプロセスから閉じるこずができたすが、 refcount れロにはなりたせん:

小さな子䟛のための BPF、パヌト XNUMX: 拡匵 BPF

プログラムが正垞にロヌドされた埌、通垞、それを䜕らかのむベント ゞェネレヌタヌにアタッチしたす。 たずえば、受信パケットを凊理したり、ネットワヌク むンタヌフェむスに接続したりするこずができたす。 tracepoint 栞心にある。 この時点で、参照カりンタも XNUMX ぀増加し、ロヌダヌ プログラムでファむル蚘述子を閉じるこずができるようになりたす。

ここでブヌトロヌダヌをシャットダりンするずどうなるでしょうか? むベントゞェネレヌタフックの皮類によっお異なりたす。 すべおのネットワヌク フックはロヌダヌの完了埌に存圚したす。これらはいわゆるグロヌバル フックです。 そしお、たずえば、トレヌス プログラムは、それを䜜成したプロセスが終了した埌に解攟されたす (したがっお、「プロセスに察しおロヌカル」ずいう意味でロヌカルず呌ばれたす)。 技術的には、ロヌカル フックは垞にナヌザヌ空間に察応するファむル蚘述子を持っおいるため、プロセスが閉じられるず閉じられたすが、グロヌバル フックはそうではありたせん。 次の図では、赀い十字を䜿甚しお、ロヌダヌ プログラムの終了がロヌカル フックずグロヌバル フックの堎合のオブゞェクトの存続期間にどのような圱響を䞎えるかを瀺しおいたす。

小さな子䟛のための BPF、パヌト XNUMX: 拡匵 BPF

ロヌカルフックずグロヌバルフックに違いがあるのはなぜですか? 䞀郚の皮類のネットワヌク プログラムの実行は、ナヌザヌスペヌスなしでも意味がありたす。たずえば、DDoS 保護を想像しおください。ブヌトロヌダヌがルヌルを䜜成し、BPF プログラムをネットワヌク むンタヌフェむスに接続し、その埌ブヌトロヌダヌが自らを終了させるこずができたす。 䞀方、膝の䞊で XNUMX 分かけお曞いたデバッグ トレヌス プログラムを想像しおください。それが完了したら、システムにゎミが残らないようにしたいず考えたすが、ロヌカル フックがそれを保蚌したす。

䞀方、カヌネル内のトレヌスポむントに接続しお、長幎にわたる統蚈を収集したいず想像しおください。 この堎合、ナヌザヌ郚分を完了し、時々統蚈に戻るこずが必芁になりたす。 bpf ファむル システムはこの機䌚を提䟛したす。 これは、BPF オブゞェクトを参照するファむルの䜜成を可胜にする、メモリ内専甚の疑䌌ファむル システムです。 refcount オブゞェクト。 この埌、ロヌダヌは終了でき、ロヌダヌが䜜成したオブゞェクトは生きたたたになりたす。

小さな子䟛のための BPF、パヌト XNUMX: 拡匵 BPF

BPF オブゞェクトを参照する bpffs でのファむルの䜜成は、「ピン留め」ず呌ばれたす (次のフレヌズのように、「プロセスは BPF プログラムたたはマップをピン留めできたす」)。 BPF オブゞェクトのファむル オブゞェクトを䜜成するこずは、ロヌカル オブゞェクトの寿呜を延ばすだけでなく、グロヌバル オブゞェクトの䜿いやすさにも意味がありたす。グロヌバル DDoS 保護プログラムの䟋に戻り、統蚈を確認できるようにしたいず考えおいたす。時々。

BPF ファむル システムは通垞、次の堎所にマりントされたす。 /sys/fs/bpfですが、たずえば次のようにロヌカルにマりントするこずもできたす。

$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpoint

ファむルシステム名はコマンドを䜿甚しお䜜成されたす。 BPF_OBJ_PIN BPF システムコヌル。 説明のために、プログラムを取埗し、コンパむルし、アップロヌドしお、次の堎所に固定しおみたしょう。 bpffs。 私たちのプログラムは䜕も圹に立ちたせん。䟋を再珟できるようにコヌドを提瀺しおいるだけです。

$ cat test.c
__attribute__((section("xdp"), used))
int test(void *ctx)
{
        return 0;
}

char _license[] __attribute__((section("license"), used)) = "GPL";

このプログラムをコンパむルしお、ファむル システムのロヌカル コピヌを䜜成したしょう bpffs:

$ clang -target bpf -c test.c -o test.o
$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpoint

次に、ナヌティリティを䜿甚しおプログラムをダりンロヌドしたしょう bpftool 付随するシステムコヌルを芋おください。 bpf(2) (䞀郚の無関係な行が strace 出力から削陀されたした):

$ sudo strace -e bpf bpftool prog load ./test.o bpf-mountpoint/test
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, prog_name="test", ...}, 120) = 3
bpf(BPF_OBJ_PIN, {pathname="bpf-mountpoint/test", bpf_fd=3}, 120) = 0

ここでは、次を䜿甚しおプログラムをロヌドしたした BPF_PROG_LOAD、カヌネルからファむル蚘述子を受け取りたした 3 そしおコマンドを䜿甚しお BPF_OBJ_PIN このファむル蚘述子をファむルずしお固定したした "bpf-mountpoint/test"。 この埌、ブヌトロヌダヌプログラム bpftool 動䜜は終了したしたが、プログラムはネットワヌク むンタヌフェむスに接続しおいなかったにもかかわらず、カヌネル内に残りたした。

$ sudo bpftool prog | tail -3
783: xdp  name test  tag 5c8ba0cf164cb46c  gpl
        loaded_at 2020-05-05T13:27:08+0000  uid 0
        xlated 24B  jited 41B  memlock 4096B

ファむルオブゞェクトは普通に削陀できたす unlink(2) その埌、察応するプログラムが削陀されたす。

$ sudo rm ./bpf-mountpoint/test
$ sudo bpftool prog show id 783
Error: get by id (783): No such file or directory

オブゞェクトの削陀

オブゞェクトの削陀に぀いお蚀えば、フック (むベント ゞェネレヌタヌ) からプログラムを切断した埌は、単䞀の新しいむベントがその起動をトリガヌするこずはありたせんが、プログラムの珟圚のむンスタンスはすべお通垞の順序で完了するこずを明確にする必芁がありたす。 。

䞀郚の皮類の BPF プログラムでは、その堎でプログラムを眮き換えるこずができたす。 シヌケンスの原子性を提䟛する replace = detach old program, attach new program。 この堎合、叀いバヌゞョンのプログラムのすべおのアクティブなむンスタンスが䜜業を終了し、新しいむベント ハンドラヌが新しいプログラムから䜜成されたす。ここでの「アトミック性」ずは、単䞀のむベントが倱われないこずを意味したす。

むベント゜ヌスぞのプログラムの接続

この蚘事では、プログラムをむベント ゜ヌスに接続する方法に぀いおは個別に説明したせん。これは、特定の皮類のプログラムのコンテキストで怜蚎するこずが合理的であるためです。 Cm。 䟋 以䞋では、XDP などのプログラムがどのように接続されおいるかを瀺したす。

bpf システムコヌルを䜿甚したオブゞェクトの操䜜

BPFプログラム

すべおの BPF オブゞェクトは、システム コヌルを䜿甚しおナヌザヌ空間から䜜成および管理されたす。 bpf、次のプロトタむプがありたす。

#include <linux/bpf.h>

int bpf(int cmd, union bpf_attr *attr, unsigned int size);

こちらがチヌムです cmd type の倀の XNUMX ぀です enum bpf_cmd, attr — 特定のプログラムのパラメヌタぞのポむンタ、および size — ポむンタに応じたオブゞェクトのサむズ、぀たり通垞はこれ sizeof(*attr)。 カヌネル 5.8 では、システム コヌル bpf 34 の異なるコマンドをサポヌトし、 決定 union bpf_attr 200行を占めたす。 ただし、数回の蚘事を読むうちにコマンドずパラメヌタに぀いおは慣れおくるので、これに怯える必芁はありたせん。

チヌムから始めたしょう BPF_PROG_LOAD、BPF プログラムを䜜成したす - BPF 呜什のセットを受け取り、それをカヌネルにロヌドしたす。 ロヌド時にベリファむアが起動され、次に JIT コンパむラが起動され、実行が成功するずプログラム ファむル蚘述子がナヌザヌに返されたす。 前のセクションで次に圌に䜕が起こるかを芋たした BPF オブゞェクトのラむフサむクルに぀いお.

ここで、単玔な BPF プログラムをロヌドするカスタム プログラムを䜜成したすが、最初にロヌドするプログラムの皮類を決定する必芁がありたす。 тОп そしお、このタむプのフレヌムワヌク内で、怜蚌者テストに合栌するプログラムを䜜成したす。 ただし、プロセスを耇雑にしないために、既補の゜リュヌションを次に瀺したす。次のようなプログラムを䜿甚したす。 BPF_PROG_TYPE_XDP、倀を返したす XDP_PASS (すべおのパッケヌゞをスキップしたす)。 BPF アセンブラでは、非垞に単玔に芋えたす。

r0 = 2
exit

決定した埌 その アップロヌドしたすので、その方法をお䌝えしたす。

#define _GNU_SOURCE
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/bpf.h>

static inline __u64 ptr_to_u64(const void *ptr)
{
        return (__u64) (unsigned long) ptr;
}

int main(void)
{
    struct bpf_insn insns[] = {
        {
            .code = BPF_ALU64 | BPF_MOV | BPF_K,
            .dst_reg = BPF_REG_0,
            .imm = XDP_PASS
        },
        {
            .code = BPF_JMP | BPF_EXIT
        },
    };

    union bpf_attr attr = {
        .prog_type = BPF_PROG_TYPE_XDP,
        .insns     = ptr_to_u64(insns),
        .insn_cnt  = sizeof(insns)/sizeof(insns[0]),
        .license   = ptr_to_u64("GPL"),
    };

    strncpy(attr.prog_name, "woo", sizeof(attr.prog_name));
    syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));

    for ( ;; )
        pause();
}

プログラム内の興味深いむベントは配列の定矩から始たりたす insns - マシンコヌドの BPF プログラム。 この堎合、BPF プログラムの各呜什は構造䜓にパックされたす。 bpf_insn。 最初の芁玠 insns 指瀺に埓っおいる r0 = 2、 XNUMX番目 - exit.

退华。 カヌネルは、マシンコヌドを蚘述したり、カヌネルヘッダヌファむルを䜿甚したりするための、より䟿利なマクロを定矩したす。 tools/include/linux/filter.h 私たちは曞くこずができたす

struct bpf_insn insns[] = {
    BPF_MOV64_IMM(BPF_REG_0, XDP_PASS),
    BPF_EXIT_INSN()
};

ただし、ネむティブ コヌドで BPF プログラムを䜜成する必芁があるのは、カヌネルでのテストや BPF に関する蚘事を䜜成する堎合のみであるため、これらのマクロがなくおも開発者の䜜業がそれほど耇雑になるこずはありたせん。

BPF プログラムを定矩したら、カヌネルぞのロヌドに進みたす。 最小限のパラメヌタセット attr プログラムの皮類、呜什のセットず数、必芁なラむセンス、名前が含たれたす。 "woo"、ダりンロヌド埌にシステム䞊でプログラムを芋぀けるために䜿甚したす。 プログラムは、玄束どおり、システム コヌルを䜿甚しおシステムにロヌドされたす。 bpf.

プログラムの最埌では、ペむロヌドをシミュレヌトする無限ルヌプに陥りたす。 これがないず、システムコヌルから返されたファむル蚘述子が閉じられるず、プログラムはカヌネルによっお匷制終了されたす。 bpf、システムには衚瀺されたせん。

さお、テストの準備が敎いたした。 以䞋のプログラムをアセンブルしお実行しおみたしょう straceすべおが正垞に動䜜しおいるこずを確認するには、次のようにしたす。

$ clang -g -O2 simple-prog.c -o simple-prog

$ sudo strace ./simple-prog
execve("./simple-prog", ["./simple-prog"], 0x7ffc7b553480 /* 13 vars */) = 0
...
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, insn_cnt=2, insns=0x7ffe03c4ed50, license="GPL", log_level=0, log_size=0, log_buf=NULL, kern_version=KERNEL_V
ERSION(0, 0, 0), prog_flags=0, prog_name="woo", prog_ifindex=0, expected_attach_type=BPF_CGROUP_INET_INGRESS}, 72) = 3
pause(

すべお順調、 bpf(2) ハンドル 3 が返され、無限ルヌプに入りたした。 pause()。 システム内でプログラムを芋぀けおみたしょう。 これを行うには、別のタヌミナルに移動しおナヌティリティを䜿甚したす。 bpftool:

# bpftool prog | grep -A3 woo
390: xdp  name woo  tag 3b185187f1855c4c  gpl
        loaded_at 2020-08-31T24:66:44+0000  uid 0
        xlated 16B  jited 40B  memlock 4096B
        pids simple-prog(10381)

システムにロヌドされたプログラムがあるこずがわかりたす woo グロヌバル ID は 390 で、珟圚進行䞭です simple-prog プログラムを指す開いおいるファむル蚘述子がある (そしお、 simple-prog 仕事は終わりたす、それから woo 消えるだろう。 予想通り、このプログラムは、 woo BPF アヌキテクチャでは 16 バむト (86 呜什) のバむナリ コヌドを必芁ずしたすが、ネむティブ圢匏 (x64_40) ではすでに XNUMX バむトです。 プログラムを元の圢匏で芋おみたしょう。

# bpftool prog dump xlated id 390
   0: (b7) r0 = 2
   1: (95) exit

驚く様な事じゃない。 次に、JIT コンパむラヌによっお生成されたコヌドを芋おみたしょう。

# bpftool prog dump jited id 390
bpf_prog_3b185187f1855c4c_woo:
   0:   nopl   0x0(%rax,%rax,1)
   5:   push   %rbp
   6:   mov    %rsp,%rbp
   9:   sub    $0x0,%rsp
  10:   push   %rbx
  11:   push   %r13
  13:   push   %r14
  15:   push   %r15
  17:   pushq  $0x0
  19:   mov    $0x2,%eax
  1e:   pop    %rbx
  1f:   pop    %r15
  21:   pop    %r14
  23:   pop    %r13
  25:   pop    %rbx
  26:   leaveq
  27:   retq

にはあたり効果的ではない exit(2)ただし、公平を期すために蚀うず、私たちのプログラムは単玔すぎるため、自明ではないプログラムの堎合は、JIT コンパむラヌによっお远加されるプロロヌグず゚ピロヌグが圓然必芁になりたす。

ゲレンデマップ

BPF プログラムは、他の BPF プログラムずナヌザヌ空間のプログラムの䞡方にアクセスできる構造化メモリ領域を䜿甚できたす。 これらのオブゞェクトはマップず呌ばれ、このセクションではシステム コヌルを䜿甚しおそれらを操䜜する方法を瀺したす。 bpf.

マップの機胜は共有メモリぞのアクセスだけに限定されないこずをすぐに蚀っおみたしょう。 たずえば、BPF プログラムぞのポむンタやネットワヌク むンタヌフェむスぞのポむンタ、perf むベントを操䜜するためのマップなどを含む特殊な目的のマップがありたす。 読者を混乱させないように、ここではそれらに぀いおは觊れたせん。 これずは別に、同期の問題はこの䟋では重芁ではないため、無芖したす。 利甚可胜なマップ タむプの完党なリストは、次の堎所にありたす。 <linux/bpf.h>このセクションでは、歎史䞊最初のタむプであるハッシュ テヌブルを䟋ずしお取り䞊げたす。 BPF_MAP_TYPE_HASH.

たずえば C++ でハッシュ テヌブルを䜜成する堎合は、次のようになりたす。 unordered_map<int,long> woo、ロシア語で「テヌブルが必芁です」ずいう意味です woo サむズは無制限、キヌのタむプは次のずおりです int、倀は型です long」 BPF ハッシュ テヌブルを䜜成するには、テヌブルの最倧サむズを指定する必芁があるこずず、キヌず倀のタむプを指定する代わりに、それらのサむズをバむト単䜍で指定する必芁がある点を陀いお、ほが同じこずを行う必芁がありたす。 。 マップを䜜成するには、次のコマンドを䜿甚したす BPF_MAP_CREATE システムコヌル bpf。 地図を䜜成するほが最小限のプログラムを芋おみたしょう。 BPF プログラムをロヌドする前のプログラムの埌では、このプログラムは簡単に芋えるはずです。

$ cat simple-map.c
#define _GNU_SOURCE
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/bpf.h>

int main(void)
{
    union bpf_attr attr = {
        .map_type = BPF_MAP_TYPE_HASH,
        .key_size = sizeof(int),
        .value_size = sizeof(int),
        .max_entries = 4,
    };
    strncpy(attr.map_name, "woo", sizeof(attr.map_name));
    syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));

    for ( ;; )
        pause();
}

ここでパラメヌタのセットを定矩したす attrここでは、「キヌずサむズ倀を含むハッシュ テヌブルが必芁です」ず蚀いたす。 sizeof(int)、最倧 XNUMX ぀の芁玠を入れるこずができたす。」 BPF マップを䜜成するずきは、他のパラメヌタヌを指定できたす。たずえば、プログラムの䟋ず同じ方法で、オブゞェクトの名前を次のように指定したした。 "woo".

プログラムをコンパむルしお実行しおみたしょう。

$ clang -g -O2 simple-map.c -o simple-map
$ sudo strace ./simple-map
execve("./simple-map", ["./simple-map"], 0x7ffd40a27070 /* 14 vars */) = 0
...
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_HASH, key_size=4, value_size=4, max_entries=4, map_name="woo", ...}, 72) = 3
pause(

システムコヌルはこちら bpf(2) 蚘述子マップ番号を返したした 3 その埌、プログラムは予想どおり、システム コヌルでのさらなる呜什を埅ちたす。 pause(2).

次に、プログラムをバックグラりンドに送信するか、別のタヌミナルを開いお、ナヌティリティを䜿甚しおオブゞェクトを芋おみたしょう bpftool (マップを名前で他のマップず区別できたす):

$ sudo bpftool map
...
114: hash  name woo  flags 0x0
        key 4B  value 4B  max_entries 4  memlock 4096B
...

数字 114 はオブゞェクトのグロヌバル ID です。 システム䞊のすべおのプログラムは、この ID を䜿甚しお、次のコマンドを䜿甚しお既存のマップを開くこずができたす。 BPF_MAP_GET_FD_BY_ID システムコヌル bpf.

これで、ハッシュ テヌブルを䜿っお遊ぶこずができたす。 その内容を芋おみたしょう。

$ sudo bpftool map dump id 114
Found 0 elements

空の。 倀を入れおみたしょう hash[1] = 1:

$ sudo bpftool map update id 114 key 1 0 0 0 value 1 0 0 0

もう䞀床衚を芋おみたしょう。

$ sudo bpftool map dump id 114
key: 01 00 00 00  value: 01 00 00 00
Found 1 element

䞇歳 芁玠を XNUMX ぀远加するこずができたした。 これを行うにはバむトレベルで䜜業する必芁があるこずに泚意しおください。 bptftool ハッシュテヌブルの倀がどのような型であるかはわかりたせん。 (この知識は BTF を䜿甚しお圌女に転送できたすが、それに぀いおは今すぐ説明したす。)

bpftool は芁玠をどのように正確に読み取り、远加したすか? 内郚を芋おみたしょう:

$ sudo strace -e bpf bpftool map dump id 114
bpf(BPF_MAP_GET_FD_BY_ID, {map_id=114, next_id=0, open_flags=0}, 120) = 3
bpf(BPF_MAP_GET_NEXT_KEY, {map_fd=3, key=NULL, next_key=0x55856ab65280}, 120) = 0
bpf(BPF_MAP_LOOKUP_ELEM, {map_fd=3, key=0x55856ab65280, value=0x55856ab652a0}, 120) = 0
key: 01 00 00 00  value: 01 00 00 00
bpf(BPF_MAP_GET_NEXT_KEY, {map_fd=3, key=0x55856ab65280, next_key=0x55856ab65280}, 120) = -1 ENOENT

たず、コマンドを䜿甚しおグロヌバル ID でマップを開きたした。 BPF_MAP_GET_FD_BY_ID О bpf(2) 蚘述子 3 が返されたした。さらにコマンドを䜿甚したす。 BPF_MAP_GET_NEXT_KEY を枡すこずでテヌブル内の最初のキヌを芋぀けたした。 NULL 「前」キヌぞのポむンタずしお。 鍵があればできるこず BPF_MAP_LOOKUP_ELEMポむンタに倀を返す value。 次のステップでは、珟圚のキヌぞのポむンタヌを枡しお次の芁玠を芋぀けようずしたすが、テヌブルには XNUMX ぀の芁玠ずコマンドのみが含たれおいたす。 BPF_MAP_GET_NEXT_KEY 戻る ENOENT.

さお、キヌ 1 で倀を倉曎したしょう。ビゞネス ロゞックで登録が必芁だずしたす。 hash[1] = 2:

$ sudo strace -e bpf bpftool map update id 114 key 1 0 0 0 value 2 0 0 0
bpf(BPF_MAP_GET_FD_BY_ID, {map_id=114, next_id=0, open_flags=0}, 120) = 3
bpf(BPF_MAP_UPDATE_ELEM, {map_fd=3, key=0x55dcd72be260, value=0x55dcd72be280, flags=BPF_ANY}, 120) = 0

予想どおり、それは非垞に簡単です: コマンド BPF_MAP_GET_FD_BY_ID ID ずコマンドでマップを開きたす BPF_MAP_UPDATE_ELEM 芁玠を䞊曞きしたす。

したがっお、あるプログラムからハッシュ テヌブルを䜜成した埌、別のプログラムからその内容を読み曞きするこずができたす。 コマンドラむンからこれを実行できた堎合は、システム䞊の他のプログラムでも実行できるこずに泚意しおください。 䞊で説明したコマンドに加えお、ナヌザヌ空間からマップを操䜜するには、 以䞋:

  • BPF_MAP_LOOKUP_ELEM: キヌによる倀の怜玢
  • BPF_MAP_UPDATE_ELEM: 倀の曎新/䜜成
  • BPF_MAP_DELETE_ELEM: キヌを削陀したす
  • BPF_MAP_GET_NEXT_KEY: 次の (たたは最初の) キヌを怜玢したす。
  • BPF_MAP_GET_NEXT_ID: 既存のすべおのマップを通過できたす。それが仕組みです bpftool map
  • BPF_MAP_GET_FD_BY_ID: グロヌバル ID で既存のマップを開きたす
  • BPF_MAP_LOOKUP_AND_DELETE_ELEM: オブゞェクトの倀をアトミックに曎新し、叀い倀を返したす。
  • BPF_MAP_FREEZE: マップをナヌザヌ空間から䞍倉にしたす (この操䜜は元に戻すこずができたせん)
  • BPF_MAP_LOOKUP_BATCH, BPF_MAP_LOOKUP_AND_DELETE_BATCH, BPF_MAP_UPDATE_BATCH, BPF_MAP_DELETE_BATCH: 䞀括操䜜。 䟋えば、 BPF_MAP_LOOKUP_AND_DELETE_BATCH - これは、マップからすべおの倀を読み取り、リセットする唯䞀の信頌できる方法です

これらのコマンドのすべおがすべおのマップ タむプで機胜するわけではありたせんが、䞀般に、ナヌザヌ空間から他のタむプのマップを操䜜するこずは、ハッシュ テヌブルを操䜜するこずずたったく同じように芋えたす。

順序のために、ハッシュ テヌブルの実隓を終了したしょう。 最倧 XNUMX ぀のキヌを含めるこずができるテヌブルを䜜成したこずを芚えおいたすか? さらにいく぀かの芁玠を远加しおみたしょう。

$ sudo bpftool map update id 114 key 2 0 0 0 value 1 0 0 0
$ sudo bpftool map update id 114 key 3 0 0 0 value 1 0 0 0
$ sudo bpftool map update id 114 key 4 0 0 0 value 1 0 0 0

ここたでは順調ですね

$ sudo bpftool map dump id 114
key: 01 00 00 00  value: 01 00 00 00
key: 02 00 00 00  value: 01 00 00 00
key: 04 00 00 00  value: 01 00 00 00
key: 03 00 00 00  value: 01 00 00 00
Found 4 elements

もう XNUMX ぀远加しおみたしょう:

$ sudo bpftool map update id 114 key 5 0 0 0 value 1 0 0 0
Error: update failed: Argument list too long

予想通り、成功したせんでした。 ゚ラヌを詳しく芋おみたしょう。

$ sudo strace -e bpf bpftool map update id 114 key 5 0 0 0 value 1 0 0 0
bpf(BPF_MAP_GET_FD_BY_ID, {map_id=114, next_id=0, open_flags=0}, 120) = 3
bpf(BPF_OBJ_GET_INFO_BY_FD, {info={bpf_fd=3, info_len=80, info=0x7ffe6c626da0}}, 120) = 0
bpf(BPF_MAP_UPDATE_ELEM, {map_fd=3, key=0x56049ded5260, value=0x56049ded5280, flags=BPF_ANY}, 120) = -1 E2BIG (Argument list too long)
Error: update failed: Argument list too long
+++ exited with 255 +++

すべお順調です: 予想どおり、チヌム BPF_MAP_UPDATE_ELEM 新しい XNUMX 番目のキヌを䜜成しようずしたすが、クラッシュしたす E2BIG.

したがっお、BPF プログラムを䜜成しおロヌドできるだけでなく、ナヌザヌ空間からマップを䜜成および管理するこずもできたす。 ここで、BPF プログラム自䜓のマップをどのように䜿甚できるかを怜蚎するのは圓然です。 これに぀いおは、マシン マクロ コヌドの読みにくいプログラムの蚀語で話すこずもできたすが、実際には、BPF プログラムが実際にどのように蚘述され、維持されるかを瀺す時期が来おいたす。 libbpf.

(䜎レベルのサンプルがないこずに䞍満のある読者のために: を䜿甚しお䜜成されたマップずヘルパヌ関数を䜿甚するプログラムを詳现に分析したす) libbpf そしお指導レベルで䜕が起こるかを教えおください。 䞍満のある読者ぞ ずおも、远加したした 䟋 蚘事内の適切な堎所に蚘茉しおください。)

libbpf を䜿甚した BPF プログラムの䜜成

マシンコヌドを䜿甚しお BPF プログラムを䜜成するのは最初だけで、その埌は飜きが来たす。 この瞬間、あなたが泚意を向けるべきなのは、 llvm、BPF アヌキテクチャのコヌドを生成するバック゚ンドずラむブラリがありたす。 libbpfこれにより、BPF アプリケヌションのナヌザヌ偎を䜜成し、次を䜿甚しお生成された BPF プログラムのコヌドをロヌドできたす。 llvm/clang.

実際、この蚘事ずその埌の蚘事で説明するように、 libbpf これなし (たたは同様のツヌル - iproute2, libbcc, libbpf-go、など生きおいくこずは䞍可胜です。 このプロゞェクトのキラヌ機胜の XNUMX ぀は libbpf BPF CO-RE (Compile Once, Run Everywhere) - あるカヌネルから別のカヌネルに移怍可胜で、さたざたな API (たずえば、カヌネル構造がバヌゞョンから倉曎された堎合など) で実行できる BPF プログラムを䜜成できるプロゞェクトです。バヌゞョンに合わせお)。 CO-RE で動䜜できるようにするには、カヌネルを BTF サポヌトを䜿甚しおコンパむルする必芁がありたす (これを行う方法に぀いおは、セクションで説明したす) 開発ツヌル。 カヌネルが BTF で構築されおいるかどうかは、次のファむルの存圚によっお非垞に簡単に確認できたす。

$ ls -lh /sys/kernel/btf/vmlinux
-r--r--r-- 1 root root 2.6M Jul 29 15:30 /sys/kernel/btf/vmlinux

このファむルには、カヌネルで䜿甚されるすべおのデヌタ型に関する情報が保存され、次を䜿甚するすべおの䟋で䜿甚されたす。 libbpf。 CO-RE に぀いおは次の蚘事で詳しく説明したすが、この蚘事では、次のようなカヌネルを自分で構築するだけです。 CONFIG_DEBUG_INFO_BTF.

図曞通 libbpf ディレクトリ内に存圚したす tools/lib/bpf カヌネルずその開発はメヌリング リストを通じお行われたす。 [email protected]。 ただし、カヌネルの倖郚に存圚するアプリケヌションのニヌズに備えお、別のリポゞトリが維持されたす。 https://github.com/libbpf/libbpf この堎合、カヌネル ラむブラリは読み取りアクセス甚にほがそのたたミラヌリングされたす。

このセクションでは、次を䜿甚するプロゞェクトを䜜成する方法を芋おいきたす。 libbpf、いく぀かの (倚かれ少なかれ意味のない) テスト プログラムを䜜成し、それがどのように機胜するかを詳现に分析しおみたしょう。 これにより、次のセクションで、BPF プログラムがマップ、カヌネル ヘルパヌ、BTF などずどのように察話するかを正確に説明するこずができたす。

通垞、次を䜿甚しおプロゞェクトを実行したす。 libbpf GitHub リポゞトリを git サブモゞュヌルずしお远加するには、同じこずを行いたす。

$ mkdir /tmp/libbpf-example
$ cd /tmp/libbpf-example/
$ git init-db
Initialized empty Git repository in /tmp/libbpf-example/.git/
$ git submodule add https://github.com/libbpf/libbpf.git
Cloning into '/tmp/libbpf-example/libbpf'...
remote: Enumerating objects: 200, done.
remote: Counting objects: 100% (200/200), done.
remote: Compressing objects: 100% (103/103), done.
remote: Total 3354 (delta 101), reused 118 (delta 79), pack-reused 3154
Receiving objects: 100% (3354/3354), 2.05 MiB | 10.22 MiB/s, done.
Resolving deltas: 100% (2176/2176), done.

に行く libbpf 非垞にシンプル

$ cd libbpf/src
$ mkdir build
$ OBJDIR=build DESTDIR=root make -s install
$ find root
root
root/usr
root/usr/include
root/usr/include/bpf
root/usr/include/bpf/bpf_tracing.h
root/usr/include/bpf/xsk.h
root/usr/include/bpf/libbpf_common.h
root/usr/include/bpf/bpf_endian.h
root/usr/include/bpf/bpf_helpers.h
root/usr/include/bpf/btf.h
root/usr/include/bpf/bpf_helper_defs.h
root/usr/include/bpf/bpf.h
root/usr/include/bpf/libbpf_util.h
root/usr/include/bpf/libbpf.h
root/usr/include/bpf/bpf_core_read.h
root/usr/lib64
root/usr/lib64/libbpf.so.0.1.0
root/usr/lib64/libbpf.so.0
root/usr/lib64/libbpf.a
root/usr/lib64/libbpf.so
root/usr/lib64/pkgconfig
root/usr/lib64/pkgconfig/libbpf.pc

このセクションの次の蚈画は次のずおりです。次のような BPF プログラムを䜜成したす。 BPF_PROG_TYPE_XDP、前の䟋ず同じですが、C では次のようにコンパむルしたす。 clangそしお、それをカヌネルにロヌドするヘルパヌ プログラムを䜜成したす。 次のセクションでは、BPF プログラムずアシスタント プログラムの䞡方の機胜を拡匵したす。

䟋: libbpf を䜿甚した本栌的なアプリケヌションの䜜成

たず、ファむルを䜿甚したす /sys/kernel/btf/vmlinux、これは䞊で説明したもので、同等のものをヘッダヌ ファむルの圢匏で䜜成したす。

$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

このファむルには、カヌネルで䜿甚できるすべおのデヌタ構造が保存されたす。たずえば、IPv4 ヘッダヌがカヌネルで定矩される方法は次のずおりです。

$ grep -A 12 'struct iphdr {' vmlinux.h
struct iphdr {
    __u8 ihl: 4;
    __u8 version: 4;
    __u8 tos;
    __be16 tot_len;
    __be16 id;
    __be16 frag_off;
    __u8 ttl;
    __u8 protocol;
    __sum16 check;
    __be32 saddr;
    __be32 daddr;
};

ここで、BPF プログラムを C で䜜成したす。

$ cat xdp-simple.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

SEC("xdp/simple")
int simple(void *ctx)
{
        return XDP_PASS;
}

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

私たちのプログラムは非垞に単玔であるこずが刀明したしたが、䟝然ずしお倚くの詳现に泚意を払う必芁がありたす。 たず、最初にむンクルヌドするヘッダヌ ファむルは次のずおりです。 vmlinux.hを䜿甚しお生成したばかりです bpftool btf dump - カヌネル構造がどのようなものかを知るために kernel-headers パッケヌゞをむンストヌルする必芁はなくなりたした。 次のヘッダヌ ファむルはラむブラリから提䟛されたす。 libbpf。 これで必芁なのはマクロを定矩するためだけです SEC、ELF オブゞェクト ファむルの適切なセクションに文字を送信したす。 私たちのプログラムはセクションに含たれおいたす xdp/simpleここで、スラッシュの前にプログラム タむプ BPF を定矩したす。これは、 libbpf、セクション名に基づいお、起動時に正しい型に眮き換えられたす。 bpf(2)。 BPF プログラム自䜓は、 C - 非垞にシンプルで XNUMX 行で構成されたす return XDP_PASS。 最埌に別セクションずしお、 "license" ラむセンスの名前が含たれたす。

llvm/clang、バヌゞョン >= 10.0.0、たたはそれ以䞊を䜿甚しおプログラムをコンパむルできたす (セクションを参照) 開発ツヌル):

$ clang --version
clang version 11.0.0 (https://github.com/llvm/llvm-project.git afc287e0abec710398465ee1f86237513f2b5091)
...

$ clang -O2 -g -c -target bpf -I libbpf/src/root/usr/include xdp-simple.bpf.c -o xdp-simple.bpf.o

興味深い機胜ずしおは、タヌゲット アヌキテクチャを瀺したす。 -target bpf ヘッダヌぞのパス libbpf、最近むンストヌルしたした。 たた、忘れないでください -O2, このオプションを䜿甚しないず、将来驚くような事態に遭遇する可胜性がありたす。 コヌドを芋おみたしょう。垌望通りのプログラムを曞くこずができたしたか?

$ llvm-objdump --section=xdp/simple --no-show-raw-insn -D xdp-simple.bpf.o

xdp-simple.bpf.o:       file format elf64-bpf

Disassembly of section xdp/simple:

0000000000000000 <simple>:
       0:       r0 = 2
       1:       exit

はい、うたくいきたした これでプログラムを含むバむナリ ファむルができたので、それをカヌネルにロヌドするアプリケヌションを䜜成したいず思いたす。 この目的のために、図曞通は libbpf には、䜎レベル API を䜿甚するか、高レベル API を䜿甚するかの XNUMX ぀のオプションがありたす。 私たちは、その埌の孊習のために最小限の劎力で BPF プログラムを䜜成、ロヌド、接続する方法を孊びたいので、XNUMX 番目の方法に進みたす。

たず、同じナヌティリティを䜿甚しお、バむナリからプログラムの「スケルトン」を生成する必芁がありたす。 bpftool — BPF 界のスむスナむフ (BPF の䜜成者および保守者の XNUMX 人であるダニ゚ル・ボルクマンはスむス人なので、文字通りに受け取るこずができたす):

$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h

ファむル内 xdp-simple.skel.h プログラムのバむナリ コヌドず、オブゞェクトのロヌド、アタッチ、削陀などを管理するための関数が含たれおいたす。 この単玔なケヌスでは、これはやりすぎのように芋えたすが、オブゞェクト ファむルに倚くの BPF プログラムずマップが含たれおいる堎合でも機胜し、この巚倧な ELF をロヌドするには、スケルトンを生成し、カスタム アプリケヌションから XNUMX ぀たたは XNUMX ぀の関数を呌び出すだけで枈みたす。曞いおいたす。さあ、先に進みたしょう。

厳密に蚀えば、私たちのロヌダヌ プログラムは簡単です。

#include <err.h>
#include <unistd.h>
#include "xdp-simple.skel.h"

int main(int argc, char **argv)
{
    struct xdp_simple_bpf *obj;

    obj = xdp_simple_bpf__open_and_load();
    if (!obj)
        err(1, "failed to open and/or load BPF objectn");

    pause();

    xdp_simple_bpf__destroy(obj);
}

それは struct xdp_simple_bpf ファむルで定矩されおいる xdp-simple.skel.h そしおオブゞェクト ファむルに぀いお説明したす。

struct xdp_simple_bpf {
    struct bpf_object_skeleton *skeleton;
    struct bpf_object *obj;
    struct {
        struct bpf_program *simple;
    } progs;
    struct {
        struct bpf_link *simple;
    } links;
};

ここで䜎レベル API の痕跡を芋るこずができたす: 構造 struct bpf_program *simple О struct bpf_link *simple。 最初の構造は、セクションに曞かれおいるプログラムを具䜓的に説明しおいたす。 xdp/simpleXNUMX 番目の郚分では、プログラムがむベント ゜ヌスにどのように接続するかを説明したす。

機胜 xdp_simple_bpf__open_and_load、ELF オブゞェクトを開いお解析し、すべおの構造ずサブ構造 (プログラムの他に、ELF にはデヌタ、読み取り専甚デヌタ、デバッグ情報、ラむセンスなどの他のセクションも含たれたす) を䜜成し、システムを䜿甚しおそれをカヌネルにロヌドしたす。電話 bpfこれは、プログラムをコンパむルしお実行するこずで確認できたす。

$ clang -O2 -I ./libbpf/src/root/usr/include/ xdp-simple.c -o xdp-simple ./libbpf/src/root/usr/lib64/libbpf.a -lelf -lz

$ sudo strace -e bpf ./xdp-simple
...
bpf(BPF_BTF_LOAD, 0x7ffdb8fd9670, 120)  = 3
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, insn_cnt=2, insns=0xdfd580, license="GPL", log_level=0, log_size=0, log_buf=NULL, kern_version=KERNEL_VERSION(5, 8, 0), prog_flags=0, prog_name="simple", prog_ifindex=0, expected_attach_type=0x25 /* BPF_??? */, ...}, 120) = 4

では、次を䜿甚しおプログラムを芋おみたしょう。 bpftool。 圌女の ID を芋぀けおみたしょう:

# bpftool p | grep -A4 simple
463: xdp  name simple  tag 3b185187f1855c4c  gpl
        loaded_at 2020-08-01T01:59:49+0000  uid 0
        xlated 16B  jited 40B  memlock 4096B
        btf_id 185
        pids xdp-simple(16498)

および dump (コマンドの短瞮圢を䜿甚したす) bpftool prog dump xlated):

# bpftool p d x id 463
int simple(void *ctx):
; return XDP_PASS;
   0: (b7) r0 = 2
   1: (95) exit

新しい䜕か プログラムは C ゜ヌス ファむルのチャンクを出力したした。これはラむブラリによっお行われたした。 libbpf、バむナリ内でデバッグ セクションを芋぀け、それを BTF オブゞェクトにコンパむルし、次を䜿甚しおカヌネルにロヌドしたした。 BPF_BTF_LOAD、そしお、コマンドでプログラムをロヌドするずきに、結果のファむル蚘述子を指定したす。 BPG_PROG_LOAD.

カヌネルヘルパヌ

BPF プログラムは、「倖郚」関数、぀たりカヌネル ヘルパヌを実行できたす。 これらのヘルパヌ関数により、BPF プログラムはカヌネル構造にアクセスし、マップを管理し、パフォヌマンス むベントの䜜成、ハヌドりェアの制埡 (パケットのリダむレクトなど) などの「珟実䞖界」ずの通信も行うこずができたす。

䟋: bpf_get_smp_processor_id

「䟋による孊習」パラダむムの枠組みの䞭で、ヘルパヌ関数の XNUMX ぀を考えおみたしょう。 bpf_get_smp_processor_id(), ある ファむル内 kernel/bpf/helpers.c。 これは、呌び出し元の BPF プログラムが実行されおいるプロセッサの番号を返したす。 しかし、私たちはそのセマンティクスにはあたり興味がなく、実装が XNUMX 行で枈むずいう事実に興味がありたす。

BPF_CALL_0(bpf_get_smp_processor_id)
{
    return smp_processor_id();
}

BPF ヘルパヌ関数の定矩は、Linux システム コヌルの定矩に䌌おいたす。 ここでは、たずえば、匕数のない関数が定矩されおいたす。 (たずえば、XNUMX ぀の匕数を取る関数は、マクロを䜿甚しお定矩されたす。 BPF_CALL_3。 匕数の最倧数は XNUMX です。) ただし、これは定矩の最初の郚分にすぎたせん。 XNUMX 番目の郚分は型構造を定矩するこずです struct bpf_func_protoこれには、怜蚌者が理解できるヘルパヌ関数の説明が含たれおいたす。

const struct bpf_func_proto bpf_get_smp_processor_id_proto = {
    .func     = bpf_get_smp_processor_id,
    .gpl_only = false,
    .ret_type = RET_INTEGER,
};

ヘルパヌ関数の登録

特定のタむプの BPF プログラムがこの関数を䜿甚するには、この関数を登録する必芁がありたす。たずえば、次のタむプの BPF プログラムです。 BPF_PROG_TYPE_XDP 関数はカヌネルで定矩されおいたす xdp_func_proto、ヘルパヌ関数 ID から、XDP がこの関数をサポヌトするかどうかを決定したす。 私たちの圹割は サポヌトする:

static const struct bpf_func_proto *
xdp_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
{
    switch (func_id) {
    ...
    case BPF_FUNC_get_smp_processor_id:
        return &bpf_get_smp_processor_id_proto;
    ...
    }
}

新しい BPF プログラム タむプはファむル内で「定矩」されたす include/linux/bpf_types.h マクロを䜿甚する BPF_PROG_TYPE。 これは論理的な定矩であるため匕甚笊で囲たれおいたす。C 蚀語の甚語では、具䜓的な構造のセット党䜓の定矩は他の堎所で行われたす。 特にファむル内では、 kernel/bpf/verifier.c ファむルからのすべおの定矩 bpf_types.h 構造䜓の配列を䜜成するために䜿甚されたす bpf_verifier_ops[]:

static const struct bpf_verifier_ops *const bpf_verifier_ops[] = {
#define BPF_PROG_TYPE(_id, _name, prog_ctx_type, kern_ctx_type) 
    [_id] = & _name ## _verifier_ops,
#include <linux/bpf_types.h>
#undef BPF_PROG_TYPE
};

぀たり、BPF プログラムのタむプごずに、そのタむプのデヌタ構造ぞのポむンタが定矩されたす。 struct bpf_verifier_ops、倀で初期化されたす _name ## _verifier_ops、぀たり、 xdp_verifier_ops のために xdp。 構造 xdp_verifier_ops によっお決定 ファむル内 net/core/filter.c 次のようにしたす。

const struct bpf_verifier_ops xdp_verifier_ops = {
    .get_func_proto     = xdp_func_proto,
    .is_valid_access    = xdp_is_valid_access,
    .convert_ctx_access = xdp_convert_ctx_access,
    .gen_prologue       = bpf_noop_prologue,
};

ここではおなじみの関数を芋おみたしょう xdp_func_proto、チャレンゞが発生するたびにベリファむアを実行したす。 いく぀か BPF プログラム内の関数に぀いおは、を参照しおください。 verifier.c.

仮説的な BPF プログラムがこの関数をどのように䜿甚するかを芋おみたしょう。 bpf_get_smp_processor_id。 これを行うには、前のセクションのプログラムを次のように曞き盎したす。

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

SEC("xdp/simple")
int simple(void *ctx)
{
    if (bpf_get_smp_processor_id() != 0)
        return XDP_DROP;
    return XDP_PASS;
}

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

シンボル bpf_get_smp_processor_id によっお決定 в <bpf/bpf_helper_defs.h> ラむブラリヌ libbpf 方法

static u32 (*bpf_get_smp_processor_id)(void) = (void *) 8;

あれは、 bpf_get_smp_processor_id 倀が 8 である関数ポむンタです。8 は倀です。 BPF_FUNC_get_smp_processor_id тОпа enum bpf_fun_id、ファむル内で定矩されおいたす vmlinux.h ファむル bpf_helper_defs.h カヌネル内の数倀はスクリプトによっお生成されるため、「マゞック」数倀は問題ありたせん)。 この関数は匕数をずらず、次の型の倀を返したす。 __u32。 プログラムで実行するず、 clang 呜什を生成したす BPF_CALL 「正しい皮類」 プログラムをコンパむルしおセクションを芋おみたしょう xdp/simple:

$ clang -O2 -g -c -target bpf -I libbpf/src/root/usr/include xdp-simple.bpf.c -o xdp-simple.bpf.o
$ llvm-objdump -D --section=xdp/simple xdp-simple.bpf.o

xdp-simple.bpf.o:       file format elf64-bpf

Disassembly of section xdp/simple:

0000000000000000 <simple>:
       0:       85 00 00 00 08 00 00 00 call 8
       1:       bf 01 00 00 00 00 00 00 r1 = r0
       2:       67 01 00 00 20 00 00 00 r1 <<= 32
       3:       77 01 00 00 20 00 00 00 r1 >>= 32
       4:       b7 00 00 00 02 00 00 00 r0 = 2
       5:       15 01 01 00 00 00 00 00 if r1 == 0 goto +1 <LBB0_2>
       6:       b7 00 00 00 01 00 00 00 r0 = 1

0000000000000038 <LBB0_2>:
       7:       95 00 00 00 00 00 00 00 exit

最初の行には指瀺が衚瀺されたす call、パラメヌタ IMM これは 8 に等しく、 SRC_REG - れロ。 怜蚌者が䜿甚する ABI 芏玄によれば、これはヘルパヌ関数 XNUMX 番ぞの呌び出しです。 起動したら、ロゞックは簡単です。 レゞスタからの戻り倀 r0 にコピヌされたした r1 2,3 行目で type に倉換されたす。 u32 — 䞊䜍 32 ビットがクリアされたす。 4,5,6,7、2、XNUMX、XNUMX 行目では XNUMX (XDP_PASS) たたは 1 (XDP_DROP) 行 0 のヘルパヌ関数がれロたたはれロ以倖の倀を返したかどうかに応じお異なりたす。

自分自身をテストしおみたしょう: プログラムをロヌドしお出力を芋おみたしょう bpftool prog dump xlated:

$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h
$ clang -O2 -g -I ./libbpf/src/root/usr/include/ -o xdp-simple xdp-simple.c ./libbpf/src/root/usr/lib64/libbpf.a -lelf -lz
$ sudo ./xdp-simple &
[2] 10914

$ sudo bpftool p | grep simple
523: xdp  name simple  tag 44c38a10c657e1b0  gpl
        pids xdp-simple(10915)

$ sudo bpftool p d x id 523
int simple(void *ctx):
; if (bpf_get_smp_processor_id() != 0)
   0: (85) call bpf_get_smp_processor_id#114128
   1: (bf) r1 = r0
   2: (67) r1 <<= 32
   3: (77) r1 >>= 32
   4: (b7) r0 = 2
; }
   5: (15) if r1 == 0x0 goto pc+1
   6: (b7) r0 = 1
   7: (95) exit

OK、怜蚌者は正しいカヌネルヘルパヌを芋぀けたした。

䟋: 匕数を枡しお、最埌にプログラムを実行したす。

すべおの実行レベルのヘルパヌ関数にはプロトタむプがありたす

u64 fn(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)

ヘルパヌ関数ぞのパラメヌタはレゞスタで枡されたす r1 - r5、倀がレゞスタに返されたす r0。 XNUMX ぀を超える匕数を取る関数はなく、それらのサポヌトは将来远加される予定はありたせん。

新しいカヌネル ヘルパヌず、BPF がパラメヌタヌを枡す方法を芋おみたしょう。 曞き盎したしょう xdp-simple.bpf.c 以䞋のようになりたす (残りの行は倉曎されおいたせん)。

SEC("xdp/simple")
int simple(void *ctx)
{
    bpf_printk("running on CPU%un", bpf_get_smp_processor_id());
    return XDP_PASS;
}

私たちのプログラムは、実行されおいる CPU の番号を出力したす。 コンパむルしおコヌドを芋おみたしょう。

$ llvm-objdump -D --section=xdp/simple --no-show-raw-insn xdp-simple.bpf.o

0000000000000000 <simple>:
       0:       r1 = 10
       1:       *(u16 *)(r10 - 8) = r1
       2:       r1 = 8441246879787806319 ll
       4:       *(u64 *)(r10 - 16) = r1
       5:       r1 = 2334956330918245746 ll
       7:       *(u64 *)(r10 - 24) = r1
       8:       call 8
       9:       r1 = r10
      10:       r1 += -24
      11:       r2 = 18
      12:       r3 = r0
      13:       call 6
      14:       r0 = 2
      15:       exit

0行目から7行目たでに文字列を曞きたす。 running on CPU%un次に、8 行目でおなじみのものを実行したす。 bpf_get_smp_processor_id。 9行目から12行目でヘルパヌ匕数を準備したす。 bpf_printk - レゞスタヌ r1, r2, r3。 なぜ XNUMX ぀ではなく XNUMX ぀あるのでしょうか? なぜなら bpf_printk -  ã“れはマクロラッパヌです 本圓のヘルパヌの呚り bpf_trace_printk、フォヌマット文字列のサむズを枡す必芁がありたす。

ここでいく぀かの行を远加したしょう xdp-simple.cプログラムがむンタヌフェヌスに接続できるようにする lo そしお本圓に始たりたした

$ cat xdp-simple.c
#include <linux/if_link.h>
#include <err.h>
#include <unistd.h>
#include "xdp-simple.skel.h"

int main(int argc, char **argv)
{
    __u32 flags = XDP_FLAGS_SKB_MODE;
    struct xdp_simple_bpf *obj;

    obj = xdp_simple_bpf__open_and_load();
    if (!obj)
        err(1, "failed to open and/or load BPF objectn");

    bpf_set_link_xdp_fd(1, -1, flags);
    bpf_set_link_xdp_fd(1, bpf_program__fd(obj->progs.simple), flags);

cleanup:
    xdp_simple_bpf__destroy(obj);
}

ここで関数を䜿甚したす bpf_set_link_xdp_fd、XDP タむプの BPF プログラムをネットワヌク むンタヌフェむスに接続したす。 むンタヌフェヌス番号をハヌドコヌディングしたした lo垞に 1 です。関数を XNUMX 回実行しお、叀いプログラムがアタッチされおいる堎合は最初にそれをデタッチしたす。 今は挑戊する必芁がないこずに泚意しおください pause たたは無限ルヌプ: ロヌダヌ プログラムは終了したすが、BPF プログラムはむベント ゜ヌスに接続されおいるため匷制終了されたせん。 ダりンロヌドず接続が成功するず、ネットワヌク パケットが到着するたびにプログラムが起動されたす。 lo.

プログラムをダりンロヌドしおむンタヌフェヌスを芋おみたしょう lo:

$ sudo ./xdp-simple
$ sudo bpftool p | grep simple
669: xdp  name simple  tag 4fca62e77ccb43d6  gpl
$ ip l show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    prog/xdp id 669

ダりンロヌドしたプログラムの ID は 669 で、むンタヌフェむス䞊でも同じ ID が衚瀺されたす。 lo。 いく぀かの荷物をに送りたす 127.0.0.1 (リク゚スト + 返信):

$ ping -c1 localhost

次に、デバッグ仮想ファむルの内容を芋おみたしょう /sys/kernel/debug/tracing/trace_pipe、 その䞭で bpf_printk 圌のメッセヌゞは次のように曞かれおいたす。

# cat /sys/kernel/debug/tracing/trace_pipe
ping-13937 [000] d.s1 442015.377014: bpf_trace_printk: running on CPU0
ping-13937 [000] d.s1 442015.377027: bpf_trace_printk: running on CPU0

XNUMX぀のパッケヌゞが芋぀かりたした lo CPU0 で凊理されたした。初めおの本栌的な意味のない BPF プログラムが機胜したした。

泚目に倀する bpf_printk デバッグ ファむルに曞き蟌むのは圓然のこずです。これは運甚環境で䜿甚するのに最も成功したヘルパヌではありたせんが、私たちの目暙は単玔なものを瀺すこずでした。

BPF プログラムからマップにアクセスする

䟋: BPF プログラムのマップの䜿甚

前のセクションでは、ナヌザヌ空間からマップを䜜成しお䜿甚する方法を孊びたした。次に、カヌネル郚分を芋おみたしょう。 い぀ものように、䟋から始めたしょう。 プログラムを曞き盎しおみたしょう xdp-simple.bpf.c 次のようにしたす。

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

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __uint(max_entries, 8);
    __type(key, u32);
    __type(value, u64);
} woo SEC(".maps");

SEC("xdp/simple")
int simple(void *ctx)
{
    u32 key = bpf_get_smp_processor_id();
    u32 *val;

    val = bpf_map_lookup_elem(&woo, &key);
    if (!val)
        return XDP_ABORTED;

    *val += 1;

    return XDP_PASS;
}

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

プログラムの最初にマップ定矩を远加したした。 woo: これは、次のような倀を栌玍する 8 芁玠の配列です。 u64 (C では、このような配列を次のように定矩したす。 u64 woo[8]。 番組内で "xdp/simple" 珟圚のプロセッサ番号を倉数に取埗したす。 key そしおヘルパヌ関数を䜿甚したす bpf_map_lookup_element 配列内の察応する゚ントリぞのポむンタを取埗し、XNUMX ず぀増やしたす。 ロシア語に翻蚳するず、どの CPU が受信パケットを凊理したかに関する統蚈を蚈算したす。 プログラムを実行しおみたしょう。

$ clang -O2 -g -c -target bpf -I libbpf/src/root/usr/include xdp-simple.bpf.c -o xdp-simple.bpf.o
$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h
$ clang -O2 -g -I ./libbpf/src/root/usr/include/ -o xdp-simple xdp-simple.c ./libbpf/src/root/usr/lib64/libbpf.a -lelf -lz
$ sudo ./xdp-simple

圌女が぀ながっおいるこずを確認したしょう lo そしおいく぀かのパケットを送信したす。

$ ip l show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    prog/xdp id 108

$ for s in `seq 234`; do sudo ping -f -c 100 127.0.0.1 >/dev/null 2>&1; done

次に、配列の内容を芋おみたしょう。

$ sudo bpftool map dump name woo
[
    { "key": 0, "value": 0 },
    { "key": 1, "value": 400 },
    { "key": 2, "value": 0 },
    { "key": 3, "value": 0 },
    { "key": 4, "value": 0 },
    { "key": 5, "value": 0 },
    { "key": 6, "value": 0 },
    { "key": 7, "value": 46400 }
]

ほがすべおのプロセスがCPU7で凊理されたした。 これは私たちにずっお重芁ではありたせん。重芁なこずは、プログラムが動䜜し、BPF プログラムからマップにアクセスする方法を理解しおいるこずです。 хелперПв bpf_mp_*.

神秘のむンデックス

したがっお、次のような呌び出しを䜿甚しお、BPF プログラムからマップにアクセスできたす。

val = bpf_map_lookup_elem(&woo, &key);

ヘルパヌ関数は次のようになりたす

void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)

しかし、私たちはポむンタを枡しおいたす &woo 名前のない構造物に struct { ... }...

プログラムのアセンブラを芋るず、倀が &woo 実際には定矩されおいたせん (行 4):

llvm-objdump -D --section xdp/simple xdp-simple.bpf.o

xdp-simple.bpf.o:       file format elf64-bpf

Disassembly of section xdp/simple:

0000000000000000 <simple>:
       0:       85 00 00 00 08 00 00 00 call 8
       1:       63 0a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r0
       2:       bf a2 00 00 00 00 00 00 r2 = r10
       3:       07 02 00 00 fc ff ff ff r2 += -4
       4:       18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll
       6:       85 00 00 00 01 00 00 00 call 1
...

これは再配眮に含たれおいたす。

$ llvm-readelf -r xdp-simple.bpf.o | head -4

Relocation section '.relxdp/simple' at offset 0xe18 contains 1 entries:
    Offset             Info             Type               Symbol's Value  Symbol's Name
0000000000000020  0000002700000001 R_BPF_64_64            0000000000000000 woo

しかし、すでにロヌドされおいるプログラムを芋るず、正しいマップぞのポむンタが衚瀺されたす (4 行目)。

$ sudo bpftool prog dump x name simple
int simple(void *ctx):
   0: (85) call bpf_get_smp_processor_id#114128
   1: (63) *(u32 *)(r10 -4) = r0
   2: (bf) r2 = r10
   3: (07) r2 += -4
   4: (18) r1 = map[id:64]
...

したがっお、ロヌダヌ プログラムの起動時に、ぞのリンクが &woo ラむブラリのあるものに眮き換えられたした libbpf。 たず出力を芋おみたしょう strace:

$ sudo strace -e bpf ./xdp-simple
...
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_ARRAY, key_size=4, value_size=8, max_entries=8, map_name="woo", ...}, 120) = 4
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, prog_name="simple", ...}, 120) = 5

それがわかりたす libbpf 地図を䜜成したした woo そしおプログラムをダりンロヌドしたした simple。 プログラムをロヌドする方法を詳しく芋おみたしょう。

  • 電話 xdp_simple_bpf__open_and_load ファむルから xdp-simple.skel.h
  • その原因 xdp_simple_bpf__load ファむルから xdp-simple.skel.h
  • その原因 bpf_object__load_skeleton ファむルから libbpf/src/libbpf.c
  • その原因 bpf_object__load_xattr の libbpf/src/libbpf.c

最埌の関数は、特に次の関数を呌び出したす。 bpf_object__create_maps、既存のマップを䜜成たたは開き、マップをファむル蚘述子に倉換したす。 (ここで私たちが芋るのは BPF_MAP_CREATE 出力内で strace.) 次に関数が呌び出されたす。 bpf_object__relocate そしお私たちが興味を持っおいるのは圌女です、なぜなら私たちは芋たものを芚えおいるからです woo 再配眮テヌブルにありたす。 それを探玢するず、最終的に関数にたどり着きたす。 bpf_program__relocate、どれず マップの再配眮を扱う:

case RELO_LD64:
    insn[0].src_reg = BPF_PSEUDO_MAP_FD;
    insn[0].imm = obj->maps[relo->map_idx].fd;
    break;

それで私たちは私たちの指瀺に埓いたす

18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll

その䞭の゜ヌスレゞスタを次のように眮き換えたす BPF_PSEUDO_MAP_FD、最初の IMM をマップのファむル蚘述子に远加し、それが次の堎合ず等しいかどうかを確認したす。 0xdeadbeef、その結果、指瀺を受け取りたす

18 11 00 00 ef eb ad de 00 00 00 00 00 00 00 00 r1 = 0 ll

これは、マップ情報がロヌドされた特定の BPF プログラムに転送される方法です。 この堎合、マップは次のように䜜成できたす。 BPF_MAP_CREATEを䜿甚しお ID によっお開かれたす。 BPF_MAP_GET_FD_BY_ID.

合蚈、䜿甚時 libbpf アルゎリズムは次のずおりです。

  • コンパむル䞭に、マップぞのリンクのレコヌドが再配眮テヌブルに䜜成されたす。
  • libbpf ELF オブゞェクト ブックを開き、䜿甚されおいるすべおのマップを怜玢し、それらのファむル蚘述子を䜜成したす。
  • ファむル蚘述子は呜什の䞀郚ずしおカヌネルにロヌドされたす。 LD64

ご想像のずおり、今埌はさらに倚くのこずがあり、その栞心を調べる必芁がありたす。 幞いなこずに、私たちは手がかりを持っおいたす - 私たちは意味を曞き留めたした BPF_PSEUDO_MAP_FD それを゜ヌスレゞスタヌに埋め蟌むず、すべおの聖人の聖地に぀ながるでしょう - kernel/bpf/verifier.c、固有の名前を持぀関数は、ファむル蚘述子を次のタむプの構造䜓のアドレスに眮き換えたす。 struct bpf_map:

static int replace_map_fd_with_map_ptr(struct bpf_verifier_env *env) {
    ...

    f = fdget(insn[0].imm);
    map = __bpf_map_get(f);
    if (insn->src_reg == BPF_PSEUDO_MAP_FD) {
        addr = (unsigned long)map;
    }
    insn[0].imm = (u32)addr;
    insn[1].imm = addr >> 32;

(完党なコヌドが芋぀かりたす リンク。 したがっお、アルゎリズムを拡匵できたす。

  • プログラムのロヌド䞭に、怜蚌者はマップの正しい䜿甚法をチェックし、察応する構造䜓のアドレスを曞き蟌みたす。 struct bpf_map

を䜿甚しお ELF バむナリをダりンロヌドする堎合 libbpf 他にもたくさんのこずが起こっおいたすが、それに぀いおは別の蚘事で説明したす。

libbpf を䜿甚しないプログラムずマップのロヌド

玄束どおり、マップを䜿甚するプログラムを助けなしで䜜成しおロヌドする方法を知りたい読者のための䟋をここに瀺したす。 libbpf。 これは、䟝存関係を構築できない環境で䜜業しおいる堎合、すべおのビットを保存する堎合、たたは次のようなプログラムを䜜成する堎合に䟿利です。 ply、BPF バむナリ コヌドをオンザフラむで生成したす。

ロゞックを理解しやすくするために、次の目的のために䟋を曞き盎したす。 xdp-simple。 この䟋で説明したプログラムの完党な、わずかに拡匵されたコヌドは、次の堎所にありたす。 芁旚.

アプリケヌションのロゞックは次のずおりです。

  • タむプマップを䜜成する BPF_MAP_TYPE_ARRAY コマンドを䜿甚しお BPF_MAP_CREATE,
  • このマップを䜿甚するプログラムを䜜成し、
  • プログラムをむンタヌフェヌスに接続する lo,

これを人間に蚳すず、

int main(void)
{
    int map_fd, prog_fd;

    map_fd = map_create();
    if (map_fd < 0)
        err(1, "bpf: BPF_MAP_CREATE");

    prog_fd = prog_load(map_fd);
    if (prog_fd < 0)
        err(1, "bpf: BPF_PROG_LOAD");

    xdp_attach(1, prog_fd);
}

それは map_create システムコヌルに関する最初の䟋で行ったのず同じ方法でマップを䜜成したす。 bpf - 「カヌネル、次のような 8 芁玠の配列の圢匏で新しいマップを䜜成しおください」 __u64 ファむル蚘述子を返しおください":

static int map_create()
{
    union bpf_attr attr;

    memset(&attr, 0, sizeof(attr));
    attr.map_type = BPF_MAP_TYPE_ARRAY,
    attr.key_size = sizeof(__u32),
    attr.value_size = sizeof(__u64),
    attr.max_entries = 8,
    strncpy(attr.map_name, "woo", sizeof(attr.map_name));
    return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
}

プログラムのロヌドも簡単です。

static int prog_load(int map_fd)
{
    union bpf_attr attr;
    struct bpf_insn insns[] = {
        ...
    };

    memset(&attr, 0, sizeof(attr));
    attr.prog_type = BPF_PROG_TYPE_XDP;
    attr.insns     = ptr_to_u64(insns);
    attr.insn_cnt  = sizeof(insns)/sizeof(insns[0]);
    attr.license   = ptr_to_u64("GPL");
    strncpy(attr.prog_name, "woo", sizeof(attr.prog_name));
    return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
}

難しい郚分 prog_load 構造䜓の配列ずしおの BPF プログラムの定矩です。 struct bpf_insn insns[]。 ただし、C で䜜成したプログラムを䜿甚しおいるため、少しチヌトするこずができたす。

$ llvm-objdump -D --section xdp/simple xdp-simple.bpf.o

0000000000000000 <simple>:
       0:       85 00 00 00 08 00 00 00 call 8
       1:       63 0a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r0
       2:       bf a2 00 00 00 00 00 00 r2 = r10
       3:       07 02 00 00 fc ff ff ff r2 += -4
       4:       18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll
       6:       85 00 00 00 01 00 00 00 call 1
       7:       b7 01 00 00 00 00 00 00 r1 = 0
       8:       15 00 04 00 00 00 00 00 if r0 == 0 goto +4 <LBB0_2>
       9:       61 01 00 00 00 00 00 00 r1 = *(u32 *)(r0 + 0)
      10:       07 01 00 00 01 00 00 00 r1 += 1
      11:       63 10 00 00 00 00 00 00 *(u32 *)(r0 + 0) = r1
      12:       b7 01 00 00 02 00 00 00 r1 = 2

0000000000000068 <LBB0_2>:
      13:       bf 10 00 00 00 00 00 00 r0 = r1
      14:       95 00 00 00 00 00 00 00 exit

合蚈 14 個の呜什を次のような構造の圢匏で蚘述する必芁がありたす。 struct bpf_insn (アドバむス 䞊蚘のダンプを取埗し、手順セクションをもう䞀床読み、開きたす linux/bpf.h О linux/bpf_common.h そしお刀断しようずする struct bpf_insn insns[] 自分で):

struct bpf_insn insns[] = {
    /* 85 00 00 00 08 00 00 00 call 8 */
    {
        .code = BPF_JMP | BPF_CALL,
        .imm = 8,
    },

    /* 63 0a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r0 */
    {
        .code = BPF_MEM | BPF_STX,
        .off = -4,
        .src_reg = BPF_REG_0,
        .dst_reg = BPF_REG_10,
    },

    /* bf a2 00 00 00 00 00 00 r2 = r10 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_X,
        .src_reg = BPF_REG_10,
        .dst_reg = BPF_REG_2,
    },

    /* 07 02 00 00 fc ff ff ff r2 += -4 */
    {
        .code = BPF_ALU64 | BPF_ADD | BPF_K,
        .dst_reg = BPF_REG_2,
        .imm = -4,
    },

    /* 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll */
    {
        .code = BPF_LD | BPF_DW | BPF_IMM,
        .src_reg = BPF_PSEUDO_MAP_FD,
        .dst_reg = BPF_REG_1,
        .imm = map_fd,
    },
    { }, /* placeholder */

    /* 85 00 00 00 01 00 00 00 call 1 */
    {
        .code = BPF_JMP | BPF_CALL,
        .imm = 1,
    },

    /* b7 01 00 00 00 00 00 00 r1 = 0 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_K,
        .dst_reg = BPF_REG_1,
        .imm = 0,
    },

    /* 15 00 04 00 00 00 00 00 if r0 == 0 goto +4 <LBB0_2> */
    {
        .code = BPF_JMP | BPF_JEQ | BPF_K,
        .off = 4,
        .src_reg = BPF_REG_0,
        .imm = 0,
    },

    /* 61 01 00 00 00 00 00 00 r1 = *(u32 *)(r0 + 0) */
    {
        .code = BPF_MEM | BPF_LDX,
        .off = 0,
        .src_reg = BPF_REG_0,
        .dst_reg = BPF_REG_1,
    },

    /* 07 01 00 00 01 00 00 00 r1 += 1 */
    {
        .code = BPF_ALU64 | BPF_ADD | BPF_K,
        .dst_reg = BPF_REG_1,
        .imm = 1,
    },

    /* 63 10 00 00 00 00 00 00 *(u32 *)(r0 + 0) = r1 */
    {
        .code = BPF_MEM | BPF_STX,
        .src_reg = BPF_REG_1,
        .dst_reg = BPF_REG_0,
    },

    /* b7 01 00 00 02 00 00 00 r1 = 2 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_K,
        .dst_reg = BPF_REG_1,
        .imm = 2,
    },

    /* <LBB0_2>: bf 10 00 00 00 00 00 00 r0 = r1 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_X,
        .src_reg = BPF_REG_1,
        .dst_reg = BPF_REG_0,
    },

    /* 95 00 00 00 00 00 00 00 exit */
    {
        .code = BPF_JMP | BPF_EXIT
    },
};

これを自分で䜜成したわけではない人のための挔習 - 怜玢 map_fd.

私たちのプログラムにはもう XNUMX ぀未公開の郚分が残っおいたす - xdp_attach。 残念ながら、XDP のようなプログラムはシステム コヌルを䜿甚しお接続するこずはできたせん。 bpf。 BPF ず XDP を䜜成した人々はオンラむン Linux コミュニティの出身者でした。぀たり、圌らは最もよく知っおいるものを䜿甚しおいたした (ただし、そうではありたせん)。 普通の people) カヌネルず察話するためのむンタヌフェむス: ネットリンク゜ケット、こちらも参照 RFC3549。 最も簡単な実装方法 xdp_attach からコヌドをコピヌしおいたす libbpf、぀たりファむルから netlink.cこれを少し短くしお、次のようにしたした。

ネットリンク゜ケットの䞖界ぞようこそ

ネットリンク゜ケットタむプを開きたす NETLINK_ROUTE:

int netlink_open(__u32 *nl_pid)
{
    struct sockaddr_nl sa;
    socklen_t addrlen;
    int one = 1, ret;
    int sock;

    memset(&sa, 0, sizeof(sa));
    sa.nl_family = AF_NETLINK;

    sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (sock < 0)
        err(1, "socket");

    if (setsockopt(sock, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)) < 0)
        warnx("netlink error reporting not supported");

    if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0)
        err(1, "bind");

    addrlen = sizeof(sa);
    if (getsockname(sock, (struct sockaddr *)&sa, &addrlen) < 0)
        err(1, "getsockname");

    *nl_pid = sa.nl_pid;
    return sock;
}

この゜ケットから次の情報を読み取りたす。

static int bpf_netlink_recv(int sock, __u32 nl_pid, int seq)
{
    bool multipart = true;
    struct nlmsgerr *errm;
    struct nlmsghdr *nh;
    char buf[4096];
    int len, ret;

    while (multipart) {
        multipart = false;
        len = recv(sock, buf, sizeof(buf), 0);
        if (len < 0)
            err(1, "recv");

        if (len == 0)
            break;

        for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, len);
                nh = NLMSG_NEXT(nh, len)) {
            if (nh->nlmsg_pid != nl_pid)
                errx(1, "wrong pid");
            if (nh->nlmsg_seq != seq)
                errx(1, "INVSEQ");
            if (nh->nlmsg_flags & NLM_F_MULTI)
                multipart = true;
            switch (nh->nlmsg_type) {
                case NLMSG_ERROR:
                    errm = (struct nlmsgerr *)NLMSG_DATA(nh);
                    if (!errm->error)
                        continue;
                    ret = errm->error;
                    // libbpf_nla_dump_errormsg(nh); too many code to copy...
                    goto done;
                case NLMSG_DONE:
                    return 0;
                default:
                    break;
            }
        }
    }
    ret = 0;
done:
    return ret;
}

最埌に、゜ケットを開いお、ファむル蚘述子を含む特別なメッセヌゞを゜ケットに送信する関数を次に瀺したす。

static int xdp_attach(int ifindex, int prog_fd)
{
    int sock, seq = 0, ret;
    struct nlattr *nla, *nla_xdp;
    struct {
        struct nlmsghdr  nh;
        struct ifinfomsg ifinfo;
        char             attrbuf[64];
    } req;
    __u32 nl_pid = 0;

    sock = netlink_open(&nl_pid);
    if (sock < 0)
        return sock;

    memset(&req, 0, sizeof(req));
    req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
    req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
    req.nh.nlmsg_type = RTM_SETLINK;
    req.nh.nlmsg_pid = 0;
    req.nh.nlmsg_seq = ++seq;
    req.ifinfo.ifi_family = AF_UNSPEC;
    req.ifinfo.ifi_index = ifindex;

    /* started nested attribute for XDP */
    nla = (struct nlattr *)(((char *)&req)
            + NLMSG_ALIGN(req.nh.nlmsg_len));
    nla->nla_type = NLA_F_NESTED | IFLA_XDP;
    nla->nla_len = NLA_HDRLEN;

    /* add XDP fd */
    nla_xdp = (struct nlattr *)((char *)nla + nla->nla_len);
    nla_xdp->nla_type = IFLA_XDP_FD;
    nla_xdp->nla_len = NLA_HDRLEN + sizeof(int);
    memcpy((char *)nla_xdp + NLA_HDRLEN, &prog_fd, sizeof(prog_fd));
    nla->nla_len += nla_xdp->nla_len;

    /* if user passed in any flags, add those too */
    __u32 flags = XDP_FLAGS_SKB_MODE;
    nla_xdp = (struct nlattr *)((char *)nla + nla->nla_len);
    nla_xdp->nla_type = IFLA_XDP_FLAGS;
    nla_xdp->nla_len = NLA_HDRLEN + sizeof(flags);
    memcpy((char *)nla_xdp + NLA_HDRLEN, &flags, sizeof(flags));
    nla->nla_len += nla_xdp->nla_len;

    req.nh.nlmsg_len += NLA_ALIGN(nla->nla_len);

    if (send(sock, &req, req.nh.nlmsg_len, 0) < 0)
        err(1, "send");
    ret = bpf_netlink_recv(sock, nl_pid, seq);

cleanup:
    close(sock);
    return ret;
}

これで、すべおをテストする準備が敎いたした。

$ cc nolibbpf.c -o nolibbpf
$ sudo strace -e bpf ./nolibbpf
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_ARRAY, map_name="woo", ...}, 72) = 3
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, insn_cnt=15, prog_name="woo", ...}, 72) = 4
+++ exited with 0 +++

私たちのプログラムが接続したかどうかを芋おみたしょう lo:

$ ip l show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    prog/xdp id 160

ping を送信しお地図を芋おみたしょう。

$ for s in `seq 234`; do sudo ping -f -c 100 127.0.0.1 >/dev/null 2>&1; done
$ sudo bpftool m dump name woo
key: 00 00 00 00  value: 90 01 00 00 00 00 00 00
key: 01 00 00 00  value: 00 00 00 00 00 00 00 00
key: 02 00 00 00  value: 00 00 00 00 00 00 00 00
key: 03 00 00 00  value: 00 00 00 00 00 00 00 00
key: 04 00 00 00  value: 00 00 00 00 00 00 00 00
key: 05 00 00 00  value: 00 00 00 00 00 00 00 00
key: 06 00 00 00  value: 40 b5 00 00 00 00 00 00
key: 07 00 00 00  value: 00 00 00 00 00 00 00 00
Found 8 elements

䞇歳、すべおうたくいきたす。 ちなみに、マップは再びバむト圢匏で衚瀺されるこずに泚意しおください。 これは、ずは異なり、 libbpf 型情報 (BTF) をロヌドしたせんでした。 しかし、これに぀いおは次回に詳しく説明したす。

開発ツヌル

このセクションでは、最小限の BPF 開発者ツヌルキットに぀いお説明したす。

䞀般的に蚀えば、BPF プログラムを開発するために特別なものは䜕も必芁ありたせん。BPF は適切なディストリビュヌション カヌネル䞊で実行され、プログラムは以䞋を䜿甚しお構築されたす。 clang、パッケヌゞから䟛絊できたす。 ただし、BPF は開発䞭であるため、カヌネルずツヌルは垞に倉曎されおおり、2019 幎以降の昔ながらの方法を䜿甚しお BPF プログラムを䜜成したくない堎合は、コンパむルする必芁がありたす。

  • llvm/clang
  • pahole
  • その栞心
  • bpftool

(参考たでに、このセクションず蚘事内のすべおの䟋は Debian 10 で実行されたした。)

llvm/クラング

BPF は LLVM ず芪和性があり、最近では BPF 甚のプログラムは gcc を䜿甚しおコンパむルできるようになりたしたが、珟圚の開発はすべお LLVM に察しお行われおいたす。 したがっお、たず第䞀に、珟圚のバヌゞョンをビルドしたす clang git から:

$ sudo apt install ninja-build
$ git clone --depth 1 https://github.com/llvm/llvm-project.git
$ mkdir -p llvm-project/llvm/build/install
$ cd llvm-project/llvm/build
$ cmake .. -G "Ninja" -DLLVM_TARGETS_TO_BUILD="BPF;X86" 
                      -DLLVM_ENABLE_PROJECTS="clang" 
                      -DBUILD_SHARED_LIBS=OFF 
                      -DCMAKE_BUILD_TYPE=Release 
                      -DLLVM_BUILD_RUNTIME=OFF
$ time ninja
... ЌМПгП вреЌеМО спустя
$

これで、すべおが正しく結合されおいるかどうかを確認できたす。

$ ./bin/llc --version
LLVM (http://llvm.org/):
  LLVM version 11.0.0git
  Optimized build.
  Default target: x86_64-unknown-linux-gnu
  Host CPU: znver1

  Registered Targets:
    bpf    - BPF (host endian)
    bpfeb  - BPF (big endian)
    bpfel  - BPF (little endian)
    x86    - 32-bit X86: Pentium-Pro and above
    x86-64 - 64-bit X86: EM64T and AMD64

組立説明 clang 私がから取った bpf_devel_QA.)

構築したばかりのプログラムをむンストヌルするのではなく、単に远加するだけです。 PATHたずえば、次のようになりたす。

export PATH="`pwd`/bin:$PATH"

(これは远加できたす .bashrc たたは別のファむルに保存したす。 個人的にはこんなものを远加しおいたす ~/bin/activate-llvm.sh そしお必芁なずきはそうしたす . activate-llvm.sh.)

パホヌルずBTF

ナヌティリティ pahole カヌネルを構築するずきに䜿甚され、BTF 圢匏でデバッグ情報を䜜成したす。 BTF テクノロゞヌの詳现に぀いおは、䟿利なので䜿いたいずいう事実以倖、この蚘事では詳しく説明したせん。 したがっお、カヌネルをビルドする堎合は、最初にビルドしおください pahole なし pahole オプションを䜿甚しおカヌネルをビルドするこずはできたせん CONFIG_DEBUG_INFO_BTF:

$ git clone https://git.kernel.org/pub/scm/devel/pahole/pahole.git
$ cd pahole/
$ sudo apt install cmake
$ mkdir build
$ cd build/
$ cmake -D__LIB=lib ..
$ make
$ sudo make install
$ which pahole
/usr/local/bin/pahole

BPF を実隓するためのカヌネル

BPFの可胜性を探る䞊で、自分なりのコアを組み立おおいきたいず思っおいたす。 ディストリビュヌション カヌネル䞊で BPF プログラムをコンパむルしおロヌドできるため、これは䞀般的には必芁ありたせんが、独自のカヌネルを䜿甚するず、最新の BPF 機胜を䜿甚できるようになり、ディストリビュヌションに反映されるたでに最長で数か月かかりたす。たたは、䞀郚のデバッグ ツヌルの堎合ず同様に、予芋可胜な将来にはたったくパッケヌゞ化されなくなりたす。 たた、独自のコアにより、コヌドを詊しおみるこずが重芁であるず感じられたす。

カヌネルを構築するには、たずカヌネル自䜓が必芁で、次にカヌネル構成ファむルが必芁です。 BPF を実隓するには、通垞のメ゜ッドを䜿甚できたす。 バニラ カヌネルたたは開発カヌネルの XNUMX ぀。 歎史的に、BPF の開発は Linux ネットワヌキング コミュニティ内で行われおいるため、すべおの倉曎は遅かれ早かれ、Linux ネットワヌキング メンテナである David Miller を介しお行われたす。 線集たたは新機胜の性質に応じお、ネットワヌクの倉曎は XNUMX ぀のコアのいずれかに分類されたす。 net たたは net-next。 BPF の倉曎は、以䞋の間で同じ方法で配垃されたす。 bpf О bpf-next、その埌、それぞれ net ず net-next にプヌルされたす。 詳现に぀いおは、を参照しおください。 bpf_devel_QA О netdev-FAQ。 したがっお、奜みずテスト察象のシステムの安定性のニヌズに基づいおカヌネルを遞択しおください (*-next カヌネルはリストされおいるものの䞭で最も䞍安定です)。

カヌネル構成ファむルの管理方法に぀いお説明するこずは、この蚘事の範囲を超えおいたす。読者はその方法をすでに知っおいるか、たたはそのいずれかを前提ずしおいたす。 孊ぶ準備ができおいる 自分自身で。 ただし、次の手順は、BPF 察応システムを動䜜させるのに倚かれ少なかれ十分です。

䞊蚘のカヌネルのいずれかをダりンロヌドしたす。

$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git
$ cd bpf-next

最小限の動䜜カヌネル構成を構築したす。

$ cp /boot/config-`uname -r` .config
$ make localmodconfig

ファむル内の BPF オプションを有効にする .config あなた自身の遞択おそらく CONFIG_BPF systemd が䜿甚するため、すでに有効になっおいたす)。 この蚘事で䜿甚したカヌネルのオプションのリストは次のずおりです。

CONFIG_CGROUP_BPF=y
CONFIG_BPF=y
CONFIG_BPF_LSM=y
CONFIG_BPF_SYSCALL=y
CONFIG_ARCH_WANT_DEFAULT_BPF_JIT=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_JIT_DEFAULT_ON=y
CONFIG_IPV6_SEG6_BPF=y
# CONFIG_NETFILTER_XT_MATCH_BPF is not set
# CONFIG_BPFILTER is not set
CONFIG_NET_CLS_BPF=y
CONFIG_NET_ACT_BPF=y
CONFIG_BPF_JIT=y
CONFIG_BPF_STREAM_PARSER=y
CONFIG_LWTUNNEL_BPF=y
CONFIG_HAVE_EBPF_JIT=y
CONFIG_BPF_EVENTS=y
CONFIG_BPF_KPROBE_OVERRIDE=y
CONFIG_DEBUG_INFO_BTF=y

その埌、モゞュヌルずカヌネルを簡単に組み立おおむンストヌルできたす (ちなみに、新しく組み立おられたものを䜿甚しおカヌネルを組み立おるこずもできたす) clang远加するこずで CC=clang):

$ make -s -j $(getconf _NPROCESSORS_ONLN)
$ sudo make modules_install
$ sudo make install

そしお新しいカヌネルで再起動したす私はこれに䜿甚したす kexec パッケヌゞから kexec-tools):

v=5.8.0-rc6+ # еслО вы пересПбОраете текущее яЎрП, тП ЌПжМП Ўелать v=`uname -r`
sudo kexec -l -t bzImage /boot/vmlinuz-$v --initrd=/boot/initrd.img-$v --reuse-cmdline &&
sudo kexec -e

bpftool

この蚘事で最もよく䜿甚されるナヌティリティは次のずおりです。 bpftool、Linux カヌネルの䞀郚ずしお提䟛されたす。 これは、BPF 開発者によっお BPF 開発者のために䜜成および保守されおおり、プログラムのロヌド、マップの䜜成ず線集、BPF ゚コシステムの探玢など、あらゆるタむプの BPF オブゞェクトの管理に䜿甚できたす。 マニュアルペヌゞの゜ヌスコヌド圢匏のドキュメントが芋぀かりたす。 䞭心郚にある たたは、すでにコンパむルされおいる、 ネットワヌク.

この蚘事の執筆時点 bpftool RHEL、Fedora、および Ubuntu 甚にのみ既補で提䟛されおいたす (たずえば、を参照) このスレッドパッケヌゞングの未完の物語を語る bpftool Debian では)。 ただし、すでにカヌネルを構築しおいる堎合は、次のように構築したす。 bpftool パむのように簡単:

$ cd ${linux}/tools/bpf/bpftool
# ... прПпОшОте путО к пПслеЎМеЌу clang, как рассказаМП выше
$ make -s

Auto-detecting system features:
...                        libbfd: [ on  ]
...        disassembler-four-args: [ on  ]
...                          zlib: [ on  ]
...                        libcap: [ on  ]
...               clang-bpf-co-re: [ on  ]

Auto-detecting system features:
...                        libelf: [ on  ]
...                          zlib: [ on  ]
...                           bpf: [ on  ]

$

ここ ${linux} - これはカヌネル ディレクトリです。) これらのコマンドを実行した埌 bpftool ディレクトリに収集されたす ${linux}/tools/bpf/bpftool そしおそれをパスに远加できたすたずナヌザヌに远加したす root) たたは単にコピヌする /usr/local/sbin.

収集したす bpftool 埌者を䜿甚するのが最善です clang、䞊蚘のように組み立おられ、正しく組み立おられおいるかどうかを確認したす。たずえば、次のコマンドを䜿甚したす。

$ sudo bpftool feature probe kernel
Scanning system configuration...
bpf() syscall for unprivileged users is enabled
JIT compiler is enabled
JIT compiler hardening is disabled
JIT compiler kallsyms exports are enabled for root
...

これにより、カヌネルでどの BPF 機胜が有効になっおいるかが衚瀺されたす。

ちなみに、先ほどのコマンドは次のように実行できたす。

# bpftool f p k

これは、パッケヌゞのナヌティリティず同様に行われたす。 iproute2、ここで、たずえば次のように蚀えたす。 ip a s eth0 代わりに ip addr show dev eth0.

たずめ

BPF を䜿甚するず、ノミに靎を履かせお、コアの機胜を効果的に枬定し、その堎で倉曎するこずができたす。 このシステムは、UNIX の最良の䌝統に埓っお、非垞に成功したこずが刀明したした。カヌネルを (再) プログラムできるシンプルなメカニズムにより、膚倧な数の人々や組織が実隓するこずができたした。 たた、実隓や BPF むンフラストラクチャ自䜓の開発はただ完成には皋遠いものの、システムにはすでに安定した ABI が備わっおおり、信頌性が高く、最も重芁なこずに効果的なビゞネス ロゞックを構築できたす。

私の意芋では、このテクノロゞヌがこれほど普及したのは、䞀方では次のようなこずができるためです。 遊ぶ マシンのアヌキテクチャは䞀晩で倚かれ少なかれ理解できたす、䞀方で、その出珟前に矎しく解決できなかった問題を解決するこずもできたす。 これら XNUMX ぀のコンポヌネントを組み合わせるこずで、人々は実隓ず倢を芋るこずを匷いられ、それがたすたす革新的な゜リュヌションの出珟に぀ながりたす。

この蚘事は特に短いものではありたせんが、BPF の䞖界を玹介するだけであり、「高床な」機胜やアヌキテクチャの重芁な郚分に぀いおは説明したせん。 今埌の蚈画は次のようなものです。次の蚘事では BPF プログラム タむプの抂芁を説明し (5.8 カヌネルでは 30 のプログラム タむプがサポヌトされおいたす)、最埌にカヌネル トレヌス プログラムを䜿甚しお実際の BPF アプリケヌションを䜜成する方法を芋おいきたす。䟋ずしお、BPF アヌキテクチャに関するより詳现なコヌスを受講し、その埌に BPF ネットワヌキングずセキュリティ アプリケヌションの䟋を説明したす。

このシリヌズの以前の蚘事

  1. 小さな子䟛のための BPF、パヌト XNUMX: 叀兞的な BPF

リンク

  1. BPF および XDP リファレンス ガむド — 繊毛、より正確には、BPF の䜜成者および保守者の XNUMX 人である Daniel Borkman からの BPF に関する文曞。 これは最初の本栌的な蚘述の XNUMX ぀であり、ダニ゚ルは自分が䜕に぀いお曞いおいるのかを正確に理解しおおり、そこに間違いがないずいう点で他の蚘述ずは異なりたす。 特に、このドキュメントでは、よく知られたナヌティリティを䜿甚しお XDP および TC タむプの BPF プログラムを操䜜する方法に぀いお説明したす。 ip パッケヌゞから iproute2.

  2. ドキュメント/ネットワヌク/filter.txt — クラシックおよび拡匵 BPF のドキュメントを含むオリゞナル ファむル。 アセンブリ蚀語ず技術的なアヌキテクチャの詳现を詳しく知りたい堎合は、ぜひ読んでください。

  3. Facebook からの BPF に関するブログ。 Alexei Starovoitov (eBPF の䜜者) ず Andrii Nakryiko - (メンテナ) がそこに曞いおいるように、めったに曎新されたせんが、適切に曎新されたす。 libbpf).

  4. bpftool の秘密。 Quentin Monnet による楜しい Twitter スレッド。bpftool の䜿甚䟋ず秘密が蚘茉されおいたす。

  5. BPF の詳现: 読み物リスト。 Quentin Monnet による BPF ドキュメントぞのリンクの巚倧な (そしお珟圚も維持されおいる) リスト。

出所 habr.com

コメントを远加したす