Author: brane
Date: Sun Jul 27 16:33:15 2025
New Revision: 1927493

Log:
In the Unbound resolver, detect when the hostname is an IPv4 or IPv6 address
and interpret it without actually calling the resolver function.

* CMakeLists.txt
  (SOURCES): Add src/inet_pton.c
* LICENSE, NOTICE: Update for the inet_pton code.
* serf_private.h
  (serf__inet_pton4, serf__inet_pton6): Declare new prototypes.
* src/inet_pton.c: New; derived from ISC, via APR.

* src/resolve.c
  (SERF__RESOLV_assert): New; conditional assertion macro.
  (MAX_ADDRLEN): New.
  (resolve_convert): Add a flag that tells if the result should be freed.
  (resolve_finalize): Update calls to resolve_convert.
  (resolve_address_async): Detect and interpret IP addresses in the host name.

* test/test_context.c
  (async_resolve_callback): Renamed from async_resolve_cancel_callback.
  (async_resolve,
   test_async_resolve_name,
   test_async_resolve_ipv4,
   test_async_resolve_ipv6,
   test_async_resolve_ipv64): New; tests for the async resolver.
  (test_async_resolve_cancel): Use async_resolve_callback.
  (test_context): Register the new tests.

Added:
   serf/trunk/src/inet_pton.c   (contents, props changed)
Modified:
   serf/trunk/CMakeLists.txt
   serf/trunk/LICENSE
   serf/trunk/NOTICE
   serf/trunk/serf_private.h
   serf/trunk/src/resolve.c
   serf/trunk/test/test_context.c

Modified: serf/trunk/CMakeLists.txt
==============================================================================
--- serf/trunk/CMakeLists.txt   Sun Jul 27 08:30:16 2025        (r1927492)
+++ serf/trunk/CMakeLists.txt   Sun Jul 27 16:33:15 2025        (r1927493)
@@ -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/trunk/LICENSE
==============================================================================
--- serf/trunk/LICENSE  Sun Jul 27 08:30:16 2025        (r1927492)
+++ serf/trunk/LICENSE  Sun Jul 27 16:33:15 2025        (r1927493)
@@ -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/trunk/NOTICE
==============================================================================
--- serf/trunk/NOTICE   Sun Jul 27 08:30:16 2025        (r1927492)
+++ serf/trunk/NOTICE   Sun Jul 27 16:33:15 2025        (r1927493)
@@ -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/trunk/serf_private.h
==============================================================================
--- serf/trunk/serf_private.h   Sun Jul 27 08:30:16 2025        (r1927492)
+++ serf/trunk/serf_private.h   Sun Jul 27 16:33:15 2025        (r1927493)
@@ -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

Added: serf/trunk/src/inet_pton.c
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ serf/trunk/src/inet_pton.c  Sun Jul 27 16:33:15 2025        (r1927493)
@@ -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/trunk/src/resolve.c
==============================================================================
--- serf/trunk/src/resolve.c    Sun Jul 27 08:30:16 2025        (r1927492)
+++ serf/trunk/src/resolve.c    Sun Jul 27 16:33:15 2025        (r1927493)
@@ -18,6 +18,13 @@
  * ====================================================================
  */
 
+#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>
 
@@ -47,6 +54,9 @@
 #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")
@@ -303,7 +313,7 @@ struct unbound_resolve_task
 };
 
 static apr_status_t resolve_convert(apr_sockaddr_t **host_address,
-                                    apr_int32_t family,
+                                    apr_int32_t family, bool free_result,
                                     const struct resolve_result *result)
 {
     const struct unbound_resolve_task *const task = result->task;
@@ -392,7 +402,7 @@ static apr_status_t resolve_convert(apr_
     }
 
   cleanup:
-    if (result->ub_result)
+    if (free_result && result->ub_result)
         ub_resolve_free(result->ub_result);
 
     return status;
@@ -403,9 +413,9 @@ static void resolve_finalize(resolve_tas
     apr_sockaddr_t *host_address = NULL;
     apr_status_t status, status6;
 
-    status = resolve_convert(&host_address, APR_INET, &task->results[0]);
+    status = resolve_convert(&host_address, APR_INET, true, &task->results[0]);
 #if APR_HAVE_IPV6
-    status6 = resolve_convert(&host_address, APR_INET6, &task->results[1]);
+    status6 = resolve_convert(&host_address, APR_INET6, true, 
&task->results[1]);
 #else
     status6 = APR_SUCCESS;
 #endif
@@ -498,11 +508,94 @@ 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;

Modified: serf/trunk/test/test_context.c
==============================================================================
--- serf/trunk/test/test_context.c      Sun Jul 27 08:30:16 2025        
(r1927492)
+++ serf/trunk/test/test_context.c      Sun Jul 27 16:33:15 2025        
(r1927493)
@@ -1106,15 +1106,72 @@ static void test_async_proxy_connection(
                                                 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)
+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_ip(tc, "http://[::1]:8080/";);
+#endif
+}
+
+static void test_async_resolve_ipv64(CuTest *tc)
+{
+#if APR_HAVE_IPV6
+    async_resolve_ip(tc, "http://[::ffff:127.0.0.1]:8080/";);
+#endif
+}
+
 static void test_async_resolve_cancel(CuTest *tc)
 {
     test_baton_t *tb = tc->testBaton;
@@ -1133,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: */
@@ -1173,6 +1230,10 @@ CuSuite *test_context(void)
     SUITE_ADD_TEST(suite, test_outgoing_request_err);
     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;
 }

Reply via email to