]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
tests/server/dnsd: basic DNS server for test suite
authorDaniel Stenberg <daniel@haxx.se>
Wed, 9 Apr 2025 07:47:43 +0000 (09:47 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Thu, 17 Apr 2025 07:13:24 +0000 (09:13 +0200)
Currently the DNS server only responds to A and AAAA queries. It always
responds with a fixed response: the localhost address. Three times.

It should work fine over either IPv4 or IPv6, but I don't think it
matters much for curl testing.

The idea is to allow curl tests to use "normal" DNS hostnames (using the
normal name resolving code paths) and still use the local test servers.

This setup currently only works if curl is built with c-ares because
redirecting DNS requests to our test server when using getaddrinfo() is
not easy.

This should be extended to respond to HTTPS queries as well to allow
more testing there, as c-ares is always used for that.

Test 2102 is the first test using this.

Closes #17015

docs/libcurl/libcurl-env-dbg.md
lib/asyn-ares.c
tests/data/Makefile.am
tests/data/test2102 [new file with mode: 0644]
tests/server/Makefile.inc
tests/server/dnsd.c [new file with mode: 0644]
tests/server/tftpd.c
tests/serverhelp.pm
tests/servers.pm

index 60c887bfd5a90aa3fc980b7497c8ea4903bec008..aeb79ff395bf74369c07dd65eb5e5ebbb8c71279 100644 (file)
@@ -77,6 +77,12 @@ HTTP/2.
 
 Fake the size returned by CURLINFO_HEADER_SIZE and CURLINFO_REQUEST_SIZE.
 
+## `CURL_DNS_SERVER`
+
+When built with c-ares for name resolving, setting this environment variable
+to `[IP:port]` makes libcurl use that DNS server instead of the system
+default. This is used by the curl test suite.
+
 ## `CURL_GETHOSTNAME`
 
 Fake the local machine's unqualified hostname for NTLM and SMTP.
index babae6fb27a77e8da0c7d943a9a6568c06dddf99..80efeeb37c0d78f5e3c85cf301cb85c504fb818a 100644 (file)
@@ -179,6 +179,17 @@ static CURLcode async_ares_init(struct Curl_easy *data)
     else
       return CURLE_FAILED_INIT;
   }
+#if defined(CURLDEBUG) && defined(HAVE_CARES_PORTS_CSV)
+  else {
+    const char *env = getenv("CURL_DNS_SERVER");
+    if(env) {
+      int rc = ares_set_servers_ports_csv(ares->channel, env);
+      if(rc)
+        infof(data, "ares_set_servers_ports_csv failed: %d", rc);
+    }
+  }
+#endif
+
   return CURLE_OK;
   /* make sure that all other returns from this function should destroy the
      ares channel before returning error! */
index 1ed9ccfab995a8e4ebb10e05b6edcd85ecf88146..793501a38fda5260cbca187e66c9733930b52d6c 100644 (file)
@@ -254,7 +254,7 @@ test2064 test2065 test2066 test2067 test2068 test2069 test2070 test2071 \
 test2072 test2073 test2074 test2075 test2076 test2077 test2078 test2079 \
 test2080 test2081 test2082 test2083 test2084 test2085 test2086 test2087 \
 test2088 \
-test2100 test2101 \
+test2100 test2101 test2102 \
 \
 test2200 test2201 test2202 test2203 test2204 test2205 \
 \
