QUIC · HTTP/3

FD.io VPP의 QUIC 프로토콜 구현과 HTTP/3 실전 운용을 심화 정리한 문서입니다. quicly 통합, QUIC engine API, ALPN 협상, 0-RTT 재개, VPP 25.10 제약사항까지를 한 곳에 모았으며, 본 문서는 호스트 스택 개요에서 QUIC/HTTP3 부분만을 별도로 분리한 심화 페이지(Page)입니다.

이 페이지에서 다루는 주요 h2 섹션은 다음과 같습니다. 첫째 VPP QUIC 프로토콜은 quicly 라이브러리 기반 통합 구조, 25.06 이후 도입된 QUIC engine API, 스트림·패킷(Packet) 처리 흐름, 운영 디버깅(Debugging) 팁을 다룹니다. 둘째 HTTP/3 실전 — ALPN 협상, 0-RTT, VPP 25.10 제약은 h2/h3 ALPN 협상 전략, 0-RTT 재개와 재전송 방어, 현재 VPP HTTP/3 어댑터의 실전 제약과 운영 고려사항을 정리합니다.

기초 개념: 세션 레이어, TCP 호스트 스택, TLS 기본 흐름은 호스트 스택 개요에서 먼저 확인해 주시기 바랍니다. TLS 심화 내용은 TLS를 함께 참고하시면 됩니다.

VPP QUIC 프로토콜

VPP는 quicly 라이브러리(H2O 프로젝트)를 기반으로 QUIC 전송 프로토콜을 네이티브 지원합니다. QUIC은 UDP 위에 TLS 1.3을 내장한 다중화(Multiplexing) 전송 프로토콜로, VPP의 유저스페이스 스택과 결합하면 커널 경유 없이 고성능 암호화(Encryption) 통신이 가능합니다.

🔄 25.02 → 26.02 변경 타임라인:
  • 25.06QUIC engine API 공식 출시. 전에는 quicly 직결 구조였지만 플러그형 엔진 인터페이스가 생기면서 다른 QUIC 구현도 끼울 수 있게 됐습니다.
  • 25.10 — HTTP/2 쪽에서 extended CONNECTUDP 터널링 over HTTP/2가 들어왔습니다. HTTP/3 중계 경로가 정비되기 시작한 시점입니다.
  • 26.02HTTP/3 framing layer + core skeleton + H3 client sideQPACK 인코딩/디코딩이 릴리스 노트에 공식 항목으로 등장했습니다. 본 절의 기존 "HTTP/3 over VPP QUIC" 내용은 이 시점에 "실험 → 사용 가능한 파일럿" 상태로 올라왔습니다.
25.02 환경에서 이 절의 HTTP/3 관련 예시(클라이언트 API, QPACK 흐름 등)를 그대로 적용하실 수 없습니다. QUIC 종단만 가능하고 HTTP/3 애플리케이션 레이어는 자체 구현이 필요합니다.

QUIC 전송 아키텍처

VPP의 QUIC 구현(src/plugins/quic/)은 세션 레이어의 전송 프로토콜로 등록되며, 단일 QUIC 연결 위에 여러 스트림을 다중화합니다:

/* QUIC 전송 프로토콜 등록 */
transport_register_protocol (TRANSPORT_PROTO_QUIC,
                             &quic_proto, FIB_PROTOCOL_IP4, ~0);

/* QUIC 세션 계층 구조 */
/*
 * Application Stream ──→ QUIC Stream (session)
 *                            │
 *      여러 스트림 ──────→ QUIC Connection (session)
 *                            │
 *                    ──→ UDP Transport (session)
 */

typedef struct quic_ctx_
{
  union {
    transport_connection_t connection;
  };
  quicly_conn_t *conn;          /* quicly 연결 객체 */
  u32 listener_ctx_id;
  u32 udp_session_handle;       /* 하부 UDP 세션 */
  u8 conn_state;                /* 연결 상태 머신 */
  u8 udp_is_ip4;
  /* TLS 1.3은 quicly 내부에서 처리 */
} quic_ctx_t;
VPP QUIC 세션 계층 구조 애플리케이션 (VCL / builtin) Stream 0 (bidi) Stream 4 (bidi) Stream 2 (uni) QUIC Connection (quicly) TLS 1.3 내장 · 연결 마이그레이션 · 0-RTT UDP 전송 (TRANSPORT_PROTO_UDP) VPP 네트워크 스택 (ip4/ip6 → DPDK/AF_XDP) 다중화 암호화 전송

QUIC 내부 패킷 처리 흐름

VPP의 QUIC 플러그인은 quicly 라이브러리를 핵심 엔진으로 사용합니다. quicly는 QUIC 프로토콜의 패킷 파싱, 암호화(TLS 1.3 via picotls), 흐름 제어(Flow Control), 혼잡 제어(Congestion Control)를 모두 처리하는 C 라이브러리입니다. VPP는 quicly를 그래프 노드 기반 파이프라인(Pipeline)에 통합하여 고성능 QUIC 처리를 구현합니다.

QUIC 패킷 수신 경로

QUIC 패킷이 네트워크에서 도착하여 애플리케이션 데이터로 전달되기까지의 경로는 다음과 같습니다:

/* QUIC 수신 경로: UDP input → quicly → 애플리케이션 */

/* 1. VPP UDP 입력 노드가 QUIC 포트(443) 패킷을 QUIC 앱에 전달 */
/*    udp-input → session-queue → quic_app_rx_callback() 호출 */

static int
quic_app_rx_callback (session_t *udp_session)
{
  quic_ctx_t *ctx;
  svm_fifo_t *rx_fifo;
  u8 *data;
  u32 data_len;
  int rv;

  /* UDP RX FIFO에서 QUIC 패킷 읽기 */
  rx_fifo = udp_session->rx_fifo;
  data_len = svm_fifo_max_dequeue (rx_fifo);
  vec_validate (data, data_len - 1);
  svm_fifo_dequeue (rx_fifo, data_len, data);

  /* Connection ID로 QUIC 컨텍스트 검색 */
  ctx = quic_find_ctx_by_conn_id (data);

  /* 2. quicly_receive()로 패킷 처리 (복호화 + 프레임 파싱) */
  rv = quicly_receive (ctx->conn, NULL,
                       &udp_session->transport.rmt_ip,
                       data, data_len);

  if (rv != 0)
    {
      /* 패킷 처리 오류: 잘못된 패킷 또는 복호화 실패 */
      quic_proto_error (ctx, rv);
      return -1;
    }

  /* 3. 스트림 데이터가 있으면 앱 세션의 RX FIFO에 인큐 */
  quic_check_streams_for_data (ctx);

  /* 4. ACK 등 응답 패킷이 필요하면 송신 스케줄링 */
  quic_send_packets (ctx);

  return 0;
}
코드 분석: QUIC 패킷 수신 경로

