HTTP3: initial (experimental) support

USe configure --with-ngtcp2 or --with-quiche

Using either option will enable a HTTP3 build.
Co-authored-by: Alessandro Ghedini <alessandro@ghedini.me>

Closes #3500
This commit is contained in:
Daniel Stenberg 2019-07-21 23:48:58 +02:00
parent 7644abf8e8
commit 3af0e76d1e
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
33 changed files with 2520 additions and 23 deletions

View File

@ -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

85
docs/HTTP3.md Normal file
View File

@ -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/

View File

@ -60,6 +60,7 @@ EXTRA_DIST = \
HISTORY.md \
HTTP-COOKIES.md \
HTTP2.md \
HTTP3.md \
INSTALL \
INSTALL.cmake \
INSTALL.md \

View 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 \

View File

@ -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.

View 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

View File

@ -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), "

View 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 \

View 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

View 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()

View 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)

View 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);

View 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))

View 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 */

View 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
/*

View 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)

38
lib/quic.c Normal file
View File

@ -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

54
lib/quic.h Normal file
View File

@ -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 */

View 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;

View 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! */

View File

@ -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;

View 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

View 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

520
lib/vquic/ngtcp2-crypto.c Normal file
View File

@ -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

93
lib/vquic/ngtcp2-crypto.h Normal file
View File

@ -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 */

1029
lib/vquic/ngtcp2.c Normal file

File diff suppressed because it is too large Load Diff

65
lib/vquic/ngtcp2.h Normal file
View File

@ -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 */

229
lib/vquic/quiche.c Normal file
View File

@ -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

47
lib/vquic/quiche.h Normal file
View File

@ -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 */

View 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 */

View 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;

View 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},

View 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);