vquic: ngtcp2 + openssl support

With the new addition of OpenSSL QUIC API support and the support in
ngtcp2 main branch, make the necessary adjustments in curl to support
this combination.

- add support in configure.ac to detect the feature OPENSSL_QUIC_API2 in
  openssl
- initialise ngtcp2 properly in this combination
- add a Curl_vquic_init() for global initialisation that ngtcp2 likes
  for performance reasons
- add documentation on how to build in docs/HTTP3.md
- add CI testing in http3-linux.yml

Assisted-by: Viktor Szakats
Closes #17027
This commit is contained in:
Stefan Eissing 2025-04-16 16:16:26 +02:00 committed by Daniel Stenberg
parent 07cc50f8eb
commit 5eefdd71a3
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
10 changed files with 223 additions and 40 deletions

View File

@ -49,7 +49,7 @@ env:
# renovate: datasource=github-tags depName=ngtcp2/nghttp3 versioning=semver registryUrl=https://github.com
nghttp3-version: 1.8.0
# renovate: datasource=github-tags depName=ngtcp2/ngtcp2 versioning=semver registryUrl=https://github.com
ngtcp2-version: 1.11.0
ngtcp2-version: 1.12.0
# renovate: datasource=github-tags depName=nghttp2/nghttp2 versioning=semver registryUrl=https://github.com
nghttp2-version: 1.65.0
# renovate: datasource=github-tags depName=cloudflare/quiche versioning=semver registryUrl=https://github.com
@ -60,6 +60,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'cache openssl'
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4
id: cache-openssl-http3
env:
cache-name: cache-openssl-http3
with:
path: ~/openssl/build
key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.openssl-version }}
- name: 'cache quictls'
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4
id: cache-quictls-no-deprecated
@ -103,7 +112,7 @@ jobs:
cache-name: cache-ngtcp2
with:
path: ~/ngtcp2/build
key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.ngtcp2-version }}-${{ env.quictls-version }}-${{ env.gnutls-version }}-${{ env.wolfssl-version }}
key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.ngtcp2-version }}-${{ env.openssl-version }}-${{ env.quictls-version }}-${{ env.gnutls-version }}-${{ env.wolfssl-version }}
- name: 'cache nghttp2'
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4
@ -116,6 +125,7 @@ jobs:
- id: settings
if: |
steps.cache-openssl-http3.outputs.cache-hit != 'true' ||
steps.cache-quictls-no-deprecated.outputs.cache-hit != 'true' ||
steps.cache-gnutls.outputs.cache-hit != 'true' ||
steps.cache-wolfssl.outputs.cache-hit != 'true' ||
@ -140,6 +150,16 @@ jobs:
echo 'CC=gcc-12' >> $GITHUB_ENV
echo 'CXX=g++-12' >> $GITHUB_ENV
- name: 'build openssl'
if: steps.cache-openssl-http3.outputs.cache-hit != 'true'
run: |
cd $HOME
git clone --quiet --depth=1 -b openssl-${{ env.openssl-version }} https://github.com/openssl/openssl
cd openssl
./config --prefix=$PWD/build --libdir=lib no-makedepend no-apps no-docs no-tests
make
make -j1 install_sw
- name: 'build quictls'
if: steps.cache-quictls-no-deprecated.outputs.cache-hit != 'true'
run: |
@ -192,13 +212,18 @@ jobs:
- name: 'build ngtcp2'
if: steps.cache-ngtcp2.outputs.cache-hit != 'true'
# building twice to get crypto libs for ossl and quictls installed
run: |
cd $HOME
git clone --quiet --depth=1 -b v${{ env.ngtcp2-version }} https://github.com/ngtcp2/ngtcp2
cd ngtcp2
autoreconf -fi
./configure --disable-dependency-tracking --prefix=$PWD/build \
PKG_CONFIG_PATH="$HOME/quictls/build/lib/pkgconfig:$HOME/gnutls/build/lib/pkgconfig:$HOME/wolfssl/build/lib/pkgconfig" \
PKG_CONFIG_PATH="$HOME/quictls/build/lib/pkgconfig" --enable-lib-only --with-quictls
make install
make clean
./configure --disable-dependency-tracking --prefix=$PWD/build \
PKG_CONFIG_PATH="$HOME/openssl/build/lib/pkgconfig:$HOME/gnutls/build/lib/pkgconfig:$HOME/wolfssl/build/lib/pkgconfig" \
--enable-lib-only --with-openssl --with-gnutls --with-wolfssl
make install
@ -226,6 +251,15 @@ jobs:
fail-fast: false
matrix:
build:
- name: openssl
PKG_CONFIG_PATH: '$HOME/openssl/build/lib/pkgconfig:$HOME/nghttp3/build/lib/pkgconfig:$HOME/ngtcp2/build/lib/pkgconfig:$HOME/nghttp2/build/lib/pkgconfig'
configure: >-
LDFLAGS="-Wl,-rpath,$HOME/openssl/build/lib"
--with-ngtcp2=$HOME/ngtcp2/build --enable-warnings --enable-werror --enable-debug --disable-ntlm
--with-test-nghttpx="$HOME/nghttp2/build/bin/nghttpx"
--with-openssl=$HOME/openssl/build --enable-ssls-export
--with-libuv
- name: quictls
PKG_CONFIG_PATH: '$HOME/quictls/build/lib/pkgconfig:$HOME/nghttp3/build/lib/pkgconfig:$HOME/ngtcp2/build/lib/pkgconfig:$HOME/nghttp2/build/lib/pkgconfig'
configure: >-
@ -264,9 +298,9 @@ jobs:
-DCURL_USE_LIBUV=ON
- name: openssl-quic
PKG_CONFIG_PATH: '$HOME/openssl/build/lib64/pkgconfig'
PKG_CONFIG_PATH: '$HOME/openssl/build/lib/pkgconfig'
configure: >-
LDFLAGS="-Wl,-rpath,$HOME/openssl/build/lib64"
LDFLAGS="-Wl,-rpath,$HOME/openssl/build/lib"
--enable-warnings --enable-werror --enable-debug --disable-ntlm
--with-test-nghttpx="$HOME/nghttp2/build/bin/nghttpx"
--with-openssl=$HOME/openssl/build --with-openssl-quic
@ -309,6 +343,17 @@ jobs:
echo 'CC=gcc-12' >> $GITHUB_ENV
echo 'CXX=g++-12' >> $GITHUB_ENV
- name: 'cache openssl'
if: matrix.build.name == 'openssl' || matrix.build.name == 'openssl-quic'
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4
id: cache-openssl-http3
env:
cache-name: cache-openssl-http3
with:
path: ~/openssl/build
key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.openssl-version }}
fail-on-cache-miss: true
- name: 'cache quictls'
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4
id: cache-quictls-no-deprecated
@ -358,7 +403,7 @@ jobs:
cache-name: cache-ngtcp2
with:
path: ~/ngtcp2/build
key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.ngtcp2-version }}-${{ env.quictls-version }}-${{ env.gnutls-version }}-${{ env.wolfssl-version }}
key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.ngtcp2-version }}-${{ env.openssl-version }}-${{ env.quictls-version }}-${{ env.gnutls-version }}-${{ env.wolfssl-version }}
fail-on-cache-miss: true
- name: 'cache nghttp2'
@ -371,26 +416,6 @@ jobs:
key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.nghttp2-version }}-${{ env.quictls-version }}-${{ env.ngtcp2-version }}-${{ env.nghttp3-version }}
fail-on-cache-miss: true
- name: 'cache openssl'
if: matrix.build.name == 'openssl-quic'
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4
id: cache-openssl
env:
cache-name: cache-openssl
with:
path: ~/openssl/build
key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.openssl-version }}
- name: 'install openssl'
if: matrix.build.name == 'openssl-quic' && steps.cache-openssl.outputs.cache-hit != 'true'
run: |
git clone --quiet --depth=1 -b openssl-${{ env.openssl-version }} https://github.com/openssl/openssl
cd openssl
./config --prefix=$HOME/openssl/build no-makedepend no-apps no-docs no-tests
make
make -j1 install_sw
cat exporters/openssl.pc
- name: 'cache quiche'
if: matrix.build.name == 'quiche'
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4

