VPP (FD.io) 심화 — 고성능 유저스페이스 패킷 처리

FD.io VPP(Vector Packet Processing) 프레임워크: 벡터 패킷 처리 모델, 그래프 노드 아키텍처, DPDK 통합, 플러그인 시스템, TAP/TUN·vhost-user·AF_PACKET·AF_XDP 커널 인터페이스, L2/L3/ACL/NAT/IPsec/SRv6 기능, CLI/API, 성능 최적화, 활용 사례 종합 가이드. 커널 내부 데이터 경로, 핵심 자료구조/API, 운영 환경 튜닝 포인트와 장애 디버깅 절차까지 실무 관점으로 다룹니다.

전제 조건: 네트워크 스택라우팅 문서를 먼저 읽으세요. 제어 평면과 데이터 평면이 분리되어 동작하므로, 규칙 갱신 시점과 실제 적용 지연을 함께 확인해야 합니다.
일상 비유: 이 주제는 도로 표지판 갱신과 교통 흐름 제어와 비슷합니다. 표지판을 바꿔도 차량 흐름 반영에는 시간이 필요하듯이, 경로/정책 갱신과 패킷 처리 시점은 분리해서 봐야 합니다.

핵심 요약

  • 패킷 수명주기 — ingress, 처리, egress 경로를 연결합니다.
  • 큐/버퍼 모델 — sk_buff와 큐 지점의 역할을 분리합니다.
  • 정책/데이터 분리 — 제어 평면과 데이터 평면을 구분합니다.
  • 성능 지표 — PPS, 지연, 드롭 원인을 함께 분석합니다.
  • 오프로딩 경계 — NIC/XDP/DPDK 경계를 명확히 유지합니다.

단계별 이해

  1. 경로 고정
    문제가 발생한 ingress/egress 지점을 먼저 특정합니다.
  2. 큐 관찰
    백로그와 드롭 위치를 계측합니다.
  3. 정책 반영 확인
    라우팅/필터 변경이 데이터 경로에 반영됐는지 봅니다.
  4. 부하 검증
    실제 트래픽 패턴에서 재현성을 확인합니다.
관련 표준: FD.io VPP Architecture (fd.io) — 고성능 패킷 처리 프레임워크 아키텍처입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
관련 기술: VPP는 커널 네트워크 스택을 우회(bypass)하여 유저스페이스에서 패킷을 처리하는 프레임워크입니다. 커널의 TAP/TUN(drivers/net/tun.c), vhost(drivers/vhost/), AF_PACKET(net/packet/af_packet.c), AF_XDP(net/xdp/xsk.c), UIO/VFIO(drivers/uio/, drivers/vfio/) 서브시스템과 밀접하게 연관됩니다.

VPP 개요

VPP(Vector Packet Processing)는 Linux Foundation 산하 FD.io(Fast Data Input/Output) 프로젝트의 핵심 구성 요소로, 고성능 유저스페이스 패킷 처리 플랫폼입니다. 원래 Cisco에서 개발한 상용 코드를 2016년에 오픈소스로 공개하였으며, 현재 Linux Foundation이 호스팅하는 커뮤니티 주도 프로젝트입니다.

VPP의 핵심 설계 철학은 벡터 패킷 처리(Vector Packet Processing)입니다. 전통적인 커널 네트워크 스택이 패킷 하나씩 전체 처리 경로를 순회하는 scalar 방식인 반면, VPP는 여러 패킷을 하나의 벡터(배열)로 묶어서 동일한 처리 노드를 일괄 통과시킵니다. 이를 통해 CPU의 명령어 캐시(I-cache) 히트율을 극대화하고, 분기 예측(branch prediction) 효율을 높여 범용 x86 CPU에서도 초당 수천만 패킷(수십 Mpps)을 처리할 수 있습니다.

FD.io / Linux Foundation 역사

연도주요 이벤트
2002~Cisco 내부에서 VPP 엔진 개발 시작 (상용 라우터/스위치용)
2016.02Linux Foundation 산하 FD.io 프로젝트로 오픈소스 공개
2017VPP 17.01 릴리스, DPDK 통합 본격화
2018Kubernetes CNI (Contiv-VPP) 지원, vhost-user 안정화
2020AF_XDP 호스트 인터페이스 지원, rdma 플러그인
2022~5G UPF 유스케이스 확대, SRv6 고도화, VPP 24.x 시리즈

패킷 처리 프레임워크 비교

항목커널 스택DPDK (raw)VPPOvS-DPDK
실행 공간커널유저스페이스유저스페이스유저스페이스
처리 모델Scalar (패킷 단위)Poll-mode (배치)Vector (그래프 노드)Flow-based (Megaflow)
성능 (64B, 단일 코어)~1 Mpps~15 Mpps~12 Mpps~8 Mpps
L2/L3 스택완전 내장직접 구현 필요완전 내장L2 + OpenFlow
확장성커널 모듈라이브러리플러그인 시스템OpenFlow 규칙
커널 통합네이티브UIO/VFIOTAP/vhost/AF_XDPvhost-user
라이선스GPL-2.0BSD/LGPLApache 2.0Apache 2.0

벡터 패킷 처리 모델

Scalar 처리 vs Vector 처리

전통적인 커널 네트워크 스택의 Scalar 처리는 패킷 하나를 수신하면 L2 → L3 → L4 → 소켓 전달까지 전체 경로를 완주한 후 다음 패킷을 처리합니다. 각 단계마다 다른 함수 코드가 I-cache에 로드되므로 캐시 미스가 빈번합니다.

VPP의 Vector 처리는 다수의 패킷(벡터)을 동시에 한 노드에서 처리한 후, 다음 노드로 벡터 전체를 넘깁니다. 동일한 코드가 수백 패킷에 반복 적용되므로 I-cache에 상주하며, 분기 예측이 안정되어 파이프라인 버블이 최소화됩니다.

Scalar 처리 vs Vector 처리 Scalar 처리 (커널) Pkt 1 L2 L3 L4 완료 Pkt 2 L2 L3 ... 순차 반복 I-cache miss ↕ Vector 처리 (VPP) Vector (256 pkts) Pkt 1, Pkt 2, ... ... Pkt N L2 Node 모든 패킷 일괄 L3 Node 모든 패킷 일괄 L4 Node 모든 패킷 일괄 완료 ✓ I-cache hot — 동일 코드 반복 실행 Scalar: 패킷마다 전체 경로 순회 → I-cache 냉각 | Vector: 노드별 일괄 처리 → I-cache 상주 VPP 기본 벡터 크기: VLIB_FRAME_SIZE = 256

캐시 효율성과 성능

VPP의 성능 우위는 CPU 마이크로아키텍처 수준의 최적화에서 비롯됩니다:

/* VPP 노드의 전형적인 dual-loop 패턴 (간략화) */
while (n_left_from >= 4) {
    /* 다음 2개 패킷 프리페치 */
    vlib_buffer_t *p2, *p3;
    p2 = vlib_get_buffer(vm, from[2]);
    p3 = vlib_get_buffer(vm, from[3]);
    CLIB_PREFETCH(p2->data, CLIB_CACHE_LINE_BYTES, LOAD);
    CLIB_PREFETCH(p3->data, CLIB_CACHE_LINE_BYTES, LOAD);

    /* 현재 2개 패킷 처리 */
    vlib_buffer_t *b0, *b1;
    b0 = vlib_get_buffer(vm, from[0]);
    b1 = vlib_get_buffer(vm, from[1]);

    /* 패킷 처리 로직... */
    next0 = process_packet(b0);
    next1 = process_packet(b1);

    vlib_validate_buffer_enqueue_x2(vm, node, next_index,
                                     to_next, n_left_to_next,
                                     bi0, bi1, next0, next1);
    from += 2;
    n_left_from -= 2;
}

쿼드 루프 패턴과 SIMD 최적화

VPP 노드의 가장 최적화된 형태는 quad-loop 패턴입니다. 4개의 패킷을 동시에 처리하여 CPU의 ILP(Instruction-Level Parallelism)를 극대화하고, 프리페치 거리를 확보합니다. 컴파일러가 SIMD 벡터화를 적용할 가능성도 높아집니다.

/* quad-loop 패턴 — 최고 성능 경로 */
while (n_left_from >= 8) {
    /* 8개 앞 패킷 프리페치 (4개 처리 동안 캐시 로드) */
    CLIB_PREFETCH(vlib_get_buffer(vm, from[4])->data,
                  CLIB_CACHE_LINE_BYTES, LOAD);
    CLIB_PREFETCH(vlib_get_buffer(vm, from[5])->data,
                  CLIB_CACHE_LINE_BYTES, LOAD);
    CLIB_PREFETCH(vlib_get_buffer(vm, from[6])->data,
                  CLIB_CACHE_LINE_BYTES, LOAD);
    CLIB_PREFETCH(vlib_get_buffer(vm, from[7])->data,
                  CLIB_CACHE_LINE_BYTES, LOAD);

    /* 현재 4개 패킷 일괄 처리 */
    vlib_buffer_t *b0, *b1, *b2, *b3;
    b0 = vlib_get_buffer(vm, from[0]);
    b1 = vlib_get_buffer(vm, from[1]);
    b2 = vlib_get_buffer(vm, from[2]);
    b3 = vlib_get_buffer(vm, from[3]);

    next0 = process_packet(b0);
    next1 = process_packet(b1);
    next2 = process_packet(b2);
    next3 = process_packet(b3);

    vlib_validate_buffer_enqueue_x4(vm, node, next_index,
        to_next, n_left_to_next,
        bi0, bi1, bi2, bi3, next0, next1, next2, next3);
    from += 4;
    n_left_from -= 4;
}

/* single-loop: 나머지 패킷 처리 (1~3개) */
while (n_left_from > 0) {
    vlib_buffer_t *b0 = vlib_get_buffer(vm, from[0]);
    next0 = process_packet(b0);
    vlib_validate_buffer_enqueue_x1(vm, node, next_index,
        to_next, n_left_to_next, bi0, next0);
    from += 1;
    n_left_from -= 1;
}
루프 패턴패킷/반복프리페치 거리ILP 활용적합한 상황
Single-loop10~1낮음복잡한 상태 기계, 잔여 패킷 처리
Dual-loop22중간중간 복잡도 노드, 분기 많은 처리
Quad-loop44~8높음단순 처리 (lookup, rewrite, forward)
패턴 선택: 노드의 처리 로직이 단순할수록 quad-loop이 유리합니다. ip4-lookup, l2-fwd 같은 핫 패스 노드는 quad-loop을 사용하고, 복잡한 상태 머신이 필요한 tcp-input은 single-loop으로 구현합니다.

벡터 크기 튜닝과 영향

VLIB_FRAME_SIZE는 한 번에 노드에 전달되는 최대 패킷 수를 결정합니다. 기본값 256은 대부분의 상황에서 최적이지만, 특수 워크로드에서는 조정이 필요할 수 있습니다.

벡터 크기I-cache 효과처리량테일 레이턴시메모리 사용
64보통낮음최소 (좋음)낮음
128좋음중간낮음중간
256 (기본)최적높음중간중간
512최적최고높음 (나쁨)높음
벡터 크기와 테일 레이턴시: 벡터가 클수록 처리량은 높아지지만, 벡터의 마지막 패킷은 첫 패킷이 노드에 도착한 후 전체 벡터 처리가 끝날 때까지 대기합니다. VoIP/실시간 트래픽이 중요한 환경에서는 벡터 크기를 줄여 테일 레이턴시를 낮출 수 있습니다.

VPP 아키텍처

