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, 성능 최적화, 활용 사례 종합 가이드.
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.02 | Linux Foundation 산하 FD.io 프로젝트로 오픈소스 공개 |
| 2017 | VPP 17.01 릴리스, DPDK 통합 본격화 |
| 2018 | Kubernetes CNI (Contiv-VPP) 지원, vhost-user 안정화 |
| 2020 | AF_XDP 호스트 인터페이스 지원, rdma 플러그인 |
| 2022~ | 5G UPF 유스케이스 확대, SRv6 고도화, VPP 24.x 시리즈 |
패킷 처리 프레임워크 비교
| 항목 | 커널 스택 | DPDK (raw) | VPP | OvS-DPDK |
|---|---|---|---|---|
| 실행 공간 | 커널 | 유저스페이스 | 유저스페이스 | 유저스페이스 |
| 처리 모델 | Scalar (패킷 단위) | Poll-mode (배치) | Vector (그래프 노드) | Flow-based (Megaflow) |
| 성능 (64B, 단일 코어) | ~1 Mpps | ~15 Mpps | ~12 Mpps | ~8 Mpps |
| L2/L3 스택 | 완전 내장 | 직접 구현 필요 | 완전 내장 | L2 + OpenFlow |
| 확장성 | 커널 모듈 | 라이브러리 | 플러그인 시스템 | OpenFlow 규칙 |
| 커널 통합 | 네이티브 | UIO/VFIO | TAP/vhost/AF_XDP | vhost-user |
| 라이선스 | GPL-2.0 | BSD/LGPL | Apache 2.0 | Apache 2.0 |
벡터 패킷 처리 모델
Scalar 처리 vs Vector 처리
전통적인 커널 네트워크 스택의 Scalar 처리는 패킷 하나를 수신하면 L2 → L3 → L4 → 소켓 전달까지 전체 경로를 완주한 후 다음 패킷을 처리합니다. 각 단계마다 다른 함수 코드가 I-cache에 로드되므로 캐시 미스가 빈번합니다.
VPP의 Vector 처리는 다수의 패킷(벡터)을 동시에 한 노드에서 처리한 후, 다음 노드로 벡터 전체를 넘깁니다. 동일한 코드가 수백 패킷에 반복 적용되므로 I-cache에 상주하며, 분기 예측이 안정되어 파이프라인 버블이 최소화됩니다.
캐시 효율성과 성능
VPP의 성능 우위는 CPU 마이크로아키텍처 수준의 최적화에서 비롯됩니다:
- I-cache 최적화: 하나의 그래프 노드 함수가 256개 패킷에 반복 적용되므로, 명령어가 L1 I-cache에 상주합니다. 커널 스택에서는 패킷마다 수십 개의 함수를 순회하며 I-cache가 지속적으로 교체됩니다.
- D-cache 프리페칭: VPP는 현재 패킷을 처리하는 동안 다음 패킷의 데이터를
CLIB_PREFETCH()매크로로 미리 캐시에 로드합니다. - 분기 예측: 동일한 코드 경로가 수백 번 반복 실행되므로 CPU의 Branch Target Buffer(BTB)가 안정화됩니다.
- 듀얼/쿼드 루프: VPP 노드는 패킷을 2개 또는 4개씩 묶어서 처리하는 dual-loop/quad-loop 패턴을 사용해 루프 오버헤드를 줄이고 ILP(Instruction-Level Parallelism)를 극대화합니다.
/* 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;
}
VPP 아키텍처
패킷 처리 그래프 (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)는 다음 순서로 동작합니다:
- PRE_INPUT 노드 실행 (epoll 이벤트 수집 등)
- INPUT 노드 실행 (dpdk-input이 NIC에서 패킷 수집 → 벡터 생성)
- INTERNAL 노드: 보류 중인 프레임이 있는 노드를 순회하며 벡터 처리
- PROCESS 노드: 타이머/이벤트 만료된 코루틴 실행
- 1번으로 돌아감 (busy-loop 또는 sleep)
startup.conf의 poll-sleep-usec 옵션으로 유휴 시 절전 모드를 활성화할 수 있지만, 레이턴시가 증가할 수 있습니다.
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
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 */
}
플러그인 시스템
VPP는 모듈식 플러그인 아키텍처를 채택하여 기능을 동적으로 확장할 수 있습니다. 각 플러그인은 공유 라이브러리(.so)로 빌드되며, VPP 시작 시 /usr/lib/vpp_plugins/에서 자동 로드됩니다.
플러그인 구조
VPP 플러그인의 기본 디렉터리 구조:
src/plugins/my_plugin/
├── CMakeLists.txt /* 빌드 설정 */
├── my_plugin.h /* 공용 헤더 */
├── my_plugin.c /* 플러그인 초기화 */
├── my_plugin.api /* Binary API 정의 */
├── node.c /* 그래프 노드 구현 */
├── cli.c /* CLI 커맨드 */
└── 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는 다양한 커널 인터페이스를 통해 커널과 패킷을 교환합니다.
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 링을 사용합니다. /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
PACKET_MMAP 링 버퍼를 사용하여 시스템 콜 오버헤드를 줄입니다.
Netlink 연동
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
주요 기능
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를 수행합니다:
/* 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: 내부 → 외부 주소 변환 */
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
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()
성능 최적화
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 (기본) | 최소 레이턴시, 최대 throughput | CPU 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
설치 및 설정
패키지 설치
# 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로 구현합니다. Travelping, free5GC 등의 오픈소스 5G 프로젝트가 VPP를 UPF 엔진으로 사용합니다.
로드 밸런서
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
커널 네트워크 스택과의 비교
성능 비교
| 시나리오 | 커널 스택 | 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) | 제한적 |
적용 시나리오 선택 기준
| 기준 | 커널 스택 적합 | VPP 적합 |
|---|---|---|
| TCP 애플리케이션 | 웹 서버, DB, 범용 서비스 | L3/L4 포워딩, 터널링 |
| 성능 요구 | < 5 Gbps, 범용 처리 | > 10 Gbps, 라인레이트 포워딩 |
| 기능 복잡도 | 소켓 API, iptables, tc | L2/L3/VPN/NAT 고성능 처리 |
| 운영 편의성 | 표준 Linux 도구 사용 | 별도 CLI/API 학습 필요 |
| NIC 공유 | 여러 앱이 NIC 공유 | NIC 독점(DPDK) 또는 AF_XDP |
| 에코시스템 | 모든 Linux 도구 호환 | linux-cp로 일부 통합 |
커널 관련 소스 및 참고 코드
VPP가 활용하는 커널 서브시스템과 관련 소스 파일 매핑:
| 커널 서브시스템 | 소스 파일 | VPP 연관 |
|---|---|---|
| TUN/TAP | drivers/net/tun.c | VPP TAP 인터페이스, /dev/net/tun |
| vhost | drivers/vhost/net.c, drivers/vhost/vhost.c | vhost-user 백엔드, virtio 링 |
| virtio | drivers/virtio/virtio_ring.c | TAP virtio, VM 네트워킹 |
| AF_PACKET | net/packet/af_packet.c | host-interface, PACKET_MMAP |
| AF_XDP | net/xdp/xsk.c, net/xdp/xsk_buff_pool.c | AF_XDP 인터페이스 |
| XDP | net/core/dev.c (XDP hooks) | AF_XDP용 XDP 프로그램 |
| UIO | drivers/uio/uio_pci_generic.c | DPDK UIO 드라이버 |
| VFIO | drivers/vfio/pci/vfio_pci_core.c | DPDK VFIO 드라이버 (IOMMU) |
| Hugepages | mm/hugetlb.c, fs/hugetlbfs/ | DPDK/VPP 메모리 할당 |
| IOMMU | drivers/iommu/intel/iommu.c, drivers/iommu/amd/iommu.c | VFIO DMA 격리 |
| Netlink | net/netlink/af_netlink.c | linux-cp 플러그인, 라우팅 동기화 |
| Namespace | net/core/net_namespace.c | TAP 네임스페이스 격리 |
https://gerrit.fd.io/r/gitweb?p=vpp.git에서 확인할 수 있습니다. 주요 디렉터리: src/vnet/(네트워킹 코어), src/vlib/(벡터 처리 엔진), src/plugins/(플러그인), src/vpp/(메인 프로세스).