View File

@ -3155,7 +3155,8 @@ if test X"$want_tcp2" != Xno; then
fi
fi
if test "x$USE_NGTCP2" = "x1" -a "x$OPENSSL_ENABLED" = "x1" -a "x$OPENSSL_IS_BORINGSSL" != "x1"; then
if test "x$USE_NGTCP2" = "x1" -a "x$OPENSSL_ENABLED" = "x1" -a \
"x$OPENSSL_IS_BORINGSSL" != "x1" -a "x$OPENSSL_QUIC_API2" != "x1"; then
dnl backup the pre-ngtcp2_crypto_quictls variables
CLEANLDFLAGS="$LDFLAGS"
CLEANLDFLAGSPC="$LDFLAGSPC"
@ -3212,6 +3213,65 @@ if test "x$USE_NGTCP2" = "x1" -a "x$OPENSSL_ENABLED" = "x1" -a "x$OPENSSL_IS_BOR
fi
fi
if test "x$USE_NGTCP2" = "x1" -a "x$OPENSSL_ENABLED" = "x1" -a \
"x$OPENSSL_IS_BORINGSSL" != "x1" -a "x$OPENSSL_QUIC_API2" = "x1"; then
dnl backup the pre-ngtcp2_crypto_ossl variables
CLEANLDFLAGS="$LDFLAGS"
CLEANLDFLAGSPC="$LDFLAGSPC"
CLEANCPPFLAGS="$CPPFLAGS"
CLEANLIBS="$LIBS"
CURL_CHECK_PKGCONFIG(libngtcp2_crypto_ossl, $want_tcp2_path)
if test "$PKGCONFIG" != "no"; then
LIB_NGTCP2_CRYPTO_QUICTLS=`CURL_EXPORT_PCDIR([$want_tcp2_path])
$PKGCONFIG --libs-only-l libngtcp2_crypto_ossl`
AC_MSG_NOTICE([-l is $LIB_NGTCP2_CRYPTO_QUICTLS])
CPP_NGTCP2_CRYPTO_QUICTLS=`CURL_EXPORT_PCDIR([$want_tcp2_path]) dnl
$PKGCONFIG --cflags-only-I libngtcp2_crypto_ossl`
AC_MSG_NOTICE([-I is $CPP_NGTCP2_CRYPTO_QUICTLS])
LD_NGTCP2_CRYPTO_QUICTLS=`CURL_EXPORT_PCDIR([$want_tcp2_path])
$PKGCONFIG --libs-only-L libngtcp2_crypto_ossl`
AC_MSG_NOTICE([-L is $LD_NGTCP2_CRYPTO_QUICTLS])
LDFLAGS="$LDFLAGS $LD_NGTCP2_CRYPTO_QUICTLS"
LDFLAGSPC="$LDFLAGSPC $LD_NGTCP2_CRYPTO_QUICTLS"
CPPFLAGS="$CPPFLAGS $CPP_NGTCP2_CRYPTO_QUICTLS"
LIBS="$LIB_NGTCP2_CRYPTO_QUICTLS $LIBS"
if test "x$cross_compiling" != "xyes"; then
DIR_NGTCP2_CRYPTO_QUICTLS=`echo $LD_NGTCP2_CRYPTO_QUICTLS | $SED -e 's/^-L//'`
fi
AC_CHECK_LIB(ngtcp2_crypto_ossl, ngtcp2_crypto_recv_client_initial_cb,
[
AC_CHECK_HEADERS(ngtcp2/ngtcp2_crypto.h,
USE_NGTCP2=1
CURL_LIBRARY_PATH="$CURL_LIBRARY_PATH:$DIR_NGTCP2_CRYPTO_QUICTLS"
export CURL_LIBRARY_PATH
AC_MSG_NOTICE([Added $DIR_NGTCP2_CRYPTO_QUICTLS to CURL_LIBRARY_PATH])
LIBCURL_PC_REQUIRES_PRIVATE="$LIBCURL_PC_REQUIRES_PRIVATE libngtcp2_crypto_ossl"
AC_DEFINE(OPENSSL_QUIC_API2, 1, [openssl with new QUIC API])
)
],
dnl not found, revert back to clean variables
LDFLAGS=$CLEANLDFLAGS
LDFLAGSPC=$CLEANLDFLAGSPC
CPPFLAGS=$CLEANCPPFLAGS
LIBS=$CLEANLIBS
)
else
dnl no ngtcp2_crypto_ossl pkg-config found, deal with it
if test X"$want_tcp2" != Xdefault; then
dnl To avoid link errors, we do not allow --with-ngtcp2 without
dnl a pkgconfig file
AC_MSG_ERROR([--with-ngtcp2 was specified but could not find ngtcp2_crypto_ossl pkg-config file.])
fi
fi
fi
if test "x$USE_NGTCP2" = "x1" -a "x$OPENSSL_ENABLED" = "x1" -a "x$OPENSSL_IS_BORINGSSL" = "x1"; then
dnl backup the pre-ngtcp2_crypto_boringssl variables
CLEANLDFLAGS="$LDFLAGS"

