QUIC/HTTP3에 필요한 배경 지식에 대해 글을 쓴게 벌써 지난 3월이네요. 그동안 IETF QUIC은 draft-22 가 나오는 등의 많은 변화가 있었습니다.

얼마 전에 HTTP 요청을 명령행 또는 프로그램으로 처리하기 위해 많이 사용되는 curl 에 최근 quiche 를 통한 http3 지원이 추가되었는데, 이 글에서는 curl 개발 버전을 빌드해서 quic + http3 동작을 확인해 보도록 하겠습니다.

빌드하기 전에

이 글에서는 Debian GNU/Linux 버전 10 (buster) (x86-64) 에서 개발버전 curl을 빌드해 보도록 하겠습니다. 설치하고 사용자 계정만 하나 만든 상태이고 현재 사용자는 sudo가 가능한 상태라고 가정 합니다.

기본적으로는 curl의 HTTP3.md에 있는 내용을 따라 하는 것이므로 추가적인 내용은 curl 사이트를 참조해 주세요.

http3 지원을 위해서 quiche를 이용하게 됩니다. quiche는 Cloudflare에서 오픈소스로 공개한 Rust 언어로 작성된 IETF QUIC 및 HTTP3 구현체입니다.

필요한 패키지 설치

다음 패키지는 아래 내용을 빌드하기 위해서 최소한으로 필요한 것입니다. curl은 상당히 많은 외부 라이브러리와 옵션을 지원하지만 지금은 quiche와 빌드할 수 있는 최소한의 패키지만을 설치 합니다.

sudo apt update
sudo apt install -y build-essential git cmake golang rustc autoconf libtool

빌드

빌드 순서는 대략 다음과 같습니다. 아래 명령을 순서대로 실행해 주면 됩니다. $HOME에서 시작하니까 작업 디렉토리는 $HOME/work/curl-quiche/가 됩니다. 기본적으로는 다음과 같습니다.

  • quiche 와 boringssl (의존성에 포함되어 있습니다)를 체크아웃
  • boringssl 을 빌드 (quiche와 curl 모두 boringssl을 필요로 하니까, 한번만 빌드해서 양쪽에서 사용 합니다)
  • quiche 를 빌드
  • curl을 --with-quiche 옵션으로 빌드
mkdir work
mkdir curl-quiche
cd curl-quiche
git clone https://github.com/cloudflare/quiche --recursive
cd quiche/deps/boringssl
mkdir build
cd build
cmake -DCMAKE_POSITION_INDEPENDENT_CODE=on ..
make
cd ..
mkdir -p .openssl/lib
cp build/crypto/libcrypto.a build/ssl/libssl.a .openssl/lib
ln -s $PWD/include .openssl
cd ../..
QUICHE_BSSL_PATH=$PWD/deps/boringssl cargo build --release
cd ..
git clone https://github.com/curl/curl
cd curl
./buildconf
./configure --with-ssl=$PWD/../quiche/deps/boringssl/.openssl --with-quiche=$PWD/../quiche --enable-debug
make

테스트해 보기

빌드가 오류 없이 종료 되었다면 이제 빌드된 curl--http3옵션을 사용할 수 있습니다. 글 작성일 기준에서는 draft-22 스펙을 따르는 것이 빌드가 되었을 텐데, 테스트 가능한 서버 중 하나인 https://cloudflare-quic.com에 QUIC+HTTP3 요청을 날려 봐서 다운로드가 제대로 되는지 확인해 보면 됩니다.

