diff --git a/docs/libcurl/libcurl-env-dbg.md b/docs/libcurl/libcurl-env-dbg.md
index 60c887bfd5..aeb79ff395 100644
--- a/docs/libcurl/libcurl-env-dbg.md
+++ b/docs/libcurl/libcurl-env-dbg.md
@@ -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.
diff --git a/lib/asyn-ares.c b/lib/asyn-ares.c
index babae6fb27..80efeeb37c 100644
--- a/lib/asyn-ares.c
+++ b/lib/asyn-ares.c
@@ -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! */
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index 1ed9ccfab9..793501a38f 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -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
index 0000000000..7e77bbbe2c
--- /dev/null
+++ b/tests/data/test2102
@@ -0,0 +1,61 @@
+
+
+
+HTTP
+HTTP GET
+
+
+
+#
+# Server-side
+
+
+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-
+
+
+
+#
+# Client-side
+
+
+http
+dns
+
+
+Debug
+c-ares
+
+
+HTTP GET with host name
+
+
+CURL_DNS_SERVER=127.0.0.1:%DNSPORT
+
+
+http://examplehost.example:%HTTPPORT/%TESTNUMBER
+
+
+
+#
+# Verify data after the test has been "shot"
+
+
+GET /%TESTNUMBER HTTP/1.1
+Host: examplehost.example:%HTTPPORT
+User-Agent: curl/%VERSION
+Accept: */*
+
+
+
+
diff --git a/tests/server/Makefile.inc b/tests/server/Makefile.inc
index 5633b04676..3d36c137f0 100644
--- a/tests/server/Makefile.inc
+++ b/tests/server/Makefile.inc
@@ -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
index 0000000000..514fb72e19
--- /dev/null
+++ b/tests/server/dnsd.c
@@ -0,0 +1,682 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, , 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
+#endif
+#ifndef UNDER_CE
+#include
+#endif
+#ifdef HAVE_FCNTL_H
+#include
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include
+#endif
+#ifdef HAVE_NETDB_H
+#include
+#endif
+#ifdef HAVE_SYS_FILIO_H
+/* FIONREAD on Solaris 7 */
+#include
+#endif
+
+#include
+
+#include
+
+#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;
+}
diff --git a/tests/server/tftpd.c b/tests/server/tftpd.c
index 8b63ca2f4e..683aafa2c2 100644
--- a/tests/server/tftpd.c
+++ b/tests/server/tftpd.c
@@ -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));
diff --git a/tests/serverhelp.pm b/tests/serverhelp.pm
index 9df95c53e6..1e23f3d01b 100644
--- a/tests/serverhelp.pm
+++ b/tests/serverhelp.pm
@@ -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 &&
diff --git a/tests/servers.pm b/tests/servers.pm
index 477698e4fd..a37622fccb 100644
--- a/tests/servers.pm
+++ b/tests/servers.pm
@@ -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',