VPP API 계층 레퍼런스

clib · vlib · vnet · VCL · TLS · offload로 이어지는 VPP API 계층 구조와 각 계층의 책임을 한 장의 지도로 정리합니다.

선행 문서: 이 페이지는 기초와 아키텍처의 벡터 패킷 처리·그래프 노드 개념을 전제로 합니다. 내부 구현 카테고리의 다른 페이지와 함께 읽어 주세요.

VPP의 소스 트리에는 기능이 비슷해 보이는 접두사(clib_, vlib_, vnet_, session_, vppcom_, tls_, vnet_crypto_)가 여러 개 섞여 있어서 처음 접하면 어느 계층의 API를 써야 할지 판단하기 어렵습니다. 이 절은 각 접두사가 어느 계층의 책임을 지고, 어떤 핵심 자료형(Type)과 함수가 있으며, 플러그인과 애플리케이션이 각각 어디에서 시작해야 하는지를 정리합니다. 아래 SVG는 전체 계층을 단 한 장에 펼쳐 본 지도(Map)입니다.

VPP API 계층 스택 — 어느 접두사가 어느 층에 속하는가 ① 애플리케이션 / 호스트 스택 API 유저 프로세스가 VPP를 "소켓처럼" 쓰는 경로. 공유 메모리 기반, syscall 없음. vppcom_* (VCL) vls_* (VLS 잠금) LD_PRELOAD (소켓 hook) Binary API / VAPI stats segment (read-only) ② 세션 / 전송 계층 API (호스트 스택 코어) TCP/UDP/TLS/QUIC 트랜스포트 엔진과 세션 핸들 관리. 플러그인과 VCL이 둘 다 여기로 모임. session_* (app/session) tcp_* / udp_* tls_* (openssl/mbedtls/picotls) quic_* (quicly 기반) svm_fifo_* (공유 FIFO) ③ vnet — 네트워크 의미(Network Semantics) 계층 인터페이스, L2/L3 스택, FIB, ACL, IPsec, 암호화 프레임워크 등 "네트워크 장비다운" 추상화. vnet_sw_interface_* ip4_fib_* / ip6_fib_* adj_* (인접성) ipsec_sa_* / esp_* vnet_crypto_* acl_* classify_* (N-tuple) feature_arc / VNET_FEATURE_INIT dpo_* (데이터 평면 오브젝트) fib_path_* vnet_hw_interface_* (드라이버 정책) ④ vlib — 벡터 처리 런타임 (Vector Library) 그래프 노드, 버퍼, 메인 루프, 프로세스 노드, 트레이스, CLI. 모든 노드가 반드시 이 계층 위에서 움직임. vlib_main_t / vm vlib_buffer_t / 풀 VLIB_REGISTER_NODE vlib_get_next_frame vlib_worker_thread_barrier_sync vlib_process_* (프로세스 노드) vlib_cli_register_* vlib_add_trace vlib_node_increment_counter vlib_frame_queue_* (핸드오프) ⑤ clib / vppinfra — 기반 자료구조 & 원자성/동기화 메모리 · 벡터 · 풀 · 해시 · 원자 · 락 · 시간 · 포맷. 표준 libc를 대체하는 범용 유틸리티. clib_mem / mheap vec_* (동적 배열) pool_* (인덱스 풀) clib_bihash clib_atomic_* clib_spinlock / rwlock ⑥ 디바이스 · 오프로드 계층 DPDK PMD / rte_* rdma / mlx5 af_xdp / xsk_* cryptodev (QAT) ipsecmb / AES-NI memif / vhost-user

이 지도를 사용하는 기본 원칙은 단순합니다. 아래 계층은 위 계층의 언어를 모른다는 것입니다. 예를 들어 clib_bihash는 자기가 FIB를 저장하는지 ACL을 저장하는지 알지 못하고, vlib는 패킷이 TLS인지 IPsec인지 구분하지 않으며, vnet은 애플리케이션 세션이 어떤 소켓 API를 통해 접근하는지 신경 쓰지 않습니다. 이 단방향 의존성 덕분에 VPP는 플러그인으로 기능을 갈아 끼워도 하부가 흔들리지 않습니다.

각 계층의 책임과 대표 API

