Data-Plane Objects · Adjacency · Tunnel Infra
VPP Data-Plane Objects(DPO), Adjacency, Tunnel Infra의 참조 구조와 포워딩 체인 구성 방식을 정리합니다.
VPP의 FIB·터널·로드 밸런싱은 모두 Data-Plane Object (DPO)라는 공통 추상화를 공유합니다. 이 페이지는 FIB 상세를 이해하려는 독자를 위해 DPO 모델과 Adjacency·Tunnel Infra의 관계를 정리합니다.
DPO — 데이터 평면 객체 모델
DPO는 "패킷이 이 객체에 도달했을 때 다음에 무엇을 할지"를 캡슐화한 객체입니다. 모든 DPO는 공통 헤더 dpo_id_t (type + index)를 가지며, 데이터 평면에서는 이 id만 보고 다음 노드를 결정합니다.
DPO_ADJACENCY— L3 이웃 정보 (MAC 주소 재작성 + 출력 인터페이스)DPO_LOAD_BALANCE— ECMP·UCMP 분산 벡터DPO_MPLS_LABEL— MPLS 레이블 푸시DPO_INTERFACE_RX— 입력 인터페이스 지정DPO_REPLICATE— 멀티캐스트 복제 벡터DPO_DROP / PUNT— 드랍, 펀트DPO_LOOKUP— 다른 FIB 테이블 재조회
FIB 엔트리는 결국 DPO를 가리킵니다. 10.0.0.0/24 → 192.168.1.1 via eth0 같은 흔한 경로는 내부적으로 DPO_LOAD_BALANCE → DPO_ADJACENCY 체인으로 표현됩니다.
vpp# show ip fib summary
vpp# show fib paths
vpp# show adj
vpp# show adj detail
Adjacency 종류
Adjacency는 "L2 다음 홉이 준비되었다"를 나타내는 객체입니다. 세 가지 상태가 있습니다.
- Complete — ARP/ND로 L2 주소가 해석 완료. MAC rewrite string이 채워짐. 고속 경로.
- Incomplete (glean) — L2 주소 미해석. 패킷을 펀트해 ARP를 트리거.
- Midchain — 터널(IPsec·VXLAN·GRE 등)의 "내부 측" 인접성. 패킷이 실제 next hop으로 가기 전에 터널 캡슐화 노드를 거침.
Tunnel Infra — 공통 터널 추상화
VPP의 터널(IPsec·VXLAN·GRE·GTP-U·IP-in-IP·WireGuard)은 서로 다른 프로토콜이지만 내부적으로 같은 tunnel_t 인프라를 공유합니다. 터널 인터페이스는 midchain adjacency를 만들고, 송신 패킷은 먼저 midchain을 거쳐 캡슐화된 뒤 실제 output 인터페이스로 나갑니다.
/* src/vnet/tunnel/tunnel.h — 공통 구조 */
typedef struct {
ip_address_t t_src;
ip_address_t t_dst;
tunnel_mode_t t_mode; /* P2P · MP */
tunnel_encap_decap_flags_t t_encap_decap_flags;
ip_dscp_t t_dscp;
u8 t_hop_limit;
u32 t_table_id; /* underlay VRF */
u32 t_flags;
} tunnel_t;
이 공통 인프라 덕분에 새 터널 프로토콜을 추가할 때 인접성·FIB 재수렴·MTU 계산·DSCP 복사 등 공통 로직을 재사용할 수 있습니다. IPsec과 VXLAN이 완전히 다른 프로토콜인데도 MTU 조정과 fragmentation 처리가 일관되게 동작하는 이유입니다.
FIB Recursive Resolution
BGP 학습 경로는 종종 재귀적 next-hop을 가집니다. 예를 들어 10.0.0.0/24 → 203.0.113.1인데 203.0.113.1은 또 다른 프리픽스로 해석되어야 합니다. VPP의 FIB는 이런 재귀를 DPO 체인으로 표현해, 하위 경로가 바뀌면 상위 경로 수만 개도 자동으로 갱신됩니다.
vpp# ip route add 10.0.0.0/24 via 203.0.113.1
vpp# show ip fib 10.0.0.0/24
# 출력에 "recursive-resolution-via" 체인이 보임
show ip fib summary의 "recursive-depth" 통계를 주시하고, 평균 깊이가 3을 넘으면 경로 설계를 재검토해야 합니다.
DPO 타입 등록과 조작 코드
플러그인이 새로운 DPO 타입을 추가하려면 dpo_vft_t 가상 함수 테이블을 구현하고 dpo_register()로 등록합니다. 이후 FIB 스택에 dpo_stack()으로 연결하면 포워딩 체인에 삽입됩니다.
/* 1. VFT 구현 */
static void
my_dpo_lock (dpo_id_t *dpo) { /* 참조 카운트 증가 */ }
static void
my_dpo_unlock (dpo_id_t *dpo) { /* 참조 카운트 감소 → 0이면 해제 */ }
static u8 *
my_dpo_format (u8 *s, va_list *args)
{
return format(s, "my-dpo:[%u]", va_arg(*args, u32));
}
const static dpo_vft_t my_dpo_vft = {
.dv_lock = my_dpo_lock,
.dv_unlock = my_dpo_unlock,
.dv_format = my_dpo_format,
};
/* 2. 노드 등록 시 DPO 타입 할당 */
static dpo_type_t my_dpo_type;
static clib_error_t *
my_plugin_init (vlib_main_t *vm)
{
const static char *my_dpo_nodes[] = { "my-dpo-node", 0 };
my_dpo_type = dpo_register_new_type(&my_dpo_vft, my_dpo_nodes);
return 0;
}
/* 3. 포워딩 체인에 삽입 */
dpo_id_t parent = DPO_INVALID;
dpo_set(&parent, my_dpo_type, DPO_PROTO_IP4, my_index);
fib_entry_contribute_forwarding(fib_entry_index, fib_forw_chain_type, &parent);
dpo_reset(&parent); /* 참조 해제 */
Midchain Adjacency — 캡슐화 흐름
터널 송신 경로에서 패킷은 Midchain adjacency를 만나 다음과 같은 순서로 처리됩니다.
ip4-rewrite
├─ complete adj → 재작성 후 인터페이스 output 으로 직행
└─ midchain adj → 재작성 후 터널 캡슐화 노드로 디스패치
└─ (예) ipsec-output, vxlan-encap, gre-encap
└─ 실제 underlay 인터페이스 output
/* Midchain adjacency 스택 — 터널 플러그인 init에서 호출 */
adj_index_t ai = adj_midchain_add_or_lock(
FIB_PROTOCOL_IP4, /* outer L3 프로토콜 */
VNET_LINK_IP4, /* link type */
&nh_addr, /* underlay next-hop */
tunnel_sw_if_index /* 터널 인터페이스 */
);
/* 캡슐화 rewrite string 설정 (예: GRE 헤더) */
adj_nbr_midchain_update_rewrite(ai, encap_node_index,
rewrite_data, rewrite_len,
ADJ_FLAG_NONE);
/* underlay FIB에 스택 → peer down 시 자동 unresolve */
adj_midchain_stack_on_fib_entry(ai, fib_entry_index, fib_forw_chain_type);
adj_midchain_stack_on_fib_entry()로 연결된 midchain은 underlay 경로가 사라지면 자동으로 unresolved 상태가 됩니다. 재수렴 후에는 같은 API를 다시 호출할 필요 없이 FIB가 자동으로 재스택합니다.
CLI로 DPO 체인 확인하기
# 특정 프리픽스의 포워딩 체인 전체 조회
vpp# show ip fib 10.0.0.0/24
10.0.0.0/24 fib:0 index:12 locks:2
path-list:[15] locks:2
path:[20] pl-index:15 ip4 weight=1 pref=0 attached-nexthop: oper-flags:resolved,
10.0.0.1 GigabitEthernet0/8/0
forwarding: ip4 via 10.0.0.1 GigabitEthernet0/8/0
[@0]: ipv4 via 10.0.0.1 GigabitEthernet0/8/0: mtu:9000 next:6
IP4: aa:bb:cc:dd:ee:01 -> aa:bb:cc:dd:ee:02
IP4 src:0.0.0.0 dst:0.0.0.0 len:0 ttl:0 prot:0
# Adjacency 목록 — index·타입·재작성 문자열
vpp# show adj
[ 6] ipv4 via 10.0.0.1 GigabitEthernet0/8/0:
mtu:9000 flags:[] aa:bb:cc:dd:ee:01 -> aa:bb:cc:dd:ee:02
# load-balance DPO → ECMP 버킷 확인
vpp# show ip fib 0.0.0.0/0
...
load-balance:[3] buckets:2
[0] [@4] ipv4 via 192.168.1.1 (weight 1)
[1] [@5] ipv4 via 192.168.1.2 (weight 1)
# 재귀 경로 깊이 통계
vpp# show ip fib summary
IPv4 FIB entries: 42 paths: 38 recursive-depth-0: 36 recursive-depth-1: 6