Author: brane Date: Sun Jul 27 16:57:54 2025 New Revision: 1927496 Log: On the user-defined-authn branch: sync with trunk r1927495.
Added: serf/branches/user-defined-authn/src/inet_pton.c - copied unchanged from r1927495, serf/trunk/src/inet_pton.c Modified: serf/branches/user-defined-authn/ (props changed) serf/branches/user-defined-authn/CMakeLists.txt serf/branches/user-defined-authn/LICENSE serf/branches/user-defined-authn/NOTICE serf/branches/user-defined-authn/serf.h serf/branches/user-defined-authn/serf_private.h serf/branches/user-defined-authn/src/outgoing.c serf/branches/user-defined-authn/src/resolve.c serf/branches/user-defined-authn/test/test_context.c Modified: serf/branches/user-defined-authn/CMakeLists.txt ============================================================================== --- serf/branches/user-defined-authn/CMakeLists.txt Sun Jul 27 16:55:29 2025 (r1927495) +++ serf/branches/user-defined-authn/CMakeLists.txt Sun Jul 27 16:57:54 2025 (r1927496) @@ -133,6 +133,7 @@ list(APPEND SOURCES "src/context.c" "src/deprecated.c" "src/incoming.c" + "src/inet_pton.c" "src/init_once.c" "src/logging.c" "src/outgoing.c" Modified: serf/branches/user-defined-authn/LICENSE ============================================================================== --- serf/branches/user-defined-authn/LICENSE Sun Jul 27 16:55:29 2025 (r1927495) +++ serf/branches/user-defined-authn/LICENSE Sun Jul 27 16:57:54 2025 (r1927496) @@ -206,8 +206,7 @@ ADDITIONAL LICENSES: For parts of the configuration code in build/scons_extras.py: - -I. MIT License + MIT License Copyright The SCons Foundation Copyright (c) 2003 Stichting NLnet Labs @@ -231,3 +230,22 @@ I. MIT License LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +From src/inet_pton.c: + +/* Copyright (c) 1996 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ Modified: serf/branches/user-defined-authn/NOTICE ============================================================================== --- serf/branches/user-defined-authn/NOTICE Sun Jul 27 16:55:29 2025 (r1927495) +++ serf/branches/user-defined-authn/NOTICE Sun Jul 27 16:57:54 2025 (r1927496) @@ -1,5 +1,5 @@ Apache Serf -Copyright 2016 The Apache Software Foundation +Copyright 2025 The Apache Software Foundation This product includes software developed by many people, and distributed under Contributor License Agreements to The Apache Software Foundation @@ -8,3 +8,6 @@ history. This product includes software developed by the SCons Foundation under the MIT license, see LICENSE. + +This product contanis code derived from the Internet Software Consortium. +See LICENSE; Copyright (c) 1996 by Internet Software Consortium. Modified: serf/branches/user-defined-authn/serf.h ============================================================================== --- serf/branches/user-defined-authn/serf.h Sun Jul 27 16:55:29 2025 (r1927495) +++ serf/branches/user-defined-authn/serf.h Sun Jul 27 16:57:54 2025 (r1927496) @@ -630,7 +630,9 @@ typedef void (*serf_connection_created_t * serf_address_resolve_async(). * * The @a created callback with @a created_baton is called when the connection - * is created but before it is opened. + * is created but before it is opened. Note that depending on the configuration + * of @a ctx,the connection may be created and this callback be invoked + * synchronously during the scope of this function call. * * @since New in 1.4. */ Modified: serf/branches/user-defined-authn/serf_private.h ============================================================================== --- serf/branches/user-defined-authn/serf_private.h Sun Jul 27 16:55:29 2025 (r1927495) +++ serf/branches/user-defined-authn/serf_private.h Sun Jul 27 16:57:54 2025 (r1927496) @@ -682,6 +682,12 @@ apr_status_t serf__create_resolve_contex apr_status_t serf__process_async_resolve_results(serf_context_t *ctx); +/*** IP address parsing ***/ + +int serf__inet_pton4(const char *src, unsigned char *dst); +int serf__inet_pton6(const char *src, unsigned char *dst); + + /*** Internal bucket functions ***/ /* Copies all data contained in vecs to *data, optionally telling how much was Copied: serf/branches/user-defined-authn/src/inet_pton.c (from r1927495, serf/trunk/src/inet_pton.c) ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ serf/branches/user-defined-authn/src/inet_pton.c Sun Jul 27 16:57:54 2025 (r1927496, copy of r1927495, serf/trunk/src/inet_pton.c) @@ -0,0 +1,194 @@ +/* Copyright (c) 1996 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +#include <apr.h> +#include <apr_network_io.h> + +#if APR_HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#if APR_HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#if APR_HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#if APR_HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#include <string.h> +#if APR_HAVE_ERRNO_H +#include <errno.h> +#endif + +#include "serf.h" +#include "serf_private.h" + +#ifndef IN6ADDRSZ +#define IN6ADDRSZ 16 +#endif + +#ifndef INADDRSZ +#define INADDRSZ 4 +#endif + +#ifndef INT16SZ +#define INT16SZ sizeof(apr_int16_t) +#endif + +/* int + * inet_pton4(src, dst) + * like inet_aton() but without all the hexadecimal and shorthand. + * return: + * 1 if `src' is a valid dotted quad, else 0. + * notice: + * does not touch `dst' unless it's returning 1. + * author: + * Paul Vixie, 1996. + */ +int serf__inet_pton4(const char *src, unsigned char *dst) +{ + static const char digits[] = "0123456789"; + int saw_digit, octets, ch; + unsigned char tmp[INADDRSZ], *tp; + + saw_digit = 0; + octets = 0; + *(tp = tmp) = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr(digits, ch)) != NULL) { + unsigned int new = *tp * 10 + (unsigned int)(pch - digits); + + if (new > 255) + return (0); + *tp = new; + if (!saw_digit) { + if (++octets > 4) + return (0); + saw_digit = 1; + } + } else if (ch == '.' && saw_digit) { + if (octets == 4) + return (0); + *++tp = 0; + saw_digit = 0; + } else + return (0); + } + if (octets < 4) + return (0); + + memcpy(dst, tmp, INADDRSZ); + return (1); +} + + +/* int + * inet_pton6(src, dst) + * convert presentation level address to network order binary form. + * return: + * 1 if `src' is a valid [RFC1884 2.2] address, else 0. + * notice: + * (1) does not touch `dst' unless it's returning 1. + * (2) :: in a full address is silently ignored. + * credit: + * inspired by Mark Andrews. + * author: + * Paul Vixie, 1996. + */ +int serf__inet_pton6(const char *src, unsigned char *dst) +{ + static const char xdigits_l[] = "0123456789abcdef", + xdigits_u[] = "0123456789ABCDEF"; + unsigned char tmp[IN6ADDRSZ], *tp, *endp, *colonp; + const char *xdigits, *curtok; + int ch, saw_xdigit; + apr_uint32_t val; + + memset((tp = tmp), '\0', IN6ADDRSZ); + endp = tp + IN6ADDRSZ; + colonp = NULL; + /* Leading :: requires some special handling. */ + if (*src == ':') + if (*++src != ':') + return (0); + curtok = src; + saw_xdigit = 0; + val = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) + pch = strchr((xdigits = xdigits_u), ch); + if (pch != NULL) { + val <<= 4; + val |= (pch - xdigits); + if (val > 0xffff) + return (0); + saw_xdigit = 1; + continue; + } + if (ch == ':') { + curtok = src; + if (!saw_xdigit) { + if (colonp) + return (0); + colonp = tp; + continue; + } + if (tp + INT16SZ > endp) + return (0); + *tp++ = (unsigned char)(val >> 8) & 0xff; + *tp++ = (unsigned char)val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + if (ch == '.' && ((tp + INADDRSZ) <= endp) + && serf__inet_pton4(curtok, tp) > 0) { + tp += INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return (0); + } + if (saw_xdigit) { + if (tp + INT16SZ > endp) + return (0); + *tp++ = (unsigned char)(val >> 8) & 0xff; + *tp++ = (unsigned char)val & 0xff; + } + if (colonp != NULL) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const apr_ssize_t n = tp - colonp; + apr_ssize_t i; + + for (i = 1; i <= n; i++) { + endp[-i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if (tp != endp) + return (0); + memcpy(dst, tmp, IN6ADDRSZ); + return (1); +} Modified: serf/branches/user-defined-authn/src/outgoing.c ============================================================================== --- serf/branches/user-defined-authn/src/outgoing.c Sun Jul 27 16:55:29 2025 (r1927495) +++ serf/branches/user-defined-authn/src/outgoing.c Sun Jul 27 16:57:54 2025 (r1927496) @@ -1386,8 +1386,10 @@ static void async_conn_create(serf_conte if (status == APR_SUCCESS) { - status = apr_sockaddr_info_copy(&host_address, host_address, - baton->conn_pool); + if (host_address) { + 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, @@ -1415,20 +1417,38 @@ apr_status_t serf_connection_create_asyn 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); + if (ctx->proxy_address) + { + /* If we're using a proxy, we do *not* resolve the host + (see serf_connection_create3(), above), so just create + the connection immediately. */ + serf_connection_t *conn; + status = serf_connection_create3(&conn, ctx, + host_info, NULL, + setup, setup_baton, + closed, closed_baton, + pool); + if (status == APR_SUCCESS) + created(ctx, created_baton, conn, status, scratch_pool); + } + else + { + 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; + + status = serf_address_resolve_async(ctx, host_info, + async_conn_create, baton, + scratch_pool); + } + apr_pool_destroy(scratch_pool); return status; } Modified: serf/branches/user-defined-authn/src/resolve.c ============================================================================== --- serf/branches/user-defined-authn/src/resolve.c Sun Jul 27 16:55:29 2025 (r1927495) +++ serf/branches/user-defined-authn/src/resolve.c Sun Jul 27 16:57:54 2025 (r1927496) @@ -18,21 +18,24 @@ * ==================================================================== */ +#if defined(_DEBUG) +#include <assert.h> +#define SERF__RESOLV_assert(x) assert(x) +#else +#define SERF__RESOLV_assert(x) ((void)0) +#endif + #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 +#define APR_WANT_BYTEFUNC +#define APR_WANT_MEMFUNC +#include <apr_want.h> #include <apr_errno.h> #include <apr_pools.h> #include <apr_atomic.h> +#include <apr_strings.h> #include <apr_network_io.h> #include <apr_thread_mutex.h> #include <apr_thread_pool.h> @@ -51,6 +54,16 @@ #include "serf.h" #include "serf_private.h" +/* Sufficient buffer size for an IPv4 and IPv6 binary address. */ +#define MAX_ADDRLEN 16 + +/* Stringified address lengths. */ +#ifndef INET_ADDRSTRLEN +#define INET_ADDRSTRLEN sizeof("255.255.255.255") +#endif +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") +#endif #define HAVE_ASYNC_RESOLVER (SERF_HAVE_ASYNC_RESOLVER || APR_HAS_THREADS) @@ -59,9 +72,6 @@ * TODO: * - Wake the poll/select in serf_context_run() when new resolve * results are available. - * - * TODO for Unbound: - * - Convert unbound results to apr_sockaddr_t. */ @@ -160,6 +170,9 @@ static apr_status_t run_async_resolver_l #if SERF_HAVE_UNBOUND +/*******************************************************************/ +/* Async resolver that uses libunbound. */ + /* DNS classes and record types. https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml */ #define RR_CLASS_IN 1 /* Internet */ @@ -277,7 +290,6 @@ typedef struct unbound_resolve_task reso struct resolve_result { - int err; apr_status_t status; struct ub_result* ub_result; resolve_task_t *task; @@ -287,6 +299,7 @@ struct resolve_result struct unbound_resolve_task { serf_context_t *ctx; + char *host_port_str; apr_port_t host_port; /* There can be one or two pending results, depending on whether @@ -299,15 +312,126 @@ struct unbound_resolve_task apr_pool_t *resolve_pool; }; +static apr_status_t resolve_convert(apr_sockaddr_t **host_address, + apr_int32_t family, bool free_result, + const struct resolve_result *result) +{ + const struct unbound_resolve_task *const task = result->task; + const struct ub_result *const ub_result = result->ub_result; + apr_pool_t *const resolve_pool = task->resolve_pool; + apr_status_t status = result->status; + + if (status == APR_SUCCESS) + { + char *hostname = apr_pstrdup(resolve_pool, (ub_result->canonname + ? ub_result->canonname + : ub_result->qname)); + apr_socklen_t salen; + int ipaddr_len; + int addr_str_len; + int i; + + if (family == APR_INET) { + salen = sizeof(struct sockaddr_in); + ipaddr_len = sizeof(struct in_addr); + addr_str_len = INET_ADDRSTRLEN; + } +#if APR_HAVE_IPV6 + else { + salen = sizeof(struct sockaddr_in6); + ipaddr_len = sizeof(struct in6_addr); + addr_str_len = INET6_ADDRSTRLEN; + } +#endif + + /* Check that all result sizes are correct. */ + for (i = 0; ub_result->data[i]; ++i) + { + if (ub_result->len[i] != ipaddr_len) { + /* TODO: Error callback */ + serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, + task->ctx->config, + "unbound resolve: [%s] invalid address: " + " length %d, expected %d\n", + result->qtype, ub_result->len[i], ipaddr_len); + status = APR_EINVAL; + goto cleanup; + } + } + + for (i = 0; ub_result->data[i]; ++i) + { + apr_sockaddr_t *addr = apr_palloc(resolve_pool, sizeof(*addr)); + addr->pool = resolve_pool; + addr->hostname = hostname; + addr->servname = task->host_port_str; + addr->port = task->host_port; + addr->family = family; + addr->salen = salen; + addr->ipaddr_len = ipaddr_len; + addr->addr_str_len = addr_str_len; + addr->next = *host_address; + + memset(&addr->sa, 0, sizeof(addr->sa)); + if (family == APR_INET) { + addr->ipaddr_ptr = &addr->sa.sin.sin_addr.s_addr; + addr->sa.sin.sin_family = APR_INET; + addr->sa.sin.sin_port = htons(addr->port); + } +#if APR_HAVE_IPV6 + else { + addr->ipaddr_ptr = &addr->sa.sin6.sin6_addr.s6_addr; + addr->sa.sin6.sin6_family = APR_INET6; + addr->sa.sin6.sin6_port = htons(addr->port); + } +#endif + memcpy(addr->ipaddr_ptr, ub_result->data[i], ipaddr_len); + *host_address = addr; + + if (serf__log_enabled(LOGLVL_DEBUG, LOGCOMP_CONN, + task->ctx->config)) { + char buf[INET6_ADDRSTRLEN]; + if (!apr_sockaddr_ip_getbuf(buf, sizeof(buf), addr)) { + serf__log(LOGLVL_DEBUG, LOGCOMP_CONN, + __FILE__, task->ctx->config, + "unbound resolve: [%s] %s: %s\n", + result->qtype, addr->hostname, buf); + } + } + } + } + + cleanup: + if (free_result && result->ub_result) + ub_resolve_free(result->ub_result); + + return status; +} + 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); + apr_sockaddr_t *host_address = NULL; + apr_status_t status, status6; + + status = resolve_convert(&host_address, APR_INET, true, &task->results[0]); +#if APR_HAVE_IPV6 + status6 = resolve_convert(&host_address, APR_INET6, true, &task->results[1]); +#else + status6 = APR_SUCCESS; +#endif + + if (!host_address) { + if (status == APR_SUCCESS) + status = status6; + if (status == APR_SUCCESS) + status = APR_ENOENT; + } + else { + /* If we got a result, ee don't care if one of the resolves failed. */ + status = APR_SUCCESS; + } - push_resolve_result(task->ctx, NULL, APR_EAFNOSUPPORT, + push_resolve_result(task->ctx, host_address, status, task->resolved, task->resolved_baton, task->resolve_pool); } @@ -369,33 +493,11 @@ static void resolve_callback(void* baton } } - 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) + if (apr_atomic_dec32(&task->pending_results) == 0) resolve_finalize(task); } @@ -406,12 +508,96 @@ static apr_status_t resolve_address_asyn apr_pool_t *resolve_pool, apr_pool_t *scratch_pool) { + unsigned char addr[MAX_ADDRLEN]; struct resolve_context *const rctx = ctx->resolve_context; - resolve_task_t *const task = apr_palloc(resolve_pool, sizeof(*task)); + resolve_task_t *task; apr_status_t status = APR_SUCCESS; int err4 = 0, err6 = 0; + apr_int32_t family; + int pton, ipaddr_len, rr_type; + + /* If the hostname is an IP address, "resolve" it immediately. */ + pton = serf__inet_pton4(host_info.hostname, addr); + if (pton > 0) { + family = APR_INET; + rr_type = RR_TYPE_A; + ipaddr_len = sizeof(struct in_addr); + SERF__RESOLV_assert(ipaddr_len <= MAX_ADDRLEN); + } + else { + pton = serf__inet_pton6(host_info.hostname, addr); + if (pton > 0) { +#if APR_HAVE_IPV6 + family = APR_INET6; + rr_type = RR_TYPE_AAAA; + ipaddr_len = sizeof(struct in6_addr); + SERF__RESOLV_assert(ipaddr_len <= MAX_ADDRLEN); +#else + return APR_EAFNOSUPPORT; +#endif + } + } + + if (pton > 0) + { + static const struct ub_result ub_template = { + NULL, /* .qname */ + 0, /* .qtype */ + RR_CLASS_IN, /* .qclass */ + NULL, /* .data */ + NULL, /* .len */ + NULL, /* .canonname */ + 0, /* .rcode */ + NULL, /* .answer_packet */ + 0, /* .answer_len */ + 1, /* .havedata */ + 0, /* .nxdomain */ + 0, /* .secure */ + 0, /* .bogus */ + NULL, /* .why_bogus */ + 0, /* .was_ratelimited */ + INT_MAX /* .ttl */ + }; + + struct ub_result ub_result = ub_template; + apr_sockaddr_t *host_address = NULL; + char *data[2] = { NULL, NULL }; + int len[2] = { 0, 0 }; + struct resolve_result *result; + resolve_task_t local_task; + + memset(&local_task, 0, sizeof(local_task)); + data[0] = (char*) addr; + len[0] = ipaddr_len; + ub_result.qname = host_info.hostname; + ub_result.qtype = rr_type; + ub_result.data = data; + ub_result.len = len; + + task = &local_task; + task->ctx = ctx; + task->host_port_str = host_info.port_str; + task->host_port = host_info.port; + task->resolve_pool = resolve_pool; + result = &task->results[0]; + result->status = APR_SUCCESS; + result->ub_result = &ub_result; + result->task = task; + result->qtype = family == APR_INET ? "v4" : "v6"; + status = resolve_convert(&host_address, family, false, result); + if (status == APR_SUCCESS) { + push_resolve_result(ctx, host_address, status, + resolved, resolved_baton, + resolve_pool); + } + return status; + } + + /* Create the async resolve tasks. */ + task = apr_palloc(resolve_pool, sizeof(*task)); task->ctx = ctx; + task->host_port_str = apr_pstrdup(resolve_pool, host_info.port_str); task->host_port = host_info.port; #if APR_HAVE_IPV6 @@ -419,7 +605,6 @@ static apr_status_t resolve_address_asyn #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; @@ -474,7 +659,7 @@ static apr_status_t resolve_address_asyn /* 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) + if (pending_results == 0) resolve_finalize(task); } @@ -508,6 +693,9 @@ static apr_status_t run_async_resolver_l #else /* !SERF_HAVE_ASYNC_RESOLVER */ #if APR_HAS_THREADS +/*******************************************************************/ +/* Default async resolver that uses APR thread pools. */ + /* This could be made configurable, but given that this is a fallback implementation, it really shouldn't be necessary. */ #define MAX_WORK_QUEUE_THREADS 50 @@ -589,14 +777,11 @@ static void *APR_THREAD_FUNC resolve(apr 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); + if (!apr_sockaddr_ip_getbuf(buf, sizeof(buf), addr)) { + serf__log(LOGLVL_DEBUG, LOGCOMP_CONN, + __FILE__, task->ctx->config, + "apr async resolve: %s: %s\n", addr->hostname, buf); + } addr = addr->next; } } Modified: serf/branches/user-defined-authn/test/test_context.c ============================================================================== --- serf/branches/user-defined-authn/test/test_context.c Sun Jul 27 16:55:29 2025 (r1927495) +++ serf/branches/user-defined-authn/test/test_context.c Sun Jul 27 16:57:54 2025 (r1927496) @@ -1018,7 +1018,7 @@ static void test_outgoing_request_err(Cu } /* Test that asynchronus name resolution happens. */ -static void test_async_resolve(CuTest *tc) +static void test_async_connection(CuTest *tc) { test_baton_t *tb = tc->testBaton; apr_status_t status; @@ -1058,15 +1058,120 @@ 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) +/* Test that async connection to proxy short-circuits. */ +static void test_async_proxy_connection(CuTest *tc) +{ + test_baton_t *tb = tc->testBaton; + handler_baton_t handler_ctx[2]; + const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]); + apr_status_t status; + int i; + + /* Set up a test context with a proxy */ + setup_test_mock_server(tb); + status = setup_test_mock_proxy(tb); + CuAssertIntEquals(tc, APR_SUCCESS, status); + status = setup_test_client_context_with_proxy(tb, NULL, tb->pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); + + Given(tb->mh) + RequestsReceivedByProxy + GETRequest( + URLEqualTo(apr_psprintf(tb->pool, "http://%s", tb->serv_host)), + HeaderEqualTo("Host", tb->serv_host), + ChunkedBodyEqualTo("1")) + Respond(WithCode(200), WithChunkedBody("")) + GETRequest( + URLEqualTo(apr_psprintf(tb->pool, "http://%s", tb->serv_host)), + HeaderEqualTo("Host", tb->serv_host), + ChunkedBodyEqualTo("2")) + Respond(WithCode(200), WithChunkedBody("")) + EndGiven + + status = use_new_async_connection(tb, tb->pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); + + /* Because we use a proxy, the connection creation is actually synchronous, + because we don't resolve the host address -- that's the proxy's job. + The connection-created callback was called immediately.*/ + CuAssertIntEquals(tc, APR_SUCCESS, tb->user_status); + CuAssertPtrNotNull(tc, tb->connection); + + /* Send some requests on the connections */ + for (i = 0 ; i < num_requests ; i++) { + create_new_request(tb, &handler_ctx[i], "GET", "/", i+1); + } + + run_client_and_mock_servers_loops_expect_ok(tc, tb, num_requests, + handler_ctx, tb->pool); +} + +static void async_resolve_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 async_resolve(CuTest *tc, const char *url) +{ + test_baton_t *tb = tc->testBaton; + serf_context_t *ctx; + apr_pool_t *ctx_pool; + apr_status_t status; + apr_uri_t host_info; + int resolved = 0; + + status = apr_uri_parse(tb->pool, url, &host_info); + 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, host_info, + async_resolve_callback, + &resolved, ctx_pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); + + while (!resolved && status == APR_SUCCESS) { + status = serf_context_run(ctx, 70, ctx_pool); + if (APR_STATUS_IS_TIMEUP(status)) + status = APR_SUCCESS; + } + + apr_pool_destroy(ctx_pool); + CuAssertIntEquals(tc, APR_SUCCESS, status); + CuAssertIntEquals(tc, 1, resolved); +} + +static void test_async_resolve_name(CuTest *tc) +{ + async_resolve(tc, "http://localhost:8080/"); +} + +static void test_async_resolve_ipv4(CuTest *tc) +{ + async_resolve(tc, "http://127.0.0.1:8080/"); +} + +static void test_async_resolve_ipv6(CuTest *tc) +{ +#if APR_HAVE_IPV6 + async_resolve(tc, "http://[::1]:8080/"); +#endif +} + +static void test_async_resolve_ipv64(CuTest *tc) +{ +#if APR_HAVE_IPV6 + async_resolve(tc, "http://[::ffff:127.0.0.1]:8080/"); +#endif +} + static void test_async_resolve_cancel(CuTest *tc) { test_baton_t *tb = tc->testBaton; @@ -1085,7 +1190,7 @@ static void test_async_resolve_cancel(Cu CuAssertPtrNotNull(tc, ctx); status = serf_address_resolve_async(ctx, url, - async_resolve_cancel_callback, + async_resolve_callback, &resolved, ctx_pool); /* This would create and actual race in the test case: */ @@ -1123,7 +1228,12 @@ CuSuite *test_context(void) SUITE_ADD_TEST(suite, test_connection_large_request); 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_connection); + SUITE_ADD_TEST(suite, test_async_proxy_connection); + SUITE_ADD_TEST(suite, test_async_resolve_name); + SUITE_ADD_TEST(suite, test_async_resolve_ipv4); + SUITE_ADD_TEST(suite, test_async_resolve_ipv6); + SUITE_ADD_TEST(suite, test_async_resolve_ipv64); SUITE_ADD_TEST(suite, test_async_resolve_cancel); return suite; }