VPP 패킷 처리 그래프 아키텍처 NIC DPDK/AF_XDP dpdk-input INPUT node ethernet-input L2 분류 ip4-input IPv4 처리 ip4-lookup FIB 검색 ip4-rewrite MAC 재작성 NAT44 ACL ip6-input IPv6 처리 l2-input L2 브릿징 l2-fwd MAC 학습/포워딩 interface-output TX queue 범례: INPUT/INTERNAL 노드 L3 처리 노드 기능 플러그인 (ACL, NAT...)

패킷 처리 그래프 (Graph Node Architecture)

VPP의 핵심은 방향성 비순환 그래프(DAG) 기반 패킷 처리 엔진입니다. 각 처리 단계는 노드(Node)로 구현되며, 노드 간 연결은 arc로 표현됩니다. 패킷(벡터)은 노드에서 노드로 arc를 따라 이동하며, 각 노드는 패킷의 다음 목적지 노드를 결정합니다.

/* 그래프 노드 등록 예제 — src/vnet/ethernet/node.c */
VLIB_REGISTER_NODE (ethernet_input_node) = {
    .function = ethernet_input,          /* 노드 처리 함수 */
    .name = "ethernet-input",
    .vector_size = sizeof(u32),          /* 벡터 원소 크기 (버퍼 인덱스) */
    .n_errors = ETHERNET_N_ERROR,
    .error_strings = ethernet_error_strings,
    .n_next_nodes = ETHERNET_INPUT_N_NEXT,
    .next_nodes = {
        [ETHERNET_INPUT_NEXT_IP4_INPUT] = "ip4-input",
        [ETHERNET_INPUT_NEXT_IP6_INPUT] = "ip6-input",
        [ETHERNET_INPUT_NEXT_L2_INPUT]  = "l2-input",
        [ETHERNET_INPUT_NEXT_DROP]      = "error-drop",
    },
};

노드 유형 (INTERNAL, INPUT, PROCESS, PRE_INPUT)

노드 유형설명실행 방식대표 예
VLIB_NODE_TYPE_INTERNAL그래프 중간 처리 노드이전 노드가 벡터를 전달할 때 실행ip4-input, ip4-lookup, l2-fwd
VLIB_NODE_TYPE_INPUT패킷 입력 노드매 메인 루프마다 폴링dpdk-input, af-packet-input
VLIB_NODE_TYPE_PROCESS코루틴 기반 프로세스이벤트/타이머 기반 실행dhcp-client-process, arp-reply
VLIB_NODE_TYPE_PRE_INPUT입력 전 처리INPUT 노드보다 먼저 실행epoll-input (이벤트 폴링)

벡터 크기와 처리 흐름

VPP의 벡터 프레임 크기는 VLIB_FRAME_SIZE로 정의되며, 기본값은 256입니다. 메인 루프(vlib_main_loop)는 다음 순서로 동작합니다:

  1. PRE_INPUT 노드 실행 (epoll 이벤트 수집 등)
  2. INPUT 노드 실행 (dpdk-input이 NIC에서 패킷 수집 → 벡터 생성)
  3. INTERNAL 노드: 보류 중인 프레임이 있는 노드를 순회하며 벡터 처리
  4. PROCESS 노드: 타이머/이벤트 만료된 코루틴 실행
  5. 1번으로 돌아감 (busy-loop 또는 sleep)
성능 팁: VPP의 메인 루프는 기본적으로 busy-polling이며, 패킷이 없을 때도 CPU를 100% 사용합니다. startup.confpoll-sleep-usec 옵션으로 유휴 시 절전 모드를 활성화할 수 있지만, 레이턴시가 증가할 수 있습니다.

vlib_buffer_t 구조체 상세

vlib_buffer_t는 VPP에서 패킷 데이터를 관리하는 핵심 구조체로, 커널의 sk_buff에 대응합니다. 정확히 2개의 캐시라인(128바이트)으로 설계되어 캐시 효율을 극대화합니다.

vlib_buffer_t 메모리 레이아웃 (2 캐시라인) Cache Line 0 (오프셋 0x00 ~ 0x3F, 64바이트) — 핫 필드 current_data (i16) current_length (u16) flags (u32) flow_id (u32) next_buffer (u32) current_config_index error (u16) n_add_refs (u8) buffer_pool_index opaque[10] (u32×10, 40바이트) ← 플러그인별 메타데이터 (ip, tcp 등) Cache Line 1 (오프셋 0x40 ~ 0x7F, 64바이트) — 콜드 필드 opaque2[16] (u32×16, 64바이트) ← 추가 메타데이터 (vnet, trace 등) trace_handle (u32) ← 패킷 트레이싱 연결 데이터 영역 (오프셋 0x80+) pre_data → [ headroom ] → data[0..current_length] → 구조체 크기: 128바이트 (2 캐시라인) + 패킷 데이터 (기본 2048바이트)
필드오프셋크기용도
current_data0x00i16data[0]부터 현재 패킷 시작 위치까지의 오프셋
current_length0x02u16현재 버퍼의 유효 데이터 길이
flags0x04u32VLIB_BUFFER_TOTAL_LENGTH_VALID, IS_TRACED 등 플래그
flow_id0x08u32NIC RSS 해시 또는 플로우 분류 ID
next_buffer0x0Cu32체인 버퍼의 다음 버퍼 인덱스 (점보 프레임)
current_config_index0x10u32Feature arc 설정 인덱스
error0x14u16노드별 에러 코드 인덱스
n_add_refs0x16u8추가 참조 카운트 (복제 시)
buffer_pool_index0x17u8버퍼가 속한 풀 인덱스
opaque[10]0x1840B노드별 메타데이터 (ip4_header_t *, adjacency index 등)
opaque2[16]0x4064B확장 메타데이터 (vnet 계층, 트레이스 정보)
trace_handleu32show trace 결과와 연결되는 핸들
/* vlib_buffer_t 핵심 접근 API */

/* 버퍼 인덱스(u32)로 버퍼 포인터 획득 */
vlib_buffer_t *b = vlib_get_buffer(vm, buffer_index);

/* 현재 데이터 시작 포인터 */
void *data = vlib_buffer_get_current(b);

/* 헤더 추가/제거 (current_data 이동) */
vlib_buffer_advance(b, -sizeof(ethernet_header_t));  /* 헤더 추가 */
vlib_buffer_advance(b, sizeof(ip4_header_t));       /* 헤더 제거 */

/* 체인 버퍼의 전체 길이 */
u32 total = vlib_buffer_length_in_chain(vm, b);

/* 버퍼 복제 (멀티캐스트 등) */
u32 clone_bi;
vlib_buffer_clone(vm, buffer_index, &clone_bi, 1, CLIB_CACHE_LINE_BYTES);
/* opaque 영역 사용 예: ip4 노드가 저장하는 메타데이터 */
typedef struct {
    ip4_header_t *ip_header;
    u32 adj_index;
    u32 flow_hash;
    u32 fib_index;
} ip4_buffer_opaque_t;

/* opaque 접근 매크로 */
#define vnet_buffer(b) ((vnet_buffer_opaque_t *)(b)->opaque)
#define vnet_buffer2(b) ((vnet_buffer_opaque2_t *)(b)->opaque2)
버퍼 풀 고갈 증상: show buffers에서 free 카운트가 0에 가까워지면, 패킷이 error-drop 노드로 전달되며 show errorsno-buffer 에러가 증가합니다. buffers-per-numa 값을 늘리거나 hugepage를 추가 할당하세요.

메인 루프 내부 동작

VPP의 vlib_main_loop()는 모든 패킷 처리의 진입점입니다. 단일 스레드 내에서 4단계를 반복 실행하며, 각 단계의 순서가 성능에 직접 영향을 미칩니다.

VPP 메인 루프 (vlib_main_loop) 흐름 1. PRE_INPUT 노드 epoll, timer wheel 체크 2. INPUT 노드 폴링 dpdk-input → 벡터 생성 3. INTERNAL 노드 디스패치 보류 프레임이 있는 노드를 순회 ethernet-input → ip4-input → ip4-lookup → ... 4. PROCESS 노드 코루틴: ARP, DHCP, BGP 등 poll-sleep-usec > 0이면 sleep 단계별 소요 시간 PRE_INPUT: ~0.1 μs INPUT: ~2~5 μs (NIC 폴링) INTERNAL: 벡터 크기에 비례 PROCESS: 이벤트 시에만 실행 busy-loop 반복 루프 1회 = 1 dispatch cycle | show runtime의 Clocks/Call = INTERNAL 노드 기준
/* vlib_main_loop 의사코드 (src/vlib/main.c) */
static void
vlib_main_loop (vlib_main_t *vm) {
    while (1) {
        /* 1단계: PRE_INPUT — epoll, 시그널 체크 */
        vlib_node_runtime_update_stats(vm);
        dispatch_pre_input_nodes(vm);

        /* 2단계: INPUT — NIC 폴링, 벡터 수집 */
        dispatch_input_nodes(vm);
        /* dpdk-input이 NIC RX 링에서 패킷 배치를 가져와
           ethernet-input으로 향하는 프레임을 생성 */

        /* 3단계: INTERNAL — 보류 프레임 디스패치 */
        for (pending_frame in vm->pending_frames) {
            node = pending_frame->node;
            frame = pending_frame->frame;
            node->function(vm, node, frame);
            /* 노드가 새 프레임을 생성하면 pending 추가 */
        }

        /* 4단계: PROCESS — 코루틴 타이머/이벤트 체크 */
        dispatch_process_nodes(vm);

        /* 유휴 시 선택적 sleep */
        if (no_work && vm->poll_sleep_usec)
            usleep(vm->poll_sleep_usec);
    }
}
루프 단계실행 노드 유형실행 빈도주요 동작
PRE_INPUTVLIB_NODE_TYPE_PRE_INPUT매 사이클epoll 이벤트 수집, 타이머 휠 갱신
INPUTVLIB_NODE_TYPE_INPUT매 사이클NIC 폴링, 패킷 벡터 생성
INTERNALVLIB_NODE_TYPE_INTERNAL프레임 존재 시그래프 노드 순회, 패킷 처리
PROCESSVLIB_NODE_TYPE_PROCESS이벤트/타이머 시코루틴 재개 (ARP, DHCP 등)

Feature Arc 메커니즘

Feature Arc는 VPP의 핵심 확장 메커니즘으로, 패킷 처리 경로에 기능을 동적으로 삽입하거나 제거할 수 있게 합니다. 커널의 Netfilter 후크 체인과 유사한 개념이지만, 컴파일 타임이 아닌 런타임에 노드 체인을 재구성합니다.

Feature Arc: ip4-unicast 경로 동적 삽입 기본 (Feature 없음): ip4-input ip4-lookup ip4-rewrite Feature Arc 활성 (ACL + NAT + IPsec 삽입): ip4-input ACL ip4-inacl NAT44 nat44-in2out IPsec ipsec-input ip4-lookup ip4-rewrite Feature Arc은 인터페이스별로 활성화 → 패킷의 current_config_index가 체인을 따라 이동 주요 내장 Feature Arc: ip4-unicast | ip4-multicast | ip4-output | ip6-unicast | ip6-multicast | ip6-output ethernet-output | interface-output | mpls-input | mpls-output | nsh-output
/* Feature Arc에 노드 등록 (VNET_FEATURE_INIT 매크로) */
VNET_FEATURE_INIT (my_acl_feature, static) = {
    .arc_name = "ip4-unicast",       /* 소속 Feature Arc */
    .node_name = "my-acl-node",       /* 노드 이름 */
    .runs_before = VNET_FEATURES("ip4-lookup"),  /* 이 노드보다 먼저 실행 */
    .runs_after  = VNET_FEATURES("ip4-input-no-checksum"),
};

/* 런타임에 인터페이스별 Feature 활성화 */
vpp# set interface feature GigabitEthernet0/8/0 my-acl-node arc ip4-unicast