quic_app_rx_callback()은 VPP 세션 레이어가 UDP 패킷을 수신할 때 호출되는 콜백(Callback)입니다. 핵심 동작은 quicly_receive() 호출입니다. quicly는 패킷 헤더를 파싱하여 Connection ID를 추출하고, AEAD(Authenticated Encryption with Associated Data)로 페이로드(Payload)를 복호화(Decryption)한 후, 내부 프레임(STREAM, ACK, MAX_DATA 등)을 처리합니다. STREAM 프레임에 포함된 애플리케이션 데이터는 해당 스트림의 수신 버퍼(Buffer)에 저장되며, 이후 quic_check_streams_for_data()가 이를 앱 세션의 RX FIFO에 복사합니다.

QUIC 패킷 송신 경로

애플리케이션 데이터가 QUIC 패킷으로 변환되어 네트워크로 전송되는 경로입니다:

/* QUIC 송신 경로: 앱 데이터 → quicly → UDP output */
static int
quic_send_packets (quic_ctx_t *ctx)
{
  quicly_address_t dest, src;
  struct iovec packets[QUIC_MAX_PACKET_BATCH];
  size_t num_packets = QUIC_MAX_PACKET_BATCH;
  int rv;

  /* 1. quicly_send()로 전송할 패킷 생성 (암호화 포함) */
  rv = quicly_send (ctx->conn, &dest, &src,
                    packets, &num_packets,
                    ctx->send_buf, sizeof(ctx->send_buf));

  if (rv != 0 || num_packets == 0)
    return 0;

  /* 2. 생성된 패킷을 UDP TX FIFO에 인큐 */
  for (size_t i = 0; i < num_packets; i++)
    {
      svm_fifo_enqueue (ctx->udp_session->tx_fifo,
                        packets[i].iov_len,
                        packets[i].iov_base);
    }

  /* 3. UDP 세션에 TX 이벤트 발생 → VPP가 패킷 전송 */
  session_send_io_evt_to_thread (ctx->udp_session->tx_fifo,
                                 SESSION_IO_EVT_TX);
  return num_packets;
}
코드 분석: QUIC 패킷 송신 경로

quic_send_packets()quicly_send()를 호출하여 전송 대기 중인 모든 데이터(스트림 데이터, ACK, 흐름 제어 프레임 등)를 QUIC 패킷으로 만듭니다. quicly는 내부적으로 혼잡 윈도우를 확인하고, 패킷 번호를 부여하며, AEAD로 암호화합니다. 생성된 패킷은 struct iovec 배열로 반환되며, VPP는 이를 UDP TX FIFO에 일괄 인큐(Enqueue)합니다. 배치 처리(QUIC_MAX_PACKET_BATCH)를 통해 시스템 콜(System Call) 오버헤드를 최소화합니다.

VPP QUIC 패킷 처리 파이프라인 수신 경로 (RX) DPDK/AF_XDP ip4-input udp-input session-queue quic_app_rx_callback quicly_receive() App RX FIFO 송신 경로 (TX) App TX FIFO quic_send_packets quicly_send() UDP TX FIFO udp-output ip4-output DPDK TX quicly 내부 처리 상세 패킷 파싱 헤더 · CID 추출 AEAD 복호화 AES-128-GCM · ChaCha20 프레임 처리 STREAM · ACK · FC 스트림 버퍼 순서 재조립 · 전달 FC = Flow Control (MAX_DATA, MAX_STREAM_DATA) CID = Connection ID | AEAD = Authenticated Encryption with Associated Data

QUIC 세션 API

QUIC은 연결(Connection)스트림(Stream)의 2계층 세션 모델을 사용합니다. VCL에서의 QUIC 사용:

/* QUIC 서버 — 연결 수락 + 스트림 수락 */
/* 1. QUIC 리스너 생성 */
int listener = vppcom_session_create (VPPCOM_PROTO_QUIC, 0);
vppcom_session_bind (listener, &addr);
vppcom_session_listen (listener, 10);

/* 2. QUIC 연결 수락 (connection-level) */
int quic_conn = vppcom_session_accept (listener, &client_ep, 0);

/* 3. 스트림 수락 (stream-level, 실제 데이터 교환) */
int stream = vppcom_session_accept (quic_conn, &stream_ep, 0);

/* 4. 스트림에서 데이터 송수신 */
vppcom_session_read (stream, buf, sizeof(buf));
vppcom_session_write (stream, response, resp_len);

/* QUIC 클라이언트 — 연결 + 스트림 생성 */
int quic_conn = vppcom_session_create (VPPCOM_PROTO_QUIC, 0);
vppcom_session_connect (quic_conn, &server_ep);

/* 동일 연결에 여러 스트림 생성 */
int stream1 = vppcom_session_create (VPPCOM_PROTO_QUIC, 0);
vppcom_session_stream_connect (stream1, quic_conn);

QUIC 스트림 생명주기 소스 분석

QUIC 스트림은 QUIC 연결 내부에서 독립적인 데이터 채널 역할을 합니다. VPP에서 스트림의 생성부터 종료까지의 전체 생명주기를 소스 코드 수준에서 분석합니다.

스트림 생성 (quic_connect_stream)

클라이언트가 새 스트림을 열 때 quic_connect_stream()이 호출됩니다. 이 함수는 quicly 연결 위에 새 스트림 객체를 생성하고 VPP 세션과 연결합니다:

/* QUIC 스트림 연결 (클라이언트 → 서버) */
static int
quic_connect_stream (session_t *quic_session,
                      session_endpoint_cfg_t *sep)
{
  quic_ctx_t *qctx, *sctx;
  quicly_stream_t *stream;
  session_t *stream_session;
  int rv;

  /* 상위 QUIC 연결 컨텍스트 조회 */
  qctx = quic_ctx_get (quic_session->connection_index);

  /* 1. quicly에 양방향 스트림 생성 요청 */
  rv = quicly_open_stream (qctx->conn, &stream, 0 /* bidi */);
  if (rv)
    {
      /* MAX_STREAMS 한도 초과 시 실패 */
      return SESSION_E_REFUSED;
    }

  /* 2. VPP 스트림 컨텍스트 할당 */
  sctx = quic_ctx_alloc (qctx->c_thread_index);
  sctx->parent_ctx_id = qctx->ctx_id;
  sctx->quicly_stream = stream;
  sctx->conn_state = QUIC_CONN_STATE_STREAM_OPEN;

  /* 3. VPP 세션 생성 및 FIFO 할당 */
  stream_session = session_alloc (qctx->c_thread_index);
  stream_session->session_type = SESSION_TYPE_QUIC_STREAM;
  svm_fifo_alloc_pair (&stream_session->rx_fifo,
                       &stream_session->tx_fifo,
                       QUIC_FIFO_SIZE);

  /* 4. quicly 스트림 콜백 등록 (데이터 수신 시 호출) */
  stream->callbacks = &quic_stream_callbacks;
  stream->data = sctx;  /* VPP 컨텍스트 연결 */

  /* 5. 애플리케이션에 연결 완료 알림 */
  app_worker_connect_notify (stream_session);

  return 0;
}
코드 분석: 스트림 생성 흐름

