mirror of
https://github.com/curl/curl.git
synced 2024-12-15 06:40:09 +08:00
671158242d
New cfilter HTTP-CONNECT for h3/h2/http1.1 eyeballing. - filter is installed when `--http3` in the tool is used (or the equivalent CURLOPT_ done in the library) - starts a QUIC/HTTP/3 connect right away. Should that not succeed after 100ms (subject to change), a parallel attempt is started for HTTP/2 and HTTP/1.1 via TCP - both attempts are subject to IPv6/IPv4 eyeballing, same as happens for other connections - tie timeout to the ip-version HAPPY_EYEBALLS_TIMEOUT - use a `soft` timeout at half the value. When the soft timeout expires, the HTTPS-CONNECT filter checks if the QUIC filter has received any data from the server. If not, it will start the HTTP/2 attempt. HTTP/3(ngtcp2) improvements. - setting call_data in all cfilter calls similar to http/2 and vtls filters for use in callback where no stream data is available. - returning CURLE_PARTIAL_FILE for prematurely terminated transfers - enabling pytest test_05 for h3 - shifting functionality to "connect" UDP sockets from ngtcp2 implementation into the udp socket cfilter. Because unconnected UDP sockets are weird. For example they error when adding to a pollset. HTTP/3(quiche) improvements. - fixed upload bug in quiche implementation, now passes 251 and pytest - error codes on stream RESET - improved debug logs - handling of DRAIN during connect - limiting pending event queue HTTP/2 cfilter improvements. - use LOG_CF macros for dynamic logging in debug build - fix CURLcode on RST streams to be CURLE_PARTIAL_FILE - enable pytest test_05 for h2 - fix upload pytests and improve parallel transfer performance. GOAWAY handling for ngtcp2/quiche - during connect, when the remote server refuses to accept new connections and closes immediately (so the local conn goes into DRAIN phase), the connection is torn down and a another attempt is made after a short grace period. This is the behaviour observed with nghttpx when we tell it to shut down gracefully. Tested in pytest test_03_02. TLS improvements - ALPN selection for SSL/SSL-PROXY filters in one vtls set of functions, replaces copy of logic in all tls backends. - standardized the infof logging of offered ALPNs - ALPN negotiated: have common function for all backends that sets alpn proprty and connection related things based on the negotiated protocol (or lack thereof). - new tests/tests-httpd/scorecard.py for testing h3/h2 protocol implementation. Invoke: python3 tests/tests-httpd/scorecard.py --help for usage. Improvements on gathering connect statistics and socket access. - new CF_CTRL_CONN_REPORT_STATS cfilter control for having cfilters report connection statistics. This is triggered when the connection has completely connected. - new void Curl_pgrsTimeWas(..) method to report a timer update with a timestamp of when it happend. This allows for updating timers "later", e.g. a connect statistic after full connectivity has been reached. - in case of HTTP eyeballing, the previous changes will update statistics only from the filter chain that "won" the eyeballing. - new cfilter query CF_QUERY_SOCKET for retrieving the socket used by a filter chain. Added methods Curl_conn_cf_get_socket() and Curl_conn_get_socket() for convenient use of this query. - Change VTLS backend to query their sub-filters for the socket when checks during the handshake are made. HTTP/3 documentation on how https eyeballing works. TLS improvements - ALPN selection for SSL/SSL-PROXY filters in one vtls set of functions, replaces copy of logic in all tls backends. - standardized the infof logging of offered ALPNs - ALPN negotiated: have common function for all backends that sets alpn proprty and connection related things based on the negotiated protocol (or lack thereof). Scorecard with Caddy. - configure can be run with `--with-test-caddy=path` to specify which caddy to use for testing - tests/tests-httpd/scorecard.py now measures download speeds with caddy pytest improvements - adding Makfile to clean gen dir - adding nghttpx rundir creation on start - checking httpd version 2.4.55 for test_05 cases where it is needed. Skipping with message if too old. - catch exception when checking for caddy existance on system. Closes #10349
346 lines
12 KiB
Markdown
346 lines
12 KiB
Markdown
# HTTP3 (and QUIC)
|
|
|
|
## Resources
|
|
|
|
[HTTP/3 Explained](https://http3-explained.haxx.se/en/) - the online free
|
|
book describing the protocols involved.
|
|
|
|
[quicwg.org](https://quicwg.org/) - home of the official protocol drafts
|
|
|
|
## QUIC libraries
|
|
|
|
QUIC libraries we are experimenting with:
|
|
|
|
[ngtcp2](https://github.com/ngtcp2/ngtcp2)
|
|
|
|
[quiche](https://github.com/cloudflare/quiche)
|
|
|
|
[msh3](https://github.com/nibanks/msh3) (with [msquic](https://github.com/microsoft/msquic))
|
|
|
|
## Experimental
|
|
|
|
HTTP/3 and QUIC support in curl is considered **EXPERIMENTAL** until further
|
|
notice. It needs to be enabled at build-time.
|
|
|
|
Further development and tweaking of the HTTP/3 support in curl will happen in
|
|
the master branch using pull-requests, just like ordinary changes.
|
|
|
|
To fix before we remove the experimental label:
|
|
|
|
- working multiplexing and GTFO handling
|
|
- fallback or another flexible way to go (back to) h1/h2 if h3 fails
|
|
- enough test cases to verify basic HTTP/3 functionality
|
|
- no "important" bugs left on HTTP/3
|
|
- it's fine to "leave" individual backends as experimental if necessary
|
|
|
|
# ngtcp2 version
|
|
|
|
## Build with OpenSSL
|
|
|
|
Build (patched) OpenSSL
|
|
|
|
% git clone --depth 1 -b openssl-3.0.7+quic https://github.com/quictls/openssl
|
|
% cd openssl
|
|
% ./config enable-tls1_3 --prefix=<somewhere1>
|
|
% make
|
|
% make install
|
|
|
|
Build nghttp3
|
|
|
|
% cd ..
|
|
% git clone https://github.com/ngtcp2/nghttp3
|
|
% cd nghttp3
|
|
% autoreconf -fi
|
|
% ./configure --prefix=<somewhere2> --enable-lib-only
|
|
% make
|
|
% make install
|
|
|
|
Build ngtcp2
|
|
|
|
% cd ..
|
|
% git clone https://github.com/ngtcp2/ngtcp2
|
|
% cd ngtcp2
|
|
% autoreconf -fi
|
|
% ./configure PKG_CONFIG_PATH=<somewhere1>/lib/pkgconfig:<somewhere2>/lib/pkgconfig LDFLAGS="-Wl,-rpath,<somewhere1>/lib" --prefix=<somewhere3> --enable-lib-only
|
|
% make
|
|
% make install
|
|
|
|
Build curl
|
|
|
|
% cd ..
|
|
% git clone https://github.com/curl/curl
|
|
% cd curl
|
|
% autoreconf -fi
|
|
% LDFLAGS="-Wl,-rpath,<somewhere1>/lib" ./configure --with-openssl=<somewhere1> --with-nghttp3=<somewhere2> --with-ngtcp2=<somewhere3>
|
|
% make
|
|
% make install
|
|
|
|
For OpenSSL 3.0.0 or later builds on Linux for x86_64 architecture, substitute all occurrences of "/lib" with "/lib64"
|
|
|
|
## Build with GnuTLS
|
|
|
|
Build GnuTLS
|
|
|
|
% git clone --depth 1 https://gitlab.com/gnutls/gnutls.git
|
|
% cd gnutls
|
|
% ./bootstrap
|
|
% ./configure --prefix=<somewhere1>
|
|
% make
|
|
% make install
|
|
|
|
Build nghttp3
|
|
|
|
% cd ..
|
|
% git clone https://github.com/ngtcp2/nghttp3
|
|
% cd nghttp3
|
|
% autoreconf -fi
|
|
% ./configure --prefix=<somewhere2> --enable-lib-only
|
|
% make
|
|
% make install
|
|
|
|
Build ngtcp2
|
|
|
|
% cd ..
|
|
% git clone https://github.com/ngtcp2/ngtcp2
|
|
% cd ngtcp2
|
|
% autoreconf -fi
|
|
% ./configure PKG_CONFIG_PATH=<somewhere1>/lib/pkgconfig:<somewhere2>/lib/pkgconfig LDFLAGS="-Wl,-rpath,<somewhere1>/lib" --prefix=<somewhere3> --enable-lib-only --with-gnutls
|
|
% make
|
|
% make install
|
|
|
|
Build curl
|
|
|
|
% cd ..
|
|
% git clone https://github.com/curl/curl
|
|
% cd curl
|
|
% autoreconf -fi
|
|
% ./configure --with-gnutls=<somewhere1> --with-nghttp3=<somewhere2> --with-ngtcp2=<somewhere3>
|
|
% make
|
|
% make install
|
|
|
|
## Build with wolfSSL
|
|
|
|
Build wolfSSL
|
|
|
|
% git clone https://github.com/wolfSSL/wolfssl.git
|
|
% cd wolfssl
|
|
% autoreconf -fi
|
|
% ./configure --prefix=<somewhere1> --enable-quic --enable-session-ticket --enable-earlydata --enable-psk --enable-harden --enable-altcertchains
|
|
% make
|
|
% make install
|
|
|
|
Build nghttp3
|
|
|
|
% cd ..
|
|
% git clone https://github.com/ngtcp2/nghttp3
|
|
% cd nghttp3
|
|
% autoreconf -fi
|
|
% ./configure --prefix=<somewhere2> --enable-lib-only
|
|
% make
|
|
% make install
|
|
|
|
Build ngtcp2
|
|
|
|
% cd ..
|
|
% git clone https://github.com/ngtcp2/ngtcp2
|
|
% cd ngtcp2
|
|
% autoreconf -fi
|
|
% ./configure PKG_CONFIG_PATH=<somewhere1>/lib/pkgconfig:<somewhere2>/lib/pkgconfig LDFLAGS="-Wl,-rpath,<somewhere1>/lib" --prefix=<somewhere3> --enable-lib-only --with-wolfssl
|
|
% make
|
|
% make install
|
|
|
|
Build curl
|
|
|
|
% cd ..
|
|
% git clone https://github.com/curl/curl
|
|
% cd curl
|
|
% autoreconf -fi
|
|
% ./configure --with-wolfssl=<somewhere1> --with-nghttp3=<somewhere2> --with-ngtcp2=<somewhere3>
|
|
% make
|
|
% make install
|
|
|
|
# quiche version
|
|
|
|
## build
|
|
|
|
Build quiche and BoringSSL:
|
|
|
|
% git clone --recursive https://github.com/cloudflare/quiche
|
|
% cd quiche
|
|
% cargo build --package quiche --release --features ffi,pkg-config-meta,qlog
|
|
% mkdir quiche/deps/boringssl/src/lib
|
|
% ln -vnf $(find target/release -name libcrypto.a -o -name libssl.a) quiche/deps/boringssl/src/lib/
|
|
|
|
Build curl:
|
|
|
|
% cd ..
|
|
% git clone https://github.com/curl/curl
|
|
% cd curl
|
|
% autoreconf -fi
|
|
% ./configure LDFLAGS="-Wl,-rpath,$PWD/../quiche/target/release" --with-openssl=$PWD/../quiche/quiche/deps/boringssl/src --with-quiche=$PWD/../quiche/target/release
|
|
% make
|
|
% make install
|
|
|
|
If `make install` results in `Permission denied` error, you will need to prepend it with `sudo`.
|
|
|
|
# msh3 (msquic) version
|
|
|
|
## Build Linux (with quictls fork of OpenSSL)
|
|
|
|
Build msh3:
|
|
|
|
% git clone -b v0.6.0 --depth 1 --recursive https://github.com/nibanks/msh3
|
|
% cd msh3 && mkdir build && cd build
|
|
% cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
|
|
% cmake --build .
|
|
% cmake --install .
|
|
|
|
Build curl:
|
|
|
|
% git clone https://github.com/curl/curl
|
|
% cd curl
|
|
% autoreconf -fi
|
|
% ./configure LDFLAGS="-Wl,-rpath,/usr/local/lib" --with-msh3=/usr/local --with-openssl
|
|
% make
|
|
% make install
|
|
|
|
Run from `/usr/local/bin/curl`.
|
|
|
|
## Build Windows
|
|
|
|
Build msh3:
|
|
|
|
% git clone -b v0.5.0 --depth 1 --recursive https://github.com/nibanks/msh3
|
|
% cd msh3 && mkdir build && cd build
|
|
% cmake -G 'Visual Studio 17 2022' -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
|
|
% cmake --build . --config Release
|
|
% cmake --install . --config Release
|
|
|
|
**Note** - On Windows, Schannel will be used for TLS support by default. If
|
|
you with to use (the quictls fork of) OpenSSL, specify the `-DQUIC_TLS=openssl`
|
|
option to the generate command above. Also note that OpenSSL brings with it an
|
|
additional set of build dependencies not specified here.
|
|
|
|
Build curl (in [Visual Studio Command prompt](../winbuild/README.md#open-a-command-prompt)):
|
|
|
|
% git clone https://github.com/curl/curl
|
|
% cd curl/winbuild
|
|
% nmake /f Makefile.vc mode=dll WITH_MSH3=dll MSH3_PATH="C:/Program Files/msh3" MACHINE=x64
|
|
|
|
**Note** - If you encounter a build error with `tool_hugehelp.c` being missing,
|
|
rename `tool_hugehelp.c.cvs` in the same directory to `tool_hugehelp.c` and
|
|
then run `nmake` again.
|
|
|
|
Run in the `C:/Program Files/msh3/lib` directory, copy `curl.exe` to that
|
|
directory, or copy `msquic.dll` and `msh3.dll` from that directory to the
|
|
`curl.exe` directory. For example:
|
|
|
|
% C:\Program Files\msh3\lib> F:\curl\builds\libcurl-vc-x64-release-dll-ipv6-sspi-schannel-msh3\bin\curl.exe --http3 https://www.google.com
|
|
|
|
# `--http3`
|
|
|
|
Use only HTTP/3:
|
|
|
|
curl --http3-only https://nghttp2.org:4433/
|
|
|
|
Use HTTP/3 with fallback to HTTP/2 or HTTP/1.1 (see "HTTPS eyeballing" below):
|
|
|
|
curl --http3 https://nghttp2.org:4433/
|
|
|
|
Upgrade via Alt-Svc:
|
|
|
|
curl --alt-svc altsvc.cache https://quic.aiortc.org/
|
|
|
|
See this [list of public HTTP/3 servers](https://bagder.github.io/HTTP3-test/)
|
|
|
|
### HTTPS eyeballing
|
|
|
|
With option `--http3` curl will attempt earlier HTTP versions as well should the connect
|
|
attempt via HTTP/3 not succeed "fast enough". This strategy is similar to IPv4/6 happy
|
|
eyeballing where the alternate address family is used in parallel after a short delay.
|
|
|
|
The IPv4/6 eyeballing has a default of 200ms and you may override that via `--happy-eyeballs-timeout-ms value`.
|
|
Since HTTP/3 is still relatively new, we decided to use this timeout also for the HTTP eyeballing - with a slight twist.
|
|
|
|
The `happy-eyeballs-timeout-ms` value is the **hard** timeout, meaning after that time expired, a TLS connection is opened in addition to negotiate HTTP/2 or HTTP/1.1. At half of that value - currently - is the **soft** timeout. The soft timeout fires, when there has been **no data at all** seen from the server on the HTTP/3 connection.
|
|
|
|
So, without you specifying anything, the hard timeout is 200ms and the soft is 100ms:
|
|
|
|
* Ideally, the whole QUIC handshake happens and curl has a HTTP/3 connection in less than 100ms.
|
|
* When QUIC is not supported (or UDP does not work for this network path), no reply is seen and the HTTP/2 TLS+TCP connection starts 100ms later.
|
|
* In the worst case, UDP replies start before 100ms, but drag on. This will start the TLS+TCP connection after 200ms.
|
|
* When the QUIC handshake fails, the TLS+TCP connection is attempted right away. For example, when the QUIC server presents the wrong certificate.
|
|
|
|
The whole transfer only fails, when **both** QUIC and TLS+TCP fail to handshake or time out.
|
|
|
|
Note that all this happens in addition to IP version happy eyeballing. If the name resolution for the server gives more than one IP address, curl will try all those until one succeeds - just as with all other protocols. And if those IP addresses contain both IPv6 and IPv4, those attempts will happen, delayed, in parallel (the actual eyeballing).
|
|
|
|
## Known Bugs
|
|
|
|
Check out the [list of known HTTP3 bugs](https://curl.se/docs/knownbugs.html#HTTP3).
|
|
|
|
# HTTP/3 Test server
|
|
|
|
This is not advice on how to run anything in production. This is for
|
|
development and experimenting.
|
|
|
|
## Prerequisite(s)
|
|
|
|
An existing local HTTP/1.1 server that hosts files. Preferably also a few huge
|
|
ones. You can easily create huge local files like `truncate -s=8G 8GB` - they
|
|
are huge but do not occupy that much space on disk since they are just big
|
|
holes.
|
|
|
|
In my Debian setup I just installed **apache2**. It runs on port 80 and has a
|
|
document root in `/var/www/html`. I can get the 8GB file from it with `curl
|
|
localhost/8GB -o dev/null`
|
|
|
|
In this description we setup and run an HTTP/3 reverse-proxy in front of the
|
|
HTTP/1 server.
|
|
|
|
## Setup
|
|
|
|
You can select either or both of these server solutions.
|
|
|
|
### nghttpx
|
|
|
|
Get, build and install **quictls**, **nghttp3** and **ngtcp2** as described
|
|
above.
|
|
|
|
Get, build and install **nghttp2**:
|
|
|
|
git clone https://github.com/nghttp2/nghttp2.git
|
|
cd nghttp2
|
|
autoreconf -fi
|
|
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/home/daniel/build-quictls/lib/pkgconfig:/home/daniel/build-nghttp3/lib/pkgconfig:/home/daniel/build-ngtcp2/lib/pkgconfig LDFLAGS=-L/home/daniel/build-quictls/lib CFLAGS=-I/home/daniel/build-quictls/include ./configure --enable-maintainer-mode --prefix=/home/daniel/build-nghttp2 --disable-shared --enable-app --enable-http3 --without-jemalloc --without-libxml2 --without-systemd
|
|
make && make install
|
|
|
|
Run the local h3 server on port 9443, make it proxy all traffic through to
|
|
HTTP/1 on localhost port 80. For local toying, we can just use the test cert
|
|
that exists in curl's test dir.
|
|
|
|
CERT=$CURLSRC/tests/stunnel.pem
|
|
$HOME/bin/nghttpx $CERT $CERT --backend=localhost,80 \
|
|
--frontend="localhost,9443;quic"
|
|
|
|
### Caddy
|
|
|
|
[Install Caddy](https://caddyserver.com/docs/install). For easiest use, the binary
|
|
should be either in your PATH or your current directory.
|
|
|
|
Create a `Caddyfile` with the following content:
|
|
~~~
|
|
localhost:7443 {
|
|
respond "Hello, world! You're using {http.request.proto}"
|
|
}
|
|
~~~
|
|
|
|
Then run Caddy:
|
|
|
|
./caddy start
|
|
|
|
Making requests to `https://localhost:7443` should tell you which protocol is being used.
|
|
|
|
You can change the hard-coded response to something more useful by replacing `respond`
|
|
with `reverse_proxy` or `file_server`, for example: `reverse_proxy localhost:80`
|