/* Feature Arc 상태 확인 */
vpp# show features verbose
Feature Arc시작 노드기본 종착 노드용도
ip4-unicastip4-inputip4-lookupIPv4 유니캐스트 인입 처리
ip4-outputip4-rewriteinterface-outputIPv4 송출 처리
ip4-multicastip4-inputip4-mfib-forward-lookupIPv4 멀티캐스트
ip6-unicastip6-inputip6-lookupIPv6 유니캐스트 인입
ethernet-outputadj-l2-midchaininterface-output이더넷 출력 경로
interface-outputinterface-outputinterface-tx최종 인터페이스 출력

메모리 관리 아키텍처

힙/mheap 관리

VPP는 여러 개의 독립적인 메모리 영역을 관리합니다. 모든 영역은 hugepage 위에 할당되며, clib_mem 인프라가 NUMA 인식 할당을 제공합니다.

메모리 영역기본 크기용도설정 위치
Main Heap1 GBFIB, 세션 테이블, 일반 할당memory { main-heap-size 2G }
Buffer PoolsNUMA별 개별패킷 데이터 (vlib_buffer_t + data)buffers { buffers-per-numa 32768 }
API Segment64 MBBinary API 공유 메모리api-segment { global-size 256M }
Stats Segment32 MB통계 카운터 공유 메모리statseg { size 64M }
# VPP 메모리 사용량 확인
vpp# show memory main-heap
Thread 0 vpp_main
  base 0x7f8a00000000, size 1g, locked, unmap-on-destroy
    page stats: page-size 2M, total 512, mapped 310, not-mapped 202
    total: 1073741824, used: 536870912, free: 536870912

vpp# show memory api-segment
  Shared API segment:
    base 0x7f8900000000, size 64m

# NUMA별 메모리 분포
vpp# show memory numa

버퍼 풀 아키텍처

VPP 버퍼 풀은 고성능 패킷 처리의 핵심입니다. NUMA 노드별로 독립된 풀을 유지하여 원격 메모리 접근을 방지하고, 프리 리스트와 재활용 메커니즘으로 할당/해제 오버헤드를 최소화합니다.

VPP 버퍼 풀 아키텍처 (per-NUMA) NUMA Node 0 Hugepages (2MB/1GB 페이지) Buffer Pool 0 buf 0 buf 1 buf 2 ... buf N Free List (LIFO) Thread 0 캐시 32개 버퍼 로컬 Thread 1 캐시 32개 버퍼 로컬 NUMA Node 1 Hugepages (2MB/1GB 페이지) Buffer Pool 1 buf 0 buf 1 buf 2 ... buf N Free List (LIFO) Thread 2 캐시 32개 버퍼 로컬 Thread 3 캐시 32개 버퍼 로컬 버퍼 인덱스(u32)로 접근 — 포인터 대신 인덱스를 사용하여 32비트로 버퍼 참조 (메모리 절약)
/* 버퍼 풀 생성과 생명주기 */

/* 1. 시작 시: hugepage 위에 버퍼 풀 사전 할당 */
/*    buffers { buffers-per-numa 32768 } → NUMA별 32768개 버퍼 */

/* 2. INPUT 노드: 버퍼 할당 */
u32 n_alloc = vlib_buffer_alloc(vm, buffer_indices, n_buffers);
/* 스레드 로컬 캐시에서 먼저 할당 → 부족 시 글로벌 free list에서 보충 */

/* 3. 패킷 처리 중: 버퍼 체인 (점보 프레임 등) */
if (b->flags & VLIB_BUFFER_NEXT_PRESENT)
    next_b = vlib_get_buffer(vm, b->next_buffer);

/* 4. 처리 완료: 버퍼 해제 (free list 반환) */
vlib_buffer_free(vm, buffer_indices, n_buffers);
/* 로컬 캐시가 가득 차면 글로벌 free list로 반환 */
포인터 대신 인덱스(u32): VPP는 버퍼를 64비트 포인터 대신 32비트 인덱스로 참조합니다. 벡터 프레임의 각 원소가 4바이트(u32)이므로, 256개 패킷 벡터가 1KB만 차지합니다. 포인터라면 2KB가 필요합니다. 이 설계는 캐시 라인 효율을 높이고, 프레임 복사/이동 비용을 절반으로 줄입니다.

멀티스레딩 아키텍처

워커 스레드 모델

VPP는 메인 스레드 + N개 워커 스레드 모델을 사용합니다. 메인 스레드는 CLI/API 처리와 제어 평면을 담당하고, 워커 스레드는 데이터 평면(패킷 처리)에 전담합니다. 각 워커는 독립된 메인 루프를 실행합니다.

VPP 멀티스레딩 아키텍처 NIC RX Queue 0 RX Queue 1 RX Queue 2 Main Thread (Core 0) CLI, API, 제어 평면, PROCESS 노드 Worker 0 (Core 2) dpdk-input → graph dispatch → interface-output Worker 1 (Core 4) dpdk-input → graph dispatch → interface-output Worker 2 (Core 6) dpdk-input → graph dispatch → interface-output Frame Handoff Worker 0 → Worker 1 (플로우 기반) lockfree ring per-thread queue Barrier Sync 설정 변경 시 전체 동기화 각 워커 = 독립 메인 루프 + 전용 RX 큐 | 워커 간 공유 데이터 없음 (lockfree)
항목Main ThreadWorker Thread
CPU 코어main-core (보통 0)corelist-workers (2,4,6...)
역할CLI, API, 제어 평면패킷 처리 (데이터 평면)
INPUT 노드실행 가능 (기본 비활성)dpdk-input 폴링
PROCESS 노드ARP, DHCP, BGP 등실행하지 않음
NIC 큐 매핑1:1 (큐 N → 워커 N)
공유 데이터 접근배리어 내에서 수정읽기 전용 (RCU 유사)
/* startup.conf — 스레딩 설정 */
cpu {
    main-core 0                /* 메인 스레드: 코어 0 */
    corelist-workers 2,4,6,8   /* 워커 4개: 물리 코어만 (HT 제외) */
    skip-cores 1               /* 코어 1 건너뛰기 (OS 예약) */
    scheduler-policy fifo      /* RT 스케줄링 (선택) */
    scheduler-priority 50      /* RT 우선순위 */
}

/* 워커 수에 따라 NIC RX 큐를 자동 매핑 */
dpdk {
    dev 0000:03:00.0 {
        num-rx-queues 4        /* 워커 수와 일치 */
    }
}

프레임 핸드오프

특정 처리(NAT 세션 룩업, IPsec SA 선택 등)는 플로우별로 동일 워커에서 처리해야 합니다. RSS가 원하는 분산을 제공하지 못할 때, frame handoff 노드가 패킷을 적절한 워커로 재분배합니다.

# handoff 노드 삽입 (nat44 이전에 플로우 해시 기반 재분배)
vpp# set interface handoff GigabitEthernet0/8/0 workers 0-3 symmetrical

# handoff 큐 상태 확인
vpp# show handoff
  worker 0: 250000 frames, 0 drops
  worker 1: 248000 frames, 0 drops
  worker 2: 251000 frames, 0 drops
  worker 3: 249000 frames, 0 drops
핸드오프 큐 오버플로: 특정 워커가 과부하되면 handoff 큐가 가득 차서 패킷이 드롭됩니다. show handoff에서 drops가 증가하면, show runtime으로 해당 워커의 병목 노드를 확인하세요. RSS 해시가 불균형한 경우가 흔합니다.

배리어 동기화

VPP의 데이터 평면은 lockfree로 동작하지만, FIB 업데이트나 인터페이스 추가 같은 제어 평면 변경은 모든 워커를 일시 정지시키는 배리어 동기화가 필요합니다.

작업배리어 필요이유
FIB 엔트리 추가/삭제필요mtrie 구조체 수정
인터페이스 생성/삭제필요sw_interface 벡터 재할당
ACL 규칙 변경필요ACL 매치 테이블 교체
통계 카운터 읽기불필요per-thread 카운터 (lockfree)
패킷 트레이싱불필요per-thread 트레이스 버퍼
show runtime불필요읽기 전용
배리어 장기 홀드: 배리어가 활성화되면 모든 워커의 패킷 처리가 정지합니다. show barrier로 배리어 점유 시간을 모니터링하세요. 대량의 FIB 업데이트(BGP 풀 테이블 로딩 등)는 배리어를 장시간 홀드하여 패킷 드롭을 유발할 수 있습니다. FIB 배치 업데이트 API를 사용하면 배리어 점유를 최소화합니다.

DPDK 통합

VPP는 DPDK(Data Plane Development Kit)를 기본 패킷 I/O 백엔드로 사용합니다. DPDK는 커널의 네트워크 드라이버를 우회하여 NIC 하드웨어에 직접 접근하는 Poll Mode Driver(PMD)를 제공합니다.

DPDK 드라이버 바인딩

DPDK PMD를 사용하려면 NIC를 커널 드라이버에서 분리(unbind)하고 UIO 또는 VFIO 드라이버에 바인딩해야 합니다:

# 현재 NIC 드라이버 확인
$ lspci -k -s 0000:03:00.0
03:00.0 Ethernet controller: Intel Corporation 82599ES ...
    Kernel driver in use: ixgbe

# VFIO-PCI 드라이버로 바인딩 (IOMMU 필요)
$ modprobe vfio-pci
$ dpdk-devbind --bind=vfio-pci 0000:03:00.0

# 또는 UIO 드라이버 (IOMMU 불필요, 보안 취약)
$ modprobe uio_pci_generic
$ dpdk-devbind --bind=uio_pci_generic 0000:03:00.0

# 상태 확인
$ dpdk-devbind --status
커널 관점: VFIO(drivers/vfio/)는 IOMMU를 통한 디바이스 격리를 제공하여 DMA 공격을 방지합니다. UIO(drivers/uio/)는 IOMMU 없이 동작하지만, 잘못된 DMA 요청이 임의 메모리를 덮어쓸 수 있어 프로덕션에서는 VFIO를 권장합니다.

Hugepages 설정

DPDK와 VPP는 Hugepages를 사용하여 TLB 미스를 줄이고 메모리 접근 성능을 높입니다. 커널의 hugetlbfs(fs/hugetlbfs/)를 활용합니다:

# 부팅 시 1GB hugepage 할당 (GRUB)
GRUB_CMDLINE_LINUX="default_hugepagesz=1G hugepagesz=1G hugepages=4 iommu=pt intel_iommu=on"

# 런타임 2MB hugepage 할당
$ echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

# hugetlbfs 마운트 (VPP가 자동으로 사용)
$ mount -t hugetlbfs none /dev/hugepages

# 할당 상태 확인
$ cat /proc/meminfo | grep -i huge
HugePages_Total:    1024
HugePages_Free:      512
Hugepagesize:       2048 kB

멀티큐와 RSS

최신 NIC은 여러 개의 RX/TX 큐를 제공하며, RSS(Receive Side Scaling)를 통해 패킷을 큐에 분산합니다. VPP는 각 워커 스레드에 큐를 할당하여 병렬 처리합니다:

/* startup.conf — DPDK 멀티큐 설정 */
dpdk {
    dev 0000:03:00.0 {
        num-rx-queues 4        /* RX 큐 4개 */
        num-tx-queues 4        /* TX 큐 4개 */
    }
}

cpu {
    main-core 0                /* 메인 스레드: 코어 0 */
    corelist-workers 1-3       /* 워커 스레드: 코어 1,2,3 */
}
dpdk-input 노드와 NIC RX 링 상호작용 NIC Hardware RX Descriptor Ring DMA Engine RSS Hash → Queue N Flow Director DMA Hugepage Buffers mbuf 0 mbuf 1 mbuf 2 mbuf 3 zero-copy DMA 대상 poll dpdk-input rte_eth_rx_burst() mbuf → vlib_buffer 변환 벡터 프레임 생성 Vector Frame [bi0, bi1, bi2, ..., biN] → ethernet-input 전달 NIC DMA → hugepage 버퍼 → dpdk-input 폴링 → vlib_buffer 변환 → 벡터 프레임 디스패치

