From: Daniel Stenberg Date: Wed, 9 Apr 2025 07:47:43 +0000 (+0200) Subject: tests/server/dnsd: basic DNS server for test suite X-Git-Tag: curl-8_14_0~277 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=02e9690c3ee2feb7c78c93c1c544c898df733383;p=thirdparty%2Fcurl.git tests/server/dnsd: basic DNS server for test suite 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 --- 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',