quic_connect_stream()의 핵심은 quicly 레벨 스트림과 VPP 세션 레벨 스트림을 양방향으로 연결하는 것입니다. quicly_open_stream()은 QUIC 프로토콜의 스트림 ID를 할당하고(클라이언트 시작 양방향 = 0, 4, 8, ...), MAX_STREAMS 한도를 확인합니다. 이후 VPP 세션을 할당하고 RX/TX FIFO 쌍을 만들어 애플리케이션과의 데이터 교환 통로를 설정합니다. stream->callbacks에 등록된 콜백은 quicly가 해당 스트림에 데이터를 수신할 때 자동으로 호출됩니다.

스트림 데이터 송수신

/* 스트림 수신: quicly → 앱 RX FIFO */
static int
quic_stream_rx (quicly_stream_t *stream, size_t off,
                const void *src, size_t len)
{
  quic_ctx_t *sctx = (quic_ctx_t *) stream->data;
  session_t *stream_session;

  stream_session = session_get (sctx->session_index);

  /* quicly가 순서 재조립 완료한 데이터를 앱 FIFO에 인큐 */
  svm_fifo_enqueue_with_offset (stream_session->rx_fifo,
                                off, len, src);

  /* 앱에 RX 이벤트 알림 */
  session_send_io_evt_to_thread (stream_session->rx_fifo,
                                 SESSION_IO_EVT_RX);
  return 0;
}

/* 스트림 송신: 앱 TX FIFO → quicly */
static int
quic_stream_tx (session_t *stream_session)
{
  quic_ctx_t *sctx;
  svm_fifo_t *tx_fifo = stream_session->tx_fifo;
  u32 deq_len;
  u8 *data;

  sctx = quic_ctx_get (stream_session->connection_index);
  deq_len = svm_fifo_max_dequeue (tx_fifo);

  if (deq_len == 0)
    return 0;

  /* TX FIFO에서 데이터 읽기 */
  vec_validate (data, deq_len - 1);
  svm_fifo_dequeue (tx_fifo, deq_len, data);

  /* quicly 스트림 버퍼에 데이터 추가 */
  quicly_streambuf_egress_write (sctx->quicly_stream, data, deq_len);

  /* 패킷 송신 스케줄링 */
  quic_send_packets (sctx->parent_ctx);
  return 0;
}
코드 분석: 스트림 데이터 경로

수신 방향에서 quic_stream_rx()는 quicly의 스트림 콜백으로 등록된 함수입니다. quicly가 STREAM 프레임을 복호화하고 순서를 재조립한 후 이 콜백을 호출합니다. off 파라미터는 스트림 내 바이트 오프셋(Offset)으로, 비순차 도착 패킷도 올바른 위치에 저장됩니다. 송신 방향에서 quic_stream_tx()는 앱이 TX FIFO에 쓴 데이터를 quicly_streambuf_egress_write()로 quicly 송신 버퍼에 전달하고, quic_send_packets()로 실제 UDP 패킷 생성을 트리거합니다.

스트림 상태 전이

QUIC 스트림은 RFC 9000에 정의된 상태 머신을 따릅니다. VPP에서 각 상태 전이는 conn_state 필드로 추적됩니다:

QUIC 스트림 상태 전이 (양방향 스트림) IDLE 스트림 미생성 open_stream() OPEN 송신 + 수신 활성 FIN 송신 HALF_CLOSED (local) 수신만 가능 FIN 수신 HALF_CLOSED (remote) 송신만 가능 FIN 수신 FIN 송신 CLOSED 정상 종료 비정상 종료 경로 (RESET) RESET_SENT RESET_STREAM 프레임 전송 RESET_RECVD RESET_STREAM 프레임 수신 RESET_STREAM은 어떤 상태에서든 발생 가능하며, 스트림을 즉시 종료합니다 STOP_SENDING은 수신측이 더 이상 데이터를 원하지 않을 때 전송합니다

스트림 종료 및 오류 처리

/* 스트림 정상 종료 */
static void
quic_stream_close (quic_ctx_t *sctx)
{
  /* 송신 방향 FIN 전송 */
  quicly_streambuf_egress_shutdown (sctx->quicly_stream);
  sctx->conn_state = QUIC_CONN_STATE_STREAM_HALF_CLOSED;

  /* 수신 FIN도 받았으면 완전 종료 */
  if (quicly_recvstate_transfer_complete (&sctx->quicly_stream->recvstate))
    {
      sctx->conn_state = QUIC_CONN_STATE_STREAM_CLOSED;
      quic_stream_cleanup (sctx);
    }
}

/* 스트림 리셋 (비정상 종료) */
static void
quic_stream_reset (quic_ctx_t *sctx, u64 error_code)
{
  /* RESET_STREAM 프레임 전송 (즉시 종료) */
  quicly_reset_stream (sctx->quicly_stream, error_code);

  /* 세션 정리: FIFO 해제, 컨텍스트 반환 */
  session_transport_reset_notify (sctx->session_index);
  quic_stream_cleanup (sctx);
}
코드 분석: 스트림 종료

QUIC 스트림의 정상 종료는 양방향 FIN 교환으로 이루어집니다. quicly_streambuf_egress_shutdown()은 송신 방향을 닫아 STREAM 프레임에 FIN 비트를 설정합니다. 수신 방향도 FIN을 받으면 quicly_recvstate_transfer_complete()가 true를 반환하여 스트림이 완전히 종료됩니다. 비정상 종료 시 quicly_reset_stream()은 RESET_STREAM 프레임을 전송하여 상대방에게 오류 코드와 함께 스트림 즉시 종료를 통보합니다. 오류 코드는 H3_NO_ERROR(0), H3_REQUEST_CANCELLED(0x10c) 등 애플리케이션 프로토콜에 따라 달라집니다.

QUIC 연결 마이그레이션

QUIC의 가장 혁신적인 기능 중 하나가 연결 마이그레이션(Connection Migration)입니다. TCP는 (src_ip, src_port, dst_ip, dst_port) 4-tuple로 연결을 식별하므로 IP가 변경되면 연결이 끊어지지만, QUIC은 Connection ID로 연결을 식별하여 IP/포트 변경 시에도 연결을 유지합니다:

