XDPとAF_XDPによる高速パケット処理
カーネルスタックを通さずにライン速度でパケットを裁ける。XDPのフックとDROP/PASS/TX/REDIRECT、AF_XDPのゼロコピー、DPDKとの設計差まで、Linux高速I/Oの正体を原理から掴めます。
- 1.XDPはドライバ直後・skb割り当て前にeBPFを走らせるフックで、DROP/PASS/TX/REDIRECTの戻り値1つでパケットの運命を決める。DROPやTXはスタックを一切通らない。
- 2.AF_XDPはUMEM(共有メモリ)とFill/Completion/RX/TXの4リングで、NICのDMA先をユーザー空間ページに直結し、XDP_REDIRECTでコピーなしにパケットを渡す。
- 3.DPDKがカーネルを丸ごと迂回しNICを専有するのに対し、XDP/AF_XDPはカーネル内に留まりドライバ・eBPF検証器・既存スタックと共存する。専有か共存かが設計の分かれ目。
なぜ「ドライバ直後」で処理するのか
DDoS防御やロードバランサのように大量のパケットを高速に裁く用途では、1パケットあたりに固定でかかるコストが致命傷になります。ネットワークスタックのカーネル内データパスで見たとおり、受信パケットはまず struct sk_buff(skb)という制御構造に包まれ、GRO・IP・TCPと層を上っていきます。skbの確保・初期化だけでもパケット1個あたり数百ナノ秒級のコストがかかり、捨てるだけのパケットにこれを払うのは純粋な無駄です。
XDP(eXpress Data Path)は、この無駄を処理地点を前倒しすることで消します。NICドライバが受信したパケットを、skbを割り当てる前——ドライバのNAPI poll内、DMAで届いた生のバッファがまだRXリング上にある段階——でeBPFプログラムに渡すのです。ここで「捨てる」と決めれば、skbは一度も作られません。本記事は、このXDPのフックと4つのアクション、ゼロコピーを担うAF_XDP、そしてDPDKとの設計思想の違いを内部から解剖します。
XDPのフック位置とコンテキスト
XDPはeBPFのフックの一種で、検証器を通った安全なプログラムだけがアタッチされます。普通のeBPFと違うのは、渡されるコンテキストが豊かな sk_buff ではなく、ごく薄い struct xdp_buff(生パケットの先頭・末尾ポインタとメタデータ領域だけ)である点です。skbがまだ存在しないので、当然です。
実行モードは3段階あり、どこまで前倒しできるかが変わります。
| モード | 実行場所 | 前倒しの度合い | 条件 |
|---|---|---|---|
| ネイティブXDP | NICドライバのpoll内 | 高(skb割り当て前) | ドライバがXDP対応している必要 |
| オフロードXDP | NICハードウェア(SmartNIC) | 最高(CPUに届く前) | 対応NIC(例 一部のNetronome) |
| ジェネリックXDP | スタック内・skb割り当て後 | 低(後退した位置) | 未対応ドライバ向けフォールバック。本来の利点はほぼ消える |
本命はネイティブXDPです。ジェネリックモードはskbができた後に呼ばれるため、XDP_DROP でも結局skb確保コストを払っており、XDPの速度的旨味はほとんどありません。動作確認用のフォールバックと割り切るべきものです。
ベンチマークで「XDPなのに速くない」となる典型は、ドライバが未対応でジェネリックモードに落ちているケースです。ネイティブで動いているかは ip link の表示や bpftool net で確認できます。xdpgeneric と出ていたら、それはskb割り当て後の実行であり、XDPの設計上の利点を測れていません。
4つのアクション:DROP/PASS/TX/REDIRECT
XDPプログラムは戻り値(整数)1つでパケットの運命を決めます。これがXDPの表現力の核です。
XDP_DROP:即座に破棄。skbを作らずRXバッファを再利用リングへ戻すだけなので、最速の捨て方です。DDoS防御の本質はここにあり、不正パケットをライン速度で落としても正規トラフィックの処理に影響しません。XDP_PASS:通常のスタックへ流す。ここで初めてskbが割り当てられ、カーネル内データパスの通常経路に合流します。XDPで触らないパケットの既定の行き先です。XDP_TX:受信したNICからそのまま送り返す。パケット内容を書き換えてから返せば、ICMPの即応答や単純なリフレクタ・簡易ロードバランサが、スタックを一切通さず1枚のNIC内で完結します。XDP_REDIRECT:別のNIC、別のCPU、あるいはAF_XDPソケットへパケットを横流しする。後述のゼロコピー・ユーザー空間処理の入口がこれです。リダイレクト先はeBPFマップ(devmap・cpumap・xskmap)で指定します。
NIC DMA → RXリング → ドライバpoll → [XDPプログラム]
│
┌──────────────┬──────────────┼───────────────┐
XDP_DROP XDP_PASS XDP_TX XDP_REDIRECT
破棄(再利用) skb作成→ 同NICへ devmap/cpumap/
通常スタック 送り返す xskmap経由で横流し
XDPでヘッダを足す(例 VXLANカプセル化)には、bpf_xdp_adjust_head() でパケット先頭を前へずらします。これはskbの skb_push と同じ発想で、ドライバが受信バッファ確保時にあらかじめ空けておいたヘッドルームを使います。ヘッドルームが足りない拡張は検証器・実行時に弾かれます。データパスでヘッダ操作がポインタ移動で済む原理は、skbの場合と共通です。
AF_XDP:ゼロコピーでユーザー空間へ
XDP_DROP や XDP_TX はカーネル内で完結しますが、パケットをユーザー空間アプリで処理したいことも多くあります(独自プロトコル終端、高速プロキシなど)。従来これは recv() のたびにカーネルバッファからユーザーバッファへコピーが入りました。AF_XDP(Address Family XDP)はこのコピーを消すソケット型です。
仕組みの中心は UMEM と4本のリングです。
- UMEM:ユーザー空間が確保し、カーネルへ登録した連続メモリ領域。等サイズの「チャンク」に区切られ、各チャンクがパケット1個分の枠になります。NICのDMAはこのUMEMへ直接書き込むため、ここがゼロコピーの肝です。
- Fillリング:ユーザー → カーネル方向。「このチャンクは空いている、ここに受信していい」とカーネルへ渡す。
- RXリング:カーネル → ユーザー方向。「このチャンクに受信した」とディスクリプタ(UMEM内オフセットと長さ)を返す。
- TX/Completionリング:送信用の対。ユーザーが送るチャンクをTXに積み、送信完了したチャンクをCompletionで回収する。
受信の流れはこうです。アプリは空きチャンクをFillリングへ補充しておく。NICがパケットをDMAでそのUMEMチャンクへ書き込み、XDPプログラムが bpf_redirect_map() でxskmap経由そのAF_XDPソケット(XSK)へREDIRECTする。カーネルはRXリングにディスクリプタを1本積むだけで、データ本体は一度もコピーされません。アプリはRXリングのディスクリプタが指すUMEM内のパケットを直接読みます。
[ユーザー空間] UMEM(NICのDMA先) Fill │ RX │ TX │ Completion
▲ │ ▲ │ ▲
│ DMA write 補充 受信 送信 完了回収
[カーネル/NIC] NIC ──── XDPプログラム ── XDP_REDIRECT(xskmap) ──→ XSK
リング操作は共有メモリ的なロックレス設計で、プロデューサ/コンシューマがそれぞれ自分側のインデックスだけを進める単一生産者・単一消費者リングです。これによりシステムコールなしでパケットを受け渡せます(ウェイクアップ時のみ sendto/poll を使う)。
AF_XDPには厳密にはコピーモードとゼロコピーモードがあります。真のゼロコピー(NICが直接UMEMへDMA)にはドライバのゼロコピー対応が必要で、未対応だとカーネルがUMEMへコピーするフォールバックモードになります。さらにNICがユーザー空間ページへDMAする以上、アドレス変換と保護はIOMMUが担います。「ユーザー空間がNICのDMA先を握る」という構図の安全性は、IOMMUによるデバイス側アドレス空間の隔離が支えています。
AF_XDPで「ゼロコピーが成立する理由」を一言で言えるかが分かれ目です。核は NICのDMA先=ユーザー空間が登録したUMEM であり、XDP_REDIRECTはデータではなくディスクリプタ(UMEM内オフセット)をリングに積むだけだから、という点。4リング(Fill/RX/TX/Completion)の役割と、空きチャンクはFillで補充しRXで返ってくる、という方向の対も頻出です。
DPDKとの設計差:専有か、共存か
ユーザー空間高速パケット処理の代表格にDPDK(Data Plane Development Kit)があります。XDP/AF_XDPと目的は近いものの、設計思想が根本的に異なります。
| 観点 | DPDK | XDP / AF_XDP |
|---|---|---|
| NICの扱い | カーネルドライバを外しUIO/VFIOで専有 | カーネルドライバのまま共存 |
| カーネルスタック | 完全に迂回(バイパス) | XDPで前段処理しPASSで合流可能 |
| CPUの使い方 | ポーリングでコアを専有(ビジーループ) | NAPI/割り込み駆動。アイドル時はCPUを空ける |
| 安全性 | ユーザー空間がNICを直接駆動 | eBPF検証器が安全性を静的に保証 |
| 共有 | そのNICは他用途に使えない | 同NICで通常トラフィックも流せる |
DPDKはカーネルを丸ごと迂回します。NICをカーネルドライバから引き剝がしVFIO等でユーザー空間に専有させ、専用コアをビジーポーリングで回し続けることで、割り込みもコンテキストスイッチもない極限のスループットを得ます。代償は、そのNICとCPUコアがその用途に占有されること、そしてカーネルの恩恵(既存のTCP/IP、ツール群、隔離)を自前で再実装する必要があることです。
XDP/AF_XDPは逆にカーネル内に留まります。ドライバはカーネルのまま、XDPは検証器に守られたeBPFとして動き、裁ききれない/裁きたくないパケットは XDP_PASS で通常スタックへ戻せます。同じNICで高速パス(AF_XDP)と通常のTCP接続を同居させられ、CPUもアイドル時にはNAPIの割り込み緩和で省けます。スループットの絶対上限はDPDKに譲る場面があっても、既存システムへの統合性・安全性・運用の柔軟性で勝ります。
「DPDKの方がpps(パケット毎秒)が高い」は多くのベンチで事実ですが、それはNICとコアの専有という前提込みの数字です。汎用サーバーで他のサービスと同居させたい、既存のカーネル機能やセキュリティ機構を活かしたい、運用チームがeBPFで動的に更新したい——こうした要件では、専有を強いるDPDKより共存できるXDP/AF_XDPが適します。要件が「このマシンはパケット処理専用機」なのか「汎用機の一機能」なのかで選択は割れます。
まとめ
XDPはskb割り当て前・ドライバ直後にeBPFを走らせ、XDP_DROP(最速の破棄)・XDP_PASS(通常スタックへ)・XDP_TX(同NICへ送り返し)・XDP_REDIRECT(横流し)の戻り値1つでパケットを裁きます。ネイティブモードが本命で、ジェネリックはskb後の劣化フォールバックです。AF_XDPは、NICのDMA先をユーザー空間のUMEMに直結し、Fill/RX/TX/Completionの4リングでディスクリプタだけをやり取りすることでゼロコピーのユーザー空間処理を実現します。DPDKがカーネルとNICを専有して絶対速度を取るのに対し、XDP/AF_XDPはカーネル内に留まりeBPF検証器の安全網と既存スタックとの共存を取る——「専有か共存か」が、両者を分ける一本の軸です。
OS Article
XDPとAF_XDPによる高速パケット処理を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Linuxカーネル
比較で見る軸
難易度: advanced / カテゴリ: OS / タグ数: 6
導入後に効く点
AF_XDPはUMEM(共有メモリ)とFill/Completion/RX/TXの4リングで、NICのDMA先をユーザー空間ページに直結し、XDP_REDIRECTでコピーなしにパケットを渡す。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- OS
- タグ数
- 6
判断チェックリスト
- 自社の用途が「Linuxカーネル / ネットワーク」に近いか確認する。
- 強みである「XDPはドライバ直後・skb割り当て前にeBPFを走らせるフックで、DROP/PASS/TX/REDIRECTの戻り値1つでパケットの運命を決める。DROPやTXはスタックを一切通らない。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。