DPDK Crypto 디바이스 연동

VPP는 DPDK의 Cryptodev API를 통해 하드웨어/소프트웨어 암호화 가속을 지원합니다. IPsec의 AES-GCM, ChaCha20-Poly1305 등의 암호화 연산을 전용 하드웨어(QAT, Mellanox ConnectX 등)로 오프로드하거나, 소프트웨어 라이브러리(IPSec-MB, OpenSSL)를 사용합니다.

/* startup.conf — Crypto 디바이스 설정 */
dpdk {
    dev 0000:03:00.0             /* 네트워크 NIC */
    dev 0000:0b:00.0 {           /* Intel QAT 디바이스 */
        num-rx-queues 2
    }
    uio-driver vfio-pci
}

/* VPP crypto 엔진 설정 */
vpp# set crypto handler aes-256-gcm ipsecmb
vpp# show crypto handlers
크립토 엔진유형지원 알고리즘성능 (1500B 기준)비고
nativeSW (VPP 내장)AES-GCM (AES-NI)~15 Gbps/코어x86 AES-NI 필수
ipsecmbSW (Intel)AES-CBC/GCM, SHA, ChaCha20~12 Gbps/코어Intel 최적화 라이브러리
opensslSW모든 OpenSSL 알고리즘~8 Gbps/코어범용, 호환성 우수
dpdk-cryptodevHW디바이스 의존~40+ GbpsQAT, Mellanox 등
HW vs SW 크립토: 소규모 패킷(64~256B)에서는 HW 오프로드의 DMA 전송 지연이 SW 처리보다 느릴 수 있습니다. show crypto engines로 현재 엔진별 처리량을 비교하고, show ipsec sa에서 SA별 바이트/패킷 카운터로 실제 성능을 측정하세요.

플러그인 시스템

VPP는 모듈식 플러그인 아키텍처를 채택하여 기능을 동적으로 확장할 수 있습니다. 각 플러그인은 공유 라이브러리(.so)로 빌드되며, VPP 시작 시 /usr/lib/vpp_plugins/에서 자동 로드됩니다.

플러그인 구조

VPP 플러그인의 기본 디렉터리 구조:

VPP 플러그인 디렉토리 구조 src/plugins/my_plugin/ CMakeLists.txt ← 빌드 설정 (플러그인 등록) my_plugin.h ← 공용 헤더 (구조체, 매크로) my_plugin.c ← 플러그인 초기화 (VLIB_INIT_FUNCTION) my_plugin.api ← Binary API 정의 (.api → .c/.h 자동 생성) node.c ← 그래프 노드 구현 (VLIB_REGISTER_NODE) cli.c ← CLI 커맨드 (VLIB_CLI_COMMAND) setup.pg ← 패킷 생성기 스크립트 (테스트용)

커스텀 플러그인 개발

/* my_plugin.c — 플러그인 초기화 */
#include <vnet/vnet.h>
#include <vlib/vlib.h>
#include <vpp/app/version.h>

typedef struct {
    u32 sw_if_index;
    u32 next_index;
} my_plugin_main_t;

my_plugin_main_t my_plugin_main;

static clib_error_t *
my_plugin_init (vlib_main_t *vm) {
    my_plugin_main_t *mpm = &my_plugin_main;
    mpm->sw_if_index = ~0;
    return 0;
}

VLIB_INIT_FUNCTION(my_plugin_init);

/* 플러그인 등록 매크로 */
VLIB_PLUGIN_REGISTER() = {
    .version = VPP_BUILD_VER,
    .description = "My Custom Plugin",
};
/* node.c — 커스텀 그래프 노드 */
#include <vlib/vlib.h>
#include <vnet/vnet.h>

typedef enum {
    MY_NODE_NEXT_DROP,
    MY_NODE_NEXT_IFACE_OUTPUT,
    MY_NODE_N_NEXT,
} my_node_next_t;

static uword
my_node_fn (vlib_main_t *vm, vlib_node_runtime_t *node,
            vlib_frame_t *frame) {
    u32 n_left_from, *from, *to_next;
    my_node_next_t next_index;

    from = vlib_frame_vector_args(frame);
    n_left_from = frame->n_vectors;
    next_index = node->cached_next_index;

    while (n_left_from > 0) {
        u32 n_left_to_next;
        vlib_get_next_frame(vm, node, next_index,
                           to_next, n_left_to_next);

        while (n_left_from > 0 && n_left_to_next > 0) {
            u32 bi0 = from[0];
            vlib_buffer_t *b0 = vlib_get_buffer(vm, bi0);
            u32 next0 = MY_NODE_NEXT_IFACE_OUTPUT;

            /* 패킷 처리 로직 */
            /* ... */

            to_next[0] = bi0;
            from += 1;
            to_next += 1;
            n_left_from -= 1;
            n_left_to_next -= 1;

            vlib_validate_buffer_enqueue_x1(vm, node, next_index,
                to_next, n_left_to_next, bi0, next0);
        }
        vlib_put_next_frame(vm, node, next_index, n_left_to_next);
    }
    return frame->n_vectors;
}

VLIB_REGISTER_NODE(my_node) = {
    .function = my_node_fn,
    .name = "my-custom-node",
    .vector_size = sizeof(u32),
    .type = VLIB_NODE_TYPE_INTERNAL,
    .n_next_nodes = MY_NODE_N_NEXT,
    .next_nodes = {
        [MY_NODE_NEXT_DROP] = "error-drop",
        [MY_NODE_NEXT_IFACE_OUTPUT] = "interface-output",
    },
};

커널 인터페이스

VPP는 유저스페이스에서 동작하지만, 커널 네트워크 스택과의 연동이 필수적인 경우가 많습니다. VPP는 다양한 커널 인터페이스를 통해 커널과 패킷을 교환합니다.

커널 네트워크 스택 vs VPP 데이터패스 커널 공간 (Kernel Space) 커널 네트워크 스택 NIC 드라이버 (ixgbe...) TAP/TUN vhost AF_PACKET AF_XDP UIO / VFIO (커널 bypass) 유저 공간 (User Space) VPP Process 그래프 노드 엔진 DPDK PMD 플러그인 Binary API CLI (vppctl) Shared Memory Physical NIC (10GbE / 25GbE / 100GbE)

TAP/TUN 인터페이스

TAP 인터페이스는 VPP와 커널 네트워크 스택 간 L2/L3 패킷을 교환하는 가장 일반적인 방법입니다. 커널의 drivers/net/tun.c가 구현합니다.

/* VPP CLI: TAP 인터페이스 생성 */
vpp# create tap id 0 host-if-name vpp-tap0 host-ip4-addr 192.168.1.1/24

/* 호스트에서 확인 */
$ ip addr show vpp-tap0
vpp-tap0: <BROADCAST,MULTICAST,UP> mtu 1500 ...
    inet 192.168.1.1/24 scope global vpp-tap0

/* VPP 측 IP 할당 */
vpp# set interface ip address tap0 192.168.1.2/24
vpp# set interface state tap0 up
virtio 기반 TAP: VPP의 TAP은 내부적으로 virtio 링을 사용합니다. /dev/net/tun + IFF_VNET_HDR 플래그로 virtio 헤더를 포함하여 체크섬 오프로드와 GSO를 지원합니다. 이는 전통적인 TAP보다 성능이 우수합니다.

vhost-user / virtio

vhost-user는 VM과 VPP 간 고성능 패킷 교환을 위한 메커니즘입니다. 공유 메모리와 virtio 링을 사용하여 데이터 복사 없이 패킷을 전달합니다. 커널의 drivers/vhost/ 서브시스템과 유사한 개념이지만, VPP는 유저스페이스에서 vhost 백엔드를 직접 구현합니다.

/* VPP: vhost-user 인터페이스 생성 (서버 모드) */
vpp# create vhost-user socket /var/run/vpp/sock0.sock server
vpp# set interface state VirtualEthernet0/0/0 up

/* QEMU VM 연결 */
$ qemu-system-x86_64 \
    -chardev socket,id=char0,path=/var/run/vpp/sock0.sock \
    -netdev vhost-user,id=net0,chardev=char0 \
    -device virtio-net-pci,netdev=net0

AF_PACKET

AF_PACKET은 커널의 기존 네트워크 인터페이스를 통해 raw 패킷을 송수신하는 소켓 인터페이스입니다(net/packet/af_packet.c). DPDK처럼 NIC를 독점하지 않으므로 기존 커널 스택과 공존 가능합니다.

/* VPP: AF_PACKET 인터페이스 (커널의 eth1에 연결) */
vpp# create host-interface name eth1
vpp# set interface state host-eth1 up
vpp# set interface ip address host-eth1 10.0.0.1/24
용도: AF_PACKET은 DPDK보다 성능이 낮지만(커널 경유), NIC를 커널에서 분리할 필요가 없어 개발/테스트 환경에 적합합니다. PACKET_MMAP 링 버퍼를 사용하여 시스템 콜 오버헤드를 줄입니다.

VPP의 linux-cp(Linux Control Plane) 플러그인은 Netlink를 통해 커널 라우팅 테이블, ARP 엔트리, 인터페이스 상태를 VPP와 동기화합니다. 이를 통해 커널의 ip route, ip neigh 등의 명령이 VPP에도 반영됩니다.

/* linux-cp 플러그인 활성화 (startup.conf) */
plugins {
    plugin linux_cp_plugin.so { enable }
    plugin linux_cp_unittest_plugin.so { enable }
}

linux-cp {
    default netns dataplane
}

/* VPP CLI: 리눅스 인터페이스 미러링 */
vpp# lcp create tap0 host-if lcp-tap0

host-interface (AF_XDP)

AF_XDP(net/xdp/xsk.c)는 커널 5.4+에서 사용 가능한 고성능 패킷 I/O 인터페이스입니다. eBPF/XDP 프로그램과 연계하여 커널 네트워크 스택을 우회하면서도 NIC 드라이버를 교체할 필요가 없습니다.

/* VPP: AF_XDP 인터페이스 생성 */
vpp# create interface af_xdp host-if eth0 num-rx-queues 4
vpp# set interface state af_xdp-eth0 up
DPDK vs AF_XDP: DPDK는 NIC를 커널에서 완전히 분리(UIO/VFIO)하여 최고 성능을 제공하지만 커널 스택과 공존이 불가합니다. AF_XDP는 NIC 드라이버를 유지하면서 XDP 후킹으로 VPP에 패킷을 전달하므로, 일부 트래픽은 커널 스택이 처리하고 특정 트래픽만 VPP로 보낼 수 있습니다.

주요 기능

L2 브릿징/스위칭

VPP는 커널의 net/bridge/와 유사한 L2 브릿지 도메인을 제공합니다. MAC 학습, 플러딩, BUM(Broadcast/Unknown unicast/Multicast) 트래픽 처리를 지원합니다.

/* 브릿지 도메인 생성 및 인터페이스 추가 */
vpp# create bridge-domain 1 learn 1 forward 1 flood 1
vpp# set interface l2 bridge GigabitEthernet0/8/0 1
vpp# set interface l2 bridge GigabitEthernet0/9/0 1
vpp# set interface l2 bridge tap0 1 bvi
vpp# show bridge-domain 1 detail

L3 라우팅 (FIB)

VPP의 FIB(Forwarding Information Base)는 mtrie(multi-way trie) 구조를 사용하여 O(1) 시간에 longest-prefix match를 수행합니다. 8-16-8 stride 구조로 최대 3회의 메모리 접근으로 lookup이 완료됩니다.