QUIC 연결 마이그레이션 (Connection Migration) 모바일 클라이언트 VPP QUIC 서버 WiFi: 192.168.1.100 CID=0xA1B2 | Stream 데이터 전송 중 CID=0xC3D4 | 응답 데이터 WiFi → LTE 전환 IP 변경: 10.0.0.50 TCP라면 여기서 연결 끊김! LTE: 10.0.0.50 CID=0xA1B2 | PATH_CHALLENGE (경로 검증) CID=0xC3D4 | PATH_RESPONSE (검증 완료) VPP: CID 기반 연결 식별 IP 변경 무관, CID=0xA1B2 → 동일 quic_ctx CID=0xA1B2 | Stream 데이터 계속 전송 (중단 없음) TCP: 재연결 필요 (2-RTT 지연) vs QUIC: 즉시 전환 (0-RTT, 데이터 손실 없음) 모바일 5G/WiFi 핸드오버, VPN IP 변경, 다중 경로(multi-path) 시나리오에서 핵심 이점

QUIC 흐름 제어와 혼잡 제어(Congestion Control)

QUIC은 TCP와 달리 2계층 흐름 제어를 제공합니다:

흐름 제어 레벨프레임설명
스트림 레벨MAX_STREAM_DATA개별 스트림의 수신 버퍼 한도
연결 레벨MAX_DATA전체 연결의 총 수신 데이터 한도
스트림 수MAX_STREAMS동시 활성 스트림 수 제한

VPP QUIC 흐름 제어 파라미터

quicly 내부에서 관리하는 주요 파라미터입니다:

initial_max_data
연결 초기 수신 윈도우 (기본: 1MB)
initial_max_stream_data_bidi
양방향 스트림 윈도우 (기본: 256KB)
initial_max_stream_data_uni
단방향 스트림 윈도우
initial_max_streams_bidi
최대 양방향 스트림 수 (기본: 100)

혼잡 제어는 quicly 기본 Reno + ECN을 지원하며, VPP 24.x부터 BBRv2 혼잡 제어 옵션이 추가되었습니다.

QUIC의 손실 감지(Loss Detection)는 TCP의 재전송(Retransmission) 메커니즘보다 정교합니다. 각 패킷에 고유한 번호가 부여되어(모호성 없음) ACK 기반의 정확한 RTT 측정과 빠른 손실 감지가 가능합니다. 또한 스트림별 독립 복구로 하나의 스트림 손실이 다른 스트림을 차단하지 않습니다(Head-of-Line blocking 해결).

quicly 혼잡 제어 알고리즘 상세

quicly는 기본적으로 Reno 혼잡 제어를 사용하며, VPP 24.x부터 BBRv2 옵션이 추가되었습니다. 두 알고리즘의 동작 방식과 적합한 사용 시나리오가 다릅니다:

항목Reno (기본)BBRv2
동작 원리패킷 손실 기반 (AIMD)대역폭(Bandwidth)·RTT 측정 기반 모델
혼잡 감지패킷 손실 발생 시대역폭 포화·큐잉 지연(Latency) 감지
슬로우 스타트지수 증가 → 손실 시 절반 감소대역폭 측정 후 모델 기반 전환
버퍼블로트(Bufferbloat) 대응약함 (큐 가득 차야 감지)강함 (큐잉 지연 직접 측정)
고손실 네트워크성능 급감안정적 처리량(Throughput) 유지
CPU 오버헤드(Overhead)낮음중간 (RTT 샘플링·모델 갱신)
적합 시나리오LAN, 저지연 데이터센터WAN, 위성 링크, 모바일 네트워크
# startup.conf — 혼잡 제어 알고리즘 선택
quic {
  # 기본 Reno 혼잡 제어 (명시적 지정 불필요)
  cc-algorithm reno

  # BBRv2 혼잡 제어 활성화 (VPP 24.x+)
  # cc-algorithm bbr
}

# CLI에서 런타임 확인
vpp# show quic connections verbose
# 출력에 cc_algorithm, cwnd, ssthresh, bytes_in_flight 표시

패킷 손실 감지 메커니즘

quicly의 손실 감지는 RFC 9002에 기반하여 두 가지 메커니즘을 병행합니다:

ACK 기반 손실 감지
수신측이 보낸 ACK 프레임을 분석하여, ACK된 가장 큰 패킷 번호보다 kPacketThreshold(기본 3) 이상 작은 번호의 미확인 패킷을 손실로 판정합니다. QUIC은 패킷 번호가 절대 재사용되지 않으므로 TCP의 재전송 모호성(Retransmission Ambiguity) 문제가 없습니다.
PTO(Probe Timeout) 기반 감지
일정 시간 동안 ACK를 받지 못하면 프로브(Probe) 패킷을 전송하여 경로 상태를 확인합니다. PTO는 smoothed_rtt + max(4 * rttvar, 1ms) + max_ack_delay로 계산됩니다. TCP의 RTO(Retransmission Timeout)보다 정밀한 RTT 측정이 가능합니다.
/* quicly 손실 감지 핵심 로직 (의사코드) */
static void
quicly_loss_on_ack_received (quicly_loss_t *loss,
                              u64 largest_acked,
                              int64_t now)
{
  quicly_sent_packet_t *sent;

  /* ACK 기반 손실 판정 */
  vec_foreach (sent, loss->sent_packets)
    {
      if (sent->acked)
        continue;

      /* 패킷 번호 임계값 초과 확인 */
      if (largest_acked - sent->pn >= QUICLY_LOSS_PACKET_THRESHOLD)
        {
          quicly_loss_mark_lost (loss, sent);
          continue;
        }

      /* 시간 임계값 초과 확인 */
      int64_t time_threshold = loss->smoothed_rtt * 9 / 8;
      if (now - sent->sent_at > time_threshold)
        quicly_loss_mark_lost (loss, sent);
    }

  /* 혼잡 제어 업데이트 */
  loss->cc->on_loss_detected (loss->cc, loss->num_lost);
}
코드 분석: 손실 감지

QUIC의 손실 감지는 패킷 번호 임계값과 시간 임계값을 모두 사용합니다. 패킷 번호 기반 판정은 ACK된 가장 큰 번호와 3 이상 차이나는 미확인 패킷을 즉시 손실로 처리합니다. 시간 기반 판정은 smoothed_rtt * 9/8을 초과하여 미확인 상태인 패킷을 손실로 처리합니다. 손실이 감지되면 혼잡 제어의 on_loss_detected 콜백이 호출되어 Reno의 경우 cwnd를 절반으로 줄이고, BBR의 경우 대역폭 모델을 업데이트합니다.

Reno vs BBR 혼잡 윈도우 동작 비교 혼잡 윈도우 (cwnd) 시간 → 0 25% 50% 75% 100% 손실 손실 손실 손실 BDP Reno (톱니파: 손실 시 cwnd 절반 감소) BBR (안정적: BDP 기반 모델링)

