af-xdp について
af-xdpは、AF_XDPによるパケットI/Oを扱うエージェント向けスキルです。ソケットやXDPの設定、リング管理、運用上の問題への対処を含み、ユーザー空間のパケット処理を実装する開発者向けです。
主な機能
- 01UMEMとXSKソケットの設定
- 02RX・TX・fill・completionリング
- 03XDPからユーザー空間へのリダイレクト
- 04コピーとゼロコピーの切り替え
- 05libbpfヘルパーと性能比較
活用シーン
- 01ユーザー空間パケット処理の実装
- 02独自ロードバランサーやIDSの構築
- 03NICのゼロコピー対応の確認
- 04パケット損失やキュー問題の調査
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mohitmishra786/ low-level-dev-skills --skill af-xdpインストール後、Claude Code で/af-xdpを入力して読み込まれたか確認します。
詳細
AF_XDP
目的
高性能なパケット I/O のために、AF_XDP ソケットの使い方をエージェントに案内します。ソケットの作成、UMEM のセットアップ、fill/completion/RX/TX リング、XSK への XDP_REDIRECT を使った XDP プログラム、コピーとゼロコピーの各モード、libbpf ヘルパー、DPDK との性能比較、実運用でのユースケースを扱います。
使用する場面
- raw ソケットよりオーバーヘッドの少ないユーザー空間パケットプロセッサーを構築する場合
- DPDK の複雑さを避けつつ、XDP でフィルタリングしたトラフィックをユーザー空間にリダイレクトする場合
- カスタムロードバランサーや IDS データプレーンを実装する場合
- NIC/ドライバーでゼロコピーとコピーの各モードを比較する場合
- 既存の libbpf/XDP インフラストラクチャと統合する場合
- カーネルの連携(ファイアウォールルール)とユーザー空間での処理の両方が必要な場合
ワークフロー
1. アーキテクチャの概要
NIC → XDP program (BPF) → XDP_REDIRECT → AF_XDP socket → userspace
↓
XDP_DROP/PASS/TX
コンポーネント:
- UMEM — フレーム用の共有メモリー領域
- Fill ring — ユーザー空間からカーネルに空のフレームアドレスを提供します
- Completion ring — カーネルから完了した TX フレームを返します
- RX ring — カーネルから受信パケットを渡します
- TX ring — ユーザー空間から送信するパケットを登録します
2. UMEM と XSK ソケットの作成
#include <bpf/xsk.h>
#include <bpf/libbpf.h>
#include <xdp/xsk.h>
#define NUM_FRAMES 4096
#define FRAME_SIZE XSK_UMEM__DEFAULT_FRAME_SIZE
#define RX_BATCH_SIZE 64
struct xsk_umem_info {
struct xsk_ring_prod fill;
struct xsk_ring_cons comp;
struct xsk_umem *umem;
void *buffer;
};
struct xsk_socket_info {
struct xsk_ring_cons rx;
struct xsk_ring_prod tx;
struct xsk_socket *xsk;
};
int xsk_setup(struct xsk_umem_info *umem_info,
struct xsk_socket_info *xsk_info,
int ifindex, int queue_id, int xsk_flags)
{
umem_info->buffer = mmap(NULL, NUM_FRAMES * FRAME_SIZE,
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
struct xsk_umem_config umem_cfg = {
.fill_size = XSK_RING_PROD__DEFAULT_NUM_DESCS,
.comp_size = XSK_RING_CONS__DEFAULT_NUM_DESCS,
.frame_size = FRAME_SIZE,
.frame_headroom = XSK_UMEM__DEFAULT_FRAME_HEADROOM,
.flags = 0,
};
int ret = xsk_umem__create(&umem_info->umem, umem_info->buffer,
NUM_FRAMES * FRAME_SIZE, &umem_info->fill, &umem_info->comp,
&umem_cfg);
if (ret)
return ret;
struct xsk_socket_config xsk_cfg = {
.rx_size = XSK_RING_CONS__DEFAULT_NUM_DESCS,
.tx_size = XSK_RING_PROD__DEFAULT_NUM_DESCS,
.libbpf_flags = XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD,
.xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST,
.bind_flags = xsk_flags, // XDP_ZEROCOPY or XDP_COPY
};
return xsk_socket__create(&xsk_info->xsk, "eth0", queue_id,
umem_info->umem, &xsk_info->rx, &xsk_info->tx, &xsk_cfg);
}
3. Fill ring への登録
void populate_fill_ring(struct xsk_umem_info *umem) {
uint32_t idx;
uint32_t ret = xsk_ring_prod__reserve(&umem->fill, RX_BATCH_SIZE, &idx);
for (uint32_t i = 0; i < ret; i++)
*xsk_ring_prod__fill_addr(&umem->fill, idx + i) = i * FRAME_SIZE;
xsk_ring_prod__submit(&umem->fill, ret);
}
Fill ring に常にバッファーを補充しておく必要があります。バッファーがないと、カーネルはパケットをドロップします。
4. XDP リダイレクトプログラム
// xdp_redirect.c
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
struct {
__uint(type, BPF_MAP_TYPE_XSKMAP);
__uint(max_entries, 64);
__type(key, int);
__type(value, int);
} xsks_map SEC(".maps");
SEC("xdp")
int xdp_redirect_prog(struct xdp_md *ctx)
{
int index = ctx->rx_queue_index;
return bpf_redirect_map(&xsks_map, index, 0);
}
# Load and attach
bpftool prog load xdp_redirect.o /sys/fs/bpf/xdp_redirect
ip link set dev eth0 xdp obj xdp_redirect.o sec xdp
# Pin xsks_map and update with socket fd
5. RX 処理ループ
while (running) {
uint32_t idx_rx = 0, rcvd;
rcvd = xsk_ring_cons__peek(&xsk_info->rx, RX_BATCH_SIZE, &idx_rx);
if (!rcvd)
continue;
for (uint32_t i = 0; i < rcvd; i++) {
const struct xdp_desc *desc = xsk_ring_cons__rx_desc(&xsk_info->rx, idx_rx + i);
uint64_t addr = desc->addr;
uint32_t len = desc->len;
uint8_t *pkt = (uint8_t *)xsk_umem__get_data(umem_info->buffer, addr);
process_packet(pkt, len);
}
xsk_ring_cons__release(&xsk_info->rx, rcvd);
// Return frames to fill ring
refill_fill_ring(umem_info, rcvd);
}
6. コピーとゼロコピー
| モード | フラグ | 要件 |
|---|---|---|
| コピー | XDP_COPY(デフォルト) | あらゆるドライバーに対応。カーネルが UMEM にコピーします |
| ゼロコピー | XDP_ZEROCOPY | ドライバーの対応が必要(i40e、ixgbe、mlx5 など) |
# Check driver ZC support
ethtool -i eth0
# Kernel log on bind:
dmesg | grep xsk
# "Zero-copy enabled" or "Copy mode"
ゼロコピー:NIC が UMEM のフレームに直接 DMA するため、レイテンシーが最小になります。コピー:より安全で、あらゆる環境で使用できます。
7. libbpf の xsk.h ヘルパー
# Modern libbpf includes xsk API
pkg-config --libs libbpf
# -lbpf -lxdp (if separate libxdp installed)
主な関数:
xsk_umem__create/xsk_umem__deletexsk_socket__create/xsk_socket__deletexsk_umem__get_data— フレームアドレスからポインターを取得しますxsk_socket__fd— epoll/poll との統合に使用します
8. DPDK との性能比較
| 要素 | AF_XDP | DPDK |
|---|---|---|
| セットアップの複雑さ | 中程度 | 高い(hugepages、EAL) |
| カーネルとの統合 | カーネル内の XDP フィルター | 完全なバイパス |
| 一般的なスループット | ZC で DPDK に近い | 最高 |
| NIC のバインド | カーネルドライバーのまま | vfio/uio にバインド |
| 適したユースケース | フィルタリングと選択的なユーザー空間処理 | データプレーンの全面的な引き継ぎ |
9. 実運用パターン
Common deployments
├── CDN edge cache — XDP_DROP junk, redirect cacheable to XSK
├── DDoS mitigation — XDP_DROP attack patterns
├── Load balancer — XDP_TX hairpin or redirect to backend XSK
└── Observability — mirror subset to XSK for analysis
# Multi-queue: one XSK per RX queue, pinned to CPU
taskset -c 2 ./xsk_app --queue 2
よくある問題
| 症状 | 原因 | 対処方法 |
|---|---|---|
| RX ring にパケットがない | XDP プログラムがリダイレクトしていない | キューインデックスに対応する xsks_map エントリーを確認します |
ソケット作成時に EBUSY が発生する | すでに XDP がアタッチされている | 先に ip link set dev eth0 xdp off を実行します |
| ゼロコピーからコピーにフォールバックする | ドライバーが ZC に対応していない | dmesg を確認し、対応する NIC を使用します |
| パケットがドロップされる | Fill ring が空 | 積極的に補充し、NUM_FRAMES を増やします |
| TX が動作しない | Completion ring がポーリングされていない | comp ring を処理してフレームを再利用します |
| 権限が拒否される | CAP_NET_RAW が必要 | 適切なケイパビリティを付与して実行します |
関連スキル
skills/observability/ebpf— XDP/BPF プログラムの開発skills/async-io/dpdk— カーネルを完全にバイパスする代替手段skills/async-io/io-uring— パケット以外のワークロード向け非同期 I/Oskills/observability/ebpf-rust— Rust で XDP を使うための Ayaskills/profilers/linux-perf— XDP プログラムの CPU 使用率をプロファイリングskills/allocators/numa-programming— NUMA ローカルな UMEM の割り当て
コメント
Markdown 対応