FIB mtrie 룩업 흐름 (8-16-8 stride) Dest IP 10.1.2.3 Ply 0 8비트 (256 엔트리) index = 10 Ply 1 16비트 (65536 엔트리) index = 1.2 (0x0102) Ply 2 8비트 (256 엔트리) index = 3 Load Balance → Adjacency → ip4-rewrite mtrie 경로: 첫 8비트 → 다음 16비트 → 마지막 8비트 = 최대 3회 메모리 접근 결과: load-balance 객체 → adjacency → ip4-rewrite 노드에서 MAC 헤더 재작성 FIB 엔트리 유형: attached | attached-host | connected | local | drop | receive | special | deag (재귀 룩업) ECMP: load-balance 객체가 여러 adjacency를 해시 기반으로 분산
/* IP 라우팅 설정 */
vpp# ip route add 10.0.0.0/8 via 192.168.1.1
vpp# ip route add 0.0.0.0/0 via 192.168.1.1

/* ECMP (Equal-Cost Multi-Path) */
vpp# ip route add 10.0.0.0/8 via 192.168.1.1 via 192.168.2.1

/* FIB 테이블 확인 */
vpp# show ip fib
vpp# show ip fib table 0 summary

ACL

VPP의 ACL 플러그인은 stateful/stateless 패킷 필터링을 제공합니다. 커널의 Netfilter/nftables에 대응합니다:

/* ACL 규칙 생성 */
vpp# set acl-plugin acl permit src 192.168.1.0/24 dst 10.0.0.0/8 \
     proto 6 sport 1024-65535 dport 80

/* 인터페이스에 적용 */
vpp# set acl-plugin interface GigabitEthernet0/8/0 input acl 0
vpp# show acl-plugin acl

NAT44/NAT64

NAT44 in2out / out2in 패킷 흐름 내부 (in) 192.168.1.0/24 in2out NAT44 Endpoint-Dependent 세션 테이블 (5-tuple → 변환 규칙) 주소 풀 관리 | 포트 할당 | 세션 타이머 외부 (out) 203.0.113.1 out2in in2out: src 192.168.1.100:45000 → src 203.0.113.1:10001 | out2in: dst 203.0.113.1:10001 → dst 192.168.1.100:45000 ED(Endpoint-Dependent) 모드: 전체 5-tuple 기반 세션 매칭 — NAT44의 권장 모드 Simple 모드(레거시): 3-tuple(src-ip, src-port, proto) — 더 이상 권장하지 않음
ED mode vs Simple mode: VPP 21.06+에서 NAT44은 Endpoint-Dependent (ED) 모드가 기본입니다. ED 모드는 전체 5-tuple(src/dst IP, src/dst port, proto)로 세션을 식별하여 동일 src-ip:port가 다른 목적지로 갈 때도 올바르게 변환합니다. 레거시 simple 모드는 포트 충돌 문제가 있으므로 신규 배포에서 사용하지 마세요.
/* NAT44: 내부 → 외부 주소 변환 */
vpp# nat44 add interface address GigabitEthernet0/8/0
vpp# set interface nat44 in GigabitEthernet0/9/0 out GigabitEthernet0/8/0

/* 정적 매핑 (DNAT) */
vpp# nat44 add static mapping local 192.168.1.100 22 \
     external GigabitEthernet0/8/0 2222 tcp

/* 세션 확인 */
vpp# show nat44 sessions

IPsec

/* IPsec 터널 모드 설정 */
vpp# ipsec sa add 10 spi 1001 esp crypto-alg aes-gcm-256 \
     crypto-key 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \
     tunnel src 10.0.0.1 dst 10.0.0.2

vpp# ipsec sa add 20 spi 1002 esp crypto-alg aes-gcm-256 \
     crypto-key fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210 \
     tunnel src 10.0.0.2 dst 10.0.0.1

vpp# ipsec policy add spd 1 priority 100 outbound action protect \
     sa 10 local-ip-range 192.168.1.0 - 192.168.1.255 \
     remote-ip-range 192.168.2.0 - 192.168.2.255

Segment Routing (SRv6)

/* SRv6 정책 설정 */
vpp# sr localsid address fc00::1 behavior end
vpp# sr policy add bsid fc00::999 next fc00::2 next fc00::3 encap
vpp# sr steer l3 10.0.0.0/8 via bsid fc00::999
알고리즘유형nativeipsecmbopensslQAT HW
AES-GCM-128/256AEADOOOO
AES-CBC-128/256암호화OOO
ChaCha20-Poly1305AEADOO
HMAC-SHA-256/512인증OOO
AES-CTR암호화OOO
NULL테스트OOO

QoS (Quality of Service)

VPP는 패킷 마킹, 폴리싱, 스케줄링 기능을 제공합니다:

# 폴리서 생성 (2r3c: 2-rate 3-color marker)
vpp# configure policer name rate-limiter cir 100000 cb 10000 \
     eir 200000 eb 20000 rate kbps color-aware

# 인터페이스에 폴리서 적용
vpp# set policer classify interface GigabitEthernet0/8/0 ip4-table 0
vpp# policer input rate-limiter GigabitEthernet0/8/0

# QoS 마킹 (DSCP 설정)
vpp# set qos record interface GigabitEthernet0/8/0 input ip
vpp# set qos mark interface GigabitEthernet0/9/0 output ip table 0

# 상태 확인
vpp# show policer
vpp# show qos record
vpp# show qos mark
QoS 기능설명노드
Policer2r3c (RFC 2698) 기반 트래픽 폴리싱policer-input
QoS Record인입 패킷의 QoS 값(DSCP/MPLS TC) 기록qos-record
QoS Mark송출 패킷에 QoS 값 마킹qos-mark
QoS Store패킷에 고정 QoS 값 저장qos-store

VXLAN / GENEVE 터널

# VXLAN 터널 생성
vpp# create vxlan tunnel src 10.0.0.1 dst 10.0.0.2 vni 100
vpp# set interface state vxlan_tunnel0 up

# 브릿지 도메인에 추가 (L2 오버레이)
vpp# set interface l2 bridge vxlan_tunnel0 100
vpp# set interface l2 bridge GigabitEthernet0/9/0 100

# GENEVE 터널 생성
vpp# create geneve tunnel src 10.0.0.1 dst 10.0.0.2 vni 200
vpp# set interface state geneve_tunnel0 up

# 터널 상태 확인
vpp# show vxlan tunnel
vpp# show geneve tunnel
VXLAN GPE vs 표준 VXLAN: 표준 VXLAN(RFC 7348)은 L2 프레임만 캡슐화하지만, VXLAN-GPE(Generic Protocol Extension, draft-ietf-nvo3-vxlan-gpe)는 L3/NSH/MPLS 등 다양한 프로토콜을 캡슐화합니다. VPP는 VXLAN-GPE + SRv6 조합으로 SFC(Service Function Chaining)를 구현합니다.

VCL과 세션 레이어

VPP 세션 레이어

VPP는 L4 전송 프로토콜(TCP, UDP, QUIC)을 유저스페이스에서 직접 구현합니다. 세션 레이어가 소켓과 유사한 추상화를 제공하며, 애플리케이션은 VCL(VPP Communications Library)을 통해 접근합니다.

항목커널 소켓 APIVPP 세션 API
APIsocket(), bind(), listen(), accept()공유 메모리 기반 세션 큐
데이터 전달send()/recv() (커널 복사)공유 메모리 FIFO (zero-copy)
이벤트epoll/selectVPP 이벤트 큐 (eventfd)
멀티플렉싱epoll_wait()vcl_epoll_wait()
성능~200K conn/s~1M+ conn/s
호환성모든 애플리케이션VCL 또는 LD_PRELOAD 필요
/* startup.conf — 세션 레이어 활성화 */
session {
    evt_qs_memfd_seg              /* memfd 기반 이벤트 큐 */
    event-queue-length 100000    /* 이벤트 큐 크기 */
}

/* 세션 활성화 확인 */
vpp# session enable
vpp# show session verbose

LD_PRELOAD 투명 가속

VCL의 LD_PRELOAD 기능은 기존 POSIX 소켓 기반 애플리케이션을 수정 없이 VPP 세션 레이어로 가속합니다. libvcl_ldpreload.solibc의 소켓 함수를 가로채어 VPP와 통신합니다.

VCL LD_PRELOAD 투명 가속 아키텍처 Application nginx, iperf3, curl ... socket()/send()/recv() libvcl_ldpreload.so POSIX → VCL 변환 공유 메모리 FIFO VPP Process Session Layer TCP/UDP 스택 DPDK / NIC wire-speed I/O 기존 경로: App → libc → syscall → 커널 TCP → NIC 드라이버 (복사 2회 + 컨텍스트 스위치) VCL 경로: App → VCL → 공유 메모리 FIFO → VPP TCP → DPDK (zero-copy, 유저스페이스)
# iperf3에 VCL LD_PRELOAD 적용
$ VCL_CONFIG=/etc/vpp/vcl.conf \
  LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libvcl_ldpreload.so \
  iperf3 -s

# nginx에 적용
$ VCL_CONFIG=/etc/vpp/vcl.conf \
  LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libvcl_ldpreload.so \
  nginx -c /etc/nginx/nginx.conf

# vcl.conf 예제
vcl {
    rx-fifo-size 4000000          /* RX FIFO 4MB */
    tx-fifo-size 4000000          /* TX FIFO 4MB */
    app-scope-global              /* 글로벌 세션 네임스페이스 */
    api-socket-name /run/vpp/api.sock
}
POSIX 소켓 함수VCL 대응지원 수준
socket()vls_create()TCP/UDP/TLS
bind()vls_bind()완전
listen()vls_listen()완전
accept()vls_accept()완전
connect()vls_connect()완전
send()/write()vls_write()완전
recv()/read()vls_read()완전
epoll_*()vls_epoll_*()완전
select()/poll()vls_select()부분 (epoll 권장)
sendmsg()/recvmsg()미지원
VCL vs 커널 워크로드 선택: VCL은 대량 동시 연결(10K+)과 높은 처리량 시나리오에서 효과적입니다. 소규모 연결(수백 개)이나 복잡한 소켓 옵션(SO_REUSEPORT, SCM_RIGHTS 등)이 필요한 워크로드는 커널 소켓이 더 적합합니다.

VPP CLI 및 API

VPP CLI (vppctl)

VPP는 유닉스 도메인 소켓(/run/vpp/cli.sock)을 통한 CLI를 제공합니다:

# VPP CLI 접속
$ vppctl
    _______    _        _   _____  ___
 __/ __/ _ \  (_)__    | | / / _ \/ _ \
 _/ _// // / / / _ \   | |/ / ___/ ___/
 /_/ /____(_)_/\___/   |___/_/  /_/

vpp# show version
vpp# show interface
vpp# show hardware-interfaces
vpp# show runtime               /* 노드별 성능 통계 */
vpp# show errors                /* 에러 카운터 */
vpp# show trace                 /* 패킷 트레이스 */

# 패킷 트레이싱 (디버깅)
vpp# trace add dpdk-input 100   /* DPDK 입력 100 패킷 추적 */
vpp# show trace                 /* 추적 결과 확인 */

# 비대화형 실행
$ vppctl show interface

Binary API

VPP Binary API는 공유 메모리를 통한 고성능 프로그래밍 인터페이스입니다. .api 파일에서 메시지를 정의하고, 코드 생성기가 C/Python/Go 바인딩을 자동 생성합니다:

/* my_plugin.api — API 메시지 정의 */
autoreply define my_plugin_enable_disable {
    u32 client_index;
    u32 context;
    bool enable_disable;
    vl_api_interface_index_t sw_if_index;
};

