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:
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;
}