HTTP/3 over VPP QUIC

HTTP/3는 QUIC 위에서 동작하는 HTTP의 차세대 버전으로, VPP의 QUIC 플러그인 위에 구현할 수 있습니다:

HTTP/3 프로토콜 스택 (VPP 환경)

┌────────────────────────────────┐
│  HTTP/3 (QPACK 헤더 압축)      │  ← HTTP 시맨틱
├────────────────────────────────┤
│  QUIC Streams (다중화)          │  ← 스트림별 독립 전송
├────────────────────────────────┤
│  QUIC Connection (quicly)      │  ← TLS 1.3 내장, 혼잡 제어
├────────────────────────────────┤
│  UDP (TRANSPORT_PROTO_UDP)     │  ← 포트 443 (IANA 표준)
├────────────────────────────────┤
│  VPP ip4/ip6 → DPDK/AF_XDP    │  ← 유저스페이스 네트워크
└────────────────────────────────┘

주요 차이: HTTP/2 vs HTTP/3
• HTTP/2: TCP 위 TLS → HoL blocking 존재
• HTTP/3: QUIC 위 → 스트림별 독립 → HoL blocking 해결
• 서버 푸시, 우선순위, 헤더 압축은 동일 시맨틱

QUIC 설정과 활성화

# startup.conf — QUIC 플러그인 활성화
plugins {
  plugin quic_plugin.so { enable }
}

quic {
  # 최대 동시 연결 수
  max-connections 100000

  # 연결당 최대 스트림 수
  max-streams-per-connection 100

  # 0-RTT 활성화
  enable-0rtt

  # 유휴 타임아웃 (초)
  idle-timeout 60
}

QUIC vs TCP+TLS 성능 비교

VPP 환경에서 QUIC과 TCP+TLS의 주요 차이점:

항목TCP + TLS 1.3QUIC
연결 수립2-RTT (TCP + TLS)1-RTT (통합 핸드셰이크)
재연결2-RTT0-RTT (PSK)
HoL Blocking있음 (TCP 순서 보장(Ordering))없음 (스트림별 독립)
다중화불가 (HTTP/2로 해결)네이티브 스트림 다중화
연결 마이그레이션불가Connection ID 기반 가능
패킷 손실 복구TCP 재전송 (느림)스트림별 독립 복구 (빠름)
VPP 처리량~40 Gbps (단일 워커)~25 Gbps (단일 워커)
CPU 오버헤드낮음중간 (UDP+QUIC 레이어)
HW 오프로드QAT, NIC Crypto제한적 (UDP checksum만)
QUIC vs TCP+TLS 핸드셰이크 비교 TCP + TLS 1.3 (2-RTT) Client Server SYN SYN+ACK ACK RTT 1 ClientHello ServerHello+Fin Client Fin RTT 2 데이터 전송 시작 QUIC (1-RTT / 0-RTT) Client Server Initial (ClientHello+key) Initial+Handshake (SH+Fin) Handshake Fin 1 RTT 데이터 전송 시작 0-RTT 재연결 시 Initial + Early Data (첫 패킷에 데이터 포함) 1-RTT 절약 → 지연시간 50% 감소
QUIC 적합 시나리오: 다수의 독립 스트림이 필요한 HTTP/3, IoT 디바이스 연결, 모바일 환경(IP 변경이 잦은 경우)에서 QUIC이 TCP+TLS 대비 확실한 이점을 제공합니다. 반면 단일 고대역폭 스트림(대용량 파일 전송)에서는 TCP+TLS의 HW 오프로드 지원이 더 효과적일 수 있습니다.

QUIC 0-RTT / 1-RTT 타이밍 상세 분해

"QUIC은 0-RTT다"라는 말은 절반만 맞습니다. 실제로는 연결 상태에 따라 세 가지 모드가 존재하며, 각각의 유효 지연 시간이 다릅니다. VPP QUIC 플러그인이 이 세 모드를 어떻게 선택하는지와, TCP+TLS 1.3과의 정확한 타이밍 차이는 아래 표로 정리됩니다(RTT=20ms 기준).

시나리오TCP+TLS 1.3 지연QUIC 지연QUIC 모드선결 조건
신규 연결2×RTT + TLS = 40 ms1×RTT = 20 msInitial + Handshake
재연결 (PSK 보유)2×RTT = 40 ms1×RTT = 20 ms1-RTT resumptionsession ticket 캐시(Cache)
재연결 + 멱등 요청2×RTT = 40 ms0×RTT + early = 0 ms*0-RTTGET/HEAD 등 멱등
재연결 + POST2×RTT = 40 ms1×RTT = 20 ms1-RTT 강제POST는 0-RTT 거부
네트워크 변경 (NAT rebind)연결 리셋 + 2×RTT = 40+α ms0 msConnection MigrationConnection ID 유지

* 0-RTT의 "0 ms"는 첫 요청 바이트가 즉시 송신된다는 의미이며, 응답이 돌아오는 시간(1×RTT)은 별도입니다.

/* VPP QUIC 플러그인 — 0-RTT 허용 판정 (개념 코드) */
static int
quic_allow_early_data (quic_ctx_t *ctx, http_request_t *req)
{
  if (!ctx->session_ticket || ctx->session_ticket_expired)
    return 0;  /* PSK 없음 → 1-RTT로 강제 */

  if (!ctx->early_data_allowed)
    return 0;  /* 서버가 max_early_data_size=0 전달 */

  /* 멱등 메서드만 0-RTT 허용 — replay 공격 방어 */
  if (req->method != HTTP_GET &&
      req->method != HTTP_HEAD &&
      req->method != HTTP_OPTIONS)
    return 0;

  /* anti-replay 윈도우 내 중복 여부 확인 */
  if (anti_replay_cache_check (ctx->ticket_nonce))
    return 0;  /* 이미 본 nonce → 거부 */

  return 1;
}

실전 결정: 모바일 클라이언트 위주 서비스에서 0-RTT의 실효 절감은 평균 8~15%에 불과합니다(캐시 hit율, 멱등 요청 비율이 낮기 때문). 반면 Connection Migration은 모바일 이동 시 끊김 없는 전환을 제공해 체감 품질 개선이 훨씬 큽니다. VPP QUIC 튜닝 우선순위(Priority)는 ① Connection ID·migration 지원 → ② 1-RTT resumption → ③ 0-RTT 순서로 두는 것이 합리적입니다.

QUIC 디버깅 및 트러블슈팅

VPP QUIC 환경에서 문제를 진단하고 해결하기 위한 CLI 명령, 이벤트 로그, 일반적인 문제 패턴을 정리합니다.

QUIC 상태 확인 CLI 명령

# QUIC 연결 목록 조회
vpp# show quic connections
# 출력: conn_id, state, streams, rx/tx bytes, rtt