define my_plugin_details {
    u32 context;
    vl_api_interface_index_t sw_if_index;
    u64 packet_count;
};

VAPI (VPP API)

VAPI는 C/C++용 고수준 API 라이브러리로, Binary API 위에서 동기/비동기 호출, 콜백, 자동 직렬화를 제공합니다:

/* VAPI 사용 예제 (C) */
#include <vapi/vapi.h>
#include <vapi/vpe.api.vapi.h>

vapi_ctx_t ctx;
vapi_ctx_alloc(&ctx);
vapi_connect(ctx, "my_app", NULL,
             64,  /* max outstanding requests */
             32,  /* response queue depth */
             VAPI_MODE_BLOCKING, true);

/* 인터페이스 목록 조회 */
vapi_msg_sw_interface_dump *msg = vapi_alloc_sw_interface_dump(ctx);
msg->payload.name_filter_valid = false;
vapi_sw_interface_dump(ctx, msg, callback_fn, NULL);

vapi_disconnect(ctx);
vapi_ctx_free(ctx);

GoVPP / Python bindings

# Python (vpp_papi) 예제
from vpp_papi import VPPApiClient

vpp = VPPApiClient(apifiles=['/usr/share/vpp/api/core/*.api.json'])
vpp.connect('my-python-app')

# 인터페이스 목록
ifaces = vpp.api.sw_interface_dump()
for iface in ifaces:
    print(f"{iface.interface_name}: {iface.sw_if_index}")

# TAP 생성
rv = vpp.api.tap_create_v2(id=0, host_if_name='vpp-tap0',
                            host_ip4_prefix='192.168.1.1/24')

vpp.disconnect()
VPP API 아키텍처 CLI (vppctl) Python (papi) GoVPP (Go) VAPI (C/C++) Prometheus Binary API 공유 메모리 전송 /run/vpp/api.sock CLI: /run/vpp/cli.sock 자동 생성: .api → C/Go/Py Stats Segment 공유 메모리 (mmap) lockfree 읽기 전용 VPP Core API Message Handler CLI Handler Graph Node Engine (데이터 평면) Per-thread 통계 카운터 (lockfree) FIB / Session / Interface 테이블

Stats Segment

VPP의 Stats Segment는 통계 카운터를 외부 프로세스에 노출하는 공유 메모리 영역입니다. 배리어 없이 읽기 전용으로 접근할 수 있어, 모니터링 시스템이 VPP 성능에 영향을 주지 않습니다.

/* C에서 Stats Segment 접근 */
#include <vpp-api/client/stat_client.h>

stat_client_main_t *sm = stat_client_get();
stat_segment_connect("/run/vpp/stats.sock");

/* 패턴으로 카운터 조회 */
u8 **patterns = 0;
vec_add1(patterns, (u8 *)"/if/rx");
stat_segment_data_t *data = stat_segment_dump(patterns);

/* Python에서 접근 */
# from vpp_papi.vpp_stats import VPPStats
# stats = VPPStats(socketname='/run/vpp/stats.sock')
# print(stats['/if/rx'])
카운터 경로유형설명
/if/rxcombined인터페이스별 RX 패킷/바이트
/if/txcombined인터페이스별 TX 패킷/바이트
/if/dropssimple인터페이스별 드롭 패킷
/err/{node}/{error}simple노드별 에러 카운터
/sys/heartbeatscalarVPP 활성 여부 (증가하는 카운터)
/mem/statsegscalarStats Segment 메모리 사용량
/if/namesname_vector인터페이스 이름 목록
Prometheus/Grafana 연동: vpp_prometheus_export 플러그인 또는 외부 vpp-exporter가 Stats Segment를 Prometheus 메트릭으로 변환합니다. /if/rx, /if/tx, /err/* 카운터를 Grafana 대시보드로 시각화하여 실시간 모니터링이 가능합니다.

성능 최적화

VPP 성능 최적화 계층 하드웨어: NIC RSS, AES-NI, QAT, NUMA 토폴로지 커널: isolcpus, nohz_full, hugepages, VFIO DPDK: 큐 수, descriptor 수, RX burst size VPP: 버퍼 풀 크기, 워커 수, 벡터 크기, Feature 최소화 노드: quad-loop, 프리페치 거리, 불필요한 Feature 비활성 영향도 조정 용이 쉬움 아래 계층부터 최적화 → 위 계층은 하드웨어 교체 필요

CPU 핀닝과 워커 스레드

VPP 성능의 핵심은 CPU 코어 격리와 적절한 워커 스레드 배치입니다:

/* startup.conf — CPU 핀닝 */
cpu {
    main-core 0                    /* 메인 스레드 (API, CLI 처리) */
    corelist-workers 2,4,6,8       /* 워커: 물리 코어만 (HT 제외) */
    skip-cores 1                   /* 코어 1 건너뛰기 (OS용) */
}

# 커널 부팅 파라미터: VPP 코어를 OS 스케줄러에서 격리
GRUB_CMDLINE_LINUX="isolcpus=2,4,6,8 nohz_full=2,4,6,8 rcu_nocbs=2,4,6,8"
커널 관점: isolcpus는 지정 코어를 CFS 로드 밸런싱에서 제외합니다. nohz_full은 해당 코어의 스케줄러 틱을 비활성화하여 인터럽트를 최소화합니다. rcu_nocbs는 RCU 콜백을 다른 코어로 오프로드합니다. 이 조합이 VPP의 busy-polling 성능을 극대화합니다.

NUMA 인식

NIC과 VPP 워커 스레드를 동일 NUMA 노드에 배치하여 원격 메모리 접근(remote memory access)을 방지합니다:

# NIC의 NUMA 노드 확인
$ cat /sys/bus/pci/devices/0000:03:00.0/numa_node
0

# NUMA 노드 0의 CPU 확인
$ lscpu | grep "NUMA node0"
NUMA node0 CPU(s): 0-7

/* startup.conf — NUMA 인식 설정 */
buffers {
    buffers-per-numa 16384        /* NUMA 노드당 버퍼 수 */
    default data-size 2048
}

dpdk {
    dev 0000:03:00.0 {            /* NUMA 0에 위치한 NIC */
        num-rx-queues 4
    }
}

cpu {
    main-core 0                   /* NUMA 0 코어 */
    corelist-workers 2,4,6        /* NUMA 0 코어 */
}

인터럽트 모드 vs 폴링 모드

모드장점단점적용 시나리오
Polling (기본)최소 레이턴시, 최대 throughputCPU 100% 사용고성능 필수 환경
Interrupt유휴 시 CPU 절약인터럽트 오버헤드트래픽 변동이 큰 환경
Adaptive부하에 따라 자동 전환전환 지연 발생 가능범용 환경
/* startup.conf — 인터럽트 모드 활성화 */
dpdk {
    dev 0000:03:00.0 {
        num-rx-queues 4
    }
}

/* 적응형 모드: 유휴 시 sleep */
unix {
    poll-sleep-usec 100           /* 유휴 시 100μs sleep */
}

성능 벤치마킹

# VPP 내장 패킷 생성기로 벤치마크
vpp# packet-generator new {
    name pg0
    limit 10000000
    size 64-64
    interface pg0
    node ethernet-input
    data { IP4: 1.2.3 -> 4.5.6
           UDP: 192.168.1.1 -> 192.168.2.1
           UDP: 1234 -> 5678
           incrementing 100
    }
}
vpp# packet-generator enable

# 런타임 통계 확인
vpp# show runtime
# 출력:
# Name                 State    Calls   Vectors  Suspends  Clocks   Vectors/Call
# dpdk-input           polling  1000000 10000000 0         24.50    10.00
# ethernet-input       active   1000000 10000000 0         18.30    10.00
# ip4-input            active   1000000 10000000 0         15.20    10.00
# ip4-lookup           active   1000000 10000000 0         12.80    10.00

# TRex 외부 트래픽 생성기 사용
$ ./t-rex-64 -f stl/bench.py -m 10mpps -d 60

버퍼 풀 최적화

NIC 속도권장 buffers-per-numaHugepage 요구량비고
1 GbE8,192~32 MB (2M pages)저트래픽 환경
10 GbE32,768~128 MB일반적 프로덕션
25 GbE65,536~256 MB고트래픽 환경
40/100 GbE131,072+~512 MB+1G hugepage 권장
# 버퍼 풀 상태 확인
vpp# show buffers
 Thread             Name           Index    Size     Alloc    Free     #Alloc   #Free
      0 default-numa-0                0     2048     32768    31200     1568     31200
      1 default-numa-0                0     2048     32768    30800     1968     30800

# 부족 시 증상: show errors에서 no-buffer 증가
vpp# show errors
   Count  Node           Reason
   15230  dpdk-input      no-buffer         ← 버퍼 부족!
       0  dpdk-input      buffer-alloc-fail
버퍼 부족 증상: dpdk-inputno-buffer 에러가 증가하면 NIC에서 패킷을 가져오지 못합니다. buffers-per-numa를 늘리고, /proc/meminfo에서 HugePages_Free가 충분한지 확인하세요. 버퍼 한 개 = sizeof(vlib_buffer_t) + data-size ≈ 2,176바이트입니다.

perf/VPP 성능 카운터 분석

# perf stat로 VPP 워커의 CPU 카운터 측정
$ perf stat -e cycles,instructions,cache-misses,cache-references,\
branch-misses,branch-instructions -p $(pidof vpp) -t $(pgrep -P $(pidof vpp)) sleep 10

# VPP 내장 성능 카운터
vpp# show runtime
# Clocks/Call: 노드당 평균 CPU 사이클 (낮을수록 좋음)
# Vectors/Call: 호출당 평균 벡터 크기 (높을수록 좋음)

# 노드별 상세 통계
vpp# show runtime max
vpp# clear runtime
CPU 카운터정상 범위 (VPP)문제 징후대응
IPC (inst/cycle)2.0~3.5< 1.5메모리 바운드, 프리페치 확인
L1 I-cache miss%< 2%> 5%노드 그래프 단순화, Feature 최소화
L1 D-cache miss%< 5%> 10%CLIB_PREFETCH 추가, NUMA 확인
Branch miss%< 1%> 3%벡터 크기 확인, 조건 분기 최소화
LLC miss%< 1%> 3%워킹셋 크기 초과, FIB 크기 확인

디버깅 및 트러블슈팅

패킷 트레이싱 심화

VPP의 패킷 트레이싱은 가장 강력한 디버깅 도구입니다. 패킷이 어떤 노드를 거쳐 어떤 결정을 받았는지 라인별로 확인할 수 있습니다.

# 트레이싱 활성화 (dpdk-input 노드에서 10개 패킷)
vpp# trace add dpdk-input 10

# 트래픽 발생 후 결과 확인
vpp# show trace

Packet 1

00:00:01:123456: dpdk-input
  GigabitEthernet0/8/0 rx queue 0
  buffer 0x9a340: current data 0, length 98, buffer-pool 0
  trace_handle 0x1000001
  l2-hdr-offset 0 ip4-hdr-offset 14

00:00:01:123458: ethernet-input              ← L2 분류
  IP4: fa:16:3e:aa:bb:cc -> fa:16:3e:dd:ee:ff

00:00:01:123459: ip4-input                   ← IPv4 처리 진입
  TCP: 192.168.1.100 -> 10.0.0.1
    tos 0x00, ttl 64, length 84, checksum 0x1234 dscp CS0 ecn NON_ECN
    fragment id 0x0001, flags DONT_FRAGMENT
  TCP: 45000 -> 80
    seq 0x12345678 ack 0x00000000
    flags [SYN] window 65535

