VPP 실전 구성 시나리오
VPP(Vector Packet Processing) 프레임워크를 활용한 실전 네트워크 구성 시나리오를 다룹니다. DPDK 기반 L3 라우터와 NAT44 결합, memif 기반 서비스 펑션 체이닝(SFC), IPsec VPN 게이트웨이, SSL/TLS Inspection까지 실무에서 바로 적용할 수 있는 구성을 포괄합니다.
개요
이 문서에서는 VPP를 실제 운영 환경에 배포하는 대표적인 시나리오를 다룹니다. 각 시나리오는 startup.conf 설정부터 인터페이스 구성, 데이터 경로 분석, 성능 확인까지 엔드-투-엔드 절차를 제공합니다.
| 시나리오 | 주요 기능 | 인터페이스 | 적합한 환경 |
|---|---|---|---|
| L3 라우터 + NAT44 | IP 포워딩, 주소 변환 | DPDK PMD | 엣지 라우터, 게이트웨이 |
| memif 서비스 체이닝 | 제로카피 패킷 전달, SFC | memif (공유 메모리) | NFV, 다단계 패킷 처리 |
| IPsec VPN | 터널 암호화, IKEv2 | DPDK + ipsec | 사이트 간 VPN, 원격 접속 |
| SSL/TLS Inspection | TLS 종단·복호화·재암호화 | VCL + TLS 플러그인 | NGFW, 보안 게이트웨이, DLP |
isolcpus), DPDK 호환 NIC를 가정합니다. VPP 설치 및 설정을 참고하여 기본 환경을 먼저 구성하세요.
실전 예제: L3 라우터 + NAT44 구성
VPP를 이용하여 DPDK 기반 L3 라우터와 NAT44를 결합한 실제 운영 환경 구성입니다. 물리 NIC 2개를 DPDK에 바인딩하고, LAN → WAN 트래픽에 NAT을 적용하는 전형적인 엣지 라우터 시나리오입니다.
startup.conf 구성
# /etc/vpp/startup.conf — L3 라우터 + NAT44 실전 구성
unix {
cli-listen /run/vpp/cli.sock
log /var/log/vpp/vpp.log
full-coredump
gid vpp
}
api-trace { on }
api-segment { gid vpp }
dpdk {
dev 0000:00:08.0 { /* LAN NIC */
name GigabitEthernet0/8/0
num-rx-queues 2
}
dev 0000:00:09.0 { /* WAN NIC */
name GigabitEthernet0/9/0
num-rx-queues 2
}
no-multi-seg /* 단일 세그먼트 버퍼 (성능 향상) */
no-tx-checksum-offload
}
cpu {
main-core 0
corelist-workers 1-3 /* 워커 3개 (RSS 큐 분산) */
}
buffers {
buffers-per-numa 32768
default data-size 2048
}
plugins {
plugin default { disable }
plugin dpdk_plugin.so { enable }
plugin nat_plugin.so { enable }
plugin acl_plugin.so { enable }
plugin ping_plugin.so { enable }
}
L3 라우팅 + NAT44 설정
# 인터페이스 설정
vpp# set interface ip address GigabitEthernet0/8/0 192.168.1.1/24
vpp# set interface ip address GigabitEthernet0/9/0 203.0.113.1/24
vpp# set interface state GigabitEthernet0/8/0 up
vpp# set interface state GigabitEthernet0/9/0 up
# 기본 라우팅 (WAN 게이트웨이)
vpp# ip route add 0.0.0.0/0 via 203.0.113.254 GigabitEthernet0/9/0
# NAT44 활성화
vpp# nat44 plugin enable sessions 65536
vpp# nat44 add interface address GigabitEthernet0/9/0
vpp# set interface nat44 in GigabitEthernet0/8/0 out GigabitEthernet0/9/0
# 포트 포워딩: 외부 TCP 8080 → 내부 서버 192.168.1.100:80
vpp# nat44 add static mapping local 192.168.1.100 80 \
external GigabitEthernet0/9/0 8080 tcp
# ACL: 외부에서 들어오는 SSH 차단
vpp# set acl-plugin acl deny proto 6 dport 22
vpp# set acl-plugin interface GigabitEthernet0/9/0 input acl 0
# 검증
vpp# show ip fib
vpp# show nat44 sessions
vpp# show interface addr
vpp# show acl-plugin acl
linux-cp 플러그인을 활성화하세요. create linux-cp lcp GigabitEthernet0/8/0 host-if vpp-lan으로 미러 인터페이스를 생성하면 FRR/BIRD 같은 라우팅 데몬과 연동할 수 있습니다.
첫 플로우 생성과 fast path 전환
NAT44를 실무에서 이해할 때 핵심은 첫 패킷과 그다음 패킷을 분리해서 보는 것입니다. 첫 패킷은 BIB(Basic Information Base)와 세션을 만들기 위해 상대적으로 무거운 경로를 거치고, 이후 동일 플로우는 이미 만들어진 상태를 조회하는 빠른 경로로 내려갑니다.
# 1. 상태를 깨끗하게 초기화
vpp# clear runtime
vpp# clear trace
vpp# trace add dpdk-input 10
vpp# show nat44 sessions detail
# 2. 첫 플로우 생성
# LAN 측 클라이언트에서 외부 HTTP 서버로 연결 1개를 생성합니다.
$ curl http://198.51.100.10/
# 3. VPP에서 상태 확인
vpp# show nat44 sessions detail
vpp# show runtime
vpp# show trace
# 4. 같은 연결을 반복해서 부하를 줍니다.
$ for i in $(seq 1 1000); do curl -s http://203.0.113.1:8080/ > /dev/null; done
# 5. fast path 전환 여부 확인
vpp# show nat44 sessions detail
vpp# show runtime
vpp# show errors
| 시점 | 관찰해야 할 출력 | 해석 |
|---|---|---|
| 트래픽 전 | show nat44 sessions detail에 세션이 거의 없습니다. | 기준선입니다. 이전 테스트 세션이 남아 있으면 결과 해석이 흐려집니다. |
| 첫 요청 직후 | 세션이 1개 이상 생기고, trace에 nat44-in2out-slowpath가 보일 수 있습니다. | 상태 생성이 정상입니다. 여기서 세션이 안 생기면 인터페이스 방향, 주소 풀, ACL 차단을 먼저 의심해야 합니다. |
| 반복 부하 후 | show runtime에서 fast path 노드 호출이 꾸준히 증가합니다. | 이미 생성된 세션을 재사용하고 있음을 뜻합니다. 처리량이 올라가도 slow path 비중이 높지 않아야 합니다. |
| 에러 증가 시 | show errors에 NAT 드롭 또는 no translation 관련 카운터가 보입니다. | 세션 용량, 번역 주소 고갈, 비대칭 응답 경로를 함께 봐야 합니다. |
NAT44 세션 테이블 내부 구조
VPP NAT44의 성능을 이해하려면 세션 테이블이 어떻게 구성되는지 알아야 합니다. NAT44는 내부적으로 BIHash(Bounded-index Extensible Hash) 기반의 세션 테이블을 사용하며, 각 세션은 5-튜플(src IP, dst IP, src port, dst port, protocol)을 키로 합니다.
/* src/plugins/nat/nat44-ed/nat44_ed.h — NAT44 ED 세션 구조체 */
typedef struct {
/* in2out 키 (LAN → WAN) */
nat_6t_flow_t i2o;
/* out2in 키 (WAN → LAN) — 역방향 매핑 */
nat_6t_flow_t o2i;
u32 flags; /* 정적/동적, TCP 상태, FIN 감지 등 */
u32 thread_index; /* 이 세션을 소유한 워커 스레드 */
u32 per_user_index; /* 사용자별 세션 목록 인덱스 */
/* 타이머: 세션 만료 관리 */
f64 last_heard; /* 마지막 패킷 수신 시각 (vlib_time_now) */
u32 lru_head_index; /* LRU 리스트 위치 (GC 우선순위) */
/* 카운터 */
u64 total_pkts;
u64 total_bytes;
} snat_session_t;
세션이 생성되면 i2o와 o2i 양방향 키가 동시에 해시 테이블에 삽입됩니다. 이후 패킷은 키 조회 한 번으로 변환 정보를 가져오므로, 세션 수가 늘어나도 조회 시간은 O(1)에 근접합니다.
/* src/plugins/nat/nat44-ed/nat44_ed_in2out.c — fast path 핵심 로직 */
static_always_inline int
nat44_ed_in2out_fast_path (snat_main_t *sm, vlib_buffer_t *b,
ip4_header_t *ip, u32 rx_fib_index)
{
clib_bihash_kv_16_8_t kv, value;
/* 5-튜플로 해시 키 생성 */
init_ed_k (&kv, ip->src_address, src_port,
ip->dst_address, dst_port, rx_fib_index, ip->protocol);
/* BIHash 조회 — 히트하면 바로 변환 */
if (clib_bihash_search_16_8 (&sm->flow_hash, &kv, &value) == 0)
{
snat_session_t *s = pool_elt_at_index (tsm->sessions, value.value);
s->last_heard = vlib_time_now (vm);
s->total_pkts++;
s->total_bytes += vlib_buffer_length_in_chain (vm, b);
/* IP 주소와 포트 재작성 */
nat_6t_flow_ip4_translate (sm, b, ip, &s->i2o);
return 0; /* fast path 성공 */
}
return 1; /* miss → slow path로 전환 */
}
clib_bihash_16_8은 16바이트 키, 8바이트 값의 bounded-index 해시로, 버킷 수가 2의 거듭제곱이며 충돌 시 체인이 아닌 페이지 단위 확장을 합니다. 세션 수가 수백만에 도달해도 캐시 미스율이 낮아 일정한 조회 성능을 유지합니다.
NAT44 성능 튜닝 파라미터
운영 환경에서 NAT44의 처리량과 안정성을 좌우하는 핵심 파라미터입니다. 기본값은 소규모 테스트에 맞춰져 있어, 실제 트래픽에서는 반드시 조정해야 합니다.
| 파라미터 | CLI 명령 | 기본값 | 권장 (10Gbps급) | 영향 |
|---|---|---|---|---|
| 최대 세션 수 | nat44 plugin enable sessions N | 65536 | 1048576 | 세션 고갈 시 신규 연결 차단 |
| 사용자당 세션 | nat44 plugin enable user-sessions N | 10000 | 50000 | 단일 내부 IP의 세션 상한 |
| TCP established 타이머 | nat set timeouts tcp-established N | 7440초 | 3600초 | 너무 길면 세션 낭비, 짧으면 끊김 |
| TCP transitory 타이머 | nat set timeouts tcp-transitory N | 240초 | 120초 | SYN/FIN 상태 세션 유지 시간 |
| UDP 타이머 | nat set timeouts udp N | 300초 | 120초 | DNS 등 짧은 플로우에 영향 |
| 주소 풀 크기 | nat44 add address | 1개 | 가능한 많이 | 포트 고갈 방지 (IP당 ~64K 포트) |
# 실전 튜닝 예시: 10Gbps 엣지 라우터
vpp# nat44 plugin enable sessions 1048576
vpp# nat set timeouts tcp-established 3600
vpp# nat set timeouts tcp-transitory 120
vpp# nat set timeouts udp 120
# 주소 풀 확장: WAN IP 여러 개 등록
vpp# nat44 add address 203.0.113.1 - 203.0.113.4
# 워커별 세션 분포 확인 (불균형 → RSS 설정 점검)
vpp# show nat44 sessions count
vpp# show nat44 summary
# 세션 GC(Garbage Collection) 동작 확인
vpp# show nat44 sessions detail | grep "last heard"
show errors에서 nat44-ed-in2out no free external addr 카운터가 증가하면 풀 고갈 징후입니다.
실전 예제: memif 서비스 체이닝
VPP의 memif(memory interface)는 공유 메모리 기반 인터페이스로, VPP 인스턴스 간 또는 VPP-DPDK 앱 간 제로카피 패킷 전달을 제공합니다. 이를 활용하면 여러 네트워크 기능(방화벽, DPI, NAT 등)을 체이닝하여 서비스 펑션 체인(SFC)을 구성할 수 있습니다.
memif 소켓 및 인터페이스 구성
# VPP #1 (분류기) — memif master 역할
vpp1# create memif socket id 1 filename /run/vpp/memif-fw.sock
vpp1# create memif socket id 2 filename /run/vpp/memif-dpi.sock
vpp1# create interface memif id 0 socket-id 1 master
vpp1# create interface memif id 0 socket-id 2 master
vpp1# set interface state memif1/0 up
vpp1# set interface state memif2/0 up
# VPP #2 (방화벽) — memif slave 역할
vpp2# create memif socket id 1 filename /run/vpp/memif-fw.sock
vpp2# create memif socket id 3 filename /run/vpp/memif-egress-fw.sock
vpp2# create interface memif id 0 socket-id 1 slave
vpp2# create interface memif id 0 socket-id 3 master
vpp2# set interface state memif1/0 up
vpp2# set interface state memif3/0 up
# L2 cross-connect (방화벽 통과 후 이그레스로)
vpp2# set interface l2 xconnect memif1/0 memif3/0
vpp2# set interface l2 xconnect memif3/0 memif1/0
# VPP #3 (DPI+NAT) — 유사하게 memif slave로 구성
vpp3# create memif socket id 2 filename /run/vpp/memif-dpi.sock
vpp3# create interface memif id 0 socket-id 2 slave
# ... NAT 설정 생략 (L3 NAT 예제 참조)
# VPP #4 (이그레스) — 여러 memif에서 수신, DPDK로 송신
vpp4# create memif socket id 3 filename /run/vpp/memif-egress-fw.sock
vpp4# create memif socket id 4 filename /run/vpp/memif-egress-dpi.sock
vpp4# create interface memif id 0 socket-id 3 slave
vpp4# create interface memif id 0 socket-id 4 slave
chmod 770 또는 공통 그룹(gid vpp) 설정이 필요합니다. 소켓 경로의 디렉터리에도 실행 권한이 있어야 합니다.
ring-size 2048과 buffer-size 2048로 생성하면 64B 패킷 기준 단일 memif 쌍에서 30Mpps 이상의 처리량을 달성할 수 있습니다. show memif로 링 사용률과 오류 카운터를 확인하세요.
memif 링 동작과 병목 확인 방법
memif는 단순한 "가상 케이블"이 아니라, 두 프로세스가 공유 메모리 위에서 서술자 링(descriptor ring)을 교환하는 구조입니다. 성능 병목이 생기면 대부분 패킷 처리 로직보다 링 고갈, peer 지연, 큐 배치 불일치에서 먼저 징후가 나타납니다.
# 보다 구체적인 생성 예시
vpp1# create memif socket id 1 filename /run/vpp/memif-sfc.sock
vpp1# create interface memif id 0 socket-id 1 master ring-size 1024 buffer-size 2048
vpp1# set interface state memif1/0 up
vpp2# create memif socket id 1 filename /run/vpp/memif-sfc.sock
vpp2# create interface memif id 0 socket-id 1 slave ring-size 1024 buffer-size 2048
vpp2# set interface state memif1/0 up
# 병목 확인
vpp1# show memif
vpp1# show hardware-interfaces memif1/0
vpp1# show interface rx-placement
vpp1# show runtime
vpp1# show errors
| 관찰 값 | 의미 | 우선 점검 항목 |
|---|---|---|
connected 플래그 미표시 | 핸드셰이크 실패 | 소켓 경로, 권한, master/slave 역할 |
| RX ring 사용률만 높음 | 수신 측이 처리 속도를 따라가지 못함 | 워커 배치, downstream 노드 clocks/call |
| TX 슬롯 부족 | peer가 링을 제때 비우지 못함 | 상대 프로세스 CPU 핀닝, 큐 정체 |
| 에러는 없지만 처리량 저하 | 링 자체보다 feature/lookup 비용 문제 | show runtime의 핫 노드 확인 |
show memif와 show runtime을 함께 보는 편이 훨씬 빠릅니다.
memif 분류기(Classifier) 구성
서비스 체이닝에서 핵심은 어떤 트래픽을 어느 체인으로 보낼지 결정하는 분류기입니다. VPP의 classify 테이블은 ACL과 달리 패킷 헤더의 임의 오프셋을 마스크 매칭할 수 있어 유연한 분류가 가능합니다.
# 분류 테이블 생성: TCP 트래픽(proto=6)을 방화벽 체인으로
# mask: IP 프로토콜 필드(offset 23)만 검사
vpp1# classify table mask l3 ip4 proto
# 매치 규칙: TCP(0x06) → memif1/0 (방화벽 체인)
vpp1# classify session acl-hit-next permit table-index 0 \
match l3 ip4 proto 6 action set-ip4-fib-id 0
vpp1# set interface input acl intfc GigabitEthernet0/8/0 ip4-table 0
# 비TCP 트래픽 → memif2/0 (DPI+NAT 체인)으로 L2 xconnect
vpp1# set interface l2 xconnect GigabitEthernet0/8/0 memif2/0
vpp1# set interface l2 xconnect memif2/0 GigabitEthernet0/8/0
보다 세밀한 분류가 필요하면 다단계 테이블 체인을 구성할 수 있습니다.
# 1단계: 프로토콜 분류 (TCP vs UDP vs 기타)
vpp1# classify table mask l3 ip4 proto
# 2단계: TCP 중 대상 포트별 세분화 (80/443 → 웹, 나머지 → 기본)
vpp1# classify table mask l4 dst_port next-table 0
vpp1# classify session table-index 1 match l4 dst_port 80 \
action set-ip4-fib-id 1
vpp1# classify session table-index 1 match l4 dst_port 443 \
action set-ip4-fib-id 1
# FIB 1은 memif1/0(웹 방화벽)으로 라우팅,
# FIB 0(기본)은 memif2/0(DPI)으로 라우팅
memif vs 다른 가상 인터페이스 비교
VPP에서 인스턴스 간 패킷을 전달하는 방법은 여러 가지가 있습니다. 각 방법의 특성과 적합한 용도를 비교합니다.
| 인터페이스 | 카피 횟수 | 64B 처리량 | 지연 | 적합한 환경 |
|---|---|---|---|---|
| memif | 제로카피 | 30+ Mpps | ~1μs | VPP ↔ VPP, VPP ↔ DPDK 앱 |
| veth pair | 2회 (커널 경유) | ~2 Mpps | ~10μs | VPP ↔ 커널 네임스페이스 |
| TAP v2 | 1회 (virtio) | ~5 Mpps | ~5μs | VPP ↔ 호스트 OS |
| AF_XDP | 제로카피 가능 | ~15 Mpps | ~2μs | VPP ↔ XDP 프로그램 |
| vhost-user | 1회 (virtio) | ~8 Mpps | ~3μs | VPP ↔ VM (QEMU) |
memif 공유 메모리 내부 구현
memif의 제로카피가 실제로 어떻게 동작하는지, 핵심 자료구조를 살펴봅니다.
/* src/plugins/memif/memif.h — 서술자 링 구조 */
typedef struct {
u16 flags; /* MEMIF_DESC_FLAG_NEXT: 체인 다음 버퍼 존재 */
u32 region; /* 공유 메모리 영역 인덱스 */
u32 offset; /* 영역 내 버퍼 시작 오프셋 */
u32 length; /* 패킷 데이터 길이 */
u8 metadata[0]; /* 가변 길이 메타데이터 */
} memif_desc_t;
/* 링 구조: head/tail 포인터로 생산자/소비자 패턴 구현 */
typedef struct {
volatile u16 head; /* 생산자가 전진 (새 서술자 추가) */
volatile u16 tail; /* 소비자가 전진 (서술자 소비) */
u16 cookie; /* 버전 검증용 매직 넘버 */
u16 flags; /* MEMIF_RING_FLAG_MASK_INT */
memif_desc_t desc[0]; /* ring-size 개의 서술자 배열 */
} memif_ring_t;
ring-size 1024(기본값)는 10Gbps 이하에서 충분하며, 25Gbps 이상에서는 ring-size 2048 또는 ring-size 4096을 고려하세요.
실전 예제: IPsec VPN 게이트웨이
VPP는 하드웨어 가속(AES-NI, QAT)을 활용한 고성능 IPsec VPN 게이트웨이를 제공합니다. 커널 IPsec 대비 5~10배 높은 처리량을 달성할 수 있어, 데이터센터 간 암호화 터널이나 원격 사이트 VPN에 적합합니다.
Site-to-Site IPsec 터널 구성
# === 사이트 A (VPP GW: 203.0.113.1) ===
# 1. IPsec 터널 인터페이스 생성
vpp# create ipsec tunnel local-ip 203.0.113.1 remote-ip 198.51.100.1 \
local-spi 1000 remote-spi 2000 \
local-crypto-key 6162636465666768696a6b6c6d6e6f70 \
remote-crypto-key 7172737475767778797a414243444546 \
crypto-alg aes-gcm-256 \
instance 0
# 2. 터널 인터페이스 활성화 및 IP 할당
vpp# set interface state ipsec0 up
vpp# set interface ip address ipsec0 10.10.10.1/30
# 3. 원격 사이트 서브넷 라우팅
vpp# ip route add 172.16.0.0/16 via 10.10.10.2 ipsec0
# 4. 검증
vpp# show ipsec tunnel
vpp# show ipsec sa
vpp# ping 10.10.10.2
# === 사이트 B (VPP GW: 198.51.100.1) ===
# 대칭 설정 (SPI 반대, 키 반대)
vpp# create ipsec tunnel local-ip 198.51.100.1 remote-ip 203.0.113.1 \
local-spi 2000 remote-spi 1000 \
local-crypto-key 7172737475767778797a414243444546 \
remote-crypto-key 6162636465666768696a6b6c6d6e6f70 \
crypto-alg aes-gcm-256 \
instance 0
vpp# set interface state ipsec0 up
vpp# set interface ip address ipsec0 10.10.10.2/30
vpp# ip route add 192.168.0.0/16 via 10.10.10.1 ipsec0
IKEv2 동적 키 교환
# IKEv2 프로파일 생성 (정적 키 대신 동적 협상)
vpp# ikev2 profile add pr1
vpp# ikev2 profile set pr1 auth shared-key-mic string MySharedSecret123
vpp# ikev2 profile set pr1 id local ip4-addr 203.0.113.1
vpp# ikev2 profile set pr1 id remote ip4-addr 198.51.100.1
vpp# ikev2 profile set pr1 traffic-selector local \
ip-range 192.168.0.0 - 192.168.255.255 port-range 0 - 65535 protocol 0
vpp# ikev2 profile set pr1 traffic-selector remote \
ip-range 172.16.0.0 - 172.16.255.255 port-range 0 - 65535 protocol 0
# 암호화 알고리즘 제안
vpp# ikev2 profile set pr1 proposals crypto-alg aes-cbc-256 \
integ-alg sha-512 dh-group modp-2048
# IKE 세션 시작
vpp# ikev2 initiate sa-init pr1
vpp# show ikev2 sa
dpdk_plugin.so의 cryptodev 설정으로 하드웨어 암호화 오프로딩(Offloading)이 가능합니다. startup.conf에 dpdk { dev 0000:XX:XX.0 { name cryptodev0 } }를 추가하고, set crypto handler all dpdk로 활성화하세요.
IPsec 패킷 처리 흐름
VPP에서 IPsec 패킷이 어떤 그래프 노드를 거치는지 이해하면, 성능 병목과 설정 오류를 빠르게 찾을 수 있습니다. 아웃바운드(암호화)와 인바운드(복호화) 경로는 완전히 다른 노드 체인을 거칩니다.
암호화 엔진 선택과 QAT 상세 구성
VPP는 여러 암호화 백엔드를 지원합니다. 하드웨어 가용성에 따라 최적의 엔진을 선택하는 것이 IPsec 성능의 핵심입니다.
| 엔진 | 구현 | AES-GCM-256 처리량 | CPU 사용 | 적합 환경 |
|---|---|---|---|---|
| ipsecmb | Intel IPSec-MB 라이브러리 | ~5 Gbps/코어 | 높음 | AES-NI 지원 CPU |
| native | VPP 내장 (AES-NI 직접) | ~4 Gbps/코어 | 높음 | 추가 라이브러리 없이 |
| openssl | OpenSSL EVP API | ~2 Gbps/코어 | 매우 높음 | 범용, 비 Intel CPU |
| dpdk cryptodev | QAT/AESNI-MB PMD | ~20 Gbps/장치 | 매우 낮음 | QAT 카드 보유 시 |
# startup.conf — QAT 하드웨어 가속 IPsec 구성
dpdk {
dev 0000:00:08.0 { name GigabitEthernet0/8/0 }
dev 0000:00:09.0 { name GigabitEthernet0/9/0 }
/* QAT VF (Virtual Function) — lspci로 확인 */
dev 0000:3d:01.0 { name cryptodev0 }
dev 0000:3d:01.1 { name cryptodev1 }
}
plugins {
plugin default { disable }
plugin dpdk_plugin.so { enable }
plugin crypto_ipsecmb_plugin.so { enable } /* SW 폴백 */
}
# 암호화 엔진 우선순위 설정
# QAT를 1순위, IPsec-MB를 2순위(폴백)로 구성
vpp# set crypto handler aes-256-gcm dpdk
vpp# set crypto handler aes-256-gcm ipsecmb 50 /* 우선순위 50 (기본 100) */
# 현재 암호화 핸들러 확인
vpp# show crypto handlers
vpp# show crypto engines
# QAT 상태 확인
vpp# show dpdk crypto devices
echo 16 > /sys/bus/pci/devices/0000:3d:00.0/sriov_numvfs로 VF를 만들고, dpdk-devbind.py -b vfio-pci 0000:3d:01.0으로 DPDK에 바인딩하세요. VF 수는 워커 스레드 수 이상으로 설정해야 각 워커가 전용 큐를 가집니다.
IPsec 성능 모니터링 및 디버깅
# SA(Security Association) 상태 전체 조회
vpp# show ipsec sa detail
# 출력 예시:
# sa-id 0 spi 1000 mode tunnel protocol esp
# crypto aes-gcm-256 integrity none
# tunnel src 203.0.113.1 dst 198.51.100.1
# seq 1234567 seq-hi 0 replay-window 64
# packets 5234121 bytes 3140472600
# 터널 인터페이스 카운터
vpp# show interface counters ipsec0
# 암호화 노드 성능 확인
vpp# show runtime | grep -E "esp-(encrypt|decrypt)"
# 출력 예시:
# esp4-encrypt 987654 1.23e7 12.5 44.3 22 (clocks/pkt)
# esp4-decrypt 876543 1.10e7 12.6 42.1 24 (clocks/pkt)
# 에러 카운터 (드롭 원인 파악)
vpp# show errors | grep -i ipsec
# 주요 에러:
# esp-decrypt: REPLAY — anti-replay 윈도우 초과 (패킷 순서 뒤바뀜)
# esp-decrypt: INTEG_ERROR — 무결성 검증 실패 (키 불일치 또는 손상)
# esp-encrypt: NO_BUFFERS — 버퍼 부족 (buffers-per-numa 증가 필요)
# 트레이스로 패킷별 경로 확인
vpp# clear trace
vpp# trace add dpdk-input 5
# 트래픽 발생 후:
vpp# show trace
| 문제 증상 | 에러 카운터 | 원인 | 해결 방법 |
|---|---|---|---|
| 터널 트래픽 0 | 없음 | 라우팅 미스매치 | show ip fib로 원격 서브넷이 ipsec0으로 라우팅되는지 확인 |
| 단방향만 동작 | INTEG_ERROR | 양쪽 키/SPI 불일치 | local/remote 키와 SPI가 대칭인지 교차 검증 |
| 간헐적 드롭 | REPLAY | 패킷 순서 뒤바뀜 | replay-window를 128 이상으로 증가, 멀티패스 라우팅 확인 |
| 처리량 저조 | 없음 | SW 암호화 병목 | show crypto handlers로 HW 엔진 활성화 확인 |
| 높은 CPU 사용 | 없음 | openssl 폴백 | ipsecmb 또는 dpdk cryptodev로 전환 |
IPsec 소스 코드 구조
VPP IPsec의 암호화 처리가 실제로 어떻게 구현되는지, 핵심 함수를 살펴봅니다.
/* src/vnet/ipsec/esp_encrypt.c — ESP 암호화 노드 핵심 루프 */
static uword
esp_encrypt_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
vlib_frame_t *frame, int is_ip6, int is_tun)
{
u32 n_left = frame->n_vectors;
vlib_buffer_t *bufs[VLIB_FRAME_SIZE];
vlib_get_buffers (vm, from, bufs, n_left);
while (n_left > 0)
{
ipsec_sa_t *sa = ipsec_sa_get (sa_index);
/* 1. ESP 헤더 삽입 (SPI + Sequence Number) */
esp_header_t *esp = vlib_buffer_push_uninit (b, sizeof (*esp));
esp->spi = clib_host_to_net_u32 (sa->spi);
esp->seq = clib_host_to_net_u32 (sa->seq++);
/* 2. IV(Initialization Vector) 생성 */
/* AES-GCM: 8바이트 nonce + 4바이트 salt */
/* 3. 암호화 연산 요청 (비동기 가능) */
vnet_crypto_op_t *op = ops + n_ops++;
op->op = VNET_CRYPTO_OP_AES_256_GCM_ENC;
op->src = payload;
op->dst = payload; /* in-place 암호화 */
op->len = payload_len;
op->tag = tag; /* GCM 인증 태그 */
/* 4. 외부 IP 헤더 재작성 */
ip4_header_set_len (oh, new_len);
oh->protocol = IP_PROTOCOL_ESP; /* proto 50 */
}
/* 비동기 엔진이면 여기서 일괄 제출 */
if (n_ops)
vnet_crypto_enqueue_ops (vm, ops, n_ops);
}
esp-encrypt 노드가 암호화 요청을 큐에 넣고, 별도의 crypto-dispatch 노드가 완료된 패킷을 수집하여 다음 노드로 전달합니다. 이 방식은 CPU가 암호화 대기 중에 다른 패킷을 처리할 수 있어 처리량이 크게 향상됩니다.
VPP IPsec vs 커널 IPsec 성능 비교
동일 하드웨어(Xeon E-2388G, 25GbE NIC)에서 AES-GCM-256 Site-to-Site 터널의 성능을 비교한 참고 수치입니다.
| 구성 | 패킷 크기 | 처리량 (Gbps) | PPS | CPU 코어 수 |
|---|---|---|---|---|
| 커널 IPsec (xfrm) | 1400B | 4.2 | 375K | 4 |
| 커널 IPsec (xfrm) | 64B | 0.3 | 520K | 4 |
| VPP IPsec (ipsecmb) | 1400B | 18.5 | 1.65M | 4 |
| VPP IPsec (ipsecmb) | 64B | 2.1 | 4.1M | 4 |
| VPP IPsec (QAT) | 1400B | 23.8 | 2.12M | 2 |
| VPP IPsec (QAT) | 64B | 3.8 | 7.4M | 2 |
실전 예제: SSL/TLS Inspection
현대 네트워크 트래픽의 90% 이상이 TLS로 암호화되어 있습니다. 방화벽이나 IDS/IPS가 암호화된 페이로드를 검사하려면 SSL/TLS Inspection(SSL 가시성 확보)이 필수입니다. VPP는 유저스페이스에서 TLS 종단(Termination)과 재암호화(Re-encryption)를 수행할 수 있어, 커널 기반 프록시 대비 훨씬 높은 처리량으로 SSL Inspection을 구현할 수 있습니다.
SSL Inspection의 원리
SSL Inspection은 본질적으로 중간자(Man-in-the-Middle) 구조입니다. 클라이언트와 실제 서버 사이에 검사 장비가 위치하여, 양쪽과 각각 독립적인 TLS 세션을 맺습니다. 이 구조에서 검사 장비는 잠시 평문 상태의 데이터를 볼 수 있고, 여기에 DPI(Deep Packet Inspection), 악성코드 탐지, DLP(Data Loss Prevention) 등의 보안 정책을 적용합니다.
VPP SSL Inspection 아키텍처
VPP에서 SSL Inspection을 구현하는 방식은 크게 두 가지입니다. 각각의 장단점을 이해해야 환경에 맞는 설계를 선택할 수 있습니다.
| 구현 방식 | 구조 | 장점 | 단점 | 적합 환경 |
|---|---|---|---|---|
| 인라인 프록시 | VPP 내 VCL + TLS 플러그인 | 단일 프로세스, 낮은 지연 | VPP 플러그인 개발 필요 | 고성능 NGFW |
| SFC 분리형 | VPP L4 분류 → memif → 외부 프록시 | 기존 프록시 재활용, 유연 | memif 오버헤드, 복잡도 | 기존 인프라 연동 |
인라인 SSL Inspection 구현
VPP의 VCL(VPP Communication Library)과 TLS 플러그인을 활용하여 단일 VPP 프로세스 내에서 SSL Inspection을 구현하는 방법입니다. 핵심은 두 개의 독립적인 TLS 컨텍스트를 관리하는 것입니다.
# /etc/vpp/startup.conf — SSL Inspection용 구성
unix {
cli-listen /run/vpp/cli.sock
log /var/log/vpp/vpp.log
full-coredump
gid vpp
}
dpdk {
dev 0000:00:08.0 { name GigabitEthernet0/8/0 } /* LAN */
dev 0000:00:09.0 { name GigabitEthernet0/9/0 } /* WAN */
no-multi-seg
}
cpu {
main-core 0
corelist-workers 1-7 /* TLS 핸드셰이크가 CPU 집약적 */
}
buffers {
buffers-per-numa 65536 /* TLS 레코드 버퍼링 위해 증가 */
default data-size 2048
}
session {
evt_qs_memfd_seg /* 세션 이벤트 큐 공유 메모리 */
event-queue-length 100000 /* 동시 세션 수에 비례 */
}
plugins {
plugin default { disable }
plugin dpdk_plugin.so { enable }
plugin tlsopenssl_plugin.so { enable }
plugin http_plugin.so { enable }
plugin nat_plugin.so { enable }
plugin acl_plugin.so { enable }
plugin ping_plugin.so { enable }
}
CA 인증서 인프라 구성
SSL Inspection의 기반은 내부 CA(Certificate Authority)입니다. 검사기가 원본 서버의 인증서 정보를 복사하여 이 CA로 동적 서명한 인증서를 클라이언트에 제시합니다.
# 1. 내부 Root CA 키 쌍 생성
$ openssl ecparam -genkey -name prime256v1 -out inspection-ca.key
$ openssl req -new -x509 -key inspection-ca.key -sha256 -days 3650 \
-out inspection-ca.crt \
-subj "/CN=Internal SSL Inspection CA/O=MyOrg/C=KR"
# 2. VPP TLS에서 사용할 서버 키 (동적 인증서 서명용)
$ openssl ecparam -genkey -name prime256v1 -out inspection-server.key
# 3. 클라이언트(브라우저)에 CA 인증서 배포
# Linux: /usr/local/share/ca-certificates/ 에 복사 후 update-ca-certificates
# Windows: GPO로 Trusted Root CA에 배포
# macOS: Keychain Access에서 "항상 신뢰"로 추가
# 4. VPP에 TLS 자격증명 등록
vpp# tls ca-cert /etc/vpp/certs/inspection-ca.crt
vpp# tls cert /etc/vpp/certs/inspection-server.crt
vpp# tls key /etc/vpp/certs/inspection-server.key
0600으로 제한하세요. CA 인증서의 유효기간도 운영 정책에 맞게 설정해야 합니다.
동적 인증서 생성 구현
SSL Inspection의 핵심 기술은 원본 서버 인증서의 Subject/SAN을 복제하여 내부 CA로 즉시 서명하는 것입니다. 이 과정이 TLS 핸드셰이크 지연의 가장 큰 비중을 차지합니다.
/* SSL Inspection 플러그인: 동적 인증서 생성 핵심 로직 */
/* OpenSSL API를 사용한 구현 예시 */
static X509 *
generate_inspection_cert (X509 *orig_cert,
EVP_PKEY *ca_key,
X509 *ca_cert)
{
X509 *cert = X509_new ();
X509_NAME *subj = X509_get_subject_name (orig_cert);
/* 원본 인증서에서 Subject 복사 */
X509_set_subject_name (cert, subj);
/* Issuer를 내부 CA로 설정 */
X509_set_issuer_name (cert, X509_get_subject_name (ca_cert));
/* 원본의 SAN(Subject Alternative Name) 복사 — 필수! */
int san_idx = X509_get_ext_by_NID (orig_cert, NID_subject_alt_name, -1);
if (san_idx >= 0)
{
X509_EXTENSION *san_ext = X509_get_ext (orig_cert, san_idx);
X509_add_ext (cert, san_ext, -1);
}
/* 일련번호, 유효기간 설정 */
ASN1_INTEGER_set (X509_get_serialNumber (cert),
generate_unique_serial ());
X509_gmtime_adj (X509_get_notBefore (cert), 0);
X509_gmtime_adj (X509_get_notAfter (cert), 86400 * 365);
/* 검사기 서버의 공개키 설정 */
X509_set_pubkey (cert, inspection_server_key);
/* 내부 CA 개인키로 서명 */
X509_sign (cert, ca_key, EVP_sha256 ());
return cert;
}
/* 캐시: 동일 서버에 대한 반복 생성 방지 */
typedef struct {
u8 *server_name; /* SNI 또는 Subject CN */
X509 *cached_cert; /* 생성된 위조 인증서 */
f64 created_at; /* 캐시 생성 시각 */
f64 ttl; /* 캐시 유효 시간 */
} ssl_cert_cache_entry_t;
SNI 기반 바이패스와 정책 분류
모든 TLS 트래픽을 복호화하는 것은 성능 낭비이자 법적 리스크입니다. SNI(Server Name Indication)를 기반으로 검사 대상과 바이패스 대상을 분류해야 합니다. SNI는 TLS ClientHello의 확장 필드에 평문으로 포함되므로, TLS 세션을 맺기 전에 읽을 수 있습니다.
/* SNI 파싱: ClientHello에서 서버 이름 추출 */
static int
parse_sni_from_client_hello (u8 *data, u32 len,
u8 **sni_out, u16 *sni_len_out)
{
/* TLS Record: ContentType(1) + Version(2) + Length(2) */
if (len < 5 || data[0] != 0x16) /* 0x16 = Handshake */
return -1;
/* Handshake: HandshakeType(1) + Length(3) */
u8 *hs = data + 5;
if (hs[0] != 0x01) /* 0x01 = ClientHello */
return -1;
/* ClientHello: Version(2) + Random(32) + SessionID(var)
* + CipherSuites(var) + Compression(var) + Extensions(var) */
u32 offset = 5 + 4; /* record header + handshake header */
offset += 2 + 32; /* version + random */
offset += 1 + data[offset]; /* session ID length + data */
u16 cs_len = (data[offset] << 8) | data[offset + 1];
offset += 2 + cs_len; /* cipher suites */
offset += 1 + data[offset]; /* compression */
/* Extensions 순회 → SNI(type=0x0000) 찾기 */
u16 ext_total = (data[offset] << 8) | data[offset + 1];
offset += 2;
u32 ext_end = offset + ext_total;
while (offset + 4 <= ext_end)
{
u16 ext_type = (data[offset] << 8) | data[offset + 1];
u16 ext_len = (data[offset + 2] << 8) | data[offset + 3];
offset += 4;
if (ext_type == 0x0000) /* SNI extension */
{
/* SNI list: ListLen(2) + Type(1) + NameLen(2) + Name */
u16 name_len = (data[offset + 3] << 8) | data[offset + 4];
*sni_out = data + offset + 5;
*sni_len_out = name_len;
return 0;
}
offset += ext_len;
}
return -1; /* SNI 없음 (IP 직접 연결 등) */
}
SFC 분리형: VPP + 외부 SSL 프록시
기존 프록시(Squid SSL Bump, mitmproxy 등)를 재활용하면서 VPP의 고성능 패킷 분류만 활용하는 구조입니다. VPP가 TLS 트래픽을 식별하여 memif로 외부 프록시에 전달하고, 검사 완료 후 다시 VPP로 복귀시킵니다.
# VPP: TLS 트래픽 분류 및 memif 전달 구성
# 1. SSL 프록시용 memif 생성
vpp# create memif socket id 10 filename /run/vpp/memif-ssl-proxy.sock
vpp# create interface memif id 0 socket-id 10 master ring-size 2048
vpp# set interface state memif10/0 up
vpp# set interface ip address memif10/0 10.255.0.1/30
# 2. 프록시로부터 복귀용 memif
vpp# create memif socket id 11 filename /run/vpp/memif-ssl-return.sock
vpp# create interface memif id 0 socket-id 11 master ring-size 2048
vpp# set interface state memif11/0 up
vpp# set interface ip address memif11/0 10.255.1.1/30
# 3. ACL로 TCP 443 트래픽 분류 → 프록시로 라우팅
vpp# set acl-plugin acl permit+reflect proto 6 dport 443
vpp# set acl-plugin interface GigabitEthernet0/8/0 input acl 0
# 4. PBR(Policy-Based Routing): TCP 443 매칭 시 프록시로 전달
vpp# ip table add 100
vpp# ip route add 0.0.0.0/0 table 100 via 10.255.0.2 memif10/0
# classify 테이블로 TLS 트래픽을 FIB 100으로 전환
vpp# classify table mask l3 ip4 proto l4 dst_port
vpp# classify session table-index 0 match l3 ip4 proto 6 l4 dst_port 443 \
action set-ip4-fib-id 100
# 5. 프록시에서 복귀한 트래픽은 기본 FIB로 WAN 전달
vpp# ip route add 0.0.0.0/0 via 203.0.113.254 GigabitEthernet0/9/0
# 외부 프록시 측: libmemif를 사용한 Squid SSL Bump 연동
# 또는 mitmproxy 기반 간이 구성
# mitmproxy 실행 (memif 대신 TAP으로 연결하는 간이 예시)
$ sudo ip link set vpp-proxy up
$ sudo ip addr add 10.255.0.2/30 dev vpp-proxy
$ sudo ip route add default via 10.255.0.1 dev vpp-proxy
# mitmproxy를 투명 프록시 모드로 실행
$ mitmproxy --mode transparent --listen-host 10.255.0.2 --listen-port 8443 \
--set ssl_insecure=true \
--set confdir=/etc/mitmproxy \
-s inspection_policy.py
# iptables로 투명 리다이렉트 (프록시 호스트에서)
$ sudo iptables -t nat -A PREROUTING -i vpp-proxy -p tcp --dport 443 \
-j REDIRECT --to-port 8443
TLS 1.3 Inspection 고려사항
TLS 1.3은 핸드셰이크 구조가 크게 바뀌어 SSL Inspection 구현에 영향을 줍니다.
| 변경점 | TLS 1.2 | TLS 1.3 | Inspection 영향 |
|---|---|---|---|
| 핸드셰이크 왕복 | 2-RTT | 1-RTT (0-RTT 가능) | 인증서 위조 시간 압박 증가 |
| 서버 인증서 위치 | 평문 전송 | 암호화 후 전송 | 검사기가 먼저 서버와 핸드셰이크 완료해야 원본 인증서 확인 가능 |
| 세션 재개 | Session ID/Ticket | PSK (0-RTT) | 0-RTT 데이터는 재생 공격 위험 → 검사기에서 0-RTT 차단 또는 제한 필요 |
| 암호 스위트 | 다양 (RC4, CBC 등) | AEAD만 (GCM, ChaCha20) | 오래된 암호 스위트 다운그레이드 불가 → 검사기도 AEAD 지원 필수 |
| SNI 암호화 (ECH) | 없음 | 선택적 (초안) | ECH 활성 시 SNI 파싱 불가 → DNS 기반 정책으로 전환 필요 |
/* TLS 1.3 핸드셰이크 시 Inspection 순서 */
/* 1. ClientHello 수신 → SNI 추출 + 바이패스 판단 */
/* 2. 검사 대상이면: 검사기가 원본 서버에 먼저 TLS 1.3 연결 */
/* 3. 서버의 EncryptedExtensions + Certificate 수신 (복호화 후) */
/* 4. 원본 인증서 정보로 위조 인증서 생성 */
/* 5. 클라이언트에게 위조 인증서로 ServerHello 응답 */
static int
tls13_inspection_handshake (ssl_inspect_ctx_t *ctx)
{
/* 서버측 TLS 1.3 핸드셰이크 먼저 완료 */
int rv = SSL_connect (ctx->server_ssl);
if (rv != 1)
return handle_ssl_error (ctx, rv);
/* 서버 인증서 체인 가져오기 */
X509 *server_cert = SSL_get_peer_certificate (ctx->server_ssl);
if (!server_cert)
return -1;
/* 원본 인증서 검증 (OCSP, CRL 확인) */
long verify_result = SSL_get_verify_result (ctx->server_ssl);
if (verify_result != X509_V_OK)
{
/* 원본 서버 인증서 검증 실패 → 정책에 따라 차단 또는 경고 */
log_cert_error (ctx, verify_result);
if (ctx->policy & SSL_INSPECT_BLOCK_INVALID_CERT)
return -1;
}
/* 동적 인증서 생성 (캐시 우선 확인) */
X509 *forged = cert_cache_lookup (ctx->sni);
if (!forged)
{
forged = generate_inspection_cert (server_cert,
ctx->ca_key, ctx->ca_cert);
cert_cache_insert (ctx->sni, forged, CERT_CACHE_TTL);
}
/* 클라이언트측 TLS 컨텍스트에 위조 인증서 설정 */
SSL_use_certificate (ctx->client_ssl, forged);
SSL_use_PrivateKey (ctx->client_ssl, ctx->server_key);
/* 클라이언트 핸드셰이크 완료 */
return SSL_accept (ctx->client_ssl);
}
SSL_CTX_set_max_early_data(ctx, 0)으로 0-RTT를 비활성화하는 것이 안전합니다.
양방향 데이터 릴레이 구현
핸드셰이크가 완료되면 검사기는 양쪽 TLS 세션의 평문 데이터를 릴레이하면서, 중간에 DPI 엔진을 통과시킵니다. VPP의 세션 레이어를 활용하면 이 릴레이를 이벤트 기반으로 효율적으로 구현할 수 있습니다.
/* 양방향 릴레이 핵심 로직 — VCL 세션 기반 */
static void
ssl_inspect_relay_data (ssl_inspect_ctx_t *ctx)
{
u8 buf[16384]; /* TLS 레코드 최대 크기 */
int n;
/* 클라이언트 → 서버 방향 */
while ((n = SSL_read (ctx->client_ssl, buf, sizeof (buf))) > 0)
{
/* DPI 엔진에 평문 전달 */
dpi_verdict_t verdict = dpi_inspect (ctx->dpi_ctx,
buf, n,
DPI_DIR_CLIENT_TO_SERVER);
if (verdict == DPI_BLOCK)
{
/* 차단: 클라이언트에 블록 페이지 전송 후 세션 종료 */
send_block_page (ctx->client_ssl, ctx->block_reason);
ssl_inspect_close (ctx);
return;
}
/* 허용: 원본 서버로 재암호화하여 전달 */
int written = SSL_write (ctx->server_ssl, buf, n);
if (written <= 0)
{
handle_ssl_error (ctx, written);
return;
}
}
/* 서버 → 클라이언트 방향 (대칭) */
while ((n = SSL_read (ctx->server_ssl, buf, sizeof (buf))) > 0)
{
dpi_verdict_t verdict = dpi_inspect (ctx->dpi_ctx,
buf, n,
DPI_DIR_SERVER_TO_CLIENT);
if (verdict == DPI_BLOCK)
{
ssl_inspect_close (ctx);
return;
}
SSL_write (ctx->client_ssl, buf, n);
}
}
SSL Inspection 성능 고려사항
SSL Inspection은 TLS 핸드셰이크(특히 RSA/ECDSA 연산)와 대칭 암호화/복호화를 이중으로 수행하므로, 일반 패킷 포워딩 대비 CPU 부담이 매우 큽니다.
| 구성 요소 | CPU 비용 (상대) | 최적화 방법 |
|---|---|---|
| RSA-2048 서명 (인증서 생성) | 매우 높음 | ECDSA P-256 전환 (10배 빠름), 인증서 캐싱 |
| TLS 핸드셰이크 | 높음 | 세션 재개(PSK), QAT 비동기 오프로드 |
| AES-GCM 대칭 암복호화 | 중간 | AES-NI 활용 (기본 활성), QAT |
| DPI 시그니처 매칭 | 가변 (룰 수 의존) | Hyperscan/Vectorscan 가속, 룰 최적화 |
| 인증서 검증 (OCSP/CRL) | 네트워크 의존 | OCSP Stapling, 로컬 CRL 캐싱 |
# SSL Inspection 성능 모니터링
# TLS 핸드셰이크 횟수 및 소요 시간 확인
vpp# show tls
# 출력 예시:
# OpenSSL engine:
# Ctx allocated: 45231
# Handshakes: 12847 (active: 23)
# Rx bytes: 4.2 GB
# Tx bytes: 1.8 GB
# 세션 레이어 상태 (동시 세션 수)
vpp# show session verbose
# Transport Active Listening
# tcp 2341 4
# tls 1892 2
# 워커별 부하 분포 확인
vpp# show runtime
# session-queue 노드의 Vectors/Call이 워커 간 균등해야 합니다.
# 편향이 심하면 TCP 해시 분산(RSS/Flow Director) 조정 필요
# 인증서 캐시 적중률 (커스텀 플러그인 시)
# 적중률 90% 이상이면 양호, 50% 이하면 캐시 크기 확대 필요
| 구성 | 동시 세션 | 신규 핸드셰이크/초 | 처리량 (Gbps) | CPU 코어 |
|---|---|---|---|---|
| VPP + OpenSSL (SW) | 50K | ~2,000 CPS | ~5 | 8 |
| VPP + OpenSSL + QAT | 100K | ~8,000 CPS | ~12 | 4 |
| 커널 Squid SSL Bump | 10K | ~500 CPS | ~1 | 8 |
SSL Inspection 트러블슈팅
| 증상 | 진단 | 원인 | 해결 |
|---|---|---|---|
| 브라우저 인증서 경고 | 브라우저에서 인증서 체인 확인 | 내부 CA가 신뢰 저장소에 미설치 | GPO/MDM으로 CA 인증서 배포 |
| 특정 앱 연결 실패 | 앱의 인증서 요구사항 확인 | Certificate Pinning | 해당 도메인을 바이패스 목록에 추가 |
| 핸드셰이크 타임아웃 | show tls active 핸드셰이크 수 | 서버 연결 지연 또는 인증서 생성 병목 | ECDSA CA 전환, QAT 추가, 타임아웃 조정 |
| CPS 저하 | show runtime session-queue 클럭 | RSA 서명 CPU 부하 | 인증서 캐시 확대, CA 키를 ECDSA로 변경 |
| QUIC/HTTP3 통과 안됨 | UDP 443 트래픽 확인 | QUIC은 UDP 기반 → TCP 분류기 미매칭 | UDP 443도 분류 대상에 추가, 또는 QUIC 차단 후 TCP 폴백 유도 |
| mTLS 서비스 장애 | 서버가 클라이언트 인증서 요구 | 검사기에 원본 클라이언트 인증서 없음 | mTLS 대상을 바이패스, 또는 클라이언트 인증서 릴레이 구현 |
성능 측정 및 벤치마크
VPP 구성을 완료한 뒤, 실제 처리량과 지연을 측정하는 체계적인 방법을 소개합니다. 수치 없는 "잘 되는 것 같다"는 운영에서 통하지 않습니다.
TRex를 이용한 트래픽 생성
TRex는 DPDK 기반 고성능 트래픽 생성기로, VPP 벤치마크의 사실상 표준입니다. CSIT(Continuous System Integration Testing)에서도 TRex를 사용합니다.
# TRex STL(Stateless) 프로파일 — NAT44 부하 테스트
from trex_stl_lib.api import *
class STLS1(object):
def create_stream(self):
base_pkt = Ether() / IP(
src="192.168.1.10",
dst="198.51.100.10"
) / UDP(
sport=1024,
dport=80
) / ("X" * 16)
# 소스 IP와 포트를 변경하여 다양한 플로우 생성
vm = STLScVmRaw([
STLVmFlowVar(name="src_ip",
min_value="192.168.1.10",
max_value="192.168.1.250",
size=4, op="inc"),
STLVmWrFlowVar(fv_name="src_ip",
pkt_offset="IP.src"),
STLVmFlowVar(name="src_port",
min_value=1024,
max_value=60000,
size=2, op="inc"),
STLVmWrFlowVar(fv_name="src_port",
pkt_offset="UDP.sport"),
STLVmFixIpv4(offset="IP"),
])
return STLStream(
packet=STLPktBuilder(pkt=base_pkt, vm=vm),
mode=STLTXCont(pps=1000000) # 1Mpps부터 시작
)
def get_streams(self, **kwargs):
return [self.create_stream()]
def register():
return STLS1()
# TRex 실행 및 결과 확인
$ sudo ./t-rex-64 -i -c 4 --cfg /etc/trex_cfg.yaml
# TRex 콘솔에서:
trex> start -f nat44_test.py -m 5mpps -d 60
trex> stats
# TX: 5.00 Mpps, RX: 4.98 Mpps → 드롭율 0.04%
# VPP 측에서 동시에 확인:
vpp# show runtime
vpp# show interface GigabitEthernet0/8/0
vpp# show nat44 sessions count
VPP 내부 성능 카운터 해석
show runtime 출력의 각 열이 무엇을 뜻하는지 정확히 알아야 병목을 찾을 수 있습니다.
Name State Calls Vectors Suspends Clocks Vectors/Call
dpdk-input polling 1234567 9.87e7 0 22.1 79.9
ethernet-input active 1234567 9.87e7 0 8.3 79.9
ip4-input active 1234567 9.87e7 0 12.5 79.9
ip4-lookup active 1234567 9.87e7 0 15.2 79.9
nat44-ed-in2out active 1200000 9.60e7 0 44.3 80.0
ip4-rewrite active 1200000 9.60e7 0 9.8 80.0
GigE0/9/0-output active 1200000 9.60e7 0 5.1 80.0
GigE0/9/0-tx active 1200000 9.60e7 0 18.7 80.0
| 열 | 의미 | 정상 범위 | 이상 징후 |
|---|---|---|---|
| Calls | 노드가 호출된 횟수 | — | — |
| Vectors | 처리한 총 패킷 수 | — | 이전 노드보다 현저히 적으면 드롭 발생 |
| Vectors/Call | 호출당 평균 배치 크기 | 64~256 | <10이면 배치 효율 저하 |
| Clocks | 패킷당 평균 CPU 클럭 | 노드 의존 | 특정 노드가 100+ 이면 병목 |
| Suspends | 자발적 중단 횟수 | 0 | >0이면 스케줄링 문제 |
Clocks가 가장 높은 노드가 처리 비용의 병목입니다. NAT의 경우 nat44-ed-in2out이 보통 가장 높고, IPsec에서는 esp-encrypt/esp-decrypt가 지배적입니다. Vectors/Call이 낮다면 입력 큐가 적절히 배치되지 않아 벡터 효율이 떨어지는 것이므로 RSS 큐와 워커 매핑을 점검하세요.
트러블슈팅 가이드
VPP 실전 구성에서 자주 만나는 문제와 체계적인 진단 방법을 정리합니다.
패킷이 전달되지 않는 경우
# 1단계: 인터페이스 상태 확인
vpp# show interface
# 모든 인터페이스가 'up'인지 확인
# 'admin-down'이면: set interface state up
# 2단계: 수신 패킷 확인
vpp# clear interface counters
# 트래픽 생성 후:
vpp# show interface
# rx-pkts가 증가하는지 확인
# 증가하지 않으면: DPDK 바인딩, Hugepage, NIC 드라이버 문제
# 3단계: 패킷 추적
vpp# clear trace
vpp# trace add dpdk-input 20
# 트래픽 생성 후:
vpp# show trace
# 패킷이 어느 노드에서 드롭되는지 확인
# 'error' 또는 'drop' 노드에서 끝나면 해당 에러 메시지 확인
# 4단계: 에러 카운터 확인
vpp# show errors
# 가장 높은 에러 카운터부터 추적
# 5단계: FIB(라우팅 테이블) 확인
vpp# show ip fib # 전체 라우팅 테이블
vpp# show ip fib 198.51.100.0/24 # 특정 서브넷 경로
자주 발생하는 문제와 해결법
| 증상 | 진단 명령 | 원인 | 해결 |
|---|---|---|---|
| VPP 시작 실패 | journalctl -u vpp | Hugepage 부족, DPDK 바인딩 실패 | echo 1024 > /proc/sys/vm/nr_hugepages, dpdk-devbind.py -b vfio-pci |
| DPDK 인터페이스 미표시 | show interface | NIC가 DPDK에 바인딩되지 않음 | dpdk-devbind.py --status로 확인, modprobe vfio-pci |
| ARP 미응답 | show ip neighbors | 인터페이스에 IP 미할당 | set interface ip address 재확인 |
| NAT 세션 미생성 | show nat44 sessions | in/out 방향 설정 오류 | set interface nat44 in/out 방향 교차 확인 |
| memif 연결 안됨 | show memif | 소켓 경로/권한, master/slave 역할 | 양쪽 소켓 파일 경로 동일 확인, 권한 chmod 770 |
| IPsec 단방향만 동작 | show ipsec sa detail | SPI/키 불일치, 라우팅 미설정 | 양쪽 설정 교차 검증, show ip fib |
| 처리량 기대 이하 | show runtime | 워커 불균형, 배치 효율 저하 | RSS 큐 수와 워커 수 일치, isolcpus 확인 |
패킷 캡처 (pcap)
패킷 내용을 직접 확인해야 할 때는 VPP의 내장 pcap 기능을 사용합니다. tcpdump와 달리 커널을 거치지 않으므로 DPDK 인터페이스의 패킷도 캡처할 수 있습니다.
# 특정 인터페이스의 수신 패킷 캡처
vpp# pcap trace rx tx max 1000 intfc GigabitEthernet0/8/0 \
file /tmp/vpp-capture.pcap
# 트래픽 발생 후 캡처 중지
vpp# pcap trace off
# 호스트에서 Wireshark로 분석
$ wireshark /tmp/vpp-capture.pcap
# 또는 tcpdump로 빠르게 확인
$ tcpdump -r /tmp/vpp-capture.pcap -nn | head -20
# 그래프 노드 단위 캡처 (더 세밀한 분석)
vpp# pcap trace rx tx max 500 \
classify-table-index 0 \
file /tmp/vpp-classified.pcap
max를 작게 설정하고, 분석이 끝나면 즉시 pcap trace off로 중지하세요. 대량 캡처가 필요하면 trace add와 show trace로 노드 경로만 먼저 확인하는 것이 효율적입니다.
관련 문서
이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.