summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Banks <nibanks@microsoft.com>2022-04-10 18:21:37 +0200
committerDaniel Stenberg <daniel@haxx.se>2022-04-10 18:23:04 +0200
commit37492ebbfa24ba4e700e6655b3dbc2bdd65c894a (patch)
treee0850c91ec4a66c654463d2ede04cd9c038288fd
parent7befbe9ce97d6f9a1525a0fcbf5cbc5ad50546e3 (diff)
downloadcurl-37492ebbfa24ba4e700e6655b3dbc2bdd65c894a.tar.gz
msh3: add support for QUIC and HTTP/3 using msh3
Considered experimental, as the other HTTP/3 backends. Closes #8517
-rw-r--r--.gitignore1
-rw-r--r--CMake/FindMSH3.cmake68
-rw-r--r--CMakeLists.txt10
-rw-r--r--Makefile.am1
-rw-r--r--configure.ac77
-rw-r--r--docs/HTTP3.md49
-rw-r--r--lib/Makefile.inc2
-rw-r--r--lib/altsvc.c2
-rw-r--r--lib/config-os400.h5
-rw-r--r--lib/curl_config.h.cmake5
-rw-r--r--lib/curl_setup.h2
-rw-r--r--lib/http.h44
-rw-r--r--lib/quic.h5
-rw-r--r--lib/vquic/msh3.c498
-rw-r--r--lib/vquic/msh3.h38
-rw-r--r--winbuild/Makefile.vc22
-rw-r--r--winbuild/MakefileBuild.vc26
-rw-r--r--winbuild/README.md2
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