계층주요 디렉터리대표 자료형대표 API언제 직접 호출하는가
⑤ clib/vppinfra src/vppinfra/ u8 * 벡터, pool_t, clib_bihash_*_t clib_mem_alloc, vec_add1, pool_get, clib_bihash_add_del, clib_atomic_fetch_add, clib_spinlock_lock 플러그인 내부 자료구조, 집계 카운터, 해시 기반 세션/플로우 테이블을 손수 만들 때
④ vlib src/vlib/ vlib_main_t, vlib_buffer_t, vlib_node_registration_t, vlib_frame_t VLIB_REGISTER_NODE, vlib_get_buffer, vlib_get_next_frame, vlib_validate_buffer_enqueue_*, vlib_process_wait_for_event, vlib_worker_thread_barrier_sync 새 그래프 노드를 작성하거나 CLI/프로세스 노드를 등록할 때 — 플러그인 개발의 기본 언어
③ vnet src/vnet/ vnet_main_t, vnet_hw_interface_t, ip4_fib_t, ipsec_sa_t, vnet_crypto_op_t vnet_sw_interface_set_flags, fib_table_entry_path_add, ipsec_sa_add_and_lock, vnet_feature_enable_disable, vnet_crypto_process_ops 인터페이스, 라우팅, ACL, IPsec, 암호화(Encryption)처럼 "네트워크 장비다운" 기능을 붙일 때
② session / transport src/vnet/session/, tcp/, tls/, quic/ session_t, app_worker_t, tls_ctx_t, svm_fifo_t session_alloc_for_connection, tcp_connection_alloc, tls_init_ctx, svm_fifo_enqueue TCP/TLS/QUIC 엔진을 확장하거나 새 트랜스포트를 플러그인으로 추가할 때. 일반 플러그인은 거의 직접 만지지 않음
① 애플리케이션 / VCL src/vcl/ vppcom_endpt_t, vls_handle_t, vppcom_cert_key_pair_t vppcom_app_create, vppcom_session_create, vppcom_session_connect, vppcom_epoll_wait, vppcom_add_cert_key_pair 유저 프로세스가 VPP 호스트 스택을 "소켓처럼" 사용하고자 할 때 (대부분의 애플리케이션 개발 경로)
⑥ 오프로드 DPDK · cryptodev · ipsecmb rte_mbuf, rte_crypto_op, rte_cryptodev_* rte_eth_rx_burst, rte_cryptodev_enqueue_burst, rte_cryptodev_dequeue_burst, IPsec-MB의 IMB_AES_GCM_ENC_* 드라이버 또는 vnet_crypto 백엔드를 직접 작성할 때 — 일반적으로 플러그인은 vnet_crypto_*를 통해 간접 사용

하나의 요청이 계층을 어떻게 관통하는가

추상 개념을 구체화하기 위해, HTTPS 요청이 들어와서 IPsec 터널로 재전송되는 극단적인 경로를 따라가 봅니다. 이 한 줄의 플로우가 위 여섯 계층을 모두 건드립니다.

  1. ⑥ DPDK PMD가 NIC 링에서 rte_mbuf를 가져와 VPP의 vlib_buffer_t로 감쌉니다.
  2. ④ vlibdpdk-input 노드가 프레임을 만들고, 벡터 단위로 다음 노드에 넘깁니다.
  3. ③ vnetethernet-input → ip4-input → ip4-lookupip4_fib_tadj_*를 사용해 FIB 룩업을 수행합니다.
  4. ② sessiontcp-established가 TCP 상태 머신을 돌리고, TLS 세션이면 tls_* → svm_fifo_* 경로로 복호화(Decryption)된 평문을 RX FIFO에 적재합니다.
  5. ① VCL 애플리케이션이 vppcom_session_read로 평문을 읽고, 검사/변형 후 vppcom_session_write로 다른 세션에 씁니다.
  6. 반대 방향으로 내려오는 패킷은 다시 ③ vnetesp-encrypt⑤ clibclib_atomic_fetch_addipsec_sa_t.seq를 증가시키고, vnet_crypto_process_ops를 호출합니다.
  7. ⑥ cryptodev(QAT)가 실제 AES-GCM 연산을 수행하고, 완료된 버퍼가 ④ vlib의 TX 경로로 돌아옵니다.
읽기 순서 가이드: 처음 VPP를 익힐 때는 이 지도의 ④ vlib → ⑤ clib → ③ vnet → ② session → ① VCL 순서가 이해도가 가장 빠릅니다. vlib는 "어떻게 움직이는가"를, clib는 "어떤 자료구조가 가용한가"를, vnet은 "네트워크 의미가 어떻게 붙는가"를, session/VCL은 "커널 소켓을 어떻게 흉내 내는가"를 설명합니다. ⑥ 오프로드는 필요할 때만 파고들어도 됩니다.

접두사만 보고 계층을 판별하는 법

실전에서는 헤더 파일명과 함수 접두사만 보고도 어느 계층에서 호출해야 하는지 거의 판단할 수 있습니다.

