Author: brane Date: Fri Jul 25 14:25:43 2025 New Revision: 1927469 Log: On the user-defined-authn branch: sync with trunk r1927468.
Modified: serf/branches/user-defined-authn/ (props changed) serf/branches/user-defined-authn/CMakeLists.txt serf/branches/user-defined-authn/README serf/branches/user-defined-authn/buckets/ssl_buckets.c serf/branches/user-defined-authn/build/FindAPR.cmake serf/branches/user-defined-authn/build/FindAPRUtil.cmake serf/branches/user-defined-authn/build/SerfGenClangd.cmake serf/branches/user-defined-authn/serf.h serf/branches/user-defined-authn/serf_private.h serf/branches/user-defined-authn/src/context.c serf/branches/user-defined-authn/src/outgoing.c serf/branches/user-defined-authn/src/resolve.c serf/branches/user-defined-authn/test/MockHTTPinC/CMakeLists.txt serf/branches/user-defined-authn/test/test_context.c serf/branches/user-defined-authn/test/test_ssl.c serf/branches/user-defined-authn/test/test_util.c Modified: serf/branches/user-defined-authn/CMakeLists.txt ============================================================================== --- serf/branches/user-defined-authn/CMakeLists.txt Fri Jul 25 13:56:10 2025 (r1927468) +++ serf/branches/user-defined-authn/CMakeLists.txt Fri Jul 25 14:25:43 2025 (r1927469) @@ -28,6 +28,7 @@ # ZLIB_ROOT - Path to zlib's install area # Brotli_ROOT - Path to Brotli's install area # GSSAPI_ROOT - Path to GSSAPI's install area +# Unbound_ROOT - Path to libunbound's install area # =================================================================== cmake_minimum_required(VERSION 3.12) @@ -263,6 +264,21 @@ else() endif() endif() + +# FIXME: VERYTEMPORARY, figure out FindUnbound.cmake first. +set(Unbound_FOUND FALSE) +if (Unbound_FOUND) + set(UNBOUND_INCLUDE_DIR "/opt/homebrew/opt/unbound/include") + set(UNBOUND_LIBRARIES "/opt/homebrew/opt/unbound/lib/libunbound.dylib") + # set(UNBOUND_LIBRARIES "/usr/lib/aarch64-linux-gnu/libunbound.so") + # set(UNBOUND_LIBRARIES "/usr/lib64/libunbound.so") + add_library(Unbound::Unbound UNKNOWN IMPORTED) + set_target_properties(Unbound::Unbound PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${UNBOUND_INCLUDE_DIR}") + set_target_properties(Unbound::Unbound PROPERTIES + IMPORTED_LOCATION "${UNBOUND_LIBRARIES}") +endif(Unbound_FOUND) + # Calculate the set of private and public targets set(SERF_PRIVATE_TARGETS OpenSSL::Crypto OpenSSL::SSL ZLIB::ZLIB) if(Brotli_FOUND) @@ -272,6 +288,11 @@ if(GSSAPI_FOUND) list(APPEND SERF_C_DEFINES "SERF_HAVE_GSSAPI") list(APPEND SERF_PRIVATE_TARGETS KRB5::GSSAPI) endif() +if(Unbound_FOUND) + list(APPEND SERF_C_DEFINES "SERF_HAVE_ASYNC_RESOLVER=1") + list(APPEND SERF_C_DEFINES "SERF_HAVE_UNBOUND") + list(APPEND SERF_PRIVATE_TARGETS Unbound::Unbound) +endif() if(APR_STATIC) if(SERF_WINDOWS) @@ -497,6 +518,8 @@ if(NOT SERF_WINDOWS) ${APRUTIL_LDFLAGS} ${APRUTIL_LIBRARIES} ${APRUTIL_EXTRALIBS} + ${BROTLI_COMMON_LIBRARY} + ${BROTLI_DECODE_LIBRARY} ${GSSAPI_LIBRARIES} ${ZLIB_LIBRARIES} ) @@ -532,6 +555,7 @@ set(_gen_dot_clangd OFF) set(_have_brotli OFF) set(_have_gssapi OFF) set(_have_sspi OFF) +set(_have_unbound OFF) if(NOT SKIP_SHARED) set(_build_shared ON) @@ -558,6 +582,9 @@ endif() if("SERF_HAVE_SSPI" IN_LIST SERF_C_DEFINES) set(_have_sspi ON) endif() +if ("SERF_HAVE_UNBOUND" IN_LIST SERF_C_DEFINES) + set(_have_unbound "EXPERIMENTAL") +endif() message(STATUS "Summary:") message(STATUS " Version ................... : ${SERF_VERSION}") @@ -574,6 +601,7 @@ message(STATUS " Options:") message(STATUS " Brotli .................. : ${_have_brotli}") message(STATUS " GSSAPI .................. : ${_have_gssapi}") message(STATUS " SSPI .................... : ${_have_sspi}") +message(STATUS " Unbound ................. : ${_have_unbound}") message(STATUS " Install:") message(STATUS " prefix: ................. : ${CMAKE_INSTALL_PREFIX}") message(STATUS " headers: ................ : ${SERF_INSTALL_HEADERS}") Modified: serf/branches/user-defined-authn/README ============================================================================== --- serf/branches/user-defined-authn/README Fri Jul 25 13:56:10 2025 (r1927468) +++ serf/branches/user-defined-authn/README Fri Jul 25 14:25:43 2025 (r1927469) @@ -31,7 +31,7 @@ create a symlink for 'scons' in your PAT Fetch the scons-local package: http://prdownloads.sourceforge.net/scons/scons-local-2.3.5.tar.gz - + Alternatively create a virtual Python environment and install SCons via pip (replace the path ~/scons_venv with your preferred path): @@ -105,6 +105,11 @@ $ scons --help $ scons check +Some tests take a long time to run, for example a 4GB request. These can +be enabled by passing ENABLE_SLOW_TESTS on the command line: + +$ scons check ENABLE_SLOW_TESTS=1 + 1.1.4 Installing Apache Serf @@ -236,6 +241,11 @@ or, on Windows using the Visual Studio g $ cmake --build out --config Release --target run_tests ) +Some tests take a long time to run, for example a 4GB request. These can +be enabled by passing ENABLE_SLOW_TESTS on the command line: + +$ cmake -DENABLE_SLOW_TESTS=ON + 1.2.5 Installing Apache Serf Modified: serf/branches/user-defined-authn/buckets/ssl_buckets.c ============================================================================== --- serf/branches/user-defined-authn/buckets/ssl_buckets.c Fri Jul 25 13:56:10 2025 (r1927468) +++ serf/branches/user-defined-authn/buckets/ssl_buckets.c Fri Jul 25 14:25:43 2025 (r1927469) @@ -1608,7 +1608,6 @@ static int ssl_pass_cb(UI *ui, UI_STRING static int ssl_need_client_cert(SSL *ssl, X509 **cert, EVP_PKEY **pkey) { serf_ssl_context_t *ctx = SSL_get_app_data(ssl); - unsigned long err = 0; #if defined(SERF_HAVE_OSSL_STORE_OPEN_EX) STACK_OF(X509) *leaves; STACK_OF(X509) *intermediates; @@ -1919,7 +1918,7 @@ static int ssl_need_client_cert(SSL *ssl return 1; } else { - err = ERR_get_error(); + unsigned long err = ERR_get_error(); ERR_clear_error(); if (ERR_GET_LIB(err) == ERR_LIB_PKCS12 && ERR_GET_REASON(err) == PKCS12_R_MAC_VERIFY_FAILURE) { Modified: serf/branches/user-defined-authn/build/FindAPR.cmake ============================================================================== --- serf/branches/user-defined-authn/build/FindAPR.cmake Fri Jul 25 13:56:10 2025 (r1927468) +++ serf/branches/user-defined-authn/build/FindAPR.cmake Fri Jul 25 14:25:43 2025 (r1927469) @@ -42,7 +42,7 @@ cmake_minimum_required(VERSION 3.12) # APR_FOUND - True if APR was found. # APR_VERSION - The version of APR found (x.y.z) # APR_CONTAINS_APRUTIL - True if the APR major version is 2 or greater. -# APR_INCLUDES - Where to find apr.h, etc. +# APR_INCLUDE_DIR - Where to find apr.h, etc. # APR_LIBRARIES - Linker switches to use with ld to link against APR # # :: @@ -211,7 +211,7 @@ if(NOT _apru_include_only_utilities) if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") - _apru_find_win_version("apr" APR_INCLUDES + _apru_find_win_version("apr" APR_INCLUDE_DIR APR_VERSION _apr_major _apr_minor) set(_apr_name "apr-${_apr_major}") @@ -229,12 +229,11 @@ if(NOT _apru_include_only_utilities) _apru_config(${APR_CONFIG_EXECUTABLE} ${_varname} "${_regexp}" "${ARGN}") endmacro(_apr_invoke) - _apr_invoke(APR_CFLAGS "(^| )-[gOW][^ ]*" --cppflags --cflags) - _apr_invoke(APR_INCLUDES "(^| )-I" --includes) - _apr_invoke(APR_LDFLAGS "" --ldflags) - _apr_invoke(APR_LIBRARIES "" --link-ld) - _apr_invoke(APR_EXTRALIBS "" --libs) - _apr_invoke(APR_VERSION "" --version) + _apr_invoke(APR_CFLAGS "(^| )-[gOW][^ ]*" --cppflags --cflags) + _apr_invoke(APR_INCLUDE_DIR "" --includedir) + _apr_invoke(APR_LIBRARIES "" --link-ld) + _apr_invoke(APR_EXTRALIBS "" --libs) + _apr_invoke(APR_VERSION "" --version) string(REGEX REPLACE "^([0-9]+)\\..*$" "\\1" _apr_major "${APR_VERSION}") endif() # NOT Windows @@ -248,7 +247,7 @@ if(NOT _apru_include_only_utilities) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( APR - REQUIRED_VARS APR_LIBRARIES APR_INCLUDES + REQUIRED_VARS APR_LIBRARIES APR_INCLUDE_DIR VERSION_VAR APR_VERSION) if(APR_FOUND) @@ -257,7 +256,7 @@ if(NOT _apru_include_only_utilities) if(APR_LIBRARIES AND APR_RUNTIME_LIBS) add_library(APR::APR SHARED IMPORTED) set_target_properties(APR::APR PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${APR_INCLUDES}" + INTERFACE_INCLUDE_DIRECTORIES "${APR_INCLUDE_DIR}" IMPORTED_LOCATION "${APR_RUNTIME_LIBS}" IMPORTED_IMPLIB "${APR_LIBRARIES}") endif() @@ -267,9 +266,10 @@ if(NOT _apru_include_only_utilities) add_library(APR::APR_static STATIC IMPORTED) set_target_properties(APR::APR_static PROPERTIES INTERFACE_COMPILE_DEFINITIONS "APR_DECLARE_STATIC" - INTERFACE_INCLUDE_DIRECTORIES "${APR_INCLUDES}" - IMPORTED_INTERFACE_LINK_LIBRARIES "${_apr_extra}" + INTERFACE_INCLUDE_DIRECTORIES "${APR_INCLUDE_DIR}" IMPORTED_LOCATION "${_apr_static}") + target_link_libraries(APR::APR_static + INTERFACE ${_apr_extra}) endif() else() # NOT Windows @@ -277,9 +277,10 @@ if(NOT _apru_include_only_utilities) _apru_location(_apr_library _apr_extra "${APR_LIBRARIES}") add_library(APR::APR UNKNOWN IMPORTED) set_target_properties(APR::APR PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${APR_INCLUDES}" - INTERFACE_LINK_LIBRARIES "${APR_LDFLAGS};${APR_EXTRALIBS};${_apr_extra}" + INTERFACE_INCLUDE_DIRECTORIES "${APR_INCLUDE_DIR}" IMPORTED_LOCATION "${_apr_library}") + target_link_libraries(APR::APR + INTERFACE ${APR_EXTRALIBS} ${_apr_extra}) endif() # NOT Windows endif(APR_FOUND) Modified: serf/branches/user-defined-authn/build/FindAPRUtil.cmake ============================================================================== --- serf/branches/user-defined-authn/build/FindAPRUtil.cmake Fri Jul 25 13:56:10 2025 (r1927468) +++ serf/branches/user-defined-authn/build/FindAPRUtil.cmake Fri Jul 25 14:25:43 2025 (r1927469) @@ -41,7 +41,7 @@ cmake_minimum_required(VERSION 3.12) # # APRUtil_FOUND - True if APR-Util was found # APRUTIL_VERSION - The version of APR-Util found (x.y.z) -# APRUTIL_INCLUDES - Where to find apr.h, etc. +# APRUTIL_INCLUDE_DIR - Where to find apr.h, etc. # APRUTIL_LIBRARIES - Linker switches to use with ld to link against APR # # :: @@ -83,7 +83,7 @@ else(APR_CONTAINS_APRUTIL) if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") - _apru_find_win_version("apu" APRUTIL_INCLUDES + _apru_find_win_version("apu" APRUTIL_INCLUDE_DIR APRUTIL_VERSION _apu_major _apu_minor) set(_apu_name "aprutil-${_apu_major}") @@ -122,18 +122,17 @@ else(APR_CONTAINS_APRUTIL) _apru_config(${APRUTIL_CONFIG_EXECUTABLE} ${_varname} "${_regexp}" "${ARGN}") endmacro(_apu_invoke) - _apu_invoke(APRUTIL_INCLUDES "(^| )-I" --includes) - _apu_invoke(APRUTIL_EXTRALIBS "" --libs) - _apu_invoke(APRUTIL_LIBRARIES "" --link-ld) - _apu_invoke(APRUTIL_LDFLAGS "" --ldflags) - _apu_invoke(APRUTIL_VERSION "" --version) + _apu_invoke(APRUTIL_INCLUDE_DIR "" --includedir) + _apu_invoke(APRUTIL_EXTRALIBS "" --libs) + _apu_invoke(APRUTIL_LIBRARIES "" --link-ld) + _apu_invoke(APRUTIL_VERSION "" --version) endif() # NOT Windows include(FindPackageHandleStandardArgs) find_package_handle_standard_args( APRUtil - REQUIRED_VARS APRUTIL_LIBRARIES APRUTIL_INCLUDES + REQUIRED_VARS APRUTIL_LIBRARIES APRUTIL_INCLUDE_DIR VERSION_VAR APRUTIL_VERSION) if(APRUtil_FOUND) @@ -142,7 +141,7 @@ else(APR_CONTAINS_APRUTIL) if(APRUTIL_LIBRARIES AND APRUTIL_RUNTIME_LIBS) add_library(APR::APRUtil SHARED IMPORTED) set_target_properties(APR::APRUtil PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${APRUTIL_INCLUDES}" + INTERFACE_INCLUDE_DIRECTORIES "${APRUTIL_INCLUDE_DIR}" IMPORTED_LOCATION "${APRUTIL_RUNTIME_LIBS}" IMPORTED_IMPLIB "${APRUTIL_LIBRARIES}") if(TARGET APR::APR) @@ -159,7 +158,7 @@ else(APR_CONTAINS_APRUTIL) add_library(APR::APRUtil_static STATIC IMPORTED) set_target_properties(APR::APRUtil_static PROPERTIES INTERFACE_COMPILE_DEFINITIONS "APU_DECLARE_STATIC" - INTERFACE_INCLUDE_DIRECTORIES "${APRUTIL_INCLUDES}" + INTERFACE_INCLUDE_DIRECTORIES "${APRUTIL_INCLUDE_DIR}" IMPORTED_LOCATION "${_apu_static}") target_link_libraries(APR::APRUtil_static INTERFACE ${_apu_extra}) @@ -170,10 +169,10 @@ else(APR_CONTAINS_APRUTIL) _apru_location(_apu_library _apu_extra "${APRUTIL_LIBRARIES}") add_library(APR::APRUtil UNKNOWN IMPORTED) set_target_properties(APR::APRUtil PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${APRUTIL_INCLUDES}" + INTERFACE_INCLUDE_DIRECTORIES "${APRUTIL_INCLUDE_DIR}" IMPORTED_LOCATION "${_apu_library}") target_link_libraries(APR::APRUtil - INTERFACE ${APRUTIL_LDFLAGS};${APRUTIL_EXTRALIBS};${_apu_extra}) + INTERFACE ${APRUTIL_EXTRALIBS} ${_apu_extra}) endif() # NOT Windows endif(APRUtil_FOUND) Modified: serf/branches/user-defined-authn/build/SerfGenClangd.cmake ============================================================================== --- serf/branches/user-defined-authn/build/SerfGenClangd.cmake Fri Jul 25 13:56:10 2025 (r1927468) +++ serf/branches/user-defined-authn/build/SerfGenClangd.cmake Fri Jul 25 14:25:43 2025 (r1927469) @@ -55,9 +55,9 @@ function(SerfGenClangd) write_flags("--language=c") write_includes("${CMAKE_SOURCE_DIR}") - list(APPEND includes ${APR_INCLUDES}) + list(APPEND includes ${APR_INCLUDE_DIR}) if(NOT APR_CONTAINS_APRUTIL) - list(APPEND includes ${APRUTIL_INCLUDES}) + list(APPEND includes ${APRUTIL_INCLUDE_DIR}) endif() list(APPEND includes ${OPENSSL_INCLUDE_DIR}) list(APPEND includes ${ZLIB_INCLUDE_DIR}) @@ -67,6 +67,9 @@ function(SerfGenClangd) if(GSSAPI_FOUND) list(APPEND includes ${GSSAPI_INCLUDES}) endif() + if(Unbound_FOUND) + list(APPEND includes ${UNBOUND_INCLUDE_DIR}) + endif() list(REMOVE_DUPLICATES includes) write_includes(${includes}) Modified: serf/branches/user-defined-authn/serf.h ============================================================================== --- serf/branches/user-defined-authn/serf.h Fri Jul 25 13:56:10 2025 (r1927468) +++ serf/branches/user-defined-authn/serf.h Fri Jul 25 14:25:43 2025 (r1927469) @@ -551,7 +551,7 @@ apr_status_t serf_connection_create3( * The @a ctx and @a resolved_baton arguments are the same that were passed * to serf_address_resolve_async(). * - * @a status contains the result of the address resolution. If it is notably + * @a status contains the result of the address resolution. If it is not * @c APR_SUCCESS, then @a host_address is invalid and should be ignored. * * The resolved @a host_address is ephemeral, allocated iun @a pool and lives @@ -597,6 +597,56 @@ apr_status_t serf_address_resolve_async( apr_pool_t *pool); +/** + * Notification callback when a connection hae been created. + * + * The @a ctx and @a created_baton arguments are the same that were passed + * to serf_connection_create_async(). + * + * @a status contains the result of the connection creation. If it is not + * @c APR_SUCCESS, then @a conn is invalid and should be ignored. + * + * The created @a conn is allocated in the pool that was passed to + * serf_connection_create_async(); this is @b not the same as @a pool. + * + * All temporary allocations should be made in @a pool. + * + * @since New in 1.4. + */ +/* FIXME: EXPERIMENTAL */ +typedef void (*serf_connection_created_t)( + serf_context_t *ctx, + void *created_baton, + serf_connection_t *conn, + apr_status_t status, + apr_pool_t *pool); + +/** + * Asyncchronously create a new connection associated with + * the @a ctx serf context. + * + * Like serf_connection_create3() with @a host_address set to @c NULL, + * except that address resolution is performed asynchronously, similarly to + * serf_address_resolve_async(). + * + * The @a created callback with @a created_baton is called when the connection + * is created but before it is opened. + * + * @since New in 1.4. + */ +/* FIXME: EXPERIMENTAL */ +apr_status_t serf_connection_create_async( + serf_context_t *ctx, + apr_uri_t host_info, + serf_connection_created_t created, + void *created_baton, + serf_connection_setup_t setup, + void *setup_baton, + serf_connection_closed_t closed, + void *closed_baton, + apr_pool_t *pool); + + typedef apr_status_t (*serf_accept_client_t)( serf_context_t *ctx, serf_listener_t *l, Modified: serf/branches/user-defined-authn/serf_private.h ============================================================================== --- serf/branches/user-defined-authn/serf_private.h Fri Jul 25 13:56:10 2025 (r1927468) +++ serf/branches/user-defined-authn/serf_private.h Fri Jul 25 14:25:43 2025 (r1927469) @@ -440,17 +440,6 @@ serf__config_store_remove_host(serf__con const char *hostname_port); -typedef struct serf__resolve_result_t serf__resolve_result_t; -struct serf__resolve_result_t -{ - apr_sockaddr_t *host_address; - apr_status_t status; - serf_address_resolved_t resolved; - void *resolved_baton; - apr_pool_t *result_pool; - serf__resolve_result_t *next; -}; - struct serf_context_t { /* the pool used for self and for other allocations */ apr_pool_t *pool; @@ -498,11 +487,10 @@ struct serf_context_t { serf_config_t *config; - /* The results of asynchronous address resolution. */ -#if APR_HAS_THREADS - apr_thread_mutex_t *resolve_guard; -#endif - serf__resolve_result_t *resolve_head; + /* Support for asynchronous address resolution. */ + void *volatile resolve_head; + apr_status_t resolve_init_status; + void *resolve_context; }; struct serf_listener_t { @@ -684,6 +672,10 @@ struct serf_connection_t { up buckets that may still reference buckets of this request */ void serf__connection_pre_cleanup(serf_connection_t *); +/* Called from serf_context_create_ex() to set up the context-specific + asynchronous address resolver context. */ +apr_status_t serf__create_resolve_context(serf_context_t *ctx); + /* Called from serf_context_prerun() before handling the connections. Processes the results of any asynchronously resolved addresses that were initiated for CTX. */ Modified: serf/branches/user-defined-authn/src/context.c ============================================================================== --- serf/branches/user-defined-authn/src/context.c Fri Jul 25 13:56:10 2025 (r1927468) +++ serf/branches/user-defined-authn/src/context.c Fri Jul 25 14:25:43 2025 (r1927469) @@ -136,6 +136,20 @@ void serf_config_authn_types(serf_contex } +#ifdef BROKEN_WSAPOLL +/* APR 1.4.x switched to using WSAPoll() on Win32, but it does not + * properly handle errors on a non-blocking sockets (such as + * connecting to a server where no listener is active). + * + * So, sadly, we must force using select() on Win32. + * + * http://mail-archives.apache.org/mod_mbox/apr-dev/201105.mbox/%3cbanlktin3rbceccbrvzua5b-14u-nwxr...@mail.gmail.com%3E + */ +#define PLATFORM_POLLSET_METHOD APR_POLLSET_SELECT +#else +#define PLATFORM_POLLSET_METHOD APR_POLLSET_DEFAULT +#endif + serf_context_t *serf_context_create_ex( void *user_baton, serf_socket_add_t addf, @@ -160,20 +174,8 @@ serf_context_t *serf_context_create_ex( ### Probably move creation of the pollset to later when we have ### the possibility of returning status to the caller. */ -#ifdef BROKEN_WSAPOLL - /* APR 1.4.x switched to using WSAPoll() on Win32, but it does not - * properly handle errors on a non-blocking sockets (such as - * connecting to a server where no listener is active). - * - * So, sadly, we must force using select() on Win32. - * - * http://mail-archives.apache.org/mod_mbox/apr-dev/201105.mbox/%3cbanlktin3rbceccbrvzua5b-14u-nwxr...@mail.gmail.com%3E - */ (void) apr_pollset_create_ex(&ps->pollset, MAX_CONN, pool, 0, - APR_POLLSET_SELECT); -#else - (void) apr_pollset_create(&ps->pollset, MAX_CONN, pool, 0); -#endif + PLATFORM_POLLSET_METHOD); ctx->pollset_baton = ps; ctx->pollset_add = pollset_add; ctx->pollset_rm = pollset_rm; @@ -193,13 +195,12 @@ serf_context_t *serf_context_create_ex( ctx->server_authn_info = apr_hash_make(pool); /* Initialize async resolver result queue. */ -#if APR_HAS_THREADS - /* FIXME: Ignore the status? */ - apr_thread_mutex_create(&ctx->resolve_guard, - APR_THREAD_MUTEX_DEFAULT, - ctx->pool); -#endif ctx->resolve_head = NULL; + ctx->resolve_init_status = APR_SUCCESS; + ctx->resolve_init_status = serf__create_resolve_context(ctx); + if (ctx->resolve_init_status != APR_SUCCESS) { + ctx->resolve_context = NULL; + } /* Assume returned status is APR_SUCCESS */ serf__config_store_init(ctx); Modified: serf/branches/user-defined-authn/src/outgoing.c ============================================================================== --- serf/branches/user-defined-authn/src/outgoing.c Fri Jul 25 13:56:10 2025 (r1927468) +++ serf/branches/user-defined-authn/src/outgoing.c Fri Jul 25 14:25:43 2025 (r1927469) @@ -1362,6 +1362,78 @@ apr_status_t serf_connection_create3( return status; } + +struct async_create_baton +{ + apr_uri_t host_info; + serf_connection_created_t created; + void *created_baton; + serf_connection_setup_t setup; + void *setup_baton; + serf_connection_closed_t closed; + void *closed_baton; + apr_pool_t *conn_pool; +}; + +static void async_conn_create(serf_context_t *ctx, + void *resolved_baton, + apr_sockaddr_t *host_address, + apr_status_t status, + apr_pool_t *scratch_pool) +{ + struct async_create_baton *const baton = resolved_baton; + serf_connection_t *conn = NULL; + + if (status == APR_SUCCESS) + { + status = apr_sockaddr_info_copy(&host_address, host_address, + baton->conn_pool); + if (status == APR_SUCCESS) { + status = serf_connection_create3(&conn, ctx, + baton->host_info, + host_address, + baton->setup, baton->setup_baton, + baton->closed, baton->closed_baton, + baton->conn_pool); + } + } + + baton->created(ctx, baton->created_baton, conn, status, scratch_pool); +} + +apr_status_t serf_connection_create_async( + serf_context_t *ctx, + apr_uri_t host_info, + serf_connection_created_t created, + void *created_baton, + serf_connection_setup_t setup, + void *setup_baton, + serf_connection_closed_t closed, + void *closed_baton, + apr_pool_t *pool) +{ + apr_pool_t *scratch_pool; + apr_status_t status; + + struct async_create_baton *const baton = apr_palloc(pool, sizeof(*baton)); + baton->host_info = host_info; + baton->created = created; + baton->created_baton = created_baton; + baton->setup = setup; + baton->setup_baton = setup_baton; + baton->closed = closed; + baton->closed_baton = closed_baton; + baton->conn_pool = pool; + + apr_pool_create(&scratch_pool, pool); + status = serf_address_resolve_async(ctx, host_info, + async_conn_create, baton, + scratch_pool); + apr_pool_destroy(scratch_pool); + return status; +} + + apr_status_t serf_connection_reset( serf_connection_t *conn) { Modified: serf/branches/user-defined-authn/src/resolve.c ============================================================================== --- serf/branches/user-defined-authn/src/resolve.c Fri Jul 25 13:56:10 2025 (r1927468) +++ serf/branches/user-defined-authn/src/resolve.c Fri Jul 25 14:25:43 2025 (r1927469) @@ -19,29 +19,49 @@ */ #include <apr.h> +#include <apr_version.h> + +/* Include the headers needed for inet_ntop and related structs. + On Windows, we'll always get <Winsock2.h> from <apr.h>. */ +#if APR_HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#if APR_HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + #include <apr_errno.h> -#include <apr_network_io.h> #include <apr_pools.h> +#include <apr_atomic.h> +#include <apr_network_io.h> #include <apr_thread_mutex.h> #include <apr_thread_pool.h> +/* Third-party resolver headers. */ +#if SERF_HAVE_ASYNC_RESOLVER +#if SERF_HAVE_UNBOUND +#include <unbound.h> +#else +/* Really shouldn't happen, but just in case it does, fall back + to the apr_thread_pool-based resolver. */ +#undef SERF_HAVE_ASYNC_RESOLVER +#endif /* SERF_HAVE_UNBOUND */ +#endif + #include "serf.h" #include "serf_private.h" -#define HAVE_ASYNC_RESOLVER (SERF_USE_ASYNC_RESOLVER || APR_HAS_THREADS) +#define HAVE_ASYNC_RESOLVER (SERF_HAVE_ASYNC_RESOLVER || APR_HAS_THREADS) /* * FIXME: EXPERIMENTAL * TODO: - * - Add cleanup function for in-flight resolve tasks if their owning - * context is destroyed. This function should be called from the - * context's pool cleanup handler. - * - Figure out what to do if the lock/unlock calls return an error. - * This should not be possible unless we messed up the implementation, - * but there should be a way for clients to back out of this situation. - * Failed lock/unlock could potentially leave the context in an - * inconsistent state. + * - Wake the poll/select in serf_context_run() when new resolve + * results are available. + * + * TODO for Unbound: + * - Convert unbound results to apr_sockaddr_t. */ @@ -51,7 +71,7 @@ onto the context's result queue. */ static void push_resolve_result(serf_context_t *ctx, apr_sockaddr_t *host_address, - apr_status_t status, + apr_status_t resolve_status, serf_address_resolved_t resolved, void *resolved_baton, apr_pool_t *resolve_pool); @@ -73,6 +93,10 @@ apr_status_t serf_address_resolve_async( { apr_pool_t *resolve_pool; + if (ctx->resolve_init_status != APR_SUCCESS) { + return ctx->resolve_init_status; + } + apr_pool_create(&resolve_pool, ctx->pool); /* See serf_connection_create3(): if there's a proxy configured in the @@ -105,10 +129,16 @@ apr_status_t serf_address_resolve_async( #endif /* !HAVE_ASYNC_RESOLVER */ -#if SERF_USE_ASYNC_RESOLVER +#if SERF_HAVE_ASYNC_RESOLVER /* TODO: Add implementation for one or more async resolver libraries. */ #if 0 +/* Called during context creation. Must initialize ctx->resolver_context. */ +static apr_status_t create_resolve_context(serf_context_t *ctx) +{ + ... +} + static apr_status_t resolve_address_async(serf_context_t *ctx, apr_uri_t host_info, serf_address_resolved_t resolved, @@ -122,13 +152,360 @@ static apr_status_t resolve_address_asyn /* Some asynchronous resolved libraries use event loop to harvest results. This function will be called from serf__process_async_resolve_results() so, in effect, from serf_context_prerun(). */ -static void run_async_resolver_loop(void) +static apr_status_t run_async_resolver_loop(serf_context_t *ctx) { ... } +#endif /* 0 */ + +#if SERF_HAVE_UNBOUND + +/* DNS classes and record types. + https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml */ +#define RR_CLASS_IN 1 /* Internet */ +#define RR_TYPE_A 1 /* IPv4 address */ +#define RR_TYPE_AAAA 28 /* IPv6 address */ + + +static apr_status_t err_to_status(enum ub_ctx_err err) +{ + switch (err) + { + case UB_NOERROR: + /* no error */ + return APR_SUCCESS; + + case UB_SOCKET: + /* socket operation. Set to -1, so that if an error from _fd() is + passed (-1) it gives a socket error. */ + if (errno) + return APR_FROM_OS_ERROR(errno); + return APR_ENOTSOCK; + + case UB_NOMEM: + /* alloc failure */ + return APR_ENOMEM; + + case UB_SYNTAX: + /* syntax error */ + return APR_EINIT; + + case UB_SERVFAIL: + /* DNS service failed */ + return APR_EAGAIN; + + case UB_FORKFAIL: + /* fork() failed */ + return APR_ENOMEM; + + case UB_AFTERFINAL: + /* cfg change after finalize() */ + return APR_EINIT; + + case UB_INITFAIL: + /* initialization failed (bad settings) */ + return APR_EINIT; + + case UB_PIPE: + /* error in pipe communication with async bg worker */ + return APR_EPIPE; + + case UB_READFILE: + /* error reading from file (resolv.conf) */ + if (errno) + return APR_FROM_OS_ERROR(errno); + return APR_ENOENT; + + case UB_NOID: + /* error async_id does not exist or result already been delivered */ + return APR_EINVAL; + + default: + return APR_EGENERAL; + } +} + + +struct resolve_context +{ + struct ub_ctx *ub_ctx; + volatile apr_uint32_t tasks; +}; + +static apr_status_t cleanup_resolve_context(void *baton) +{ + struct resolve_context *const rctx = baton; + ub_ctx_delete(rctx->ub_ctx); + return APR_SUCCESS; +} + +static apr_status_t create_resolve_context(serf_context_t *ctx) +{ + struct resolve_context *const rctx = apr_palloc(ctx->pool, sizeof(*rctx)); + int err; + + rctx->ub_ctx = ub_ctx_create(); + rctx->tasks = 0; + if (!rctx->ub_ctx) + return APR_ENOMEM; + + err = ub_ctx_resolvconf(rctx->ub_ctx, NULL); + if (!err) + err = ub_ctx_hosts(rctx->ub_ctx, NULL); + if (!err) + err = ub_ctx_async(rctx->ub_ctx, true); + + if (err) { + const apr_status_t status = err_to_status(err); + /* TODO: Error callback */ + serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, ctx->config, + "unbound ctx init: %s\n", ub_strerror(err)); + cleanup_resolve_context(rctx); + return status; + } + + ctx->resolve_context = rctx; + /* pre-cleanup because the live resolve tasks contain subpools of the + context pool and must be canceled before their pools go away. */ + apr_pool_pre_cleanup_register(ctx->pool, rctx, cleanup_resolve_context); + return APR_SUCCESS; +} + + +/* Task data for the Unbound resolver. */ +typedef struct unbound_resolve_task resolve_task_t; + +struct resolve_result +{ + int err; + apr_status_t status; + struct ub_result* ub_result; + resolve_task_t *task; + const char *qtype; +}; + +struct unbound_resolve_task +{ + serf_context_t *ctx; + apr_port_t host_port; + + /* There can be one or two pending results, depending on whether + we resolve for IPv6 as well as IPv4. */ + volatile apr_uint32_t pending_results; + struct resolve_result results[2]; + + serf_address_resolved_t resolved; + void *resolved_baton; + apr_pool_t *resolve_pool; +}; + +static void resolve_finalize(resolve_task_t *task) +{ + /* TODO: Convert ub_result to apr_sockaddr_t */ + if (task->results[0].ub_result) + ub_resolve_free(task->results[0].ub_result); + if (task->results[1].ub_result) + ub_resolve_free(task->results[1].ub_result); + + push_resolve_result(task->ctx, NULL, APR_EAFNOSUPPORT, + task->resolved, task->resolved_baton, + task->resolve_pool); +} + +static void resolve_callback(void* baton, int err, + struct ub_result* ub_result) +{ + struct resolve_result *const resolve_result = baton; + resolve_task_t *const task = resolve_result->task; + apr_status_t status = err_to_status(err); + + struct resolve_context *const rctx = task->ctx->resolve_context; + apr_atomic_dec32(&rctx->tasks); + + if (err) + { + /* TODO: Error callback */ + serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, task->ctx->config, + "unbound resolve: [%s] error %s\n", + resolve_result->qtype, ub_strerror(err)); + } + else if (!ub_result->havedata) + { + if (ub_result->nxdomain) { + /* TODO: Error callback */ + serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, task->ctx->config, + "unbound resolve: [%s] NXDOMAIN [%d]\n", + resolve_result->qtype, ub_result->rcode); + if (status == APR_SUCCESS) + status = APR_ENOENT; + } + if (ub_result->bogus) { + /* TODO: Error callback */ + serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, task->ctx->config, + "unbound resolve: [%s] BOGUS [%d]%s%s\n", + resolve_result->qtype, ub_result->rcode, + ub_result->why_bogus ? " " : "", + ub_result->why_bogus ? ub_result->why_bogus : ""); + if (status == APR_SUCCESS) + status = APR_EINVAL; + } + if (ub_result->was_ratelimited) { + /* TODO: Error callback */ + serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, task->ctx->config, + "unbound resolve: [%s] SERVFAIL [%d]\n", + resolve_result->qtype, ub_result->rcode); + if (status == APR_SUCCESS) + status = APR_EAGAIN; + } + + /* This shouldn't happen, one of the previous checks should + have caught an error. */ + if (status == APR_SUCCESS) { + /* TODO: Error callback */ + serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, task->ctx->config, + "unbound resolve: [%s] no data [%d]\n", + resolve_result->qtype, ub_result->rcode); + status = APR_ENOENT; + } + } + + resolve_result->err = err; + resolve_result->status = status; + resolve_result->ub_result = ub_result; + + if (status == APR_SUCCESS + && serf__log_enabled(LOGLVL_DEBUG, LOGCOMP_CONN, task->ctx->config)) + { + char buf[INET6_ADDRSTRLEN]; + const socklen_t len = sizeof(buf); + int i; + + for (i = 0; ub_result->data && ub_result->data[i]; ++i) { + const char *address = "(AF-unknown)"; + + if (ub_result->len[i] == sizeof(struct in_addr)) + address = inet_ntop(AF_INET, ub_result->data[i], buf, len); + else if (ub_result->len[i] == sizeof(struct in6_addr)) + address = inet_ntop(AF_INET6, ub_result->data[i], buf, len); + serf__log(LOGLVL_DEBUG, LOGCOMP_CONN, + __FILE__, task->ctx->config, + "unbound resolve: [%s] %s: %s\n", + resolve_result->qtype, ub_result->qname, address); + } + } + + /* The last pending task combines and publishes the results. */ + if (apr_atomic_dec32(&task->pending_results) == 1) + resolve_finalize(task); +} + +static apr_status_t resolve_address_async(serf_context_t *ctx, + apr_uri_t host_info, + serf_address_resolved_t resolved, + void *resolved_baton, + apr_pool_t *resolve_pool, + apr_pool_t *scratch_pool) +{ + struct resolve_context *const rctx = ctx->resolve_context; + resolve_task_t *const task = apr_palloc(resolve_pool, sizeof(*task)); + apr_status_t status = APR_SUCCESS; + int err4 = 0, err6 = 0; + + task->ctx = ctx; + task->host_port = host_info.port; + +#if APR_HAVE_IPV6 + task->pending_results = 2; +#else + task->pending_results = 1; #endif + task->results[0].err = task->results[1].err = 0; + task->results[0].status = task->results[1].status = APR_SUCCESS; + task->results[0].ub_result = task->results[1].ub_result = NULL; + task->results[0].task = task->results[1].task = task; + task->results[0].qtype = task->results[1].qtype = "??"; + + task->resolved = resolved; + task->resolved_baton = resolved_baton; + task->resolve_pool = resolve_pool; + + task->results[0].qtype = "v4"; + err4 = ub_resolve_async(rctx->ub_ctx, host_info.hostname, + RR_TYPE_A, RR_CLASS_IN, + &task->results[0], resolve_callback, NULL); + if (!err4) { + apr_atomic_inc32(&rctx->tasks); + } + +#if APR_HAVE_IPV6 + task->results[1].qtype = "v6"; + err6 = ub_resolve_async(rctx->ub_ctx, host_info.hostname, + RR_TYPE_AAAA, RR_CLASS_IN, + &task->results[1], resolve_callback, NULL); + if (!err6) { + apr_atomic_inc32(&rctx->tasks); + } +#endif /* APR_HAVE_IPV6 */ + + if (err4 || err6) + { + apr_uint32_t pending_results = -1; -#else /* !SERF_USE_ASYNC_RESOLVER */ + if (err4) { + pending_results = apr_atomic_dec32(&task->pending_results); + /* TODO: Error callback */ + serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, ctx->config, + "unbound resolve start: [v4] %s\n", ub_strerror(err4)); + status = err_to_status(err4); + } + +#if APR_HAVE_IPV6 + if (err6) { + pending_results = apr_atomic_dec32(&task->pending_results); + /* TODO: Error callback */ + serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, ctx->config, + "unbound resolve start: [v6] %s\n", ub_strerror(err6)); + /* We have only one status to report. */ + if (!err4) + status = err_to_status(err6); + } +#endif /* APR_HAVE_IPV6 */ + + /* If one of the tasks failed and the other has already completed, + we have to do the result processing here. Note that the Unbound + callbacks can be called synchronously from ub_resolve_async(). */ + if (pending_results == 1) + resolve_finalize(task); + } + + return status; +} + +static apr_status_t run_async_resolver_loop(serf_context_t *ctx) +{ + struct resolve_context *const rctx = ctx->resolve_context; + + /* No need to poll if there are no in-flight tasks. */ + if (apr_atomic_read32(&rctx->tasks)) + { + if (ub_poll(rctx->ub_ctx)) { + const int err = ub_process(rctx->ub_ctx); + if (err) { + const apr_status_t status = err_to_status(err); + /* TODO: Error callback */ + serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, ctx->config, + "unbound process: %s\n", ub_strerror(err)); + return status; + } + } + } + + return APR_SUCCESS; +} + +#endif /* SERF_HAVE_UNBOUND */ + +#else /* !SERF_HAVE_ASYNC_RESOLVER */ #if APR_HAS_THREADS /* This could be made configurable, but given that this is a fallback @@ -136,9 +513,10 @@ static void run_async_resolver_loop(void #define MAX_WORK_QUEUE_THREADS 50 static apr_pool_t *work_pool = NULL; static apr_thread_pool_t *work_queue = NULL; -static apr_status_t init_work_queue(void *baton) + +static apr_status_t do_init_work_queue(void *baton) { - serf_context_t *ctx = baton; + serf_context_t *const ctx = baton; apr_status_t status; apr_pool_create(&work_pool, NULL); @@ -152,10 +530,35 @@ static apr_status_t init_work_queue(void return status; } +static apr_status_t init_work_queue(serf_context_t *ctx) +{ + SERF__DECLARE_STATIC_INIT_ONCE_CONTEXT(init_ctx); + return serf__init_once(&init_ctx, do_init_work_queue, ctx); +} + + +static apr_status_t cleanup_resolve_tasks(void *baton) +{ + /* baton is serf_context_t */ + return apr_thread_pool_tasks_cancel(work_queue, baton); +} + +static apr_status_t create_resolve_context(serf_context_t *ctx) +{ + apr_status_t status; + + ctx->resolve_context = NULL; + status = init_work_queue(ctx); + if (status == APR_SUCCESS) + apr_pool_pre_cleanup_register(ctx->pool, ctx, cleanup_resolve_tasks); + + return status; +} + /* Task data for the thred pool resolver. */ -typedef struct resolve_task_t resolve_task_t; -struct resolve_task_t +typedef struct threadpool_resolve_task resolve_task_t; +struct threadpool_resolve_task { serf_context_t *ctx; apr_uri_t host_info; @@ -176,6 +579,28 @@ static void *APR_THREAD_FUNC resolve(apr APR_UNSPEC, task->host_info.port, 0, task->resolve_pool); + + if (status) { + host_address = NULL; + } + else if (serf__log_enabled(LOGLVL_DEBUG, LOGCOMP_CONN, task->ctx->config)) + { + apr_sockaddr_t *addr = host_address; + while (addr) + { + char buf[INET6_ADDRSTRLEN]; + const socklen_t len = sizeof(buf); + const char *address = "(AF-unknown)"; + + if (addr->family == APR_INET || addr->family == APR_INET6) + address = inet_ntop(addr->family, addr->ipaddr_ptr, buf, len); + serf__log(LOGLVL_DEBUG, LOGCOMP_CONN, + __FILE__, task->ctx->config, + "apr async resolve: %s: %s\n", addr->hostname, address); + addr = addr->next; + } + } + push_resolve_result(task->ctx, host_address, status, task->resolved, task->resolved_baton, task->resolve_pool); @@ -190,10 +615,7 @@ static apr_status_t resolve_address_asyn apr_pool_t *scratch_pool) { resolve_task_t *task; - apr_status_t status; - SERF__DECLARE_STATIC_INIT_ONCE_CONTEXT(init_ctx); - - status = serf__init_once(&init_ctx, init_work_queue, ctx); + apr_status_t status = init_work_queue(ctx); if (status) return status; @@ -210,103 +632,93 @@ static apr_status_t resolve_address_asyn /* This is a no-op since we're using a thread pool that does its own task queue management. */ -static void run_async_resolver_loop(void) {} +static apr_status_t run_async_resolver_loop(serf_context_t *ctx) +{ + return APR_SUCCESS; +} #endif /* !APR_HAS_THREADS */ -#endif /* !SERF_USE_ASYNC_RESOLVER */ +#endif /* !SERF_HAVE_ASYNC_RESOLVER */ /*******************************************************************/ /* The result queue implementation. */ #if HAVE_ASYNC_RESOLVER -static apr_status_t lock_results(serf_context_t *ctx) -{ -#if APR_HAS_THREADS - apr_status_t status = apr_thread_mutex_lock(ctx->resolve_guard); - if (status) { - /* TODO: ctx->error_callback... */ - char buffer[256]; - serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, ctx->config, - "Lock async resolve results: %s\n", - apr_strerror(status, buffer, sizeof(buffer))); - } - return status; -#else - return APR_SUCCESS; -#endif -} +#if APR_MAJOR_VERSION < 2 +/* NOTE: The atomic pointer prototypes in apr-1.x are just horribly + wrong. They're fixed in version 2.x and these macros deal + with the difference. */ +#define apr_atomic_casptr(mem, with, cmp) \ + (apr_atomic_casptr)((volatile void**)(mem), (with), (cmp)) +#define apr_atomic_xchgptr(mem, with) \ + (apr_atomic_xchgptr)((volatile void**)(mem), (with)) +#endif /* APR_MAJOR_VERSION < 2 */ + -static apr_status_t unlock_results(serf_context_t *ctx) +typedef struct resolve_result_t resolve_result_t; +struct resolve_result_t { -#if APR_HAS_THREADS - apr_status_t status = apr_thread_mutex_unlock(ctx->resolve_guard); - if (status) { - /* TODO: ctx->error_callback... */ - char buffer[256]; - serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, ctx->config, - "Unlock async resolve results: %s\n", - apr_strerror(status, buffer, sizeof(buffer))); - } - return status; -#else - return APR_SUCCESS; -#endif -} + apr_sockaddr_t *host_address; + apr_status_t status; + serf_address_resolved_t resolved; + void *resolved_baton; + apr_pool_t *result_pool; + resolve_result_t *next; +}; static void push_resolve_result(serf_context_t *ctx, apr_sockaddr_t *host_address, - apr_status_t status, + apr_status_t resolve_status, serf_address_resolved_t resolved, void *resolved_baton, apr_pool_t *resolve_pool) { - serf__resolve_result_t *result; - apr_status_t lock_status; + resolve_result_t *result; + void *head; result = apr_palloc(resolve_pool, sizeof(*result)); result->host_address = host_address; - result->status = status; + result->status = resolve_status; result->resolved = resolved; result->resolved_baton = resolved_baton; result->result_pool = resolve_pool; - lock_status = lock_results(ctx); - if (!lock_status) - { - result->next = ctx->resolve_head; - ctx->resolve_head = result; - lock_status = unlock_results(ctx); - } + /* Atomic push this result to the result stack. This might look like + a potential priority inversion, however, it's not likely that we'll + resolve several tens of thousands of results per second in hundreds + of separate threads. */ + head = apr_atomic_casptr(&ctx->resolve_head, NULL, NULL); + do { + result->next = head; + head = apr_atomic_casptr(&ctx->resolve_head, result, head); + } while(head != result->next); +} + - /* TODO: if (lock_status) ... then what? */ +/* Internal API */ +apr_status_t serf__create_resolve_context(serf_context_t *ctx) +{ + return create_resolve_context(ctx); } /* Internal API */ apr_status_t serf__process_async_resolve_results(serf_context_t *ctx) { - serf__resolve_result_t *result = NULL; - apr_status_t lock_status; - - run_async_resolver_loop(); + resolve_result_t *result; + apr_status_t status; - lock_status = lock_results(ctx); - if (lock_status) - return lock_status; - - result = ctx->resolve_head; - ctx->resolve_head = NULL; - lock_status = unlock_results(ctx); - - /* TODO: if (lock_status) ... then what? Shouldn't be possible. */ - /* if (lock_status) */ - /* return lock_status; */ + status = run_async_resolver_loop(ctx); + if (status) + return status; + /* Grab the whole stack, leaving it empty, and process the contents. */ + result = apr_atomic_xchgptr(&ctx->resolve_head, NULL); while (result) { - serf__resolve_result_t *const next = result->next; + resolve_result_t *const next = result->next; result->resolved(ctx, result->resolved_baton, result->host_address, result->status, result->result_pool); Modified: serf/branches/user-defined-authn/test/MockHTTPinC/CMakeLists.txt ============================================================================== --- serf/branches/user-defined-authn/test/MockHTTPinC/CMakeLists.txt Fri Jul 25 13:56:10 2025 (r1927468) +++ serf/branches/user-defined-authn/test/MockHTTPinC/CMakeLists.txt Fri Jul 25 14:25:43 2025 (r1927469) @@ -56,5 +56,7 @@ target_compile_options(mockhttpinc target_compile_definitions(mockhttpinc PUBLIC "MOCKHTTP_OPENSSL" PRIVATE ${MockHTTPinC_DEFINES}) -target_include_directories(mockhttpinc SYSTEM BEFORE - PRIVATE ${APR_INCLUDES} ${APRUTIL_INCLUDES}) +target_link_libraries(mockhttpinc + ${SERF_PRIVATE_TARGETS} + ${SERF_PUBLIC_TARGETS} + ${SERF_STANDARD_LIBRARIES}) Modified: serf/branches/user-defined-authn/test/test_context.c ============================================================================== --- serf/branches/user-defined-authn/test/test_context.c Fri Jul 25 13:56:10 2025 (r1927468) +++ serf/branches/user-defined-authn/test/test_context.c Fri Jul 25 14:25:43 2025 (r1927469) @@ -1046,8 +1046,8 @@ static void test_async_resolve(CuTest *t if (!APR_STATUS_IS_TIMEUP(status)) CuAssertIntEquals(tc, APR_SUCCESS, status); } - CuAssertPtrNotNull(tc, tb->connection); CuAssertIntEquals(tc, APR_SUCCESS, tb->user_status); + CuAssertPtrNotNull(tc, tb->connection); /* Send some requests on the connections */ for (i = 0 ; i < num_requests ; i++) { @@ -1058,6 +1058,44 @@ static void test_async_resolve(CuTest *t handler_ctx, tb->pool); } +static void async_resolve_cancel_callback(serf_context_t *ctx, + void *resolved_baton, + apr_sockaddr_t *host_address, + apr_status_t status, + apr_pool_t *scratch_pool) +{ + *(int*)resolved_baton = 1; +} + +static void test_async_resolve_cancel(CuTest *tc) +{ + test_baton_t *tb = tc->testBaton; + serf_context_t *ctx; + apr_pool_t *ctx_pool; + apr_status_t status; + apr_uri_t url; + int resolved = 0; + + status = apr_uri_parse(tb->pool, "http://localhost:8080/", &url); + CuAssertIntEquals(tc, APR_SUCCESS, status); + + apr_pool_create(&ctx_pool, tb->pool); + CuAssertPtrNotNull(tc, ctx_pool); + ctx = serf_context_create(ctx_pool); + CuAssertPtrNotNull(tc, ctx); + + status = serf_address_resolve_async(ctx, url, + async_resolve_cancel_callback, + &resolved, ctx_pool); + + /* This would create and actual race in the test case: */ + /* serf_context_prerun(ctx); */ + + apr_pool_destroy(ctx_pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertIntEquals(tc, 0, resolved); +} + /*****************************************************************************/ CuSuite *test_context(void) @@ -1086,5 +1124,6 @@ CuSuite *test_context(void) SUITE_ADD_TEST(suite, test_max_keepalive_requests); SUITE_ADD_TEST(suite, test_outgoing_request_err); SUITE_ADD_TEST(suite, test_async_resolve); + SUITE_ADD_TEST(suite, test_async_resolve_cancel); return suite; } Modified: serf/branches/user-defined-authn/test/test_ssl.c ============================================================================== --- serf/branches/user-defined-authn/test/test_ssl.c Fri Jul 25 13:56:10 2025 (r1927468) +++ serf/branches/user-defined-authn/test/test_ssl.c Fri Jul 25 14:25:43 2025 (r1927469) @@ -31,6 +31,7 @@ #include <openssl/ssl.h> #include <openssl/x509v3.h> +#include <openssl/opensslv.h> #ifndef OPENSSL_NO_OCSP /* requires openssl 0.9.7 or later */ #include <openssl/ocsp.h> #endif @@ -2783,10 +2784,16 @@ static void test_ssl_ocsp_verify_respons { #ifndef OPENSSL_NO_OCSP apr_status_t status = verify_ocsp_response(tc, 1, 0, 0, 0); - /* OCSP responses MUST be signed, we can't even create one - without a signature. This error doesn't come from response - validation but because OCSP_response_create() fails. */ +#if OPENSSL_VERSION_NUMBER >= (3 << 28) /* OpenSSL 3.0.0 */ + /* OCSP responses MUST be signed, and on newer versions of OpenSSL we + can't even create one without a signature. This error doesn't come + from response validation but because OCSP_response_create() fails. */ CuAssertIntEquals(tc, APR_EGENERAL, status); +#else + /* But both LibreSSL and OpenSSL up to 1.1.1 do allow creating such + a response, and so our validation will return a different error. */ + CuAssertIntEquals(tc, SERF_ERROR_SSL_OCSP_RESPONSE_INVALID, status); +#endif #endif /* OPENSSL_NO_OCSP */ } Modified: serf/branches/user-defined-authn/test/test_util.c ============================================================================== --- serf/branches/user-defined-authn/test/test_util.c Fri Jul 25 13:56:10 2025 (r1927468) +++ serf/branches/user-defined-authn/test/test_util.c Fri Jul 25 14:25:43 2025 (r1927469) @@ -142,44 +142,22 @@ apr_status_t use_new_connection(test_bat return status; } -struct async_reolved_baton +static void conn_created(serf_context_t *ctx, + void *resolved_baton, + serf_connection_t *conn, + apr_status_t status, + apr_pool_t *unused_scratch_pool) { - test_baton_t *tb; - apr_uri_t url; -}; - -static void address_resolved(serf_context_t *ctx, - void *resolved_baton, - apr_sockaddr_t *host_address, - apr_status_t status, - apr_pool_t *unused_scratch_pool) -{ - struct async_reolved_baton *baton = resolved_baton; - test_baton_t *tb = baton->tb; - serf_connection_t *conn; - apr_pool_t *conn_pool = tb->pool; + test_baton_t *tb = resolved_baton; if (tb->context != ctx) REPORT_TEST_SUITE_ERROR(); if (status == APR_SUCCESS) { - status = apr_sockaddr_info_copy(&host_address, host_address, conn_pool); - if (status == APR_SUCCESS) - status = serf_connection_create3(&conn, ctx, - baton->url, - host_address, - tb->conn_setup, - tb, - default_closed_connection, - tb, - conn_pool); - if (status == APR_SUCCESS) - { - tb->connection = conn; - apr_pool_cleanup_register(conn_pool, tb->connection, cleanup_conn, - apr_pool_cleanup_null); - } + tb->connection = conn; + apr_pool_cleanup_register(tb->pool, tb->connection, cleanup_conn, + apr_pool_cleanup_null); } tb->user_status = status; @@ -190,7 +168,6 @@ apr_status_t use_new_async_connection(te { apr_uri_t url; apr_status_t status; - struct async_reolved_baton *baton; if (tb->connection) cleanup_conn(tb->connection); @@ -200,12 +177,12 @@ apr_status_t use_new_async_connection(te if (status != APR_SUCCESS) return status; - baton = apr_palloc(pool, sizeof(*baton)); - baton->tb = tb; - baton->url = url; tb->user_status = APR_SUCCESS; - return serf_address_resolve_async(tb->context, url, - address_resolved, baton, pool); + return serf_connection_create_async(tb->context, url, + conn_created, tb, + tb->conn_setup, tb, + default_closed_connection, tb, + tb->pool); } static test_baton_t *initTestCtx(CuTest *tc, apr_pool_t *pool)