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