XDP (Express Data Path)

Linux XDP(Express Data Path)를 실무 관점에서 심층 분석합니다. NIC 드라이버 수준의 고성능 패킷 처리, Generic/Native/Offload 세 가지 실행 모드, XDP_REDIRECT 경로(devmap/cpumap/AF_XDP), SmartNIC 하드웨어 오프로드, DDoS 방어와 L4 로드밸런서 실전 구현, 멀티버퍼와 메타데이터, ftrace/bpftrace 기반 성능 분석과 튜닝까지 단계적으로 정리합니다.

전제 조건: 네트워크 스택(Network Stack)네트워크 디바이스 드라이버 문서를 먼저 읽으세요. XDP는 NIC 드라이버 레벨에서 패킷을 처리하므로 드라이버 경계와 큐 구조를 먼저 이해하는 것이 중요합니다. eBPF 프로그래밍 기초는 eBPF 프레임워크 문서를 참고하세요.
일상 비유: XDP는 고속 톨게이트 하이패스 차선과 비슷합니다. 일반 차선(커널 스택)을 거치지 않고 NIC 레벨에서 즉시 패킷을 처리하므로 지연(Latency)과 처리량(Throughput)이 크게 개선됩니다.

핵심 요약

  • 패킷 처리 위치 — 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를 달성합니다.

단계별 이해

  1. 모드 선택
    NIC 드라이버가 XDP를 지원하는지 확인하고 적절한 모드(Generic/Native/Offload)를 선택합니다.
  2. BPF 프로그램 작성
    패킷 파싱, 필터링, 리다이렉트 로직을 작성합니다. Verifier 통과를 위해 경계 검사가 필수입니다.
  3. 맵 설계
    devmap, cpumap, xskmap 등 용도에 맞는 BPF 맵을 설계합니다.
  4. 성능 튜닝
    RSS, IRQ affinity, ring buffer 크기, NUMA 지역성을 최적화합니다.
관련 표준: IEEE 802.3 (Ethernet), RFC 791 (IPv4), RFC 8200 (IPv6) — XDP는 NIC 드라이버 레벨에서 이 표준 프레임/패킷을 직접 처리합니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

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";
ICE XDP 프레임 크기 제한: Intel E810(ice) 드라이버에서 XDP를 사용할 때 프레임 크기가 3KB(3072바이트)를 초과하면 XDP가 동작하지 않습니다. 따라서 Jumbo Frame(MTU > 1500)과 XDP를 동시에 사용할 수 없습니다. MTU 9000 등 Jumbo Frame이 필요한 환경에서는 XDP 대신 tc-bpf를 검토하세요. XDP를 활성화하면 드라이버가 자동으로 frame 크기를 검증하며, 초과 시 -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 모드별 패킷 처리 위치 NIC 하드웨어 (SmartNIC/DPU) XDP Offload (xdpoffload) — BPF를 NIC 프로세서에서 실행, 호스트 CPU 0% ~40+ Mpps (와이어 레이트) NIC 드라이버 (NAPI poll 콜백) XDP Native (xdp) — sk_buff 할당 전에 xdp_buff로 직접 처리 ~24 Mpps (단일 코어) 네트워크 스택 진입점 (netif_receive_skb) XDP Generic (xdpgeneric) — sk_buff 할당 후 처리, 모든 NIC 지원 ~5 Mpps (개발/테스트용) 커널 네트워크 스택 (TC → Netfilter → Routing → Socket) XDP 없이 전체 스택 통과 — 기능 풍부하지만 지연 높음 패킷 흐름 (인그레스) 최고 성능 최저 성능 위로 갈수록 패킷이 일찍 처리되어 지연이 낮고 처리량이 높음 — 아래로 갈수록 기능이 풍부함
XDP 모드: 패킷 처리 위치가 높을수록 성능이 좋지만 기능이 제한됨

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의 제로카피 패킷 처리를 지원합니다.

참고: AF_XDP 소켓의 아키텍처, UMEM 설정, 제로카피 모드, libbpf xsk API 사용법 등 상세 내용은 AF_XDP 소켓 전용 페이지(Page)를 참고하세요.

XDP Redirect — devmap, cpumap, AF_XDP

XDP 프로그램의 XDP_REDIRECT 반환값은 단순한 포워딩이 아닙니다. devmap으로 다른 NIC로 전달하거나, cpumap으로 특정 CPU에서 처리하거나, AF_XDP 소켓으로 제로카피 수신할 수 있습니다. 이 세 가지 redirect 경로를 이해하는 것이 XDP 기반 네트워킹의 핵심입니다.

