exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit 21bee06765ded280c10e702af051fb3402c33ad2
parent 5eb9e43bb063caa873ade4a0a8f767d4107fd5b3
Author: Christian Grothoff <christian@grothoff.org>
Date:   Tue, 11 Aug 2026 23:54:51 +0200

clean up HTTP1/3 selection logic

Diffstat:
Mmeson.build | 2+-
Msrc/curl/curl.c | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/taler/exchange/common.h | 18++++++++++++++++--
Msrc/include/taler/taler_auditor_service.h | 15+++++++++++++--
Msrc/include/taler/taler_curl_lib.h | 19+++++++++++++++++++
Msrc/lib/auditor_api_curl_defaults.c | 9++++-----
Msrc/lib/exchange_api_curl_defaults.c | 137++++++++-----------------------------------------------------------------------
7 files changed, 204 insertions(+), 134 deletions(-)

diff --git a/meson.build b/meson.build @@ -283,7 +283,7 @@ if not get_option('only-doc') libltversions = [ ['libtalerutil', '17:0:3'], ['libtalerjson', '9:0:5'], - ['libtalercurl', '0:1:0'], + ['libtalercurl', '1:0:1'], ['libtalerpq', '1:0:0'], ['libtalersq', '0:0:0'], ['libtalermhd', '8:0:1'], diff --git a/src/curl/curl.c b/src/curl/curl.c @@ -22,6 +22,7 @@ * @author Christian Grothoff */ #include "taler/taler_curl_lib.h" +#include <curl/curl.h> #if TALER_CURL_COMPRESS_BODIES @@ -29,6 +30,143 @@ #endif +/* libcurl versions before 7.87.0 lack 'feature_names' in the version + info, and versions before 7.66.0 lack 'quic_version' and HTTP/3 + altogether. With those we simply never consider HTTP/3 usable. */ +#if LIBCURL_VERSION_NUM >= 0x075700 + +/** + * Check if feature @a name is in the @a vi + * + * @param vi version data to test + * @param name feature to test for + * @return true if feature is supported by curl + */ +static bool +has_feature (const curl_version_info_data *vi, + const char *name) +{ + if (NULL == vi->feature_names) + return false; + for (const char *const *p = vi->feature_names; NULL != *p; p++) + if (0 == + strcmp (*p, + name)) + return true; + return false; +} + + +/** + * Check if @a s is non-NULL and starts with @a prefix + * + * @param s string to check, can be NULL + * @param prefix prefix to check for + * @return true if @a s starts with @a prefix + */ +static bool +starts_with (const char *s, + const char *prefix) +{ + return ( (NULL != s) && + (0 == strncmp (s, + prefix, + strlen (prefix)) ) ); +} + + +#endif + + +/** + * Check if using HTTP/3 is likely OK with our version of libcurl. + * + * @return true if HTTP/3 should be well-supported + */ +static bool +curl_http3_is_conservative_ok (void) +{ +#if LIBCURL_VERSION_NUM >= 0x075700 + const curl_version_info_data *vi + = curl_version_info (CURLVERSION_NOW); + + if (! has_feature (vi, + "HTTP3")) + return false; + + /* + * Require a QUIC backend we regard as production-ready. + * + * curl currently considers ngtcp2 non-experimental. + * quiche is still experimental. + */ + if (! starts_with (vi->quic_version, + "ngtcp2/")) + return false; + + /* + * Conservative policy: don't use HTTP/3 with GnuTLS. + * (too many bugs in recent releases still) + */ + if (starts_with (vi->ssl_version, + "GnuTLS/")) + return false; + + /* + * At this point the remaining ngtcp2 TLS configurations + * are the OpenSSL family and wolfSSL. + * + * We deliberately whitelist them rather than assuming + * every possible future TLS backend is safe. + */ + if (starts_with (vi->ssl_version, + "OpenSSL/")) + return true; + if (starts_with (vi->ssl_version, + "wolfSSL/")) + return true; +#endif + return false; +} + + +void +TALER_curl_set_http_version (CURL *eh, + bool enable_http3) +{ + static enum GNUNET_GenericReturnValue http3 = GNUNET_SYSERR; + long version = CURL_HTTP_VERSION_1_1; + + if (enable_http3) + { + if (GNUNET_SYSERR == http3) + http3 = curl_http3_is_conservative_ok () + ? GNUNET_YES + : GNUNET_NO; +#ifdef CURL_HTTP_VERSION_3 + if (GNUNET_YES == http3) + { + /* Falls back to HTTP/2 or HTTP/1.1 if the server does + not speak HTTP/3. */ + version = CURL_HTTP_VERSION_3; + } + else +#endif + { +#ifdef CURL_HTTP_VERSION_2TLS + /* HTTP/3 support of this libcurl build is spotty, at + most use HTTP/2 (which falls back to HTTP/1.1). */ + version = CURL_HTTP_VERSION_2TLS; +#endif + } + } + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HTTP_VERSION, + version)); +} + + void TALER_curl_set_secure_redirect_policy (CURL *eh, const char *url) diff --git a/src/include/taler/exchange/common.h b/src/include/taler/exchange/common.h @@ -45,20 +45,34 @@ enum TALER_EXCHANGE_GlobalOptions { /** - * Use defaults. + * Use defaults. In particular, this means that HTTP/1.1 is used, as + * that is the conservative, best-tested option. */ TALER_EXCHANGE_GO_NONE = 0, /** - * Force use of HTTP/1.1. + * Force use of HTTP/1.1. As HTTP/1.1 is already the default, this + * flag only matters to override an otherwise given + * #TALER_EXCHANGE_GO_ENABLE_HTTP3. */ TALER_EXCHANGE_GO_FORCE_HTTP1_1 = 1, + /** + * Allow the use of HTTP/2 and HTTP/3. Note that HTTP/3 is only + * actually enabled if the libcurl we run against is deemed suitable + * (see #TALER_curl_set_http_version()). Ignored if + * #TALER_EXCHANGE_GO_FORCE_HTTP1_1 is also set. + */ + TALER_EXCHANGE_GO_ENABLE_HTTP3 = 2, + }; /** * Set global options for HTTP requests made with libtalerexchange. + * Note that this also applies the equivalent options to + * libtalerauditor, as libtalerexchange uses libtalerauditor to + * talk to the auditors of an exchange. * * @param go global options to use */ diff --git a/src/include/taler/taler_auditor_service.h b/src/include/taler/taler_auditor_service.h @@ -120,15 +120,26 @@ enum TALER_AUDITOR_GlobalOptions { /** - * Use defaults. + * Use defaults. In particular, this means that HTTP/1.1 is used, as + * that is the conservative, best-tested option. */ TALER_AUDITOR_GO_NONE = 0, /** - * Force use of HTTP/1.1. + * Force use of HTTP/1.1. As HTTP/1.1 is already the default, this + * flag only matters to override an otherwise given + * #TALER_AUDITOR_GO_ENABLE_HTTP3. */ TALER_AUDITOR_GO_FORCE_HTTP1_1 = 1, + /** + * Allow the use of HTTP/2 and HTTP/3. Note that HTTP/3 is only + * actually enabled if the libcurl we run against is deemed suitable + * (see #TALER_curl_set_http_version()). Ignored if + * #TALER_AUDITOR_GO_FORCE_HTTP1_1 is also set. + */ + TALER_AUDITOR_GO_ENABLE_HTTP3 = 2, + }; diff --git a/src/include/taler/taler_curl_lib.h b/src/include/taler/taler_curl_lib.h @@ -92,4 +92,23 @@ void TALER_curl_set_secure_redirect_policy (CURL *eh, const char *url); + +/** + * Select the HTTP protocol version to be used for @a eh. + * + * Unless @a enable_http3 is true, we force the use of HTTP/1.1, as that + * is the conservative and by far best-tested code path. Even if @a + * enable_http3 is true, we only actually enable HTTP/3 if the libcurl we + * are running against was built with a QUIC and TLS backend combination + * that we consider production-ready; otherwise we merely allow HTTP/2 + * (which in turn falls back to HTTP/1.1 if unavailable). + * + * @param[in,out] eh easy handle to modify + * @param enable_http3 true if the application explicitly asked for HTTP/3 + */ +void +TALER_curl_set_http_version (CURL *eh, + bool enable_http3); + + #endif diff --git a/src/lib/auditor_api_curl_defaults.c b/src/lib/auditor_api_curl_defaults.c @@ -63,10 +63,9 @@ TALER_AUDITOR_curl_easy_get_ (const char *url) curl_easy_setopt (eh, CURLOPT_TCP_FASTOPEN, 1L)); - if (TALER_AUDITOR_GO_FORCE_HTTP1_1 & aglobal_options) - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_HTTP_VERSION, - CURL_HTTP_VERSION_1_1)); + TALER_curl_set_http_version ( + eh, + (0 != (TALER_AUDITOR_GO_ENABLE_HTTP3 & aglobal_options)) && + (0 == (TALER_AUDITOR_GO_FORCE_HTTP1_1 & aglobal_options))); return eh; } diff --git a/src/lib/exchange_api_curl_defaults.c b/src/lib/exchange_api_curl_defaults.c @@ -34,105 +34,16 @@ static enum TALER_EXCHANGE_GlobalOptions eglobal_options; void TALER_EXCHANGE_setup (enum TALER_EXCHANGE_GlobalOptions go) { - eglobal_options = go; - if (TALER_EXCHANGE_GO_FORCE_HTTP1_1) - { - /* libtalerexchange also makes connections via libtalerauditor - to the auditor, so do enforce HTTP/1.1 there as well. */ - TALER_AUDITOR_setup (TALER_AUDITOR_GO_FORCE_HTTP1_1); - } -} - + unsigned int ago = TALER_AUDITOR_GO_NONE; -#include <curl/curl.h> -#include <stdbool.h> -#include <string.h> - - -/** - * Check if feature @a name is in the @a vi - * - * @param vi version data to test - * @param name feature to test for - * @true if feature is supported by curl - */ -static bool -has_feature (const curl_version_info_data *vi, - const char *name) -{ - if (! vi->feature_names) - return false; - - for (const char * const *p = vi->feature_names; *p; ++p) - if (0 == - strcmp (*p, - name)) - return true; - return false; -} - - -/** - * Check if @a s is non-NULL and starts with @a prefix - */ -static bool -starts_with (const char *s, - const char *prefix) -{ - return ( (NULL != s) && - (0 == strncmp (s, - prefix, - strlen (prefix)) ) ); -} - - -/** - * Check if using HTTP3 is likely OK with our version of libcurl. - * - * @return true if HTTP3 should be well-supported - */ -static bool -curl_http3_is_conservative_ok (void) -{ - const curl_version_info_data *vi = - curl_version_info (CURLVERSION_NOW); - - if (! has_feature (vi, - "HTTP3")) - return false; - - /* - * Require a QUIC backend we regard as production-ready. - * - * curl currently considers ngtcp2 non-experimental. - * quiche is still experimental. - */ - if (! starts_with (vi->quic_version, - "ngtcp2/")) - return false; - - /* - * Conservative policy: don't use HTTP/3 with GnuTLS. - * (too many bugs in recent releases still) - */ - if (starts_with (vi->ssl_version, - "GnuTLS/")) - return false; - - /* - * At this point the remaining ngtcp2 TLS configurations - * are the OpenSSL family and wolfSSL. - * - * We deliberately whitelist them rather than assuming - * every possible future TLS backend is safe. - */ - if (starts_with (vi->ssl_version, - "OpenSSL/")) - return true; - if (starts_with (vi->ssl_version, - "wolfSSL/")) - return true; - return false; + eglobal_options = go; + if (0 != (TALER_EXCHANGE_GO_FORCE_HTTP1_1 & go)) + ago |= TALER_AUDITOR_GO_FORCE_HTTP1_1; + if (0 != (TALER_EXCHANGE_GO_ENABLE_HTTP3 & go)) + ago |= TALER_AUDITOR_GO_ENABLE_HTTP3; + /* libtalerexchange also makes connections via libtalerauditor + to the auditor, so apply the equivalent options there as well. */ + TALER_AUDITOR_setup ((enum TALER_AUDITOR_GlobalOptions) ago); } @@ -163,31 +74,9 @@ TALER_EXCHANGE_curl_easy_get_ (const char *url) curl_easy_setopt (eh, CURLOPT_TCP_FASTOPEN, 1L)); - if (TALER_EXCHANGE_GO_FORCE_HTTP1_1 & eglobal_options) - { - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_HTTP_VERSION, - CURL_HTTP_VERSION_1_1)); - } - else - { - static enum GNUNET_GenericReturnValue http3 = GNUNET_SYSERR; - - if (GNUNET_SYSERR == http3) - http3 = curl_http3_is_conservative_ok () ? GNUNET_YES : GNUNET_NO; - if (GNUNET_YES == http3) - { - /* HTTP/3 support with GnuTLS remains spotty, prefer HTTP/2 */ - curl_easy_setopt (eh, - CURLOPT_HTTP_VERSION, - CURL_HTTP_VERSION_2TLS); - } - else - { - curl_easy_setopt (eh, CURLOPT_HTTP_VERSION, - CURL_HTTP_VERSION_3); - } - } + TALER_curl_set_http_version ( + eh, + (0 != (TALER_EXCHANGE_GO_ENABLE_HTTP3 & eglobal_options)) && + (0 == (TALER_EXCHANGE_GO_FORCE_HTTP1_1 & eglobal_options))); return eh; }