diff --git a/tests/data/test2102 b/tests/data/test2102
new file mode 100644 (file)
index 0000000..7e77bbb
--- /dev/null
@@ -0,0 +1,61 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data crlf="yes">
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
+ETag: "21025-dc7-39462498"
+Accept-Ranges: bytes
+Content-Length: 6
+Connection: close
+Content-Type: text/html
+Funny-head: yesyes
+
+-foo-
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+http
+dns
+</server>
+<features>
+Debug
+c-ares
+</features>
+<name>
+HTTP GET with host name
+</name>
+<setenv>
+CURL_DNS_SERVER=127.0.0.1:%DNSPORT
+</setenv>
+<command>
+http://examplehost.example:%HTTPPORT/%TESTNUMBER
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol crlf="yes">
+GET /%TESTNUMBER HTTP/1.1
+Host: examplehost.example:%HTTPPORT
+User-Agent: curl/%VERSION
+Accept: */*
+
+</protocol>
+</verify>
+</testcase>
index 5633b04676aaba211ab4dca7a15847c242a29a15..3d36c137f02ef4f59c68fb6e7a7a83c657592828 100644 (file)
@@ -22,7 +22,7 @@
 #
 ###########################################################################
 
-SERVERPROGS = resolve rtspd sockfilt sws tftpd socksd disabled mqttd
+SERVERPROGS = resolve rtspd sockfilt sws tftpd socksd disabled mqttd dnsd
 
 MEMDEBUG = \
   ../../lib/memdebug.c \
@@ -118,4 +118,9 @@ tftpd_SOURCES = $(MEMDEBUG) $(CURLX_SRCS) $(CURLX_HDRS) $(UTIL) \
 tftpd_LDADD = @CURL_NETWORK_AND_TIME_LIBS@
 tftpd_CFLAGS = $(AM_CFLAGS)
 
+dnsd_SOURCES = $(MEMDEBUG) $(CURLX_SRCS) $(CURLX_HDRS) $(UTIL) \
+  dnsd.c
+dnsd_LDADD = @CURL_NETWORK_AND_TIME_LIBS@
+dnsd_CFLAGS = $(AM_CFLAGS)
+
 disabled_SOURCES = disabled.c
diff --git a/tests/server/dnsd.c b/tests/server/dnsd.c
new file mode 100644 (file)
index 0000000..514fb72
--- /dev/null
@@ -0,0 +1,682 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  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 "server_setup.h"
+
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifndef UNDER_CE
+#include <signal.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef HAVE_SYS_FILIO_H
+/* FIONREAD on Solaris 7 */
+#include <sys/filio.h>
+#endif
+
+#include <setjmp.h>
+
+#include <ctype.h>
+
+#include "curlx.h" /* from the private lib dir */
+#include "getpart.h"
+#include "util.h"
+#include "server_sockaddr.h"
+
+/* include memdebug.h last */
+#include "memdebug.h"
+
+static int dnsd_wrotepidfile = 0;
+static int dnsd_wroteportfile = 0;
+
+static unsigned short get16bit(const unsigned char **pkt,
+                               size_t *size)
+{
+  const unsigned char *p = *pkt;
+  (*pkt) += 2;
+  *size -= 2;
+  return (unsigned short)((p[0] << 8) | p[1]);
+}
+
+static char name[256];
+
+static int qname(const unsigned char **pkt, size_t *size)
+{
+  unsigned char length;
+  int o = 0;
+  const unsigned char *p = *pkt;
+  do {
+    int i;
+    length = *p++;
+    if(*size < length)
+      /* too long */
+      return 1;
+    if(length && o)
+      name[o++] = '.';
+    for(i = 0; i < length; i++) {
+      name[o++] = *p++;
+    }
+  } while(length);
+  *size -= (p - *pkt);
+  *pkt = p;
+  name[o++] = '\0';
+  return 0;
+}
+
+#define QTYPE_A 1
+#define QTYPE_AAAA 28
+
+/*
+ * Handle initial connection protocol.
+ *
+ * Return query (qname + type + class), type and id.
+ */
+static int store_incoming(const unsigned char *data, size_t size,
+                          unsigned char *qbuf, size_t *qlen,
+                          unsigned short *qtype, unsigned short *idp)
+{
+  FILE *server;
+  char dumpfile[256];
+#if 0
+  size_t i;
+#endif
+  unsigned short qd;
+  const unsigned char *qptr;
+  size_t qsize;
+
+  *qlen = 0;
+  *qtype = 0;
+  *idp = 0;
+
+  msnprintf(dumpfile, sizeof(dumpfile), "%s/dnsd.input", logdir);
+
+  /* Open request dump file. */
+  server = fopen(dumpfile, "ab");
+  if(!server) {
+    int error = errno;
+    logmsg("fopen() failed with error (%d) %s", error, strerror(error));
+    logmsg("Error opening file '%s'", dumpfile);
+    return -1;
+  }
+
+  /*
+                                    1  1  1  1  1  1
+      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |                      ID                       |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |                    QDCOUNT                    |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |                    ANCOUNT                    |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |                    NSCOUNT                    |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+    |                    ARCOUNT                    |
+    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+   */
+  *idp = get16bit(&data, &size);
+  data += 2; /* skip the next 16 bits */
+  size -= 2;
+#if 0
+  fprintf(server, "QR: %x\n", (id & 0x8000) > 15);
+  fprintf(server, "OPCODE: %x\n", (id & 0x7800) >> 11);
+  fprintf(server, "TC: %x\n", (id & 0x200) >> 9);
+  fprintf(server, "RD: %x\n", (id & 0x100) >> 8);
+  fprintf(server, "Z: %x\n", (id & 0x70) >> 4);
+  fprintf(server, "RCODE: %x\n", (id & 0x0f));
+#endif
+  qd = get16bit(&data, &size);
+  fprintf(server, "QDCOUNT: %04x\n", qd);
+
+  data += 6; /* skip ANCOUNT, NSCOUNT and ARCOUNT */
+  size -= 6;
+
+  /* store pointer and size at the QD point */
+  qsize = size;
+  qptr = data;
+
+  if(!qname(&data, &size)) {
+    fprintf(server, "QNAME: %s\n", name);
+    qd = get16bit(&data, &size);
+    fprintf(server, "QTYPE: %04x\n", qd);
+    *qtype = qd;
+    logmsg("Question for '%s' type %x", name, qd);
+
+    qd = get16bit(&data, &size);
+    fprintf(server, "QCLASS: %04x\n", qd);
+
+    *qlen = qsize - size; /* total size of the query */
+    memcpy(qbuf, qptr, *qlen);
+  }
+#if 0
+  for(i = 0; i < size; i++) {
+    fprintf(server, "%02d", (unsigned int)data[i]);
+  }
+  fprintf(server, "\n");
+#endif
+
+  fclose(server);
+
+  return 0;
+}
+
+#if 0
+static int send_response(curl_socket_t sock,
+                         struct sockaddr *addr,
+                         curl_socklen_t addrlen,
+                         unsigned short id)
+{
+  ssize_t rc;
+  unsigned char bytes[] = {
+    0x80, 0xea, /* ID, overwrite */
+    0x81, 0x80,
+    /*
+    Flags: 0x8180 Standard query response, No error
+        1... .... .... .... = Response: Message is a response
+        .000 0... .... .... = Opcode: Standard query (0)
+        .... .0.. .... .... = Authoritative: Server is not an authority for
+                              domain
+        .... ..0. .... .... = Truncated: Message is not truncated
+        .... ...1 .... .... = Recursion desired: Do query recursively
+        .... .... 1... .... = Recursion available: Server can do recursive
+                              queries
+        .... .... .0.. .... = Z: reserved (0)
+        .... .... ..0. .... = Answer authenticated: Answer/authority portion
+                              was not authenticated by the server
+        .... .... ...0 .... = Non-authenticated data: Unacceptable
+        .... .... .... 0000 = Reply code: No error (0)
+    */
+    0x0, 0x1, /* QDCOUNT */
+    0x0, 0x4, /* ANCOUNT */
+    0x0, 0x0, /* NSCOUNT */
+    0x0, 0x0, /* ARCOUNT */
+
+    /* here's the question */
+    0x4, 0x63, 0x75, 0x72, 0x6c, 0x2, 0x73, 0x65, 0x0, /* curl.se */
+    0x0, 0x1, /* QTYPE: A */
+    0x0, 0x1, /* QCLASS: IN */
+
+    /* 4 answers */
+    0xc0, 0xc, /* points to curl.se */
+    0x0, 0x1, /* QTYPE A */
+    0x0, 0x1, /* QCLASS IN */
+    0x0, 0x0, 0xa, 0x14, /* Time to live: 2580 (43 minutes) */
+    0x0, 0x4, /* data length */
+    0x97, 0x65, 0x41, 0x5b, /* Address: 151.101.65.91 */
+
+    0xc0, 0xc, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0xa, 0x14,
+    0x0, 0x4, 0x97, 0x65, 0x81, 0x5b, /* Address: 151.101.129.91 */
+    0xc0, 0xc, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0xa, 0x14,
+    0x0, 0x4, 0x97, 0x65, 0xc1, 0x5b, /* Address: 151.101.193.91 */
+    0xc0, 0xc, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0xa, 0x14,
+    0x0, 0x4, 0x97, 0x65, 0x1, 0x5b,  /* Address: 151.101.1.91 */
+#if 0
+    /* 1 additional record (ARCOUNT) */
+
+    0x0, 0x0, 0x29, 0x4, 0xd0, 0x0, 0x0,
+    0x0, 0x0, 0x0, 0x0
+#endif
+  };
+  size_t len = sizeof(bytes);
+
+  bytes[0] = (unsigned char)(id >> 8);
+  bytes[1] = (unsigned char)(id & 0xff);
+
+  rc = sendto(sock, bytes, len, 0, addr, addrlen);
+  if(rc != (ssize_t)len) {
+    fprintf(stderr, "failed sending %d bytes\n", (int)len);
+  }
+  return 0;
+}
+#endif
+
+static void add_answer(unsigned char *bytes, size_t *w,
+                       const unsigned char *a, size_t alen,
+                       unsigned short qtype)
+{
+  size_t i = *w;
+
+  /* add answer */
+  bytes[i++] = 0xc0;
+  bytes[i++] = 0x0c; /* points to the query at this fixed packet index */
+
+  /* QTYPE */
+  bytes[i++] = (unsigned char)(qtype >> 8);
+  bytes[i++] = (unsigned char)(qtype & 0xff);
+
+  /* QCLASS IN */
+  bytes[i++] = 0x00;
+  bytes[i++] = 0x01;
+
+  /* TTL, Time to live: 2580 (43 minutes) */
+  bytes[i++] = 0x00;
+  bytes[i++] = 0x00;
+  bytes[i++] = 0x0a;
+  bytes[i++] = 0x14;
+
+  /* QTYPE size */
+  bytes[i++] = (unsigned char)(alen >> 8);
+  bytes[i++] = (unsigned char)(alen & 0xff);
+
+  memcpy(&bytes[i], a, alen);
+  i += alen;
+
+  *w = i;
+}
+
+#ifdef _WIN32
+#define SENDTO3 int
+#else
+#define SENDTO3 size_t
+#endif
+
+/* this is an answer to a question */
+static int send_response(curl_socket_t sock,
+                         const struct sockaddr *addr, curl_socklen_t addrlen,
+                         unsigned char *qbuf, size_t qlen,
+                         unsigned short qtype, unsigned short id)
+{
+  ssize_t rc;
+  size_t i;
+  int a;
+  unsigned char ancount = 3;
+  unsigned char bytes[256] = {
+    0x80, 0xea, /* ID, overwrite */
+    0x81, 0x80,
+    /*
+    Flags: 0x8180 Standard query response, No error
+        1... .... .... .... = Response: Message is a response
+        .000 0... .... .... = Opcode: Standard query (0)
+        .... .0.. .... .... = Authoritative: Server is not an authority for
+                              domain
+        .... ..0. .... .... = Truncated: Message is not truncated
+        .... ...1 .... .... = Recursion desired: Do query recursively
+        .... .... 1... .... = Recursion available: Server can do recursive
+                              queries
+        .... .... .0.. .... = Z: reserved (0)
+        .... .... ..0. .... = Answer authenticated: Answer/authority portion
+                              was not authenticated by the server
+        .... .... ...0 .... = Non-authenticated data: Unacceptable
+        .... .... .... 0000 = Reply code: No error (0)
+    */
+    0x0, 0x1, /* QDCOUNT a single question */
+    0x0, 0x0, /* ANCOUNT number of answers */
+    0x0, 0x0, /* NSCOUNT */
+    0x0, 0x0  /* ARCOUNT */
+  };
+  static const unsigned char ipv4_localhost[] = { 127, 0, 0, 1 };
+  static const unsigned char ipv6_localhost[] = {
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
+  };
+
+  bytes[0] = (unsigned char)(id >> 8);
+  bytes[1] = (unsigned char)(id & 0xff);
+  bytes[7] = ancount;
+
+  if(qlen > (sizeof(bytes) - 12))
+    return -1;
+
+  /* append query, includes QTYPE and QCLASS */
+  memcpy(&bytes[12], qbuf, qlen);
+
+  i = 12 + qlen;
+
+  for(a = 0; a < ancount; a++) {
+    switch(qtype) {
+    case QTYPE_A:
+      add_answer(bytes, &i, ipv4_localhost, sizeof(ipv4_localhost), QTYPE_A);
+      break;
+    case QTYPE_AAAA:
+      add_answer(bytes, &i, ipv6_localhost, sizeof(ipv6_localhost),
+                 QTYPE_AAAA);
+      break;
+    }
+  }
+
+#ifdef __AMIGA__
+  /* Amiga breakage */
+  (void)rc;
+  (void)sock;
+  (void)addr;
+  (void)addrlen;
+  fprintf(stderr, "Not working\n");
+  return -1;
+#else
+  rc = sendto(sock, (const void *)bytes, (SENDTO3) i, 0, addr, addrlen);
+  if(rc != (ssize_t)i) {
+    fprintf(stderr, "failed sending %d bytes\n", (int)i);
+  }
+#endif
+  return 0;
+}
+
+int main(int argc, char **argv)
+{
+  srvr_sockaddr_union_t me;
+  ssize_t n = 0;
+  int arg = 1;
+  unsigned short port = 9123; /* UDP */
+  curl_socket_t sock = CURL_SOCKET_BAD;
+  int flag;
+  int rc;
+  int error;
+  int result = 0;
+
+  pidname = ".dnsd.pid";
+  serverlogfile = "log/dnsd.log";
+  serverlogslocked = 0;
+
+  while(argc > arg) {
+    if(!strcmp("--verbose", argv[arg])) {
+      arg++;
+      /* nothing yet */
+    }
+    else if(!strcmp("--version", argv[arg])) {
+      printf("dnsd IPv4%s\n",
+#ifdef USE_IPV6
+             "/IPv6"
+#else
+             ""
+#endif
+             );
+      return 0;
+    }
+    else if(!strcmp("--pidfile", argv[arg])) {
+      arg++;
+      if(argc > arg)
+        pidname = argv[arg++];
+    }
+    else if(!strcmp("--portfile", argv[arg])) {
+      arg++;
+      if(argc > arg)
+        portname = argv[arg++];
+    }
+    else if(!strcmp("--logfile", argv[arg])) {
+      arg++;
+      if(argc > arg)
+        serverlogfile = argv[arg++];
+    }
+    else if(!strcmp("--logdir", argv[arg])) {
+      arg++;
+      if(argc > arg)
+        logdir = argv[arg++];
+    }
+    else if(!strcmp("--ipv4", argv[arg])) {
+#ifdef USE_IPV6
+      ipv_inuse = "IPv4";
+      use_ipv6 = FALSE;
+#endif
+      arg++;
+    }
+    else if(!strcmp("--ipv6", argv[arg])) {
+#ifdef USE_IPV6
+      ipv_inuse = "IPv6";
+      use_ipv6 = TRUE;
+#endif
+      arg++;
+    }
+    else if(!strcmp("--port", argv[arg])) {
+      arg++;
+      if(argc > arg) {
+        char *endptr;
+        unsigned long ulnum = strtoul(argv[arg], &endptr, 10);
+        port = util_ultous(ulnum);
+        arg++;
+      }
+    }
+    else {
+      if(argv[arg])
+        fprintf(stderr, "unknown option: %s\n", argv[arg]);
+      puts("Usage: dnsd [option]\n"
+           " --version\n"
+           " --logfile [file]\n"
+           " --logdir [directory]\n"
+           " --pidfile [file]\n"
+           " --portfile [file]\n"
+           " --ipv4\n"
+           " --ipv6\n"
+           " --port [port]\n");
+      return 0;
+    }
+  }
+
+  msnprintf(loglockfile, sizeof(loglockfile), "%s/%s/dnsd-%s.lock",
+            logdir, SERVERLOGS_LOCKDIR, ipv_inuse);
+
+#ifdef _WIN32
+  if(win32_init())
+    return 2;
+#endif
+
+#ifdef USE_IPV6
+  if(!use_ipv6)
+#endif
+    sock = socket(AF_INET, SOCK_DGRAM, 0);
+#ifdef USE_IPV6
+  else
+    sock = socket(AF_INET6, SOCK_DGRAM, 0);
+#endif
+
+  if(CURL_SOCKET_BAD == sock) {
+    error = SOCKERRNO;
+    logmsg("Error creating socket (%d) %s", error, sstrerror(error));
+    result = 1;
+    goto dnsd_cleanup;
+  }
+
+  flag = 1;
+  if(0 != setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+            (void *)&flag, sizeof(flag))) {
+    error = SOCKERRNO;
+    logmsg("setsockopt(SO_REUSEADDR) failed with error (%d) %s",
+           error, sstrerror(error));
+    result = 1;
+    goto dnsd_cleanup;
+  }
+
+#ifdef USE_IPV6
+  if(!use_ipv6) {
+#endif
+    memset(&me.sa4, 0, sizeof(me.sa4));
+    me.sa4.sin_family = AF_INET;
+    me.sa4.sin_addr.s_addr = INADDR_ANY;
+    me.sa4.sin_port = htons(port);
+    rc = bind(sock, &me.sa, sizeof(me.sa4));
+#ifdef USE_IPV6
+  }
+  else {
+    memset(&me.sa6, 0, sizeof(me.sa6));
+    me.sa6.sin6_family = AF_INET6;
+    me.sa6.sin6_addr = in6addr_any;
+    me.sa6.sin6_port = htons(port);
+    rc = bind(sock, &me.sa, sizeof(me.sa6));
+  }
+#endif /* USE_IPV6 */
+  if(0 != rc) {
+    error = SOCKERRNO;
+    logmsg("Error binding socket on port %hu (%d) %s", port, error,
+           sstrerror(error));
+    result = 1;
+    goto dnsd_cleanup;
+  }
+
+  if(!port) {
+    /* The system was supposed to choose a port number, figure out which
+       port we actually got and update the listener port value with it. */
+    curl_socklen_t la_size;
+    srvr_sockaddr_union_t localaddr;
+#ifdef USE_IPV6
+    if(!use_ipv6)
+#endif
+      la_size = sizeof(localaddr.sa4);
+#ifdef USE_IPV6
+    else
+      la_size = sizeof(localaddr.sa6);
+#endif
+    memset(&localaddr.sa, 0, (size_t)la_size);
+    if(getsockname(sock, &localaddr.sa, &la_size) < 0) {
+      error = SOCKERRNO;
+      logmsg("getsockname() failed with error (%d) %s",
+             error, sstrerror(error));
+      sclose(sock);
+      goto dnsd_cleanup;
+    }
+    switch(localaddr.sa.sa_family) {
+    case AF_INET:
+      port = ntohs(localaddr.sa4.sin_port);
+      break;
+#ifdef USE_IPV6
+    case AF_INET6:
+      port = ntohs(localaddr.sa6.sin6_port);
+      break;
+#endif
+    default:
+      break;
+    }
+    if(!port) {
+      /* Real failure, listener port shall not be zero beyond this point. */
+      logmsg("Apparently getsockname() succeeded, with listener port zero.");
+      logmsg("A valid reason for this failure is a binary built without");
+      logmsg("proper network library linkage. This might not be the only");
+      logmsg("reason, but double check it before anything else.");
+      result = 2;
+      goto dnsd_cleanup;
+    }
+  }
+
+  dnsd_wrotepidfile = write_pidfile(pidname);
+  if(!dnsd_wrotepidfile) {
+    result = 1;
+    goto dnsd_cleanup;
+  }
+
+  if(portname) {
+    dnsd_wroteportfile = write_portfile(portname, port);
+    if(!dnsd_wroteportfile) {
+      result = 1;
+      goto dnsd_cleanup;
+    }
+  }
+
+  logmsg("Running %s version on port UDP/%d", ipv_inuse, (int)port);
+
+  for(;;) {
+    unsigned short id = 0;
+    unsigned char inbuffer[1500];
+    srvr_sockaddr_union_t from;
+    curl_socklen_t fromlen;
+    unsigned char qbuf[256]; /* query storage */
+    size_t qlen = 0; /* query size */
+    unsigned short qtype = 0;
+    fromlen = sizeof(from);
+#ifdef USE_IPV6
+    if(!use_ipv6)
+#endif
+      fromlen = sizeof(from.sa4);
+#ifdef USE_IPV6
+    else
+      fromlen = sizeof(from.sa6);
+#endif
+    n = (ssize_t)recvfrom(sock, (char *)inbuffer, sizeof(inbuffer), 0,
+                          &from.sa, &fromlen);
+    if(got_exit_signal)
+      break;
+    if(n < 0) {
+      logmsg("recvfrom");
+      result = 3;
+      break;
+    }
+
+    store_incoming(inbuffer, n, qbuf, &qlen, &qtype, &id);
+
+    set_advisor_read_lock(loglockfile);
+    serverlogslocked = 1;
+
+    send_response(sock, &from.sa, fromlen, qbuf, qlen, qtype, id);
+
+    if(got_exit_signal)
+      break;
+
+    if(serverlogslocked) {
+      serverlogslocked = 0;
+      clear_advisor_read_lock(loglockfile);
+    }
+
+    logmsg("end of one transfer");
+
+  }
+
+dnsd_cleanup:
+
+#if 0
+  if((peer != sock) && (peer != CURL_SOCKET_BAD))
+    sclose(peer);
+#endif
+
+  if(sock != CURL_SOCKET_BAD)
+    sclose(sock);
+
+  if(got_exit_signal)
+    logmsg("signalled to die");
+
+  if(dnsd_wrotepidfile)
+    unlink(pidname);
+  if(dnsd_wroteportfile)
+    unlink(portname);
+
+  if(serverlogslocked) {
+    serverlogslocked = 0;
+    clear_advisor_read_lock(loglockfile);
+  }
+
+  restore_signal_handlers(true);
+
+  if(got_exit_signal) {
+    logmsg("========> %s dnsd (port: %d pid: %ld) exits with signal (%d)",
+           ipv_inuse, (int)port, (long)curlx_getpid(), exit_signal);
+    /*
+     * To properly set the return status of the process we
+     * must raise the same signal SIGINT or SIGTERM that we
+     * caught and let the old handler take care of it.
+     */
+    raise(exit_signal);
+  }
+
+  logmsg("========> dnsd quits");
+  return result;
+}
index 8b63ca2f4e05c984b0ecc037ee2d479de7b34ba4..683aafa2c2dd57a9e61b937be0136656b2ecc7d9 100644 (file)
@@ -186,9 +186,6 @@ static int prevchar = -1;  /* putbuf: previous char (cr check) */
 static tftphdr_storage_t trsbuf;
 static tftphdr_storage_t ackbuf;
 