00:00:01:123460: ip4-lookup                  ← FIB 룩업
  fib 0 dpo-idx 7 flow hash: 0x00001234
  TCP: 192.168.1.100 -> 10.0.0.1

00:00:01:123461: ip4-rewrite                 ← MAC 재작성
  tx_sw_if_index 2 dpo-idx 7
  adjacency rewrite: GigabitEthernet0/9/0
    dst aa:bb:cc:dd:ee:01 src fa:16:3e:xx:yy:zz

00:00:01:123462: GigabitEthernet0/9/0-output ← 인터페이스 출력
  GigabitEthernet0/9/0
  IP4: fa:16:3e:xx:yy:zz -> aa:bb:cc:dd:ee:01

00:00:01:123463: GigabitEthernet0/9/0-tx     ← TX 큐 전송
  buffer 0x9a340: current data -14, length 112
디버깅 시나리오trace add 대상확인 포인트
패킷이 VPP에 도달하지 않음dpdk-input 또는 af-xdp-inputRX 큐 수신 여부
라우팅 문제ip4-inputip4-lookup의 dpo-idx
NAT 변환 문제nat44-in2out-output세션 매칭, 주소 변환
ACL 드롭acl-plugin-in-ip4-fa매치 규칙, deny 여부
IPsec 터널 문제ipsec-input-ip4SA 매칭, 복호화 에러
인터페이스 미출력interface-outputTX 큐 도달 여부

에러 카운터 분석

vpp# show errors
   Count   Node                  Reason
   15230   dpdk-input            no buffer available
     842   ip4-input             ip4 spoofed local-address packet drops
     156   ip4-lookup            ip4 destination lookup miss
      23   acl-plugin-in-ip4-fa  acl deny
       5   nat44-in2out          no translation
       2   ipsec-input-ip4       SA not found
에러노드원인해결
no bufferdpdk-input버퍼 풀 고갈buffers-per-numa 증가, hugepage 추가
ttl-exceededip4-inputTTL이 0에 도달라우팅 루프 확인
destination lookup missip4-lookupFIB에 경로 없음show ip fib로 경로 확인
adjacency incompleteip4-rewriteARP 미해석show ip neighbor, ARP 상태 확인
acl denyacl-pluginACL 규칙에 의한 차단show acl-plugin acl로 규칙 확인
no translationnat44NAT 세션/주소 풀 소진show nat44 sessions, 주소 풀 확인
SA not foundipsec-inputIPsec SA 매칭 실패show ipsec sa, SPI 확인
interface downinterface-output출력 인터페이스 downshow interface, admin up 확인
worker handoff dropshandoff핸드오프 큐 오버플로워커 부하 분산, RSS 확인
dpdk driver init faildpdkNIC 바인딩 실패dpdk-devbind --status, VFIO/UIO 확인

PCAP 캡처

# 인입 패킷 PCAP 캡처
vpp# pcap trace rx max 1000 file rx-capture.pcap

# 송출 패킷 캡처
vpp# pcap trace tx max 1000 file tx-capture.pcap

# 드롭 패킷 캡처 (문제 진단에 가장 유용)
vpp# pcap trace drop max 1000 file drop-capture.pcap

# 특정 인터페이스만 캡처
vpp# pcap trace rx max 500 intfc GigabitEthernet0/8/0 file intf-rx.pcap

# 캡처 중지 및 저장
vpp# pcap trace off

# 파일 위치: /tmp/*.pcap
Wireshark 연계: /tmp/*.pcap 파일을 Wireshark에서 바로 열 수 있습니다. pcap trace drop은 VPP가 드롭한 패킷만 캡처하므로, "왜 패킷이 전달되지 않는가?"에 대한 직접적인 답을 제공합니다.

이벤트 로거와 코어 덤프

VPP 디버깅 의사결정 트리 패킷이 전달되지 않음 1. show interface → RX 증가? No NIC/DPDK 확인 Yes 2. show errors → 에러 있음? Yes 에러 테이블 참조 No 3. trace add → 경로 추적 4. pcap trace drop → 드롭 패킷 Wireshark 분석 5. show ip fib / show ip neighbor 확인
# 이벤트 로거 활성화
vpp# event-logger resize 1000000
vpp# event-logger restart
vpp# event-logger save /tmp/elog.dat

# GDB로 VPP 디버깅 (디버그 빌드 필요)
$ gdb /usr/bin/vpp_main core.12345
(gdb) bt                          # 백트레이스
(gdb) thread apply all bt         # 모든 스레드 백트레이스
(gdb) frame 3                     # 특정 프레임으로 이동
(gdb) p *b                        # vlib_buffer_t 내용 출력

# startup.conf에서 코어 덤프 활성화
# unix { full-coredump }
# coredump 크기 제한 해제: ulimit -c unlimited
코어 덤프 분석: VPP 코어 덤프 크기가 수 GB에 달할 수 있습니다(hugepage 매핑 포함). coredump_filter를 조정하여 hugepage를 제외하면 코어 크기를 줄일 수 있습니다: echo 0x33 > /proc/$(pidof vpp)/coredump_filter

자주 발생하는 문제와 해결

문제증상진단 방법해결
버퍼 고갈RX 드롭 급증show errors no-bufferbuffers-per-numa 증가
DPDK 초기화 실패VPP 시작 실패/var/log/vpp/vpp.logVFIO/UIO 바인딩, IOMMU 확인
Hugepage 부족VPP 시작 실패 또는 OOMcat /proc/meminfohugepage 추가 할당
워커 정지특정 큐 처리 중단show runtime Suspends배리어 장기 홀드 확인
ARP 미해석adjacency incompleteshow ip neighbor연결성 확인, proxy-arp
NAT 세션 소진no translation 에러show nat44 sessions세션 한도/타이머 조정
RSS 불균형일부 워커만 과부하show runtime per-workerRSS 해시 키 변경, handoff
MTU 불일치패킷 드롭/단편화show hardware인터페이스 MTU 통일
CLI 소켓 거부vppctl 연결 실패소켓 권한 확인api-segment { gid vpp }
플러그인 충돌VPP 크래시코어 덤프 분석문제 플러그인 비활성, 버전 확인

보안 고려사항

API 소켓 보안

접근 제어설정설명
소켓 그룹api-segment { gid vpp }API 소켓을 vpp 그룹만 접근
소켓 경로cli-listen /run/vpp/cli.sockCLI 소켓 위치 제어
API 추적api-trace { on }모든 API 호출 로깅
메시지 필터API 메시지별 권한특정 API만 허용 (플러그인 개발)
/* 보안 API 설정 예제 (startup.conf) */
unix {
    cli-listen /run/vpp/cli.sock   /* 로컬 유닉스 소켓만 (TCP 금지) */
    gid vpp                         /* vpp 그룹만 CLI 접근 */
}

api-segment {
    gid vpp                         /* API 세그먼트 그룹 제한 */
    api-pvt-heap-size 64M
}

api-trace {
    on                              /* API 호출 감사 추적 */
    save-api-table /tmp/api-table   /* API 테이블 저장 */
}

DPDK/VFIO 보안 격리

UIO vs VFIO 보안: uio_pci_generic은 IOMMU 없이 NIC에 직접 DMA 접근을 허용합니다. 악의적인 VPP 프로세스나 NIC 펌웨어가 임의 메모리를 읽고 쓸 수 있습니다. 프로덕션에서는 반드시 VFIO-PCI를 사용하세요. VFIO는 IOMMU를 통해 DMA 영역을 제한하여 디바이스 격리를 보장합니다.

네트워크 보안 기능

보안 기능플러그인/노드설명
ACLacl_pluginStateful/Stateless L3/L4 패킷 필터링
COPcop_plugin소스 IP 화이트리스트 (uRPF 유사)
RPFip4-source-checkReverse Path Forwarding 검증
Rate Limitingpolicer2r3c 기반 트래픽 폴리싱
IPsecipsec_pluginESP/AH 기반 터널/전송 모드 암호화
WireGuardwireguard_pluginWireGuard VPN (ChaCha20)
TLStlsopenssl/tlsmbedtlsTLS 종단 (세션 레이어 연동)
DDoS 방어flowprobe + 외부IPFIX 내보내기로 외부 DDoS 탐지 연동

설치 및 설정

패키지 설치

# Ubuntu/Debian — FD.io 공식 저장소
$ curl -s https://packagecloud.io/install/repositories/fdio/release/script.deb.sh | sudo bash
$ sudo apt-get install vpp vpp-plugin-core vpp-plugin-dpdk vpp-dev

# CentOS/RHEL
$ curl -s https://packagecloud.io/install/repositories/fdio/release/script.rpm.sh | sudo bash
$ sudo yum install vpp vpp-plugins vpp-devel

# 서비스 시작
$ sudo systemctl start vpp
$ sudo systemctl enable vpp

소스 빌드

# 소스 클론
$ git clone https://gerrit.fd.io/r/vpp
$ cd vpp

# 의존성 설치 (Ubuntu)
$ make install-dep
$ make install-ext-deps

# 빌드
$ make build             /* 디버그 빌드 */
$ make build-release     /* 릴리스 빌드 */

# 실행
$ make run               /* 빌드 디렉터리에서 직접 실행 */

startup.conf 설정

/* /etc/vpp/startup.conf — 프로덕션 설정 예제 */

unix {
    nodaemon                       /* 포그라운드 실행 (디버깅 시) */
    cli-listen /run/vpp/cli.sock   /* CLI 소켓 경로 */
    log /var/log/vpp/vpp.log       /* 로그 파일 */
    full-coredump                  /* 코어 덤프 활성화 */
    exec /etc/vpp/setup.gate       /* 시작 시 실행할 CLI 스크립트 */
}

api-trace {
    on                             /* API 추적 활성화 */
}

api-segment {
    gid vpp                        /* API 소켓 그룹 */
}

cpu {
    main-core 0
    corelist-workers 2,4,6,8
}

dpdk {
    dev default {
        num-rx-queues 4
        num-tx-queues 4
        num-rx-desc 1024
        num-tx-desc 1024
    }
    dev 0000:03:00.0
    dev 0000:03:00.1
    uio-driver vfio-pci            /* VFIO 사용 */
}

buffers {
    buffers-per-numa 32768
    default data-size 2048
}

plugins {
    plugin default { disable }     /* 기본 비활성화 */
    plugin dpdk_plugin.so { enable }
    plugin acl_plugin.so { enable }
    plugin nat_plugin.so { enable }
    plugin linux_cp_plugin.so { enable }
}

활용 사례

가상 스위치 (vSwitch)

VPP는 데이터센터의 가상 스위치로 활용됩니다. VM 간 트래픽을 vhost-user 인터페이스로 고성능 전달하며, OvS-DPDK 대비 더 높은 pps 성능을 제공합니다. Kubernetes 환경에서는 Calico/VPP CNI 플러그인으로 Pod 네트워킹을 가속합니다.

CPE/VNF

통신사의 고객 구내 장비(CPE)를 소프트웨어로 구현하는 VNF(Virtual Network Function)에 VPP가 사용됩니다. 라우팅, NAT, IPsec, QoS를 단일 VPP 인스턴스에서 처리하여 전용 하드웨어를 대체합니다.

5G UPF

5G 코어 네트워크의 UPF(User Plane Function)에서 GTP-U 터널 종단, PDU 세션 관리, SRv6 기반 모바일 유저플레인을 VPP로 구현합니다.

5G UPF 아키텍처 (VPP 기반) gNB 기지국 N3 GTP-U UPF (VPP) GTP-U 디캡슐레이션 PDR/FAR 매칭 QoS / 폴리싱 SRv6 Mobile URR (사용량 보고) PFCP (N4 제어) N6 DN 인터넷/엣지 N9 (UPF 간) SMF (N4)
# GTP-U 터널 설정 (upf 플러그인)
vpp# upf pfcp association setup-params
vpp# upf nwi create name internet
vpp# create upf application name internet

