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',