cloudflare/quiche — 🥧 Savoury implementation of the QUIC transport protocol and HTTP/3
cloudflare/quiche — QUIC 전송 프로토콜과 HTTP/3의 구현
Cloudflare의 quiche는 IETF 표준 QUIC과 HTTP/3을 구현한 Rust 기반 라이브러리입니다. 네트워크 I/O와 이벤트 루프를 애플리케이션에 맡기는 저수준 API를 제공하며, Cloudflare 엣지 네트워크와 Android DNS resolver의 HTTP/3 구현 등에 사용됩니다.
- 주제
AI 요약
quiche는 IETF가 정의한 QUIC(Quick UDP Internet Connections) 전송 프로토콜과 HTTP/3을 구현한 Rust 라이브러리입니다. 연결 상태와 QUIC 패킷 처리를 위한 저수준 API를 제공하지만, 소켓을 포함한 네트워크 I/O와 타이머를 지원하는 이벤트 루프는 애플리케이션이 직접 제공해야 합니다. 따라서 특정 운영체제나 네트워크 프레임워크의 이벤트 모델에 quiche를 맞춰 통합할 수 있는 구조입니다. quiche는 Cloudflare 엣지 네트워크의 HTTP/3 지원을 구동하며, Android DNS resolver의 DNS over HTTP/3 구현에도 사용됩니다. curl에 통합해 HTTP/3을 지원하는 방식도 제공됩니다. 테스트와 실험에는 cloudflare-quic.com을 사용할 수 있습니다.
■ 제공 범위와 실행 예제
저장소에는 quiche API를 사용하는 예제로 quiche-apps crate의 클라이언트와 서버가 포함되어 있습니다. 빌드한 뒤 클라이언트는 `cargo run --bin quiche-client -- https://cloudflare-quic.com/` 명령으로 실행할 수 있고, 서버는 `cargo run --bin quiche-server -- --cert apps/src/bin/cert.crt --key apps/src/bin/cert.key` 명령으로 실행할 수 있습니다. 다만 제공되는 인증서는 self-signed 인증서이므로 운영 환경에서는 사용하면 안 됩니다. 각 도구의 세부 옵션은 `--help` 플래그로 확인할 수 있으며, 저장소는 이 도구들이 production 환경에 적합하지 않다고 명시합니다.
■ 연결 구성과 클라이언트·서버 초기화
QUIC 연결을 만들기 전에는 `quiche::Config::new(quiche::PROTOCOL_VERSION)`으로 `Config` 객체를 생성하고, `set_application_protos()`를 사용해 ALPN(Application-Layer Protocol Negotiation) 식별자를 설정합니다. `Config`는 QUIC 버전, ALPN ID, flow control, congestion control, idle timeout 등 연결의 주요 동작을 제어합니다. QUIC은 범용 전송 프로토콜이므로 모든 애플리케이션에 공통으로 적용할 수 있는 합리적인 기본값이 없는 설정도 있습니다. 예를 들어 특정 유형의 동시 스트림 수는 QUIC 위에서 실행되는 애플리케이션에 따라 달라집니다. 이에 따라 일부 속성은 기본값이 0으로 설정되어 있으며, 애플리케이션이 용도에 맞는 값으로 변경해야 합니다.
`Config`에는 TLS 설정도 포함됩니다. 기존 객체의 mutator를 사용해 변경하거나, TLS context를 직접 구성한 뒤 `with_boring_ssl_ctx_builder()`를 통해 설정을 만들 수 있습니다. 하나의 configuration object는 여러 연결에서 공유할 수 있습니다. 클라이언트는 `connect()`로 새 연결을 만들고, 서버는 `accept()`를 사용합니다. 예제에서는 클라이언트가 서버 이름, source connection ID, 로컬·피어 주소와 설정을 전달해 연결을 만들고, 서버는 `accept()`에 같은 종류의 연결 정보를 전달해 연결을 수락합니다.
■ 패킷 수신·송신과 타이머 처리
네트워크에서 수신한 패킷은 애플리케이션이 소켓에서 읽은 뒤 `RecvInfo`에 발신지와 로컬 주소를 담아 연결의 `recv()` 메서드에 전달합니다. `recv()`가 반환하는 처리 결과와 오류는 애플리케이션이 직접 다뤄야 합니다. 반대로 송신할 패킷은 `send()` 메서드로 생성합니다. `send()`가 반환한 바이트 수만큼 출력 버퍼를 사용해 `send_to()`로 목적지에 전송하며, 더 보낼 데이터가 없을 때는 `quiche::Error::Done`을 받아 송신 루프를 종료합니다.
QUIC 연결은 시간 기반 이벤트를 처리해야 하므로 애플리케이션이 타이머도 관리해야 합니다. 다음 만료 시각은 `conn.timeout()`으로 얻을 수 있으며, 타이머가 만료되면 `conn.on_timeout()`을 호출한 뒤 추가 패킷을 생성해 네트워크로 보내야 합니다. 타이머 구현은 운영체제나 네트워크 프레임워크에 맞춰 선택할 수 있습니다. 또한 quiche는 짧은 시간에 패킷이 몰려 네트워크에 일시적인 혼잡과 손실을 일으키는 상황을 피하도록 송신 pacing을 권장합니다. `send()`가 반환하는 `SendInfo`의 `at` 필드에는 특정 패킷을 네트워크에 보내야 하는 시점에 대한 pacing hint가 담깁니다. 애플리케이션은 Linux의 `SO_TXTIME` 소켓 옵션이나 사용자 공간 타이머 같은 플랫폼별 방법으로 이 시점을 반영할 수 있습니다.
■ 스트림 데이터와 HTTP/3 API
핸드셰이크가 완료되어 `conn.is_established()`가 참이 되면 스트림을 통해 애플리케이션 데이터를 보낼 수 있습니다. 예제에서는 `conn.stream_send(0, b"hello", true)`로 스트림 0에 데이터를 보내고 전송 종료 여부를 함께 표시합니다. 읽을 데이터가 있는 스트림은 `conn.readable()`이 반환하는 iterator로 확인합니다. 각 스트림의 데이터는 `stream_recv()`로 읽으며, 반환값에는 읽은 바이트 수와 FIN 수신 여부가 포함됩니다. 애플리케이션은 readable stream을 순회하면서 더 읽을 데이터가 없을 때까지 반복해 처리할 수 있습니다.
전송 계층 API 외에도 quiche는 QUIC 위에서 HTTP 요청과 응답을 주고받기 위한 고수준 HTTP/3 모듈을 제공합니다. 더 완전한 사용 사례는 `quiche/examples/` 디렉터리에서 확인할 수 있으며, Rust API뿐 아니라 C/C++ 애플리케이션에 quiche를 연결하는 예제도 포함됩니다.
■ C/C++ 연동과 빌드 요구 사항
quiche는 Rust API 위에 얇은 C API를 제공하며, C 언어의 제약을 제외하면 Rust API와 같은 설계를 따릅니다. C API를 활성화하려면 기본적으로 비활성화된 `ffi` feature를 `cargo build --features ffi`처럼 지정해야 합니다. 빌드가 끝나면 Rust 라이브러리와 함께 정적 라이브러리 `libquiche.a`가 자동으로 생성되며, 이를 C/C++ 애플리케이션에 직접 링크할 수 있습니다. C API를 호출할 수 있는 FFI 환경을 가진 다른 언어에서도 이 방식을 활용할 수 있습니다.
quiche를 빌드하려면 Rust 1.88 이상이 필요합니다. 소스는 `git clone https://github.com/cloudflare/quiche`로 가져오고, `cargo build --examples`로 예제까지 빌드할 수 있으며, 테스트는 `cargo test`로 실행합니다. QUIC의 TLS 기반 암호화 핸드셰이크에는 BoringSSL이 사용됩니다. Cargo로 빌드할 때 `boring-sys` crate가 BoringSSL을 자동으로 빌드하고 링크하지만, 시스템에 `cmake` 명령이 있어야 합니다. Windows에서는 NASM도 필요합니다. 자체 BoringSSL 빌드를 사용하려면 `BORING_BSSL_PATH` 환경 변수로 경로를 지정할 수 있습니다.
■ Android·iOS 및 Docker 빌드
Android 빌드에는 Android NDK 19 이상이 필요하고, 21이 권장됩니다. `ANDROID_NDK_HOME`에 NDK 경로를 설정한 뒤 `aarch64-linux-android`, `armv7-linux-androideabi`, `i686-linux-android`, `x86_64-linux-android` 등 필요한 Rust target을 설치합니다. `cargo-ndk` 2.0 이상을 설치한 뒤에는 예를 들어 `cargo ndk -t arm64-v8a -p 21 -- build --features ffi`를 실행해 빌드합니다. 모든 대상 아키텍처의 최소 API level은 21입니다.
iOS 빌드에는 Xcode command-line tools와 iOS용 Rust target인 `aarch64-apple-ios`, `x86_64-apple-ios`가 필요합니다. `cargo-lipo`를 설치한 뒤 `cargo lipo --features ffi` 또는 release 빌드 명령을 사용할 수 있습니다. 저장소는 Xcode 10.1과 11.2에서 iOS 빌드를 테스트했다고 안내합니다. Docker 이미지는 `make docker-build`로 만들 수 있으며, Docker Hub에는 실행 파일이 설치된 `cloudflare/quiche`와 quic-interop-runner에서 quiche를 테스트하는 스크립트를 제공하는 `cloudflare/quiche-qns` 이미지가 있습니다.
원문: GitHub / 번역·요약: Trawling