View File

@ -50,6 +50,50 @@ improvements.
The build examples use `$NGHTTP3_VERSION` and `$NGTCP2_VERSION` as placeholders
for the version you build.
## Build with OpenSSL
OpenSSL v3.5.0+ offers APIs for integration with *ngtcp2* v1.12.0+. Earlier
versions do not work.
Build OpenSSL (version 3.5.0 or newer):
% git clone --quiet --depth=1 -b openssl-$OPENSSL_VERSION https://github.com/openssl/openssl
% cd openssl
% ./config --prefix=<somewhere1> --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=<somewhere2> --enable-lib-only
% make
% make install
Build ngtcp2:
% cd ..
% git clone -b $NGTCP2_VERSION https://github.com/ngtcp2/ngtcp2
% cd ngtcp2
% autoreconf -fi
% ./configure PKG_CONFIG_PATH=<somewhere1>/lib/pkgconfig:<somewhere2>/lib/pkgconfig LDFLAGS="-Wl,-rpath,<somewhere1>/lib" --prefix=<somewhere3> --enable-lib-only --with-openssl
% make
% make install
Build curl:
% cd ..
% git clone https://github.com/curl/curl
% cd curl
% autoreconf -fi
% LDFLAGS="-Wl,-rpath,<somewhere1>/lib" ./configure --with-openssl=<somewhere1> --with-nghttp3=<somewhere2> --with-ngtcp2=<somewhere3>
% make
% make install
## Build with quictls
OpenSSL does not offer the required APIs for building a QUIC client. You need
@ -59,7 +103,7 @@ Build quictls (any `+quic` tagged version works):
% git clone --depth 1 -b openssl-3.1.4+quic https://github.com/quictls/openssl
% cd openssl
% ./config enable-tls1_3 --prefix=<somewhere1>
% ./config enable-tls1_3 --prefix=<somewhere1> --libdir=lib
% make
% make install
@ -94,8 +138,6 @@ Build curl:
% make
% make install
For OpenSSL 3.0.0 or later builds on Linux for x86_64 architecture, substitute all occurrences of "/lib" with "/lib64"
## Build with GnuTLS
Build GnuTLS:

