msh3: add support for QUIC and HTTP/3 using msh3

Considered experimental, as the other HTTP/3 backends.

Closes #8517
This commit is contained in:
Nick Banks 2022-04-10 18:21:37 +02:00 committed by Daniel Stenberg
parent 7befbe9ce9
commit 37492ebbfa
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
18 changed files with 850 additions and 7 deletions

1
.gitignore vendored
View File

@ -18,6 +18,7 @@
.project
.settings
/.vs
/bld/
/build/
/builds/
/stats/

68
CMake/FindMSH3.cmake Normal file
View File

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

View File

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

View File

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

View File

@ -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"
@ -3036,6 +3036,78 @@ AC_INCLUDES_DEFAULT
fi
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

498
lib/vquic/msh3.c Normal file
View File

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

38
lib/vquic/msh3.h Normal file
View File

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

View File

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

View File

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

View File

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