# QUIC 연결 상세 정보
vpp# show quic connections verbose
# 출력: 혼잡 윈도우, ssthresh, bytes_in_flight, 스트림 상세

# 활성 스트림 목록
vpp# show quic streams
# 출력: stream_id, state, tx/rx offset, flow_control_limit

# QUIC 플러그인 통계
vpp# show errors
# quic-input, quic-output 노드의 오류 카운터 확인

# QUIC 패킷 트레이싱
vpp# trace add session-queue 100
vpp# show trace
# QUIC 패킷의 수신/처리/송신 경로 확인

# UDP 레벨 패킷 트레이싱 (QUIC 하부 전송)
vpp# trace add udp-input 100
vpp# trace add udp-output 100

quicly 이벤트 로그 활용

quicly는 내부적으로 상세한 이벤트 로그를 생성할 수 있습니다. VPP에서 이를 활성화하면 QUIC 프로토콜 수준의 문제를 세밀하게 분석할 수 있습니다:

# startup.conf — quicly 이벤트 로깅 활성화
quic {
  # 이벤트 로그 파일 경로
  event-log /tmp/quicly-events.json

  # 로그 레벨: packet, cc(혼잡 제어), loss(손실), stream
  event-log-level packet
}
# quicly 이벤트 로그 분석 (JSON 형식)
# 연결 수립 이벤트
cat /tmp/quicly-events.json | jq '.[] | select(.type == "connect")'

# 패킷 손실 이벤트 필터링
cat /tmp/quicly-events.json | jq '.[] | select(.type == "packet-lost")'

# 혼잡 윈도우 변화 추적
cat /tmp/quicly-events.json | jq '.[] | select(.type == "cc-cwnd-update")'

# qvis (QUIC 시각화 도구)로 분석
# https://qvis.quictools.info/ 에서 JSON 파일 업로드

일반적인 QUIC 문제 및 해결

문제증상진단 방법해결책
핸드셰이크 실패연결 수립 불가, 타임아웃show errors에 quic-handshake-fail인증서 경로·유효기간 확인, TLS 1.3 지원 여부 점검
0-RTT 거부0-RTT 데이터가 무시됨이벤트 로그에 early-data-rejected서버의 PSK 캐시 유효기간 확인, ALPN 일치 여부 점검
스트림 생성 실패MAX_STREAMS 초과 오류show quic connections verbose에서 스트림 수 확인max-streams-per-connection 설정 증가
처리량 저하예상보다 낮은 대역폭show quic connections verbose에서 cwnd 확인혼잡 제어 알고리즘 변경(BBR), 초기 윈도우 크기 조정
연결 마이그레이션 실패IP 변경 시 연결 끊김이벤트 로그에 path-validation 실패방화벽(Firewall)에서 새 경로의 UDP 트래픽 허용
과도한 패킷 손실높은 재전송률이벤트 로그에 packet-lost 빈도 확인네트워크 경로 점검, MTU 설정 확인(PMTUD)
메모리 증가QUIC 세션 수 대비 과다 메모리show memory verbose로 quic 힙 확인idle-timeout 줄이기, 비활성 연결 정리
UDP 블로킹QUIC 패킷이 도달하지 않음show interface에서 UDP 드롭 확인방화벽·NAT에서 UDP 443 포트 허용

QUIC 성능 진단 절차

# 1. 기본 상태 확인
vpp# show version
vpp# show quic connections
vpp# show quic streams

# 2. 혼잡 제어 상태 확인
vpp# show quic connections verbose
# cwnd, ssthresh, bytes_in_flight 값 확인
# cwnd가 비정상적으로 작으면 손실 과다 의심

# 3. 세션 FIFO 상태 확인
vpp# show session verbose
# RX/TX FIFO 사용률이 100%에 가까우면 병목

# 4. 오류 카운터 확인
vpp# show errors
# quic-input, quic-output 노드별 오류 확인

# 5. 패킷 경로 트레이싱
vpp# trace add session-queue 50
vpp# show trace
# 패킷이 어느 노드에서 드롭되는지 확인