# 또는 travelping/upg-vpp 사용 시
vpp# upf gtpu endpoint ip 10.200.0.1 nwi access teid-range-timeout 30
오픈소스 5G UPFVPP 플러그인PFCP 지원비고
Travelping UPGupg-vpp (커스텀)완전프로덕션 검증, 상용 지원
free5GC UPFgtp-u (외부)부분Go 기반 제어 + VPP 데이터
Open5GS없음 (커널 GTP)완전VPP 미사용 (참고용)
OAI-CN5G없음 (eBPF/커널)부분연구용

Kubernetes CNI 통합

Calico/VPP — Kubernetes CNI 아키텍처 Kubernetes Node Pod A Pod B Pod C TAP VPP (Calico dataplane) L3 라우팅 | ACL (NetworkPolicy) | IPsec | memif DPDK 또는 AF_XDP | BGP (Calico agent) Physical NIC (25/100GbE) Remote Node Pod D VPP IPsec 터널 (노드 간 암호화) IPsec
# Calico/VPP CNI 설치 (Helm)
$ helm repo add projectcalico https://docs.tigera.io/calico/charts
$ helm install calico projectcalico/tigera-operator \
    --set installation.cni.type=Calico \
    --set installation.calicoNetwork.linuxDataplane=VPP
CNI데이터플레인암호화성능 (Pod 간)비고
Calico/VPPVPP (DPDK)IPsec (VPP)~25 Gbps+최고 성능, memif 지원
Calico/eBPFeBPFWireGuard~15 Gbps커널 내 처리, 범용
CiliumeBPFWireGuard/IPsec~15 GbpsL7 관측성 우수
OVN-KubernetesOvSIPsec~10 GbpsOpenShift 기본
Contiv-VPP: 초기 VPP 기반 CNI 프로젝트였으나 현재 유지보수가 중단되었습니다. Kubernetes VPP CNI는 Calico/VPP가 사실상 표준입니다.

로드 밸런서

VPP의 lb 플러그인은 Maglev 해시 기반 L3/L4 로드 밸런서를 제공합니다. GRE 또는 VXLAN으로 백엔드 서버에 패킷을 분산합니다:

vpp# lb vip 10.0.0.100/32 protocol tcp port 80 encap gre4
vpp# lb as 10.0.0.100/32 protocol tcp port 80 192.168.1.10
vpp# lb as 10.0.0.100/32 protocol tcp port 80 192.168.1.11

CSIT 테스팅 프레임워크

CSIT 개요

CSIT(Continuous System Integration and Testing)은 FD.io 프로젝트의 공식 테스트 프레임워크입니다. VPP의 기능 검증, 성능 측정, 회귀 테스트를 자동화합니다.

테스트 유형목적환경도구
Functional기능 정확성 검증VM / 컨테이너Robot Framework + PAPI
Performance처리량/레이턴시 측정전용 물리 서버TRex + Robot Framework
DeviceNIC 호환성 테스트다양한 NIC 장착 서버DPDK testpmd + VPP
make test단위 테스트로컬 빌드 환경Python unittest + scapy

로컬 테스트 실행

# VPP 소스에서 단위 테스트 실행
$ cd vpp
$ make test                        /* 전체 단위 테스트 */
$ make test TEST=test_ip4          /* 특정 테스트만 */
$ make test-debug                  /* 디버그 빌드 테스트 */
$ make test V=2                    /* 상세 출력 */

# CSIT 기능 테스트 실행 (Docker 필요)
$ git clone https://gerrit.fd.io/r/csit
$ cd csit
$ ./bootstrap-verify-perf.sh       /* 성능 테스트 */
make test: VPP의 make test는 Python + scapy 기반 단위 테스트입니다. VPP 인스턴스를 자동으로 시작하고, scapy로 패킷을 주입하여 출력을 검증합니다. 플러그인 개발 시 test/test_my_plugin.py를 작성하면 CI에서 자동 검증됩니다.

실습 랩

랩 토폴로지

3개의 네트워크 네임스페이스와 VPP TAP 인터페이스를 사용한 라우팅/NAT 실습 환경입니다. 물리 NIC 없이 로컬에서 VPP의 핵심 기능을 검증할 수 있습니다.

VPP 실습 랩 토폴로지 ns-client vpp-tap0 192.168.1.1/24 클라이언트 역할 VPP tap0 192.168.1.2 tap1 10.0.0.1 ip4-lookup | NAT44 ACL | trace ns-server vpp-tap1 10.0.0.2/24 서버 역할 호스트 (모니터링) vppctl, tcpdump, curl

환경 구성

#!/bin/bash — VPP 실습 랩 환경 구성

# 1. 네임스페이스 생성
sudo ip netns add ns-client
sudo ip netns add ns-server

# 2. VPP 시작 (minimal startup.conf)
cat <<'EOF' | sudo tee /tmp/lab-startup.conf
unix {
    cli-listen /run/vpp/cli.sock
    log /tmp/vpp-lab.log
    nodaemon
}
api-segment { gid $(id -gn) }
plugins {
    plugin default { disable }
    plugin nat_plugin.so { enable }
    plugin acl_plugin.so { enable }
    plugin ping_plugin.so { enable }
}
EOF

sudo vpp -c /tmp/lab-startup.conf &
sleep 2

# 3. TAP 인터페이스 생성
sudo vppctl create tap id 0 host-if-name vpp-tap0 host-ns ns-client \
    host-ip4-addr 192.168.1.1/24
sudo vppctl set interface ip address tap0 192.168.1.2/24
sudo vppctl set interface state tap0 up

sudo vppctl create tap id 1 host-if-name vpp-tap1 host-ns ns-server \
    host-ip4-addr 10.0.0.2/24
sudo vppctl set interface ip address tap1 10.0.0.1/24
sudo vppctl set interface state tap1 up

# 4. 네임스페이스 기본 게이트웨이 설정
sudo ip netns exec ns-client ip route add default via 192.168.1.2
sudo ip netns exec ns-server ip route add default via 10.0.0.1

echo "Lab ready! Test: sudo ip netns exec ns-client ping 10.0.0.2"

라우팅 및 NAT 실습

# IP 라우팅 확인
vpp# show ip fib
# 192.168.1.0/24 → tap0, 10.0.0.0/24 → tap1 확인

# NAT44 설정 (ns-client → ns-server 시 주소 변환)
vpp# nat44 plugin enable sessions 1024
vpp# nat44 add interface address tap1
vpp# set interface nat44 in tap0 out tap1

# 정적 매핑 (ns-server의 포트 8080 → ns-client)
vpp# nat44 add static mapping local 192.168.1.1 8080 \
     external tap1 8080 tcp

# ACL 설정 (ICMP만 허용 테스트)
vpp# set acl-plugin acl permit proto 1       /* ICMP */
vpp# set acl-plugin acl permit proto 6 dport 8080  /* TCP 8080 */
vpp# set acl-plugin acl deny
vpp# set acl-plugin interface tap0 input acl 0
vpp# set acl-plugin interface tap0 input acl 1
vpp# set acl-plugin interface tap0 input acl 2

검증 및 모니터링

# 1. 기본 연결 테스트
$ sudo ip netns exec ns-client ping -c 3 10.0.0.2
PING 10.0.0.2: 64 bytes from 10.0.0.2: icmp_seq=1 ttl=63 time=0.12 ms

# 2. NAT 세션 확인
vpp# show nat44 sessions
  192.168.1.1:45000 -> 10.0.0.1:45000 -> 10.0.0.2:8080 [TCP]

# 3. 패킷 트레이싱으로 전체 경로 확인
vpp# trace add virtio-input 10
$ sudo ip netns exec ns-client ping -c 1 10.0.0.2
vpp# show trace
# virtio-input → ethernet-input → ip4-input → nat44-in2out →
#   ip4-lookup → ip4-rewrite → tap1-output 경로 확인

# 4. 런타임 통계
vpp# show runtime
# 각 노드의 Calls, Vectors, Clocks/Call 확인

# 5. 에러 확인
vpp# show errors
# 에러 카운터가 0이면 정상

# 6. 정리
$ sudo killall vpp
$ sudo ip netns del ns-client
$ sudo ip netns del ns-server
trace로 패킷 흐름 시각화: trace add virtio-input 10 → ping → show trace는 VPP 학습의 가장 효과적인 방법입니다. 각 패킷이 어떤 노드를 거치고, 어떤 결정(FIB lookup, NAT 변환, ACL 매칭)을 받는지 한눈에 파악할 수 있습니다.

커널 네트워크 스택과의 비교

성능 비교

시나리오커널 스택VPP (DPDK)VPP (AF_XDP)
L3 포워딩 (64B, 10GbE)~1.2 Mpps~14.8 Mpps (라인레이트)~11 Mpps
L2 브릿징 (64B, 10GbE)~2 Mpps~14.8 Mpps (라인레이트)~12 Mpps
IPsec (AES-GCM-256, 1500B)~2 Gbps~20 Gbps (AES-NI)
NAT44 (64B)~800 Kpps~10 Mpps~8 Mpps
TCP 연결 처리우수 (네이티브)제한적 (VCL)제한적
주의: 성능 수치는 하드웨어, 패킷 크기, 규칙 수에 따라 크게 달라집니다. 위 표는 단일 코어, 10GbE NIC 기준의 대략적인 참고값입니다.

적용 시나리오 선택 기준

기준커널 스택 적합VPP 적합
TCP 애플리케이션웹 서버, DB, 범용 서비스L3/L4 포워딩, 터널링
성능 요구< 5 Gbps, 범용 처리> 10 Gbps, 라인레이트 포워딩
기능 복잡도소켓 API, iptables, tcL2/L3/VPN/NAT 고성능 처리
운영 편의성표준 Linux 도구 사용별도 CLI/API 학습 필요
NIC 공유여러 앱이 NIC 공유NIC 독점(DPDK) 또는 AF_XDP
에코시스템모든 Linux 도구 호환linux-cp로 일부 통합

커널 관련 소스 및 참고 코드

VPP가 활용하는 커널 서브시스템과 관련 소스 파일 매핑:

커널 서브시스템소스 파일VPP 연관
TUN/TAPdrivers/net/tun.cVPP TAP 인터페이스, /dev/net/tun
vhostdrivers/vhost/net.c, drivers/vhost/vhost.cvhost-user 백엔드, virtio 링
virtiodrivers/virtio/virtio_ring.cTAP virtio, VM 네트워킹
AF_PACKETnet/packet/af_packet.chost-interface, PACKET_MMAP
AF_XDPnet/xdp/xsk.c, net/xdp/xsk_buff_pool.cAF_XDP 인터페이스
XDPnet/core/dev.c (XDP hooks)AF_XDP용 XDP 프로그램
UIOdrivers/uio/uio_pci_generic.cDPDK UIO 드라이버
VFIOdrivers/vfio/pci/vfio_pci_core.cDPDK VFIO 드라이버 (IOMMU)
Hugepagesmm/hugetlb.c, fs/hugetlbfs/DPDK/VPP 메모리 할당
IOMMUdrivers/iommu/intel/iommu.c, drivers/iommu/amd/iommu.cVFIO DMA 격리
Netlinknet/netlink/af_netlink.clinux-cp 플러그인, 라우팅 동기화
Namespacenet/core/net_namespace.cTAP 네임스페이스 격리
소스 탐색: VPP 소스 코드는 https://gerrit.fd.io/r/gitweb?p=vpp.git에서 확인할 수 있습니다. 주요 디렉터리: src/vnet/(네트워킹 코어), src/vlib/(벡터 처리 엔진), src/plugins/(플러그인), src/vpp/(메인 프로세스).

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