오버레이 · 실전 시나리오
VPP에서 오버레이 네트워크와 실전 시나리오를 구성하는 가이드입니다. Segment Routing(SRv6), QoS, VXLAN/GENEVE 터널, 실전 시나리오 맵, L3 라우터+NAT44 예제, memif 서비스 체이닝 예제를 다룹니다.
Segment Routing (SRv6)
VPP는 IPv6 Segment Routing(SRv6)을 데이터 플레인 수준에서 완전히 구현합니다. SRv6는 IPv6 확장 헤더인 Segment Routing Header(SRH)를 사용하여 패킷 경로를 소스에서 명시적으로 지정합니다. SRH에는 128비트 IPv6 주소 형식의 SID(Segment Identifier) 목록이 역순으로 저장되며, Segments Left 카운터가 현재 활성 세그먼트를 가리킵니다. 각 SRv6 노드는 Segments Left 값을 1씩 감소시키고 다음 SID를 IPv6 목적지 주소에 복사하여 패킷을 다음 홉으로 전달합니다.
VPP는 두 가지 SRv6 적용 모드를 지원합니다. 캡슐화 모드는 원본 패킷을 새로운 IPv6 + SRH 외부 헤더로 감싸는 방식이며, 삽입(Insertion) 모드는 기존 IPv6 헤더에 SRH를 직접 삽입합니다. 캡슐화 모드가 기본이며, 삽입 모드는 중간 라우터가 SRv6를 인식하지 못하는 환경에서 유용합니다.
로컬 SID 동작(Local SID Behavior)은 각 노드에서 SID가 자신에게 할당된 것일 때 수행하는 처리를 정의합니다. VPP가 지원하는 주요 동작은 다음과 같습니다:
- End: 중간 노드에서 Segments Left를 감소시키고 다음 SID로 전달합니다.
- End.X: End와 동일하되, 지정된 인접 노드(cross-connect)로 L3 전달합니다.
- End.DT4 / End.DT6: SRH를 제거하고 내부 IPv4 또는 IPv6 패킷을 특정 VRF 테이블에서 라우팅합니다. VPN 서비스 구현에 핵심적입니다.
- End.DX4 / End.DX6: SRH를 제거하고 내부 IPv4 또는 IPv6 패킷을 지정된 인접 노드로 직접 전달합니다.
BSID(Binding SID)는 SRv6 정책 전체를 하나의 SID로 추상화하여 트래픽 엔지니어링을 단순화합니다. 입구 노드는 BSID만 참조하면 되므로 정책 내부의 세그먼트 목록 변경이 투명하게 처리됩니다. VPP에서는 sr policy add bsid 명령으로 BSID를 정책에 바인딩하고, sr steer 명령으로 특정 트래픽을 해당 정책으로 유도합니다.
VPP는 SRv6 Mobile 기능도 제공하여 5G 환경에서 UPF(User Plane Function) 역할을 수행합니다. GTP-U 터널 패킷을 SRv6 세그먼트로 변환(T.M.GTP4.D)하거나, SRv6 패킷을 다시 GTP-U로 복원(End.M.GTP4.E)하는 상호 변환을 지원합니다. 이를 통해 모바일 백홀 네트워크에서 GTP-U 터널링 대신 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
| 알고리즘 | 유형 | native | ipsecmb | openssl | QAT HW |
|---|---|---|---|---|---|
| AES-GCM-128/256 | AEAD | O | O | O | O |
| AES-CBC-128/256 | 암호화 | — | O | O | O |
| ChaCha20-Poly1305 | AEAD | — | O | O | — |
| HMAC-SHA-256/512 | 인증 | — | O | O | O |
| AES-CTR | 암호화 | — | O | O | O |
| NULL | 테스트 | O | O | O | — |
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 기능 | 설명 | 노드 |
|---|---|---|
| Policer | 2r3c (RFC 2698) 기반 트래픽 폴리싱 | policer-input |
| QoS Record | 인입 패킷의 QoS 값(DSCP/MPLS TC) 기록 | qos-record |
| QoS Mark | 송출 패킷에 QoS 값 마킹 | qos-mark |
| QoS Store | 패킷에 고정 QoS 값 저장 | qos-store |
엔드투엔드 QoS 흐름 — Ingress 분류 → Policing → Marking → Egress Shaping
위에서 다룬 폴리서/마킹은 개별 기능이지만, 운영에서는 4단계가 한 흐름으로 묶여야 의미가 있습니다. 패킷이 들어와서 나갈 때까지 어떤 결정이 어디서 일어나는지 정리합니다.
- Ingress 분류 (qos-record + classify) — 외부 헤더의 DSCP/PCP/MPLS TC 또는 ACL 5-tuple 매칭으로 트래픽 클래스를 결정합니다. 결과는 버퍼의 opaque 영역에 저장되어 이후 노드에 전달됩니다.
- Policing (policer-input) — 클래스별 토큰 버킷에서 색상(green/yellow/red)을 부여합니다. red는 즉시 드롭, yellow는 강등 마킹 또는 통과, green은 통과합니다. 이는 contract enforcement로, 송신단의 burst를 평탄화하지는 않습니다.
- Egress Marking (qos-mark) — 송신 헤더에 결정된 DSCP/EXP 값을 기록합니다. 다음 hop이 이 마킹을 보고 자신의 큐로 분류하므로, 도메인 전체에서 DSCP 매핑 일관성이 핵심입니다.
- Egress Shaping — VPP 자체에는 본격적인 HTB/HFSC 같은 계층적 스케줄러가 없습니다. 대신 다음 두 가지로 shaping을 구현합니다:
- Output policer — 정확히 말하면 shaping이 아닌 policing이지만, 송신 인터페이스에 polica를 걸어 average rate를 제한할 수 있습니다. burst가 짧으면 충분합니다.
- NIC 하드웨어 큐 — DPDK/Mellanox PMD가 지원하는 NIC TX rate limit 또는 priority queue를 사용합니다.
show hardware-interfaces detail에서 지원 여부를 확인합니다. - 외부 스케줄러로 위임 — 컨테이너 환경에서는 VPP TX → 커널 tap → tc HTB로 넘기는 구조도 흔합니다. 단, tap을 거치면 zero-copy 이점은 사라집니다.
QoS 트러블슈팅
- polic가 적용되지 않음 —
show policer에서 카운터가 0인지 확인합니다.set policer classify의 classify table이 인터페이스에 바인딩되어 있는지(show classify table verbose)를 함께 봅니다. - green 트래픽이 모두 yellow로 마킹됨 — color-aware 모드에서 인입 DSCP가 이미 yellow로 인식되는 경우입니다.
show qos record로 입력 DSCP 매핑 테이블을 확인합니다. color-blind 모드로 전환하면 모든 입력을 green으로 가정합니다. - 마킹이 다음 hop에 보존되지 않음 — 캡슐화/디캡슐화 시 외부 헤더로 DSCP를 복사하는 옵션이 있습니다. VXLAN/IPsec은 기본적으로 inner DSCP를 outer로 복사하지 않으므로, 도메인 정책에 따라 명시적으로 설정해야 합니다.
- polic 카운터는 증가하는데 라인 sat에서 트래픽이 줄지 않음 — policer는 평균 rate를 강제하지만 burst를 부드럽게 하지 않습니다. 짧은 burst가 반복되면 평균만 만족하고 line은 saturate될 수 있습니다. 이때는 NIC 하드웨어 큐 또는 외부 shaper를 추가해야 합니다.
VXLAN / GENEVE 터널
VXLAN(Virtual Extensible LAN, RFC 7348)은 L2 프레임을 UDP 패킷으로 캡슐화하여 L3 네트워크 위에 가상의 L2 오버레이 네트워크를 구성하는 터널링 프로토콜입니다. 원본 이더넷 프레임 앞에 외부 이더넷 헤더(Outer Ethernet), 외부 IP 헤더(Outer IP), 외부 UDP 헤더(목적지 포트 4789), 그리고 8바이트 VXLAN 헤더가 추가됩니다. VXLAN 헤더에는 24비트 VNI(VXLAN Network Identifier)가 포함되어 최대 약 1,600만 개의 논리적 네트워크 세그먼트를 지원합니다. BUM(Broadcast, Unknown unicast, Multicast) 트래픽 처리에는 멀티캐스트 그룹을 사용한 복제 방식과 헤드엔드 유니캐스트 복제(Head-End Replication) 방식이 있으며, VPP는 두 가지 모두 지원합니다.
GENEVE(Generic Network Virtualization Encapsulation, RFC 8926)는 VXLAN의 후속 프로토콜로, 고정 헤더 뒤에 가변 길이 TLV(Type-Length-Value) 옵션 필드를 추가할 수 있습니다. 이를 통해 OAM(Operations, Administration, Maintenance) 정보, 보안 태그, 텔레메트리 메타데이터 등을 터널 헤더에 직접 포함할 수 있어 확장성이 크게 향상됩니다. UDP 목적지 포트는 6081을 사용하며, VXLAN과 동일하게 24비트 VNI를 지원합니다.
VPP의 패킷 그래프에서 VXLAN 디캡슐화는 ip4-input → ip4-lookup → ip4-local → udp-local → vxlan4-input 경로를 거치며,
vxlan4-input 노드에서 VNI를 기반으로 해당 터널 인터페이스를 식별하고 내부 프레임을 추출합니다.
캡슐화 경로는 l2-output → vxlan4-encap → ip4-rewrite → interface-output 순서로 진행됩니다.
최신 NIC(Intel X710, Mellanox ConnectX-5 이상 등)은 VTEP(VXLAN Tunnel End Point) 오프로드를 지원하여 외부 헤더의 추가·제거를 하드웨어에서 처리함으로써 CPU 부담을 줄이고 처리량을 높일 수 있습니다.
# 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/GENEVE 트러블슈팅
VXLAN/GENEVE 터널은 정상 구성 후에도 캡슐화 오버헤드, MAC 학습, BUM(Broadcast/Unknown-unicast/Multicast) 처리 때문에 여러 함정이 있습니다. 증상별 진단 흐름을 정리합니다.
- MTU 부족 — VXLAN은 50바이트(GENEVE는 가변, 보통 50~74) 오버헤드를 추가합니다. 언더레이 MTU가 1500이면 페이로드 MTU는 1450 이하여야 합니다. 게스트가 1500 그대로 보내면 외부 IP 단편화가 발생하거나 DF 비트가 켜진 패킷이 드롭됩니다.
ip4-input fragmentation needed또는vxlan4-encap drop카운터가 증가합니다. - 해결 — 언더레이를 점보프레임(1600+ 권장, 9000 이상 이상적)으로 올리거나, 게스트 MTU를 1450으로 강제.
show interface로 양단 MTU 일치 확인.
- MAC 학습 실패 — VXLAN을 브릿지 도메인에 넣을 때
learn옵션이 꺼져 있으면 원격 MAC을 학습하지 않습니다.show bridge-domain <id> detail에서 learn 플래그를 확인합니다. - 해결 —
set bridge-domain learn <bd> 1로 활성화. EVPN 컨트롤 평면을 쓰는 경우는 학습을 끄고 정적 MAC을 주입합니다. show l2fib bd_id <bd>로 학습된 MAC 테이블 직접 확인.
- flood 모드 미설정 — VXLAN BUM은 두 가지로 처리합니다: head-end replication(VTEP가 모든 원격에 유니캐스트 복제) 또는 multicast underlay(언더레이 멀티캐스트 그룹 사용). VPP는 기본적으로 head-end replication이며,
create vxlan tunnel에 원격을 명시적으로 등록해야 합니다. - 증상 — ARP/DHCP 같은 브로드캐스트가 일부 VTEP에만 도달하거나, 멀티캐스트 그룹이 동기화되지 않아 BUM이 모두 드롭됩니다.
- 해결 — 모든 원격 VTEP을 같은 BD에 명시적으로 등록(
create vxlan tunnel ... decap-next l2)하거나, multicast underlay 모드로 전환합니다.
- UDP 포트/VNI 미매칭 —
vxlan4-input노드가 패킷을 인식하려면 dst port 4789(GENEVE는 6081) + 등록된 VNI가 일치해야 합니다. 한쪽이 다른 VNI를 쓰면 디캡 노드가 무시하고ip4-localdrop으로 진행합니다. - 진단 —
show trace로 vxlan4-input 노드에 도달했는지 확인. 도달했지만 드롭이면show vxlan tunnel의 VNI 목록과 비교. - NIC VTEP 오프로드와의 충돌 — VTEP 오프로드 활성화 시 일부 NIC은 디캡을 NIC에서 수행합니다. VPP가 디캡 패킷을 또 디캡하려고 시도하면 실패합니다.
show hardware-interfaces detail로 오프로드 상태를 확인하고 필요하면 비활성화합니다.
VXLAN-GPE · GTP-U · PPPoE — 전송 오버레이
VXLAN/GENEVE 외에도 VPP는 서비스별 특화 터널 프로토콜을 다수 지원합니다. 각각 다른 제어 평면 / 도메인에서 쓰이지만 VPP 안에서는 모두 그래프 노드 한 쌍(input/output)으로 구현됩니다.
VXLAN-GPE — Generic Protocol Extension
기본 VXLAN(RFC 7348)은 이더넷 프레임만 운반할 수 있습니다. VXLAN-GPE는 헤더에 next-protocol 필드를 추가해 IPv4/IPv6/이더넷/NSH 같은 다양한 페이로드를 식별할 수 있게 확장합니다. 서비스 체인 헤더(NSH)를 운반하는 주된 transport 중 하나입니다.
vpp# create vxlan-gpe tunnel local 10.0.0.1 remote 10.0.0.2 vni 100 next-protocol ip4 encap-vrf-id 0
vpp# set interface state vxlan_gpe_tunnel0 up
vpp# set interface ip address vxlan_gpe_tunnel0 192.168.100.1/24
vpp# show vxlan-gpe tunnel
GTP-U — GPRS Tunneling Protocol User Plane
GTP-U(3GPP TS 29.281)는 4G/5G 모바일 코어의 사용자 평면 터널입니다. 단말의 IP 패킷을 TEID(Tunnel Endpoint Identifier)로 식별해 UDP 2152 포트로 운반합니다. VPP의 gtpu 플러그인은 UPF(User Plane Function) 구현의 기반이 됩니다.
vpp# create gtpu tunnel src 10.0.0.1 dst 10.0.0.2 teid 1000 encap-vrf-id 0
vpp# set interface ip address gtpu_tunnel0 172.16.0.1/24
vpp# show gtpu tunnel
5G UPF 실전 구성에서는 활용 사례 — 5G UPF와 함께 PFCP 플러그인(N4 인터페이스)이 필요합니다.
PPPoE — 가입자망 터널
PPPoE(RFC 2516)는 ADSL·VDSL·GPON 가입자망에서 이더넷 위에 PPP 세션을 만드는 프로토콜입니다. VPP의 pppoe 플러그인은 BRAS(Broadband Remote Access Server) 역할로 수천 개 가입자 세션을 종단할 수 있습니다.
vpp# create pppoe session client-ip 10.0.0.2 session-id 100 client-mac aa:bb:cc:dd:ee:ff decap-vrf-id 0
vpp# show pppoe session
NSH — Network Service Header
NSH(RFC 8300)는 서비스 체이닝의 메타데이터 캐리어입니다. 트래픽이 방화벽·IDS·WAF 같은 서비스 노드를 거쳐야 할 때, NSH 헤더가 경로(Service Path)와 현재 단계(Service Index)를 기록합니다. VPP는 nsh 플러그인으로 인코딩/디코딩 노드를 제공하며, VXLAN-GPE나 SRv6 위에 실어 전달합니다.
실전 시나리오 맵
VPP의 대표적인 실전 시나리오 5종을 한눈에 비교합니다. 각 시나리오는 startup.conf 설정부터 인터페이스 구성, 데이터 경로 분석, 성능 확인까지 엔드-투-엔드 절차를 제공하며, 상세 구성은 아래 표의 "상세 위치" 열에서 해당 Part로 이동하여 확인할 수 있습니다.
| 시나리오 | 주요 기능 | 인터페이스 | 적합한 환경 | 상세 위치 |
|---|---|---|---|---|
| L3 라우터 + NAT44 | IP 포워딩, 주소 변환(Address Translation) | DPDK PMD | 엣지 라우터, 게이트웨이 | 본 문서 |
| memif 서비스 체이닝 | 제로카피 패킷(Packet) 전달, SFC | memif (공유 메모리) | NFV, 다단계 패킷 처리 | 본 문서 |
| IPsec VPN | 터널(Tunnel) 암호화(Encryption), IKEv2 | DPDK + ipsec | 사이트 간 VPN, 원격 접속 | 보안과 터널링 |
| SSL/TLS Inspection | TLS 종단·복호화(Decryption)·재암호화 | VCL + TLS 플러그인 | NGFW, 보안 게이트웨이, DLP | TCP/TLS 프록시 · SSL Inspection |
| TPROXY (투명 프록시) | 원본 IP 보존 투명 프록시 | VCL + session layer / memif | L7 보안 게이트웨이, CDN, 서비스 메시 | 보안과 터널링 |
isolcpus), DPDK 호환 NIC를 가정합니다. 설치 및 설정을 참고하여 기본 환경을 먼저 구성하세요.
실전 예제: 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 라우팅(Routing) + 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 노드 호출이 꾸준히 증가합니다. | 이미 생성된 세션을 재사용하고 있음을 뜻합니다. 처리량(Throughput)이 올라가도 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 양방향 키가 동시에 해시 테이블(Hash Table)에 삽입됩니다. 이후 패킷은 키 조회 한 번으로 변환 정보를 가져오므로, 세션 수가 늘어나도 조회 시간은 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 해시(Hash)로, 버킷 수가 2의 거듭제곱이며 충돌 시 체인이 아닌 페이지(Page) 단위 확장을 합니다. 세션 수가 수백만에 도달해도 캐시(Cache) 미스율이 낮아 일정한 조회 성능을 유지합니다.
NAT44 ED BIHash 충돌 처리와 성능 영향
BIHash는 "bounded-index"라는 이름처럼, 하나의 버킷이 들고 있을 수 있는 KV 페어 수에 상한이 있는 해시입니다. 일반 체이닝 해시맵과 달리 버킷 자체가 (log2_pages, offset) 디스크립터이며 실제 페어는 별도 페이지 풀에 저장됩니다. 충돌이 발생하면 clib_bihash_add_del_16_8가 해당 버킷의 페이지 수를 2배로 키우고(log2_pages++) 페어들을 새 페이지 슬롯에 재분산합니다. 이 구조는 평균 조회는 빠르지만, 충돌이 집중될 때 특정 버킷의 재할당 비용이 튀는 꼬리 지연(tail latency)을 만들어냅니다.
NAT44 ED에서 충돌이 문제되는 시나리오는 세 가지입니다. ① DDoS SYN flood: 공격자가 동일 dst/sport를 유지하며 src IP를 무작위화하면, 일부 버킷이 극단적으로 편중됩니다. ② 대형 CDN 클라이언트: 수만 개 클라이언트가 동일 VIP로 접속하면 (any, vip, *, 443) 조합이 동일 버킷에 몰립니다. ③ 해시 시드 고정: 기본 crc32c 해시는 시드를 바꾸지 않으면 공격자가 충돌 키를 예측해 의도적으로 특정 버킷을 폭파시킬 수 있습니다.
/* BIHash 버킷 충돌과 페이지 확장 — 의사 코드 */
int clib_bihash_add_del_16_8 (clib_bihash_16_8_t *h,
clib_bihash_kv_16_8_t *kv, int is_add)
{
u32 hash = crc32c_u64 (kv->key); /* 해시 계산 */
u32 bucket_idx = hash & (h->nbuckets - 1);
clib_bihash_bucket_t *b = &h->buckets[bucket_idx];
clib_spinlock_lock (&b->lock); /* 버킷 단위 잠금 */
if (b->n_entries >= b->page_size) { /* 페이지 가득 참 */
/* → 페이지 수 2배로 확장 (log2_pages++) */
if (b->log2_pages >= BIHASH_MAX_LOG2_PAGES) {
clib_spinlock_unlock (&b->lock);
return -1; /* 포기 — 세션 생성 실패 */
}
bihash_split_page (h, b); /* O(page_size) 복사 */
}
bihash_insert_into_page (h, b, kv);
clib_spinlock_unlock (&b->lock);
return 0;
}
위 의사 코드에서 주목할 점은 두 가지입니다. ① 버킷 단위 spinlock은 일반적으로 경합이 적지만, 충돌이 집중된 핫 버킷에서는 경합(Contention)으로 실질적으로 직렬화(Serialization)됩니다. ② 페이지 분할(split_page)은 드문 이벤트지만 발생 시 수백 ns 단위의 지연이 튀고, 동시에 다른 워커가 동일 버킷을 만지면 긴 대기가 발생합니다.
| 시나리오 | 평균 세션 생성 지연 | p99.9 | 실효 CPS | 대응 |
|---|---|---|---|---|
| 균등 트래픽 | 180 ns | 450 ns | 5.5 M CPS | — |
| SYN flood (단일 핫 버킷) | 220 ns | 18 μs | 1.2 M CPS | 해시 시드 랜덤화 |
| 대형 CDN (VIP 편중) | 190 ns | 2.4 μs | 3.8 M CPS | dst-hashing 활성화 |
| 시드 랜덤화 + dst-hashing | 175 ns | 520 ns | 5.3 M CPS | — |
# 충돌 진단 — BIHash 통계 덤프
vpp# show bihash nat44-ed-sessions verbose
Heap: base 0x7f.. size 1024M free 612M
Bucket util: avg_entries_per_bucket 3.2 max 41 ← 최대값이 크면 편중
Splits: 1204 (0.3% of inserts)
Page depth distribution:
1-page buckets: 65530
2-page buckets: 6
3-page buckets: 0
# 대응 1: 해시 시드 재설정 (충돌 공격 완화)
vpp# set nat44 hash-seed random
# 대응 2: 버킷 수 증가 (충돌 확률 ↓, 메모리 ↑)
$ cat /etc/vpp/startup.conf
nat {
translation hash buckets 524288 # 기본 65536 → 8배
translation hash memory 2147483648 # 2GB
}
# 대응 3: 워커별 독립 테이블 — 버킷 공유 자체 제거
nat {
max-translations-per-thread 524288
}
진단 팁: show bihash ... verbose의 max_entries_per_bucket이 버킷 상한(기본 4~8)을 자주 초과하면 편중이 심한 것입니다. show errors의 nat44-ed-in2out-slowpath bihash add failed가 증가하면 페이지 분할도 실패한 상황이므로, 버킷 수 증가와 시드 랜덤화를 동시에 적용해야 합니다.
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 앱 간 제로카피 패킷 전달을 제공합니다. 이를 활용하면 여러 네트워크 기능(방화벽(Firewall), DPI, NAT 등)을 체이닝하여 서비스 펑션 체인(SFC)을 구성할 수 있습니다.
memif 소켓(Socket) 및 인터페이스 구성
# 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 링 동작과 병목(Bottleneck) 확인 방법
memif는 단순한 "가상 케이블"이 아니라, 두 프로세스가 공유 메모리 위에서 서술자 링(descriptor ring)을 교환하는 구조입니다. 성능 병목이 생기면 대부분 패킷 처리 로직보다 링 고갈, peer 지연(Latency), 큐 배치 불일치에서 먼저 징후가 나타납니다.
# 보다 구체적인 생성 예시
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과 달리 패킷 헤더의 임의 오프셋(Offset)을 마스크 매칭할 수 있어 유연한 분류가 가능합니다.
# 분류 테이블 생성: 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 ↔ 커널 네임스페이스(Namespace) |
| 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을 고려하세요.