Author: brane
Date: Fri Jul 25 01:26:09 2025
New Revision: 1927455
Log:
In the Unbound resolver, look for both IPv4 and IPv6 addresses.
* src/resolve.c: Update top-level docstrings and todo lists.
(serf_address_resolve_async): The context's resolve_init_status is now
independent of the availability of threads.
(RR_CLASS_IN, RR_TYPE_A, RR_TYPE_AAAA): New constants for query types.
(resolve_result): Result for each query type.
(unbound_resolve_task): Add an array of resolve_results and an atomic
counter of pendinf queries.
(resolve_finalize): New; will aggregate results after all queries are done.
(resolve_callback): Log results for one query and optionally invoke
resolve_finalize if all queries have completed.
(resolve_address_async): Launch IPv4 and IPv6 queries in parallel.
Modified:
serf/trunk/src/resolve.c
Modified: serf/trunk/src/resolve.c
==============================================================================
--- serf/trunk/src/resolve.c Fri Jul 25 00:45:03 2025 (r1927454)
+++ serf/trunk/src/resolve.c Fri Jul 25 01:26:09 2025 (r1927455)
@@ -20,7 +20,7 @@
#include <apr.h>
-/* This will the headers needed for inet_ntop and related structs.
+/* 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>
@@ -59,8 +59,6 @@
* - Wake the poll/select in serf_context_run() when new resolve
* results are available.
*
- * - Add a way to cancel a resolve task?
- *
* - 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.
@@ -69,13 +67,6 @@
*
* TODO for Unbound:
* - Convert unbound results to apr_sockaddr_t.
- *
- * - Resolve both IPv4 and IPv6 addresses. This will require creating two
- * asynchronous resolve tasks and combining the results so that our
- * callback gets invoked only once both tasks are completed.
- *
- * - Figure out how to use libunbound's event-based API, because it uses
- * true asynchronous I/O instead of background threads.
*/
@@ -107,11 +98,9 @@ apr_status_t serf_address_resolve_async(
{
apr_pool_t *resolve_pool;
-#if APR_HAS_THREADS
if (ctx->resolve_init_status != APR_SUCCESS) {
return ctx->resolve_init_status;
}
-#endif
apr_pool_create(&resolve_pool, ctx->pool);
@@ -176,6 +165,13 @@ static apr_status_t run_async_resolver_l
#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)
@@ -283,50 +279,86 @@ static apr_status_t create_resolve_conte
/* 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* result)
+ struct ub_result* ub_result)
{
- resolve_task_t *const task = baton;
+ 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) {
+ if (err)
+ {
/* TODO: Error callback */
serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, task->ctx->config,
- "unbound resolve: error %s\n", ub_strerror(err));
+ "unbound resolve: [%s] error %s\n",
+ resolve_result->qtype, ub_strerror(err));
}
- if (!result->havedata) {
- if (result->nxdomain) {
+ else if (!ub_result->havedata)
+ {
+ if (ub_result->nxdomain) {
/* TODO: Error callback */
serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, task->ctx->config,
- "unbound resolve: NXDOMAIN [%d]\n", result->rcode);
+ "unbound resolve: [%s] NXDOMAIN [%d]\n",
+ resolve_result->qtype, ub_result->rcode);
if (status == APR_SUCCESS)
status = APR_ENOENT;
}
- if (result->bogus) {
+ if (ub_result->bogus) {
/* TODO: Error callback */
serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, task->ctx->config,
- "unbound resolve: BOGUS [%d]%s%s\n", result->rcode,
- result->why_bogus ? " " : "",
- result->why_bogus ? result->why_bogus : "");
+ "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 (result->was_ratelimited) {
+ if (ub_result->was_ratelimited) {
/* TODO: Error callback */
serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, task->ctx->config,
- "unbound resolve: SERVFAIL [%d]\n", result->rcode);
+ "unbound resolve: [%s] SERVFAIL [%d]\n",
+ resolve_result->qtype, ub_result->rcode);
if (status == APR_SUCCESS)
status = APR_EAGAIN;
}
@@ -336,45 +368,40 @@ static void resolve_callback(void* baton
if (status == APR_SUCCESS) {
/* TODO: Error callback */
serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, task->ctx->config,
- "unbound resolve: no data [%d]\n", result->rcode);
+ "unbound resolve: [%s] no data [%d]\n",
+ resolve_result->qtype, ub_result->rcode);
status = APR_ENOENT;
}
}
- if (status)
- {
- push_resolve_result(task->ctx, NULL, status,
- task->resolved, task->resolved_baton,
- task->resolve_pool);
- }
- else
+ 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))
{
- if (serf__log_enabled(LOGLVL_DEBUG, LOGCOMP_CONN,task->ctx->config))
- {
- int i;
-
- for (i = 0; result->data && result->data[i]; ++i) {
- char buf[INET6_ADDRSTRLEN];
- const socklen_t len = sizeof(buf);
- const char *address = "(AF-unknown)";
-
- if (result->len[i] == sizeof(struct in_addr))
- address = inet_ntop(AF_INET, result->data[i], buf, len);
- else if (result->len[i] == sizeof(struct in6_addr))
- address = inet_ntop(AF_INET6, result->data[i], buf, len);
- serf__log(LOGLVL_DEBUG, LOGCOMP_CONN,
- __FILE__, task->ctx->config,
- "unbound resolve: %s: %s\n", result->qname, address);
- }
+ 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);
}
-
- /* TODO: Convert ub_result to apr_sockaddr_t */
- push_resolve_result(task->ctx, NULL, APR_EAFNOSUPPORT,
- task->resolved, task->resolved_baton,
- task->resolve_pool);
}
- ub_resolve_free(result);
+ /* 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,
@@ -387,29 +414,75 @@ static apr_status_t resolve_address_asyn
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 err;
+ 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;
- /* FIXME: We should resolve both RRType 1 (A) and RRType 28 (AAAA). */
- if ((err = ub_resolve_async(rctx->ub_ctx, host_info.hostname,
- 1, /* rrtype: IPv4 host address (A) */
- 1, /* rrclass: IN(ternet) */
- task, resolve_callback, NULL)))
- {
- /* TODO: Error callback */
- status = err_to_status(err);
- serf__log(LOGLVL_ERROR, LOGCOMP_CONN, __FILE__, ctx->config,
- "unbound resolve start: %s\n", ub_strerror(err));
+ 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 (status == APR_SUCCESS) {
+#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;
+
+ 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;
}