자주 하는 혼동: clib_*vlib_*는 이름이 비슷해 보이지만, clib_*컨텍스트가 없는 순수 유틸리티이고 vlib_*는 반드시 런타임 컨텍스트(vm)를 요구합니다. 예를 들어 clib_mem_alloc은 어디서든 부를 수 있지만 vlib_get_buffer(vm, bi)vm 없이 호출되면 세그폴트합니다. 이 차이가 플러그인 초기화 함수에서 가장 자주 틀리는 지점입니다.

계층 경계 교차 — 실제 코드 예제

플러그인이 하나의 작업을 수행하면서 여러 계층을 함께 쓰는 전형적인 패턴입니다. ACL-match 후 FIB 우선순위를 갱신하는 간단한 예제를 통해 각 계층 API의 역할 분담을 보여줍니다.

/* ① PROCESS 노드 (vlib 계층) — CLI 핸들러가 이벤트를 보내면 깨어남 */
static uword
myplugin_process (vlib_main_t *vm, vlib_node_runtime_t *rt, vlib_frame_t *f)
{
  uword event_type, *event_data = 0;
  while (1) {
    vlib_process_wait_for_event(vm);          /* ④ vlib — 이벤트 대기 */
    event_type = vlib_process_get_events(vm, &event_data);
    if (event_type == MY_EVENT_UPDATE_ROUTE) {
      /* ③ vnet — FIB 엔트리 갱신 (배리어 불필요: 이미 메인 루프 내) */
      fib_prefix_t pfx = { .fp_len = 32, .fp_proto = FIB_PROTOCOL_IP4 };
      pfx.fp_addr.ip4 = *(ip4_address_t *) event_data[0];
      fib_table_entry_path_add(
        0,              /* table-id 0 */
        &pfx,
        FIB_SOURCE_PLUGIN_LOW, FIB_ENTRY_FLAG_NONE,
        DPO_PROTO_IP4, &nh_addr, sw_if_index,
        ~0, 1, NULL, FIB_ROUTE_PATH_FLAG_NONE);
    }
    vec_reset_length(event_data);
  }
  return 0;
}

