summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Stenberg <daniel@haxx.se>2019-07-21 23:48:58 +0200
committerDaniel Stenberg <daniel@haxx.se>2019-07-21 23:49:03 +0200
commit3af0e76d1e71995b7790c74e79b76af86ee7c681 (patch)
treeb80190acaf03d83f5f408cc6da3ec1a9f831d8d3
parent7644abf8e8101910ed86ab2869b7cc4031b27720 (diff)
downloadcurl-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
-rwxr-xr-xconfigure.ac167
-rw-r--r--docs/HTTP3.md85
-rw-r--r--docs/Makefile.am1
-rw-r--r--docs/cmdline-opts/Makefile.inc1
-rw-r--r--docs/cmdline-opts/http3-direct.d16
-rw-r--r--docs/libcurl/curl_easy_setopt.32
-rw-r--r--docs/libcurl/opts/CURLOPT_H3.361
-rw-r--r--docs/libcurl/opts/Makefile.inc1
-rw-r--r--docs/libcurl/symbols-in-versions3
-rw-r--r--include/curl/curl.h8
-rw-r--r--lib/Makefile.inc14
-rw-r--r--lib/asyn-thread.c3
-rw-r--r--lib/connect.c23
-rw-r--r--lib/curl_setup.h4
-rw-r--r--lib/hostip6.c3
-rw-r--r--lib/http.c29
-rw-r--r--lib/quic.c38
-rw-r--r--lib/quic.h54
-rw-r--r--lib/setopt.c8
-rw-r--r--lib/tftp.c2
-rw-r--r--lib/url.c2
-rw-r--r--lib/urldata.h12
-rw-r--r--lib/version.c9
-rw-r--r--lib/vquic/ngtcp2-crypto.c520
-rw-r--r--lib/vquic/ngtcp2-crypto.h93
-rw-r--r--lib/vquic/ngtcp2.c1029
-rw-r--r--lib/vquic/ngtcp2.h65
-rw-r--r--lib/vquic/quiche.c229
-rw-r--r--lib/vquic/quiche.h47
-rw-r--r--src/tool_cfgable.h1
-rw-r--r--src/tool_getparam.c7
-rw-r--r--src/tool_help.c3
-rw-r--r--src/tool_operate.c3
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! */
diff --git a/lib/url.c b/lib/url.c
index 2b47b235d..c1d68bdbc 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -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, &params, 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, &params);
+ 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(
+ &params, 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,
+ &params);
+ 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);