diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | CMake/FindMSH3.cmake | 68 | ||||
-rw-r--r-- | CMakeLists.txt | 10 | ||||
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | configure.ac | 77 | ||||
-rw-r--r-- | docs/HTTP3.md | 49 | ||||
-rw-r--r-- | lib/Makefile.inc | 2 | ||||
-rw-r--r-- | lib/altsvc.c | 2 | ||||
-rw-r--r-- | lib/config-os400.h | 5 | ||||
-rw-r--r-- | lib/curl_config.h.cmake | 5 | ||||
-rw-r--r-- | lib/curl_setup.h | 2 | ||||
-rw-r--r-- | lib/http.h | 44 | ||||
-rw-r--r-- | lib/quic.h | 5 | ||||
-rw-r--r-- | lib/vquic/msh3.c | 498 | ||||
-rw-r--r-- | lib/vquic/msh3.h | 38 | ||||
-rw-r--r-- | winbuild/Makefile.vc | 22 | ||||
-rw-r--r-- | winbuild/MakefileBuild.vc | 26 | ||||
-rw-r--r-- | winbuild/README.md | 2 |
18 files changed, 850 insertions, 7 deletions
diff --git a/.gitignore b/.gitignore index e35747146..4807c9beb 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ .project .settings /.vs +/bld/ /build/ /builds/ /stats/ diff --git a/CMake/FindMSH3.cmake b/CMake/FindMSH3.cmake new file mode 100644 index 000000000..1b8b9d83a --- /dev/null +++ b/CMake/FindMSH3.cmake @@ -0,0 +1,68 @@ +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) 1998 - 2022, 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.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. +# +########################################################################### + +#[=======================================================================[.rst: +FindMSH3 +---------- + +Find the msh3 library + +Result Variables +^^^^^^^^^^^^^^^^ + +``MSH3_FOUND`` + System has msh3 +``MSH3_INCLUDE_DIRS`` + The msh3 include directories. +``MSH3_LIBRARIES`` + The libraries needed to use msh3 +#]=======================================================================] +if(UNIX) + find_package(PkgConfig QUIET) + pkg_search_module(PC_MSH3 libmsh3) +endif() + +find_path(MSH3_INCLUDE_DIR msh3.h + HINTS + ${PC_MSH3_INCLUDEDIR} + ${PC_MSH3_INCLUDE_DIRS} +) + +find_library(MSH3_LIBRARY NAMES msh3 + HINTS + ${PC_MSH3_LIBDIR} + ${PC_MSH3_LIBRARY_DIRS} +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(MSH3 + REQUIRED_VARS + MSH3_LIBRARY + MSH3_INCLUDE_DIR +) + +if(MSH3_FOUND) + set(MSH3_LIBRARIES ${MSH3_LIBRARY}) + set(MSH3_INCLUDE_DIRS ${MSH3_INCLUDE_DIR}) +endif() + +mark_as_advanced(MSH3_INCLUDE_DIRS MSH3_LIBRARIES) diff --git a/CMakeLists.txt b/CMakeLists.txt index b77de6d5e..6957f619d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -556,6 +556,16 @@ if(USE_QUICHE) cmake_pop_check_state() endif() +option(USE_MSH3 "Use msquic library for HTTP/3 support" OFF) +if(USE_MSH3) + if(USE_NGTCP2 OR USE_QUICHE) + message(FATAL_ERROR "Only one HTTP/3 backend can be selected!") + endif() + set(USE_MSH3 ON) + include_directories(${MSH3_INCLUDE_DIRS}) + list(APPEND CURL_LIBS ${MSH3_LIBRARIES}) +endif() + if(NOT CURL_DISABLE_LDAP) if(WIN32) option(USE_WIN32_LDAP "Use Windows LDAP implementation" ON) diff --git a/Makefile.am b/Makefile.am index 97dfc77ab..f2fa800f9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -36,6 +36,7 @@ CMAKE_DIST = \ CMake/FindGSS.cmake \ CMake/FindLibSSH2.cmake \ CMake/FindMbedTLS.cmake \ + CMake/FindMSH3.cmake \ CMake/FindNGHTTP2.cmake \ CMake/FindNGHTTP3.cmake \ CMake/FindNGTCP2.cmake \ diff --git a/configure.ac b/configure.ac index b5755fb76..2580f6a8d 100644 --- a/configure.ac +++ b/configure.ac @@ -170,7 +170,7 @@ curl_verbose_msg="enabled (--disable-verbose)" ssl_backends= curl_h1_msg="enabled (internal)" curl_h2_msg="no (--with-nghttp2, --with-hyper)" - curl_h3_msg="no (--with-ngtcp2, --with-quiche)" + curl_h3_msg="no (--with-ngtcp2, --with-quiche --with-msh3)" enable_altsvc="yes" hsts="yes" @@ -3037,6 +3037,78 @@ AC_INCLUDES_DEFAULT fi dnl ********************************************************************** +dnl Check for msh3 (QUIC) +dnl ********************************************************************** + +OPT_MSH3="no" + +if test "x$disable_http" = "xyes" -o "x$USE_NGTCP" = "x1"; then + # without HTTP or with ngtcp2, msh3 is no use + OPT_MSH3="no" +fi + +AC_ARG_WITH(msh3, +AS_HELP_STRING([--with-msh3=PATH],[Enable msh3 usage]) +AS_HELP_STRING([--without-msh3],[Disable msh3 usage]), + [OPT_MSH3=$withval]) +case "$OPT_MSH3" in + no) + dnl --without-msh3 option used + want_msh3="no" + ;; + yes) + dnl --with-msh3 option used without path + want_msh3="default" + want_msh3_path="" + ;; + *) + dnl --with-msh3 option used with path + want_msh3="yes" + want_msh3_path="$withval" + ;; +esac + +if test X"$want_msh3" != Xno; then + + if test "$NGHTTP3_ENABLED" = 1; then + AC_MSG_ERROR([--with-msh3 and --with-ngtcp2 are mutually exclusive]) + fi + + dnl backup the pre-msh3 variables + CLEANLDFLAGS="$LDFLAGS" + CLEANCPPFLAGS="$CPPFLAGS" + CLEANLIBS="$LIBS" + + if test -n "$want_msh3_path"; then + LD_MSH3="-L$want_msh3_path/lib" + CPP_MSH3="-I$want_msh3_path/include" + DIR_MSH3="$want_msh3_path/lib" + LDFLAGS="$LDFLAGS $LD_MSH3" + CPPFLAGS="$CPPFLAGS $CPP_MSH3" + fi + LIBS="-lmsh3 $LIBS" + + AC_CHECK_LIB(msh3, MsH3ApiOpen, + [ + AC_CHECK_HEADERS(msh3.h, + curl_h3_msg="enabled (msh3)" + MSH3_ENABLED=1 + AC_DEFINE(USE_MSH3, 1, [if msh3 is in use]) + AC_SUBST(USE_MSH3, [1]) + CURL_LIBRARY_PATH="$CURL_LIBRARY_PATH:$DIR_MSH3" + export CURL_LIBRARY_PATH + AC_MSG_NOTICE([Added $DIR_MSH3 to CURL_LIBRARY_PATH]), + experimental="$experimental HTTP3" + ) + ], + dnl not found, revert back to clean variables + LDFLAGS=$CLEANLDFLAGS + CPPFLAGS=$CLEANCPPFLAGS + LIBS=$CLEANLIBS + ) +fi + +dnl ********************************************************************** dnl Check for zsh completion path dnl ********************************************************************** @@ -4146,7 +4218,8 @@ if test "x$USE_NGHTTP2" = "x1" -o "x$USE_HYPER" = "x1"; then SUPPORT_FEATURES="$SUPPORT_FEATURES HTTP2" fi -if test "x$USE_NGTCP2" = "x1" -o "x$USE_QUICHE" = "x1"; then +if test "x$USE_NGTCP2" = "x1" -o "x$USE_QUICHE" = "x1" \ + -o "x$USE_MSH3" = "x1"; then SUPPORT_FEATURES="$SUPPORT_FEATURES HTTP3" fi diff --git a/docs/HTTP3.md b/docs/HTTP3.md index 56f87ce95..7cb8a0203 100644 --- a/docs/HTTP3.md +++ b/docs/HTTP3.md @@ -19,6 +19,8 @@ QUIC libraries we are experimenting with: [quiche](https://github.com/cloudflare/quiche) +[msquic](https://github.com/microsoft/msquic) & [msh3](https://github.com/nibanks/msh3) + ## Experimental HTTP/3 and QUIC support in curl is considered **EXPERIMENTAL** until further @@ -136,6 +138,53 @@ Build curl: If `make install` results in `Permission denied` error, you will need to prepend it with `sudo`. +# msh3 (msquic) version + +## Build Linux (with quictls fork of OpenSSL) + +Build msh3: + + % git clone -b v0.1.0 --single-branch --recursive https://github.com/nibanks/msh3 + % cd msh3 && mkdir build && cd build + % cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=RelWithDebInfo .. + % cmake --build . + % cmake --install . + +Build curl: + + % git clone https://github.com/curl/curl + % cd curl + % autoreconf -fi + % ./configure LDFLAGS="-Wl,-rpath,/usr/local/lib" --with-msh3=/usr/local --with-openssl + % make + % make install + +Run from `/usr/local/bin/curl`. + +## Build Windows + +Build msh3: + + % git clone -b v0.2.0 --single-branch --recursive https://github.com/nibanks/msh3 + % cd msh3 && mkdir build && cd build + % cmake -G 'Visual Studio 17 2022' -DCMAKE_BUILD_TYPE=RelWithDebInfo .. + % cmake --build . --config Release + % cmake --install . --config Release + +> **Note** - On Windows, Schannel will be used for TLS support by default. If you with to use (the quictls fork of) OpenSSL, specify the `-DQUIC_TLS=openssl` option to the generate command above. Also note that OpenSSL brings with it an additional set of build dependencies not specified here. + +Build curl (in [Visual Studio Command prompt](../winbuild/README.md#open-a-command-prompt)): + + % git clone https://github.com/curl/curl + % cd curl/winbuild + % nmake /f Makefile.vc mode=dll WITH_MSH3=dll MSH3_PATH="C:/Program Files/msh3" MACHINE=x64 + +Note: If you encouter a build error with `tool_hugehelp.c` being missing, rename `tool_hugehelp.c.cvs` in the same directory to `tool_hugehelp.c` and then run `nmake` again. + +Run in the `C:/Program Files/msh3/lib` directory, copy `curl.exe` to that directory, or copy `msquic.dll` and `msh3.dll` from that directory to the `curl.exe` directory. For example: + + % C:\Program Files\msh3\lib> F:\curl\builds\libcurl-vc-x64-release-dll-ipv6-sspi-schannel-msh3\bin\curl.exe --http3 https://www.google.com + # `--http3` Use HTTP/3 directly: diff --git a/lib/Makefile.inc b/lib/Makefile.inc index b39691b24..cfd56d719 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -76,11 +76,13 @@ LIB_VTLS_HFILES = \ vtls/x509asn1.h LIB_VQUIC_CFILES = \ + vquic/msh3.c \ vquic/ngtcp2.c \ vquic/quiche.c \ vquic/vquic.c LIB_VQUIC_HFILES = \ + vquic/msh3.h \ vquic/ngtcp2.h \ vquic/quiche.h \ vquic/vquic.h diff --git a/lib/altsvc.c b/lib/altsvc.c index 759e862c8..45929a5df 100644 --- a/lib/altsvc.c +++ b/lib/altsvc.c @@ -54,6 +54,8 @@ #define H3VERSION "h3-29" #elif defined(USE_NGTCP2) && !defined(UNITTESTS) #define H3VERSION "h3-29" +#elif defined(USE_MSH3) && !defined(UNITTESTS) +#define H3VERSION "h3-29" #else #define H3VERSION "h3" #endif diff --git a/lib/config-os400.h b/lib/config-os400.h index d68cb4b18..dc9e20660 100644 --- a/lib/config-os400.h +++ b/lib/config-os400.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2022, 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 @@ -371,7 +371,8 @@ /* Define if you can safely include both <sys/time.h> and <time.h>. */ #define TIME_WITH_SYS_TIME -/* Define to enable HTTP3 support (experimental, requires NGTCP2 or QUICHE) */ +/* Define to enable HTTP3 support (experimental, requires NGTCP2, QUICHE or + MSH3) */ #undef ENABLE_QUIC /* Version number of package */ diff --git a/lib/curl_config.h.cmake b/lib/curl_config.h.cmake index d2a0f438c..6b55a3c55 100644 --- a/lib/curl_config.h.cmake +++ b/lib/curl_config.h.cmake @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2022, 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 @@ -949,6 +949,9 @@ ${SIZEOF_TIME_T_CODE} /* Define to 1 if you have the quiche_conn_set_qlog_fd function. */ #cmakedefine HAVE_QUICHE_CONN_SET_QLOG_FD 1 +/* to enable msh3 */ +#cmakedefine USE_MSH3 1 + /* if Unix domain sockets are enabled */ #cmakedefine USE_UNIX_SOCKETS diff --git a/lib/curl_setup.h b/lib/curl_setup.h index 25c667477..0babb032c 100644 --- a/lib/curl_setup.h +++ b/lib/curl_setup.h @@ -794,7 +794,7 @@ int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, #define USE_HTTP2 #endif -#if defined(USE_NGTCP2) || defined(USE_QUICHE) +#if defined(USE_NGTCP2) || defined(USE_QUICHE) || defined(USE_MSH3) #define ENABLE_QUIC #endif diff --git a/lib/http.h b/lib/http.h index 07e963dc4..0972261e6 100644 --- a/lib/http.h +++ b/lib/http.h @@ -38,6 +38,10 @@ typedef enum { #include <nghttp2/nghttp2.h> #endif +#if defined(_WIN32) && defined(ENABLE_QUIC) +#include <stdint.h> +#endif + extern const struct Curl_handler Curl_handler_http; #ifdef USE_SSL @@ -163,6 +167,29 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data); struct h3out; /* see ngtcp2 */ #endif +#ifdef USE_MSH3 +#ifdef _WIN32 +#define msh3_lock CRITICAL_SECTION +#define msh3_lock_initialize(lock) InitializeCriticalSection(lock) +#define msh3_lock_uninitialize(lock) DeleteCriticalSection(lock) +#define msh3_lock_acquire(lock) EnterCriticalSection(lock) +#define msh3_lock_release(lock) LeaveCriticalSection(lock) +#else /* !_WIN32 */ +#include <pthread.h> +#define msh3_lock pthread_mutex_t +#define msh3_lock_initialize(lock) { \ + pthread_mutexattr_t attr; \ + pthread_mutexattr_init(&attr); \ + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \ + pthread_mutex_init(lock, &attr); \ + pthread_mutexattr_destroy(&attr); \ +} +#define msh3_lock_uninitialize(lock) pthread_mutex_destroy(lock) +#define msh3_lock_acquire(lock) pthread_mutex_lock(lock) +#define msh3_lock_release(lock) pthread_mutex_unlock(lock) +#endif /* _WIN32 */ +#endif /* USE_MSH3 */ + /**************************************************************************** * HTTP unique setup ***************************************************************************/ @@ -228,11 +255,13 @@ struct HTTP { #endif #ifdef ENABLE_QUIC +#ifndef USE_MSH3 /*********** for HTTP/3 we store stream-local data here *************/ int64_t stream3_id; /* stream we are interested in */ bool firstheader; /* FALSE until headers arrive */ bool firstbody; /* FALSE until body arrives */ bool h3req; /* FALSE until request is issued */ +#endif bool upload_done; #endif #ifdef USE_NGHTTP3 @@ -240,6 +269,21 @@ struct HTTP { struct h3out *h3out; /* per-stream buffers for upload */ struct dynbuf overflow; /* excess data received during a single Curl_read */ #endif +#ifdef USE_MSH3 + struct MSH3_REQUEST *req; + msh3_lock recv_lock; + /* Receive Buffer (Headers and Data) */ + uint8_t* recv_buf; + size_t recv_buf_alloc; + /* Receive Headers */ + size_t recv_header_len; + bool recv_header_complete; + /* Receive Data */ + size_t recv_data_len; + bool recv_data_complete; + /* General Receive Error */ + CURLcode recv_error; +#endif }; #ifdef USE_NGHTTP2 diff --git a/lib/quic.h b/lib/quic.h index b030359dd..f92720f5d 100644 --- a/lib/quic.h +++ b/lib/quic.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2022, 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 @@ -31,6 +31,9 @@ #ifdef USE_QUICHE #include "vquic/quiche.h" #endif +#ifdef USE_MSH3 +#include "vquic/msh3.h" +#endif #include "urldata.h" diff --git a/lib/vquic/msh3.c b/lib/vquic/msh3.c new file mode 100644 index 000000000..be18e6e83 --- /dev/null +++ b/lib/vquic/msh3.c @@ -0,0 +1,498 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2022, 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.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_MSH3 + +#include "urldata.h" +#include "curl_printf.h" +#include "timeval.h" +#include "multiif.h" +#include "sendf.h" +#include "connect.h" +#include "h2h3.h" +#include "msh3.h" + +/* #define DEBUG_HTTP3 1 */ +#ifdef DEBUG_HTTP3 +#define H3BUGF(x) x +#else +#define H3BUGF(x) do { } while(0) +#endif + +#define MSH3_REQ_INIT_BUF_LEN 8192 + +static CURLcode msh3_do_it(struct Curl_easy *data, bool *done); +static int msh3_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks); +static CURLcode msh3_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection); +static unsigned int msh3_conncheck(struct Curl_easy *data, + struct connectdata *conn, + unsigned int checks_to_perform); +static Curl_recv msh3_stream_recv; +static Curl_send msh3_stream_send; +static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, + void *IfContext, + const MSH3_HEADER *Header); +static void MSH3_CALL msh3_data_received(MSH3_REQUEST *Request, + void *IfContext, uint32_t Length, + const uint8_t *Data); +static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext, + bool Aborted, uint64_t AbortError); +static void MSH3_CALL msh3_shutdown(MSH3_REQUEST *Request, void *IfContext); + +static const struct Curl_handler msh3_curl_handler_http3 = { + "HTTPS", /* scheme */ + ZERO_NULL, /* setup_connection */ + msh3_do_it, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + msh3_getsock, /* proto_getsock */ + msh3_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + msh3_getsock, /* perform_getsock */ + msh3_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + msh3_conncheck, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_HTTP, /* defport */ + CURLPROTO_HTTPS, /* protocol */ + CURLPROTO_HTTP, /* family */ + PROTOPT_SSL | PROTOPT_STREAM /* flags */ +}; + +static const MSH3_REQUEST_IF msh3_request_if = { + msh3_header_received, + msh3_data_received, + msh3_complete, + msh3_shutdown +}; + +void Curl_quic_ver(char *p, size_t len) +{ + (void)msnprintf(p, len, "msh3/%s", "0.0.1"); +} + +CURLcode Curl_quic_connect(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t sockfd, + int sockindex, + const struct sockaddr *addr, + socklen_t addrlen) +{ + struct quicsocket *qs = &conn->hequic[sockindex]; + bool unsecure = !conn->ssl_config.verifypeer; + memset(qs, 0, sizeof(*qs)); + + (void)sockfd; + (void)addr; /* TODO - Pass address along */ + (void)addrlen; + + H3BUGF(infof(data, "creating new api/connection")); + + qs->api = MsH3ApiOpen(); + if(!qs->api) { + failf(data, "can't create msh3 api"); + return CURLE_FAILED_INIT; + } + + qs->conn = MsH3ConnectionOpen(qs->api, conn->host.name, unsecure); + if(!qs->conn) { + failf(data, "can't create msh3 connection"); + if(qs->api) { + MsH3ApiClose(qs->api); + } + return CURLE_FAILED_INIT; + } + + return CURLE_OK; +} + +CURLcode Curl_quic_is_connected(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + bool *connected) +{ + struct quicsocket *qs = &conn->hequic[sockindex]; + MSH3_CONNECTION_STATE state; + + state = MsH3ConnectionGetState(qs->conn, false); + if(state == MSH3_CONN_HANDSHAKE_FAILED || state == MSH3_CONN_DISCONNECTED) { + failf(data, "failed to connect, state=%u", (uint32_t)state); + return CURLE_COULDNT_CONNECT; + } + + if(state == MSH3_CONN_CONNECTED) { + H3BUGF(infof(data, "connection connected")); + *connected = true; + conn->quic = qs; + conn->recv[sockindex] = msh3_stream_recv; + conn->send[sockindex] = msh3_stream_send; + conn->handler = &msh3_curl_handler_http3; + conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + conn->httpversion = 30; + conn->bundle->multiuse = BUNDLE_MULTIPLEX; + /* TODO - Clean up other happy-eyeballs connection(s)? */ + } + + return CURLE_OK; +} + +static int msh3_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks) +{ + struct HTTP *stream = data->req.p.http; + int bitmap = GETSOCK_BLANK; + + socks[0] = conn->sock[FIRSTSOCKET]; + + if(stream->recv_error) { + bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); + data->state.drain++; + } + else if(stream->recv_header_len || stream->recv_data_len) { + bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); + data->state.drain++; + } + + H3BUGF(infof(data, "msh3_getsock %u", (uint32_t)data->state.drain)); + + return bitmap; +} + +static CURLcode msh3_do_it(struct Curl_easy *data, bool *done) +{ + struct HTTP *stream = data->req.p.http; + H3BUGF(infof(data, "msh3_do_it")); + stream->recv_buf = malloc(MSH3_REQ_INIT_BUF_LEN); + if(!stream->recv_buf) { + return CURLE_OUT_OF_MEMORY; + } + stream->req = ZERO_NULL; + msh3_lock_initialize(&stream->recv_lock); + stream->recv_buf_alloc = MSH3_REQ_INIT_BUF_LEN; + stream->recv_header_len = 0; + stream->recv_header_complete = false; + stream->recv_data_len = 0; + stream->recv_data_complete = false; + stream->recv_error = CURLE_OK; + return Curl_http(data, done); +} + +static unsigned int msh3_conncheck(struct Curl_easy *data, + struct connectdata *conn, + unsigned int checks_to_perform) +{ + (void)data; + (void)conn; + (void)checks_to_perform; + H3BUGF(infof(data, "msh3_conncheck")); + return CONNRESULT_NONE; +} + +static void msh3_cleanup(struct quicsocket *qs, struct HTTP *stream) +{ + if(stream && stream->recv_buf) { + free(stream->recv_buf); + stream->recv_buf = ZERO_NULL; + msh3_lock_uninitialize(&stream->recv_lock); + } + if(qs->conn) { + MsH3ConnectionClose(qs->conn); + qs->conn = ZERO_NULL; + } + if(qs->api) { + MsH3ApiClose(qs->api); + qs->api = ZERO_NULL; + } +} + +static CURLcode msh3_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead_connection) +{ + (void)dead_connection; + H3BUGF(infof(data, "disconnecting (msh3)")); + msh3_cleanup(conn->quic, data->req.p.http); + return CURLE_OK; +} + +void Curl_quic_disconnect(struct Curl_easy *data, struct connectdata *conn, + int tempindex) +{ + if(conn->transport == TRNSPRT_QUIC) { + H3BUGF(infof(data, "disconnecting (curl)")); + msh3_cleanup(&conn->hequic[tempindex], data->req.p.http); + } +} + +/* Requires stream->recv_lock to be held */ +static bool msh3request_ensure_room(struct HTTP *stream, size_t len) +{ + uint8_t *new_recv_buf; + const size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len; + if(cur_recv_len + len > stream->recv_buf_alloc) { + size_t new_recv_buf_alloc_len = stream->recv_buf_alloc; + do { + new_recv_buf_alloc_len <<= 1; /* TODO - handle overflow */ + } while(cur_recv_len + len > new_recv_buf_alloc_len); + new_recv_buf = malloc(new_recv_buf_alloc_len); + if(!new_recv_buf) { + return false; + } + if(cur_recv_len) { + memcpy(new_recv_buf, stream->recv_buf, cur_recv_len); + } + stream->recv_buf_alloc = new_recv_buf_alloc_len; + free(stream->recv_buf); + stream->recv_buf = new_recv_buf; + } + return true; +} + +static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, + void *IfContext, + const MSH3_HEADER *Header) +{ + struct HTTP *stream = IfContext; + size_t total_len; + (void)Request; + H3BUGF(printf("* msh3_header_received\n")); + + if(stream->recv_header_complete) { + H3BUGF(printf("* ignoring header after data\n")); + return; + } + + msh3_lock_acquire(&stream->recv_lock); + + if((Header->NameLength == 7) && + !strncmp(H2H3_PSEUDO_STATUS, (char *)Header->Name, 7)) { + total_len = 9 + Header->ValueLength; + if(!msh3request_ensure_room(stream, total_len)) { + /* TODO - handle error */ + goto release_lock; + } + msnprintf((char *)stream->recv_buf + stream->recv_header_len, + stream->recv_buf_alloc - stream->recv_header_len, + "HTTP/3 %.*s\n", (int)Header->ValueLength, Header->Value); + } + else { + total_len = Header->NameLength + 4 + Header->ValueLength; + if(!msh3request_ensure_room(stream, total_len)) { + /* TODO - handle error */ + goto release_lock; + } + msnprintf((char *)stream->recv_buf + stream->recv_header_len, + stream->recv_buf_alloc - stream->recv_header_len, + "%.*s: %.*s\n", + (int)Header->NameLength, Header->Name, + (int)Header->ValueLength, Header->Value); + } + + stream->recv_header_len += total_len - 1; /* don't include null-terminator */ + +release_lock: + msh3_lock_release(&stream->recv_lock); +} + +static void MSH3_CALL msh3_data_received(MSH3_REQUEST *Request, + void *IfContext, uint32_t Length, + const uint8_t *Data) +{ + struct HTTP *stream = IfContext; + size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len; + (void)Request; + H3BUGF(printf("* msh3_data_received %u. %zu buffered, %zu allocated\n", + Length, cur_recv_len, stream->recv_buf_alloc)); + msh3_lock_acquire(&stream->recv_lock); + if(!stream->recv_header_complete) { + H3BUGF(printf("* Headers complete!\n")); + if(!msh3request_ensure_room(stream, 2)) { + /* TODO - handle error */ + goto release_lock; + } + stream->recv_buf[stream->recv_header_len++] = '\r'; + stream->recv_buf[stream->recv_header_len++] = '\n'; + stream->recv_header_complete = true; + cur_recv_len += 2; + } + if(!msh3request_ensure_room(stream, Length)) { + /* TODO - handle error */ + goto release_lock; + } + memcpy(stream->recv_buf + cur_recv_len, Data, Length); + stream->recv_data_len += (size_t)Length; +release_lock: + msh3_lock_release(&stream->recv_lock); +} + +static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext, + bool Aborted, uint64_t AbortError) +{ + struct HTTP *stream = IfContext; + (void)Request; + (void)AbortError; + H3BUGF(printf("* msh3_complete, aborted=%hhu\n", Aborted)); + msh3_lock_acquire(&stream->recv_lock); + if(Aborted) { + stream->recv_error = CURLE_HTTP3; /* TODO - how do we pass AbortError? */ + } + stream->recv_header_complete = true; + stream->recv_data_complete = true; + msh3_lock_release(&stream->recv_lock); +} + +static void MSH3_CALL msh3_shutdown(MSH3_REQUEST *Request, void *IfContext) +{ + struct HTTP *stream = IfContext; + (void)Request; + (void)stream; +} + +static_assert(sizeof(MSH3_HEADER) == sizeof(struct h2h3pseudo), + "Sizes must match for cast below to work"); + +static ssize_t msh3_stream_send(struct Curl_easy *data, + int sockindex, + const void *mem, + size_t len, + CURLcode *curlcode) +{ + struct connectdata *conn = data->conn; + struct HTTP *stream = data->req.p.http; + struct quicsocket *qs = conn->quic; + struct h2h3req *hreq; + + (void)sockindex; + H3BUGF(infof(data, "msh3_stream_send %zu", len)); + + if(!stream->req) { + *curlcode = Curl_pseudo_headers(data, mem, len, &hreq); + if(*curlcode) { + failf(data, "Curl_pseudo_headers failed"); + return -1; + } + H3BUGF(infof(data, "starting request with %zu headers", hreq->entries)); + stream->req = MsH3RequestOpen(qs->conn, &msh3_request_if, stream, + (MSH3_HEADER*)hreq->header, hreq->entries); + Curl_pseudo_free(hreq); + if(!stream->req) { + failf(data, "request open failed"); + *curlcode = CURLE_SEND_ERROR; + return -1; + } + *curlcode = CURLE_OK; + return len; + } + H3BUGF(infof(data, "send %zd body bytes on request %p", len, + (void *)stream->req)); + *curlcode = CURLE_SEND_ERROR; + return -1; +} + +static ssize_t msh3_stream_recv(struct Curl_easy *data, + int sockindex, + char *buf, + size_t buffersize, + CURLcode *curlcode) +{ + struct HTTP *stream = data->req.p.http; + size_t outsize = 0; + (void)sockindex; + H3BUGF(infof(data, "msh3_stream_recv %zu", buffersize)); + + if(stream->recv_error) { + failf(data, "request aborted"); + *curlcode = stream->recv_error; + return -1; + } + + msh3_lock_acquire(&stream->recv_lock); + + if(stream->recv_header_len) { + outsize = buffersize; + if(stream->recv_header_len < outsize) { + outsize = stream->recv_header_len; + } + memcpy(buf, stream->recv_buf, outsize); + if(outsize < stream->recv_header_len + stream->recv_data_len) { + memmove(stream->recv_buf, stream->recv_buf + outsize, + stream->recv_header_len + stream->recv_data_len - outsize); + } + stream->recv_header_len -= outsize; + H3BUGF(infof(data, "returned %zu bytes of headers", outsize)); + } + else if(stream->recv_data_len) { + outsize = buffersize; + if(stream->recv_data_len < outsize) { + outsize = stream->recv_data_len; + } + memcpy(buf, stream->recv_buf, outsize); + if(outsize < stream->recv_data_len) { + memmove(stream->recv_buf, stream->recv_buf + outsize, + stream->recv_data_len - outsize); + } + stream->recv_data_len -= outsize; + H3BUGF(infof(data, "returned %zu bytes of data", outsize)); + } + else if(stream->recv_data_complete) { + H3BUGF(infof(data, "receive complete")); + } + + msh3_lock_release(&stream->recv_lock); + + return (ssize_t)outsize; +} + +CURLcode Curl_quic_done_sending(struct Curl_easy *data) +{ + struct connectdata *conn = data->conn; + H3BUGF(infof(data, "Curl_quic_done_sending")); + if(conn->handler == &msh3_curl_handler_http3) { + struct HTTP *stream = data->req.p.http; + stream->upload_done = TRUE; + } + + return CURLE_OK; +} + +void Curl_quic_done(struct Curl_easy *data, bool premature) +{ + (void)data; + (void)premature; + H3BUGF(infof(data, "Curl_quic_done")); +} + +bool Curl_quic_data_pending(const struct Curl_easy *data) +{ + struct HTTP *stream = data->req.p.http; + H3BUGF(infof((struct Curl_easy *)data, "Curl_quic_data_pending")); + return stream->recv_header_len || stream->recv_data_len; +} + +#endif /* USE_MSH3 */ diff --git a/lib/vquic/msh3.h b/lib/vquic/msh3.h new file mode 100644 index 000000000..bacdcb132 --- /dev/null +++ b/lib/vquic/msh3.h @@ -0,0 +1,38 @@ +#ifndef HEADER_CURL_VQUIC_MSH3_H +#define HEADER_CURL_VQUIC_MSH3_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2022, 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.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_MSH3 + +#include <msh3.h> + +struct quicsocket { + MSH3_API* api; + MSH3_CONNECTION* conn; +}; + +#endif /* USE_MSQUIC */ + +#endif /* HEADER_CURL_VQUIC_MSH3_H */ diff --git a/winbuild/Makefile.vc b/winbuild/Makefile.vc index 54da51e10..48b0acc37 100644 --- a/winbuild/Makefile.vc +++ b/winbuild/Makefile.vc @@ -149,6 +149,23 @@ NGHTTP2 = static USE_NGHTTP2 = false !ENDIF +!IF "$(ENABLE_MSH3)"=="yes" +# compatibility bit, WITH_MSH3 is the correct flag +WITH_MSH3 = dll +USE_MSH3 = true +MSH3 = dll +!ELSEIF "$(WITH_MSH3)"=="dll" +USE_MSH3 = true +MSH3 = dll +!ELSEIF "$(WITH_MSH3)"=="static" +USE_MSH3 = true +MSH3 = static +!ENDIF + +!IFNDEF USE_MSH3 +USE_MSH3 = false +!ENDIF + !IF "$(WITH_MBEDTLS)"=="dll" || "$(WITH_MBEDTLS)"=="static" USE_MBEDTLS = true MBEDTLS = $(WITH_MBEDTLS) @@ -240,6 +257,10 @@ CONFIG_NAME_LIB = $(CONFIG_NAME_LIB)-schannel CONFIG_NAME_LIB = $(CONFIG_NAME_LIB)-nghttp2-$(NGHTTP2) !ENDIF +!IF "$(USE_MSH3)"=="true" +CONFIG_NAME_LIB = $(CONFIG_NAME_LIB)-msh3 +!ENDIF + !MESSAGE configuration name: $(CONFIG_NAME_LIB) BUILD_DIR=../builds/$(CONFIG_NAME_LIB) @@ -261,6 +282,7 @@ $(MODE): @SET CONFIG_NAME_LIB=$(CONFIG_NAME_LIB) @SET MACHINE=$(MACHINE) @SET USE_NGHTTP2=$(USE_NGHTTP2) + @SET USE_MSH3=$(USE_MSH3) @SET USE_IDN=$(USE_IDN) @SET USE_IPV6=$(USE_IPV6) @SET USE_SSPI=$(USE_SSPI) diff --git a/winbuild/MakefileBuild.vc b/winbuild/MakefileBuild.vc index c4b8fc94b..33f779467 100644 --- a/winbuild/MakefileBuild.vc +++ b/winbuild/MakefileBuild.vc @@ -164,6 +164,26 @@ NGHTTP2_LIBS = nghttp2.lib !ENDIF
!ENDIF
+!IFDEF MSH3_PATH
+MSH3_INC_DIR = $(MSH3_PATH)\include
+MSH3_LIB_DIR = $(MSH3_PATH)\lib
+MSH3_LFLAGS = $(MSH3_LFLAGS) "/LIBPATH:$(MSH3_LIB_DIR)"
+!ELSE
+MSH3_INC_DIR = $(DEVEL_INCLUDE)
+MSH3_LIB_DIR = $(DEVEL_LIB)
+!ENDIF
+
+!IF "$(WITH_MSH3)"=="dll"
+MSH3_CFLAGS = /DUSE_MSH3 /I"$(MSH3_INC_DIR)"
+MSH3_LIBS = msh3.lib
+!ELSEIF "$(WITH_MSH3)"=="static"
+MSH3_CFLAGS = /DUSE_MSH3 /DMSH3_STATICLIB /I"$(MSH3_INC_DIR)"
+!IF EXISTS("$(NGHTTP2_LIB_DIR)\msh3_static.lib")
+MSH3_LIBS = msh3_static.lib
+!ELSE
+MSH3_LIBS = msh3.lib
+!ENDIF
+!ENDIF
!IFDEF MBEDTLS_PATH
MBEDTLS_INC_DIR = $(MBEDTLS_PATH)\include
@@ -492,6 +512,11 @@ CFLAGS = $(CFLAGS) $(NGHTTP2_CFLAGS) LFLAGS = $(LFLAGS) $(NGHTTP2_LFLAGS) $(NGHTTP2_LIBS)
!ENDIF
+!IF "$(USE_MSH3)"=="true"
+CFLAGS = $(CFLAGS) $(MSH3_CFLAGS)
+LFLAGS = $(LFLAGS) $(MSH3_LFLAGS) $(MSH3_LIBS)
+!ENDIF
+
!IF "$(GEN_PDB)"=="true"
CFLAGS = $(CFLAGS) $(CFLAGS_PDB) /Fd"$(LIB_DIROBJ)\$(PDB)"
LFLAGS = $(LFLAGS) $(LFLAGS_PDB)
@@ -545,6 +570,7 @@ package: $(TARGET) $(TARGET): $(LIB_OBJS) $(LIB_DIROBJ) $(DIRDIST)
@echo Using SSL: $(USE_SSL)
@echo Using NGHTTP2: $(USE_NGHTTP2)
+ @echo Using MSH3: $(USE_MSH3)
@echo Using c-ares: $(USE_CARES)
@echo Using SSH2: $(USE_SSH2)
@echo Using SSH: $(USE_SSH)
diff --git a/winbuild/README.md b/winbuild/README.md index 4c33d4ff6..3f664c010 100644 --- a/winbuild/README.md +++ b/winbuild/README.md @@ -80,6 +80,7 @@ where `<options>` is one or many of: Uncompress them into the deps folder.
- `WITH_SSL=<dll/static>` - Enable OpenSSL support, DLL or static
- `WITH_NGHTTP2=<dll/static>` - Enable HTTP/2 support, DLL or static
+ - `WITH_MSH3=<dll/static>` - Enable (experimental) HTTP/3 support, DLL or static
- `WITH_MBEDTLS=<dll/static>` - Enable mbedTLS support, DLL or static
- `WITH_CARES=<dll/static>` - Enable c-ares support, DLL or static
- `WITH_ZLIB=<dll/static>` - Enable zlib support, DLL or static
@@ -103,6 +104,7 @@ where `<options>` is one or many of: - `CARES_PATH=<path>` - Custom path for c-ares
- `MBEDTLS_PATH=<path>` - Custom path for mbedTLS
- `NGHTTP2_PATH=<path>` - Custom path for nghttp2
+ - `MSH3_PATH=<path>` - Custom path for msh3
- `SSH2_PATH=<path>` - Custom path for libSSH2
- `SSL_PATH=<path>` - Custom path for OpenSSL
- `ZLIB_PATH=<path>` - Custom path for zlib
|