-static srvr_sockaddr_union_t from;
-static curl_socklen_t fromlen;
-
 static curl_socket_t peer = CURL_SOCKET_BAD;
 
 static unsigned int timeout;
@@ -544,6 +541,8 @@ int main(int argc, char **argv)
   int error;
   struct testcase test;
   int result = 0;
+  srvr_sockaddr_union_t from;
+  curl_socklen_t fromlen;
 
   memset(&test, 0, sizeof(test));
 
index 9df95c53e6010191509952208dbccd92a35ec63f..1e23f3d01b2b94f4ae530baf89d035f541c3a993 100644 (file)
@@ -122,7 +122,7 @@ sub serverfactors {
         $ipvnum = ($4 && ($4 =~ /6$/)) ? 6 : 4;
     }
     elsif($server =~
-        /^(tftp|sftp|socks|ssh|rtsp|gopher|httptls)(\d*)(-ipv6|)$/) {
+        /^(dns|tftp|sftp|socks|ssh|rtsp|gopher|httptls)(\d*)(-ipv6|)$/) {
         $proto  = $1;
         $idnum  = ($2 && ($2 > 1)) ? $2 : 1;
         $ipvnum = ($3 && ($3 =~ /6$/)) ? 6 : 4;
@@ -142,7 +142,7 @@ sub servername_str {
 
     $proto = uc($proto) if($proto);
     die "unsupported protocol: '$proto'" unless($proto &&
-        ($proto =~ /^(((FTP|HTTP|HTTP\/2|HTTP\/3|IMAP|POP3|GOPHER|SMTP|HTTPS-MTLS)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|HTTPTLS|DICT|SMB|SMBS|TELNET|MQTT))$/));
+        ($proto =~ /^(((DNS|FTP|HTTP|HTTP\/2|HTTP\/3|IMAP|POP3|GOPHER|SMTP|HTTPS-MTLS)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|HTTPTLS|DICT|SMB|SMBS|TELNET|MQTT))$/));
 
     $ipver = (not $ipver) ? 'ipv4' : lc($ipver);
     die "unsupported IP version: '$ipver'" unless($ipver &&
index 477698e4fd628bf6a91fa37470ed87c9156afac6..a37622fccb93d2beefc9f273dc79f3f0219547f4 100644 (file)
@@ -235,7 +235,8 @@ sub init_serverpidfile_hash {
     }
   }
   for my $proto (('tftp', 'sftp', 'socks', 'ssh', 'rtsp', 'httptls',
-                  'dict', 'smb', 'smbs', 'telnet', 'mqtt', 'https-mtls')) {
+                  'dict', 'smb', 'smbs', 'telnet', 'mqtt', 'https-mtls',
+                  'dns')) {
     for my $ipvnum ((4, 6)) {
       for my $idnum ((1, 2)) {
         my $serv = servername_id($proto, $ipvnum, $idnum);
@@ -295,6 +296,7 @@ sub serverfortest {
     for(my $i = scalar(@what) - 1; $i >= 0; $i--) {
         my $srvrline = $what[$i];
         chomp $srvrline if($srvrline);
+
         if($srvrline =~ /^(\S+)((\s*)(.*))/) {
             my $server = "${1}";
             my $lnrest = "${2}";
@@ -303,7 +305,12 @@ sub serverfortest {
                 $server = "${1}${4}${5}";
                 $tlsext = uc("TLS-${3}");
             }
-            if(! grep /^\Q$server\E$/, @protocols) {
+
+            my @lprotocols = @protocols;
+
+            push @lprotocols, "dns";
+
+            if(! grep /^\Q$server\E$/, @lprotocols) {
                 if(substr($server,0,5) ne "socks") {
                     if($tlsext) {
                         return ("curl lacks $tlsext support", 4);
@@ -1029,6 +1036,7 @@ my %protofunc = ('http' => \&verifyhttp,
                  'smtps' => \&verifyftp,
                  'tftp' => \&verifyftp,
                  'ssh' => \&verifyssh,
+                 'dns' => \&verifypid,
                  'socks' => \&verifypid,
                  'socks5unix' => \&verifypid,
                  'gopher' => \&verifyhttp,
@@ -1614,6 +1622,70 @@ sub runtftpserver {
     return (0, $pid2, $tftppid, $port);
 }
 
+#######################################################################
+# start the dns server
+#
+sub rundnsserver {
+    my ($id, $verb, $ipv6) = @_;
+    my $ip = $HOSTIP;
+    my $proto = 'dns';
+    my $ipvnum = 4;
+    my $idnum = ($id && ($id =~ /^(\d+)$/) && ($id > 1)) ? $id : 1;
+
+    if($ipv6) {
+        # if IPv6, use a different setup
+        $ipvnum = 6;
+        $ip = $HOST6IP;
+    }
+
+    my $server = servername_id($proto, $ipvnum, $idnum);
+
+    my $pidfile = $serverpidfile{$server};
+
+    # don't retry if the server doesn't work
+    if ($doesntrun{$pidfile}) {
+        return (2, 0, 0, 0);
+    }
+
+    my $pid = processexists($pidfile);
+    if($pid > 0) {
+        stopserver($server, "$pid");
+    }
+    unlink($pidfile) if(-f $pidfile);
+
+    my $srvrname = servername_str($proto, $ipvnum, $idnum);
+    my $portfile = $serverportfile{$server};
+    my $logfile = server_logfilename($LOGDIR, $proto, $ipvnum, $idnum);
+
+    my $cmd=server_exe('dnsd');
+    $cmd .= " --port 0";
+    $cmd .= " --verbose" if($debugprotocol);
+    $cmd .= " --pidfile \"$pidfile\"";
+    $cmd .= " --portfile \"$portfile\"";
+    $cmd .= " --logfile \"$logfile\"";
+    $cmd .= " --logdir \"$LOGDIR\"";
+    $cmd .= " --id $idnum" if($idnum > 1);
+    $cmd .= " --ipv$ipvnum";
+
+    # start DNS server on a random port
+    my ($dnspid, $pid2) = startnew($cmd, $pidfile, 15, 0);
+
+    if($dnspid <= 0 || !pidexists($dnspid)) {
+        # it is NOT alive
+        logmsg "RUN: failed to start the $srvrname server\n";
+        stopserver($server, "$pid2");
+        $doesntrun{$pidfile} = 1;
+        return (1, 0, 0, 0);
+    }
+
+    my $port = pidfromfile($portfile);
+
+    if($verb) {
+        logmsg "RUN: $srvrname server on PID $dnspid port $port\n";
+    }
+
+    return (0, $pid2, $dnspid, $port);
+}
 
 #######################################################################
 # start the rtsp server
@@ -2217,6 +2289,28 @@ sub responsive_tftp_server {
     return &responsiveserver($proto, $ipvnum, $idnum, $ip, $port);
 }
 
+#######################################################################
+# Single shot dns server responsiveness test. This should only be
+# used to verify that a server present in %run hash is still functional
+#
+sub responsive_dns_server {
+    my ($id, $verb, $ipv6) = @_;
+    my $proto = 'dns';
+    my $port = protoport($proto);
+    my $ip = $HOSTIP;
+    my $ipvnum = 4;
+    my $idnum = ($id && ($id =~ /^(\d+)$/) && ($id > 1)) ? $id : 1;
+
+    if($ipv6) {
+        # if IPv6, use a different setup
+        $ipvnum = 6;
+        $port = protoport('dns6');
+        $ip = $HOST6IP;
+    }
+
+    return &responsiveserver($proto, $ipvnum, $idnum, $ip, $port);
+}
+
 #######################################################################
 # Single shot non-stunnel HTTP TLS extensions capable server
 # responsiveness test. This should only be used to verify that a
@@ -2721,6 +2815,23 @@ sub startservers {
                 $run{'httptls-ipv6'}="$pid $pid2";
             }
         }
+        elsif($what eq "dns") {
+            if($run{'dns'} &&
+               !responsive_dns_server("", $verbose)) {
+                if(stopserver('dns')) {
+                    return ("failed stopping unresponsive DNS server", 3);
+                }
+            }
+            if(!$run{'dns'}) {
+                ($serr, $pid, $pid2, $PORT{'dns'}) =
+                    rundnsserver("", $verbose);
+                if($pid <= 0) {
+                    return ("failed starting DNS server", $serr);
+                }
+                logmsg sprintf("* pid dns => %d %d\n", $pid, $pid2) if($verbose);
+                $run{'dns'}="$pid $pid2";
+            }
+        }
         elsif($what eq "tftp") {
             if($run{'tftp'} &&
                !responsive_tftp_server("", $verbose)) {
@@ -2936,7 +3047,7 @@ sub subvariables {
 
     # test server ports
     # Substitutes variables like %HTTPPORT and %SMTP6PORT with the server ports
-    foreach my $proto ('DICT',
+    foreach my $proto ('DICT', 'DNS',
                        'FTP', 'FTP6', 'FTPS',
                        'GOPHER', 'GOPHER6', 'GOPHERS',
                        'HTTP', 'HTTP6', 'HTTPS', 'HTTPS-MTLS',