XDP_REDIRECT 경로별 동작 NIC RX (패킷 수신) XDP 프로그램 실행 devmap cpumap xskmap DEVMAP / DEVMAP_HASH bpf_redirect_map() 다른 NIC으로 직접 전달 대상 NIC의 TX 큐로 bulk 전송 CPUMAP bpf_redirect_map() 특정 CPU로 패킷 분배 대상 CPU의 backlog 큐 → 커널 스택 XSKMAP (AF_XDP) bpf_redirect_map() 제로카피 유저스페이스 수신 UMEM 공유 메모리 → 유저 앱 devmap: 10-14 Mpps (로드밸런서) cpumap: RSS 보완 (멀티코어 분산) AF_XDP: 20+ Mpps (제로카피)
XDP_REDIRECT의 세 가지 맵 타입: devmap(NIC 간 전달), cpumap(CPU 분배), xskmap(AF_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행 CPUMAPvalue(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;
}
제로카피 요구사항: AF_XDP 제로카피 모드는 NIC 드라이버의 명시적 지원이 필요합니다(i40e, mlx5, ice 등). 미지원 드라이버에서는 자동으로 카피 모드로 폴백되며, ethtool -i <dev>로 드라이버를 확인하세요.

XDP 오프로드 — NIC 하드웨어 실행

XDP 오프로드(XDP_FLAGS_HW_MODE)는 BPF 프로그램을 NIC의 프로그래머블 하드웨어(SmartNIC)에서 직접 실행합니다. CPU를 전혀 사용하지 않으므로 최고의 패킷 처리 성능을 달성할 수 있지만, 지원되는 BPF 기능 집합이 제한적입니다.

XDP 실행 계층: Generic → Native → Offload Generic XDP net/core/dev.c sk_buff 할당 후 실행 성능: ~3 Mpps 모든 드라이버 지원 Native XDP 드라이버 ndo_bpf 콜백 xdp_buff 직접 접근 성능: ~24 Mpps i40e, mlx5, ice 등 Offload XDP SmartNIC 하드웨어 실행 CPU 사용 없음 성능: 와이어레이트 Netronome, Mellanox CX-6+ 빠름 최적 하드웨어 오프로드 지원 현황 Netronome Agilio (NFP) - 완전한 XDP 오프로드 지원 - BPF 맵 오프로드 (해시, 배열) - JIT → NFP 마이크로코드 변환 - 40/100 Gbps 와이어레이트 - 제한: 루프, 일부 헬퍼 미지원 - 드라이버: nfp (drivers/net/ethernet/netronome/) - bpftool prog load ... offload dev <nfp_if> NVIDIA/Mellanox ConnectX-6+ - Native XDP: mlx5_core 드라이버 - HW Steering + eSwitch 규칙 오프로드 - TC flower 오프로드 연동 - AF_XDP 제로카피 완벽 지원 - 25/50/100/200 Gbps - 드라이버: mlx5 (drivers/net/ethernet/mellanox/) - devlink dev eswitch set mode switchdev
XDP 실행 계층별 성능 차이와 주요 SmartNIC 오프로드 지원 현황

오프로드 설정 절차

/* 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 XDPNative XDPOffload XDP
실행 위치netif_receive_skb()드라이버 NAPI pollNIC 하드웨어
데이터 접근sk_buff (복사)xdp_buff (제로카피)NIC 메모리
CPU 사용있음있음 (최소화)없음
성능 (64B)~3 Mpps~24 Mpps와이어레이트
BPF 맵전체전체hash, array만
헬퍼 함수전체전체제한적
bounded loop지원지원미지원
설정 플래그XDP_FLAGS_SKB_MODEXDP_FLAGS_DRV_MODEXDP_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 커널 개발 시 고려사항

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 연동

고성능 패킷 처리 파이프라인(Pipeline):
  • 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_buff (멀티버퍼) 메타데이터 영역 data_meta → data (최대 256B) 선형 데이터 (headroom) data → data_end 직접 접근 가능 (포인터 산술) Fragment #1 (skb_frag_t) bpf_xdp_load_bytes()로 접근 Fragment #2 (skb_frag_t) bpf_xdp_store_bytes()로 수정 Fragment #N MAX_SKB_FRAGS (17) 개까지 cpumap / devmap BPF_MAP_TYPE_CPUMAP BPF_MAP_TYPE_DEVMAP XDP_REDIRECT → 다른 CPU/디바이스로 패킷 분산 (RSS 대체/보완) XDP 메타데이터 활용 NIC → XDP (HW 힌트 전달) RSS hash, VLAN tag, HW timestamp XDP → TC/스택 (커스텀 데이터) mark, priority, 분류 결과 bpf_xdp_adjust_meta()로 영역 확장

멀티버퍼 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_typemb(multi-buffer) 플래그를 설정합니다. 이 플래그가 없으면 커널은 점보 프레임을 선형화(linearize)해야 하므로 memcpy 비용이 발생합니다. 멀티버퍼 모드에서는 xdp_buffmb 비트가 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 성능 분석 도구 체계 NIC RX XDP 프로그램 커널 스택 소켓 전달 유저 앱 Tracepoint 위치 xdp:xdp_exception XDP 프로그램 에러 시 발생 act: XDP_ABORTED 반환 값 ifindex, prog_id 포함 드롭 원인 디버깅 핵심 xdp:xdp_redirect redirect 성공/실패 추적 to_ifindex, map_id 포함 devmap/cpumap 경로 확인 err 필드로 실패 원인 파악 xdp:xdp_bulk_tx bulk 전송 완료 이벤트 sent, drops, err 필드 devmap 배치 전송 효율 TX 큐 병목 감지 bpftrace 실전 분석 도구 패킷 처리 지연 측정 kprobe:napi_gro_receive 진입 → kretprobe 반환 시간 측정 히스토그램 분포 출력 p99 지연 식별 드롭 위치 역추적 tracepoint:skb:kfree_skb → 드롭 이유(reason) 수집 → 스택 트레이스 출력 커널 내 정확한 드롭 지점 CPU 사용률 분포 softirq NET_RX 시간 측정 → CPU별 처리량 비교 → RSS 불균형 탐지 IRQ affinity 튜닝 근거
XDP 성능 분석: tracepoint 위치와 bpftrace 활용 패턴

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 showXDP 프로그램이 로드되었는지, 올바른 인터페이스에 연결되었는지 확인
간헐적 드롭ethtool -S <dev> | grep xdpxdp_drop, xdp_tx_err 카운터 확인
높은 지연mpstat -P ALL 1softirq CPU 사용률 확인, RSS 불균형
redirect 실패xdp:xdp_redirect_err tracepoint대상 인터페이스 상태, devmap 설정 확인
Verifier 거부bpftool prog load -d상세 Verifier 로그로 원인 파악
메모리 부족cat /proc/meminfo | grep BPFBPF 맵 메모리 사용량, RLIMIT_MEMLOCK 확인
실무 팁: bpftool prog profile id <ID> duration 5 cycles instructions로 특정 BPF 프로그램의 CPU 사이클과 명령어 수를 프로파일링할 수 있습니다. IPC(Instructions Per Cycle)가 낮으면 캐시 미스를 의심하세요.

XDP 성능 비교와 튜닝 가이드

고성능 패킷 처리에서 XDP, iptables(Netfilter), DPDK는 각각 다른 계층에서 동작하며 성능 특성이 크게 다릅니다. 이 섹션에서는 실측 기반 벤치마크와 실전 튜닝 포인트를 정리합니다.

패킷 처리 솔루션 성능 비교 (단일 코어, 64B 패킷) 처리량 (Mpps) iptables ~2 Mpps nftables ~3 Mpps TC BPF ~6 Mpps XDP generic ~3 Mpps XDP native ~24 Mpps AF_XDP ~20 Mpps DPDK ~30 Mpps 0 5 10 15 20 30 Mpps XDP 성능 튜닝 포인트 하드웨어 튜닝 - RSS 큐 수 = CPU 코어 수 - IRQ affinity 1:1 매핑 - Ring buffer 크기 최적화 - ethtool -G -L -C 설정 BPF 프로그램 최적화 - 맵 접근 최소화 - PERCPU 맵 사용 - 분기 예측 친화적 구조 - batch API 활용 시스템 튜닝 - busy_poll 활성화 - NUMA 로컬 메모리 할당 - CPU isolation (isolcpus) - netdev_budget 조정
패킷 처리 솔루션별 성능 비교 (테스트 환경: Intel Xeon, 25GbE, 64B 패킷)

솔루션별 상세 비교

항목iptablesXDP (Native)AF_XDPDPDK
실행 계층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
NUMANIC과 같은 NUMA 노드크로스 NUMA 접근 시 30-40% 성능 저하
Turbo Boost비활성화일관된 결과를 위해
선택 기준 요약:
  • 단순 필터링/라우팅 → XDP Native (커널 통합, 안전성, 충분한 성능)
  • 유저스페이스 패킷 처리 → AF_XDP (제로카피 + 커널 통합)
  • 최대 성능 + 전용 NIC → DPDK (와이어레이트, 높은 복잡도 감수)
  • 기존 인프라 호환 → iptables/nftables (가장 쉬운 운영)

실전 예제: XDP 기반 DDoS 방어

XDP의 가장 대표적인 실전 활용은 DDoS 방어입니다. NIC 드라이버 레벨에서 악의적 패킷을 즉시 드롭하여 커널 네트워크 스택 진입 전에 처리를 완료합니다. iptables 대비 10배 이상의 PPS 처리가 가능합니다.

인터넷 (공격) NIC RX Queue XDP 프로그램 블랙리스트 맵 조회 Rate Limit 체크 XDP_DROP 악성 패킷 커널 네트워크 스택 정상 패킷 XDP_TX 반사 BPF Maps (제어 평면) blacklist_map (LPM_TRIE) — IP 대역 rate_map (PER_CPU_HASH) — 속도제한 stats_map (PERCPU_ARRAY) — 통계 allowlist_map (HASH) — 화이트리스트 사용자 공간 데몬 블랙리스트 업데이트 통계 수집 · 알림

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 vs TC BPF 선택:
  • 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

참고자료

커널 공식 문서

커뮤니티 및 생태계

도구 및 라이브러리

커널 소스 경로

XDP와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.