diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index b6b9359d57..181ba34303 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -439,21 +439,6 @@ jobs: -DCURL_USE_WOLFSSL=ON -DUSE_NGTCP2=ON -DUSE_ECH=ON - - name: 'openssl-quic' - install_steps: skipall - PKG_CONFIG_PATH: /home/runner/openssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig - tflags: '--min=1640' - configure: >- - LDFLAGS=-Wl,-rpath,/home/runner/openssl/build/lib - --with-openssl=/home/runner/openssl/build --with-openssl-quic - - - name: 'openssl-quic' - PKG_CONFIG_PATH: /home/runner/openssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig - generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/openssl/build -DUSE_OPENSSL_QUIC=ON - -DCURL_DISABLE_LDAP=ON - -DCMAKE_UNITY_BUILD=ON - - name: 'quiche' install_steps: skipall PKG_CONFIG_PATH: /home/runner/nghttp2/build/lib/pkgconfig diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 09305fe3ea..9ad31ff629 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -232,7 +232,7 @@ jobs: - name: 'OpenSSL libssh' compiler: llvm@18 install: libssh libnghttp3 - generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DUSE_OPENSSL_QUIC=ON -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF + generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF - name: '!ssl c-ares' compiler: clang configure: --enable-debug --enable-ares --without-ssl @@ -330,19 +330,19 @@ jobs: compiler: clang install: libnghttp3 install_steps: torture - generate: -DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DENABLE_THREADED_RESOLVER=OFF -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DUSE_OPENSSL_QUIC=ON + generate: -DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DENABLE_THREADED_RESOLVER=OFF -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl tflags: '-t --shallow=25 --min=480 1 to 500' - name: 'OpenSSL torture 2' compiler: clang install: libnghttp3 install_steps: torture - generate: -DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DENABLE_THREADED_RESOLVER=OFF -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DUSE_OPENSSL_QUIC=ON + generate: -DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DENABLE_THREADED_RESOLVER=OFF -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl tflags: '-t --shallow=25 --min=730 501 to 1250' - name: 'OpenSSL torture 3' compiler: clang install: libnghttp3 install_steps: torture - generate: -DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DENABLE_THREADED_RESOLVER=OFF -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DUSE_OPENSSL_QUIC=ON + generate: -DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DENABLE_THREADED_RESOLVER=OFF -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl tflags: '-t --shallow=25 --min=628 1251 to 9999' steps: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 16abc5ae17..48de62739d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -200,7 +200,7 @@ jobs: - { build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: '' , config: '--with-openssl', install: 'openssl-devel libssh2-devel', name: 'default R' } # MinGW - { build: 'autotools', sys: 'mingw64' , env: 'x86_64' , tflags: 'skiprun' , config: '--enable-debug --with-openssl --disable-threaded-resolver --disable-curldebug --enable-static --without-zlib', install: 'mingw-w64-x86_64-openssl mingw-w64-x86_64-libssh2', name: 'default' } - - { build: 'autotools', sys: 'mingw64' , env: 'x86_64' , tflags: '' , config: '--enable-debug --with-openssl --enable-windows-unicode --enable-ares --with-openssl-quic --enable-static --disable-shared --enable-ca-native', install: 'mingw-w64-x86_64-c-ares mingw-w64-x86_64-openssl mingw-w64-x86_64-nghttp3 mingw-w64-x86_64-libssh2', name: 'c-ares U' } + - { build: 'autotools', sys: 'mingw64' , env: 'x86_64' , tflags: '' , config: '--enable-debug --with-openssl --enable-windows-unicode --enable-ares --enable-static --disable-shared --enable-ca-native', install: 'mingw-w64-x86_64-c-ares mingw-w64-x86_64-openssl mingw-w64-x86_64-nghttp3 mingw-w64-x86_64-libssh2', name: 'c-ares U' } - { build: 'cmake' , sys: 'mingw64' , env: 'x86_64' , tflags: '--min=1650', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON', install: 'mingw-w64-x86_64-c-ares mingw-w64-x86_64-libssh2', type: 'Debug', name: 'schannel c-ares U' } # MinGW torture - { build: 'cmake' , sys: 'mingw64' , env: 'x86_64' , tflags: '-t --shallow=13 --min=700 1 to 950' , config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON', install: 'mingw-w64-x86_64-c-ares mingw-w64-x86_64-libssh2', type: 'Debug', name: 'schannel U torture 1' } diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b2427ee62..e21f1384f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -686,11 +686,6 @@ else() set(_openssl_default ON) endif() cmake_dependent_option(CURL_USE_OPENSSL "Enable OpenSSL for SSL/TLS" ${_openssl_default} CURL_ENABLE_SSL OFF) -option(USE_OPENSSL_QUIC "Use OpenSSL and nghttp3 libraries for HTTP/3 support" OFF) -if(USE_OPENSSL_QUIC AND NOT CURL_USE_OPENSSL) - message(WARNING "OpenSSL QUIC has been requested, but without enabling OpenSSL. Will not enable QUIC.") - set(USE_OPENSSL_QUIC OFF) -endif() option(CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG "Disable automatic loading of OpenSSL configuration" OFF) curl_count_true(_enabled_ssl_options_count @@ -959,7 +954,7 @@ endmacro() # Ensure that OpenSSL (or fork) or wolfSSL actually supports QUICTLS API. macro(curl_openssl_check_quic) - if(USE_OPENSSL AND NOT USE_OPENSSL_QUIC) + if(USE_OPENSSL) if(OPENSSL_VERSION VERSION_GREATER_EQUAL 3.5.0) if(NOT DEFINED HAVE_SSL_SET_QUIC_TLS_CBS) curl_openssl_check_exists("SSL_set_quic_tls_cbs" HAVE_SSL_SET_QUIC_TLS_CBS) @@ -1057,7 +1052,7 @@ if(USE_NGTCP2) find_package(NGTCP2 REQUIRED COMPONENTS "wolfSSL") elseif(HAVE_BORINGSSL OR HAVE_AWSLC) find_package(NGTCP2 REQUIRED COMPONENTS "BoringSSL") - elseif(OPENSSL_VERSION VERSION_GREATER_EQUAL 3.5.0 AND NOT USE_OPENSSL_QUIC) + elseif(OPENSSL_VERSION VERSION_GREATER_EQUAL 3.5.0) find_package(NGTCP2 REQUIRED COMPONENTS "ossl") if(NGTCP2_VERSION VERSION_LESS 1.12.0) message(FATAL_ERROR "ngtcp2 1.12.0 or upper required for OpenSSL") @@ -1106,19 +1101,6 @@ if(USE_QUICHE) endif() endif() -if(USE_OPENSSL_QUIC) - if(USE_NGTCP2 OR USE_QUICHE) - message(FATAL_ERROR "Only one HTTP/3 backend can be selected") - elseif(CURL_WITH_MULTI_SSL) - message(FATAL_ERROR "MultiSSL cannot be enabled with HTTP/3 and vice versa.") - endif() - find_package(OpenSSL 3.3.0 REQUIRED) - - find_package(NGHTTP3 REQUIRED) - set(USE_NGHTTP3 ON) - list(APPEND CURL_LIBS CURL::nghttp3) -endif() - if(NOT CURL_DISABLE_SRP AND (HAVE_GNUTLS_SRP OR HAVE_OPENSSL_SRP)) set(USE_TLS_SRP 1) endif() @@ -1991,7 +1973,7 @@ curl_add_if("NTLM" NOT CURL_DISABLE_NTLM AND (_use_curl_ntlm_core OR USE_WINDOWS_SSPI)) curl_add_if("TLS-SRP" USE_TLS_SRP) curl_add_if("HTTP2" USE_NGHTTP2) -curl_add_if("HTTP3" USE_NGTCP2 OR USE_QUICHE OR USE_OPENSSL_QUIC) +curl_add_if("HTTP3" USE_NGTCP2 OR USE_QUICHE) curl_add_if("MultiSSL" CURL_WITH_MULTI_SSL) curl_add_if("HTTPS-proxy" NOT CURL_DISABLE_PROXY AND _ssl_enabled AND (USE_OPENSSL OR USE_GNUTLS OR USE_SCHANNEL OR USE_RUSTLS OR USE_MBEDTLS OR diff --git a/RELEASE-NOTES b/RELEASE-NOTES index d0232115a8..73831b9ede 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -77,7 +77,6 @@ For all changes ever done in curl: Planned upcoming removals include: - o OpenSSL-QUIC o RTMP support o Support for c-ares versions before 1.16.0 o Support for Windows XP/2003 diff --git a/configure.ac b/configure.ac index aa4a37ef09..eb009c4fdb 100644 --- a/configure.ac +++ b/configure.ac @@ -170,7 +170,7 @@ curl_unix_sockets_msg="no (--enable-unix-sockets)" ssl_backends= curl_h1_msg="enabled (internal)" curl_h2_msg="no (--with-nghttp2)" - curl_h3_msg="no (--with-ngtcp2 --with-nghttp3, --with-quiche, --with-openssl-quic)" + curl_h3_msg="no (--with-ngtcp2 --with-nghttp3, --with-quiche)" enable_altsvc="yes" hsts="yes" @@ -3607,53 +3607,14 @@ if test "$USE_NGTCP2" = "1" && test "$WOLFSSL_ENABLED" = "1"; then fi fi -dnl ********************************************************************** -dnl Check for OpenSSL QUIC -dnl ********************************************************************** - -OPT_OPENSSL_QUIC="no" - -if test "$disable_http" = "yes" || test "$OPENSSL_ENABLED" != "1"; then - # without HTTP or without openssl, no use - OPT_OPENSSL_QUIC="no" -fi - -AC_ARG_WITH(openssl-quic, -AS_HELP_STRING([--with-openssl-quic],[Enable OpenSSL QUIC usage]) -AS_HELP_STRING([--without-openssl-quic],[Disable OpenSSL QUIC usage]), - [OPT_OPENSSL_QUIC=$withval]) -case "$OPT_OPENSSL_QUIC" in - no) - dnl --without-openssl-quic option used - want_openssl_quic="no" - ;; - yes) - dnl --with-openssl-quic option used - want_openssl_quic="yes" - ;; -esac - -curl_openssl_quic_msg="no (--with-openssl-quic)" -if test "$want_openssl_quic" = "yes"; then - - if test "$USE_NGTCP2" = "1"; then - AC_MSG_ERROR([--with-openssl-quic and --with-ngtcp2 are mutually exclusive]) - fi - if test "$have_openssl_quic" != "1"; then - AC_MSG_ERROR([--with-openssl-quic requires quic support and OpenSSL >= 3.3.0]) - fi - AC_DEFINE(USE_OPENSSL_QUIC, 1, [if openssl QUIC is in use]) - USE_OPENSSL_QUIC=1 -fi - dnl ********************************************************************** dnl Check for nghttp3 (HTTP/3 with ngtcp2) dnl ********************************************************************** OPT_NGHTTP3="yes" -if test "$USE_NGTCP2" != "1" && test "$USE_OPENSSL_QUIC" != "1"; then - # without ngtcp2 or openssl quic, nghttp3 is of no use for us +if test "$USE_NGTCP2" != "1"; then + # without ngtcp2, nghttp3 is of no use for us OPT_NGHTTP3="no" want_nghttp3="no" fi @@ -3682,9 +3643,9 @@ esac curl_http3_msg="no (--with-nghttp3)" if test "$want_nghttp3" != "no"; then - if test "$USE_NGTCP2" != "1" && test "$USE_OPENSSL_QUIC" != "1"; then - # without ngtcp2 or openssl quic, nghttp3 is of no use for us - AC_MSG_ERROR([nghttp3 enabled without a QUIC library; enable ngtcp2 or OpenSSL-QUIC]) + if test "x$USE_NGTCP2" != "x1"; then + # without ngtcp2, nghttp3 is of no use for us + AC_MSG_ERROR([nghttp3 enabled without a QUIC library; enable ngtcp2]) fi dnl backup the pre-nghttp3 variables @@ -3753,17 +3714,6 @@ if test "$USE_NGTCP2" = "1" && test "$USE_NGHTTP3" = "1"; then curl_h3_msg="enabled (ngtcp2 + nghttp3)" fi -dnl ********************************************************************** -dnl Check for OpenSSL and nghttp3 (HTTP/3 with nghttp3 using OpenSSL QUIC) -dnl ********************************************************************** - -if test "$USE_OPENSSL_QUIC" = "1" && test "$USE_NGHTTP3" = "1"; then - experimental="$experimental HTTP3" - USE_OPENSSL_H3=1 - AC_MSG_NOTICE([HTTP3 support is experimental]) - curl_h3_msg="enabled (openssl + nghttp3)" -fi - dnl ********************************************************************** dnl Check for quiche (QUIC) dnl ********************************************************************** diff --git a/docs/DEPRECATE.md b/docs/DEPRECATE.md index 8ba75acee4..6ad0b7ad06 100644 --- a/docs/DEPRECATE.md +++ b/docs/DEPRECATE.md @@ -16,21 +16,6 @@ how your use case cannot be satisfied properly using a workaround. In March 2026, we drop support for all c-ares versions before 1.16.0. -## OpenSSL-QUIC - -OpenSSL-QUIC is what we call the curl QUIC backend that uses the OpenSSL QUIC -stack. - - - It is slower and uses more memory than the alternatives and is only - experimental in curl. - - It gets little attention from OpenSSL and we have no expectation of the - major flaws getting corrected anytime soon. - - No one has spoken up for keeping it - - curl users building with vanilla OpenSSL can still use QUIC through the - means of ngtcp2 - -We remove the OpenSSL-QUIC backend in January 2026. - ## RTMP RTMP in curl is powered by the 3rd party library librtmp. @@ -71,3 +56,4 @@ CMake 3.18 was released on 2020-07-15. - Support for Visual Studio 2008 (removed in 8.18.0) - OpenSSL 1.1.1 and older (removed in 8.18.0) - Support for Windows XP (removed in 8.19.0) + - OpenSSL-QUIC (removed in 8.19.0) diff --git a/docs/HTTP3.md b/docs/HTTP3.md index 59fba0e5ce..53c200c088 100644 --- a/docs/HTTP3.md +++ b/docs/HTTP3.md @@ -250,54 +250,6 @@ Build curl: If `make install` results in `Permission denied` error, you need to prepend it with `sudo`. -# OpenSSL version - -QUIC support is **EXPERIMENTAL** - -Use OpenSSL 3.3.1 or newer (QUIC support was added in 3.3.0, with -shortcomings on some platforms like macOS). 3.4.1 or newer is recommended. -Build via: - - % cd .. - % git clone -b $OPENSSL_VERSION https://github.com/openssl/openssl - % cd openssl - % ./config enable-tls1_3 --prefix= --libdir=lib - % make - % make install - -Build nghttp3: - - % cd .. - % git clone -b $NGHTTP3_VERSION https://github.com/ngtcp2/nghttp3 - % cd nghttp3 - % git submodule update --init - % autoreconf -fi - % ./configure --prefix= --enable-lib-only - % make - % make install - -Build curl: - - % cd .. - % git clone https://github.com/curl/curl - % cd curl - % autoreconf -fi - % LDFLAGS="-Wl,-rpath,/lib" ./configure --with-openssl= --with-openssl-quic --with-nghttp3= - % make - % make install - -You can build curl with cmake: - - % cd .. - % git clone https://github.com/curl/curl - % cd curl - % cmake -B bld -DCURL_USE_OPENSSL=ON -DUSE_OPENSSL_QUIC=ON - % cmake --build bld - % cmake --install bld - - If `make install` results in `Permission denied` error, you need to prepend - it with `sudo`. - # `--http3` Use only HTTP/3: diff --git a/docs/INSTALL-CMAKE.md b/docs/INSTALL-CMAKE.md index 8ff3f30146..bc62da242f 100644 --- a/docs/INSTALL-CMAKE.md +++ b/docs/INSTALL-CMAKE.md @@ -275,7 +275,6 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) - `ENABLE_UNIX_SOCKETS`: Enable Unix domain sockets support. Default: `ON` - `USE_ECH`: Enable ECH support. Default: `OFF` - `USE_HTTPSRR`: Enable HTTPS RR support. Default: `OFF` -- `USE_OPENSSL_QUIC`: Use OpenSSL and nghttp3 libraries for HTTP/3 support. Default: `OFF` - `USE_SSLS_EXPORT`: Enable experimental SSL session import/export. Default: `OFF` ## Disabling features diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 402a5be229..36befa3250 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -118,14 +118,12 @@ LIB_VTLS_HFILES = \ LIB_VQUIC_CFILES = \ vquic/curl_ngtcp2.c \ - vquic/curl_osslq.c \ vquic/curl_quiche.c \ vquic/vquic.c \ vquic/vquic-tls.c LIB_VQUIC_HFILES = \ vquic/curl_ngtcp2.h \ - vquic/curl_osslq.h \ vquic/curl_quiche.h \ vquic/vquic.h \ vquic/vquic_int.h \ diff --git a/lib/curl_config-cmake.h.in b/lib/curl_config-cmake.h.in index dbbe7e353a..1f5d9399e3 100644 --- a/lib/curl_config-cmake.h.in +++ b/lib/curl_config-cmake.h.in @@ -749,9 +749,6 @@ ${SIZEOF_TIME_T_CODE} /* to enable quiche */ #cmakedefine USE_QUICHE 1 -/* to enable openssl + nghttp3 */ -#cmakedefine USE_OPENSSL_QUIC 1 - /* to enable openssl + ngtcp2 + nghttp3 */ #cmakedefine OPENSSL_QUIC_API2 1 diff --git a/lib/vquic/curl_osslq.c b/lib/vquic/curl_osslq.c deleted file mode 100644 index 958b9a4a74..0000000000 --- a/lib/vquic/curl_osslq.c +++ /dev/null @@ -1,2457 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, , 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. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "../curl_setup.h" - -#if !defined(CURL_DISABLE_HTTP) && defined(USE_OPENSSL_QUIC) && \ - defined(USE_NGHTTP3) - -#include -#include -#include -#include - -#include "../urldata.h" -#include "../curl_trc.h" -#include "../multiif.h" -#include "../cfilters.h" -#include "../cf-socket.h" -#include "../connect.h" -#include "../progress.h" -#include "../curlx/dynbuf.h" -#include "../http1.h" -#include "../select.h" -#include "../uint-hash.h" -#include "vquic.h" -#include "vquic_int.h" -#include "vquic-tls.h" -#include "../vtls/keylog.h" -#include "../vtls/vtls.h" -#include "../vtls/openssl.h" -#include "curl_osslq.h" -#include "../url.h" -#include "../bufref.h" -#include "../curlx/strerr.h" -#include "../curlx/strcopy.h" - -/* A stream window is the maximum amount we need to buffer for - * each active transfer. We use HTTP/3 flow control and only ACK - * when we take things out of the buffer. - * Chunk size is large enough to take a full DATA frame */ -#define H3_STREAM_WINDOW_SIZE (128 * 1024) -#define H3_STREAM_CHUNK_SIZE (16 * 1024) -/* The pool keeps spares around and half of a full stream window - * seems good. More does not seem to improve performance. - * The benefit of the pool is that stream buffer to not keep - * spares. Memory consumption goes down when streams run empty, - * have a large upload done, etc. */ -#define H3_STREAM_POOL_SPARES \ - (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) / 2 -/* Receive and Send max number of chunks just follows from the - * chunk size and window size */ -#define H3_STREAM_RECV_CHUNKS \ - (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) -#define H3_STREAM_SEND_CHUNKS \ - (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) - -#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) -typedef uint32_t sslerr_t; -#else -typedef unsigned long sslerr_t; -#endif - - -/* How to access `call_data` from a cf_osslq filter */ -#undef CF_CTX_CALL_DATA -#define CF_CTX_CALL_DATA(cf) ((struct cf_osslq_ctx *)(cf)->ctx)->call_data - -static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, - struct Curl_easy *data); - -static const char *osslq_SSL_ERROR_to_str(int err) -{ - switch(err) { - case SSL_ERROR_NONE: - return "SSL_ERROR_NONE"; - case SSL_ERROR_SSL: - return "SSL_ERROR_SSL"; - case SSL_ERROR_WANT_READ: - return "SSL_ERROR_WANT_READ"; - case SSL_ERROR_WANT_WRITE: - return "SSL_ERROR_WANT_WRITE"; - case SSL_ERROR_WANT_X509_LOOKUP: - return "SSL_ERROR_WANT_X509_LOOKUP"; - case SSL_ERROR_SYSCALL: - return "SSL_ERROR_SYSCALL"; - case SSL_ERROR_ZERO_RETURN: - return "SSL_ERROR_ZERO_RETURN"; - case SSL_ERROR_WANT_CONNECT: - return "SSL_ERROR_WANT_CONNECT"; - case SSL_ERROR_WANT_ACCEPT: - return "SSL_ERROR_WANT_ACCEPT"; -#ifdef SSL_ERROR_WANT_ASYNC /* OpenSSL 1.1.0+, LibreSSL 3.6.0+ */ - case SSL_ERROR_WANT_ASYNC: - return "SSL_ERROR_WANT_ASYNC"; -#endif -#ifdef SSL_ERROR_WANT_ASYNC_JOB /* OpenSSL 1.1.0+, LibreSSL 3.6.0+ */ - case SSL_ERROR_WANT_ASYNC_JOB: - return "SSL_ERROR_WANT_ASYNC_JOB"; -#endif -#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB /* OpenSSL 1.1.1, LibreSSL 3.6.0+ */ - case SSL_ERROR_WANT_CLIENT_HELLO_CB: - return "SSL_ERROR_WANT_CLIENT_HELLO_CB"; -#endif - default: - return "SSL_ERROR unknown"; - } -} - -/* Return error string for last OpenSSL error */ -static char *osslq_strerror(unsigned long error, char *buf, size_t size) -{ - DEBUGASSERT(size); - *buf = '\0'; - -#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) - ERR_error_string_n((uint32_t)error, buf, size); -#else - ERR_error_string_n(error, buf, size); -#endif - - if(!*buf) { - const char *msg = error ? "Unknown error" : "No error"; - if(strlen(msg) < size) - curlx_strcopy(buf, size, msg, strlen(msg)); - } - - return buf; -} - -static CURLcode make_bio_addr(BIO_ADDR **pbio_addr, - const struct Curl_sockaddr_ex *addr) -{ - BIO_ADDR *bio_addr; - CURLcode result = CURLE_FAILED_INIT; - - bio_addr = BIO_ADDR_new(); - if(!bio_addr) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - switch(addr->family) { - case AF_INET: { - struct sockaddr_in * const sin = - (struct sockaddr_in * const)CURL_UNCONST(&addr->curl_sa_addr); - if(!BIO_ADDR_rawmake(bio_addr, AF_INET, &sin->sin_addr, - sizeof(sin->sin_addr), sin->sin_port)) { - goto out; - } - result = CURLE_OK; - break; - } -#ifdef USE_IPV6 - case AF_INET6: { - struct sockaddr_in6 * const sin = - (struct sockaddr_in6 * const)CURL_UNCONST(&addr->curl_sa_addr); - if(!BIO_ADDR_rawmake(bio_addr, AF_INET6, &sin->sin6_addr, - sizeof(sin->sin6_addr), sin->sin6_port)) { - goto out; - } - result = CURLE_OK; - break; - } -#endif /* USE_IPV6 */ - default: - /* sunsupported */ - DEBUGASSERT(0); - break; - } - -out: - if(result && bio_addr) { - BIO_ADDR_free(bio_addr); - bio_addr = NULL; - } - *pbio_addr = bio_addr; - return result; -} - -/* QUIC stream (not necessarily H3) */ -struct cf_osslq_stream { - int64_t id; - SSL *ssl; - struct bufq recvbuf; /* QUIC war data recv buffer */ - BIT(recvd_eos); - BIT(closed); - BIT(reset); - BIT(send_blocked); -}; - -static CURLcode cf_osslq_stream_open(struct cf_osslq_stream *s, - SSL *conn, - uint64_t flags, - struct bufc_pool *bufcp, - void *user_data) -{ - DEBUGASSERT(!s->ssl); - Curl_bufq_initp(&s->recvbuf, bufcp, 1, BUFQ_OPT_NONE); - s->ssl = SSL_new_stream(conn, flags); - if(!s->ssl) { - return CURLE_FAILED_INIT; - } - s->id = SSL_get_stream_id(s->ssl); - SSL_set_app_data(s->ssl, user_data); - return CURLE_OK; -} - -static void cf_osslq_stream_cleanup(struct cf_osslq_stream *s) -{ - if(s->ssl) { - SSL_set_app_data(s->ssl, NULL); - SSL_free(s->ssl); - } - Curl_bufq_free(&s->recvbuf); - memset(s, 0, sizeof(*s)); -} - -static void cf_osslq_stream_close(struct cf_osslq_stream *s) -{ - if(s->ssl) { - SSL_free(s->ssl); - s->ssl = NULL; - } -} - -struct cf_osslq_h3conn { - nghttp3_conn *conn; - nghttp3_settings settings; - struct cf_osslq_stream s_ctrl; - struct cf_osslq_stream s_qpack_enc; - struct cf_osslq_stream s_qpack_dec; - struct cf_osslq_stream remote_ctrl[3]; /* uni streams opened by the peer */ - size_t remote_ctrl_n; /* number of peer streams opened */ -}; - -static void cf_osslq_h3conn_cleanup(struct cf_osslq_h3conn *h3) -{ - size_t i; - - if(h3->conn) - nghttp3_conn_del(h3->conn); - cf_osslq_stream_cleanup(&h3->s_ctrl); - cf_osslq_stream_cleanup(&h3->s_qpack_enc); - cf_osslq_stream_cleanup(&h3->s_qpack_dec); - for(i = 0; i < h3->remote_ctrl_n; ++i) { - cf_osslq_stream_cleanup(&h3->remote_ctrl[i]); - } -} - -struct cf_osslq_ctx { - struct cf_quic_ctx q; - struct ssl_peer peer; - struct curl_tls_ctx tls; - struct cf_call_data call_data; - struct cf_osslq_h3conn h3; - struct curltime started_at; /* time the current attempt started */ - struct curltime handshake_at; /* time connect handshake finished */ - struct curltime first_byte_at; /* when first byte was recvd */ - struct bufc_pool stream_bufcp; /* chunk pool for streams */ - struct uint_hash streams; /* hash `data->mid` to `h3_stream_ctx` */ - size_t max_stream_window; /* max flow window for one stream */ - SSL_POLL_ITEM *poll_items; /* Array for polling on writable state */ - struct Curl_easy **curl_items; /* Array of easy objs */ - size_t items_max; /* max elements in poll/curl_items */ - BIT(initialized); - BIT(got_first_byte); /* if first byte was received */ - BIT(x509_store_setup); /* if x509 store has been set up */ - BIT(protocol_shutdown); /* QUIC connection is shut down */ - BIT(need_recv); /* QUIC connection needs to receive */ - BIT(need_send); /* QUIC connection needs to send */ -}; - -static void h3_stream_hash_free(unsigned int id, void *stream); - -static void cf_osslq_ctx_init(struct cf_osslq_ctx *ctx) -{ - DEBUGASSERT(!ctx->initialized); - Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, - H3_STREAM_POOL_SPARES); - Curl_uint32_hash_init(&ctx->streams, 63, h3_stream_hash_free); - ctx->poll_items = NULL; - ctx->curl_items = NULL; - ctx->items_max = 0; - ctx->initialized = TRUE; -} - -static void cf_osslq_ctx_free(struct cf_osslq_ctx *ctx) -{ - if(ctx && ctx->initialized) { - Curl_bufcp_free(&ctx->stream_bufcp); - Curl_uint32_hash_destroy(&ctx->streams); - Curl_ssl_peer_cleanup(&ctx->peer); - curlx_free(ctx->poll_items); - curlx_free(ctx->curl_items); - } - curlx_free(ctx); -} - -static void cf_osslq_ctx_close(struct cf_osslq_ctx *ctx) -{ - struct cf_call_data save = ctx->call_data; - - cf_osslq_h3conn_cleanup(&ctx->h3); - Curl_vquic_tls_cleanup(&ctx->tls); - vquic_ctx_free(&ctx->q); - ctx->call_data = save; -} - -static CURLcode cf_osslq_shutdown(struct Curl_cfilter *cf, - struct Curl_easy *data, bool *done) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - struct cf_call_data save; - CURLcode result = CURLE_OK; - int rc; - - CF_DATA_SAVE(save, cf, data); - - if(cf->shutdown || ctx->protocol_shutdown) { - *done = TRUE; - return CURLE_OK; - } - - CF_DATA_SAVE(save, cf, data); - *done = FALSE; - ctx->need_send = FALSE; - ctx->need_recv = FALSE; - - rc = SSL_shutdown_ex(ctx->tls.ossl.ssl, - SSL_SHUTDOWN_FLAG_NO_BLOCK, NULL, 0); - if(rc == 0) { /* ongoing */ - CURL_TRC_CF(data, cf, "shutdown ongoing"); - ctx->need_recv = TRUE; - goto out; - } - else if(rc == 1) { /* done */ - CURL_TRC_CF(data, cf, "shutdown finished"); - *done = TRUE; - goto out; - } - else { - long sslerr; - char err_buffer[256]; - int err = SSL_get_error(ctx->tls.ossl.ssl, rc); - - switch(err) { - case SSL_ERROR_NONE: - case SSL_ERROR_ZERO_RETURN: - CURL_TRC_CF(data, cf, "shutdown not received, but closed"); - *done = TRUE; - goto out; - case SSL_ERROR_WANT_READ: - /* SSL has send its notify and now wants to read the reply - * from the server. We are not really interested in that. */ - CURL_TRC_CF(data, cf, "shutdown sent, want receive"); - ctx->need_recv = TRUE; - goto out; - case SSL_ERROR_WANT_WRITE: - CURL_TRC_CF(data, cf, "shutdown send blocked"); - ctx->need_send = TRUE; - goto out; - default: - /* We give up on this. */ - sslerr = ERR_get_error(); - CURL_TRC_CF(data, cf, "shutdown, ignore recv error: '%s', errno %d", - (sslerr ? - osslq_strerror(sslerr, err_buffer, sizeof(err_buffer)) : - osslq_SSL_ERROR_to_str(err)), - SOCKERRNO); - *done = TRUE; - result = CURLE_OK; - goto out; - } - } -out: - CF_DATA_RESTORE(cf, save); - return result; -} - -static void cf_osslq_close(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - struct cf_call_data save; - - CF_DATA_SAVE(save, cf, data); - if(ctx && ctx->tls.ossl.ssl) { - CURL_TRC_CF(data, cf, "cf_osslq_close()"); - if(!cf->shutdown && !ctx->protocol_shutdown) { - /* last best effort, which OpenSSL calls a "rapid" shutdown. */ - SSL_shutdown_ex(ctx->tls.ossl.ssl, - (SSL_SHUTDOWN_FLAG_NO_BLOCK | SSL_SHUTDOWN_FLAG_RAPID), - NULL, 0); - } - cf_osslq_ctx_close(ctx); - } - - cf->connected = FALSE; - CF_DATA_RESTORE(cf, save); -} - -static void cf_osslq_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - struct cf_call_data save; - - CF_DATA_SAVE(save, cf, data); - CURL_TRC_CF(data, cf, "destroy"); - if(ctx) { - CURL_TRC_CF(data, cf, "cf_osslq_destroy()"); - if(ctx->tls.ossl.ssl) - cf_osslq_ctx_close(ctx); - cf_osslq_ctx_free(ctx); - } - cf->ctx = NULL; - /* No CF_DATA_RESTORE(cf, save) possible */ - (void)save; -} - -static CURLcode cf_osslq_h3conn_add_stream(struct cf_osslq_h3conn *h3, - SSL *stream_ssl, - struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - int64_t stream_id = (int64_t)SSL_get_stream_id(stream_ssl); - int stype = SSL_get_stream_type(stream_ssl); - /* This could be a GREASE stream, e.g. HTTP/3 rfc9114 ch 6.2.3 - * reserved stream type that is supposed to be discarded silently. - * BUT OpenSSL does not offer this information to us. So, we silently - * ignore all such streams we do not expect. */ - switch(stype) { - case SSL_STREAM_TYPE_READ: { - struct cf_osslq_stream *nstream; - if(h3->remote_ctrl_n >= CURL_ARRAYSIZE(h3->remote_ctrl)) { - /* rejected, we are full */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] reject remote uni stream", - stream_id); - SSL_free(stream_ssl); - return CURLE_OK; - } - nstream = &h3->remote_ctrl[h3->remote_ctrl_n++]; - nstream->id = stream_id; - nstream->ssl = stream_ssl; - Curl_bufq_initp(&nstream->recvbuf, &ctx->stream_bufcp, 1, BUFQ_OPT_NONE); - CURL_TRC_CF(data, cf, "[%" PRId64 "] accepted remote uni stream", - stream_id); - return CURLE_OK; - } - default: - CURL_TRC_CF(data, cf, "[%" PRId64 "] reject remote %s" - " stream, type=%x", stream_id, - (stype == SSL_STREAM_TYPE_BIDI) ? "bidi" : "write", stype); - SSL_free(stream_ssl); - return CURLE_OK; - } -} - -static CURLcode cf_osslq_ssl_err(struct Curl_cfilter *cf, - struct Curl_easy *data, - int detail, CURLcode def_result) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - CURLcode result = def_result; - sslerr_t errdetail; - char ebuf[256] = "unknown"; - const char *err_descr = ebuf; - long lerr; - int lib; - int reason; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - - errdetail = ERR_get_error(); - lib = ERR_GET_LIB(errdetail); - reason = ERR_GET_REASON(errdetail); - - if((lib == ERR_LIB_SSL) && - ((reason == SSL_R_CERTIFICATE_VERIFY_FAILED) || - (reason == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED))) { - result = CURLE_PEER_FAILED_VERIFICATION; - - lerr = SSL_get_verify_result(ctx->tls.ossl.ssl); - if(lerr != X509_V_OK) { - ssl_config->certverifyresult = lerr; - curl_msnprintf(ebuf, sizeof(ebuf), "SSL certificate problem: %s", - X509_verify_cert_error_string(lerr)); - } - else - err_descr = "SSL certificate verification failed"; - } -#ifdef SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED - /* SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED is only available on - OpenSSL version above v1.1.1, not LibreSSL, BoringSSL, or AWS-LC */ - else if((lib == ERR_LIB_SSL) && - (reason == SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED)) { - /* If client certificate is required, communicate the - error to client */ - result = CURLE_SSL_CLIENTCERT; - osslq_strerror(errdetail, ebuf, sizeof(ebuf)); - } -#endif - else if((lib == ERR_LIB_SSL) && (reason == SSL_R_PROTOCOL_IS_SHUTDOWN)) { - ctx->protocol_shutdown = TRUE; - err_descr = "QUIC connection has been shut down"; - result = def_result; - } - else { - result = def_result; - osslq_strerror(errdetail, ebuf, sizeof(ebuf)); - } - - /* detail is already set to the SSL error above */ - - /* If we e.g. use SSLv2 request-method and the server does not like us - * (RST connection, etc.), OpenSSL gives no explanation whatsoever and - * the SO_ERROR is also lost. - */ - if(CURLE_SSL_CONNECT_ERROR == result && errdetail == 0) { - char extramsg[80] = ""; - int sockerr = SOCKERRNO; - struct ip_quadruple ip; - - if(sockerr && detail == SSL_ERROR_SYSCALL) - curlx_strerror(sockerr, extramsg, sizeof(extramsg)); - if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip)) - failf(data, "QUIC connect: %s in connection to %s:%d (%s)", - extramsg[0] ? extramsg : osslq_SSL_ERROR_to_str(detail), - ctx->peer.dispname, ip.remote_port, ip.remote_ip); - } - else { - /* Could be a CERT problem */ - failf(data, "%s", err_descr); - } - return result; -} - -static CURLcode cf_osslq_verify_peer(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer); -} - -/** - * All about the H3 internals of a stream - */ -struct h3_stream_ctx { - struct cf_osslq_stream s; - struct bufq sendbuf; /* h3 request body */ - struct bufq recvbuf; /* h3 response body */ - struct h1_req_parser h1; /* h1 request parsing */ - size_t sendbuf_len_in_flight; /* sendbuf amount "in flight" */ - size_t recv_buf_nonflow; /* buffered bytes, - not counting for flow control */ - uint64_t error3; /* HTTP/3 stream error code */ - curl_off_t upload_left; /* number of request bytes left to upload */ - curl_off_t download_recvd; /* number of response DATA bytes received */ - int status_code; /* HTTP status code */ - BIT(resp_hds_complete); /* we have a complete, final response */ - BIT(closed); /* TRUE on stream close */ - BIT(reset); /* TRUE on stream reset */ - BIT(send_closed); /* stream is local closed */ - BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ -}; - -static void h3_stream_ctx_free(struct h3_stream_ctx *stream) -{ - cf_osslq_stream_cleanup(&stream->s); - Curl_bufq_free(&stream->sendbuf); - Curl_bufq_free(&stream->recvbuf); - Curl_h1_req_parse_free(&stream->h1); - curlx_free(stream); -} - -static void h3_stream_hash_free(unsigned int id, void *stream) -{ - (void)id; - DEBUGASSERT(stream); - h3_stream_ctx_free((struct h3_stream_ctx *)stream); -} - -static CURLcode h3_data_setup(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - - if(!data) - return CURLE_FAILED_INIT; - - if(stream) - return CURLE_OK; - - stream = curlx_calloc(1, sizeof(*stream)); - if(!stream) - return CURLE_OUT_OF_MEMORY; - - stream->s.id = -1; - /* on send, we control how much we put into the buffer */ - Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, - H3_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); - stream->sendbuf_len_in_flight = 0; - /* on recv, we need a flexible buffer limit since we also write - * headers to it that are not counted against the nghttp3 flow limits. */ - Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, - H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); - stream->recv_buf_nonflow = 0; - Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); - - if(!Curl_uint32_hash_set(&ctx->streams, data->mid, stream)) { - h3_stream_ctx_free(stream); - return CURLE_OUT_OF_MEMORY; - } - - return CURLE_OK; -} - -static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - - (void)cf; - if(stream) { - CURL_TRC_CF(data, cf, "[%" PRIu64 "] easy handle is done", stream->s.id); - if(ctx->h3.conn && (stream->s.id >= 0) && !stream->closed) { - nghttp3_conn_shutdown_stream_read(ctx->h3.conn, stream->s.id); - nghttp3_conn_close_stream(ctx->h3.conn, stream->s.id, - NGHTTP3_H3_REQUEST_CANCELLED); - nghttp3_conn_set_stream_user_data(ctx->h3.conn, stream->s.id, NULL); - stream->closed = TRUE; - } - - Curl_uint32_hash_remove(&ctx->streams, data->mid); - } -} - -struct cf_ossq_find_ctx { - int64_t stream_id; - struct h3_stream_ctx *stream; -}; - -static bool cf_osslq_find_stream(uint32_t mid, void *val, void *user_data) -{ - struct h3_stream_ctx *stream = val; - struct cf_ossq_find_ctx *fctx = user_data; - - (void)mid; - if(stream && stream->s.id == fctx->stream_id) { - fctx->stream = stream; - return FALSE; /* stop iterating */ - } - return TRUE; -} - -static struct cf_osslq_stream *cf_osslq_get_qstream(struct Curl_cfilter *cf, - struct Curl_easy *data, - int64_t stream_id) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - - if(stream && stream->s.id == stream_id) { - return &stream->s; - } - else if(ctx->h3.s_ctrl.id == stream_id) { - return &ctx->h3.s_ctrl; - } - else if(ctx->h3.s_qpack_enc.id == stream_id) { - return &ctx->h3.s_qpack_enc; - } - else if(ctx->h3.s_qpack_dec.id == stream_id) { - return &ctx->h3.s_qpack_dec; - } - else { - struct cf_ossq_find_ctx fctx; - fctx.stream_id = stream_id; - fctx.stream = NULL; - Curl_uint32_hash_visit(&ctx->streams, cf_osslq_find_stream, &fctx); - if(fctx.stream) - return &fctx.stream->s; - } - return NULL; -} - -static CURLcode h3_data_pause(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool pause) -{ - (void)cf; - if(!pause) { - /* unpaused. make it run again right away */ - Curl_multi_mark_dirty(data); - } - return CURLE_OK; -} - -static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) -{ - struct Curl_cfilter *cf = user_data; - struct cf_osslq_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - (void)conn; - (void)stream_id; - - /* we might be called by nghttp3 after we already cleaned up */ - if(!stream) - return 0; - - stream->closed = TRUE; - stream->error3 = app_error_code; - if(stream->error3 != NGHTTP3_H3_NO_ERROR) { - stream->reset = TRUE; - stream->send_closed = TRUE; - CURL_TRC_CF(data, cf, "[%" PRId64 "] RESET: error %" PRIu64, - stream->s.id, stream->error3); - } - else { - CURL_TRC_CF(data, cf, "[%" PRId64 "] CLOSED", stream->s.id); - } - Curl_multi_mark_dirty(data); - return 0; -} - -/* - * write_resp_raw() copies response data in raw format to the `data`'s - * receive buffer. If not enough space is available, it appends to the - * `data`'s overflow buffer. - */ -static CURLcode write_resp_raw(struct Curl_cfilter *cf, - struct Curl_easy *data, - const void *mem, size_t memlen, - bool flow) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - CURLcode result = CURLE_OK; - size_t nwritten; - - (void)cf; - if(!stream) { - return CURLE_RECV_ERROR; - } - result = Curl_bufq_write(&stream->recvbuf, mem, memlen, &nwritten); - if(result) - return result; - - if(!flow) - stream->recv_buf_nonflow += nwritten; - - if(nwritten < memlen) { - /* This MUST not happen. Our recbuf is dimensioned to hold the - * full max_stream_window and then some for this reason. */ - DEBUGASSERT(0); - return CURLE_RECV_ERROR; - } - return result; -} - -static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, - const uint8_t *buf, size_t buflen, - void *user_data, void *stream_user_data) -{ - struct Curl_cfilter *cf = user_data; - struct cf_osslq_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - CURLcode result; - - (void)conn; - (void)stream3_id; - - if(!stream) - return NGHTTP3_ERR_CALLBACK_FAILURE; - - result = write_resp_raw(cf, data, buf, buflen, TRUE); - if(result) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu, ERROR %d", - stream->s.id, buflen, result); - return NGHTTP3_ERR_CALLBACK_FAILURE; - } - stream->download_recvd += (curl_off_t)buflen; - CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu, total=%" FMT_OFF_T, - stream->s.id, buflen, stream->download_recvd); - Curl_multi_mark_dirty(data); - return 0; -} - -static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream_id, - size_t consumed, void *user_data, - void *stream_user_data) -{ - struct Curl_cfilter *cf = user_data; - struct cf_osslq_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - - (void)conn; - (void)stream_id; - if(stream) - CURL_TRC_CF(data, cf, "[%" PRId64 "] deferred consume %zu bytes", - stream->s.id, consumed); - return 0; -} - -static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, - int32_t token, nghttp3_rcbuf *name, - nghttp3_rcbuf *value, uint8_t flags, - void *user_data, void *stream_user_data) -{ - struct Curl_cfilter *cf = user_data; - struct cf_osslq_ctx *ctx = cf->ctx; - nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); - nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - CURLcode result = CURLE_OK; - (void)conn; - (void)stream_id; - (void)token; - (void)flags; - (void)cf; - - /* we might have cleaned up this transfer already */ - if(!stream) - return 0; - - if(token == NGHTTP3_QPACK_TOKEN__STATUS) { - char line[14]; /* status line is always 13 characters long */ - size_t ncopy; - - result = Curl_http_decode_status(&stream->status_code, - (const char *)h3val.base, h3val.len); - if(result) - return -1; - ncopy = curl_msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", - stream->status_code); - CURL_TRC_CF(data, cf, "[%" PRId64 "] status: %s", stream_id, line); - result = write_resp_raw(cf, data, line, ncopy, FALSE); - if(result) { - return -1; - } - } - else { - /* store as an HTTP1-style header */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] header: %.*s: %.*s", - stream_id, (int)h3name.len, h3name.base, - (int)h3val.len, h3val.base); - result = write_resp_raw(cf, data, h3name.base, h3name.len, FALSE); - if(result) { - return -1; - } - result = write_resp_raw(cf, data, ": ", 2, FALSE); - if(result) { - return -1; - } - result = write_resp_raw(cf, data, h3val.base, h3val.len, FALSE); - if(result) { - return -1; - } - result = write_resp_raw(cf, data, "\r\n", 2, FALSE); - if(result) { - return -1; - } - } - return 0; -} - -static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, - int fin, void *user_data, void *stream_user_data) -{ - struct Curl_cfilter *cf = user_data; - struct cf_osslq_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - CURLcode result = CURLE_OK; - (void)conn; - (void)stream_id; - (void)fin; - (void)cf; - - if(!stream) - return 0; - /* add a CRLF only if we have received some headers */ - result = write_resp_raw(cf, data, "\r\n", 2, FALSE); - if(result) { - return -1; - } - - CURL_TRC_CF(data, cf, "[%" PRId64 "] end_headers, status=%d", - stream_id, stream->status_code); - if(stream->status_code / 100 != 1) { - stream->resp_hds_complete = TRUE; - } - Curl_multi_mark_dirty(data); - return 0; -} - -static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) -{ - struct Curl_cfilter *cf = user_data; - struct cf_osslq_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - (void)conn; - (void)app_error_code; - - if(!stream || !stream->s.ssl) - return 0; - - CURL_TRC_CF(data, cf, "[%" PRId64 "] stop_sending", stream_id); - cf_osslq_stream_close(&stream->s); - return 0; -} - -static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) -{ - struct Curl_cfilter *cf = user_data; - struct cf_osslq_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - int rv; - (void)conn; - - if(stream && stream->s.ssl) { - SSL_STREAM_RESET_ARGS args = { 0 }; - args.quic_error_code = app_error_code; - rv = !SSL_stream_reset(stream->s.ssl, &args, sizeof(args)); - CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); - if(!rv) { - return NGHTTP3_ERR_CALLBACK_FAILURE; - } - } - return 0; -} - -static nghttp3_ssize cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, - nghttp3_vec *vec, size_t veccnt, - uint32_t *pflags, void *user_data, - void *stream_user_data) -{ - struct Curl_cfilter *cf = user_data; - struct cf_osslq_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - ssize_t nwritten = 0; - size_t nvecs = 0; - (void)cf; - (void)conn; - (void)stream_id; - (void)user_data; - (void)veccnt; - - if(!stream) - return NGHTTP3_ERR_CALLBACK_FAILURE; - /* nghttp3 keeps references to the sendbuf data until it is ACKed - * by the server (see `cb_h3_acked_req_body()` for updates). - * `sendbuf_len_in_flight` is the amount of bytes in `sendbuf` - * that we have already passed to nghttp3, but which have not been - * ACKed yet. - * Any amount beyond `sendbuf_len_in_flight` we need still to pass - * to nghttp3. Do that now, if we can. */ - if(stream->sendbuf_len_in_flight < Curl_bufq_len(&stream->sendbuf)) { - nvecs = 0; - while(nvecs < veccnt && - Curl_bufq_peek_at(&stream->sendbuf, - stream->sendbuf_len_in_flight, - CURL_UNCONST(&vec[nvecs].base), - &vec[nvecs].len)) { - stream->sendbuf_len_in_flight += vec[nvecs].len; - nwritten += vec[nvecs].len; - ++nvecs; - } - DEBUGASSERT(nvecs > 0); /* we SHOULD have been be able to peek */ - } - - if(nwritten > 0 && stream->upload_left != -1) - stream->upload_left -= nwritten; - - /* When we stopped sending and everything in `sendbuf` is "in flight", - * we are at the end of the request body. */ - if(stream->upload_left == 0) { - *pflags = NGHTTP3_DATA_FLAG_EOF; - stream->send_closed = TRUE; - } - else if(!nwritten) { - /* Not EOF, and nothing to give, we signal WOULDBLOCK. */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> AGAIN", - stream->s.id); - return NGHTTP3_ERR_WOULDBLOCK; - } - - CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> " - "%d vecs%s with %zu (buffered=%zu, left=%" FMT_OFF_T ")", - stream->s.id, (int)nvecs, - *pflags == NGHTTP3_DATA_FLAG_EOF ? " EOF" : "", - nwritten, Curl_bufq_len(&stream->sendbuf), - stream->upload_left); - return (nghttp3_ssize)nvecs; -} - -static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, - uint64_t datalen, void *user_data, - void *stream_user_data) -{ - struct Curl_cfilter *cf = user_data; - struct cf_osslq_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - size_t skiplen; - - (void)cf; - if(!stream) - return 0; - /* The server acknowledged `datalen` of bytes from our request body. - * This is a delta. We have kept this data in `sendbuf` for - * re-transmissions and can free it now. */ - if(datalen >= (uint64_t)stream->sendbuf_len_in_flight) - skiplen = stream->sendbuf_len_in_flight; - else - skiplen = (size_t)datalen; - Curl_bufq_skip(&stream->sendbuf, skiplen); - stream->sendbuf_len_in_flight -= skiplen; - - /* Resume upload processing if we have more data to send */ - if(stream->sendbuf_len_in_flight < Curl_bufq_len(&stream->sendbuf)) { - int rv = nghttp3_conn_resume_stream(conn, stream_id); - if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - return NGHTTP3_ERR_CALLBACK_FAILURE; - } - } - return 0; -} - -static nghttp3_callbacks ngh3_callbacks = { - cb_h3_acked_stream_data, - cb_h3_stream_close, - cb_h3_recv_data, - cb_h3_deferred_consume, - NULL, /* begin_headers */ - cb_h3_recv_header, - cb_h3_end_headers, - NULL, /* begin_trailers */ - cb_h3_recv_header, - NULL, /* end_trailers */ - cb_h3_stop_sending, - NULL, /* end_stream */ - cb_h3_reset_stream, - NULL, /* shutdown */ - NULL, /* recv_settings (deprecated) */ -#ifdef NGHTTP3_CALLBACKS_V2 /* nghttp3 v1.11.0+ */ - NULL, /* recv_origin */ - NULL, /* end_origin */ - NULL, /* rand */ -#endif -#ifdef NGHTTP3_CALLBACKS_V3 /* nghttp3 v1.14.0+ */ - NULL, /* recv_settings2 */ -#endif -}; - -static CURLcode cf_osslq_h3conn_init(struct cf_osslq_ctx *ctx, SSL *conn, - void *user_data) -{ - struct cf_osslq_h3conn *h3 = &ctx->h3; - CURLcode result; - int rc; - - nghttp3_settings_default(&h3->settings); - rc = nghttp3_conn_client_new(&h3->conn, - &ngh3_callbacks, - &h3->settings, - Curl_nghttp3_mem(), - user_data); - if(rc) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - result = cf_osslq_stream_open(&h3->s_ctrl, conn, - SSL_STREAM_FLAG_ADVANCE | SSL_STREAM_FLAG_UNI, - &ctx->stream_bufcp, NULL); - if(result) { - result = CURLE_QUIC_CONNECT_ERROR; - goto out; - } - result = cf_osslq_stream_open(&h3->s_qpack_enc, conn, - SSL_STREAM_FLAG_ADVANCE | SSL_STREAM_FLAG_UNI, - &ctx->stream_bufcp, NULL); - if(result) { - result = CURLE_QUIC_CONNECT_ERROR; - goto out; - } - result = cf_osslq_stream_open(&h3->s_qpack_dec, conn, - SSL_STREAM_FLAG_ADVANCE | SSL_STREAM_FLAG_UNI, - &ctx->stream_bufcp, NULL); - if(result) { - result = CURLE_QUIC_CONNECT_ERROR; - goto out; - } - - rc = nghttp3_conn_bind_control_stream(h3->conn, h3->s_ctrl.id); - if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto out; - } - rc = nghttp3_conn_bind_qpack_streams(h3->conn, h3->s_qpack_enc.id, - h3->s_qpack_dec.id); - if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto out; - } - - result = CURLE_OK; -out: - return result; -} - -static CURLcode cf_osslq_ctx_start(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - CURLcode result; - int rv; - const struct Curl_sockaddr_ex *peer_addr = NULL; - BIO *bio = NULL; - BIO_ADDR *baddr = NULL; - static const struct alpn_spec ALPN_SPEC_H3 = {{ "h3" }, 1}; - - DEBUGASSERT(ctx->initialized); - -#define H3_ALPN "\x2h3" - result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, - &ALPN_SPEC_H3, NULL, NULL, NULL, NULL); - if(result) - goto out; - - result = vquic_ctx_init(data, &ctx->q); - if(result) - goto out; - - result = CURLE_QUIC_CONNECT_ERROR; - if(Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, &peer_addr, NULL) || - !peer_addr) - goto out; - - ctx->q.local_addrlen = sizeof(ctx->q.local_addr); - rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr, - &ctx->q.local_addrlen); - if(rv == -1) - goto out; - - result = make_bio_addr(&baddr, peer_addr); - if(result) { - failf(data, "error creating BIO_ADDR from sockaddr"); - goto out; - } - - /* Type conversions, see #12861: OpenSSL wants an `int`, but on 64-bit - * Win32 systems, Microsoft defines SOCKET as `unsigned long long`. - */ -#if defined(_WIN32) && !defined(__LWIP_OPT_H__) && !defined(LWIP_HDR_OPT_H) - if(ctx->q.sockfd > INT_MAX) { - failf(data, "Windows socket identifier larger than MAX_INT, " - "unable to set in OpenSSL dgram API."); - result = CURLE_QUIC_CONNECT_ERROR; - goto out; - } - bio = BIO_new_dgram((int)ctx->q.sockfd, BIO_NOCLOSE); -#else - bio = BIO_new_dgram(ctx->q.sockfd, BIO_NOCLOSE); -#endif - if(!bio) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - if(!SSL_set1_initial_peer_addr(ctx->tls.ossl.ssl, baddr)) { - failf(data, "failed to set the initial peer address"); - result = CURLE_FAILED_INIT; - goto out; - } - if(!SSL_set_blocking_mode(ctx->tls.ossl.ssl, 0)) { - failf(data, "failed to turn off blocking mode"); - result = CURLE_FAILED_INIT; - goto out; - } - - SSL_set_bio(ctx->tls.ossl.ssl, bio, bio); - bio = NULL; - SSL_set_connect_state(ctx->tls.ossl.ssl); - SSL_set_incoming_stream_policy(ctx->tls.ossl.ssl, - SSL_INCOMING_STREAM_POLICY_ACCEPT, 0); - /* from our side, there is no idle timeout */ - SSL_set_value_uint(ctx->tls.ossl.ssl, - SSL_VALUE_CLASS_FEATURE_REQUEST, - SSL_VALUE_QUIC_IDLE_TIMEOUT, 0); - /* setup the H3 things on top of the QUIC connection */ - result = cf_osslq_h3conn_init(ctx, ctx->tls.ossl.ssl, cf); - -out: - if(bio) - BIO_free(bio); - if(baddr) - BIO_ADDR_free(baddr); - CURL_TRC_CF(data, cf, "QUIC tls init -> %d", result); - return result; -} - -struct h3_quic_recv_ctx { - struct Curl_cfilter *cf; - struct Curl_easy *data; - struct cf_osslq_stream *s; -}; - -static CURLcode h3_quic_recv(void *reader_ctx, - unsigned char *buf, size_t len, - size_t *pnread) -{ - struct h3_quic_recv_ctx *x = reader_ctx; - int rv; - - rv = SSL_read_ex(x->s->ssl, buf, len, pnread); - if(rv <= 0) { - int detail = SSL_get_error(x->s->ssl, rv); - if(detail == SSL_ERROR_WANT_READ || detail == SSL_ERROR_WANT_WRITE) { - return CURLE_AGAIN; - } - else if(detail == SSL_ERROR_ZERO_RETURN) { - CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] h3_quic_recv -> EOS", - x->s->id); - x->s->recvd_eos = TRUE; - return CURLE_OK; - } - else if(SSL_get_stream_read_state(x->s->ssl) == - SSL_STREAM_STATE_RESET_REMOTE) { - uint64_t app_error_code = NGHTTP3_H3_NO_ERROR; - if(!SSL_get_stream_read_error_code(x->s->ssl, &app_error_code)) { - x->s->reset = TRUE; - return CURLE_RECV_ERROR; - } - else { - CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] h3_quic_recv -> RESET, " - "rv=%d, app_err=%" PRIu64, - x->s->id, rv, app_error_code); - if(app_error_code != NGHTTP3_H3_NO_ERROR) - x->s->reset = TRUE; - } - x->s->recvd_eos = TRUE; - return CURLE_OK; - } - else { - return cf_osslq_ssl_err(x->cf, x->data, detail, CURLE_RECV_ERROR); - } - } - return CURLE_OK; -} - -static CURLcode cf_osslq_stream_recv(struct cf_osslq_stream *s, - struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - ssize_t nread; - size_t n; - struct h3_quic_recv_ctx x; - bool eagain = FALSE; - size_t total_recv_len = 0; - - DEBUGASSERT(s); - if(s->closed) - return CURLE_OK; - - x.cf = cf; - x.data = data; - x.s = s; - while(s->ssl && !s->closed && !eagain && - (total_recv_len < H3_STREAM_CHUNK_SIZE)) { - if(Curl_bufq_is_empty(&s->recvbuf) && !s->recvd_eos) { - while(!eagain && !s->recvd_eos && !Curl_bufq_is_full(&s->recvbuf)) { - result = Curl_bufq_sipn(&s->recvbuf, 0, h3_quic_recv, &x, &n); - if(result) { - if(result != CURLE_AGAIN) - goto out; - result = CURLE_OK; - eagain = TRUE; - } - } - } - - /* Forward what we have to nghttp3 */ - if(!Curl_bufq_is_empty(&s->recvbuf)) { - const unsigned char *buf; - size_t blen; - - while(Curl_bufq_peek(&s->recvbuf, &buf, &blen)) { - nread = nghttp3_conn_read_stream(ctx->h3.conn, s->id, - buf, blen, 0); - CURL_TRC_CF(data, cf, "[%" PRId64 "] forward %zu bytes " - "to nghttp3 -> %zd", s->id, blen, nread); - if(nread < 0) { - failf(data, "nghttp3_conn_read_stream(len=%zu) error: %s", - blen, nghttp3_strerror((int)nread)); - result = CURLE_RECV_ERROR; - goto out; - } - /* success, `nread` is the flow for QUIC to count as "consumed", - * not sure how that will work with OpenSSL. Anyways, without error, - * all data that we passed is not owned by nghttp3. */ - Curl_bufq_skip(&s->recvbuf, blen); - total_recv_len += blen; - } - } - - /* When we forwarded everything, handle RESET/EOS */ - if(Curl_bufq_is_empty(&s->recvbuf) && !s->closed) { - int rv; - result = CURLE_OK; - if(s->reset) { - uint64_t app_error; - if(!SSL_get_stream_read_error_code(s->ssl, &app_error)) { - failf(data, "SSL_get_stream_read_error_code returned error"); - result = CURLE_RECV_ERROR; - goto out; - } - rv = nghttp3_conn_close_stream(ctx->h3.conn, s->id, app_error); - s->closed = TRUE; - if(rv < 0 && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - failf(data, "nghttp3_conn_close_stream returned error: %s", - nghttp3_strerror(rv)); - result = CURLE_RECV_ERROR; - goto out; - } - } - else if(s->recvd_eos) { - rv = nghttp3_conn_close_stream(ctx->h3.conn, s->id, - NGHTTP3_H3_NO_ERROR); - s->closed = TRUE; - CURL_TRC_CF(data, cf, "[%" PRId64 "] close nghttp3 stream -> %d", - s->id, rv); - if(rv < 0 && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - failf(data, "nghttp3_conn_close_stream returned error: %s", - nghttp3_strerror(rv)); - result = CURLE_RECV_ERROR; - goto out; - } - } - } - } -out: - if(result) - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_osslq_stream_recv -> %d", - s->id, result); - return result; -} - -struct cf_ossq_recv_ctx { - struct Curl_cfilter *cf; - struct Curl_multi *multi; - CURLcode result; -}; - -static bool cf_osslq_iter_recv(uint32_t mid, void *val, void *user_data) -{ - struct h3_stream_ctx *stream = val; - struct cf_ossq_recv_ctx *rctx = user_data; - - (void)mid; - if(stream && !stream->closed && !Curl_bufq_is_full(&stream->recvbuf)) { - struct Curl_easy *sdata = Curl_multi_get_easy(rctx->multi, mid); - if(sdata) { - rctx->result = cf_osslq_stream_recv(&stream->s, rctx->cf, sdata); - if(rctx->result) - return FALSE; /* abort iteration */ - } - } - return TRUE; -} - -static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - - if(!ctx->tls.ossl.ssl) - goto out; - - ERR_clear_error(); - - /* 1. Check for new incoming streams */ - while(1) { - SSL *snew = SSL_accept_stream(ctx->tls.ossl.ssl, - SSL_ACCEPT_STREAM_NO_BLOCK); - if(!snew) - break; - - result = cf_osslq_h3conn_add_stream(&ctx->h3, snew, cf, data); - if(result) - goto out; - } - - if(!SSL_handle_events(ctx->tls.ossl.ssl)) { - int detail = SSL_get_error(ctx->tls.ossl.ssl, 0); - result = cf_osslq_ssl_err(cf, data, detail, CURLE_RECV_ERROR); - if(result) - goto out; - } - - if(ctx->h3.conn) { - size_t i; - for(i = 0; i < ctx->h3.remote_ctrl_n; ++i) { - result = cf_osslq_stream_recv(&ctx->h3.remote_ctrl[i], cf, data); - if(result) - goto out; - } - } - - if(ctx->h3.conn) { - struct cf_ossq_recv_ctx rctx; - - DEBUGASSERT(data->multi); - rctx.cf = cf; - rctx.multi = data->multi; - rctx.result = CURLE_OK; - Curl_uint32_hash_visit(&ctx->streams, cf_osslq_iter_recv, &rctx); - result = rctx.result; - } - -out: - CURL_TRC_CF(data, cf, "progress_ingress -> %d", result); - return result; -} - -struct cf_ossq_fill_ctx { - struct cf_osslq_ctx *ctx; - struct Curl_multi *multi; - size_t n; -}; - -static bool cf_osslq_collect_block_send(uint32_t mid, void *val, - void *user_data) -{ - struct h3_stream_ctx *stream = val; - struct cf_ossq_fill_ctx *fctx = user_data; - struct cf_osslq_ctx *ctx = fctx->ctx; - - if(fctx->n >= ctx->items_max) /* should not happen, prevent mayhem */ - return FALSE; - - if(stream && stream->s.ssl && stream->s.send_blocked) { - struct Curl_easy *sdata = Curl_multi_get_easy(fctx->multi, mid); - if(sdata) { - ctx->poll_items[fctx->n].desc = SSL_as_poll_descriptor(stream->s.ssl); - ctx->poll_items[fctx->n].events = SSL_POLL_EVENT_W; - ctx->curl_items[fctx->n] = sdata; - fctx->n++; - } - } - return TRUE; -} - -/* Iterate over all streams and check if blocked can be unblocked */ -static CURLcode cf_osslq_check_and_unblock(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream; - size_t poll_count; - size_t result_count = 0; - size_t idx_count = 0; - CURLcode res = CURLE_OK; - struct timeval timeout; - void *tmpptr; - - if(ctx->h3.conn) { - struct cf_ossq_fill_ctx fill_ctx; - - if(ctx->items_max < Curl_uint32_hash_count(&ctx->streams)) { - size_t nmax = Curl_uint32_hash_count(&ctx->streams); - ctx->items_max = 0; - tmpptr = curlx_realloc(ctx->poll_items, nmax * sizeof(SSL_POLL_ITEM)); - if(!tmpptr) { - curlx_free(ctx->poll_items); - ctx->poll_items = NULL; - res = CURLE_OUT_OF_MEMORY; - goto out; - } - ctx->poll_items = tmpptr; - - tmpptr = curlx_realloc(ctx->curl_items, - nmax * sizeof(struct Curl_easy *)); - if(!tmpptr) { - curlx_free(ctx->curl_items); - ctx->curl_items = NULL; - res = CURLE_OUT_OF_MEMORY; - goto out; - } - ctx->curl_items = tmpptr; - ctx->items_max = nmax; - } - - fill_ctx.ctx = ctx; - fill_ctx.multi = data->multi; - fill_ctx.n = 0; - Curl_uint32_hash_visit(&ctx->streams, cf_osslq_collect_block_send, - &fill_ctx); - poll_count = fill_ctx.n; - if(poll_count) { - CURL_TRC_CF(data, cf, "polling %zu blocked streams", poll_count); - - memset(&timeout, 0, sizeof(struct timeval)); - res = CURLE_UNRECOVERABLE_POLL; - if(!SSL_poll(ctx->poll_items, poll_count, sizeof(SSL_POLL_ITEM), - &timeout, 0, &result_count)) - goto out; - - res = CURLE_OK; - - for(idx_count = 0; idx_count < poll_count && result_count > 0; - idx_count++) { - if(ctx->poll_items[idx_count].revents & SSL_POLL_EVENT_W) { - stream = H3_STREAM_CTX(ctx, ctx->curl_items[idx_count]); - DEBUGASSERT(stream); /* should still exist */ - if(stream) { - nghttp3_conn_unblock_stream(ctx->h3.conn, stream->s.id); - stream->s.send_blocked = FALSE; - Curl_multi_mark_dirty(ctx->curl_items[idx_count]); - CURL_TRC_CF(ctx->curl_items[idx_count], cf, "unblocked"); - } - result_count--; - } - } - } - } - -out: - return res; -} - -static CURLcode h3_send_streams(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - - if(!ctx->tls.ossl.ssl || !ctx->h3.conn) - goto out; - - for(;;) { - struct cf_osslq_stream *s = NULL; - nghttp3_vec vec[16]; - nghttp3_ssize n, i; - int64_t stream_id; - size_t written; - int eos, ok, rv; - size_t total_len, acked_len = 0; - bool blocked = FALSE, eos_written = FALSE; - - n = nghttp3_conn_writev_stream(ctx->h3.conn, &stream_id, &eos, - vec, CURL_ARRAYSIZE(vec)); - if(n < 0) { - failf(data, "nghttp3_conn_writev_stream returned error: %s", - nghttp3_strerror((int)n)); - result = CURLE_SEND_ERROR; - goto out; - } - if(stream_id < 0) { - result = CURLE_OK; - goto out; - } - - /* Get the stream for this data */ - s = cf_osslq_get_qstream(cf, data, stream_id); - if(!s) { - failf(data, "nghttp3_conn_writev_stream gave unknown stream %" - PRId64, stream_id); - result = CURLE_SEND_ERROR; - goto out; - } - /* Now write the data to the stream's SSL*, it may not all fit! */ - DEBUGASSERT(s->id == stream_id); - for(i = 0, total_len = 0; i < n; ++i) { - total_len += vec[i].len; - } - for(i = 0; (i < n) && !blocked; ++i) { - /* Without stream->s.ssl, we closed that already, so - * pretend the write did succeed. */ - uint64_t flags = (eos && ((i + 1) == n)) ? SSL_WRITE_FLAG_CONCLUDE : 0; - written = vec[i].len; - ok = !s->ssl || SSL_write_ex2(s->ssl, vec[i].base, vec[i].len, flags, - &written); - if(ok && flags & SSL_WRITE_FLAG_CONCLUDE) - eos_written = TRUE; - if(ok) { - /* As OpenSSL buffers the data, we count this as acknowledged - * from nghttp3's point of view */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] send %zu bytes to QUIC ok", - s->id, vec[i].len); - acked_len += vec[i].len; - } - else { - int detail = SSL_get_error(s->ssl, 0); - switch(detail) { - case SSL_ERROR_WANT_WRITE: - case SSL_ERROR_WANT_READ: - /* QUIC blocked us from writing more */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] send %zu bytes to " - "QUIC blocked", s->id, vec[i].len); - written = 0; - nghttp3_conn_block_stream(ctx->h3.conn, s->id); - s->send_blocked = blocked = TRUE; - break; - default: - failf(data, "[%" PRId64 "] send %zu bytes to QUIC, SSL error %d", - s->id, vec[i].len, detail); - result = cf_osslq_ssl_err(cf, data, detail, CURLE_HTTP3); - goto out; - } - } - } - - if(acked_len > 0 || (eos && !s->send_blocked)) { - /* Since QUIC buffers the data written internally, we can tell - * nghttp3 that it can move forward on it */ - ctx->q.last_io = *Curl_pgrs_now(data); - rv = nghttp3_conn_add_write_offset(ctx->h3.conn, s->id, acked_len); - if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - failf(data, "nghttp3_conn_add_write_offset returned error: %s", - nghttp3_strerror(rv)); - result = CURLE_SEND_ERROR; - goto out; - } - rv = nghttp3_conn_add_ack_offset(ctx->h3.conn, s->id, acked_len); - if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - failf(data, "nghttp3_conn_add_ack_offset returned error: %s", - nghttp3_strerror(rv)); - result = CURLE_SEND_ERROR; - goto out; - } - CURL_TRC_CF(data, cf, "[%" PRId64 "] forwarded %zu/%zu h3 bytes " - "to QUIC, eos=%d", s->id, acked_len, total_len, eos); - } - - if(eos && !s->send_blocked && !eos_written) { - /* wrote everything and H3 indicates end of stream */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] closing QUIC stream", s->id); - SSL_stream_conclude(s->ssl, 0); - } - } - -out: - CURL_TRC_CF(data, cf, "h3_send_streams -> %d", result); - return result; -} - -static CURLcode cf_progress_egress(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - - if(!ctx->tls.ossl.ssl) - goto out; - - ERR_clear_error(); - result = h3_send_streams(cf, data); - if(result) - goto out; - - if(!SSL_handle_events(ctx->tls.ossl.ssl)) { - int detail = SSL_get_error(ctx->tls.ossl.ssl, 0); - result = cf_osslq_ssl_err(cf, data, detail, CURLE_SEND_ERROR); - } - - result = cf_osslq_check_and_unblock(cf, data); - -out: - CURL_TRC_CF(data, cf, "progress_egress -> %d", result); - return result; -} - -static CURLcode check_and_set_expiry(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - struct timeval tv; - timediff_t timeoutms; - int is_infinite = 1; - - if(ctx->tls.ossl.ssl && - SSL_get_event_timeout(ctx->tls.ossl.ssl, &tv, &is_infinite) && - !is_infinite) { - timeoutms = curlx_tvtoms(&tv); - /* QUIC want to be called again latest at the returned timeout */ - if(timeoutms <= 0) { - result = cf_progress_ingress(cf, data); - if(result) - goto out; - result = cf_progress_egress(cf, data); - if(result) - goto out; - if(SSL_get_event_timeout(ctx->tls.ossl.ssl, &tv, &is_infinite)) { - timeoutms = curlx_tvtoms(&tv); - } - } - if(!is_infinite) { - Curl_expire(data, timeoutms, EXPIRE_QUIC); - CURL_TRC_CF(data, cf, "QUIC expiry in %ldms", (long)timeoutms); - } - } -out: - return result; -} - -static CURLcode cf_osslq_connect(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - struct cf_call_data save; - int err; - - if(cf->connected) { - *done = TRUE; - return CURLE_OK; - } - - /* Connect the UDP filter first */ - if(!cf->next->connected) { - result = Curl_conn_cf_connect(cf->next, data, done); - if(result || !*done) - return result; - } - - *done = FALSE; - CF_DATA_SAVE(save, cf, data); - - if(!ctx->tls.ossl.ssl) { - ctx->started_at = *Curl_pgrs_now(data); - result = cf_osslq_ctx_start(cf, data); - if(result) - goto out; - } - - if(!ctx->got_first_byte) { - int readable = SOCKET_READABLE(ctx->q.sockfd, 0); - if(readable > 0 && (readable & CURL_CSELECT_IN)) { - ctx->got_first_byte = TRUE; - ctx->first_byte_at = *Curl_pgrs_now(data); - } - } - - /* Since OpenSSL does its own send/recv internally, we may miss the - * moment to populate the x509 store right before the server response. - * Do it instead before we start the handshake, at the loss of the - * time to set this up. */ - result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); - if(result) - goto out; - - ERR_clear_error(); - err = SSL_do_handshake(ctx->tls.ossl.ssl); - - if(err == 1) { - /* connected */ - if(!ctx->got_first_byte) { - /* if not recorded yet, take the timestamp before we called - * SSL_do_handshake() as the time we received the first packet. */ - ctx->got_first_byte = TRUE; - ctx->first_byte_at = *Curl_pgrs_now(data); - } - /* Record the handshake complete with a new time stamp. */ - ctx->handshake_at = *Curl_pgrs_now(data); - ctx->q.last_io = *Curl_pgrs_now(data); - CURL_TRC_CF(data, cf, "handshake complete after %" FMT_TIMEDIFF_T "ms", - curlx_ptimediff_ms(Curl_pgrs_now(data), &ctx->started_at)); - result = cf_osslq_verify_peer(cf, data); - if(!result) { - CURL_TRC_CF(data, cf, "peer verified"); - cf->connected = TRUE; - *done = TRUE; - } - } - else { - int detail = SSL_get_error(ctx->tls.ossl.ssl, err); - switch(detail) { - case SSL_ERROR_WANT_READ: - ctx->q.last_io = *Curl_pgrs_now(data); - CURL_TRC_CF(data, cf, "QUIC SSL_connect() -> WANT_RECV"); - goto out; - case SSL_ERROR_WANT_WRITE: - ctx->q.last_io = *Curl_pgrs_now(data); - CURL_TRC_CF(data, cf, "QUIC SSL_connect() -> WANT_SEND"); - result = CURLE_OK; - goto out; -#ifdef SSL_ERROR_WANT_ASYNC - case SSL_ERROR_WANT_ASYNC: - ctx->q.last_io = *Curl_pgrs_now(data); - CURL_TRC_CF(data, cf, "QUIC SSL_connect() -> WANT_ASYNC"); - result = CURLE_OK; - goto out; -#endif -#ifdef SSL_ERROR_WANT_RETRY_VERIFY - case SSL_ERROR_WANT_RETRY_VERIFY: - result = CURLE_OK; - goto out; -#endif - default: - result = cf_osslq_ssl_err(cf, data, detail, CURLE_COULDNT_CONNECT); - goto out; - } - } - -out: - if(result == CURLE_RECV_ERROR && ctx->tls.ossl.ssl && - ctx->protocol_shutdown) { - /* When a QUIC server instance is shutting down, it may send us a - * CONNECTION_CLOSE right away. Our connection then enters the DRAINING - * state. The CONNECT may work in the near future again. Indicate - * that as a "weird" reply. */ - result = CURLE_WEIRD_SERVER_REPLY; - } - -#ifndef CURL_DISABLE_VERBOSE_STRINGS - if(result) { - struct ip_quadruple ip; - - if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip)) - infof(data, "QUIC connect to %s port %u failed: %s", - ip.remote_ip, ip.remote_port, curl_easy_strerror(result)); - } -#endif - if(!result) - result = check_and_set_expiry(cf, data); - if(result || *done) - CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done); - CF_DATA_RESTORE(cf, save); - return result; -} - -static CURLcode h3_stream_open(struct Curl_cfilter *cf, - struct Curl_easy *data, - const uint8_t *buf, size_t len, - size_t *pnwritten) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = NULL; - struct dynhds h2_headers; - size_t nheader; - nghttp3_nv *nva = NULL; - int rc = 0; - unsigned int i; - nghttp3_data_reader reader; - nghttp3_data_reader *preader = NULL; - CURLcode result; - - Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); - - result = h3_data_setup(cf, data); - if(result) - goto out; - stream = H3_STREAM_CTX(ctx, data); - DEBUGASSERT(stream); - if(!stream) { - result = CURLE_FAILED_INIT; - goto out; - } - - result = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, - !data->state.http_ignorecustom ? - data->set.str[STRING_CUSTOMREQUEST] : NULL, - 0, pnwritten); - if(result) - goto out; - if(!stream->h1.done) { - /* need more data */ - goto out; - } - DEBUGASSERT(stream->h1.req); - - result = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data); - if(result) - goto out; - /* no longer needed */ - Curl_h1_req_parse_free(&stream->h1); - - nheader = Curl_dynhds_count(&h2_headers); - nva = curlx_malloc(sizeof(nghttp3_nv) * nheader); - if(!nva) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - for(i = 0; i < nheader; ++i) { - struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i); - nva[i].name = (unsigned char *)e->name; - nva[i].namelen = e->namelen; - nva[i].value = (unsigned char *)e->value; - nva[i].valuelen = e->valuelen; - nva[i].flags = NGHTTP3_NV_FLAG_NONE; - } - - DEBUGASSERT(stream->s.id == -1); - result = cf_osslq_stream_open(&stream->s, ctx->tls.ossl.ssl, 0, - &ctx->stream_bufcp, data); - if(result) { - failf(data, "cannot get bidi streams"); - result = CURLE_SEND_ERROR; - goto out; - } - - switch(data->state.httpreq) { - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - case HTTPREQ_PUT: - /* known request body size or -1 */ - if(data->state.infilesize != -1) - stream->upload_left = data->state.infilesize; - else - /* data sending without specifying the data amount up front */ - stream->upload_left = -1; /* unknown */ - break; - default: - /* there is not request body */ - stream->upload_left = 0; /* no request body */ - break; - } - - stream->send_closed = (stream->upload_left == 0); - if(!stream->send_closed) { - reader.read_data = cb_h3_read_req_body; - preader = &reader; - } - - rc = nghttp3_conn_submit_request(ctx->h3.conn, stream->s.id, - nva, nheader, preader, data); - if(rc) { - switch(rc) { - case NGHTTP3_ERR_CONN_CLOSING: - CURL_TRC_CF(data, cf, "h3sid[%" PRId64 "] failed to send, " - "connection is closing", stream->s.id); - break; - default: - CURL_TRC_CF(data, cf, "h3sid[%" PRId64 "] failed to send -> %d (%s)", - stream->s.id, rc, nghttp3_strerror(rc)); - break; - } - result = CURLE_SEND_ERROR; - goto out; - } - - if(Curl_trc_is_verbose(data)) { - infof(data, "[HTTP/3] [%" PRId64 "] OPENED stream for %s", - stream->s.id, Curl_bufref_ptr(&data->state.url)); - for(i = 0; i < nheader; ++i) { - infof(data, "[HTTP/3] [%" PRId64 "] [%.*s: %.*s]", - stream->s.id, - (int)nva[i].namelen, nva[i].name, - (int)nva[i].valuelen, nva[i].value); - } - } - -out: - curlx_free(nva); - Curl_dynhds_free(&h2_headers); - return result; -} - -static CURLcode cf_osslq_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const uint8_t *buf, size_t len, bool eos, - size_t *pnwritten) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = NULL; - struct cf_call_data save; - CURLcode result = CURLE_OK; - - (void)eos; /* use to end stream */ - CF_DATA_SAVE(save, cf, data); - DEBUGASSERT(cf->connected); - DEBUGASSERT(ctx->tls.ossl.ssl); - DEBUGASSERT(ctx->h3.conn); - *pnwritten = 0; - - result = cf_progress_ingress(cf, data); - if(result) - goto out; - - result = cf_progress_egress(cf, data); - if(result) - goto out; - - stream = H3_STREAM_CTX(ctx, data); - if(!stream || stream->s.id < 0) { - result = h3_stream_open(cf, data, buf, len, pnwritten); - if(result) { - CURL_TRC_CF(data, cf, "failed to open stream -> %d", result); - goto out; - } - stream = H3_STREAM_CTX(ctx, data); - } - else if(stream->closed) { - if(stream->resp_hds_complete) { - /* Server decided to close the stream after having sent us a final - * response. This is valid if it is not interested in the request - * body. This happens on 30x or 40x responses. - * We silently discard the data sent, since this is not a transport - * error situation. */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] discarding data" - "on closed stream with response", stream->s.id); - result = CURLE_OK; - *pnwritten = len; - goto out; - } - CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " - "-> stream closed", stream->s.id, len); - result = CURLE_HTTP3; - goto out; - } - else { - result = Curl_bufq_write(&stream->sendbuf, buf, len, pnwritten); - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send, add to " - "sendbuf(len=%zu) -> %d, %zu", - stream->s.id, len, result, *pnwritten); - if(result) - goto out; - (void)nghttp3_conn_resume_stream(ctx->h3.conn, stream->s.id); - } - - result = Curl_1st_err(result, cf_progress_egress(cf, data)); - -out: - result = Curl_1st_err(result, check_and_set_expiry(cf, data)); - - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %d, %zu", - stream ? stream->s.id : -1, len, result, *pnwritten); - CF_DATA_RESTORE(cf, save); - return result; -} - -static CURLcode recv_closed_stream(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct h3_stream_ctx *stream, - size_t *pnread) -{ - (void)cf; - *pnread = 0; - if(stream->reset) { - failf(data, "HTTP/3 stream %" PRId64 " reset by server (error 0x%" PRIx64 - " %s)", stream->s.id, stream->error3, - vquic_h3_err_str(stream->error3)); - return data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; - } - else if(!stream->resp_hds_complete) { - failf(data, - "HTTP/3 stream %" PRId64 - " was closed cleanly, but before getting" - " all response header fields, treated as error", - stream->s.id); - return CURLE_HTTP3; - } - return CURLE_OK; -} - -static CURLcode cf_osslq_recv(struct Curl_cfilter *cf, struct Curl_easy *data, - char *buf, size_t len, size_t *pnread) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream; - struct cf_call_data save; - CURLcode result = CURLE_OK; - - CF_DATA_SAVE(save, cf, data); - DEBUGASSERT(cf->connected); - DEBUGASSERT(ctx->tls.ossl.ssl); - DEBUGASSERT(ctx->h3.conn); - *pnread = 0; - - stream = H3_STREAM_CTX(ctx, data); - if(!stream) { - result = CURLE_RECV_ERROR; - goto out; - } - - if(!Curl_bufq_is_empty(&stream->recvbuf)) { - result = Curl_bufq_cread(&stream->recvbuf, buf, len, pnread); - if(result) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) -> %d, %zu", - stream->s.id, len, result, *pnread); - goto out; - } - } - - result = Curl_1st_err(result, cf_progress_ingress(cf, data)); - if(result) - goto out; - - /* recvbuf had nothing before, maybe after progressing ingress? */ - if(!*pnread && !Curl_bufq_is_empty(&stream->recvbuf)) { - result = Curl_bufq_cread(&stream->recvbuf, buf, len, pnread); - if(result) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) -> %d, %zu", - stream->s.id, len, result, *pnread); - goto out; - } - } - - if(*pnread) { - Curl_multi_mark_dirty(data); - } - else { - if(stream->closed) { - result = recv_closed_stream(cf, data, stream, pnread); - goto out; - } - result = CURLE_AGAIN; - } - -out: - result = Curl_1st_err(result, cf_progress_egress(cf, data)); - result = Curl_1st_err(result, check_and_set_expiry(cf, data)); - - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv(len=%zu) -> %d, %zu", - stream ? stream->s.id : -1, len, result, *pnread); - CF_DATA_RESTORE(cf, save); - return result; -} - -/* - * Called from transfer.c:data_pending to know if we should keep looping - * to receive more data from the connection. - */ -static bool cf_osslq_data_pending(struct Curl_cfilter *cf, - const struct Curl_easy *data) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - const struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - (void)cf; - return stream && !Curl_bufq_is_empty(&stream->recvbuf); -} - -static CURLcode cf_osslq_cntrl(struct Curl_cfilter *cf, - struct Curl_easy *data, - int event, int arg1, void *arg2) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - struct cf_call_data save; - - CF_DATA_SAVE(save, cf, data); - (void)arg1; - (void)arg2; - switch(event) { - case CF_CTRL_DATA_SETUP: - break; - case CF_CTRL_DATA_PAUSE: - result = h3_data_pause(cf, data, (arg1 != 0)); - break; - case CF_CTRL_DATA_DONE: - h3_data_done(cf, data); - break; - case CF_CTRL_DATA_DONE_SEND: { - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - if(stream && !stream->send_closed) { - stream->send_closed = TRUE; - stream->upload_left = Curl_bufq_len(&stream->sendbuf) - - stream->sendbuf_len_in_flight; - (void)nghttp3_conn_resume_stream(ctx->h3.conn, stream->s.id); - } - break; - } - case CF_CTRL_CONN_INFO_UPDATE: - if(!cf->sockindex && cf->connected) { - cf->conn->httpversion_seen = 30; - Curl_conn_set_multiplex(cf->conn); - } - break; - default: - break; - } - CF_DATA_RESTORE(cf, save); - return result; -} - -static bool cf_osslq_conn_is_alive(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *input_pending) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - bool alive = FALSE; - struct cf_call_data save; - - CF_DATA_SAVE(save, cf, data); - *input_pending = FALSE; - if(!ctx->tls.ossl.ssl) - goto out; - -#ifdef SSL_VALUE_QUIC_IDLE_TIMEOUT - /* Added in OpenSSL v3.3.x */ - { - timediff_t idletime; - uint64_t idle_ms = 0; - if(!SSL_get_value_uint(ctx->tls.ossl.ssl, - SSL_VALUE_CLASS_FEATURE_NEGOTIATED, - SSL_VALUE_QUIC_IDLE_TIMEOUT, &idle_ms)) { - CURL_TRC_CF(data, cf, "error getting negotiated idle timeout, " - "assume connection is dead."); - goto out; - } - CURL_TRC_CF(data, cf, "negotiated idle timeout: %" PRIu64 "ms", idle_ms); - idletime = curlx_ptimediff_ms(Curl_pgrs_now(data), &ctx->q.last_io); - if(idle_ms && idletime > 0 && (uint64_t)idletime > idle_ms) - goto out; - } - -#endif - - if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) - goto out; - - alive = TRUE; - if(*input_pending) { - CURLcode result; - /* This happens before we have sent off a request and the connection is - not in use by any other transfer, there should not be any data here, - only "protocol frames" */ - *input_pending = FALSE; - result = cf_progress_ingress(cf, data); - CURL_TRC_CF(data, cf, "is_alive, progress ingress -> %d", result); - alive = result ? FALSE : TRUE; - } - -out: - CF_DATA_RESTORE(cf, save); - return alive; -} - -static CURLcode cf_osslq_adjust_pollset(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct easy_pollset *ps) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - - if(!ctx->tls.ossl.ssl) { - /* NOP */ - } - else if(!cf->connected) { - /* during handshake, transfer has not started yet. we always - * add our socket for polling if SSL wants to send/recv */ - result = Curl_pollset_set(data, ps, ctx->q.sockfd, - SSL_net_read_desired(ctx->tls.ossl.ssl), - SSL_net_write_desired(ctx->tls.ossl.ssl)); - } - else { - /* once connected, we only modify the socket if it is present. - * this avoids adding it for paused transfers. */ - bool want_recv, want_send; - Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); - if(want_recv || want_send) { - result = Curl_pollset_set(data, ps, ctx->q.sockfd, - SSL_net_read_desired(ctx->tls.ossl.ssl), - SSL_net_write_desired(ctx->tls.ossl.ssl)); - } - else if(ctx->need_recv || ctx->need_send) { - result = Curl_pollset_set(data, ps, ctx->q.sockfd, - ctx->need_recv, ctx->need_send); - } - } - return result; -} - -static CURLcode cf_osslq_query(struct Curl_cfilter *cf, - struct Curl_easy *data, - int query, int *pres1, void *pres2) -{ - struct cf_osslq_ctx *ctx = cf->ctx; - - switch(query) { - case CF_QUERY_MAX_CONCURRENT: { -#ifdef SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL - /* Added in OpenSSL v3.3.x */ - uint64_t v = 0; - if(ctx->tls.ossl.ssl && - !SSL_get_value_uint(ctx->tls.ossl.ssl, SSL_VALUE_CLASS_GENERIC, - SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL, &v)) { - CURL_TRC_CF(data, cf, "error getting available local bidi streams"); - return CURLE_HTTP3; - } - /* we report avail + in_use */ - v += cf->conn->attached_xfers; - *pres1 = (v > INT_MAX) ? INT_MAX : (int)v; -#else - *pres1 = 100; -#endif - CURL_TRC_CF(data, cf, "query max_concurrent -> %d", *pres1); - return CURLE_OK; - } - case CF_QUERY_CONNECT_REPLY_MS: - if(ctx->got_first_byte) { - timediff_t ms = curlx_ptimediff_ms(&ctx->first_byte_at, - &ctx->started_at); - *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; - } - else - *pres1 = -1; - return CURLE_OK; - case CF_QUERY_TIMER_CONNECT: { - struct curltime *when = pres2; - if(ctx->got_first_byte) - *when = ctx->first_byte_at; - return CURLE_OK; - } - case CF_QUERY_TIMER_APPCONNECT: { - struct curltime *when = pres2; - if(cf->connected) - *when = ctx->handshake_at; - return CURLE_OK; - } - case CF_QUERY_HTTP_VERSION: - *pres1 = 30; - return CURLE_OK; - case CF_QUERY_SSL_INFO: - case CF_QUERY_SSL_CTX_INFO: { - struct curl_tlssessioninfo *info = pres2; - if(Curl_vquic_tls_get_ssl_info(&ctx->tls, - (query == CF_QUERY_SSL_CTX_INFO), info)) - return CURLE_OK; - break; - } - case CF_QUERY_ALPN_NEGOTIATED: { - const char **palpn = pres2; - DEBUGASSERT(palpn); - *palpn = cf->connected ? "h3" : NULL; - return CURLE_OK; - } - default: - break; - } - return cf->next ? - cf->next->cft->query(cf->next, data, query, pres1, pres2) : - CURLE_UNKNOWN_OPTION; -} - -struct Curl_cftype Curl_cft_http3 = { - "HTTP/3", - CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX | CF_TYPE_HTTP, - 0, - cf_osslq_destroy, - cf_osslq_connect, - cf_osslq_close, - cf_osslq_shutdown, - cf_osslq_adjust_pollset, - cf_osslq_data_pending, - cf_osslq_send, - cf_osslq_recv, - cf_osslq_cntrl, - cf_osslq_conn_is_alive, - Curl_cf_def_conn_keep_alive, - cf_osslq_query, -}; - -CURLcode Curl_cf_osslq_create(struct Curl_cfilter **pcf, - struct Curl_easy *data, - struct connectdata *conn, - const struct Curl_addrinfo *ai) -{ - struct cf_osslq_ctx *ctx = NULL; - struct Curl_cfilter *cf = NULL; - CURLcode result; - - ctx = curlx_calloc(1, sizeof(*ctx)); - if(!ctx) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - cf_osslq_ctx_init(ctx); - - result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); - if(result) - goto out; - cf->conn = conn; - - result = Curl_cf_udp_create(&cf->next, data, conn, ai, TRNSPRT_QUIC); - if(result) - goto out; - - cf->next->conn = cf->conn; - cf->next->sockindex = cf->sockindex; - -out: - *pcf = (!result) ? cf : NULL; - if(result) { - if(cf) - Curl_conn_cf_discard_chain(&cf, data); - else if(ctx) - cf_osslq_ctx_free(ctx); - } - return result; -} - -bool Curl_conn_is_osslq(const struct Curl_easy *data, - const struct connectdata *conn, - int sockindex) -{ - struct Curl_cfilter *cf = conn ? conn->cfilter[sockindex] : NULL; - - (void)data; - for(; cf; cf = cf->next) { - if(cf->cft == &Curl_cft_http3) - return TRUE; - if(cf->cft->flags & CF_TYPE_IP_CONNECT) - return FALSE; - } - return FALSE; -} - -/* - * Store ngtcp2 version info in this buffer. - */ -void Curl_osslq_ver(char *p, size_t len) -{ - const nghttp3_info *ht3 = nghttp3_version(0); - (void)curl_msnprintf(p, len, "nghttp3/%s", ht3->version_str); -} - -#endif /* !CURL_DISABLE_HTTP && USE_OPENSSL_QUIC && USE_NGHTTP3 */ diff --git a/lib/vquic/curl_osslq.h b/lib/vquic/curl_osslq.h deleted file mode 100644 index 7aa93d6728..0000000000 --- a/lib/vquic/curl_osslq.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef HEADER_CURL_VQUIC_CURL_OSSLQ_H -#define HEADER_CURL_VQUIC_CURL_OSSLQ_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, , 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. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "../curl_setup.h" - -#if !defined(CURL_DISABLE_HTTP) && defined(USE_OPENSSL_QUIC) && \ - defined(USE_NGHTTP3) - -#ifdef HAVE_NETINET_UDP_H -#include -#endif - -struct Curl_cfilter; - -#include "../urldata.h" - -void Curl_osslq_ver(char *p, size_t len); - -CURLcode Curl_cf_osslq_create(struct Curl_cfilter **pcf, - struct Curl_easy *data, - struct connectdata *conn, - const struct Curl_addrinfo *ai); - -bool Curl_conn_is_osslq(const struct Curl_easy *data, - const struct connectdata *conn, - int sockindex); -#endif - -#endif /* HEADER_CURL_VQUIC_CURL_OSSLQ_H */ diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c index 96a3bcb594..9d45833820 100644 --- a/lib/vquic/vquic.c +++ b/lib/vquic/vquic.c @@ -36,7 +36,6 @@ #include "../cfilters.h" #include "../curl_trc.h" #include "curl_ngtcp2.h" -#include "curl_osslq.h" #include "curl_quiche.h" #include "../multiif.h" #include "../progress.h" @@ -66,8 +65,6 @@ void Curl_quic_ver(char *p, size_t len) { #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) Curl_ngtcp2_ver(p, len); -#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) - Curl_osslq_ver(p, len); #elif defined(USE_QUICHE) Curl_quiche_ver(p, len); #endif @@ -703,8 +700,6 @@ CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, DEBUGASSERT(transport == TRNSPRT_QUIC); #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) return Curl_cf_ngtcp2_create(pcf, data, conn, ai); -#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) - return Curl_cf_osslq_create(pcf, data, conn, ai); #elif defined(USE_QUICHE) return Curl_cf_quiche_create(pcf, data, conn, ai); #else diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index c88e2b81d9..bab1b9a3a1 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -3652,11 +3652,7 @@ static CURLcode ossl_init_method(struct Curl_cfilter *cf, return CURLE_SSL_CONNECT_ERROR; } -#ifdef USE_OPENSSL_QUIC - *pmethod = OSSL_QUIC_client_method(); -#else *pmethod = TLS_method(); -#endif break; default: failf(data, "unsupported transport %d in SSL init", peer->transport); diff --git a/m4/curl-openssl.m4 b/m4/curl-openssl.m4 index be30c2c01b..ee63f46c0a 100644 --- a/m4/curl-openssl.m4 +++ b/m4/curl-openssl.m4 @@ -394,24 +394,5 @@ AS_HELP_STRING([--disable-openssl-auto-load-config],[Disable automatic loading o fi ]) - dnl --- - dnl We may use OpenSSL QUIC. - dnl --- - AC_MSG_CHECKING([for QUIC support and OpenSSL >= 3.3]) - AC_LINK_IFELSE([ - AC_LANG_PROGRAM([[ - #include - ]],[[ - #if (OPENSSL_VERSION_NUMBER < 0x30300000L) - #error need at least version 3.3.0 - #endif - OSSL_QUIC_client_method(); - ]]) - ],[ - AC_MSG_RESULT([yes]) - have_openssl_quic=1 - ],[ - AC_MSG_RESULT([no]) - ]) fi ]) diff --git a/tests/http/test_02_download.py b/tests/http/test_02_download.py index bb0f2f31f1..ba11880b0b 100644 --- a/tests/http/test_02_download.py +++ b/tests/http/test_02_download.py @@ -314,8 +314,6 @@ class TestDownload: # download, several at a time, pause and abort paused @pytest.mark.parametrize("proto", Env.http_protos()) def test_02_23a_lib_abort_paused(self, env: Env, httpd, nghttpx, proto): - if proto == 'h3' and env.curl_uses_ossl_quic(): - pytest.skip('OpenSSL QUIC fails here') if proto == 'h3' and env.ci_run and env.curl_uses_lib('quiche'): pytest.skip("fails in CI, but works locally for unknown reasons") count = 10 @@ -341,8 +339,6 @@ class TestDownload: # download, several at a time, abort after n bytes @pytest.mark.parametrize("proto", Env.http_protos()) def test_02_23b_lib_abort_offset(self, env: Env, httpd, nghttpx, proto): - if proto == 'h3' and env.curl_uses_ossl_quic(): - pytest.skip('OpenSSL QUIC fails here') if proto == 'h3' and env.ci_run and env.curl_uses_lib('quiche'): pytest.skip("fails in CI, but works locally for unknown reasons") count = 10 @@ -368,8 +364,6 @@ class TestDownload: # download, several at a time, abort after n bytes @pytest.mark.parametrize("proto", Env.http_protos()) def test_02_23c_lib_fail_offset(self, env: Env, httpd, nghttpx, proto): - if proto == 'h3' and env.curl_uses_ossl_quic(): - pytest.skip('OpenSSL QUIC fails here') if proto == 'h3' and env.ci_run and env.curl_uses_lib('quiche'): pytest.skip("fails in CI, but works locally for unknown reasons") count = 10 diff --git a/tests/http/test_03_goaway.py b/tests/http/test_03_goaway.py index 2e1d6a5802..524f11e953 100644 --- a/tests/http/test_03_goaway.py +++ b/tests/http/test_03_goaway.py @@ -75,8 +75,6 @@ class TestGoAway: @pytest.mark.skipif(condition=not Env.have_h3(), reason="h3 not supported") def test_03_02_h3_goaway(self, env: Env, httpd, nghttpx): proto = 'h3' - if proto == 'h3' and env.curl_uses_ossl_quic(): - pytest.skip('OpenSSL QUIC fails here') count = 3 self.r = None diff --git a/tests/http/test_05_errors.py b/tests/http/test_05_errors.py index 29225983e9..5b3d8f4a58 100644 --- a/tests/http/test_05_errors.py +++ b/tests/http/test_05_errors.py @@ -58,8 +58,6 @@ class TestErrors: # download files, check that we get CURLE_PARTIAL_FILE for all @pytest.mark.parametrize("proto", Env.http_mplx_protos()) def test_05_02_partial_20(self, env: Env, httpd, nghttpx, proto): - if proto == 'h3' and env.curl_uses_ossl_quic(): - pytest.skip("openssl-quic is flaky in yielding proper error codes") if proto == 'h3' and env.curl_uses_lib('quiche') and \ not env.curl_lib_version_at_least('quiche', '0.24.8'): pytest.skip("quiche issue #2277 not fixed") diff --git a/tests/http/test_07_upload.py b/tests/http/test_07_upload.py index 9a21a8bb7e..070afeb757 100644 --- a/tests/http/test_07_upload.py +++ b/tests/http/test_07_upload.py @@ -507,8 +507,6 @@ class TestUpload: @pytest.mark.parametrize("proto", Env.http_protos()) def test_07_43_upload_denied(self, env: Env, httpd, nghttpx, proto): - if proto == 'h3' and env.curl_uses_ossl_quic(): - pytest.skip("openssl-quic is flaky in filed PUTs") fdata = os.path.join(env.gen_dir, 'data-10m') count = 1 max_upload = 128 * 1024 diff --git a/tests/http/test_14_auth.py b/tests/http/test_14_auth.py index 9f2ae7a27f..da3e716a68 100644 --- a/tests/http/test_14_auth.py +++ b/tests/http/test_14_auth.py @@ -65,8 +65,6 @@ class TestAuth: def test_14_03_digest_put_auth(self, env: Env, httpd, nghttpx, proto): if not env.curl_has_feature('digest'): pytest.skip("curl built without digest") - if proto == 'h3' and env.curl_uses_ossl_quic(): - pytest.skip("openssl-quic is flaky in retrying POST") data='0123456789' curl = CurlClient(env=env) url = f'https://{env.authority_for(env.domain1, proto)}/restricted/digest/data.json' @@ -97,7 +95,7 @@ class TestAuth: def test_14_05_basic_large_pw(self, env: Env, httpd, nghttpx, proto): if proto == 'h3' and not env.curl_uses_lib('ngtcp2'): # See - pytest.skip("quiche/openssl-quic have problems with large requests") + pytest.skip("quiche has problems with large requests") # just large enough that nghttp2 will submit password = 'x' * (47 * 1024) fdata = os.path.join(env.gen_dir, 'data-10m') diff --git a/tests/http/testenv/curl.py b/tests/http/testenv/curl.py index 129abf0ec4..4fc11c7923 100644 --- a/tests/http/testenv/curl.py +++ b/tests/http/testenv/curl.py @@ -515,7 +515,7 @@ class ExecResult: s = self._stats[idx] url = s['url_effective'] - # connect time is sometimes reported as 0 by openssl-quic (sigh) + self.check_stat_positive_or_0(s, idx, 'time_connect') # all stat keys which reporting timings all_keys = {