View File

@ -49,6 +49,7 @@
#include "transfer.h"
#include "vtls/vtls.h"
#include "vtls/vtls_scache.h"
#include "vquic/vquic.h"
#include "url.h"
#include "getinfo.h"
#include "hostip.h"
@ -170,6 +171,11 @@ static CURLcode global_init(long flags, bool memoryfuncs)
goto fail;
}
if(!Curl_vquic_init()) {
DEBUGF(fprintf(stderr, "Error: Curl_vquic_init failed\n"));
goto fail;
}
if(Curl_win32_init(flags)) {
DEBUGF(fprintf(stderr, "Error: win32_init failed\n"));
goto fail;

View File

@ -32,6 +32,8 @@
#include <openssl/err.h>
#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
#include <ngtcp2/ngtcp2_crypto_boringssl.h>
#elif defined(OPENSSL_QUIC_API2)
#include <ngtcp2/ngtcp2_crypto_ossl.h>
#else
#include <ngtcp2/ngtcp2_crypto_quictls.h>
#endif
@ -117,6 +119,9 @@ struct cf_ngtcp2_ctx {
struct cf_quic_ctx q;
struct ssl_peer peer;
struct curl_tls_ctx tls;
#ifdef OPENSSL_QUIC_API2
ngtcp2_crypto_ossl_ctx *ossl_ctx;
#endif
ngtcp2_path connected_path;
ngtcp2_conn *qconn;
ngtcp2_cid dcid;
@ -2014,10 +2019,20 @@ static void cf_ngtcp2_ctx_close(struct cf_ngtcp2_ctx *ctx)
ctx->qlogfd = -1;
Curl_vquic_tls_cleanup(&ctx->tls);
vquic_ctx_free(&ctx->q);
if(ctx->h3conn)
if(ctx->h3conn) {
nghttp3_conn_del(ctx->h3conn);
if(ctx->qconn)
ctx->h3conn = NULL;
}
if(ctx->qconn) {
ngtcp2_conn_del(ctx->qconn);
ctx->qconn = NULL;
}
#ifdef OPENSSL_QUIC_API2
if(ctx->ossl_ctx) {
ngtcp2_crypto_ossl_ctx_del(ctx->ossl_ctx);
ctx->ossl_ctx = NULL;
}
#endif
ctx->call_data = save;
}
@ -2133,6 +2148,7 @@ static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
{
CURL_TRC_CF(data, cf, "destroy");
if(cf->ctx) {
cf_ngtcp2_close(cf, data);
cf_ngtcp2_ctx_free(cf->ctx);
cf->ctx = NULL;
}
@ -2291,6 +2307,8 @@ static CURLcode cf_ngtcp2_tls_ctx_setup(struct Curl_cfilter *cf,
failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed");
return CURLE_FAILED_INIT;
}
#elif defined(OPENSSL_QUIC_API2)
/* nothing to do */
#else
if(ngtcp2_crypto_quictls_configure_client_context(ctx->ossl.ssl_ctx) != 0) {
failf(data, "ngtcp2_crypto_quictls_configure_client_context failed");
@ -2453,6 +2471,9 @@ static const struct alpn_spec ALPN_SPEC_H3 = {
if(rc)
return CURLE_QUIC_CONNECT_ERROR;
ctx->conn_ref.get_conn = get_conn;
ctx->conn_ref.user_data = cf;
result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, &ALPN_SPEC_H3,
cf_ngtcp2_tls_ctx_setup, &ctx->tls,
&ctx->conn_ref,
@ -2460,7 +2481,17 @@ static const struct alpn_spec ALPN_SPEC_H3 = {
if(result)
return result;
#ifdef USE_OPENSSL
#if defined(USE_OPENSSL) && defined(OPENSSL_QUIC_API2)
if(ngtcp2_crypto_ossl_ctx_new(&ctx->ossl_ctx, ctx->tls.ossl.ssl) != 0) {
failf(data, "ngtcp2_crypto_ossl_ctx_new failed");
return CURLE_FAILED_INIT;
}
ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->ossl_ctx);
if(ngtcp2_crypto_ossl_configure_client_session(ctx->tls.ossl.ssl) != 0) {
failf(data, "ngtcp2_crypto_ossl_configure_client_session failed");
return CURLE_FAILED_INIT;
}
#elif defined(USE_OPENSSL)
SSL_set_quic_use_legacy_codepoint(ctx->tls.ossl.ssl, 0);
ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.ossl.ssl);
#elif defined(USE_GNUTLS)
@ -2473,9 +2504,6 @@ static const struct alpn_spec ALPN_SPEC_H3 = {
ngtcp2_ccerr_default(&ctx->last_error);
ctx->conn_ref.get_conn = get_conn;
ctx->conn_ref.user_data = cf;
return CURLE_OK;
}

View File

@ -33,6 +33,9 @@
#endif
#include <ngtcp2/ngtcp2_crypto.h>
#ifdef OPENSSL_QUIC_API2
#include <ngtcp2/ngtcp2_crypto_ossl.h>
#endif
#include <nghttp3/nghttp3.h>
#ifdef USE_OPENSSL
#include <openssl/ssl.h>

View File

@ -58,6 +58,16 @@
#define NW_SEND_CHUNKS 2
int Curl_vquic_init(void)
{
#if defined(USE_NGTCP2) && defined(OPENSSL_QUIC_API2)
if(ngtcp2_crypto_ossl_init())
return 0;
#endif
return 1;
}
void Curl_quic_ver(char *p, size_t len)
{
#if defined(USE_NGTCP2) && defined(USE_NGHTTP3)

View File

@ -33,6 +33,7 @@ struct connectdata;
struct Curl_addrinfo;
void Curl_quic_ver(char *p, size_t len);
int Curl_vquic_init(void);
CURLcode Curl_qlogdir(struct Curl_easy *data,
unsigned char *scid,
@ -48,6 +49,8 @@ CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
extern struct Curl_cftype Curl_cft_http3;
#else
#define Curl_vquic_init() 1
#endif /* !USE_HTTP3 */
CURLcode Curl_conn_may_http3(struct Curl_easy *data,

View File

@ -200,7 +200,8 @@
#define OSSL_PACKAGE "BoringSSL"
#elif defined(OPENSSL_IS_AWSLC)
#define OSSL_PACKAGE "AWS-LC"
#elif (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || defined(USE_MSH3)
#elif (defined(USE_NGTCP2) && defined(USE_NGHTTP3) && \
!defined(OPENSSL_QUIC_API2)) || defined(USE_MSH3)
#define OSSL_PACKAGE "quictls"
#else
#define OSSL_PACKAGE "OpenSSL"

View File

@ -316,12 +316,17 @@ if test "x$OPT_OPENSSL" != xno; then
fi
dnl is this OpenSSL (fork) providing the original QUIC API?
AC_CHECK_FUNCS([SSL_set_quic_use_legacy_codepoint],
[QUIC_ENABLED=yes])
AC_CHECK_FUNCS([SSL_set_quic_use_legacy_codepoint], [QUIC_ENABLED=yes])
if test "$QUIC_ENABLED" = "yes"; then
AC_MSG_NOTICE([OpenSSL fork speaks QUIC API])
else
AC_MSG_NOTICE([OpenSSL version does not speak QUIC API])
AC_CHECK_FUNCS([SSL_set_quic_tls_cbs], [QUIC_ENABLED=yes])
if test "$QUIC_ENABLED" = "yes"; then
AC_MSG_NOTICE([OpenSSL with QUIC APIv2])
OPENSSL_QUIC_API2=1
else
AC_MSG_NOTICE([OpenSSL version does not speak any known QUIC API])
fi
fi
if test "$OPENSSL_ENABLED" = "1"; then