]> granicus.if.org Git - curl/commitdiff
HTTP3: initial (experimental) support
authorDaniel Stenberg <daniel@haxx.se>
Sun, 21 Jul 2019 21:48:58 +0000 (23:48 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Sun, 21 Jul 2019 21:49:03 +0000 (23:49 +0200)
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:
configure.ac
docs/HTTP3.md [new file with mode: 0644]
docs/Makefile.am
docs/cmdline-opts/Makefile.inc
docs/cmdline-opts/http3-direct.d [new file with mode: 0644]
docs/libcurl/curl_easy_setopt.3
docs/libcurl/opts/CURLOPT_H3.3 [new file with mode: 0644]
docs/libcurl/opts/Makefile.inc
docs/libcurl/symbols-in-versions
include/curl/curl.h
lib/Makefile.inc
lib/asyn-thread.c
lib/connect.c
lib/curl_setup.h
lib/hostip6.c
lib/http.c
lib/quic.c [new file with mode: 0644]
lib/quic.h [new file with mode: 0644]
lib/setopt.c
lib/tftp.c
lib/url.c
lib/urldata.h
lib/version.c
lib/vquic/ngtcp2-crypto.c [new file with mode: 0644]
lib/vquic/ngtcp2-crypto.h [new file with mode: 0644]
lib/vquic/ngtcp2.c [new file with mode: 0644]
lib/vquic/ngtcp2.h [new file with mode: 0644]
lib/vquic/quiche.c [new file with mode: 0644]
lib/vquic/quiche.h [new file with mode: 0644]
src/tool_cfgable.h
src/tool_getparam.c
src/tool_help.c
src/tool_operate.c

index 7dd14808573a7e7652d5ba827a930928ed251dee..1ddc3d60100c2591154ecd3fe6a75142877db21f 100755 (executable)
@@ -3337,6 +3337,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 (file)
index 0000000..c09bf0a
--- /dev/null
@@ -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/
index ed6a212e07d79775904923392517bf90e8cc5667..a29c059a1d7291f9c5038f9acc8b639dca9ec406 100644 (file)
@@ -60,6 +60,7 @@ EXTRA_DIST =                                    \
  HISTORY.md                                     \
  HTTP-COOKIES.md                                \
  HTTP2.md                                       \
+ HTTP3.md                                       \
  INSTALL                                        \
  INSTALL.cmake                                  \
  INSTALL.md                                     \
index 886fa6cafe24de3baf68dda477a6168f61244584..d50a8bb2669c033dd8688e9982e7c91a1a619460 100644 (file)
@@ -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 (file)
index 0000000..fb8c8cf
--- /dev/null
@@ -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.
+
index 1f18a3494d6984b4dfa8b247e186a8dcb2247c38..cb5c418fbb5dc9b277cf6842ed0820f0dd57cdc6 100644 (file)
@@ -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 (file)
index 0000000..be521b6
--- /dev/null
@@ -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), "
index c8e15a5ed36491e4a8d02c7bd04c828cdf7f83c1..5460b2a57f4b8bb6618500644e726aeda3079a5a 100644 (file)
@@ -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                              \
index 16fb5622c242cc5b64e01c6c82ff72df2c7d3679..1b452d2458faedb4e4722c417a072e6a41a6de44 100644 (file)
@@ -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
index 25f095a69968abf19c82d383b6408630fe35a0db..215b3e9e1f6cd0eafa16d09b71f46b1f6bda36b7 100644 (file)
@@ -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()
index 37f7026818b6c1a6790051e254bdcd2a1a590240..5e59f39b3af81531182ff278636aeabd55294c7c 100644 (file)
@@ -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)
index 55e0811c5cdc8144d4d57d4afa28f3b81118d90e..5f33c9affd0f27ef1893134d31af62e24c227237 100644 (file)
@@ -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);
 
index 4a1f2c64060c2512d27c156d8b9e3384cba616ae..59084f251dd83bb0b2ea0cf24638727004af551c 100644 (file)
@@ -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))
index 27414a540ecea3cfdc1bc65c21218d79505135e7..afd5441b0b8308a2e95063c513a73106fc67dbd1 100644 (file)
@@ -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 */
index 5511f1aab14b938544c0ef4c825413bf8ae4ea98..e0e0c58dfaacee8adaa9bcd1546e7d773103b47f 100644 (file)
@@ -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
   /*
index 9fbd7201e8c4c96d7fad68370dbd5d08938e574f..36e94f762f846361a601729a74894f79968a5eca 100644 (file)
@@ -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 (file)
index 0000000..746f005
--- /dev/null
@@ -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 (file)
index 0000000..9c90b2e
--- /dev/null
@@ -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 */
index 1dbf00faf7c236d837f3971215d8228c73b8adc3..64a6b010d726ffc6993f469bdd088f593e2abfac 100644 (file)
@@ -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;
index 289cda2825f05356c5e26dc76f1d7ffd2b760f7b..c71e5f0aaf6ec41955d35b1cd51491c2d6eddd0d 100644 (file)
@@ -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! */
index 2b47b235de4f47f7a68276a1ac809b1085beecbe..c1d68bdbc4a27e51d2f6059ec898f9a6dce98074 100644 (file)
--- 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;
index fdc185b2289261f1554b28182855a87112e9b6a0..c8f01aa4f5555e248ee04e202752cba8cece5415 100644 (file)
@@ -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
index 941313dfb871b0c645d11cc5205ee1476852e50f..c1b5a1c51ab51cfcfdc857006fffdf980446a83f 100644 (file)
@@ -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 (file)
index 0000000..dc060c9
--- /dev/null
@@ -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 (file)
index 0000000..f91b4e0
--- /dev/null
@@ -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 (file)
index 0000000..a8c563c
--- /dev/null
@@ -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 (file)
index 0000000..8342c2c
--- /dev/null
@@ -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 (file)
index 0000000..f6c4ad4
--- /dev/null
@@ -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 (file)
index 0000000..cf54329
--- /dev/null
@@ -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 */
index 848123e7c498c4d2daa2697547dffd70df279800..d43f03c402aaef0766979e5650f41b58aa110785 100644 (file)
@@ -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 */
index ae0902613f74a23c2993f737ac99ca5e4ef3366b..d0336351a835a950737175ce2a9c35f14f1d3023 100644 (file)
@@ -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;
index a8f285a005757bc70e90ecc9790d18498eaa595c..a5b6e72043740da38832d8d16c80ef225c0838c6 100644 (file)
@@ -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},
index 238d87c9fd0c75a09e92eb8813a0e1432f15e30d..14fffda36a13ee11f7c444c02ae0db4789ad8446 100644 (file)
@@ -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);