Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Programmable Bandwidth Management with eBPF

Avatar for Takeru Hayasaka Takeru Hayasaka
June 14, 2025
220

Programmable Bandwidth Management with eBPF

Japan Community Day at KubeCon + CloudNativeCon Japan 2025で発表した資料です。

イベントについて詳しくはこちらでご覧ください
https://community.cncf.io/events/details/cncf-cloud-native-community-japan-presents-japan-community-day-at-kubecon-cloudnativecon-japan-2025/

Avatar for Takeru Hayasaka

Takeru Hayasaka

June 14, 2025
Tweet

More Decks by Takeru Hayasaka

Transcript

  1. Who am I? - Takeru Hayasaka(@takemioIO) - Senior Software Engineer

    / Mobile Core Development - MobileCore, SRv6-MUP - Mainly using eBPF(XDP) for PGW-U - eBPF Japan Meetup Co-Organizer
  2. - eBPF Japan Meetup は、日本国内中心の eBPFの技術コミュニティです。 主に以下のようなトピックにフォーカスを当て、 ベンダ中立な立場で自由闊達に議論できる コミュニティを目指しています。 -

    つまる所, eBPF ユーザー会みたいなものを やってたりしてますということです - discord: https://t.co/qUPClQiBsj - 発表者は共同オーガナイザーをしていて、 本発表はeBPF Meetupでアレンジしました。 - この発表が面白いと思ったら、本日他にもeBPFに 関するセッションがあるのでぜひ聞いてみてください eBPF Japan Meetup(PR) cf. https://ebpf.connpass.com/
  3. Agenda - eBPF とは - パケット処理技術の概要説明 - eBPFを利用してなぜパケット処理を行うのか - 帯域制御とは?

    - 実際にeBPFを利用した手段での帯域制御手法の紹介 - XDP+TCを利用した帯域制御 - XDPだけで実現した帯域制御 - eBPF Qdiscを利用した帯域制御
  4. eBPF(extended Berkeley Packet Filter)とは? - Linuxカーネルに対する拡張を楽に書いて、動的にロードさせる仕組み - つまりカーネル空間で動作する拡張プログラムを用意できる仕組み - 言語仕様としてはRISC型仮想マシンとして表現される

    - 主に以下の用途で利用される - セキュリティ: seccomp, LSM… - オブザーバビリティ: kprobe, uprobe, retprobe, tracepoint, fentry… - ネットワーク: XDP, TC, TCP-BPF, cgroup_skb… - その他: CPUスケジューラー … cf. https://ebpf.io/what-is-ebpf/ 誤解を恐れずにいうと, eBPFを使えば • 手軽にkernelの拡張ができる • Kernel内部で実行された関数の結果を ほぼ何でも取れる • 100GbEを超える速度のパケット処理 が手軽にできるようになる と言ったことが出来る嬉しさがある
  5. - Linuxにはカーネルモジュールと呼ばれる機能がある - これを使えばカーネル空間で動く独自の拡張を書くことができる - eBPFで実装できる機能は基本的はカーネルモジュールで作れる機能と同じ - しかし以下の点が異なる - 1.

    安全性: Verifier による検証でプログラマのミスを未然に防ぐ - メモリアクセス違反でカーネルがハングすることを防いだり、 メモリリークや無限ループに対する事前検証をVerifierが行ってくれる - 2. 後方互換性: カーネルモジュールが後方互換性を保証しないのに対し、 eBPFはAPI Interface経由で動作するため後方互換性が保証される - 更に仮想マシンなのでCPUアーキテクチャに依存しない、ポータビリティもある - なので頑張ってLinuxのアップストリームに入れる必要もない - カーネルメンテナと議論する必要もなくなり、開発アジリティも改善する KernelModule(従来の仕組み)と何が違う?
  6. eBPF Attach & Hook - カーネルにロードしたら、実行したい場所にアタッチする必要がある - 何かのイベントをトリガーにeBPFのプログラムが実行される - Hookの例

    - Socket: socketに対してのIOをhookしてフィルタ - kprobe, kretprobe: カーネルの関数呼び出し・返り値をトレース - TC, XDP: NICに対してのIOをhookして読み書き - アタッチ可能なタイプがbpf_attach_typeに定義されている - Linux v6.11現在で58のattach pointがある
  7. eBPF Map - eBPFプログラムとユーザー空間から読み書きできるKVストア - 様々なMap Typeが存在している - Hash, Array

    - Trie(IPアドレスのプレフィックスマッチなどに使う) - Per-CPU (Hash|Array): RWでロックしないように割り込みCPU毎でデータを持ちたい時に使う - LRU, CPUMAP, QUEUE etc… - 例えば、IPアドレスのブラックリストをeBPF Mapで持っていれば、 それを参照することでカーネルレベルでパケットを落とせる。 このブラックリストはユーザー空間から更新可能である
  8. - bpf syscallでロードされたプログラムはVerifierにより二段階でチェック - 一段階目は全ての分岐をトレースした大雑把なもの - DAGをみることで無限ループの回避 - 最大命令長(100万命令)を超えてしまわないかのチェック -

    不正なジャンプがないかの確認 - 内部的にはcheck_cfgで深さ優先探索して実現してる - 二段階目は制約の上でレジスタの演算ができるなどの細かい確認 - 例えばスカラー値をポインタとしてメモリに対して読み書きできたら、 任意のアドレス空間の読み書きができてしまい大変危ない。 - バッファオーバーフローしないかのチェックもここでやっている - 型や定数の追跡をすることでこれらを実現している - 具体的にはbpf_reg_typeやbpf_reg_stateに定義してある eBPF Verifier
  9. Application Driver NIC Protocol stack/ socket User Space Hardware -

    パケットを処理する場所・技術には 様々な選択肢と向き不向きがある - User space - 🙆実装自由度が高い。独自プロトコルや特殊処理を 直接実装可能で、KernelBypassさせることで高速化 - 🙅プロトコルスタックの再実装をする負荷が 大きく、運用・負荷コストも高い - Kernel space - 🙆カーネル内で比較的低遅延かつ共通 API により 柔軟拡張可能、既存資産の利用&拡張が便利 - 🙅カーネルバージョン/Verifier 制約があり、 ユーザ空間より開発自由度は低い - Hardware - 🙆超低レイテンシでラインレート処理が可能 - 🙅ベンダ固有機能に依存し、細粒度ポリシーや動的 変更が困難なことが多い Three-Layer Model of Packet Processing Kernel Space
  10. よくある技術だと以下のようなものが有名 - User Space - DPDK - AF_XDP - netmap

    - Kernel Space - Netfliter - TC & TC-eBPF - XDP - Hardware - ASIC: Tofino, Bluefield…etc - SDK: P4, DOCA Application Driver NIC Protocol stack/ socket User Space Hardware Kernel Space
  11. - 既存の Linux ネットワークスタックを最大限に活用しつつ、 必要最小限の拡張だけで目的を達成できる - NIC 受信直後の XDP フックなど、最速経路で処理できるため、

    カーネルバイパスや追加ドライバを要さず低レイテンシを実現できる - HW非依存での汎用サーバで動作することができる - つまり、UserSpaceやHWの処理系と比べると比較的低い開発コストで、 そこそこ高速で大体の欲しい仕組みが動く処理が実現できる - 最高の性能・仕組みではないが、早い・安い・美味いの三拍子揃って嬉しい:) なぜeBPFでパケット処理をするのか?
  12. eBPF Network Handler Hook Point Map Application Driver NIC Protocol

    stack/ Socket User Space Hardware Kernel Space eBPF for TC/XDP offload eBPF XDP(driver level) eBPF XDP(generic) eBPF Socket filter TC ingress TC egress Netfilter ingress Netfilter egress eBPF TC fiilter AF_XDP Socket App eBPF TC fiilter eBPF hook point linux net component eBPF Qdisc eBPF Flow Dissector eBPF TCP Congestion eBPF MPTCP sched datapath
  13. XDPとは? - eBPFを利用した高速なパケット処理系 - IOVisor Projectの技術として始まった - 実は結構時間が経っており、当時発表されてから約8年経過している - NetDev

    1.1 (Feb 2016)にJesper Dangaard Brouerからpacket-pageという基本デザインが発表された - Tom Herbert, Alexei and Brenden Blancoの三人がXDPの実際のパッチセットを実装した - 彼らはIOVisorの原型であるPLUMgridの開発者で、XDPに近しい技術を作ってた人が実装した - ※Tom HerbertはPLUMgridではなくFBのエンジニアで、RPS 等の作者 - cf. PLUMgridによるLinuxへの開発の様子(主にeBPFとNetwork関連に力を入れてる) - cf. 最初期のXDPのパッチ(Jul 2016) - NetDev 1.2 (Dec 2016)にDavid S. MillerがXDPでKeynoteを話している - NICドライバレベルでパケットの処理ができることから高速な処理ができる - これにより、1コアで24Mppsという高速なパケット処理を実現している - Linuxのカーネルネットワークスタックとの共存ができるのがめっちゃ強い
  14. 動作アーキテクチャ - パケット着信時にデバドラのhook pointで eBPFのプログラムが動く。ここで実行する モノをXDPと呼び、Interfaceにアタッチして 動作させる - XDPはパケットに基づいてデータ処理を行 い、最終的にどのようにパケットを送り出す

    かを決定している - 送り先は終了コードで制御できる - PASS: 通常のネットワークスタックに送る - DROP: ドロップさせる - TX: 同一Interfaceに返す - REDIRECT: 他のInterfaceに返す - ABORTED: 異常終了ステータスでドロップさせる cf. The eXpress data path: fast programmable packet processing in the operating system kernel / Figure 1
  15. - DPDK(従来の処理系) - コアの専有(特定コアでCPU100%で張り付かせてポーリングする) - カーネルバイパスでコンテキストスイッチを避けて高速化 - ネットワークスタックの再実装が必要 - VPPなどの既存の実装の仕組みに乗ることで多少は解決できる

    - XDP(今回紹介の技術) - 専有で割り当てるコアは必要ないので電力面で有利 - NAPI Basedで実装されているので、必要なとき(==負荷増大時)にポーリングする形式 - カーネルのネットワークスタックと共存できるのでARP等のケアは自分で書く必要がない - netnsでも動いて、nexthopを特定のコンテナに向けるような仕組みもあることから、 比較的コンテナとも高い親和性がある このことから、XDPは良い塩梅のパケット処理フレームワークとして存在している 「最高の性能が出せるのがDPDK」「そこそこ凄くてお手軽に性能が出せるのがXDP」 という体感がある 従来のパケット処理系と比較する
  16. 帯域制御とは? - パケット送信レートを意図的に調整し、望ましい 速度・遅延・公平性 を 維持する仕組み - 超過パケットを「捨てる」か「遅らせる」かを行うことで実現 - policing

    - 動作: 超過パケットを即 Drop(パケットを捨てる) - 適用シーン: 課金/罰則・DoS 抑制、Ingress制限 - 遅延ゼロだがロスを許容する - e.g. モバイルの帯域制御例(cf. 3GPP TS 23.203) - shaping - 動作: キューに貯め遅延させて送出(パケットを遅らせる) - 適用シーン: スムーズなスループット維持、QoS制御 - ロスを抑えつつ遅延を受容する - e.g. WebRTCのリーキーバケットアルゴリズムのQoS例 - Token Bucket 系アルゴリズムを基礎に実装される - 対象単位は ポート・フロー・テナントなどで設定されることが多い
  17. 帯域制御をなぜやるか? - 品質保証 (QoS) - SLA を維持しサービスの体験を安定化 - レイテンシ/ジッター -

    公平性の確保 - 大口通信が他フローを圧迫しないよう隔離 - per-Flow Throughput - インフラ保護 - DDoS や輻輳で装置リソースを枯渇させない - CPU / Buffer 使用率 - 安易に止めてしまうと再送やretryで輻輳したり... - cf. https://www.itmedia.co.jp/news/articles/1907/10/news041_3.html
  18. トークンバケットを利用したポリシング レート:トークンの補充スピード リクエスト Drop 処理される リクエスト レートリミットは トークンバケットに基づく レート&バーストの2つの設定値 に従って行われる

    パケット送信時、パケットの長さに相当するトークン量の取得を試みる トークンが足りたら送信・足りなければ破棄(ポリシング)が行われる バースト: バケットの大きさ (※バケツが一杯になると トークンを受け取らない) バーストを許容するので出力に偏りが出る
  19. トークンバケットベースのシェーピングの例 レート:トークンの補充スピード Drop 処理される リクエスト パケット送信時、パケットの長さに相当するトークン量の取得を試みる トークンが足りたら送信・足りなければ何もしないという操作が行われる バースト: バケットの大きさ (※バケツが一杯になると

    トークンを受け取らない) シェーピングはQueueに リクエストされたパケットを保持 (ある量より多く持ってたらDrop) 定期的にQueueから取り出して、 トークンバケットで送信を判断 Queue 均等に出力される (ペーサーの特徴)
  20. XDPだけで実現した帯域制御 - XDPだけではシェーピングは難しい - XDP には Queueと逐次送信するの仕組みがない - eBPF Map

    でQueueを模しても送信タイミングを制御できず、平滑化不可🙅 - XDPはポリシングならできる - 先ほど説明したトークンバケットで実現 - 判定に合わせてトークンが足りない場合 XDP_DROPでPktをDrop - 高速化テクニック - トークンバケットでの帯域制御は同一のMapエントリをlookupし続ける必要があるが、 ナイーブに用意すると読み書きでCPU間でlockが取られて処理が遅くなる問題がある - Per-CPU Hash Map でトークン残量をコアごとに分離で - CPUMAP で 5-tuple ハッシュでCPUリダイレクト(負荷分散)し Per-Flowを固定コアへ配置 - 各コアはロックレスで Per-CPU Map を更新し競合せずに動く
  21. eBPF Network Handler Hook Point Map Application Driver NIC Protocol

    stack/ Socket User Space Hardware Kernel Space eBPF XDP(driver level) eBPF XDP(generic) eBPF Socket TC ingress TC egress Netfilter ingress Netfilter egress eBPF TC fiilter eBPF TC fiilter eBPF hook point linux net component eBPF Qdisc eBPF Flow Dissector eBPF TCP Congestion eBPF MPTCP sched datapath XDPのプログラムは ここで動く eBPF for TC/XDP offload AF_XDP Socket App
  22. XDPだけを利用したポリシングのアーキテクチャ図 cpu0 cpu1 NIC cpu2 cpu3 RX Queue 00 RX

    Queue 01 RX Queue 02 RX Queue 03 HashMask
 pkt..
 Indirection Table 0
 HashFunc
 1
 2
 …
 MSX割り込み XDP Proc HashFunc
 HashFunc
 HashFunc
 HashFunc
 XDP Proc XDP Proc XDP Proc token bucket token bucket token bucket token bucket cpu rediect
  23. XDPだけを利用したポリシングのアーキテクチャ図 NIC cpu0 cpu1 cpu2 cpu3 RX Queue 00 RX

    Queue 01 RX Queue 02 RX Queue 03 HashMask
 pkt..
 Indirection Table 0
 HashFunc
 1
 2
 …
 MSX割り込み XDP Proc HashFunc
 HashFunc
 HashFunc
 HashFunc
 XDP Proc XDP Proc XDP Proc token bucket token bucket token bucket token bucket 一段目で処理 するべきCPU にRedirect 2段目で処理 するべき中身 や帯域制御を 行う 1段目で処理 するべきCPU にRedirect NIC Queue ごとに割り振る ように事前に設定
  24. x86のレジスタのマッピング例 呼び出し規約に合致していることがわかる 戻り値,第1~5引数,呼び出し元保存 1~4…と対応してる cf. https://github.com/torvalds/linux/blob/9f16d5e6f220661f73b36a4be1b21575651d8833/arch/x86/net/bpf_jit_comp.c#L178 struct { __uint(type, BPF_MAP_TYPE_CPUMAP);

    __uint(max_entries, 128); __type(key, __u32); __type(value, struct bpf_cpumap_val); } cpus_map SEC(".maps"); struct bpf_cpumap_val { __u32 qsize; /* queue size to remote target CPU */ union { int fd; /* prog fd on map write */ __u32 id; /* prog id on map read */ } bpf_prog; }; volatile const __u32 cpu_count; // replace by loader program SEC("xdp_cpu_dispatch") int cpu_dispatch(struct xdp_md *ctx){ // 中略 __u32 cpu = iph->saddr % cpu_count; int ret = bpf_redirect_map(&cpus_map, cpu, 0); if (ret < 0) return XDP_PASS; return ret; } CPUMAPでのCPU Redirectの実装例 (CPU間の負荷分散) 32 redirectをした後のCPUで動かすXDP プログラムを指定できるので、 トークンバケットが動く プログラムを設定できる 送信元IPを元に適当にCPUに対して LBする例
  25. x86のレジスタのマッピング例 呼び出し規約に合致していることがわかる 戻り値,第1~5引数,呼び出し元保存 1~4…と対応してる cf. https://github.com/torvalds/linux/blob/9f16d5e6f220661f73b36a4be1b21575651d8833/arch/x86/net/bpf_jit_comp.c#L178 struct tb_state { __u64

    tokens; // current tokens __u64 last_ns; // last update time in jiffies __u64 burst_limit; // maximum capacity of tokens __u64 rate_bps_to_q32_32; // rate in bps converted to Q32.32 format }; static inline bool tb_allow(struct tb_state *st, __u32 len, __u64 now_ns){ __u64 dt = now_ns - st->last_ns; if (dt) { __u64 add = (st->rate_bps_to_q32_32 * dt) >> 32; st->tokens = st->tokens + add; if (st->tokens > st->burst_limit) st->tokens = st->burst_limit; st->last_ns = now_ns; } if (st->tokens < len)// check if enough tokens return false; st->tokens -= len;// consume tokens return true; } XDPでトークンバケット の実装例 33 パケットの長さ分だけ トークンを消費します ※足りなければDrop パケットが来るたびに呼び出される 前回のトークン更新時刻をmapに保持 しておいて、経過時間dtを使い トークンをおかわりする
  26. XDP + TCの連携を利用した帯域制御 - XDPを使っててもシェーピングしたい!->TCを併用すればできる。 - TC (Traffic Control) とは、Linux

    カーネルのキュー制御サブシステムで あり、パケットの 分類・整形・優先順位付け を行う仕組みである。 - ネットワークデバイスごとに Qdisc(帯域制御オブジェクト)を設定し、 クラス階層 + フィルタ の組み合わせで多様な帯域制御を組み立てられる。 - TCに対してeBPFのプログラムを書くことができる - BPF_PROG_TYPE_SCHED_CLS: パケット分類に利用。Qdiscを決定することが可能。 - BPF_PROG_TYPE_SCHED_ACT: フィルタ機能として利用想定(mirrorやdrop等) - XDPでパケット処理後、予め決めたQdiscに送信して帯域制御を実現 - XDPでパケット分類し、期待したQdiscを示すClassIDをmetadataに書き込んでおく - tc-ebpfでmetadataからClassIDを抽出して、期待したQdiscをEgressで実行可能にする - ※どの様なコマンドで帯域制御するかは調べれば出るので今回は話しません
  27. eBPF Network Handler Hook Point Map Application Driver NIC Protocol

    stack/ Socket User Space Hardware Kernel Space eBPF XDP(driver level) eBPF XDP(generic) eBPF Socket TC ingress TC egress Netfilter ingress Netfilter egress eBPF TC fiilter eBPF TC fiilter eBPF hook point linux net component eBPF Qdisc eBPF Flow Dissector eBPF TCP Congestion eBPF MPTCP sched datapath TCのプログラムは ここで動く eBPF for TC/XDP offload AF_XDP Socket App
  28. XDP + TC連携でのシェーピングのアーキテクチャ図 NIC cpu0 cpu1 cpu2 cpu3 RX Queue

    00 RX Queue 01 RX Queue 02 RX Queue 03 HashMask
 pkt..
 Indirection Table 0
 HashFunc
 1
 2
 …
 MSX割り込み XDP Proc HashFunc
 HashFunc
 HashFunc
 HashFunc
 XDP Proc XDP Proc XDP Proc ingress cls_bpf ingress cls_bpf egress cls_bpf egress cls_bpf cpu rediect HTB Qdisc
 HTB Qdisc
 bpf_rediect
  29. XDP + TC連携でのシェーピングのアーキテクチャ図 NIC cpu0 cpu1 cpu2 cpu3 RX Queue

    00 RX Queue 01 RX Queue 02 RX Queue 03 HashMask
 pkt..
 Indirection Table 0
 HashFunc
 1
 2
 …
 MSX割り込み XDP Proc HashFunc
 HashFunc
 HashFunc
 HashFunc
 XDP Proc XDP Proc XDP Proc ingress cls_bpf ingress cls_bpf egress cls_bpf egress cls_bpf 帯域制御をするCPUを あらかじめ決めておいて、 帯域制御をする場合はそっちに CPUリダイレクトする ※「デフォルトだとTCは1coreで 動く設定でlock取られる問題」と「TCに CPUが取られて従来高速に処理したい モノも遅くなる問題」が存在するため cf. https://github.com/xdp-project/xdp-cpumap-tc cpu rediect HTB Qdisc
 HTB Qdisc
 bpf_rediect bpf_redirectで渡すと egressに直接 飛んでいきます (nftablesとかまで いかない) XDPの処理時にmetadataを渡 しておくと使いたいclass id が使えて便利。これで利用す るべきQdiscを決定する
  30. x86のレジスタのマッピング例 呼び出し規約に合致していることがわかる 戻り値,第1~5引数,呼び出し元保存 1~4…と対応してる cf. https://github.com/torvalds/linux/blob/9f16d5e6f220661f73b36a4be1b21575651d8833/arch/x86/net/bpf_jit_comp.c#L178 struct meta_info { __u32

    mark; }; struct meta_info *meta; int ret = bpf_xdp_adjust_meta(ctx, -(int)sizeof(*meta)); if (ret < 0) { return XDP_DROP; } meta = (struct meta_info *)(long)ctx->data_meta; data = (void *)(long)ctx->data; if ((void *)(meta + 1) > data) { return XDP_PASS; } meta->mark = gw->classID; return XDP_PASS; metadata受け渡し例 (XDP側) 38 xdp_mdというパケットを保持する 構造体にmetadata領域を確保する このmetadata領域に書き込んでおく
  31. x86のレジスタのマッピング例 呼び出し規約に合致していることがわかる 戻り値,第1~5引数,呼び出し元保存 1~4…と対応してる cf. https://github.com/torvalds/linux/blob/9f16d5e6f220661f73b36a4be1b21575651d8833/arch/x86/net/bpf_jit_comp.c#L178 SEC("classifier_ingress") int ingress(struct __sk_buff

    *ctx){ void *data = (void *)(unsigned long)ctx->data; void *data_end = (void *)(unsigned long)ctx->data_end; void *data_meta = (void *)(unsigned long)ctx->data_meta; struct meta_info *meta = data_meta; if (meta + 1 > data) return TC_ACT_OK; if (meta->mark == 0) return TC_ACT_OK; ctx->mark = meta->mark; return bpf_redirect(ctx->ifindex, 0); }; SEC("classifier_egress") int egress(struct __sk_buff *ctx){ return TC_ACT_OK; }; metadata受け渡し例 (TC-eBPF側) 39 metadata領域からmarkする中身を 取り出し、それをsk_buffのmarkに 渡して利用するQdiscの設定をして EgressへRedirect Egress側はただPassするだけOK (TC Qdiscにそのまま渡っていく)
  32. - Qdisc を使えばシェーピングや優先制御が可能と説明した - 複数Qdiscの組み合わせで高度なポリシーも実現可能だが、構築と運用が大変 - これまで Qdisc のアルゴリズム拡張はカーネルモジュールの開発が必要で ハードルが高かった

    - v6.16 から eBPF で Qdisc を拡張可能 に(2025/06の話!) - cf. https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/commit/?h=net&id=93cb5375 fb16693273144e7627e4fe27b811003a - カーネルモジュールなしで安全・柔軟に独自の帯域制御アルゴリズムを追加可能に - 他にもTSNのようなタイムスタンプのある値を並び替えたり、DTNの様なQueueを保持する 仕組みが必要な制御の時にも利用できて嬉しい eBPF Qdiscを利用した帯域制御
  33. eBPF Network Handler Hook Point Map Application Driver NIC Protocol

    stack/ Socket User Space Hardware Kernel Space eBPF XDP(driver level) eBPF XDP(generic) eBPF Socket TC ingress TC egress Netfilter ingress Netfilter egress eBPF TC fiilter AF_XDP Socket App eBPF TC fiilter eBPF hook point linux net component eBPF Qdisc eBPF Flow Dissector eBPF TCP Congestion eBPF MPTCP sched datapath BPF Qdiscの プログラムは ここで動く eBPF for TC/XDP offload
  34. x86のレジスタのマッピング例 呼び出し規約に合致していることがわかる 戻り値,第1~5引数,呼び出し元保存 1~4…と対応してる cf. https://github.com/torvalds/linux/blob/9f16d5e6f220661f73b36a4be1b21575651d8833/arch/x86/net/bpf_jit_comp.c#L178 SEC("struct_ops/bpf_qdisc_enqueue") int BPF_PROG(bpf_qdisc_enqueue, struct

    sk_buff *skb, struct Qdisc *sch, struct bpf_sk_buff_ptr *to_free) { // 中略 } SEC("struct_ops/bpf_qdisc_dequeue") struct sk_buff *BPF_PROG(bpf_qdisc_dequeue, struct Qdisc *sch) { // 中略 return skb; } SEC(".struct_ops") struct Qdisc_ops fifo = { .enqueue = (void *)bpf_qdisc_enqueue, .dequeue = (void *)bpf_qdisc_dequeue, .id = "bpf_qdisc_sample", }; BPF Qdiscの最小の構成 42 Enqueue: pktのinstanceを受け取り 分類などをしてQueueに詰める Dequeue: 定期的に呼び出されてpktを 取り出してpktを送信する この二つのハンドラを記述するのが肝 ※本来であれば他にもinitやdestroyなどの 初期化に関するモノもありますが省略
  35. x86のレジスタのマッピング例 呼び出し規約に合致していることがわかる 戻り値,第1~5引数,呼び出し元保存 1~4…と対応してる struct skb_node { struct sk_buff __kptr

    *skb; struct bpf_list_node node; }; private(A) struct bpf_spin_lock q_fifo_lock; private(A) struct bpf_list_head q_fifo __contains(skb_node, node); SEC("struct_ops/bpf_fifo_enqueue") int BPF_PROG(bpf_fifo_enqueue, struct sk_buff *skb, struct Qdisc *sch, struct bpf_sk_buff_ptr *to_free) { // 中略 return NET_XMIT_SUCCESS; } SEC("struct_ops/bpf_fifo_dequeue") struct sk_buff *BPF_PROG(bpf_fifo_dequeue, struct Qdisc *sch) { // 中略 return skb; } BPF Qdisc 例: FIFO に詰めて出す例です cf.https://github.com/torvalds/linux/blob/master/tools/testing/self tests/bpf/progs/bpf_qdisc_fifo.c 43
  36. x86のレジスタのマッピング例 呼び出し規約に合致していることがわかる 戻り値,第1~5引数,呼び出し元保存 1~4…と対応してる cf. https://github.com/torvalds/linux/blob/9f16d5e6f220661f73b36a4be1b21575651d8833/arch/x86/net/bpf_jit_comp.c#L178 struct skb_node { struct

    sk_buff __kptr *skb; struct bpf_list_node node; }; private(A) struct bpf_spin_lock q_fifo_lock; private(A) struct bpf_list_head q_fifo __contains(skb_node, node); 44 q_fifo: FIFOを実現するためのQueueのインスタンス (pktを保持するQueue実体) q_fifo_lock: マルチコア環境でenqueue/dequeueを呼び出 して同時にCPUがアクセスしても壊れない様に保護 pkt(skbuff)を扱うためのラッパー構造体 __kptrにすることでBPFVerifierが安全に ポインタの生存期間をTrackingしてくれる
  37. x86のレジスタのマッピング例 呼び出し規約に合致していることがわかる 戻り値,第1~5引数,呼び出し元保存 1~4…と対応してる cf. https://github.com/torvalds/linux/blob/9f16d5e6f220661f73b36a4be1b21575651d8833/arch/x86/net/bpf_jit_comp.c#L178 SEC("struct_ops/bpf_fifo_enqueue") int BPF_PROG(bpf_fifo_enqueue, struct

    sk_buff *skb, struct Qdisc *sch, struct bpf_sk_buff_ptr *to_free) { struct skb_node *skbn; u32 pkt_len; if (sch->q.qlen == sch->limit) goto drop; skbn = bpf_obj_new(typeof(*skbn)); if (!skbn) goto drop; skb = bpf_kptr_xchg(&skbn->skb, skb); if (skb) bpf_qdisc_skb_drop(skb, to_free); // 中略 } 45 queueが満杯かチェック pktを格納するノードを動的確保する (skb_nodeを動的にメモリ確保する) pktのkptrとして取り出す (※skbのptrの保持がverifierから見 て合法にしたいので実施している) Enqueueの中身の 解説1
  38. x86のレジスタのマッピング例 呼び出し規約に合致していることがわかる 戻り値,第1~5引数,呼び出し元保存 1~4…と対応してる cf. https://github.com/torvalds/linux/blob/9f16d5e6f220661f73b36a4be1b21575651d8833/arch/x86/net/bpf_jit_comp.c#L178 SEC("struct_ops/bpf_fifo_enqueue") int BPF_PROG(bpf_fifo_enqueue, struct

    sk_buff *skb, struct Qdisc *sch, struct bpf_sk_buff_ptr *to_free) { // 中略 bpf_spin_lock(&q_fifo_lock); bpf_list_push_back(&q_fifo, &skbn->node); bpf_spin_unlock(&q_fifo_lock); // ※説明のために本来の順番と入れ替えてる sch->q.qlen++; sch->qstats.backlog += pkt_len; return NET_XMIT_SUCCESS; drop: bpf_qdisc_skb_drop(skb, to_free); return NET_XMIT_DROP; } 46 Enqueueの中身の 解説2 ロックを取得してキューの 末尾にノードを追加 (FIFO) 統計情報の更新&成功を返却 drop処理&Dropの結果を返却
  39. x86のレジスタのマッピング例 呼び出し規約に合致していることがわかる 戻り値,第1~5引数,呼び出し元保存 1~4…と対応してる cf. https://github.com/torvalds/linux/blob/9f16d5e6f220661f73b36a4be1b21575651d8833/arch/x86/net/bpf_jit_comp.c#L178 SEC("struct_ops/bpf_fifo_dequeue") struct sk_buff *BPF_PROG(bpf_fifo_dequeue,

    struct Qdisc *sch) { // 中略 bpf_spin_lock(&q_fifo_lock); node = bpf_list_pop_front(&q_fifo); bpf_spin_unlock(&q_fifo_lock); if (!node) return NULL; skbn = container_of(node, struct skb_node, node); skb = bpf_kptr_xchg(&skbn->skb, skb); bpf_obj_drop(skbn); if (!skb) return NULL; sch->qstats.backlog -= qdisc_pkt_len(skb); bpf_qdisc_bstats_update(sch, skb); sch->q.qlen--; return skb; } Dequeueの中身の解説 47 ロックを取得してキューの先頭からノード を取り出す (FIFO) (失敗したらNullを返す) ノードからskb_node構造体とパケット ポインタを取得&ノードのメモリを解放 (失敗したらNullを返す) 統計情報の更新と送信するパケットを返す
  40. XDP + TC連携でのシェーピングのアーキテクチャ図 ingress cls_bpf ingress cls_bpf egress cls_bpf egress

    cls_bpf eBPF Qdisc bpf_rediect enqueue dequeue eBPF Qdisc enqueue dequeue Enqueueしたものを定期的にDequeueで 取り出すことでパケットを送信できる
  41. dequeueが呼び出されるタイミングは? - NAPIフィードバックループで呼び出される - NICがパケット送信完了 → ハードウェア割り込み発生し、 napi_schedule() でソフト割り込み (NET_RX_SOFTIRQ)

    を発行 - net_tx_action経由 で qdisc_run が呼ばれることで実現する - または定期的な呼び出しをtimerで設定できる - eBPF経由で実現の仕組みも実はある! - __bpf_kfunc void bpf_qdisc_watchdog_schedule(struct Qdisc *sch, u64 expire, u64 delta_ns) - cf. https://www.spinics.net/lists/netdev/msg1066116.html - よって、アルゴリズムから呼び出しタイミングも含めて調整可能なQdiscと いうのが作れる様になった!
  42. まとめ - eBPFを使えば自分で必要な部分を拡張してそこそこ動くものが作れる! - XDP単体でもポリシングができる! - XDPとTCを使えばシェーピングができる! - さらにeBPF Qdiscを使えば自分の独自アルゴリズムで帯域制御ができる!

    - 宣伝 - これを見てパケット処理をeBPFでやりたくなった人向け - 発表者が書いたCPURedirectのサンプルが入ってるテンプレートプログラム類です。 XDPをGoでロードしたりコントロールするためのサンプルコードがあります。 Starとかつけてくれると嬉しいです(CPUMAPやUnittestなどの例も入ってます) - https://github.com/takehaya/goxdp-template/tree/main - あとこの後0215からeBPFを実世界のプロダクションで使ってるぜという パネルディスカッションもやるのでぜひ来てください!:)