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

Reply via email to