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만 보고 다음 노드를 결정합니다.

FIB 엔트리는 결국 DPO를 가리킵니다. 10.0.0.0/24 → 192.168.1.1 via eth0 같은 흔한 경로는 내부적으로 DPO_LOAD_BALANCEDPO_ADJACENCY 체인으로 표현됩니다.

vpp# show ip fib summary
vpp# show fib paths
vpp# show adj
vpp# show adj detail

Adjacency 종류

Adjacency는 "L2 다음 홉이 준비되었다"를 나타내는 객체입니다. 세 가지 상태가 있습니다.

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" 체인이 보임
운영 관점: FIB의 재귀 해석 깊이가 너무 깊어지면 패킷당 DPO 조회 횟수가 늘어 성능이 떨어집니다. 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);
Peer Down 처리: 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