]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
lib: provide a getaddrinfo wrapper 17134/head
authorDaniel Stenberg <daniel@haxx.se>
Tue, 22 Apr 2025 12:51:49 +0000 (14:51 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 28 Apr 2025 21:48:02 +0000 (23:48 +0200)
This uses c-ares under the hood and supports the CURL_DNS_SERVER
environment variable - for debug builds only. The getaddrinfo()
replacement function is only used if CURL_DNS_SERVER is set to make a
debug build work more like a release version without the variable set.

'override-dns' is a new feature for the test suite when curl can be told
to use a dedicated DNS server, and test 2102 is the first to require
this.

Requires c-ares 1.26.0 or later.

Closes #17134

lib/Makefile.inc
lib/curl_addrinfo.c
lib/fake_addrinfo.c [new file with mode: 0644]
lib/fake_addrinfo.h [new file with mode: 0644]
src/curlinfo.c
tests/FILEFORMAT.md
tests/README.md
tests/data/test2102

index 414c9559457b7c09dd15367a76a1f2e0a074c0ee..87e6f8c43b24861f32b59d25726f2a302fae61e0 100644 (file)
@@ -123,10 +123,10 @@ LIB_CFILES =         \
   cf-socket.c        \
   cfilters.c         \
   conncache.c        \
-  cshutdn.c          \
   connect.c          \
   content_encoding.c \
   cookie.c           \
+  cshutdn.c          \
   curl_addrinfo.c    \
   curl_des.c         \
   curl_endian.c      \
@@ -154,6 +154,7 @@ LIB_CFILES =         \
   easygetopt.c       \
   easyoptions.c      \
   escape.c           \
+  fake_addrinfo.c    \
   file.c             \
   fileinfo.c         \
   fopen.c            \
@@ -304,6 +305,7 @@ LIB_HFILES =         \
   easyif.h           \
   easyoptions.h      \
   escape.h           \
+  fake_addrinfo.h    \
   file.h             \
   fileinfo.h         \
   fopen.h            \
index d7b98f468c8fd1e3b1d7fa4c289ebaf64a33efaf..a984508b6e3557bb3be6112372b804ec46884b67 100644 (file)
@@ -50,6 +50,7 @@
 #include <stddef.h>
 
 #include "curl_addrinfo.h"
+#include "fake_addrinfo.h"
 #include "inet_pton.h"
 #include "warnless.h"
 /* The last 3 #include files should be in this order */
@@ -508,6 +509,14 @@ curl_dbg_freeaddrinfo(struct addrinfo *freethis,
                source, line, (void *)freethis);
 #ifdef USE_LWIPSOCK
   lwip_freeaddrinfo(freethis);
+#elif defined(USE_FAKE_GETADDRINFO)
+  {
+    const char *env = getenv("CURL_DNS_SERVER");
+    if(env)
+      r_freeaddrinfo(freethis);
+    else
+      freeaddrinfo(freethis);
+  }
 #else
   freeaddrinfo(freethis);
 #endif
@@ -526,13 +535,20 @@ curl_dbg_freeaddrinfo(struct addrinfo *freethis,
 
 int
 curl_dbg_getaddrinfo(const char *hostname,
-                    const char *service,
-                    const struct addrinfo *hints,
-                    struct addrinfo **result,
-                    int line, const char *source)
+                     const char *service,
+                     const struct addrinfo *hints,
+                     struct addrinfo **result,
+                     int line, const char *source)
 {
 #ifdef USE_LWIPSOCK
   int res = lwip_getaddrinfo(hostname, service, hints, result);
+#elif defined(USE_FAKE_GETADDRINFO)
+  int res;
+  const char *env = getenv("CURL_DNS_SERVER");
+  if(env)
+    res = r_getaddrinfo(hostname, service, hints, result);
+  else
+    res = getaddrinfo(hostname, service, hints, result);
 #else
   int res = getaddrinfo(hostname, service, hints, result);
 #endif
diff --git a/lib/fake_addrinfo.c b/lib/fake_addrinfo.c
new file mode 100644 (file)
index 0000000..20d55ba
--- /dev/null
@@ -0,0 +1,210 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+#include "fake_addrinfo.h"
+
+#ifdef USE_FAKE_GETADDRINFO
+
+#include <string.h>
+#include <stdlib.h>
+#include <ares.h>
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+void r_freeaddrinfo(struct addrinfo *cahead)
+{
+  struct addrinfo *canext;
+  struct addrinfo *ca;
+
+  for(ca = cahead; ca; ca = canext) {
+    canext = ca->ai_next;
+    free(ca);
+  }
+}
+
+struct context {
+  struct ares_addrinfo *result;
+};
+
+static void async_addrinfo_cb(void *userp, int status, int timeouts,
+                              struct ares_addrinfo *result)
+{
+  struct context *ctx = (struct context *)userp;
+  (void)timeouts;
+  if(ARES_SUCCESS == status) {
+    ctx->result = result;
+  }
+}
+
+/* convert the c-ares version into the "native" version */
+static struct addrinfo *mk_getaddrinfo(const struct ares_addrinfo *aihead)
+{
+  const struct ares_addrinfo_node *ai;
+  struct addrinfo *ca;
+  struct addrinfo *cafirst = NULL;
+  struct addrinfo *calast = NULL;
+  const char *name = aihead->name;
+
+  /* traverse the addrinfo list */
+  for(ai = aihead->nodes; ai != NULL; ai = ai->ai_next) {
+    size_t ss_size;
+    size_t namelen = name ? strlen(name) + 1 : 0;
+    /* ignore elements with unsupported address family, */
+    /* settle family-specific sockaddr structure size.  */
+    if(ai->ai_family == AF_INET)
+      ss_size = sizeof(struct sockaddr_in);
+    else if(ai->ai_family == AF_INET6)
+      ss_size = sizeof(struct sockaddr_in6);
+    else
+      continue;
+
+    /* ignore elements without required address info */
+    if(!ai->ai_addr || !(ai->ai_addrlen > 0))
+      continue;
+
+    /* ignore elements with bogus address size */
+    if((size_t)ai->ai_addrlen < ss_size)
+      continue;
+
+    ca = malloc(sizeof(struct addrinfo) + ss_size + namelen);
+    if(!ca) {
+      r_freeaddrinfo(cafirst);
+      return NULL;
+    }
+
+    /* copy each structure member individually, member ordering, */
+    /* size, or padding might be different for each platform.    */
+
+    ca->ai_flags     = ai->ai_flags;
+    ca->ai_family    = ai->ai_family;
+    ca->ai_socktype  = ai->ai_socktype;
+    ca->ai_protocol  = ai->ai_protocol;
+    ca->ai_addrlen   = (curl_socklen_t)ss_size;
+    ca->ai_addr      = NULL;
+    ca->ai_canonname = NULL;
+    ca->ai_next      = NULL;
+
+    ca->ai_addr = (void *)((char *)ca + sizeof(struct addrinfo));
+    memcpy(ca->ai_addr, ai->ai_addr, ss_size);
+
+    if(namelen) {
+      ca->ai_canonname = (void *)((char *)ca->ai_addr + ss_size);
+      memcpy(ca->ai_canonname, name, namelen);
+
+      /* the name is only pointed to by the first entry in the "real"
+         addrinfo chain, so stop now */
+      name = NULL;
+    }
+
+    /* if the return list is empty, this becomes the first element */
+    if(!cafirst)
+      cafirst = ca;
+
+    /* add this element last in the return list */
+    if(calast)
+      calast->ai_next = ca;
+    calast = ca;
+  }
+
+  return cafirst;
+}
+
+/*
+  RETURN VALUE
+
+  getaddrinfo() returns 0 if it succeeds, or one of the following nonzero
+  error codes:
+
+  ...
+*/
+int r_getaddrinfo(const char *node,
+                  const char *service,
+                  const struct addrinfo *hints,
+                  struct addrinfo **res)
+{
+  int status;
+  struct context ctx;
+  struct ares_options options;
+  int optmask = 0;
+  struct ares_addrinfo_hints ahints;
+  ares_channel channel;
+  int rc = 0;
+
+  memset(&options, 0, sizeof(options));
+  optmask      |= ARES_OPT_EVENT_THREAD;
+  options.evsys = ARES_EVSYS_DEFAULT;
+
+  memset(&ahints, 0, sizeof(ahints));
+  memset(&ctx, 0, sizeof(ctx));
+
+  if(hints) {
+    ahints.ai_flags = hints->ai_flags;
+    ahints.ai_family = hints->ai_family;
+    ahints.ai_socktype = hints->ai_socktype;
+    ahints.ai_protocol = hints->ai_protocol;
+  }
+
+  status = ares_init_options(&channel, &options, optmask);
+  if(status)
+    return EAI_MEMORY; /* major problem */
+
+  else {
+    const char *env = getenv("CURL_DNS_SERVER");
+    if(env) {
+      rc = ares_set_servers_ports_csv(channel, env);
+      if(rc) {
+        fprintf(stderr, "ares_set_servers_ports_csv failed: %d", rc);
+        /* Cleanup */
+        ares_destroy(channel);
+        return EAI_MEMORY; /* we can't run */
+      }
+    }
+  }
+
+  ares_getaddrinfo(channel, node, service, &ahints,
+                   async_addrinfo_cb, &ctx);
+
+  /* Wait until no more requests are left to be processed */
+  ares_queue_wait_empty(channel, -1);
+
+  if(ctx.result) {
+    /* convert the c-ares version */
+    *res = mk_getaddrinfo(ctx.result);
+    /* free the old */
+    ares_freeaddrinfo(ctx.result);
+  }
+  else
+    rc = EAI_NONAME; /* got nothing */
+
+  /* Cleanup */
+  ares_destroy(channel);
+
+  return rc;
+}
+
+#endif /* USE_FAKE_GETADDRINFO */
diff --git a/lib/fake_addrinfo.h b/lib/fake_addrinfo.h
new file mode 100644 (file)
index 0000000..13b0d71
--- /dev/null
@@ -0,0 +1,54 @@
+#ifndef HEADER_FAKE_ADDRINFO_H
+#define HEADER_FAKE_ADDRINFO_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_ARES
+#include <ares.h>
+#endif
+
+#if defined(CURLDEBUG) && defined(USE_ARES) && defined(HAVE_GETADDRINFO) && \
+  (ARES_VERSION >= 0x011a00) /* >= 1.26. 0 */
+#define USE_FAKE_GETADDRINFO 1
+#endif
+
+#ifdef USE_FAKE_GETADDRINFO
+
+#ifdef HAVE_NETDB_H
+#  include <netdb.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#  include <arpa/inet.h>
+#endif
+
+void r_freeaddrinfo(struct addrinfo *res);
+int r_getaddrinfo(const char *node,
+                  const char *service,
+                  const struct addrinfo *hints,
+                  struct addrinfo **res);
+#endif /* USE_FAKE_GETADDRINFO */
+
+#endif /* HEADER_FAKE_ADDRINFO_H */
index 3c8aa62df216451930051d2d85896726f3389230..0274b0a2d51ecb7c9e1dcd71aab2c721de9a7d80 100644 (file)
@@ -35,6 +35,8 @@
 #include "multihandle.h" /* for ENABLE_WAKEUP */
 #include "tool_xattr.h" /* for USE_XATTR */
 #include "curl_sha512_256.h" /* for CURL_HAVE_SHA512_256 */
+#include "asyn.h" /* for CURLRES_ARES */
+#include "fake_addrinfo.h" /* for USE_FAKE_GETADDRINFO */
 #include <stdio.h>
 
 static const char *disabled[]={
@@ -225,6 +227,14 @@ static const char *disabled[]={
   "OFF"
 #else
   "ON"
+#endif
+  ,
+  "override-dns: "
+#if defined(CURLDEBUG) &&                                       \
+  (defined(CURLRES_ARES) || defined(USE_FAKE_GETADDRINFO))
+  "ON"
+#else
+  "OFF"
 #endif
   ,
   NULL
index a325b1a1e565995bbce46855ff421b8797c19721..436c4ce29b378097c04da7e196dd57def8d435f3 100644 (file)
@@ -476,6 +476,7 @@ Features testable here are:
 - `NTLM`
 - `NTLM_WB`
 - `OpenSSL`
+- `override-dns` - this build can use a "fake" DNS server
 - `parsedate`
 - `proxy`
 - `PSL`
index a0634ea1ea7a1ac4edbbee2b44d8a03bcebf9811..e6fce14f0a10becba95379f2fb1aa480a05ed064 100644 (file)
@@ -118,10 +118,42 @@ SPDX-License-Identifier: curl
   The HTTP server supports listening on a Unix domain socket, the default
   location is 'http.sock'.
 
-  For HTTP/2 and HTTP/3 testing an installed `nghttpx` is used. HTTP/3
-  tests check if nghttpx supports the protocol. To override the nghttpx
-  used, set the environment variable `NGHTTPX`. The default can also be
-  changed by specifying `--with-test-nghttpx=<path>` as argument to `configure`.
+  For HTTP/2 and HTTP/3 testing an installed `nghttpx` is used. HTTP/3 tests
+  check if nghttpx supports the protocol. To override the nghttpx used, set
+  the environment variable `NGHTTPX`. The default can also be changed by
+  specifying `--with-test-nghttpx=<path>` as argument to `configure`.
+
+### DNS server
+
+  There is a test DNS server to allow tests to resolve hostnames to verify
+  those code paths. This server is started like all the other servers within
+  the `<servers>` section.
+
+  To make a curl build actually use the test DNS server requires a debug
+  build. When such a test runs, the environment variable `CURL_DNS_SERVER` is
+  set to identify the IP address and port number of the DNS server to use.
+
+  - curl built to use c-ares for resolving automatically asks that server for
+    host information
+
+  - curl built to use `getaddrinfo()` for resolving *and* is built with c-ares
+    1.26.0 or later, gets a special work-around. In such builds, when the
+    environment variable is set, curl instead invokes a getaddrinfo wrapper
+    that emulates the function and acknowledges the DNS server environment
+    variable. This way, the getaddrinfo-using code paths in curl are verified,
+    and yet the custom responses from the test DNS server are used.
+
+  curl that is built to support a custom DNS server in a test gets the
+  `override-dns` feature set.
+
+  When curl ask for HTTPS-RR, c-ares is always used and in debug builds such
+  asks respects the dns server environment variable as well.
+
+  The test DNS server only has a few limited responses. When asked for
+
+  - type `A` response, it returns the address `127.0.0.1` three times
+  - type `AAAA` response, it returns the address `::1` three times
+  - other types, it returns a blank response without answers
 
 ### Shell startup scripts
 
index 7e77bbbe2c5a119e36a40cf7d2031794efb97c11..32b29658dc10166c24e2c9899d8c4d20bd399590 100644 (file)
@@ -33,8 +33,7 @@ http
 dns
 </server>
 <features>
-Debug
-c-ares
+override-dns
 </features>
 <name>
 HTTP GET with host name