# 6. 워커 스레드별 부하 확인
vpp# show runtime
# quic 관련 노드의 vectors/call, clocks/vector 확인
디버깅 팁: QUIC 문제의 대부분은 네트워크 경로상의 UDP 차단(방화벽, NAT)에서 발생합니다. QUIC 디버깅을 시작하기 전에 먼저 ping과 간단한 UDP 에코 테스트로 UDP 연결성을 확인하는 것이 좋습니다. 또한 quicly의 이벤트 로그를 qvis 도구(https://qvis.quictools.info/)에 업로드하면 패킷 타임라인, 혼잡 윈도우 변화, 스트림별 데이터 흐름을 시각적으로 분석할 수 있습니다.

HTTP/3 실전 — ALPN 협상, 0-RTT, VPP 25.10 제약

HTTP/3는 HTTP/2의 의미를 거의 그대로 유지한 채 전송 계층만 TCP에서 QUIC로 바꾼 것입니다. 그러나 이 한 가지 변화가 HOL 블로킹·연결 마이그레이션·0-RTT 재개 같은 굵직한 개선을 가져옵니다. FD.io VPP 25.10은 quicly 라이브러리 위에 HTTP/3 어댑터를 얹은 실험적 지원을 제공하며, 이 절은 실전 배포 관점에서 마주치는 ALPN 협상, 0-RTT, 현재 제약을 정리합니다.

ALPN 협상 — h2 vs h3 vs 둘 다

같은 호스트가 HTTP/2와 HTTP/3를 모두 지원할 때, 클라이언트는 어느 쪽을 쓸지 어떻게 결정할까요. 두 단계가 함께 작동합니다.

  1. Alt-Svc 헤더 또는 HTTPS DNS 레코드로 클라이언트는 "이 서버가 HTTP/3를 지원한다"는 사실을 학습합니다.
  2. 다음 요청부터 클라이언트는 UDP/443으로 QUIC Initial 패킷을 보내면서 TLS 1.3 ClientHello의 ALPN 확장에 h3를 넣습니다.
  3. 서버가 ALPN으로 h3를 선택하면 HTTP/3로 협상이 끝납니다. UDP 경로가 막혀 있으면 클라이언트는 즉시 TCP/443로 폴백하고 ALPN으로 h2를 받습니다.
클라이언트 서버 (VPP+quicly) ① TCP/443 + TLS h2 (1차 방문) ② Alt-Svc: h3=":443"; ma=86400 (응답 헤더) ③ 다음 요청: UDP/443 QUIC Initial + ALPN [h3] ④ 0-RTT 가능 시 첫 패킷에 HTTP 요청 동봉

0-RTT 재개 — 빠르지만 위험한 지름길

QUIC의 0-RTT는 TLS 1.3의 PSK(pre-shared key)를 활용해, 핸드셰이크 완료를 기다리지 않고 첫 패킷에 HTTP 요청을 함께 실어 보내는 기능입니다. 한 번이라도 같은 서버와 정상 핸드셰이크를 한 적이 있어야 합니다. 효과는 RTT 1회 절감이며, 모바일 환경에서 체감 지연이 크게 줄어듭니다.

0-RTT 적용안전한 메서드금지 메서드
✅ 권장GET (멱등, 캐시 가능)
⚠️ 주의HEAD, OPTIONS
❌ 금지POST, PUT, DELETE 등 부수효과 있는 메서드

금지 이유는 리플레이(replay) 공격입니다. 0-RTT 데이터는 같은 PSK로 중간자가 그대로 재전송할 수 있어, "결제 실행" 같은 비멱등 요청이 두 번 일어날 수 있습니다. RFC 9001은 0-RTT에 대해 애플리케이션이 명시적 anti-replay 방어(서버측 nonce 캐시)를 두지 않는 한 비멱등 메서드를 허용해서는 안 된다고 못 박습니다.

/* 0-RTT 허용 정책: GET만 통과 */
static int
h3_validate_0rtt_request (http_msg_t *msg)
{
  if (msg->method != HTTP_METHOD_GET && msg->method != HTTP_METHOD_HEAD)
    return -1;  /* 425 Too Early로 응답 */
  /* 헤더에 If-None-Match 있으면 캐시 검증으로 안전 */
  return 0;
}

/* quicly 콜백: 0-RTT 데이터 도착 시 호출 */
static int
h3_on_early_data (quicly_conn_t *qc, ptls_iovec_t data)
{
  http_msg_t msg;
  if (http3_parse_headers (data, &msg) < 0) return -1;
  if (h3_validate_0rtt_request (&msg) < 0)
    return h3_send_status (qc, 425, "Too Early");
  return h3_dispatch_request (qc, &msg);
}

VPP 25.10 HTTP/3 현재 제약

기능상태비고
QUIC 전송 (quicly)✅ 안정클라이언트·서버 모두
HTTP/3 어댑터🟡 실험적http3_plugin (out-of-tree)
QPACK🟡 부분정적 테이블 위주, 동적 테이블 제한적
0-RTT🟡 실험적anti-replay 정책 수동 구성 필요
연결 마이그레이션🟡 실험적NAT rebinding 한정 검증
Datagram 확장 (RFC 9221)❌ 미지원WebTransport 의존 기능 불가
Multipath QUIC❌ 미지원표준화 진행 중
프로덕션 권고: VPP 25.10에서 HTTP/3는 가시성 확보·부하 분산(Load Balancing) 실험·내부 서비스 PoC까지가 안전 영역입니다. 외부 사용자에게 제공하는 운영 트래픽은 nginx-quic 또는 Cloudflare quiche 같은 성숙한 사용자 공간(User Space) 스택 뒤에 두는 편이 안정적입니다. VPP의 강점은 같은 워커에서 L2~L4까지 함께 처리할 수 있다는 점이므로, HTTP/3는 점진적 도입을 권합니다.

CLI 예시 — HTTP/3 활성화와 관찰

# 1) QUIC 플러그인 로드
vppctl show plugins | grep -E '(quic|http3)'

# 2) HTTP/3 서버 활성화 (가상 CLI)
vppctl http3 server enable uri quic://0.0.0.0/443 \
    cert /etc/vpp/cert.pem key /etc/vpp/key.pem \
    enable-0rtt allowed-methods GET,HEAD

# 3) Alt-Svc 광고를 위한 HTTP/2 서버도 같이 실행
vppctl http server enable uri tls://0.0.0.0/443 \
    alt-svc 'h3=":443"; ma=86400'

# 4) curl로 HTTP/3 강제 요청
curl --http3 -k https://192.0.2.10/

# 5) QUIC 통계
vppctl show quic sessions
vppctl show quic stats
vppctl show http3 streams

Common Pitfalls

Connection Migration 운영

QUIC의 대표 기능인 Connection Migration은 클라이언트의 IP·포트가 바뀌어도 Connection ID(CID)로 같은 연결을 이어갑니다. TCP와 달리 5튜플 변경에 무관하게 세션이 유지되므로 모바일 셀 전환, Wi-Fi↔LTE 핸드오버, NAT 재바인딩에 유리합니다. 그러나 실제 운영에서는 CID 관리, 경로 검증(Path Validation), DoS 완화의 세 가지 축을 모두 튜닝해야 합니다.

핵심 개념: Connection Migration은 새 경로로 PATH_CHALLENGE/PATH_RESPONSE 프레임을 교환해 경로 도달성을 검증한 뒤, amplification limit(3배 제한)을 두고 트래픽을 옮겨갑니다. 서버는 active_connection_id_limit 만큼 예비 CID를 미리 발급해 두어야 하며, 이 값이 작으면 마이그레이션 시 CID 고갈로 연결이 끊어집니다.

시나리오 1: NAT 재바인딩

UDP NAT 매핑은 흔히 30~60초 만에 만료됩니다. 클라이언트가 PING 프레임을 보내지 않거나 마지막 트래픽 이후 시간이 지나면, NAT 게이트웨이는 새 외부 포트를 재할당합니다. 서버에서 관찰되는 증상은 기존 CID가 다른 5튜플에서 도착하는 것입니다.

시나리오 2: 모바일 셀 · Wi-Fi↔LTE 핸드오버

스마트폰이 Wi-Fi에서 LTE로 전환되는 순간 커널 소켓은 EHOSTUNREACH 또는 ENETUNREACH를 받습니다. QUIC 스택은 새 인터페이스에서 같은 CID로 QUIC 패킷을 재전송하고, 서버는 마이그레이션을 감지해 active path를 교체합니다. 관찰 가능한 지표:

지표정상 범위이상 신호
Migration 완료 시간< 200ms (1-RTT + path validation)> 1s — 패킷 손실 또는 스로틀링
Path validation 실패율< 1%> 5% — 새 경로의 MTU/방화벽 이슈
CID 고갈 이벤트0> 0 — active_connection_id_limit 증가 필요

Amplification 공격 방어

Connection Migration은 DoS 증폭 공격의 벡터가 될 수 있습니다. 공격자가 피해자의 주소로 위조된 마이그레이션 시도를 보내면, 서버는 그 경로로 응답을 쏟아냅니다. QUIC 표준(RFC 9000 §8)은 이를 막기 위해 서버가 검증되지 않은 경로로는 클라이언트가 보낸 바이트의 3배 이상을 보낼 수 없다고 규정합니다.

0-RTT 재개와 재전송 방어

0-RTT는 이전 세션에서 서버가 발급한 session ticket을 이용해 첫 패킷과 함께 응용 데이터를 전송하는 기능입니다. 지연이 줄어드는 대가로 재전송 공격(replay attack)에 노출되는데, 공격자가 0-RTT 데이터를 캡처해 재생하면 서버가 같은 요청을 두 번 처리할 수 있습니다. TLS 1.3과 QUIC은 공통적으로 "0-RTT는 idempotent 요청에만 쓸 것"을 권고합니다.

Replay Window 설계

VPP quicly는 기본적으로 anti-replay를 활성화합니다. 세 가지 보호 계층이 있습니다.

  1. Single-use ticket: 각 session ticket은 한 번만 사용 가능. quicly의 ticket_file에 사용 기록이 저장되고, 중복 사용 시 full handshake로 폴백합니다.
  2. Time-bound: Ticket lifetime은 기본 7200초. 이 창(window) 밖의 티켓은 거절합니다.
  3. Request class filter: HTTP 어댑터 레벨에서 GET·HEAD·OPTIONS와 일부 PUT(ETag/If-Match 조건부)만 0-RTT 수락. POST는 1-RTT로 강제 연기.
함정: Single-use ticket을 여러 프로세스가 공유하는 경우(서버 클러스터 뒤의 로드 밸런서) ticket store를 중앙화하지 않으면 anti-replay가 무력화됩니다. vpp-quic-ticket-store를 Redis 같은 공유 저장소로 연결하거나, 세션 스티키니스를 보장해야 합니다.

애플리케이션 계약

VPP HTTP 어댑터는 0-RTT에서 도착한 요청의 X-VPP-Early-Data: 1 헤더를 설정합니다. 애플리케이션은 이 헤더가 있는 요청이 재전송될 수 있음을 가정하고 다음 규칙을 따라야 합니다.

0-RTT 모니터링 지표

운영에서 추적해야 할 카운터:

카운터의미권장 임계
/quic/0rtt/accepted0-RTT 데이터를 수락한 수전체 연결의 20~60%
/quic/0rtt/rejected_replayanti-replay로 거절한 수가급적 0, 1% 초과 시 알람
/quic/0rtt/rejected_expiredticket 만료로 거절정상 — 튜닝 대상 아님
/http/early_data/425_response애플리케이션이 0-RTT 거절한 수설계 의도에 따름

VPP 25.10 → 26.02 변경 요약 (QUIC · HTTP/3)

본 문서의 기준 버전은 VPP v26.02(2026-02-25 릴리스)입니다. 앞 절에서 언급한 "VPP 25.10 제약" 일부는 26.02에서 개선되었으므로 운영 판단이 달라질 수 있습니다. 아래 표는 공식 26.02 릴리스 노트에 명시된 HTTP/3·QUIC 관련 변경을 추려 운영 관점에서 재정리한 것입니다.

영역VPP 25.10VPP 26.02운영 영향
HTTP/3 코어 quicly 기반 얇은 h3 어댑터(실험) H3 core skeletonH3 framing layer 도입 — HTTP/3 프레이밍을 VPP http 플러그인 내에서 처리하는 1급 경로가 생겼습니다. h3 요청 파싱·상태 기계가 VPP 측에 있으므로, 호스트 스택 세션 훅(rx_callback, 통계, ACL)을 h3 트래픽에도 그대로 적용할 수 있습니다.
HTTP/3 클라이언트 사실상 없음(테스트 경로만) H3 client side 공식 지원 VPP 자체를 HTTP/3 클라이언트로 사용하는 프로브·헬스체크·proxy origin 호출이 가능해졌습니다.
QPACK 미지원 QPACK encoder/decoder 도입(요청·응답 모두). 단, static table 전용으로 dynamic table은 아직 없습니다. 상호운용성은 확보되지만 동적 테이블 기반 고압축은 불가합니다. 대역폭 감축 기대치는 h2 HPACK 대비 소폭입니다.
HTTP CONNECT 프록시 없음(HTTP/2 extended CONNECT 서버 측만) 호스트 스택에 HTTP CONNECT proxy client 추가 VPP가 upstream proxy를 CONNECT로 경유하는 outbound 경로(기업망·사이드카)를 직접 구성할 수 있습니다.
HTTP 클라이언트 redirect 수동 처리 필요 HTTP 클라이언트에 basic redirect 지원 3xx 응답을 따라가는 프로브·파일 다운로드 시 애플리케이션 단순화가 가능합니다.
DPDK/RDMA 토대 DPDK 25.07 + rdma-core 58.0 DPDK 25.11 + rdma-core 60.0 QUIC 가속 오프로드(UDP GSO/GRO)와 관련한 PMD 옵션 정비. 업그레이드 시 DPDK 25.11 deprecated 옵션을 점검해야 합니다.
여전히 성숙도는 experimental: VPP 26.02의 HTTP/3는 코어 프레이밍과 클라이언트가 들어와 완성도가 크게 올랐지만, 공식 feature list 기준으로는 여전히 experimental 분류입니다. QPACK dynamic table 부재, 일부 extensions 미구현으로 인해 가용성 99.99% SLO를 약속하기 전에는 카나리아 트래픽과 공식 릴리스 노트 교차 확인이 필요합니다.
업그레이드 체크리스트: (1) QPACK static-table 전제에서 테스트 트래픽의 헤더 사이즈 측정, (2) HTTP CONNECT proxy client를 사용한다면 upstream proxy에 대한 상호 TLS(mTLS) 설정을 TLS 문서의 26.02 변경 절과 함께 검토, (3) DPDK 25.11 전환을 데이터 평면 문서의 PMD 변경 표와 함께 확인, (4) HTTP/3 경로를 h3 전용이 아닌 h2 fallback과 함께 서빙.

이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하시기 바랍니다.

오픈소스 코드 인용 고지

라이선스 고지: 이 문서의 코드 예제에는 아래 오픈소스 프로젝트의 소스 코드에서 발췌·간략화한 내용이 포함되어 있습니다. 해당 코드 블록에는 원본 프로젝트의 라이선스가 그대로 적용되며, 본 사이트의 CC BY-NC-SA 4.0 라이선스 대상에서 제외됩니다. 이들 코드의 포함은 한국 저작권법 제28조 및 제35조의5에 근거한 교육 목적의 공정 이용에 해당합니다.
프로젝트저작권자라이선스공식 저장소
VPP (Vector Packet Processing) FD.io contributors Apache License 2.0 github.com/FDio/vpp
DPDK (Data Plane Development Kit) DPDK contributors BSD 3-Clause License github.com/DPDK/dpdk

코드 블록 내 주석에 원본 파일 경로가 표기된 부분은 해당 프로젝트 소스 코드에서 발췌·간략화한 것입니다. 전체 소스 코드는 위 공식 저장소에서 확인하시기 바랍니다.