/* ② 데이터 경로 노드 (vlib 계층) — 패킷이 올 때마다 호출 */
VLIB_NODE_FN(myplugin_node) (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
{
  u32 *from = vlib_frame_vector_args(frame), n = frame->n_vectors;
  while (n--) {
    vlib_buffer_t *b = vlib_get_buffer(vm, from[0]); /* ④ vlib */
    ip4_header_t  *ip = vlib_buffer_get_current(b);

    /* ⑤ clib — bihash 조회 (락 없는 reader 경로) */
    clib_bihash_kv_8_8_t kv = { .key = ip->dst_address.as_u32 };
    if (BV(clib_bihash_search)(&myplugin_main.flow_table, &kv, &kv) == 0) {
      /* hit — 프로세스 노드에 갱신 요청 */
      vlib_process_signal_event(vm,        /* ④ vlib */
                                my_process_node.index,
                                MY_EVENT_UPDATE_ROUTE,
                                (uword) &ip->dst_address);
    }
    from++; /* 다음 버퍼 인덱스 */
  }
  vlib_buffer_enqueue_to_single_next(vm, node, vlib_frame_vector_args(frame),
                                      MY_NEXT_LOOKUP, frame->n_vectors);
  return frame->n_vectors;
}

계층 경계 교차 비용 분석

플러그인이 자신이 속한 계층을 벗어나 다른 계층의 API를 직접 호출하면, 기능 오작동뿐만 아니라 측정 가능한 성능 비용이 발생합니다. 아래는 대표적인 세 가지 교차 시나리오와 그 비용입니다.

잘못된 호출 패턴 요약

잘못된 호출 패턴실제 증상올바른 대안
데이터 경로 node_fn에서 fib_table_entry_path_add() 직접 호출 워커 배리어 경합 → 전체 워커 stall → 지연(latency) 급증 및 크래시 가능 PROCESS 노드에 이벤트 전송 후 PROCESS 노드에서 FIB 갱신 수행
VLIB_INIT_FUNCTION 내에서 vlib_buffer_alloc() 호출 버퍼 풀 미초기화 상태 → NULL 역참조 → 즉시 크래시 VLIB_MAIN_LOOP_ENTER_FUNCTION에서 첫 번째 루프 진입 시 할당
워커 스레드에서 rte_eth_rx_burst() 직접 사용 VPP 버퍼 수명 주기 우회 → 메모리 누수·더블 프리 · PMD 경합 VNET_DEVICE_CLASS + VLIB_REGISTER_NODE로 드라이버 플러그인 등록
커널 플러그인 내부에서 vppcom_session_create() 호출 VCL은 외부 프로세스용 라이브러리 — 동일 주소 공간 호출 시 이중 세션 레이어 혼동 플러그인에서는 session_* / app_* API 사용

올바른 이벤트 전달 패턴

아래 예제는 VLIB_NODE_FN 데이터 경로에서 제어 평면 작업이 필요할 때 PROCESS 노드에 이벤트를 전달하는 올바른 패턴을 보여줍니다. 이 패턴을 사용하면 데이터 경로 노드는 배리어 없이 즉시 복귀하고, 제어 평면 갱신은 PROCESS 노드에서 안전하게 실행됩니다.

/*
 * 올바른 패턴: VLIB_NODE_FN에서 제어 평면 작업이 필요할 때
 * 직접 FIB/vnet API를 호출하지 않고 PROCESS 노드에 이벤트를 전달한다.
 */

/* 이벤트 타입 정의 */
#define MY_CTRL_EVENT_FIB_UPDATE  1

/* 데이터 경로 노드 — 패킷 벡터마다 호출 */
VLIB_NODE_FN (my_worker_node)
  (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
{
  u32 *from = vlib_frame_vector_args (frame);
  u32  n    = frame->n_vectors;

  while (n--)
    {
      vlib_buffer_t *b  = vlib_get_buffer (vm, from[0]);   /* ④ vlib */
      ip4_header_t  *ip = vlib_buffer_get_current (b);

      /* ⑤ clib bihash — 락 없는 reader, 데이터 경로 안전 */
      clib_bihash_kv_8_8_t kv = { .key = ip->dst_address.as_u32 };
      if (clib_bihash_search_8_8 (&my_main.flow_tbl, &kv, &kv) == 0)
        {
          /*
           * 제어 평면 갱신 필요: FIB를 직접 건드리지 않고
           * vlib_process_signal_event()로 PROCESS 노드에 이벤트 전송.
           * 이 호출은 lock-free 큐에 쓰기만 하므로 배리어 없이 복귀한다.
           */
          vlib_process_signal_event (vm,
                                     my_ctrl_process_node_index,
                                     MY_CTRL_EVENT_FIB_UPDATE,
                                     (uword) ip->dst_address.as_u32);
        }
      from++;
    }

  vlib_buffer_enqueue_to_single_next (vm, node,
                                       vlib_frame_vector_args (frame),
                                       MY_NEXT_IP4_LOOKUP, frame->n_vectors);
  return frame->n_vectors;
}

/* PROCESS 노드 — 메인 루프에서 깨어나 제어 평면 갱신 수행 */
static uword
my_ctrl_process (vlib_main_t *vm, vlib_node_runtime_t *rt, vlib_frame_t *f)
{
  uword  event_type;
  uword *event_data = 0;

  while (1)
    {
      vlib_process_wait_for_event (vm);
      event_type = vlib_process_get_events (vm, &event_data);

      switch (event_type)
        {
        case MY_CTRL_EVENT_FIB_UPDATE:
          {
            for (int i = 0; i < vec_len (event_data); i++)
              {
                ip4_address_t dst;
                dst.as_u32 = (u32) event_data[i];

                fib_prefix_t pfx = {
                  .fp_len   = 32,
                  .fp_proto = FIB_PROTOCOL_IP4,
                  .fp_addr  = { .ip4 = dst },
                };
                /* ③ vnet FIB 갱신 — PROCESS 노드 컨텍스트이므로 안전 */
                fib_table_entry_path_add (
                  0, &pfx,
                  FIB_SOURCE_PLUGIN_LOW, FIB_ENTRY_FLAG_NONE,
                  DPO_PROTO_IP4, &my_main.nh_addr,
                  my_main.sw_if_index, ~0, 1, NULL,
                  FIB_ROUTE_PATH_FLAG_NONE);
              }
          }
          break;
        default:
          break;
        }

      vec_reset_length (event_data);
    }
  return 0;
}

잘못된 계층 호출의 결과

상황잘못된 코드결과올바른 방법
데이터 경로 노드에서 FIB 갱신 fib_table_entry_path_add()를 노드 함수에서 직접 호출 배리어 없이 FIB 트라이 수정 → 다른 워커와 경합 → 크래시 또는 메모리 손상 PROCESS 노드에 이벤트를 보내고 거기서 FIB 갱신
플러그인 init에서 버퍼 할당 vlib_get_buffer(vm, bi)VLIB_INIT_FUNCTION에서 호출 버퍼 풀이 아직 초기화되지 않음 → NULL 역참조 VLIB_MAIN_LOOP_ENTER_FUNCTION에서 최초 실행 시 할당
VCL API를 플러그인 내부에서 호출 vppcom_session_create()를 플러그인 코드에서 직접 호출 VCL은 독립된 사용자 공간 라이브러리 — VPP 프로세스 내에서 호출 시 이중 세션 레이어 혼동 플러그인에서는 session_* / app_* API 사용
rte_*를 일반 플러그인에서 직접 호출 rte_eth_rx_burst()를 입력 노드에서 직접 사용 DPDK 초기화 순서 의존성 위반, 다른 PMD와 충돌 가능 VNET_DEVICE_CLASS + VLIB_REGISTER_NODE로 드라이버 플러그인 등록

잘못된 계층 호출 증상 및 디버깅

잘못된 계층 호출은 즉시 크래시를 일으키지 않는 경우도 있어서 원인 파악이 어렵습니다. 아래는 실수 유형별 증상과 GDB 중단점(breakpoint) 설정 방법입니다.

증상 메시지원인GDB 중단점
SIGSEGV at vlib_get_buffer 초기화 컨텍스트(VLIB_INIT_FUNCTION)에서 vlib API 호출 — 버퍼 풀 미초기화 상태
break vlib_get_buffer
assert failed at vlib_buffer_alloc 워커 컨텍스트 바깥에서 버퍼 할당 시도 — 풀 메타데이터 불일치
break vlib_buffer_alloc
worker thread barrier timeout 데이터 경로 노드에서 FIB 갱신 호출 → 워커 배리어 대기 시간 초과
break vlib_worker_thread_barrier_sync

show runtime 일시 정지 카운터 급등: VPP CLI에서 show runtime을 주기적으로 실행할 때 특정 PROCESS 노드의 suspends 카운터가 비정상적으로 높다면 PROCESS 노드에 이벤트가 폭주하고 있다는 신호입니다. 데이터 경로 노드가 과도하게 이벤트를 시그널하거나, PROCESS 노드의 처리 루프가 느린 제어 평면 API를 반복 호출하고 있을 때 나타납니다.

# PROCESS 노드 오버로드 확인 — suspends 카운터 확인
vpp# show runtime

이벤트 로그로 배리어 대기 패턴 추적: VPP의 내장 이벤트 로거를 활성화하면 배리어 획득·해제 타임스탬프를 기록합니다. show event-log로 출력된 로그에서 barrier_syncbarrier_release 사이의 간격이 길면 제어 평면 API가 데이터 경로를 방해하고 있음을 의미합니다.

# 이벤트 로거 활성화 및 배리어 대기 패턴 확인
vpp# event-logger on
vpp# # ... 재현 트래픽 발생 ...
vpp# show event-log

25.10 / 26.02 주요 API 변경

아래 변경사항은 기존 플러그인이나 바이너리 API 클라이언트에 영향을 줄 수 있습니다. 각 계층별 상세 내용은 해당 페이지를 참조하시기 바랍니다.

계층변경 종류대상버전조치
⑥ 디바이스 🗑️ 제거 avf_create / avf_delete (AVF 플러그인 전체) 26.02 dev_infra API 사용 권장 (src/dev_infra/dev.api — 공식 마이그레이션 가이드 참조)
③ vnet 🗑️ Deprecated tap_create_v2 / tap_create_v2_reply 26.02 tap_create_v3로 마이그레이션 (num_tx_queues u16 필드 추가)
③ vnet ⚠️ 코드 변경 레거시 virtio pre-1.0 지원 제거 26.02 커널 4.1+ 에서는 영향 없음 (deprecated API 목록 미등재)
⑤ clib 🗑️ 제거 vppmem_preload 라이브러리 26.02 빌트인 clib_mem_main_t.alloc_free_intercept 사용
④ vlib 🆕 신규 vlib_get_next_frame_p (포인터 기반 next frame) 26.02 기존 카운터 기반 매크로와 병용 가능
① VCL 🆕 신규 vppcom_app_create_with_config(vppcom_cfg_t *) 26.02 환경 변수 없이 프로그래밍 방식으로 VCL 초기화 가능
② session 🆕 신규 APP_OPTIONS_MAX_FIFO_MEMORY 25.10 애플리케이션별 FIFO 메모리 상한 설정
③ vnet 🆕 신규 gre_tunnel_add_del_v2 (GRE 키 지원) 25.10 기존 gre_tunnel_add_del은 유지