XDP (Express Data Path)
Linux XDP(Express Data Path)를 실무 관점에서 심층 분석합니다. NIC 드라이버 수준의 고성능 패킷 처리, Generic/Native/Offload 세 가지 실행 모드, XDP_REDIRECT 경로(devmap/cpumap/AF_XDP), SmartNIC 하드웨어 오프로드, DDoS 방어와 L4 로드밸런서 실전 구현, 멀티버퍼와 메타데이터, ftrace/bpftrace 기반 성능 분석과 튜닝까지 단계적으로 정리합니다.
핵심 요약
- 패킷 처리 위치 — XDP는 NIC 드라이버의 NAPI poll 단계에서 sk_buff 할당 전에 패킷을 처리합니다.
- 세 가지 모드 — Generic(모든 NIC, 저성능), Native(드라이버 지원, 고성능), Offload(SmartNIC, 최고성능)로 나뉩니다.
- 다섯 가지 액션 — XDP_PASS, XDP_DROP, XDP_TX, XDP_REDIRECT, XDP_ABORTED로 패킷 운명을 결정합니다.
- Redirect 경로 — devmap(NIC 간 전달), cpumap(CPU 분배), xskmap(AF_XDP 제로카피)으로 패킷을 전달합니다.
- 성능 지표 — Native XDP로 단일 코어에서 ~24 Mpps, AF_XDP 제로카피로 ~20 Mpps를 달성합니다.
단계별 이해
- 모드 선택
NIC 드라이버가 XDP를 지원하는지 확인하고 적절한 모드(Generic/Native/Offload)를 선택합니다. - BPF 프로그램 작성
패킷 파싱, 필터링, 리다이렉트 로직을 작성합니다. Verifier 통과를 위해 경계 검사가 필수입니다. - 맵 설계
devmap, cpumap, xskmap 등 용도에 맞는 BPF 맵을 설계합니다. - 성능 튜닝
RSS, IRQ affinity, ring buffer 크기, NUMA 지역성을 최적화합니다.
XDP (eXpress Data Path)
XDP는 NIC 드라이버 수준에서 패킷을 처리하는 고성능 데이터 경로입니다. 전체 네트워크 스택을 거치지 않으므로 초저지연 패킷 처리가 가능합니다.
/* XDP program example (C for BPF) */
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
SEC("xdp")
int xdp_drop_icmp(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
if (eth->h_proto != htons(ETH_P_IP))
return XDP_PASS;
struct iphdr *iph = (void *)(eth + 1);
if ((void *)(iph + 1) > data_end)
return XDP_PASS;
if (iph->protocol == IPPROTO_ICMP)
return XDP_DROP; /* Drop ICMP packets */
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
-EINVAL로 거부합니다.
# ICE XDP 로드 전 MTU 확인
ip link show eth0 | grep mtu
# MTU 1500이면 XDP 사용 가능 (프레임 = MTU + 헤더 ≈ 1514 < 3072)
# XDP 프로그램 로드
ip link set dev eth0 xdp obj xdp_prog.o sec xdp
# Jumbo Frame + XDP 조합 시 에러 예시
# ip link set eth0 mtu 9000 → XDP 로드 실패 (-EINVAL)
XDP 드라이버 모드별 성능 비교
XDP는 세 가지 실행 모드를 제공하며, 각 모드는 패킷 처리 위치와 성능이 크게 다릅니다. 실무에서는 NIC 드라이버 지원 여부와 성능 요구사항에 따라 적절한 모드를 선택해야 합니다.
XDP Actions
| Action | 값 | 동작 | 사용 사례 |
|---|---|---|---|
XDP_PASS |
2 | 패킷을 커널 스택으로 전달 | 정상 트래픽 통과 |
XDP_DROP |
1 | 패킷을 즉시 폐기 (sk_buff 미생성) | DDoS 방어, 블랙리스트 |
XDP_TX |
3 | 수신한 NIC으로 패킷 반환 (hairpin) | 로드밸런서, 패킷 리플렉터 |
XDP_REDIRECT |
4 | 다른 NIC, CPU, 또는 AF_XDP로 전달 | 포워딩, AF_XDP, CPU 분산 |
XDP_ABORTED |
0 | 오류 발생, 패킷 폐기 + trace 이벤트 | 비정상 종료 (디버깅용) |
XDP_REDIRECT와 특수 맵
/* 1. DEVMAP: 다른 NIC으로 패킷 포워딩 */
struct {
__uint(type, BPF_MAP_TYPE_DEVMAP);
__uint(max_entries, 256);
__type(key, __u32);
__type(value, __u32); /* ifindex */
} tx_port SEC(".maps");
SEC("xdp")
int xdp_redirect_prog(struct xdp_md *ctx) {
/* 인덱스 0의 인터페이스로 리다이렉트 */
return bpf_redirect_map(&tx_port, 0, 0);
}
/* 2. CPUMAP: 특정 CPU로 패킷 분산 */
struct {
__uint(type, BPF_MAP_TYPE_CPUMAP);
__uint(max_entries, 64); /* CPU 수 */
__type(key, __u32);
__type(value, __u32); /* 큐 크기 */
} cpu_map SEC(".maps");
SEC("xdp")
int xdp_cpu_balance(struct xdp_md *ctx) {
/* 해시 기반으로 CPU 선택 */
__u32 cpu = compute_hash(ctx) % num_cpus;
return bpf_redirect_map(&cpu_map, cpu, 0);
}
/* 3. XSKMAP: AF_XDP 소켓으로 전달 */
struct {
__uint(type, BPF_MAP_TYPE_XSKMAP);
__uint(max_entries, 64);
__type(key, __u32);
__type(value, __u32); /* xsk fd */
} xsks_map SEC(".maps");
SEC("xdp")
int xdp_to_af_xdp(struct xdp_md *ctx) {
return bpf_redirect_map(&xsks_map, ctx->rx_queue_index, XDP_PASS);
}
코드 설명
-
1-12행 (DEVMAP)
커널 내부에서
bpf_redirect_map()은 즉시 패킷을 전송하지 않습니다. 대신ri->map에 대상 맵을 기록하고, NAPI poll 루프 종료 시xdp_do_flush()가 bulk 전송을 수행합니다. 이 배치 처리가 per-packet 오버헤드를 줄여 10-14 Mpps를 달성하는 핵심입니다. -
14-28행 (CPUMAP)
CPUMAP은 대상 CPU마다kthread_run()으로 생성된 전용 커널 스레드를 가집니다.bpf_redirect_map()호출 시 패킷이 per-CPU의ptr_ring큐에 삽입되고, 대상 CPU의 kthread가 큐에서 꺼내어netif_receive_skb()로 일반 네트워크 스택에 전달합니다.value의 큐 크기는 이ptr_ring의 용량입니다. -
30-42행 (XSKMAP)
XSKMAP리다이렉트 시 커널은 AF_XDP 소켓의 UMEM pool에서 버퍼를 할당하고, zero-copy 모드에서는 DMA 버퍼 주소를 RX Ring에 직접 게시합니다.rx_queue_index를 키로 사용하면 NIC 하드웨어 큐와 AF_XDP 소켓을 1:1로 매핑하여 락 경합 없이 병렬 수신이 가능합니다.
AF_XDP 소켓 아키텍처
AF_XDP는 커널 바이패스에 가까운 성능을 제공하면서도 커널의 XDP 프레임워크와 통합되는 고성능 소켓입니다. 4개의 링(Fill/RX/TX/Completion)과 UMEM 공유 메모리를 통해 사용자 공간에서 20+ Mpps의 제로카피 패킷 처리를 지원합니다.
XDP Redirect — devmap, cpumap, AF_XDP
XDP 프로그램의 XDP_REDIRECT 반환값은 단순한 포워딩이 아닙니다. devmap으로 다른 NIC로 전달하거나, cpumap으로 특정 CPU에서 처리하거나, AF_XDP 소켓으로 제로카피 수신할 수 있습니다. 이 세 가지 redirect 경로를 이해하는 것이 XDP 기반 네트워킹의 핵심입니다.
devmap을 이용한 패킷 포워딩
BPF_MAP_TYPE_DEVMAP은 XDP 프로그램이 패킷을 다른 네트워크 인터페이스로 직접 전달할 수 있게 합니다. L2 스위칭, 로드밸런서, hairpin 모드 구현에 핵심적으로 사용됩니다:
struct {
__uint(type, BPF_MAP_TYPE_DEVMAP);
__uint(max_entries, 256);
__type(key, __u32);
__type(value, __u32); /* ifindex */
} tx_port SEC(".maps");
/* devmap_val 확장: egress 프로그램 연결 */
struct {
__uint(type, BPF_MAP_TYPE_DEVMAP);
__uint(max_entries, 256);
__type(key, __u32);
__type(value, struct bpf_devmap_val);
} tx_port_prog SEC(".maps");
SEC("xdp")
int xdp_redirect_port(struct xdp_md *ctx)
{
__u32 key = 0; /* 대상 포트 인덱스 */
return bpf_redirect_map(&tx_port, key, 0);
}
/* devmap egress 프로그램: redirect 직전 패킷 수정 */
SEC("xdp/devmap")
int xdp_egress_rewrite(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
/* MAC 주소 재작성 */
__builtin_memcpy(eth->h_source, new_src_mac, ETH_ALEN);
return XDP_PASS;
}
코드 설명
-
1-7행
DEVMAP의 기본 형태는 value가__u32(ifindex)이며, 커널은dev_map_lookup_elem()에서 ifindex를dev_get_by_index()로net_device로 변환하여 캐싱합니다. 패킷 전송 시 대상 디바이스의ndo_xdp_xmit()콜백이 호출됩니다. -
9-13행
bpf_devmap_val확장형은 커널 5.8+에서 도입되었습니다.bpf_prog.fd필드를 설정하면 redirect 직전에 egress XDP 프로그램이 실행됩니다. 이를 통해 ingress 프로그램은 라우팅 결정만, egress 프로그램은 MAC 재작성 같은 포트별 처리를 담당하는 관심사 분리가 가능합니다. -
21-23행
커널 내부에서
bpf_redirect_map()은struct bpf_redirect_info의 per-CPU 변수에 대상 맵과 키를 기록만 합니다. 실제 전송은 NAPI poll 완료 시xdp_do_flush_map()에서dev_map_flush_node()가 bulk enqueue로 처리하여 DMA doorbell 호출을 최소화합니다. -
26-31행
egress 프로그램의
SEC("xdp/devmap")섹션 이름은 커널이 프로그램 타입을BPF_XDP_DEVMAP으로 식별하게 합니다. 이 프로그램은 대상 디바이스의 TX 경로에서 실행되므로, 수신 인터페이스와 무관한 포트별 MAC 주소 재작성에 적합합니다.
cpumap을 이용한 CPU 분배
RSS(Receive Side Scaling)가 특정 플로우를 하나의 CPU에 집중시키는 문제를 BPF_MAP_TYPE_CPUMAP으로 해결할 수 있습니다:
struct {
__uint(type, BPF_MAP_TYPE_CPUMAP);
__uint(max_entries, 64); /* CPU 수 */
__type(key, __u32);
__type(value, __u32); /* queue_size */
} cpu_map SEC(".maps");
SEC("xdp")
int xdp_cpu_distribute(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct iphdr *iph = data + sizeof(struct ethhdr);
if ((void *)(iph + 1) > data_end)
return XDP_PASS;
/* 5-tuple 해시로 CPU 선택 */
__u32 cpu = jhash_2words(iph->saddr, iph->daddr, 0) % 8;
return bpf_redirect_map(&cpu_map, cpu, 0);
}
코드 설명
-
1-5행
CPUMAP의value(queue_size)는 대상 CPU의ptr_ring용량입니다. 커널은 맵 업데이트 시cpu_map_kthread_run()으로 대상 CPU에 바인딩된 전용 kthread를 생성합니다. 이 kthread가 큐에서 xdp_frame을 꺼내cpu_map_build_skb()로 skb를 생성한 뒤netif_receive_skb_list()로 일반 네트워크 스택에 전달합니다. -
11-16행
jhash_2words()는 Jenkins 해시로 소스/목적지 IP를 해싱하여 CPU를 선택합니다. RSS가 하드웨어 레벨에서 RX 큐를 분배하는 것과 달리, CPUMAP은 XDP 프로그램 레벨에서 소프트웨어적으로 재분배하므로, RSS가 특정 플로우를 한 CPU에 집중시키는 문제를 해결할 수 있습니다. -
17행
bpf_redirect_map()의 세 번째 인자0은 플래그입니다. 대상 CPU 큐가 가득 찼을 때 기본 동작은 패킷 드롭이며, 커널 5.9+에서는BPF_F_BROADCAST플래그로 모든 활성 CPU에 복제 전달하는 것도 가능합니다.
AF_XDP 제로카피 수신
AF_XDP 소켓은 BPF_MAP_TYPE_XSKMAP을 통해 XDP 프로그램에서 유저스페이스로 직접 패킷을 전달합니다. UMEM(공유 메모리)을 사용한 제로카피 모드에서 최대 성능을 달성합니다:
struct {
__uint(type, BPF_MAP_TYPE_XSKMAP);
__uint(max_entries, 64);
__type(key, __u32);
__type(value, __u32);
} xsks_map SEC(".maps");
SEC("xdp")
int xdp_sock_prog(struct xdp_md *ctx)
{
__u32 index = ctx->rx_queue_index;
/* 해당 큐의 AF_XDP 소켓으로 제로카피 전달 */
if (bpf_map_lookup_elem(&xsks_map, &index))
return bpf_redirect_map(&xsks_map, index, 0);
return XDP_PASS;
}
ethtool -i <dev>로 드라이버를 확인하세요.
XDP 오프로드 — NIC 하드웨어 실행
XDP 오프로드(XDP_FLAGS_HW_MODE)는 BPF 프로그램을 NIC의 프로그래머블 하드웨어(SmartNIC)에서 직접 실행합니다. CPU를 전혀 사용하지 않으므로 최고의 패킷 처리 성능을 달성할 수 있지만, 지원되는 BPF 기능 집합이 제한적입니다.
오프로드 설정 절차
/* XDP 오프로드 프로그램 로드 (ip 명령) */
// # ip link set dev eth0 xdpoffload obj xdp_prog.o sec xdp
/* bpftool로 오프로드 로드 */
// # bpftool prog load xdp_prog.o /sys/fs/bpf/xdp_offload \
// type xdp dev eth0 offload
/* 오프로드 BPF 프로그램 제약사항 */
/*
* 1. 지원 헬퍼 제한: bpf_map_lookup_elem, bpf_map_update_elem 등
* 2. 루프 불가 (모든 경로가 정적으로 결정)
* 3. 맵 크기 제한 (하드웨어 메모리에 의존)
* 4. BPF-to-BPF 호출 미지원
* 5. 스택 크기 추가 제약 (하드웨어별 상이)
*/
| 기능 | Generic XDP | Native XDP | Offload XDP |
|---|---|---|---|
| 실행 위치 | netif_receive_skb() | 드라이버 NAPI poll | NIC 하드웨어 |
| 데이터 접근 | sk_buff (복사) | xdp_buff (제로카피) | NIC 메모리 |
| CPU 사용 | 있음 | 있음 (최소화) | 없음 |
| 성능 (64B) | ~3 Mpps | ~24 Mpps | 와이어레이트 |
| BPF 맵 | 전체 | 전체 | hash, array만 |
| 헬퍼 함수 | 전체 | 전체 | 제한적 |
| bounded loop | 지원 | 지원 | 미지원 |
| 설정 플래그 | XDP_FLAGS_SKB_MODE | XDP_FLAGS_DRV_MODE | XDP_FLAGS_HW_MODE |
ip link show dev eth0에서 xdpoffload 키워드가 표시되면 하드웨어에서 실행 중입니다. xdpdrv는 Native, xdpgeneric는 Generic 모드입니다.
SmartNIC과 하드웨어 오프로드
SmartNIC(또는 DPU — Data Processing Unit)은 네트워크 처리를 NIC 자체의 프로세서/FPGA에서 수행하여 호스트 CPU를 해방시키는 차세대 네트워크 인프라 기술입니다.
SmartNIC 유형과 커널 지원
| 제조사 | 제품군 | 아키텍처 | 커널 드라이버 | XDP 지원 |
|---|---|---|---|---|
| NVIDIA (Mellanox) | ConnectX-6/7, BlueField-2/3 | ASIC + ARM SoC (DPU) | mlx5_core |
XDP native + TC flower HW offload |
| Netronome | Agilio CX | NFP (Network Flow Processor) | nfp |
XDP HW offload (BPF → NFP JIT) |
| Intel | E810 (IPU), Mount Evans | ASIC + FPGA | ice |
XDP native, ADQ (Application Device Queues) |
| Broadcom | Stingray PS1100 | ARM SoC | bnxt_en |
XDP native |
| Marvell | OCTEON 10 DPU | ARM SoC + HW accel | octeontx2 |
XDP native |
XDP 하드웨어 오프로드
# XDP 실행 모드 비교
# 1. Generic XDP — 모든 NIC에서 동작 (성능 낮음, 테스트/개발용)
ip link set dev eth0 xdpgeneric obj prog.o sec xdp
# 2. Native XDP — NIC 드라이버 지원 필요 (고성능)
ip link set dev eth0 xdp obj prog.o sec xdp
# 3. HW Offload XDP — SmartNIC에서 실행 (최고 성능, 호스트 CPU 0%)
ip link set dev eth0 xdpoffload obj prog.o sec xdp
# 현재 XDP 모드 확인
ip link show dev eth0
# ... xdp/id:42 ... (native)
# ... xdpoffload/id:42 ... (hw offload)
# 성능 비교 (64B 패킷, 단일 코어 기준)
# Generic XDP: ~5 Mpps
# Native XDP: ~24 Mpps
# HW Offload: ~40+ Mpps (NIC 와이어 레이트)
TC Flower 하드웨어 오프로드
# TC flower 규칙을 SmartNIC HW로 오프로드
# — 매칭/액션을 NIC의 eSwitch에서 처리
# switchdev 모드 설정 (eSwitch 활성화)
devlink dev eswitch set pci/0000:03:00.0 mode switchdev
# VF representor를 통한 플로우 오프로드
tc qdisc add dev enp3s0f0_0 ingress
tc filter add dev enp3s0f0_0 ingress protocol ip flower src_ip 10.0.0.0/24 dst_ip 192.168.1.0/24 action mirred egress redirect dev enp3s0f0_1 skip_sw # ← skip_sw: HW 전용 (소프트웨어 경로 스킵)
# 오프로드된 플로우 확인
tc -s filter show dev enp3s0f0_0 ingress
# in_hw ← 하드웨어에서 실행 중임을 표시
SmartNIC 커널 개발 시 고려사항
- BPF 명령어 제한 — HW offload 시 NIC 프로세서가 지원하는 BPF 명령어 서브셋만 사용 가능. 복잡한 맵 조회, 루프, 헬퍼 함수 일부가 제한됨
- 맵 동기화 — 오프로드된 BPF 맵은 NIC 메모리에 존재. 호스트에서 맵 업데이트 시 지연 발생 가능
- switchdev 모드 전환 — eSwitch 모드 변경 시 네트워크 순간 단절. 운영 중 변경 주의
- 펌웨어(Firmware) 호환성 — SmartNIC 펌웨어와 커널 드라이버 버전 호환성 확인 필수. 불일치 시 오프로드 실패
- 디버깅 어려움 — HW 오프로드된 로직은 tcpdump/BPF trace로 관찰 불가.
skip_sw대신skip_hw로 먼저 S/W 테스트 후 전환 - Fallback 전략 — HW offload 실패 시 자동으로 SW 경로로 fallback되지만 성능 급락. 모니터링 필수
- CT offload — conntrack 오프로드(
tc ct action)는 제한적. 지원되는 연결 수, 타임아웃 동작이 SW와 다를 수 있음
SmartNIC과 DPDK/AF_XDP 연동
- AF_XDP — XDP에서 사용자 공간(User Space)으로 제로카피 패킷 전달.
XDP_REDIRECT→ AF_XDP 소켓(Socket)으로 커널 바이패스 수준의 성능 - DPDK + SmartNIC — 완전한 커널 바이패스. PMD(Poll Mode Driver)로 NIC 직접 제어. 최대 성능이지만 커널 네트워크 스택 기능(방화벽(Firewall), 라우팅) 사용 불가
- 하이브리드 — SmartNIC eSwitch에서 fast path(TC flower offload)와 slow path(커널 스택) 분리. 일반 트래픽은 HW, 예외는 SW 처리
XDP 멀티버퍼와 메타데이터
커널 6.x에서 도입된 XDP 멀티버퍼 지원은 점보 프레임과 GRO 패킷을 XDP에서 처리할 수 있게 합니다. 또한 XDP 메타데이터를 통해 NIC에서 커널 스택으로 추가 정보를 전달할 수 있습니다.
멀티버퍼 XDP 프로그램
/* XDP 멀티버퍼 프로그램 (6.x+)
* SEC("xdp.frags") 사용으로 멀티버퍼 지원 선언 */
SEC("xdp.frags")
int xdp_multibuf(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
/* 전체 패킷 길이 (선형 + 프래그먼트) */
__u64 total = bpf_xdp_get_buff_len(ctx);
int linear = data_end - data;
/* 선형 부분에서 이더넷 + IP 헤더 파싱 */
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
/* 프래그먼트에 있는 페이로드 접근 */
if (total > linear) {
char payload[128];
int offset = linear; /* 프래그먼트 시작 오프셋 */
int ret = bpf_xdp_load_bytes(ctx, offset, payload, sizeof(payload));
if (ret < 0)
return XDP_PASS;
/* payload 검사 로직 ... */
}
return XDP_PASS;
}
/* cpumap을 이용한 CPU 분산 */
struct {
__uint(type, BPF_MAP_TYPE_CPUMAP);
__type(key, __u32);
__type(value, struct bpf_cpumap_val);
__uint(max_entries, 64); /* CPU 수 */
} cpu_map SEC(".maps");
SEC("xdp")
int xdp_cpu_redirect(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
/* 소스 IP 기반 CPU 분산 */
if (eth->h_proto == bpf_htons(ETH_P_IP)) {
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return XDP_PASS;
__u32 cpu = ip->saddr % bpf_num_possible_cpus();
return bpf_redirect_map(&cpu_map, cpu, 0);
}
return XDP_PASS;
}
/* XDP 메타데이터 전달 (XDP → TC) */
struct xdp_meta {
__u32 mark;
__u16 class_id;
};
SEC("xdp")
int xdp_set_meta(struct xdp_md *ctx)
{
/* 메타데이터 영역 확장 */
int ret = bpf_xdp_adjust_meta(ctx, -(int)sizeof(struct xdp_meta));
if (ret < 0)
return XDP_PASS;
void *data = (void *)(long)ctx->data;
void *data_meta = (void *)(long)ctx->data_meta;
struct xdp_meta *meta = data_meta;
if ((void *)(meta + 1) > data)
return XDP_PASS;
/* 분류 결과를 메타데이터에 저장 */
meta->mark = 0x42;
meta->class_id = 10;
return XDP_PASS; /* TC BPF에서 data_meta로 접근 가능 */
}
코드 설명
-
1-4행 (SEC("xdp.frags"))
커널 6.x+에서
SEC("xdp.frags")는 프로그램의expected_attach_type에mb(multi-buffer) 플래그를 설정합니다. 이 플래그가 없으면 커널은 점보 프레임을 선형화(linearize)해야 하므로 memcpy 비용이 발생합니다. 멀티버퍼 모드에서는xdp_buff의mb비트가 1로 설정되어 프래그먼트 체인이 존재함을 나타냅니다. -
10행
bpf_xdp_get_buff_len()은 선형 데이터 + 모든 프래그먼트의 총 길이를 반환합니다. 커널 내부에서xdp_get_buff_len()이skb_frag_size()를 순회하며 합산합니다. 9000바이트 MTU 점보 프레임에서 선형 부분은 보통 3500바이트, 나머지는 프래그먼트에 분산됩니다. -
17-19행
bpf_xdp_load_bytes()는 프래그먼트에 분산된 데이터를 연속 버퍼로 복사하는 헬퍼입니다. DPI(Deep Packet Inspection)처럼 페이로드 전체를 검사해야 하는 경우에 사용하며, 내부적으로skb_frag_address()와kmap_local_page()를 사용하여 highmem 프래그먼트에도 접근합니다. -
25-34행 (cpumap)
커널 5.14+에서 CPUMAP의 value 타입이
struct bpf_cpumap_val로 확장되어bpf_prog.fd필드가 추가되었습니다. 이를 통해 대상 CPU에서 패킷이 네트워크 스택에 전달되기 전에 추가 BPF 프로그램을 실행할 수 있습니다. -
38-56행 (메타데이터)
bpf_xdp_adjust_meta()는xdp_buff->data_meta포인터를 앞으로 이동시켜 메타데이터 영역을 확보합니다. 이 영역은 XDP→TC BPF 프로그램 간 데이터 전달에 사용되며, skb로 변환될 때skb->data - skb->head영역에 보존됩니다. TC 프로그램에서는skb->data_meta로 접근 가능합니다.
ftrace/bpftrace를 이용한 XDP 성능 분석
XDP 프로그램의 성능 문제를 진단할 때 ftrace tracepoint와 bpftrace는 가장 강력한 도구입니다. 패킷 드롭 원인, 처리 지연, CPU 병목을 실시간(Real-time)으로 관찰할 수 있습니다.
XDP Tracepoint 활용
커널은 XDP 관련 tracepoint를 /sys/kernel/debug/tracing/events/xdp/에 제공합니다:
# XDP 예외(에러) 모니터링
# bpftrace -e 'tracepoint:xdp:xdp_exception { \
# @drops[args->act] = count(); }'
# XDP redirect 성공/실패 추적
# bpftrace -e 'tracepoint:xdp:xdp_redirect_err { \
# printf("redirect err: ifindex=%d map=%d err=%d\n", \
# args->to_ifindex, args->map_id, args->err); }'
# XDP 액션별 패킷 수 집계 (1초 간격)
# bpftrace -e 'tracepoint:xdp:xdp_redirect, \
# tracepoint:xdp:xdp_exception { \
# @[probe] = count(); } \
# interval:s:1 { print(@); clear(@); }'
bpftrace 성능 분석 스크립트
/* XDP 프로그램 실행 시간 히스토그램 (bpftrace 스크립트) */
// #!/usr/bin/env bpftrace
// kprobe:bpf_prog_run_xdp {
// @start[tid] = nsecs;
// }
// kretprobe:bpf_prog_run_xdp /@start[tid]/ {
// @ns = hist(nsecs - @start[tid]);
// delete(@start[tid]);
// }
// interval:s:5 { print(@ns); clear(@ns); }
/* 패킷 드롭 역추적 (bpftrace 스크립트) */
// #!/usr/bin/env bpftrace
// tracepoint:skb:kfree_skb {
// @reason[args->reason] = count();
// if (args->reason == 2 /* SKB_DROP_REASON_NOT_SPECIFIED */) {
// @stacks[kstack] = count();
// }
// }
// interval:s:10 {
// printf("\n--- Drop reasons ---\n");
// print(@reason); clear(@reason);
// printf("\n--- Drop stacks ---\n");
// print(@stacks, 5); clear(@stacks);
// }
XDP 디버깅 체크리스트
| 증상 | 진단 명령 | 원인 및 해결 |
|---|---|---|
| 패킷이 전달 안 됨 | bpftool prog show | XDP 프로그램이 로드되었는지, 올바른 인터페이스에 연결되었는지 확인 |
| 간헐적 드롭 | ethtool -S <dev> | grep xdp | xdp_drop, xdp_tx_err 카운터 확인 |
| 높은 지연 | mpstat -P ALL 1 | softirq CPU 사용률 확인, RSS 불균형 |
| redirect 실패 | xdp:xdp_redirect_err tracepoint | 대상 인터페이스 상태, devmap 설정 확인 |
| Verifier 거부 | bpftool prog load -d | 상세 Verifier 로그로 원인 파악 |
| 메모리 부족 | cat /proc/meminfo | grep BPF | BPF 맵 메모리 사용량, RLIMIT_MEMLOCK 확인 |
bpftool prog profile id <ID> duration 5 cycles instructions로 특정 BPF 프로그램의 CPU 사이클과 명령어 수를 프로파일링할 수 있습니다. IPC(Instructions Per Cycle)가 낮으면 캐시 미스를 의심하세요.
XDP 성능 비교와 튜닝 가이드
고성능 패킷 처리에서 XDP, iptables(Netfilter), DPDK는 각각 다른 계층에서 동작하며 성능 특성이 크게 다릅니다. 이 섹션에서는 실측 기반 벤치마크와 실전 튜닝 포인트를 정리합니다.
솔루션별 상세 비교
| 항목 | iptables | XDP (Native) | AF_XDP | DPDK |
|---|---|---|---|---|
| 실행 계층 | Netfilter (L3/L4) | 드라이버 NAPI | 드라이버 + 유저 | 유저스페이스 PMD |
| sk_buff 할당 | 필수 | 불필요 (xdp_buff) | 불필요 (UMEM) | 불필요 (mbuf) |
| 64B drop PPS | ~2M | ~24M | ~20M | ~30M |
| 64B fwd PPS | ~1.5M | ~14M | ~15M | ~25M |
| 지연 (p99) | ~50us | ~5us | ~3us | ~2us |
| 커널 통합 | 완전 | 완전 | 부분 (소켓) | 없음 (바이패스) |
| 안전성 | 높음 | 높음 (Verifier) | 높음 | 낮음 (root) |
| 배포 복잡도 | 낮음 | 중간 | 중간 | 높음 |
| 핫 업데이트 | 규칙 추가/삭제 | 프로그램 교체 | 프로그램 교체 | 프로세스 재시작(Reboot) |
| 멀티테넌트 | 네임스페이스(Namespace) | 네임스페이스 | 네임스페이스 | SR-IOV 필요 |
XDP 성능 튜닝 가이드
## 1. NIC 하드웨어 설정
# RSS 큐 수를 CPU 수에 맞춤
# ethtool -L eth0 combined 16
# Ring buffer 크기 최대화
# ethtool -G eth0 rx 4096 tx 4096
# 인터럽트 coalescing 조정 (저지연 vs 처리량)
# ethtool -C eth0 rx-usecs 0 rx-frames 1 # 저지연
# ethtool -C eth0 rx-usecs 50 rx-frames 64 # 고처리량
## 2. IRQ affinity 설정
# CPU 코어별 1:1 매핑
# for i in $(seq 0 15); do
# echo $i > /proc/irq/$(cat /sys/class/net/eth0/device/msi_irqs/ \
# | sed -n "$((i+1))p")/smp_affinity_list
# done
## 3. CPU isolation (성능 크리티컬)
# GRUB: isolcpus=4-15 nohz_full=4-15 rcu_nocbs=4-15
# XDP 전용 CPU에서 다른 워크로드 배제
## 4. busy_poll 활성화 (AF_XDP와 함께)
# sysctl -w net.core.busy_poll=50
# sysctl -w net.core.busy_read=50
## 5. NUMA 로컬 메모리 확인
# numactl --cpubind=0 --membind=0 ./xdp_app
벤치마크 방법론
정확한 XDP 벤치마크를 위해 다음 조건을 통제해야 합니다:
| 항목 | 권장 설정 | 이유 |
|---|---|---|
| 패킷 크기 | 64B (최소) + 1518B (MTU) | PPS는 작은 패킷에서 병목, BPS는 큰 패킷에서 병목 |
| 트래픽 생성기 | T-Rex, MoonGen, pktgen | 하드웨어 기반 또는 DPDK 기반 생성기 사용 |
| 워밍업 | 30초 이상 | JIT 컴파일, 캐시 프라이밍 |
| 측정 시간 | 60초 이상 | 안정적인 평균/p99 값 확보 |
| CPU 주파수 | 고정 (performance governor) | cpufreq-set -g performance |
| NUMA | NIC과 같은 NUMA 노드 | 크로스 NUMA 접근 시 30-40% 성능 저하 |
| Turbo Boost | 비활성화 | 일관된 결과를 위해 |
- 단순 필터링/라우팅 → XDP Native (커널 통합, 안전성, 충분한 성능)
- 유저스페이스 패킷 처리 → AF_XDP (제로카피 + 커널 통합)
- 최대 성능 + 전용 NIC → DPDK (와이어레이트, 높은 복잡도 감수)
- 기존 인프라 호환 → iptables/nftables (가장 쉬운 운영)
실전 예제: XDP 기반 DDoS 방어
XDP의 가장 대표적인 실전 활용은 DDoS 방어입니다. NIC 드라이버 레벨에서 악의적 패킷을 즉시 드롭하여 커널 네트워크 스택 진입 전에 처리를 완료합니다. iptables 대비 10배 이상의 PPS 처리가 가능합니다.
XDP DDoS 방어 프로그램 코드
다음은 IP 블랙리스트 기반 DDoS 방어 XDP 프로그램의 핵심 구조입니다. LPM_TRIE 맵을 사용하여 CIDR 대역 단위 차단을 지원합니다.
// xdp_ddos_filter.bpf.c — XDP DDoS 방어 프로그램
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#define ETH_P_IP 0x0800
#define MAX_ENTRIES 100000
struct lpm_key {
__u32 prefixlen;
__u32 addr;
};
/* IP 블랙리스트 (LPM_TRIE — CIDR 대역 지원) */
struct {
__uint(type, BPF_MAP_TYPE_LPM_TRIE);
__type(key, struct lpm_key);
__type(value, __u64); /* 차단 시작 시각 (ns) */
__uint(max_entries, MAX_ENTRIES);
__uint(map_flags, BPF_F_NO_PREALLOC);
} blacklist_map SEC(".maps");
/* per-CPU 패킷 카운터 */
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, __u32);
__type(value, __u64);
__uint(max_entries, 4); /* 0:pass, 1:drop, 2:tx, 3:total */
} stats_map SEC(".maps");
/* per-CPU rate limiter */
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_HASH);
__type(key, __u32); /* source IP */
__type(value, __u64); /* 마지막 패킷 타임스탬프 */
__uint(max_entries, MAX_ENTRIES);
} rate_map SEC(".maps");
static __always_inline void update_stats(__u32 idx)
{
__u64 *cnt = bpf_map_lookup_elem(&stats_map, &idx);
if (cnt)
__sync_fetch_and_add(cnt, 1);
}
SEC("xdp")
int xdp_ddos_filter(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
/* 이더넷 헤더 범위 검사 */
if ((void *)(eth + 1) > data_end)
return XDP_DROP;
if (eth->h_proto != bpf_htons(ETH_P_IP))
return XDP_PASS; /* IPv4 외 트래픽은 통과 */
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return XDP_DROP;
/* 1단계: IP 블랙리스트 조회 (LPM) */
struct lpm_key key = {
.prefixlen = 32,
.addr = ip->saddr,
};
if (bpf_map_lookup_elem(&blacklist_map, &key)) {
update_stats(1); /* drop counter */
return XDP_DROP;
}
/* 2단계: Rate Limiting (초당 패킷 제한) */
__u64 now = bpf_ktime_get_ns();
__u64 *last = bpf_map_lookup_elem(&rate_map, &ip->saddr);
if (last && (now - *last) < 1000) { /* 1μs 간격 미만 → 의심 */
update_stats(1);
return XDP_DROP;
}
bpf_map_update_elem(&rate_map, &ip->saddr, &now, BPF_ANY);
/* 정상 패킷 → 커널 스택 전달 */
update_stats(0); /* pass counter */
return XDP_PASS;
}
char LICENSE[] SEC("license") = "GPL";
사용자 공간 관리 프로그램
// ddos_manager.c — 블랙리스트 관리 및 통계 수집
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include "xdp_ddos_filter.skel.h"
int main(int argc, char **argv)
{
struct xdp_ddos_filter_bpf *skel;
int err;
skel = xdp_ddos_filter_bpf__open_and_load();
if (!skel) {
fprintf(stderr, "Failed to open and load BPF skeleton\n");
return 1;
}
/* eth0에 XDP 프로그램 부착 */
int ifindex = if_nametoindex("eth0");
err = bpf_xdp_attach(ifindex, bpf_program__fd(skel->progs.xdp_ddos_filter),
XDP_FLAGS_DRV_MODE, NULL);
if (err) {
fprintf(stderr, "Failed to attach XDP: %d\n", err);
goto cleanup;
}
/* 블랙리스트 추가: 192.168.1.0/24 */
struct lpm_key key = { .prefixlen = 24 };
inet_pton(AF_INET, "192.168.1.0", &key.addr);
__u64 ts = time(NULL);
bpf_map_update_elem(bpf_map__fd(skel->maps.blacklist_map),
&key, &ts, BPF_ANY);
printf("XDP DDoS filter attached. Monitoring...\n");
/* 통계 주기적 출력 */
while (1) {
sleep(1);
__u32 key_stat;
__u64 values[libbpf_num_possible_cpus()];
for (key_stat = 0; key_stat < 4; key_stat++) {
bpf_map_lookup_elem(bpf_map__fd(skel->maps.stats_map),
&key_stat, values);
__u64 total = 0;
for (int i = 0; i < libbpf_num_possible_cpus(); i++)
total += values[i];
printf("stat[%u] = %llu ", key_stat, total);
}
printf("\n");
}
cleanup:
xdp_ddos_filter_bpf__destroy(skel);
return err;
}
BPF_MAP_TYPE_LRU_PERCPU_HASH를 rate_map에 사용하여 메모리 사용량을 자동 관리합니다. 또한 bpf_map_lookup_and_delete_elem을 통해 통계를 원자적으로 읽고 초기화할 수 있습니다.
실전 예제: TC BPF 트래픽 셰이핑
TC(Traffic Control) BPF를 사용하면 XDP로는 처리하기 어려운 egress 방향 트래픽 제어(Traffic Control)와 세밀한 폴리싱(policing)이 가능합니다. TC BPF는 sk_buff 기반이므로 L4 이상의 정보에 쉽게 접근할 수 있습니다.
// tc_police.bpf.c — TC BPF 기반 트래픽 폴리서
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#define TC_ACT_OK 0
#define TC_ACT_SHOT 2
struct rate_info {
__u64 tokens; /* 현재 토큰 수 */
__u64 last_update; /* 마지막 업데이트 시각 (ns) */
};
/* 클래스별 토큰 버킷 */
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, __u32); /* 분류 키 (IP 해시) */
__type(value, struct rate_info);
__uint(max_entries, 65536);
} token_bucket SEC(".maps");
/* 설정: 최대 속도 (bytes/sec) */
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, __u64);
__uint(max_entries, 1); /* [0] = rate_limit (bytes/ns) */
} config_map SEC(".maps");
SEC("tc")
int tc_rate_limiter(struct __sk_buff *skb)
{
/* 설정값 조회 */
__u32 cfg_key = 0;
__u64 *rate = bpf_map_lookup_elem(&config_map, &cfg_key);
if (!rate || *rate == 0)
return TC_ACT_OK;
/* 분류: 소스 IP 기반 */
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return TC_ACT_OK;
if (eth->h_proto != bpf_htons(ETH_P_IP))
return TC_ACT_OK;
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return TC_ACT_OK;
__u32 key = ip->saddr;
/* Token Bucket Algorithm */
__u64 now = bpf_ktime_get_ns();
struct rate_info *ri = bpf_map_lookup_elem(&token_bucket, &key);
struct rate_info new_ri = {};
if (ri) {
__u64 elapsed = now - ri->last_update;
__u64 new_tokens = ri->tokens + elapsed * (*rate);
__u64 burst = (*rate) * 1000000; /* 1ms burst */
if (new_tokens > burst)
new_tokens = burst;
if (new_tokens < skb->len) {
/* 토큰 부족 → 드롭 */
return TC_ACT_SHOT;
}
new_ri.tokens = new_tokens - skb->len;
} else {
new_ri.tokens = 0;
}
new_ri.last_update = now;
bpf_map_update_elem(&token_bucket, &key, &new_ri, BPF_ANY);
return TC_ACT_OK;
}
char LICENSE[] SEC("license") = "GPL";
TC BPF 프로그램 부착 명령
# TC BPF 프로그램 부착 (egress 방향)
tc qdisc add dev eth0 clsact
tc filter add dev eth0 egress bpf direct-action obj tc_police.bpf.o sec tc
# 상태 확인
tc filter show dev eth0 egress
# ingress 방향도 가능
tc filter add dev eth0 ingress bpf direct-action obj tc_police.bpf.o sec tc
# 분리
tc filter del dev eth0 egress
tc qdisc del dev eth0 clsact
- XDP — ingress 전용, 최고 성능,
xdp_buff기반 (sk_buff 없음) - TC BPF — ingress + egress 모두 가능,
sk_buff기반, L4+ 정보 접근 용이 - 두 가지를 조합하는 것이 일반적: XDP에서 1차 필터링 → TC에서 세밀한 정책 적용
실습 가이드: XDP 프로그램 작성부터 테스트까지
이 실습에서는 XDP 프로그램을 처음부터 작성, 컴파일, 로드, 테스트하는 전 과정을 단계별로 안내합니다.
1단계: 개발 환경 준비
# 필수 패키지 설치 (Ubuntu/Debian)
sudo apt install -y clang llvm libbpf-dev linux-tools-common \
linux-tools-$(uname -r) bpftool gcc-multilib
# Fedora/RHEL
sudo dnf install -y clang llvm libbpf-devel bpftool kernel-devel
# BTF 지원 확인
ls /sys/kernel/btf/vmlinux
# 파일이 있으면 BTF 활성 (CO-RE 사용 가능)
# vmlinux.h 생성
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
# 프로젝트 구조
mkdir -p xdp-lab && cd xdp-lab
# xdp-lab/
# ├── vmlinux.h
# ├── xdp_counter.bpf.c (BPF 프로그램)
# ├── xdp_counter.c (사용자 공간 로더)
# └── Makefile
2단계: XDP 프로그램 작성
// xdp_counter.bpf.c — 프로토콜별 패킷 카운터
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#define ETH_P_IP 0x0800
#define ETH_P_IPV6 0x86DD
#define ETH_P_ARP 0x0806
enum proto_idx {
PROTO_IPV4 = 0,
PROTO_IPV6 = 1,
PROTO_ARP = 2,
PROTO_OTHER = 3,
PROTO_MAX = 4,
};
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, __u32);
__type(value, __u64);
__uint(max_entries, PROTO_MAX);
} pkt_count SEC(".maps");
static __always_inline void count_packet(__u32 idx)
{
__u64 *cnt = bpf_map_lookup_elem(&pkt_count, &idx);
if (cnt)
*cnt += 1;
}
SEC("xdp")
int xdp_counter(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
switch (bpf_ntohs(eth->h_proto)) {
case ETH_P_IP:
count_packet(PROTO_IPV4);
break;
case ETH_P_IPV6:
count_packet(PROTO_IPV6);
break;
case ETH_P_ARP:
count_packet(PROTO_ARP);
break;
default:
count_packet(PROTO_OTHER);
break;
}
return XDP_PASS; /* 모든 패킷 통과 (관측만) */
}
char LICENSE[] SEC("license") = "GPL";
3단계: 컴파일과 스켈레톤 생성
# BPF 프로그램 컴파일
clang -O2 -g -target bpf -D__TARGET_ARCH_x86 \
-c xdp_counter.bpf.c -o xdp_counter.bpf.o
# BPF 스켈레톤 헤더 생성
bpftool gen skeleton xdp_counter.bpf.o > xdp_counter.skel.h
# 사용자 공간 프로그램 컴파일
gcc -O2 -Wall -o xdp_counter xdp_counter.c -lbpf -lelf -lz
4단계: 로드 및 테스트
# 방법 1: ip 명령으로 직접 부착
sudo ip link set dev lo xdpgeneric obj xdp_counter.bpf.o sec xdp
# 프로그램 확인
sudo bpftool prog list
# ID Type Name Attach
# 42 xdp xdp_counter lo
# 맵 내용 확인 (per-CPU 값 합산)
sudo bpftool map dump name pkt_count
# 트래픽 생성 (테스트)
ping -c 100 127.0.0.1
# 통계 확인
sudo bpftool map dump name pkt_count
# key: 00 00 00 00 value (per-CPU):
# cpu00: 200 ...
# 프로그램 분리
sudo ip link set dev lo xdpgeneric off
# 방법 2: xdp-loader 사용 (권장)
sudo xdp-loader load -m skb lo xdp_counter.bpf.o
sudo xdp-loader status
sudo xdp-loader unload lo --all
5단계: veth 쌍을 이용한 격리(Isolation) 테스트
# 네트워크 네임스페이스로 안전한 테스트 환경 구성
sudo ip netns add test_ns
sudo ip link add veth0 type veth peer name veth1
sudo ip link set veth1 netns test_ns
sudo ip addr add 10.0.0.1/24 dev veth0
sudo ip link set veth0 up
sudo ip netns exec test_ns ip addr add 10.0.0.2/24 dev veth1
sudo ip netns exec test_ns ip link set veth1 up
sudo ip netns exec test_ns ip link set lo up
# XDP 부착 (veth에는 generic 모드)
sudo ip link set dev veth0 xdpgeneric obj xdp_counter.bpf.o sec xdp
# 다른 네임스페이스에서 트래픽 생성
sudo ip netns exec test_ns ping -c 50 10.0.0.1
# 결과 확인
sudo bpftool map dump name pkt_count
# 정리
sudo ip link set dev veth0 xdpgeneric off
sudo ip link del veth0
sudo ip netns del test_ns
참고자료
커널 공식 문서
- AF_XDP Documentation — AF_XDP 공식 문서
- BPF CPUMAP — XDP에서 다른 CPU로 패킷 리다이렉션
- BPF Documentation Hub — 커널 BPF 문서 최상위
- BPF_PROG_RUN — 유닛 테스트용 BPF 프로그램 실행 인터페이스
커뮤니티 및 생태계
- ebpf.io — eBPF Foundation 공식 포털
- Cilium BPF & XDP Reference Guide — 아키텍처/opcode/맵 상세 설명
- Cloudflare: Programmable Packet Filtering with eBPF — 프로덕션 XDP 방화벽 사례
도구 및 라이브러리
- XDP Tutorial — 단계별 XDP 학습
- XDP Tools — xdp-loader, xdp-filter 등
- libbpf (GitHub) — 공식 BPF 라이브러리
- bpftool (GitHub) — BPF 프로그램/맵 관리 CLI
- Cilium (GitHub) — eBPF 기반 CNI, 네트워크 보안, 로드 밸런싱
커널 소스 경로
net/core/dev.c— XDP 훅 포인트 (generic XDP)net/core/filter.c— 네트워크 BPF 헬퍼 함수net/xdp/— XDP 프레임워크 핵심 코드include/net/xdp.h— XDP 구조체 정의drivers/net/ethernet/— XDP 지원 NIC 드라이버samples/bpf/— BPF/XDP 예제 프로그램
관련 문서
XDP와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.