$ src/curl --http3 -vso/dev/null https://cloudflare-quic.com
* STATE: INIT => CONNECT handle 0x5620d87e0288; line 1362 (connection #-5000)
* Added connection 0. The cache now contains 1 members
* STATE: CONNECT => WAITRESOLVE handle 0x5620d87e0288; line 1403 (connection #0)
*   Trying 198.41.213.75:443...
* Sent QUIC client Initial, ALPN: h3-22
* STATE: WAITRESOLVE => WAITCONNECT handle 0x5620d87e0288; line 1482 (connection #0)
* Connected to cloudflare-quic.com (198.41.213.75) port 443 (#0)
* STATE: WAITCONNECT => SENDPROTOCONNECT handle 0x5620d87e0288; line 1538 (connection #0)
* Marked for [keep alive]: HTTP default
* STATE: SENDPROTOCONNECT => PROTOCONNECT handle 0x5620d87e0288; line 1553 (connection #0)
* quiche established connection!
* STATE: PROTOCONNECT => DO handle 0x5620d87e0288; line 1572 (connection #0)
* h3 [:method: GET]
* h3 [:path: /]
* h3 [:scheme: https]
* h3 [:authority: cloudflare-quic.com]
* h3 [User-Agent: curl/7.66.0-DEV]
* h3 [Accept: */*]
* Using HTTP/3 Stream ID: 0 (easy handle 0x5620d87e0288)
> GET / HTTP/3
> Host: cloudflare-quic.com
> User-Agent: curl/7.66.0-DEV
> Accept: */*
>
* STATE: DO => DO_DONE handle 0x5620d87e0288; line 1627 (connection #0)
* STATE: DO_DONE => PERFORM handle 0x5620d87e0288; line 1749 (connection #0)
* quiche says HEADERS
* HTTP 1.1 or later with persistent connection
< HTTP/3 200
< date: Mon, 12 Aug 2019 18:43:24 GMT
< content-type: text/html
< content-length: 106072
* Added cookie __cfduid="dc4e2a1388a141fc9687836a627e284d41565635404" for domain cloudflare-quic.com, path /, expire 1597171404
< set-cookie: __cfduid=dc4e2a1388a141fc9687836a627e284d41565635404; expires=Tue, 11-Aug-20 18:43:24 GMT; path=/; domain=.cloudflare-quic.com; HttpOnly; Secure
< alt-svc: h3-20=":443"; ma=86400
< expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
< server: cloudflare
< cf-ray: 5054983d2c5fa8e5-SJC-PIG
* quiche says DATA
<
{ [2 bytes data]
* quiche says DATA
{ [21264 bytes data]
* quiche says DATA
{ [11028 bytes data]
* quiche says DATA
{ [11905 bytes data]
* quiche says DATA
{ [11480 bytes data]
* quiche says DATA
{ [8688 bytes data]
* quiche says DATA
{ [695 bytes data]
* quiche says DATA
{ [3892 bytes data]
* quiche says DATA
{ [8036 bytes data]
* quiche says DATA
{ [11480 bytes data]
* quiche says DATA
{ [9360 bytes data]
* quiche says DATA
{ [3265 bytes data]
* quiche says DATA
{ [4977 bytes data]
* readwrite_data: we're done!
* nread <= 0, server closed connection, bailing
* STATE: PERFORM => DONE handle 0x5620d87e0288; line 1939 (connection #0)
* multi_done
* Connection #0 to host cloudflare-quic.com left intact
* Expire cleared (transfer 0x5620d87e0288)

로깅 옵션(-v)을 켠 거라 출력되는 내용이 많은데 대략 다음을 보면 요청이 성공적이었는지 알 수 있습니다.

$ src/curl --http3 -vso/dev/null https://cloudflare-quic.com
...
* Connected to cloudflare-quic.com (198.41.213.75) port 443 (#0)
...
* quiche established connection!
...
< HTTP/3 200
...
* STATE: PERFORM => DONE handle 0x5620d87e0288; line 1939 (connection #0)

제일 중요한 것은 서버 응답이 HTTP/3 200으로 시작된다는 것입니다.

최근에는 Facebook에서 IETF QUIC 지원을 시작하였으므로 다음 명령도 해 보세요.

$ src/curl --http3 -vso/dev/null https://www.facebook.com
* STATE: INIT => CONNECT handle 0x56260b83d288; line 1362 (connection #-5000)
* Added connection 0. The cache now contains 1 members
* STATE: CONNECT => WAITRESOLVE handle 0x56260b83d288; line 1403 (connection #0)
*   Trying 31.13.65.36:443...
* Sent QUIC client Initial, ALPN: h3-22
* STATE: WAITRESOLVE => WAITCONNECT handle 0x56260b83d288; line 1482 (connection #0)
* Connected to www.facebook.com (31.13.65.36) port 443 (#0)
* STATE: WAITCONNECT => SENDPROTOCONNECT handle 0x56260b83d288; line 1538 (connection #0)
* Marked for [keep alive]: HTTP default
* STATE: SENDPROTOCONNECT => PROTOCONNECT handle 0x56260b83d288; line 1553 (connection #0)
* quiche established connection!
* STATE: PROTOCONNECT => DO handle 0x56260b83d288; line 1572 (connection #0)
* h3 [:method: GET]
* h3 [:path: /]
* h3 [:scheme: https]
* h3 [:authority: www.facebook.com]
* h3 [User-Agent: curl/7.66.0-DEV]
* h3 [Accept: */*]
* Using HTTP/3 Stream ID: 0 (easy handle 0x56260b83d288)
> GET / HTTP/3
> Host: www.facebook.com
> User-Agent: curl/7.66.0-DEV
> Accept: */*
>
* STATE: DO => DO_DONE handle 0x56260b83d288; line 1627 (connection #0)
* STATE: DO_DONE => PERFORM handle 0x56260b83d288; line 1749 (connection #0)
* quiche says HEADERS
* HTTP 1.1 or later with persistent connection
< HTTP/3 200
* Added cookie fr="1YI4SLqCaD3bRXSl9..BdUbOS.gc.AAA.0.0.BdUbOS.AWVS0s4t" for domain facebook.com, path /, expire 1597171473
< set-cookie: fr=1YI4SLqCaD3bRXSl9..BdUbOS.gc.AAA.0.0.BdUbOS.AWVS0s4t; expires=Tue, 11-Aug-2020 18:44:33 GMT; Max-Age=31535999; path=/; domain=.facebook.com; secure; httponly
* Added cookie sb="krNRXdTUXuwUrRWqwCnuIfCt" for domain facebook.com, path /, expire 1628707474
< set-cookie: sb=krNRXdTUXuwUrRWqwCnuIfCt; expires=Wed, 11-Aug-2021 18:44:34 GMT; Max-Age=63072000; path=/; domain=.facebook.com; secure; httponly
< cache-control: private, no-cache, no-store, must-revalidate
< pragma: no-cache
< strict-transport-security: max-age=15552000; preload
< vary: Accept-Encoding
< x-content-type-options: nosniff
< x-frame-options: DENY
< x-xss-protection: 0
< expires: Sat, 01 Jan 2000 00:00:00 GMT
< content-type: text/html; charset="utf-8"
< x-fb-debug: KZkV9h1ApQ/mNZP55Pz+ra+bbnQAZhRgflHSr7tt7TVkpzesp3zu7QXHUrqOKdgDmGdK9o3yOsqQ1SGui4eMlg==
< date: Mon, 12 Aug 2019 18:44:34 GMT
* quiche says DATA
<
{ [2 bytes data]
* quiche says DATA
{ [716 bytes data]
...
* quiche says DATA
{ [648 bytes data]
* quiche says FINISHED
* nread <= 0, server closed connection, bailing
* STATE: PERFORM => DONE handle 0x56260b83d288; line 1939 (connection #0)
* multi_done
* Connection #0 to host www.facebook.com left intact
* Expire cleared (transfer 0x56260b83d288)

이외 인터넷을 통해 테스트 가능한 서버 목록은 여기서 찾아볼 수 있습니다. 다만 아직 개발 중이므로 호환되지 않거나 잘못 동작할 경우도 있으므로 이점 유의하시기 바랍니다.

끝으로

아직 개발 버전이므로 제대로 동작하지 않는 부분들이 많이 있지만, 사람들이 많이 사용하는 curl 유틸리티에서 http3 지원이 추가되고 있는 것은 긍정적인 부분입니다. 현재는 작업 중이지만 조만간 다른 QUIC 라이브러리인 ngtcp2를 이용한 http3 지원도 추가될 예정입니다.