diff options
author | Daniel Stenberg <daniel@haxx.se> | 2019-07-21 23:48:58 +0200 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2019-07-21 23:49:03 +0200 |
commit | 3af0e76d1e71995b7790c74e79b76af86ee7c681 (patch) | |
tree | b80190acaf03d83f5f408cc6da3ec1a9f831d8d3 | |
parent | 7644abf8e8101910ed86ab2869b7cc4031b27720 (diff) | |
download | curl-3af0e76d1e71995b7790c74e79b76af86ee7c681.tar.gz |
HTTP3: initial (experimental) support
USe configure --with-ngtcp2 or --with-quiche
Using either option will enable a HTTP3 build.
Co-authored-by: Alessandro Ghedini <alessandro@ghedini.me>
Closes #3500
33 files changed, 2520 insertions, 23 deletions
diff --git a/configure.ac b/configure.ac index 7dd148085..1ddc3d601 100755 --- a/configure.ac +++ b/configure.ac @@ -3338,6 +3338,163 @@ if test X"$want_h2" != Xno; then fi dnl ********************************************************************** +dnl Check for ngtcp2 (QUIC) +dnl ********************************************************************** + +OPT_TCP2="yes" +curl_h3_msg="disabled (--with-ngtcp2, --with-quiche)" + +if test "x$disable_http" = "xyes"; then + # without HTTP, ngtcp2 is no use + OPT_TCP2="no" +fi + +AC_ARG_WITH(ngtcp2, +AC_HELP_STRING([--with-ngtcp2=PATH],[Enable ngtcp2 usage]) +AC_HELP_STRING([--without-ngtcp2],[Disable ngtcp2 usage]), + [OPT_TCP2=$withval]) +case "$OPT_TCP2" in + no) + dnl --without-ngtcp2 option used + want_tcp2="no" + ;; + yes) + dnl --with-ngtcp2 option used without path + want_tcp2="default" + want_tcp2_path="" + ;; + *) + dnl --with-ngtcp2 option used with path + want_tcp2="yes" + want_tcp2_path="$withval/lib/pkgconfig" + ;; +esac + +curl_tcp2_msg="disabled (--with-ngtcp2)" +if test X"$want_tcp2" != Xno; then + dnl backup the pre-ngtcp2 variables + CLEANLDFLAGS="$LDFLAGS" + CLEANCPPFLAGS="$CPPFLAGS" + CLEANLIBS="$LIBS" + + CURL_CHECK_PKGCONFIG(libngtcp2, $want_tcp2_path) + + if test "$PKGCONFIG" != "no" ; then + LIB_TCP2=`CURL_EXPORT_PCDIR([$want_tcp2_path]) + $PKGCONFIG --libs-only-l libngtcp2` + AC_MSG_NOTICE([-l is $LIB_TCP2]) + + CPP_TCP2=`CURL_EXPORT_PCDIR([$want_tcp2_path]) dnl + $PKGCONFIG --cflags-only-I libngtcp2` + AC_MSG_NOTICE([-I is $CPP_TCP2]) + + LD_TCP2=`CURL_EXPORT_PCDIR([$want_tcp2_path]) + $PKGCONFIG --libs-only-L libngtcp2` + AC_MSG_NOTICE([-L is $LD_TCP2]) + + LDFLAGS="$LDFLAGS $LD_TCP2" + CPPFLAGS="$CPPFLAGS $CPP_TCP2" + LIBS="$LIB_TCP2 $LIBS" + + if test "x$cross_compiling" != "xyes"; then + DIR_TCP2=`echo $LD_TCP2 | $SED -e 's/-L//'` + fi + AC_CHECK_LIB(ngtcp2, ngtcp2_conn_client_new, + [ + AC_CHECK_HEADERS(ngtcp2/ngtcp2.h, + curl_h3_msg="enabled (ngtcp2)" + NGTCP2_ENABLED=1 + AC_DEFINE(USE_NGTCP2, 1, [if ngtcp2 is in use]) + AC_SUBST(USE_NGTCP2, [1]) + CURL_LIBRARY_PATH="$CURL_LIBRARY_PATH:$DIR_TCP2" + export CURL_LIBRARY_PATH + AC_MSG_NOTICE([Added $DIR_TCP2 to CURL_LIBRARY_PATH]) + experimental="$experimental HTTP3" + ) + ], + dnl not found, revert back to clean variables + LDFLAGS=$CLEANLDFLAGS + CPPFLAGS=$CLEANCPPFLAGS + LIBS=$CLEANLIBS + ) + + else + dnl no ngtcp2 pkg-config found, deal with it + if test X"$want_tcp2" != Xdefault; then + dnl To avoid link errors, we do not allow --with-ngtcp2 without + dnl a pkgconfig file + AC_MSG_ERROR([--with-ngtcp2 was specified but could not find ngtcp2 pkg-config file.]) + fi + fi + +fi + +dnl ********************************************************************** +dnl Check for quiche (QUIC) +dnl ********************************************************************** + +OPT_QUICHE="yes" + +if test "x$disable_http" = "xyes" -o "x$USE_NGTCP" = "x1"; then + # without HTTP or with ngtcp2, quiche is no use + OPT_QUICHE="no" +fi + +AC_ARG_WITH(quiche, +AC_HELP_STRING([--with-quiche=PATH],[Enable quiche usage]) +AC_HELP_STRING([--without-quiche],[Disable quiche usage]), + [OPT_QUICHE=$withval]) +case "$OPT_QUICHE" in + *) + dnl --with-quiche option used without path + want_quiche="default" + want_quiche_path="" + ;; + no) + dnl --without-quiche option used + want_quiche="no" + ;; +esac + +if test X"$want_quiche" != Xno; then + dnl backup the pre-quiche variables + CLEANLDFLAGS="$LDFLAGS" + CLEANCPPFLAGS="$CPPFLAGS" + CLEANLIBS="$LIBS" + + LIB_QUICHE="-l:libquiche.a -ldl -lpthread" + CPP_QUICHE="-I$OPT_QUICHE/include" + LD_QUICHE="-L$OPT_QUICHE/target/release" + + LDFLAGS="$LDFLAGS $LD_QUICHE" + CPPFLAGS="$CPPFLAGS $CPP_QUICHE" + LIBS="$LIB_QUICHE $LIBS" + + if test "x$cross_compiling" != "xyes"; then + DIR_QUICHE=`echo $LD_QUICHE | $SED -e 's/-L//'` + fi + AC_CHECK_LIB(quiche, quiche_connect, + [ + AC_CHECK_HEADERS(quiche.h, + experimental="$experimental HTTP3" + AC_MSG_NOTICE([HTTP3 support is experimental]) + curl_h3_msg="enabled (quiche)" + QUICHE_ENABLED=1 + AC_DEFINE(USE_QUICHE, 1, [if quiche is in use]) + AC_SUBST(USE_QUICHE, [1]) + CURL_LIBRARY_PATH="$CURL_LIBRARY_PATH:$DIR_QUICHE" + export CURL_LIBRARY_PATH + AC_MSG_NOTICE([Added $DIR_QUICHE to CURL_LIBRARY_PATH]), + ) + ], + dnl not found, revert back to clean variables + LDFLAGS=$CLEANLDFLAGS + CPPFLAGS=$CLEANCPPFLAGS + LIBS=$CLEANLIBS + ) +fi + +dnl ********************************************************************** dnl Check for zsh completion path dnl ********************************************************************** @@ -4162,7 +4319,6 @@ AC_HELP_STRING([--disable-alt-svc],[Disable alt-svc support]), *) AC_MSG_RESULT(yes) curl_altsvc_msg="enabled"; enable_altsvc="yes" - experimental="alt-svc" ;; esac ], AC_MSG_RESULT(no) @@ -4170,7 +4326,7 @@ AC_HELP_STRING([--disable-alt-svc],[Disable alt-svc support]), if test "$enable_altsvc" = "yes"; then AC_DEFINE(USE_ALTSVC, 1, [to enable alt-svc]) - experimental="alt-svc" + experimental="$experimental alt-svc" fi dnl ************************************************************ @@ -4281,6 +4437,10 @@ if test "x$USE_NGHTTP2" = "x1"; then SUPPORT_FEATURES="$SUPPORT_FEATURES HTTP2" fi +if test "x$USE_NGTCP2" = "x1" -o "x$USE_QUICHE" = "x1"; then + SUPPORT_FEATURES="$SUPPORT_FEATURES HTTP3" +fi + if test "x$CURL_WITH_MULTI_SSL" = "x1"; then SUPPORT_FEATURES="$SUPPORT_FEATURES MultiSSL" fi @@ -4472,11 +4632,12 @@ AC_MSG_NOTICE([Configured to build curl/libcurl: PSL: ${curl_psl_msg} Alt-svc: ${curl_altsvc_msg} HTTP2: ${curl_h2_msg} + HTTP3: ${curl_h3_msg} Protocols: ${SUPPORT_PROTOCOLS} Features: ${SUPPORT_FEATURES} ]) if test -n "$experimental"; then cat >&2 << _EOF - WARNING: $experimental is enabled but marked EXPERIMENTAL. Use with caution! + WARNING: $experimental enabled but marked EXPERIMENTAL. Use with caution! _EOF fi diff --git a/docs/HTTP3.md b/docs/HTTP3.md new file mode 100644 index 000000000..c09bf0a76 --- /dev/null +++ b/docs/HTTP3.md @@ -0,0 +1,85 @@ +# HTTP3 (and QUIC) + +## Resources + +[HTTP/3 Explained](https://daniel.haxx.se/http3-explained/) - the online free +book describing the protocols involved. + +[QUIC implementation](https://github.com/curl/curl/wiki/QUIC-implementation) - +the wiki page describing the plan for how to support QUIC and HTTP/3 in curl +and libcurl. + +[quicwg.org](https://quicwg.org/) - home of the official protocol drafts + +## QUIC libraries + +QUIC libraries we're experiementing with: + +[ngtcp2](https://github.com/ngtcp2/ngtcp2) + +[quiche](https://github.com/cloudflare/quiche) + +## Experimental! + +HTTP/3 and QUIC support in curl is not yet working and this is early days. +Consider all QUIC and HTTP/3 code to be **EXPERIMENTAL** until further notice. + +curl does not have HTTP/3 support (yet). + +The bleeding edge QUIC work is done in the dedicated +[QUIC](https://github.com/curl/curl/tree/QUIC) branch, but the plan is to +merge as often as possible from there to master. All QUIC related code will +remain being build-time conditionally enabled. + +# ngtcp2 version + +## Build + +1. clone ngtcp2 from git (the draft-17 branch) +2. build and install ngtcp2's custom OpenSSL version (the quic-draft-17 branch) +3. build and install ngtcp2 according to its instructions +4. configure curl with ngtcp2 support: `./configure --with-ngtcp2=<install prefix>` +5. build curl "normally" + +## Running + +Make sure the custom OpenSSL library is the one used at run-time, as otherwise +you'll just get ld.so linker errors. + +## Invoke from command line + + curl --http3-direct https://nghttp2.org:8443/ + +# quiche version + +## build + +Build BoringSSL (it needs to be built manually so it can be reused with curl): + + % mkdir -p quiche/deps/boringssl/build + % cd quiche/deps/boringssl/build + % cmake -DCMAKE_POSITION_INDEPENDENT_CODE=on .. + % make -j`nproc` + % cd .. + % mkdir .openssl/lib -p + % cp build/crypto/libcrypto.a build/ssl/libssl.a .openssl/lib + % ln -s $PWD/include .openssl + +Build quiche: + + % cd ../.. + % QUICHE_BSSL_PATH=$PWD/deps/boringssl cargo build --release + +Clone and build curl: + + % cd .. + % git clone https://github.com/curl/curl + % ./buildconf + % ./configure --with-ssl=$PWD/../quiche/deps/boringssl/.openssl --with-quiche=$PWD/../quiche --enable-debug + % make -j`nproc` + +## Running + +Make an HTTP/1.1 request to a QUIC server: + + % src/curl --http3-direct https://cloudflare-quic.com/ diff --git a/docs/Makefile.am b/docs/Makefile.am index ed6a212e0..a29c059a1 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -60,6 +60,7 @@ EXTRA_DIST = \ HISTORY.md \ HTTP-COOKIES.md \ HTTP2.md \ + HTTP3.md \ INSTALL \ INSTALL.cmake \ INSTALL.md \ diff --git a/docs/cmdline-opts/Makefile.inc b/docs/cmdline-opts/Makefile.inc index 886fa6caf..d50a8bb26 100644 --- a/docs/cmdline-opts/Makefile.inc +++ b/docs/cmdline-opts/Makefile.inc @@ -65,6 +65,7 @@ DPAGES = \ http1.0.d \ http1.1.d http2.d \ http2-prior-knowledge.d \ + http3-direct.d \ ignore-content-length.d \ include.d \ insecure.d \ diff --git a/docs/cmdline-opts/http3-direct.d b/docs/cmdline-opts/http3-direct.d new file mode 100644 index 000000000..fb8c8cf28 --- /dev/null +++ b/docs/cmdline-opts/http3-direct.d @@ -0,0 +1,16 @@ +Long: http3-direct +Tags: Versions +Protocols: HTTP +Added: 7.66.0 +Mutexed: http1.1 http1.0 http2 http2-prior-knowledge +Requires: HTTP/3 +Help: Use HTTP v3 +--- + +WARNING: this option is experiemental. Do not use in production. + +Tells curl to use HTTP version 3 directly to the host and port number used in +the URL. A normal HTTP/3 transaction will be done to a host and then get +redirected via Alt-SVc, but this option allows a user to circumvent that when +you know that the target speaks HTTP/3 on the given host and port. + diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3 index 1f18a3494..cb5c418fb 100644 --- a/docs/libcurl/curl_easy_setopt.3 +++ b/docs/libcurl/curl_easy_setopt.3 @@ -321,6 +321,8 @@ Enable and configure Alt-Svc: treatment. See \fICURLOPT_ALTSVC_CTRL(3)\fP Do an HTTP GET request. See \fICURLOPT_HTTPGET(3)\fP .IP CURLOPT_REQUEST_TARGET Set the request target. \fICURLOPT_REQUEST_TARGET(3)\fP +.IP CURLOPT_H3 +Specify HTTP/3 behavior. \fICURLOPT_H3(3)\fP .IP CURLOPT_HTTP_VERSION HTTP version to use. \fICURLOPT_HTTP_VERSION(3)\fP .IP CURLOPT_HTTP09_ALLOWED diff --git a/docs/libcurl/opts/CURLOPT_H3.3 b/docs/libcurl/opts/CURLOPT_H3.3 new file mode 100644 index 000000000..be521b6bf --- /dev/null +++ b/docs/libcurl/opts/CURLOPT_H3.3 @@ -0,0 +1,61 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. +.\" * +.\" * This software is licensed as described in the file COPYING, which +.\" * you should have received as part of this distribution. The terms +.\" * are also available at https://curl.haxx.se/docs/copyright.html. +.\" * +.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell +.\" * copies of the Software, and permit persons to whom the Software is +.\" * furnished to do so, under the terms of the COPYING file. +.\" * +.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +.\" * KIND, either express or implied. +.\" * +.\" ************************************************************************** +.\" +.TH CURLOPT_H3 3 "27 Nov 2018" "libcurl 7.66.0" "curl_easy_setopt options" +.SH NAME +CURLOPT_H3 \- specify HTTP/3 protocol behavior +.SH SYNOPSIS +#include <curl/curl.h> + +CURLcode curl_easy_setopt(CURL *handle, CURLOPT_H3, long bitmask); +.SH EXPERIMENTAL +Warning: this feature is early code and is marked as experimental. It can only +be enabled by explicitly invoking configure with \fB--with-quiche\fP or +\fB--with-ngtcp2\fP. You are advised to not ship this feature used in +production before the experimental label is removed. +.SH DESCRIPTION +This function accepts a long \fIbitmask\fP with a set of flags set that +controls the HTTP/3 behavior for this transfer. +.IP "CURLH3_DIRECT" +If this bit is set in \fIbitmask\fP, the host name and port number given in +the URL will be used to connect to directly with QUIC and the port number then +being a UDP port number. +.SH DEFAULT +0 +.SH PROTOCOLS +HTTPS +.SH EXAMPLE +.nf +CURL *curl = curl_easy_init(); +if(curl) { + CURLcode ret; + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/"); + curl_easy_setopt(curl, CURLOPT_H3, (long)CURLH3_DIRECT); + ret = curl_easy_perform(curl); +} +.fi +.SH AVAILABILITY +Added in 7.66.0 +.SH RETURN VALUE +Returns CURLE_OK if supported, an error otherwise. +.SH "SEE ALSO" +.BR CURLOPT_HTTP_VERSION "(3), " diff --git a/docs/libcurl/opts/Makefile.inc b/docs/libcurl/opts/Makefile.inc index c8e15a5ed..5460b2a57 100644 --- a/docs/libcurl/opts/Makefile.inc +++ b/docs/libcurl/opts/Makefile.inc @@ -150,6 +150,7 @@ man_MANS = \ CURLOPT_FTP_USE_EPSV.3 \ CURLOPT_FTP_USE_PRET.3 \ CURLOPT_GSSAPI_DELEGATION.3 \ + CURLOPT_H3.3 \ CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS.3 \ CURLOPT_HAPROXYPROTOCOL.3 \ CURLOPT_HEADER.3 \ diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index 16fb5622c..1b452d245 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -209,6 +209,7 @@ CURLFTP_CREATE_DIR_RETRY 7.19.4 CURLGSSAPI_DELEGATION_FLAG 7.22.0 CURLGSSAPI_DELEGATION_NONE 7.22.0 CURLGSSAPI_DELEGATION_POLICY_FLAG 7.22.0 +CURLH3_DIRECT 7.66.0 CURLHEADER_SEPARATE 7.37.0 CURLHEADER_UNIFIED 7.37.0 CURLINFO_ACTIVESOCKET 7.45.0 @@ -424,6 +425,7 @@ CURLOPT_FTP_USE_EPRT 7.10.5 CURLOPT_FTP_USE_EPSV 7.9.2 CURLOPT_FTP_USE_PRET 7.20.0 CURLOPT_GSSAPI_DELEGATION 7.22.0 +CURLOPT_H3 7.66.0 CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS 7.59.0 CURLOPT_HAPROXYPROTOCOL 7.60.0 CURLOPT_HEADER 7.1 @@ -925,6 +927,7 @@ CURL_VERSION_DEBUG 7.10.6 CURL_VERSION_GSSAPI 7.38.0 CURL_VERSION_GSSNEGOTIATE 7.10.6 7.38.0 CURL_VERSION_HTTP2 7.33.0 +CURL_VERSION_HTTP3 7.66.0 CURL_VERSION_HTTPS_PROXY 7.52.0 CURL_VERSION_IDN 7.12.0 CURL_VERSION_IPV6 7.10 diff --git a/include/curl/curl.h b/include/curl/curl.h index 25f095a69..215b3e9e1 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -923,6 +923,10 @@ typedef enum { #define CURLPROTO_SMBS (1<<27) #define CURLPROTO_ALL (~0) /* enable everything */ +/* bitmask defines for CURLOPT_H3 */ +#define CURLH3_DIRECT (1<<0) /* go QUIC + HTTP/3 directly to the given host + + port */ + /* long may be 32 or 64 bits, but we should never depend on anything else but 32 */ #define CURLOPTTYPE_LONG 0 @@ -1925,6 +1929,9 @@ typedef enum { /* maximum age of a connection to consider it for reuse (in seconds) */ CINIT(MAXAGE_CONN, LONG, 288), + /* Bitmask to control HTTP/3 behavior. See CURLH3_* */ + CINIT(H3, LONG, 289), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; @@ -2793,6 +2800,7 @@ typedef struct { #define CURL_VERSION_MULTI_SSL (1<<22) /* Multiple SSL backends available */ #define CURL_VERSION_BROTLI (1<<23) /* Brotli features are present. */ #define CURL_VERSION_ALTSVC (1<<24) /* Alt-Svc handling built-in */ +#define CURL_VERSION_HTTP3 (1<<25) /* HTTP3 support built-in */ /* * NAME curl_version_info() diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 37f702681..5e59f39b3 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -37,6 +37,10 @@ LIB_VTLS_HFILES = vtls/openssl.h vtls/vtls.h vtls/gtls.h \ vtls/wolfssl.h vtls/schannel.h vtls/sectransp.h vtls/gskit.h \ vtls/mbedtls.h vtls/mesalink.h +LIB_VQUIC_CFILES = vquic/ngtcp2.c vquic/ngtcp2-crypto.c vquic/quiche.c + +LIB_VQUIC_HFILES = vquic/ngtcp2.h vquic/ngtcp2-crypto.h vquic/quiche.h + LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c \ cookie.c http.c sendf.c ftp.c url.c dict.c if2ip.c speedcheck.c \ ldap.c version.c getenv.c escape.c mprintf.c telnet.c netrc.c \ @@ -55,7 +59,7 @@ LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c \ curl_multibyte.c hostcheck.c conncache.c dotdot.c \ x509asn1.c http2.c smb.c curl_endian.c curl_des.c system_win32.c \ mime.c sha256.c setopt.c curl_path.c curl_ctype.c curl_range.c psl.c \ - doh.c urlapi.c curl_get_line.c altsvc.c + doh.c urlapi.c curl_get_line.c altsvc.c quic.c LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \ formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h if2ip.h \ @@ -76,9 +80,11 @@ LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \ x509asn1.h http2.h sigpipe.h smb.h curl_endian.h curl_des.h \ curl_printf.h system_win32.h rand.h mime.h curl_sha256.h setopt.h \ curl_path.h curl_ctype.h curl_range.h psl.h doh.h urlapi-int.h \ - curl_get_line.h altsvc.h + curl_get_line.h altsvc.h quic.h LIB_RCFILES = libcurl.rc -CSOURCES = $(LIB_CFILES) $(LIB_VAUTH_CFILES) $(LIB_VTLS_CFILES) -HHEADERS = $(LIB_HFILES) $(LIB_VAUTH_HFILES) $(LIB_VTLS_HFILES) +CSOURCES = $(LIB_CFILES) $(LIB_VAUTH_CFILES) $(LIB_VTLS_CFILES) \ + $(LIB_VQUIC_CFILES) +HHEADERS = $(LIB_HFILES) $(LIB_VAUTH_HFILES) $(LIB_VTLS_HFILES) \ + $(LIB_VQUIC_HFILES) diff --git a/lib/asyn-thread.c b/lib/asyn-thread.c index 55e0811c5..5f33c9aff 100644 --- a/lib/asyn-thread.c +++ b/lib/asyn-thread.c @@ -706,7 +706,8 @@ Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn, memset(&hints, 0, sizeof(hints)); hints.ai_family = pf; - hints.ai_socktype = conn->socktype; + hints.ai_socktype = (conn->transport == TRNSPRT_TCP)? + SOCK_STREAM : SOCK_DGRAM; msnprintf(sbuf, sizeof(sbuf), "%d", port); diff --git a/lib/connect.c b/lib/connect.c index 4a1f2c640..59084f251 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -75,6 +75,7 @@ #include "conncache.h" #include "multihandle.h" #include "system_win32.h" +#include "quic.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -683,8 +684,8 @@ UNITTEST bool getaddressinfo(struct sockaddr *sa, char *addr, connection */ void Curl_updateconninfo(struct connectdata *conn, curl_socket_t sockfd) { - if(conn->socktype == SOCK_DGRAM) - /* there's no connection! */ + if(conn->transport != TRNSPRT_TCP) + /* there's no TCP connection! */ return; #if defined(HAVE_GETPEERNAME) || defined(HAVE_GETSOCKNAME) @@ -1099,8 +1100,8 @@ static CURLcode singleipconnect(struct connectdata *conn, if(conn->num_addr > 1) Curl_expire(data, conn->timeoutms_per_addr, EXPIRE_DNS_PER_NAME); - /* Connect TCP sockets, bind UDP */ - if(!isconnected && (conn->socktype == SOCK_STREAM)) { + /* Connect TCP and QUIC sockets */ + if(!isconnected && (conn->transport != TRNSPRT_UDP)) { if(conn->bits.tcp_fastopen) { #if defined(CONNECT_DATA_IDEMPOTENT) /* Darwin */ # if defined(HAVE_BUILTIN_AVAILABLE) @@ -1152,6 +1153,15 @@ static CURLcode singleipconnect(struct connectdata *conn, return CURLE_OK; } +#ifdef ENABLE_QUIC + if(!isconnected && (conn->transport == TRNSPRT_QUIC)) { + result = Curl_quic_connect(conn, sockfd, &addr.sa_addr, addr.addrlen); + if(result) + return result; + rc = 0; /* connect success */ + } +#endif + if(-1 == rc) { switch(error) { case EINPROGRESS: @@ -1386,8 +1396,9 @@ CURLcode Curl_socket(struct connectdata *conn, */ addr->family = ai->ai_family; - addr->socktype = conn->socktype; - addr->protocol = conn->socktype == SOCK_DGRAM?IPPROTO_UDP:ai->ai_protocol; + addr->socktype = (conn->transport == TRNSPRT_TCP) ? SOCK_STREAM : SOCK_DGRAM; + addr->protocol = conn->transport != TRNSPRT_TCP ? IPPROTO_UDP : + ai->ai_protocol; addr->addrlen = ai->ai_addrlen; if(addr->addrlen > sizeof(struct Curl_sockaddr_storage)) diff --git a/lib/curl_setup.h b/lib/curl_setup.h index 27414a540..afd5441b0 100644 --- a/lib/curl_setup.h +++ b/lib/curl_setup.h @@ -827,4 +827,8 @@ int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, #define UNITTEST static #endif +#if defined(USE_NGTCP2) || defined(USE_QUICHE) +#define ENABLE_QUIC +#endif + #endif /* HEADER_CURL_SETUP_H */ diff --git a/lib/hostip6.c b/lib/hostip6.c index 5511f1aab..e0e0c58df 100644 --- a/lib/hostip6.c +++ b/lib/hostip6.c @@ -165,7 +165,8 @@ Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, memset(&hints, 0, sizeof(hints)); hints.ai_family = pf; - hints.ai_socktype = conn->socktype; + hints.ai_socktype = (conn->transport == TRNSPRT_TCP) ? + SOCK_STREAM : SOCK_DGRAM; #ifndef USE_RESOLVE_ON_IPS /* diff --git a/lib/http.c b/lib/http.c index 9fbd7201e..36e94f762 100644 --- a/lib/http.c +++ b/lib/http.c @@ -171,10 +171,22 @@ static CURLcode http_setup_conn(struct connectdata *conn) Curl_mime_initpart(&http->form, conn->data); data->req.protop = http; - if(!CONN_INUSE(conn)) - /* if not already multi-using, setup connection details */ - Curl_http2_setup_conn(conn); - Curl_http2_setup_req(data); + if(data->set.h3opts & CURLH3_DIRECT) { + if(conn->handler->flags & PROTOPT_SSL) + /* Only go h3-direct on HTTPS URLs. It needs a UDP socket and does the + QUIC dance. */ + conn->transport = TRNSPRT_QUIC; + else { + failf(data, "HTTP/3 requested for non-HTTPS URL"); + return CURLE_URL_MALFORMAT; + } + } + else { + if(!CONN_INUSE(conn)) + /* if not already multi-using, setup connection details */ + Curl_http2_setup_conn(conn); + Curl_http2_setup_req(data); + } return CURLE_OK; } @@ -1555,6 +1567,15 @@ static CURLcode https_connecting(struct connectdata *conn, bool *done) CURLcode result; DEBUGASSERT((conn) && (conn->handler->flags & PROTOPT_SSL)); +#ifdef ENABLE_QUIC + if(conn->transport == TRNSPRT_QUIC) { + result = Curl_quic_is_connected(conn, FIRSTSOCKET, done); + if(result) + connclose(conn, "Failed HTTPS connection (over QUIC)"); + return result; + } +#endif + /* perform SSL initialization for this socket */ result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, done); if(result) diff --git a/lib/quic.c b/lib/quic.c new file mode 100644 index 000000000..746f00564 --- /dev/null +++ b/lib/quic.c @@ -0,0 +1,38 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef ENABLE_QUIC +#include "quic.h" +/* backend-independent QUIC functionality */ +const char *Curl_quic_backend(void) +{ +#ifdef USE_NGTCP2 + return "ngtcp2"; +#endif +#ifdef USE_QUICHE + return "quiche"; +#endif +} +#endif + diff --git a/lib/quic.h b/lib/quic.h new file mode 100644 index 000000000..9c90b2ec5 --- /dev/null +++ b/lib/quic.h @@ -0,0 +1,54 @@ +#ifndef HEADER_CURL_QUIC_H +#define HEADER_CURL_QUIC_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef ENABLE_QUIC +#ifdef USE_NGTCP2 +#include "vquic/ngtcp2.h" +#endif +#ifdef USE_QUICHE +#include "vquic/quiche.h" +#endif + +#include "urldata.h" + +/* generic */ +const char *Curl_quic_backend(void); + +/* functions provided by the specific backends */ +CURLcode Curl_quic_connect(struct connectdata *conn, + curl_socket_t sockfd, + const struct sockaddr *addr, + socklen_t addrlen); +CURLcode Curl_quic_is_connected(struct connectdata *conn, int sockindex, + bool *done); +int Curl_quic_ver(char *p, size_t len); + +#else +/* no QUIC */ +#define Curl_quic_backend() "" +#endif + +#endif /* HEADER_CURL_QUIC_H */ diff --git a/lib/setopt.c b/lib/setopt.c index 1dbf00faf..64a6b010d 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -2744,6 +2744,14 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) return result; break; #endif + case CURLOPT_H3: +#ifdef ENABLE_QUIC + arg = va_arg(param, long); + data->set.h3opts = arg; +#else + return CURLE_NOT_BUILT_IN; +#endif + break; default: /* unknown tag and its companion, just ignore: */ result = CURLE_UNKNOWN_OPTION; diff --git a/lib/tftp.c b/lib/tftp.c index 289cda282..c71e5f0aa 100644 --- a/lib/tftp.c +++ b/lib/tftp.c @@ -1376,7 +1376,7 @@ static CURLcode tftp_setup_connection(struct connectdata * conn) struct Curl_easy *data = conn->data; char *type; - conn->socktype = SOCK_DGRAM; /* UDP datagram based */ + conn->transport = TRNSPRT_UDP; /* TFTP URLs support an extension like ";mode=<typecode>" that * we'll try to get now! */ @@ -2110,7 +2110,7 @@ static CURLcode setup_connection_internals(struct connectdata *conn) { const struct Curl_handler * p; CURLcode result; - conn->socktype = SOCK_STREAM; /* most of them are TCP streams */ + conn->transport = TRNSPRT_TCP; /* most of them are TCP streams */ /* Perform setup complement if some. */ p = conn->handler; diff --git a/lib/urldata.h b/lib/urldata.h index fdc185b22..c8f01aa4f 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -129,6 +129,7 @@ typedef ssize_t (Curl_recv)(struct connectdata *conn, /* connection data */ #include "smb.h" #include "wildcard.h" #include "multihandle.h" +#include "quic.h" #ifdef HAVE_GSSAPI # ifdef HAVE_GSSGNU @@ -831,7 +832,15 @@ struct connectdata { unsigned int scope_id; /* Scope id for IPv6 */ - int socktype; /* SOCK_STREAM or SOCK_DGRAM */ + enum { + TRNSPRT_TCP = 3, + TRNSPRT_UDP = 4, + TRNSPRT_QUIC = 5 + } transport; + +#ifdef ENABLE_QUIC + struct quicsocket quic; +#endif struct hostname host; char *hostname_resolve; /* host name to resolve to address, allocated */ @@ -1672,6 +1681,7 @@ struct UserDefined { CURLU *uh; /* URL handle for the current parsed URL */ void *trailer_data; /* pointer to pass to trailer data callback */ curl_trailer_callback trailer_callback; /* trailing data callback */ + long h3opts; /* the CURLOPT_H3 bitmask */ bit is_fread_set:1; /* has read callback been set to non-NULL? */ bit is_fwrite_set:1; /* has write callback been set to non-NULL? */ bit free_referer:1; /* set TRUE if 'referer' points to a string we diff --git a/lib/version.c b/lib/version.c index 941313dfb..c1b5a1c51 100644 --- a/lib/version.c +++ b/lib/version.c @@ -27,6 +27,7 @@ #include "vtls/vtls.h" #include "http2.h" #include "ssh.h" +#include "quic.h" #include "curl_printf.h" #ifdef USE_ARES @@ -187,6 +188,11 @@ char *curl_version(void) left -= len; ptr += len; #endif +#ifdef ENABLE_QUIC + len = Curl_quic_ver(ptr, left); + left -= len; + ptr += len; +#endif #ifdef USE_LIBRTMP { char suff[2]; @@ -358,6 +364,9 @@ static curl_version_info_data version_info = { #if defined(USE_NGHTTP2) | CURL_VERSION_HTTP2 #endif +#if defined(ENABLE_QUIC) + | CURL_VERSION_HTTP3 +#endif #if defined(USE_UNIX_SOCKETS) | CURL_VERSION_UNIX_SOCKETS #endif diff --git a/lib/vquic/ngtcp2-crypto.c b/lib/vquic/ngtcp2-crypto.c new file mode 100644 index 000000000..dc060c91b --- /dev/null +++ b/lib/vquic/ngtcp2-crypto.c @@ -0,0 +1,520 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_NGTCP2 +#include <ngtcp2/ngtcp2.h> +#include <openssl/ssl.h> +#include <openssl/evp.h> +#include <openssl/kdf.h> +#include "ngtcp2-crypto.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +void Curl_qc_prf_sha256(struct Context *ctx) +{ + ctx->prf = EVP_sha256(); +} + +void Curl_qc_aead_aes_128_gcm(struct Context *ctx) +{ + ctx->aead = EVP_aes_128_gcm(); + ctx->hp = EVP_aes_128_ctr(); +} + +size_t Curl_qc_aead_nonce_length(const struct Context *ctx) +{ + return EVP_CIPHER_iv_length(ctx->aead); +} + + +int Curl_qc_negotiated_prf(struct Context *ctx, SSL *ssl) +{ + switch(SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case 0x03001301u: /* TLS_AES_128_GCM_SHA256 */ + case 0x03001303u: /* TLS_CHACHA20_POLY1305_SHA256 */ + ctx->prf = EVP_sha256(); + return 0; + case 0x03001302u: /* TLS_AES_256_GCM_SHA384 */ + ctx->prf = EVP_sha384(); + return 0; + default: + return -1; + } +} + +int Curl_qc_negotiated_aead(struct Context *ctx, SSL *ssl) +{ + switch(SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case 0x03001301u: /* TLS_AES_128_GCM_SHA256 */ + ctx->aead = EVP_aes_128_gcm(); + ctx->hp = EVP_aes_128_ctr(); + return 0; + case 0x03001302u: /* TLS_AES_256_GCM_SHA384 */ + ctx->aead = EVP_aes_256_gcm(); + ctx->hp = EVP_aes_256_ctr(); + return 0; + case 0x03001303u: /* TLS_CHACHA20_POLY1305_SHA256 */ + ctx->aead = EVP_chacha20_poly1305(); + ctx->hp = EVP_chacha20(); + return 0; + default: + return -1; + } +} + +ssize_t Curl_qc_encrypt_pn(uint8_t *dest, size_t destlen, + const uint8_t *plaintext, size_t plaintextlen, + const struct Context *ctx, + const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen) +{ + EVP_CIPHER_CTX *actx = EVP_CIPHER_CTX_new(); + size_t outlen = 0; + int len; + (void)destlen; + (void)keylen; + (void)noncelen; + + if(!actx) + return -1; + + if(EVP_EncryptInit_ex(actx, ctx->hp, NULL, key, nonce) != 1) + goto error; + + if(EVP_EncryptUpdate(actx, dest, &len, plaintext, (int)plaintextlen) != 1) + goto error; + + assert(len > 0); + + outlen = len; + + if(EVP_EncryptFinal_ex(actx, dest + outlen, &len) != 1) + goto error; + + assert(len == 0); + /* outlen += len; */ + + EVP_CIPHER_CTX_free(actx); + return outlen; + + error: + EVP_CIPHER_CTX_free(actx); + return -1; +} + +static int hkdf_expand(uint8_t *dest, size_t destlen, const uint8_t *secret, + size_t secretlen, const uint8_t *info, size_t infolen, + const struct Context *ctx) +{ + void *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if(!pctx) + return -1; + + if(EVP_PKEY_derive_init(pctx) != 1) + goto err; + + if(EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) != 1) + goto err; + + if(EVP_PKEY_CTX_set_hkdf_md(pctx, ctx->prf) != 1) + goto err; + + if(EVP_PKEY_CTX_set1_hkdf_salt(pctx, "", 0) != 1) + goto err; + + if(EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, (int)secretlen) != 1) + goto err; + + if(EVP_PKEY_CTX_add1_hkdf_info(pctx, info, (int)infolen) != 1) + goto err; + + if(EVP_PKEY_derive(pctx, dest, &destlen) != 1) + goto err; + + return 0; + err: + EVP_PKEY_CTX_free(pctx); + return -1; +} + +static int hkdf_extract(uint8_t *dest, size_t destlen, + const uint8_t *secret, size_t secretlen, + const uint8_t *salt, size_t saltlen, + const struct Context *ctx) +{ + void *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if(!pctx) + return -1; + + if(EVP_PKEY_derive_init(pctx) != 1) + goto err; + + if(EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) != 1) { + goto err; + } + + if(EVP_PKEY_CTX_set_hkdf_md(pctx, ctx->prf) != 1) { + goto err; + } + + if(EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, (int)saltlen) != 1) { + goto err; + } + + if(EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, (int)secretlen) != 1) { + goto err; + } + + if(EVP_PKEY_derive(pctx, dest, &destlen) != 1) { + goto err; + } + + EVP_PKEY_CTX_free(pctx); + return 0; + err: + EVP_PKEY_CTX_free(pctx); + return -1; +} + +static int qhkdf_expand(uint8_t *dest, size_t destlen, + const uint8_t *secret, size_t secretlen, + const uint8_t *qlabel, size_t qlabellen, + const struct Context *ctx) +{ + uint8_t info[256]; + static const char LABEL[] = "quic "; + + uint8_t *p = &info[0]; + *p++ = (destlen / 256) & 0xff; + *p++ = destlen % 256; + *p++ = (strlen(LABEL) + qlabellen) & 0xff; + memcpy(p, LABEL, strlen(LABEL)); + p += strlen(LABEL); + memcpy(p, qlabel, qlabellen); + p += qlabellen; + *p++ = 0; + + return hkdf_expand(dest, destlen, secret, secretlen, &info[0], + p - &info[0], ctx); +} + +static size_t aead_key_length(const struct Context *ctx) +{ + return EVP_CIPHER_key_length(ctx->aead); +} + +static size_t aead_tag_length(const struct Context *ctx) +{ + if(ctx->aead == EVP_aes_128_gcm() || ctx->aead == EVP_aes_256_gcm()) { + return EVP_GCM_TLS_TAG_LEN; + } + if(ctx->aead == EVP_chacha20_poly1305()) { + return EVP_CHACHAPOLY_TLS_TAG_LEN; + } + assert(0); +} + +size_t Curl_qc_aead_max_overhead(const struct Context *ctx) +{ + return aead_tag_length(ctx); +} + +ssize_t Curl_qc_encrypt(uint8_t *dest, size_t destlen, + const uint8_t *plaintext, size_t plaintextlen, + const struct Context *ctx, + const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *ad, size_t adlen) +{ + size_t taglen = aead_tag_length(ctx); + EVP_CIPHER_CTX *actx; + size_t outlen = 0; + int len; + (void)keylen; + + if(destlen < plaintextlen + taglen) { + return -1; + } + + actx = EVP_CIPHER_CTX_new(); + if(!actx) + return -1; + + if(EVP_EncryptInit_ex(actx, ctx->aead, NULL, NULL, NULL) != 1) + goto error; + + if(EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, + (int)noncelen, NULL) != 1) + goto error; + + if(EVP_EncryptInit_ex(actx, NULL, NULL, key, nonce) != 1) + goto error; + + if(EVP_EncryptUpdate(actx, NULL, &len, ad, (int)adlen) != 1) + goto error; + + if(EVP_EncryptUpdate(actx, dest, &len, plaintext, (int)plaintextlen) != 1) + goto error; + + outlen = len; + if(EVP_EncryptFinal_ex(actx, dest + outlen, &len) != 1) + goto error; + + outlen += len; + assert(outlen + taglen <= destlen); + + if(EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_GET_TAG, + (int)taglen, dest + outlen) != 1) + goto error; + + outlen += taglen; + + EVP_CIPHER_CTX_free(actx); + return outlen; + + error: + EVP_CIPHER_CTX_free(actx); + return -1; +} + +ssize_t Curl_qc_decrypt(uint8_t *dest, size_t destlen, + const uint8_t *ciphertext, size_t ciphertextlen, + const struct Context *ctx, + const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *ad, size_t adlen) +{ + size_t taglen = aead_tag_length(ctx); + const uint8_t *tag; + EVP_CIPHER_CTX *actx; + size_t outlen; + int len; + (void)keylen; + + if(taglen > ciphertextlen || destlen + taglen < ciphertextlen) { + return -1; + } + + ciphertextlen -= taglen; + tag = ciphertext + ciphertextlen; + + actx = EVP_CIPHER_CTX_new(); + if(!actx) + return -1; + + if(EVP_DecryptInit_ex(actx, ctx->aead, NULL, NULL, NULL) != 1) + goto error; + + if(EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, (int)noncelen, NULL) != + 1) + goto error; + + if(EVP_DecryptInit_ex(actx, NULL, NULL, key, nonce) != 1) + goto error; + + if(EVP_DecryptUpdate(actx, NULL, &len, ad, (int)adlen) != 1) + goto error; + + if(EVP_DecryptUpdate(actx, dest, &len, ciphertext, (int)ciphertextlen) != 1) + goto error; + + outlen = len; + if(EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, + (int)taglen, (char *)tag) != 1) + goto error; + + if(EVP_DecryptFinal_ex(actx, dest + outlen, &len) != 1) + goto error; + + outlen += len; + + EVP_CIPHER_CTX_free(actx); + return outlen; + error: + EVP_CIPHER_CTX_free(actx); + return -1; +} + +int Curl_qc_derive_initial_secret(uint8_t *dest, size_t destlen, + const ngtcp2_cid *secret, + const uint8_t *salt, + size_t saltlen) +{ + struct Context ctx; + Curl_qc_prf_sha256(&ctx); + return hkdf_extract(dest, destlen, secret->data, secret->datalen, salt, + saltlen, &ctx); +} + +int Curl_qc_derive_client_initial_secret(uint8_t *dest, + size_t destlen, + const uint8_t *secret, + size_t secretlen) +{ + static uint8_t LABEL[] = "client in"; + struct Context ctx; + Curl_qc_prf_sha256(&ctx); + return qhkdf_expand(dest, destlen, secret, secretlen, LABEL, + strlen((char *)LABEL), &ctx); +} + +ssize_t Curl_qc_derive_packet_protection_key(uint8_t *dest, size_t destlen, + const uint8_t *secret, + size_t secretlen, + const struct Context *ctx) +{ + int rv; + static uint8_t LABEL_KEY[] = "key"; + size_t keylen = aead_key_length(ctx); + if(keylen > destlen) { + return -1; + } + + rv = qhkdf_expand(dest, keylen, secret, secretlen, LABEL_KEY, + strlen((char *)LABEL_KEY), ctx); + if(rv) { + return -1; + } + + return keylen; +} + +ssize_t Curl_qc_derive_packet_protection_iv(uint8_t *dest, size_t destlen, + const uint8_t *secret, + size_t secretlen, + const struct Context *ctx) +{ + int rv; + static uint8_t LABEL_IV[] = "iv"; + + size_t ivlen = CURLMAX(8, Curl_qc_aead_nonce_length(ctx)); + if(ivlen > destlen) { + return -1; + } + + rv = qhkdf_expand(dest, ivlen, secret, secretlen, LABEL_IV, + strlen((char *)LABEL_IV), ctx); + if(rv) { + return -1; + } + + return ivlen; +} + +int Curl_qc_derive_server_initial_secret(uint8_t *dest, size_t destlen, + const uint8_t *secret, + size_t secretlen) +{ + static uint8_t LABEL[] = "server in"; + struct Context ctx; + Curl_qc_prf_sha256(&ctx); + return qhkdf_expand(dest, destlen, secret, secretlen, LABEL, + strlen((char *)LABEL), &ctx); +} + +static int +hkdf_expand_label(uint8_t *dest, size_t destlen, const uint8_t *secret, + size_t secretlen, const uint8_t *label, size_t labellen, + const struct Context *ctx) +{ + uint8_t info[256]; + static const uint8_t LABEL[] = "tls13 "; + + uint8_t *p = &info[0]; + *p++ = (destlen / 256)&0xff; + *p++ = destlen % 256; + *p++ = (strlen((char *)LABEL) + labellen)&0xff; + memcpy(p, LABEL, strlen((char *)LABEL)); + p += strlen((char *)LABEL); + memcpy(p, label, labellen); + p += labellen; + *p++ = 0; + + return hkdf_expand(dest, destlen, secret, secretlen, &info[0], + p - &info[0], ctx); +} + +ssize_t +Curl_qc_derive_header_protection_key(uint8_t *dest, size_t destlen, + const uint8_t *secret, size_t secretlen, + const struct Context *ctx) +{ + int rv; + static uint8_t LABEL[] = "quic hp"; + + size_t keylen = aead_key_length(ctx); + if(keylen > destlen) + return -1; + + rv = hkdf_expand_label(dest, keylen, secret, secretlen, LABEL, + strlen((char *)LABEL), ctx); + + if(rv) + return -1; + + return keylen; +} + +ssize_t Curl_qc_hp_mask(uint8_t *dest, size_t destlen, + const struct Context *ctx, + const uint8_t *key, size_t keylen, + const uint8_t *sample, size_t samplelen) +{ + static uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00"; + EVP_CIPHER_CTX *actx; + size_t outlen = 0; + int len; + (void)destlen; /* TODO: make use of these! */ + (void)keylen; + (void)samplelen; + + actx = EVP_CIPHER_CTX_new(); + if(!actx) + return -1; + + if(EVP_EncryptInit_ex(actx, ctx->hp, NULL, key, sample) != 1) + goto error; + if(EVP_EncryptUpdate(actx, dest, &len, PLAINTEXT, + (int)strlen((char *)PLAINTEXT)) != 1) + goto error; + + DEBUGASSERT(len == 5); + + outlen = len; + + if(EVP_EncryptFinal_ex(actx, dest + outlen, &len) != 1) + goto error; + + DEBUGASSERT(len == 0); + + return outlen; + error: + EVP_CIPHER_CTX_free(actx); + return -1; +} + + +#endif diff --git a/lib/vquic/ngtcp2-crypto.h b/lib/vquic/ngtcp2-crypto.h new file mode 100644 index 000000000..f91b4e0ea --- /dev/null +++ b/lib/vquic/ngtcp2-crypto.h @@ -0,0 +1,93 @@ +#ifndef HEADER_CURL_VQUIC_NGTCP2_CRYPTO_H +#define HEADER_CURL_VQUIC_NGTCP2_CRYPTO_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_NGTCP2 +struct Context { +#if defined(OPENSSL_IS_BORINGSSL) + const EVP_AEAD *aead; +#else /* !OPENSSL_IS_BORINGSSL */ + const EVP_CIPHER *aead; +#endif /* !OPENSSL_IS_BORINGSSL */ + const EVP_CIPHER *hp; + const EVP_MD *prf; + uint8_t tx_secret[64]; + uint8_t rx_secret[64]; + size_t secretlen; +}; + +void Curl_qc_prf_sha256(struct Context *ctx); +void Curl_qc_aead_aes_128_gcm(struct Context *ctx); +size_t Curl_qc_aead_nonce_length(const struct Context *ctx); +int Curl_qc_negotiated_prf(struct Context *ctx, SSL *ssl); +int Curl_qc_negotiated_aead(struct Context *ctx, SSL *ssl); +size_t Curl_qc_aead_max_overhead(const struct Context *ctx); +ssize_t Curl_qc_encrypt(uint8_t *dest, size_t destlen, + const uint8_t *plaintext, size_t plaintextlen, + const struct Context *ctx, + const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *ad, size_t adlen); +ssize_t Curl_qc_decrypt(uint8_t *dest, size_t destlen, + const uint8_t *ciphertext, size_t ciphertextlen, + const struct Context *ctx, + const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *ad, size_t adlen); +ssize_t Curl_qc_encrypt_pn(uint8_t *dest, size_t destlen, + const uint8_t *plaintext, size_t plaintextlen, + const struct Context *ctx, + const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen); +int Curl_qc_derive_initial_secret(uint8_t *dest, size_t destlen, + const ngtcp2_cid *secret, + const uint8_t *salt, + size_t saltlen); +int Curl_qc_derive_client_initial_secret(uint8_t *dest, + size_t destlen, + const uint8_t *secret, + size_t secretlen); +ssize_t Curl_qc_derive_packet_protection_key(uint8_t *dest, size_t destlen, + const uint8_t *secret, + size_t secretlen, + const struct Context *ctx); +ssize_t Curl_qc_derive_packet_protection_iv(uint8_t *dest, size_t destlen, + const uint8_t *secret, + size_t secretlen, + const struct Context *ctx); +int Curl_qc_derive_server_initial_secret(uint8_t *dest, size_t destlen, + const uint8_t *secret, + size_t secretlen); +ssize_t +Curl_qc_derive_header_protection_key(uint8_t *dest, size_t destlen, + const uint8_t *secret, size_t secretlen, + const struct Context *ctx); + +ssize_t Curl_qc_hp_mask(uint8_t *dest, size_t destlen, + const struct Context *ctx, + const uint8_t *key, size_t keylen, + const uint8_t *sample, size_t samplelen); +#endif /* USE_NGTCP2 */ +#endif /* HEADER_CURL_VQUIC_NGTCP2_CRYPTO_H */ diff --git a/lib/vquic/ngtcp2.c b/lib/vquic/ngtcp2.c new file mode 100644 index 000000000..a8c563ce1 --- /dev/null +++ b/lib/vquic/ngtcp2.c @@ -0,0 +1,1029 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_NGTCP2 +#include <ngtcp2/ngtcp2.h> +#include <openssl/err.h> +#include "urldata.h" +#include "sendf.h" +#include "strdup.h" +#include "rand.h" +#include "ngtcp2.h" +#include "ngtcp2-crypto.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#define QUIC_MAX_STREAMS (256*1024) +#define QUIC_MAX_DATA (1*1024*1024) +#define QUIC_IDLE_TIMEOUT 60 /* seconds? */ +#define QUIC_CIPHERS "TLS13-AES-128-GCM-SHA256:" \ + "TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256" +#define QUIC_GROUPS "P-256:X25519:P-384:P-521" + +static void quic_printf(void *user_data, const char *fmt, ...) +{ + va_list ap; + (void)user_data; /* TODO, use this to do infof() instead long-term */ + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static int setup_initial_crypto_context(struct connectdata *conn) +{ + int rv; + uint8_t initial_secret[32]; + uint8_t secret[32]; + const ngtcp2_cid *dcid; + uint8_t key[16]; + ssize_t keylen; + uint8_t iv[16]; + ssize_t ivlen; + uint8_t hp[16]; + ssize_t hplen; + + dcid = ngtcp2_conn_get_dcid(conn->quic.conn); + rv = Curl_qc_derive_initial_secret(initial_secret, sizeof(initial_secret), + dcid, (uint8_t *)NGTCP2_INITIAL_SALT, + strlen(NGTCP2_INITIAL_SALT)); + if(rv) { + return -1; + } + + Curl_qc_prf_sha256(&conn->quic.hs_crypto_ctx); + Curl_qc_aead_aes_128_gcm(&conn->quic.hs_crypto_ctx); + + rv = Curl_qc_derive_client_initial_secret(secret, sizeof(secret), + initial_secret, + sizeof(initial_secret)); + if(rv) { + return -1; + } + + keylen = Curl_qc_derive_packet_protection_key(key, sizeof(key), + secret, sizeof(secret), + &conn->quic.hs_crypto_ctx); + if(keylen < 0) { + return -1; + } + + ivlen = Curl_qc_derive_packet_protection_iv(iv, sizeof(iv), + secret, sizeof(secret), + &conn->quic.hs_crypto_ctx); + if(ivlen < 0) { + return -1; + } + + hplen = Curl_qc_derive_header_protection_key(hp, sizeof(hp), + secret, sizeof(secret), + &conn->quic.hs_crypto_ctx); + if(hplen < 0) { + return -1; + } + + ngtcp2_conn_install_initial_tx_keys(conn->quic.conn, key, keylen, iv, ivlen, + hp, hplen); + + rv = Curl_qc_derive_server_initial_secret(secret, sizeof(secret), + initial_secret, + sizeof(initial_secret)); + if(rv) { + return -1; + } + + keylen = Curl_qc_derive_packet_protection_key(key, sizeof(key), + secret, sizeof(secret), + &conn->quic.hs_crypto_ctx); + if(keylen < 0) { + return -1; + } + + ivlen = Curl_qc_derive_packet_protection_iv(iv, sizeof(iv), + secret, sizeof(secret), + &conn->quic.hs_crypto_ctx); + if(ivlen < 0) { + return -1; + } + + hplen = Curl_qc_derive_header_protection_key(hp, sizeof(hp), + secret, sizeof(secret), + &conn->quic.hs_crypto_ctx); + if(hplen < 0) { + return -1; + } + + ngtcp2_conn_install_initial_rx_keys(conn->quic.conn, + key, keylen, iv, ivlen, hp, hplen); + + return 0; +} + +static void quic_settings(ngtcp2_settings *s) +{ + s->log_printf = quic_printf; + s->initial_ts = 0; + s->max_stream_data_bidi_local = QUIC_MAX_STREAMS; + s->max_stream_data_bidi_remote = QUIC_MAX_STREAMS; + s->max_stream_data_uni = QUIC_MAX_STREAMS; + s->max_data = QUIC_MAX_DATA; + s->max_streams_bidi = 1; + s->max_streams_uni = 1; + s->idle_timeout = QUIC_IDLE_TIMEOUT; + s->max_packet_size = NGTCP2_MAX_PKT_SIZE; + s->ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT; + s->max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY; +} + +/* SSL extension functions */ +static int transport_params_add_cb(SSL *ssl, unsigned int ext_type, + unsigned int content, + const unsigned char **out, + size_t *outlen, X509 *x, + size_t chainidx, int *al, void *add_arg) +{ + int rv; + struct connectdata *conn = (struct connectdata *)SSL_get_app_data(ssl); + ngtcp2_transport_params params; + uint8_t buf[64]; + ssize_t nwrite; + (void)ext_type; + (void)content; + (void)x; + (void)chainidx; + (void)add_arg; + + rv = ngtcp2_conn_get_local_transport_params( + conn->quic.conn, ¶ms, NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO); + if(rv) { + *al = SSL_AD_INTERNAL_ERROR; + return -1; + } + + nwrite = ngtcp2_encode_transport_params( + buf, sizeof(buf), NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, ¶ms); + if(nwrite < 0) { + fprintf(stderr, "ngtcp2_encode_transport_params: %s\n", + ngtcp2_strerror((int)nwrite)); + *al = SSL_AD_INTERNAL_ERROR; + return -1; + } + + *out = Curl_memdup(buf, nwrite); + *outlen = nwrite; + + return 1; +} + +static void transport_params_free_cb(SSL *ssl, unsigned int ext_type, + unsigned int context, + const unsigned char *out, + void *add_arg) +{ + (void)ssl; + (void)ext_type; + (void)context; + (void)add_arg; + free((char *)out); +} + +static int transport_params_parse_cb(SSL *ssl, unsigned int ext_type, + unsigned int context, + const unsigned char *in, + size_t inlen, X509 *x, size_t chainidx, + int *al, void *parse_arg) +{ + struct connectdata *conn = (struct connectdata *)SSL_get_app_data(ssl); + int rv; + ngtcp2_transport_params params; + (void)ext_type; + (void)context; + (void)x; + (void)chainidx; + (void)parse_arg; + + rv = ngtcp2_decode_transport_params( + ¶ms, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, in, inlen); + if(rv) { + fprintf(stderr, "ngtcp2_decode_transport_params: %s\n", + ngtcp2_strerror(rv)); + *al = SSL_AD_ILLEGAL_PARAMETER; + return -1; + } + + rv = ngtcp2_conn_set_remote_transport_params( + conn->quic.conn, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, + ¶ms); + if(rv) { + *al = SSL_AD_ILLEGAL_PARAMETER; + return -1; + } + + return 1; +} + +static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data) +{ + SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); + + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); + + /* This makes OpenSSL client not send CCS after an initial ClientHello. */ + SSL_CTX_clear_options(ssl_ctx, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); + + SSL_CTX_set_default_verify_paths(ssl_ctx); + + if(SSL_CTX_set_cipher_list(ssl_ctx, QUIC_CIPHERS) != 1) { + failf(data, "SSL_CTX_set_cipher_list: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NULL; + } + + if(SSL_CTX_set1_groups_list(ssl_ctx, QUIC_GROUPS) != 1) { + failf(data, "SSL_CTX_set1_groups_list failed"); + return NULL; + } + + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_QUIC_HACK); + + if(SSL_CTX_add_custom_ext(ssl_ctx, + NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS, + SSL_EXT_CLIENT_HELLO | + SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS, + transport_params_add_cb, + transport_params_free_cb, NULL, + transport_params_parse_cb, NULL) != 1) { + failf(data, "SSL_CTX_add_custom_ext(NGTCP2_TLSEXT_QUIC_TRANSPORT_" + "PARAMETERS) failed: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return NULL; + } + + return ssl_ctx; +} + +/** SSL callbacks ***/ + +static void set_tls_alert(struct connectdata *conn, + uint8_t alert) +{ + struct quicsocket *qs = &conn->quic; + qs->tls_alert = alert; +} + +static int ssl_on_key(struct connectdata *conn, + int name, const uint8_t *secret, size_t secretlen) +{ + int rv; + uint8_t hp[64]; + ssize_t hplen; + uint8_t key[64]; + ssize_t keylen; + uint8_t iv[64]; + ssize_t ivlen; + struct Context *crypto_ctx = &conn->quic.crypto_ctx; + + switch(name) { + case SSL_KEY_CLIENT_EARLY_TRAFFIC: + case SSL_KEY_CLIENT_HANDSHAKE_TRAFFIC: + case SSL_KEY_CLIENT_APPLICATION_TRAFFIC: + case SSL_KEY_SERVER_HANDSHAKE_TRAFFIC: + case SSL_KEY_SERVER_APPLICATION_TRAFFIC: + break; + default: + return 0; + } + + /* TODO We don't have to call this everytime we get key generated. */ + rv = Curl_qc_negotiated_prf(crypto_ctx, conn->quic.ssl); + if(rv != 0) { + return -1; + } + rv = Curl_qc_negotiated_aead(crypto_ctx, conn->quic.ssl); + if(rv != 0) { + return -1; + } + + keylen = Curl_qc_derive_packet_protection_key(key, sizeof(key), + secret, sizeof(secret), + crypto_ctx); + if(keylen < 0) { + return -1; + } + + ivlen = Curl_qc_derive_packet_protection_iv(iv, sizeof(iv), + secret, sizeof(secret), + crypto_ctx); + if(ivlen < 0) { + return -1; + } + + hplen = + Curl_qc_derive_header_protection_key(hp, sizeof(hp), + secret, secretlen, crypto_ctx); + if(hplen < 0) + return -1; + + /* TODO Just call this once. */ + ngtcp2_conn_set_aead_overhead(conn->quic.conn, + Curl_qc_aead_max_overhead(crypto_ctx)); + + switch(name) { + case SSL_KEY_CLIENT_EARLY_TRAFFIC: + ngtcp2_conn_install_early_keys(conn->quic.conn, key, keylen, iv, ivlen, + hp, hplen); + break; + case SSL_KEY_CLIENT_HANDSHAKE_TRAFFIC: + ngtcp2_conn_install_handshake_tx_keys(conn->quic.conn, key, keylen, + iv, ivlen, hp, hplen); + break; + case SSL_KEY_CLIENT_APPLICATION_TRAFFIC: + ngtcp2_conn_install_tx_keys(conn->quic.conn, key, keylen, iv, ivlen, + hp, hplen); + break; + case SSL_KEY_SERVER_HANDSHAKE_TRAFFIC: + ngtcp2_conn_install_handshake_rx_keys(conn->quic.conn, key, keylen, + iv, ivlen, + hp, hplen); + break; + case SSL_KEY_SERVER_APPLICATION_TRAFFIC: + ngtcp2_conn_install_rx_keys(conn->quic.conn, key, keylen, iv, ivlen, + hp, hplen); + break; + } + return 0; +} + +static void ssl_msg_cb(int write_p, int version, int content_type, + const void *buf, size_t len, SSL *ssl, void *user_data) +{ + int rv; + struct connectdata *conn = (struct connectdata *)user_data; + uint8_t *msg = (uint8_t *)buf; + (void)version; + (void)ssl; + + if(!write_p) + return; + + switch(content_type) { + case SSL3_RT_HANDSHAKE: + break; + case SSL3_RT_ALERT: + assert(len == 2); + if(msg[0] != 2 /* FATAL */) { + return; + } + set_tls_alert(conn, msg[1]); + return; + default: + return; + } + + rv = ngtcp2_conn_submit_crypto_data(conn->quic.conn, buf, len); + if(rv) { + fprintf(stderr, "write_client_handshake failed\n"); + } + assert(0 == rv); +} + +static int ssl_key_cb(SSL *ssl, int name, + const unsigned char *secret, + size_t secretlen, + void *arg) +{ + struct connectdata *conn = (struct connectdata *)arg; + (void)ssl; + + if(ssl_on_key(conn, name, secret, secretlen) != 0) + return 0; + + /* log_secret(ssl, name, secret, secretlen); */ + + return 1; +} + +static int read_server_handshake(struct connectdata *conn, + char *buf, int buflen) +{ + struct quic_handshake *hs = &conn->quic.handshake; + int avail = (int)(hs->len - hs->nread); + int n = CURLMIN(buflen, avail); + memcpy(buf, &hs->buf[hs->nread], n); + infof(conn->data, "read %d bytes of handshake data\n", n); + hs->nread += n; + return n; +} + +static void write_server_handshake(struct connectdata *conn, + const uint8_t *ptr, size_t datalen) +{ + char *p; + struct quic_handshake *hs = &conn->quic.handshake; + size_t alloclen = datalen + hs->alloclen; + infof(conn->data, "store %zd bytes of handshake data\n", datalen); + if(alloclen > hs->alloclen) { + alloclen *= 2; + p = realloc(conn->quic.handshake.buf, alloclen); + if(!p) + return; /* BAAAAAD */ + hs->buf = p; + hs->alloclen = alloclen; + } + memcpy(&hs->buf[hs->len], ptr, datalen); + hs->len += datalen; +} + +/** BIO functions ***/ + +static int bio_write(BIO *b, const char *buf, int len) +{ + (void)b; + (void)buf; + (void)len; + assert(0); + return -1; +} + +static int bio_read(BIO *b, char *buf, int len) +{ + struct connectdata *conn; + BIO_clear_retry_flags(b); + + conn = (struct connectdata *)BIO_get_data(b); + + len = read_server_handshake(conn, buf, len); + if(len == 0) { + BIO_set_retry_read(b); + return -1; + } + + return len; +} + +static int bio_puts(BIO *b, const char *str) +{ + return bio_write(b, str, (int)strlen(str)); +} + +static int bio_gets(BIO *b, char *buf, int len) +{ + (void)b; + (void)buf; + (void)len; + return -1; +} + +static long bio_ctrl(BIO *b, int cmd, long num, void *ptr) +{ + (void)b; + (void)cmd; + (void)num; + (void)ptr; + switch(cmd) { + case BIO_CTRL_FLUSH: + return 1; + } + + return 0; +} + +static int bio_create(BIO *b) +{ + BIO_set_init(b, 1); + return 1; +} + +static int bio_destroy(BIO *b) +{ + if(!b) + return 0; + + return 1; +} + +static BIO_METHOD *create_bio_method(void) +{ + BIO_METHOD *meth = BIO_meth_new(BIO_TYPE_FD, "bio"); + BIO_meth_set_write(meth, bio_write); + BIO_meth_set_read(meth, bio_read); + BIO_meth_set_puts(meth, bio_puts); + BIO_meth_set_gets(meth, bio_gets); + BIO_meth_set_ctrl(meth, bio_ctrl); + BIO_meth_set_create(meth, bio_create); + BIO_meth_set_destroy(meth, bio_destroy); + return meth; +} + + +static int quic_init_ssl(struct connectdata *conn) +{ + struct quicsocket *qs = &conn->quic; + BIO *bio; + const uint8_t *alpn = NULL; + size_t alpnlen = 0; + /* this will need some attention when HTTPS proxy over QUIC get fixed */ + const char * const hostname = conn->host.name; + + if(qs->ssl) + SSL_free(qs->ssl); + + qs->ssl = SSL_new(qs->sslctx); + bio = BIO_new(create_bio_method()); + /* supposedly this can fail too? */ + + BIO_set_data(bio, conn); + SSL_set_bio(qs->ssl, bio, bio); + SSL_set_app_data(qs->ssl, conn); + SSL_set_connect_state(qs->ssl); + SSL_set_msg_callback(qs->ssl, ssl_msg_cb); + SSL_set_msg_callback_arg(qs->ssl, conn); + SSL_set_key_callback(qs->ssl, ssl_key_cb, conn); + + switch(qs->version) { +#ifdef NGTCP2_PROTO_VER_D17 + case NGTCP2_PROTO_VER_D17: + alpn = (const uint8_t *)NGTCP2_ALPN_D17; + alpnlen = strlen(NGTCP2_ALPN_D17); + break; +#endif + } + if(alpn) + SSL_set_alpn_protos(qs->ssl, alpn, (int)alpnlen); + + /* set SNI */ + SSL_set_tlsext_host_name(qs->ssl, hostname); + return 0; +} + +static int quic_tls_handshake(struct connectdata *conn, + bool resumption, + bool initial) +{ + int rv; + struct quicsocket *qs = &conn->quic; + ERR_clear_error(); + + /* Note that SSL_SESSION_get_max_early_data() and + SSL_get_max_early_data() return completely different value. */ + if(initial && resumption && + SSL_SESSION_get_max_early_data(SSL_get_session(qs->ssl))) { + size_t nwrite; + /* OpenSSL returns error if SSL_write_early_data is called when resumption + is not attempted. Sending empty string is a trick to just early_data + extension. */ + rv = SSL_write_early_data(qs->ssl, "", 0, &nwrite); + if(rv == 0) { + int err = SSL_get_error(qs->ssl, rv); + switch(err) { + case SSL_ERROR_SSL: + fprintf(stderr, "TLS handshake error: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return -1; + default: + fprintf(stderr, "TLS handshake error: %d\n", err); + return -1; + } + } + } + + rv = SSL_do_handshake(qs->ssl); + if(rv <= 0) { + int err = SSL_get_error(qs->ssl, rv); + switch(err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return 0; + case SSL_ERROR_SSL: + fprintf(stderr, "TLS handshake error: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return -1; + default: + fprintf(stderr, "TLS handshake error: %d\n", err); + return -1; + } + } + + /* SSL_get_early_data_status works after handshake completes. */ + if(resumption && + SSL_get_early_data_status(qs->ssl) != SSL_EARLY_DATA_ACCEPTED) { + fprintf(stderr, "Early data was rejected by server\n"); + ngtcp2_conn_early_data_rejected(conn->quic.conn); + } + + ngtcp2_conn_handshake_completed(conn->quic.conn); + return 0; +} + +static int cb_initial(ngtcp2_conn *quic, void *user_data) +{ + struct connectdata *conn = (struct connectdata *)user_data; + (void)quic; + if(quic_tls_handshake(conn, false, true) != 0) + return NGTCP2_ERR_CALLBACK_FAILURE; + return 0; +} + +static int quic_read_tls(struct connectdata *conn) +{ + uint8_t buf[4096]; + size_t nread; + + ERR_clear_error(); + for(;;) { + int err; + int rv = SSL_read_ex(conn->quic.ssl, buf, sizeof(buf), &nread); + if(rv == 1) { + infof(conn->data, "Read %zd bytes from TLS crypto stream", + nread); + continue; + } + err = SSL_get_error(conn->quic.ssl, 0); + switch(err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return 0; + case SSL_ERROR_SSL: + case SSL_ERROR_ZERO_RETURN: + infof(conn->data, "TLS read error: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return NGTCP2_ERR_CRYPTO; + default: + infof(conn->data, "TLS read error: %d\n", err); + return NGTCP2_ERR_CRYPTO; + } + } + /* NEVER-REACHED */ +} + +static int +cb_recv_crypto_data(ngtcp2_conn *tconn, ngtcp2_crypto_level crypto_level, + uint64_t offset, + const uint8_t *data, size_t datalen, + void *user_data) +{ + struct connectdata *conn = (struct connectdata *)user_data; + (void)offset; + (void)crypto_level; + + write_server_handshake(conn, data, datalen); + + if(!ngtcp2_conn_get_handshake_completed(tconn) && + quic_tls_handshake(conn, false, false)) { + return NGTCP2_ERR_CRYPTO; + } + + /* SSL_do_handshake() might not consume all data (e.g., + NewSessionTicket). */ + return quic_read_tls(conn); +} + +static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data) +{ + struct connectdata *conn = (struct connectdata *)user_data; + (void)tconn; + infof(conn->data, "QUIC handshake is completed\n"); + return 0; +} + +static ssize_t cb_in_encrypt(ngtcp2_conn *tconn, + uint8_t *dest, size_t destlen, + const uint8_t *plaintext, + size_t plaintextlen, + const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *ad, size_t adlen, + void *user_data) +{ + struct connectdata *conn = (struct connectdata *)user_data; + ssize_t nwrite = Curl_qc_encrypt(dest, destlen, plaintext, plaintextlen, + &conn->quic.hs_crypto_ctx, + key, keylen, nonce, noncelen, ad, adlen); + if(nwrite < 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + (void)tconn; + + return nwrite; +} + +static ssize_t cb_in_decrypt(ngtcp2_conn *tconn, + uint8_t *dest, size_t destlen, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *ad, size_t adlen, + void *user_data) +{ + struct connectdata *conn = (struct connectdata *)user_data; + (void)tconn; + return Curl_qc_decrypt(dest, destlen, ciphertext, ciphertextlen, + &conn->quic.hs_crypto_ctx, key, keylen, + nonce, noncelen, ad, adlen); +} + + +static ssize_t cb_encrypt_data(ngtcp2_conn *tconn, + uint8_t *dest, size_t destlen, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *ad, size_t adlen, + void *user_data) +{ + struct connectdata *conn = (struct connectdata *)user_data; + ssize_t rc; + (void)tconn; + rc = Curl_qc_encrypt(dest, destlen, plaintext, plaintextlen, + &conn->quic.crypto_ctx, + key, keylen, nonce, noncelen, ad, adlen); + if(rc < 0) + return NGTCP2_ERR_CALLBACK_FAILURE; + return rc; +} + +static ssize_t +cb_decrypt_data(ngtcp2_conn *tconn, + uint8_t *dest, size_t destlen, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *key, size_t keylen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *ad, size_t adlen, + void *user_data) +{ + struct connectdata *conn = (struct connectdata *)user_data; + ssize_t rc; + (void)tconn; + rc = Curl_qc_decrypt(dest, destlen, ciphertext, ciphertextlen, + &conn->quic.crypto_ctx, + key, keylen, nonce, noncelen, ad, adlen); + if(rc < 0) + return NGTCP2_ERR_TLS_DECRYPT; + return rc; +} + +static int cb_recv_stream_data(ngtcp2_conn *tconn, int64_t stream_id, + int fin, uint64_t offset, + const uint8_t *buf, size_t buflen, + void *user_data, void *stream_user_data) +{ + struct connectdata *conn = (struct connectdata *)user_data; + (void)fin; + (void)offset; + (void)stream_user_data; + /* TODO: handle the data */ + infof(conn->data, "Received %ld bytes at %p\n", buflen, buf); + ngtcp2_conn_extend_max_stream_offset(tconn, stream_id, buflen); + ngtcp2_conn_extend_max_offset(tconn, buflen); + return 0; +} + +static int cb_acked_crypto_offset(ngtcp2_conn *tconn, + uint64_t offset, size_t datalen, + void *user_data) +{ + struct connectdata *conn = (struct connectdata *)user_data; + (void)conn; + (void)tconn; + (void)offset; + (void)datalen; + + /* TODO: uhm... what should it do? */ + + return 0; +} + +static int +cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id, + uint64_t offset, size_t datalen, void *user_data, + void *stream_user_data) +{ + struct connectdata *conn = (struct connectdata *)user_data; + (void)conn; + (void)stream_id; + (void)tconn; + (void)offset; + (void)datalen; + (void)stream_user_data; + + /* TODO: implement */ + + return 0; +} + +static int cb_stream_close(ngtcp2_conn *tconn, int64_t stream_id, + uint16_t app_error_code, + void *user_data, void *stream_user_data) +{ + struct connectdata *conn = (struct connectdata *)user_data; + (void)conn; + (void)tconn; + (void)stream_id; + (void)app_error_code; + (void)stream_user_data; + /* stream is closed... */ + + return 0; +} + +static int cb_recv_retry(ngtcp2_conn *tconn, const ngtcp2_pkt_hd *hd, + const ngtcp2_pkt_retry *retry, void *user_data) +{ + /* Re-generate handshake secrets here because connection ID might change. */ + struct connectdata *conn = (struct connectdata *)user_data; + (void)tconn; + (void)hd; + (void)retry; + + quic_init_ssl(conn); + setup_initial_crypto_context(conn); + + return 0; +} + +static ssize_t cb_in_hp_mask(ngtcp2_conn *tconn, uint8_t *dest, size_t destlen, + const uint8_t *key, size_t keylen, + const uint8_t *sample, size_t samplelen, + void *user_data) +{ + struct connectdata *conn = (struct connectdata *)user_data; + ssize_t nwrite; + (void)tconn; + + nwrite = Curl_qc_hp_mask(dest, destlen, &conn->quic.hs_crypto_ctx, + key, keylen, sample, samplelen); + if(nwrite < 0) + return NGTCP2_ERR_CALLBACK_FAILURE; + + return nwrite; +} + +static ssize_t cb_hp_mask(ngtcp2_conn *tconn, uint8_t *dest, size_t destlen, + const uint8_t *key, size_t keylen, + const uint8_t *sample, size_t samplelen, + void *user_data) +{ + struct connectdata *conn = (struct connectdata *)user_data; + ssize_t nwrite; + (void)tconn; + + nwrite = Curl_qc_hp_mask(dest, destlen, &conn->quic.crypto_ctx, + key, keylen, sample, samplelen); + if(nwrite < 0) + return NGTCP2_ERR_CALLBACK_FAILURE; + + return nwrite; +} + +static int cb_extend_max_streams_bidi(ngtcp2_conn *tconn, uint64_t max_streams, + void *user_data) +{ + /* struct connectdata *conn = (struct connectdata *)user_data; */ + (void)tconn; + (void)max_streams; + (void)user_data; + return 0; +} + +static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid, + uint8_t *token, size_t cidlen, + void *user_data) +{ + struct connectdata *conn = (struct connectdata *)user_data; + CURLcode result; + (void)tconn; + + result = Curl_rand(conn->data, cid->data, cidlen); + if(result) + return 1; + + result = Curl_rand(conn->data, token, NGTCP2_STATELESS_RESET_TOKENLEN); + if(result) + return 1; + + return 0; +} + +static void quic_callbacks(ngtcp2_conn_callbacks *c) +{ + memset(c, 0, sizeof(ngtcp2_conn_callbacks)); + c->client_initial = cb_initial; + /* recv_client_initial = NULL */ + c->recv_crypto_data = cb_recv_crypto_data; + c->handshake_completed = cb_handshake_completed; + /* recv_version_negotiation = NULL */ + c->in_encrypt = cb_in_encrypt; + c->in_decrypt = cb_in_decrypt; + c->encrypt = cb_encrypt_data; + c->decrypt = cb_decrypt_data; + c->in_hp_mask = cb_in_hp_mask; + c->hp_mask = cb_hp_mask; + c->recv_stream_data = cb_recv_stream_data; + c->acked_crypto_offset = cb_acked_crypto_offset; + c->acked_stream_data_offset = cb_acked_stream_data_offset; + /* stream_open = NULL */ + c->stream_close = cb_stream_close; + /* recv_stateless_reset = NULL */ + c->recv_retry = cb_recv_retry; + c->extend_max_streams_bidi = cb_extend_max_streams_bidi; + /* extend_max_streams_uni = NULL */ + /* rand = NULL */ + c->get_new_connection_id = cb_get_new_connection_id; + /* remove_connection_id = NULL */ +} + + +CURLcode Curl_quic_connect(struct connectdata *conn, + curl_socket_t sockfd, + const struct sockaddr *addr, + socklen_t addrlen) +{ + int rc; + struct quicsocket *qs = &conn->quic; + CURLcode result; + ngtcp2_path path; /* TODO: this must be initialized properly */ + (void)sockfd; + (void)addr; + (void)addrlen; + infof(conn->data, "Connecting socket %d over QUIC\n", sockfd); + + qs->sslctx = quic_ssl_ctx(conn->data); + if(!qs->sslctx) + return CURLE_FAILED_INIT; /* TODO: better return code */ + + if(quic_init_ssl(conn)) + return CURLE_FAILED_INIT; /* TODO: better return code */ + + qs->dcid.datalen = NGTCP2_MAX_CIDLEN; + result = Curl_rand(conn->data, qs->dcid.data, NGTCP2_MAX_CIDLEN); + if(result) + return result; + + qs->scid.datalen = NGTCP2_MAX_CIDLEN; + result = Curl_rand(conn->data, qs->scid.data, NGTCP2_MAX_CIDLEN); + if(result) + return result; + + quic_settings(&qs->settings); + quic_callbacks(&qs->callbacks); + +#ifdef NGTCP2_PROTO_VER_D18 +#define QUICVER NGTCP2_PROTO_VER_D18 +#else +#error "unsupported ngtcp2 version" +#endif + rc = ngtcp2_conn_client_new(&qs->conn, &qs->dcid, &qs->scid, + &path, + QUICVER, &qs->callbacks, &qs->settings, conn); + if(rc) + return CURLE_FAILED_INIT; /* TODO: create a QUIC error code */ + + rc = setup_initial_crypto_context(conn); + if(rc) + return CURLE_FAILED_INIT; /* TODO: better return code */ + + return CURLE_OK; +} + +/* + * Store ngtp2 version info in this buffer, Prefix with a space. Return total + * length written. + */ +int Curl_quic_ver(char *p, size_t len) +{ + return msnprintf(p, len, " ngtcp2/blabla"); +} + +CURLcode Curl_quic_is_connected(struct connectdata *conn, int sockindex, + bool *done) +{ + (void)conn; + (void)sockindex; + *done = FALSE; + return CURLE_OK; +} +#endif diff --git a/lib/vquic/ngtcp2.h b/lib/vquic/ngtcp2.h new file mode 100644 index 000000000..8342c2c9b --- /dev/null +++ b/lib/vquic/ngtcp2.h @@ -0,0 +1,65 @@ +#ifndef HEADER_CURL_VQUIC_NGTCP2_H +#define HEADER_CURL_VQUIC_NGTCP2_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_NGTCP2 + +#include <ngtcp2/ngtcp2.h> +#include <openssl/ssl.h> +#include "ngtcp2-crypto.h" + +struct quic_handshake { + char *buf; /* pointer to the buffer */ + size_t alloclen; /* size of allocation */ + size_t len; /* size of content in buffer */ + size_t nread; /* how many bytes have been read */ +}; + +struct quicsocket { + ngtcp2_conn *conn; + ngtcp2_cid dcid; + ngtcp2_cid scid; + uint32_t version; + ngtcp2_conn_callbacks callbacks; + ngtcp2_settings settings; + SSL_CTX *sslctx; + SSL *ssl; + struct Context crypto_ctx; + struct Context hs_crypto_ctx; + struct quic_handshake handshake; + /* the last TLS alert description generated by the local endpoint */ + uint8_t tls_alert; +}; + +#include "urldata.h" + +CURLcode Curl_quic_connect(struct connectdata *conn, + curl_socket_t sockfd, + const struct sockaddr *addr, + socklen_t addrlen); +int Curl_quic_ver(char *p, size_t len); +#endif + +#endif /* HEADER_CURL_VQUIC_NGTCP2_H */ diff --git a/lib/vquic/quiche.c b/lib/vquic/quiche.c new file mode 100644 index 000000000..f6c4ad42c --- /dev/null +++ b/lib/vquic/quiche.c @@ -0,0 +1,229 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_QUICHE +#include <quiche.h> +#include <openssl/err.h> +#include "urldata.h" +#include "sendf.h" +#include "strdup.h" +#include "rand.h" +#include "quic.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#define QUIC_MAX_STREAMS (256*1024) +#define QUIC_MAX_DATA (1*1024*1024) +#define QUIC_IDLE_TIMEOUT 60 * 1000 /* milliseconds */ + +static CURLcode process_ingress(struct connectdata *conn, + curl_socket_t sockfd); + +static CURLcode flush_egress(struct connectdata *conn, curl_socket_t sockfd); + +static Curl_recv quic_stream_recv; +static Curl_send quic_stream_send; + + +CURLcode Curl_quic_connect(struct connectdata *conn, curl_socket_t sockfd, + const struct sockaddr *addr, socklen_t addrlen) +{ + CURLcode result; + struct quicsocket *qs = &conn->quic; + (void)addr; + (void)addrlen; + + infof(conn->data, "Connecting socket %d over QUIC\n", sockfd); + + qs->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION); + if(!qs->cfg) + return CURLE_FAILED_INIT; /* TODO: better return code */ + + quiche_config_set_idle_timeout(qs->cfg, QUIC_IDLE_TIMEOUT); + quiche_config_set_initial_max_data(qs->cfg, QUIC_MAX_DATA); + quiche_config_set_initial_max_stream_data_bidi_local(qs->cfg, QUIC_MAX_DATA); + quiche_config_set_initial_max_stream_data_bidi_remote(qs->cfg, QUIC_MAX_DATA); + quiche_config_set_initial_max_stream_data_uni(qs->cfg, QUIC_MAX_DATA); + quiche_config_set_initial_max_streams_bidi(qs->cfg, QUIC_MAX_STREAMS); + quiche_config_set_initial_max_streams_uni(qs->cfg, QUIC_MAX_STREAMS); + quiche_config_set_application_protos(qs->cfg, (uint8_t *) "\x05hq-20", 6); + + result = Curl_rand(conn->data, qs->scid, sizeof(qs->scid)); + if(result) + return result; + + qs->conn = quiche_connect(conn->host.name, (const uint8_t *) qs->scid, + sizeof(qs->scid), qs->cfg); + if(!qs->conn) + return CURLE_FAILED_INIT; /* TODO: better return code */ + + result = flush_egress(conn, sockfd); + if(result) + return CURLE_FAILED_INIT; /* TODO: better return code */ + + infof(conn->data, "Sent QUIC client Initial\n"); + + return CURLE_OK; +} + +CURLcode Curl_quic_is_connected(struct connectdata *conn, int sockindex, + bool *done) +{ + CURLcode result; + struct quicsocket *qs = &conn->quic; + curl_socket_t sockfd = conn->sock[sockindex]; + + result = process_ingress(conn, sockfd); + if(result) + return result; + + result = flush_egress(conn, sockfd); + if(result) + return result; + + if(quiche_conn_is_established(qs->conn)) { + conn->recv[sockindex] = quic_stream_recv; + conn->send[sockindex] = quic_stream_send; + *done = TRUE; + } + + return CURLE_OK; +} + +static CURLcode process_ingress(struct connectdata *conn, int sockfd) +{ + ssize_t recvd; + struct quicsocket *qs = &conn->quic; + static uint8_t buf[65535]; + + do { + recvd = recv(sockfd, buf, sizeof(buf), 0); + if((recvd < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) + break; + + if(recvd < 0) + return CURLE_RECV_ERROR; + + recvd = quiche_conn_recv(qs->conn, buf, recvd); + if(recvd == QUICHE_ERR_DONE) + break; + + if(recvd < 0) + return CURLE_RECV_ERROR; + } while(1); + + return CURLE_OK; +} + +static CURLcode flush_egress(struct connectdata *conn, int sockfd) +{ + ssize_t sent; + struct quicsocket *qs = &conn->quic; + static uint8_t out[1200]; + + do { + sent = quiche_conn_send(qs->conn, out, sizeof(out)); + if(sent == QUICHE_ERR_DONE) + break; + + if(sent < 0) + return CURLE_SEND_ERROR; + + sent = send(sockfd, out, sent, 0); + if(sent < 0) + return CURLE_SEND_ERROR; + } while(1); + + return CURLE_OK; +} + +static ssize_t quic_stream_recv(struct connectdata *conn, + int sockindex, + char *buf, + size_t buffersize, + CURLcode *curlcode) +{ + bool fin; + ssize_t recvd; + struct quicsocket *qs = &conn->quic; + curl_socket_t sockfd = conn->sock[sockindex]; + + if(process_ingress(conn, sockfd)) { + *curlcode = CURLE_RECV_ERROR; + return -1; + } + + recvd = quiche_conn_stream_recv(qs->conn, 0, (uint8_t *) buf, buffersize, &fin); + if(recvd == QUICHE_ERR_DONE) { + *curlcode = CURLE_AGAIN; + return -1; + } + + if(recvd < 0) { + *curlcode = CURLE_RECV_ERROR; + return -1; + } + + *curlcode = CURLE_OK; + return recvd; +} + +static ssize_t quic_stream_send(struct connectdata *conn, + int sockindex, + const void *mem, + size_t len, + CURLcode *curlcode) +{ + ssize_t sent; + struct quicsocket *qs = &conn->quic; + curl_socket_t sockfd = conn->sock[sockindex]; + + sent = quiche_conn_stream_send(qs->conn, 0, mem, len, true); + if(sent < 0) { + *curlcode = CURLE_SEND_ERROR; + return -1; + } + + if(flush_egress(conn, sockfd)) { + *curlcode = CURLE_SEND_ERROR; + return -1; + } + + *curlcode = CURLE_OK; + return sent; +} + +/* + * Store quiche version info in this buffer, Prefix with a space. Return total + * length written. + */ +int Curl_quic_ver(char *p, size_t len) +{ + return msnprintf(p, len, " quiche"); +} + +#endif diff --git a/lib/vquic/quiche.h b/lib/vquic/quiche.h new file mode 100644 index 000000000..cf5432962 --- /dev/null +++ b/lib/vquic/quiche.h @@ -0,0 +1,47 @@ +#ifndef HEADER_CURL_VQUIC_QUICHE_H +#define HEADER_CURL_VQUIC_QUICHE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_QUICHE + +#include <quiche.h> + +struct quic_handshake { + char *buf; /* pointer to the buffer */ + size_t alloclen; /* size of allocation */ + size_t len; /* size of content in buffer */ + size_t nread; /* how many bytes have been read */ +}; + +struct quicsocket { + quiche_config *cfg; + quiche_conn *conn; + uint8_t scid[QUICHE_MAX_CONN_ID_LEN]; + uint32_t version; +}; + +#endif + +#endif /* HEADER_CURL_VQUIC_QUICHE_H */ diff --git a/src/tool_cfgable.h b/src/tool_cfgable.h index 848123e7c..d43f03c40 100644 --- a/src/tool_cfgable.h +++ b/src/tool_cfgable.h @@ -258,6 +258,7 @@ struct OperationConfig { 0 is valid. default: CURL_HET_DEFAULT. */ bool haproxy_protocol; /* whether to send HAProxy protocol v1 */ bool disallow_username_in_url; /* disallow usernames in URLs */ + bool h3direct; /* go HTTP/3 directly */ struct GlobalConfig *global; struct OperationConfig *prev; struct OperationConfig *next; /* Always last in the struct */ diff --git a/src/tool_getparam.c b/src/tool_getparam.c index ae0902613..d0336351a 100644 --- a/src/tool_getparam.c +++ b/src/tool_getparam.c @@ -200,6 +200,7 @@ static const struct LongShort aliases[]= { {"01", "http1.1", ARG_NONE}, {"02", "http2", ARG_NONE}, {"03", "http2-prior-knowledge", ARG_NONE}, + {"04", "http3-direct", ARG_NONE}, {"09", "http0.9", ARG_BOOL}, {"1", "tlsv1", ARG_NONE}, {"10", "tlsv1.0", ARG_NONE}, @@ -1189,10 +1190,14 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */ /* HTTP version 2.0 */ config->httpversion = CURL_HTTP_VERSION_2_0; break; - case '3': + case '3': /* --http2-prior-knowledge */ /* HTTP version 2.0 over clean TCP*/ config->httpversion = CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE; break; + case '4': /* --http3-direct */ + /* HTTP version 3 over QUIC - at once */ + config->h3direct = toggle; + break; case '9': /* Allow HTTP/0.9 responses! */ config->http09_allowed = toggle; diff --git a/src/tool_help.c b/src/tool_help.c index a8f285a00..a5b6e7204 100644 --- a/src/tool_help.c +++ b/src/tool_help.c @@ -191,6 +191,8 @@ static const struct helptxt helptext[] = { "Use HTTP 2"}, {" --http2-prior-knowledge", "Use HTTP 2 without HTTP/1.1 Upgrade"}, + {" --http3-direct", + "Use HTTP v3"}, {" --ignore-content-length", "Ignore the size of the remote resource"}, {"-i, --include", @@ -530,6 +532,7 @@ static const struct feat feats[] = { {"CharConv", CURL_VERSION_CONV}, {"TLS-SRP", CURL_VERSION_TLSAUTH_SRP}, {"HTTP2", CURL_VERSION_HTTP2}, + {"HTTP3", CURL_VERSION_HTTP3}, {"UnixSockets", CURL_VERSION_UNIX_SOCKETS}, {"HTTPS-proxy", CURL_VERSION_HTTPS_PROXY}, {"MultiSSL", CURL_VERSION_MULTI_SSL}, diff --git a/src/tool_operate.c b/src/tool_operate.c index 238d87c9f..14fffda36 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -1118,6 +1118,9 @@ static CURLcode create_transfers(struct GlobalConfig *global, if(config->tcp_fastopen) my_setopt(curl, CURLOPT_TCP_FASTOPEN, 1L); + if(config->h3direct) + my_setopt(curl, CURLOPT_H3, CURLH3_DIRECT); + /* where to store */ my_setopt(curl, CURLOPT_WRITEDATA, per); my_setopt(curl, CURLOPT_INTERLEAVEDATA, per); |