tests: Add a simple DNS server
authorJakub Hrozek <jakub.hrozek@gmail.com>
Mon, 8 Sep 2014 07:48:17 +0000 (09:48 +0200)
committerMichael Adam <obnox@samba.org>
Tue, 21 Oct 2014 11:39:39 +0000 (13:39 +0200)
This DNS server will be usable for testing. Currently only supports A
records.

The server responds to any A query with the value of RWRAP_TEST_A_REC
environment variable. If undefined, the server always replies with
127.0.10.10.

Please note the server does *not* perform many security checks and is
completely unsuitable outside a test suite! Check out servers like
dnsmasq for anything more complex.

Signed-off-by: Jakub Hrozek <jakub.hrozek@gmail.com>
Reviewed-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Michael Adam <obnox@samba.org>
ConfigureChecks.cmake
tests/CMakeLists.txt
tests/dns_srv.c [new file with mode: 0644]

index 7738179c8b990892712e2d0aa0c233c93abd1d96..079737587d14fcc85eef2579cb3280c3f14ef61b 100644 (file)
@@ -84,6 +84,24 @@ check_function_exists(__res_nsearch HAVE___RES_NSEARCH)
 
 set(CMAKE_REQUIRED_LIBRARIES)
 
+if (UNIX)
+    if (NOT LINUX)
+        # libsocket (Solaris)
+        check_library_exists(socket getaddrinfo "" HAVE_LIBSOCKET)
+        if (HAVE_LIBSOCKET)
+          set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} socket)
+        endif (HAVE_LIBSOCKET)
+
+        # libnsl/inet_pton (Solaris)
+        check_library_exists(nsl inet_pton "" HAVE_LIBNSL)
+        if (HAVE_LIBNSL)
+            set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} nsl)
+        endif (HAVE_LIBNSL)
+    endif (NOT LINUX)
+
+    check_function_exists(getaddrinfo HAVE_GETADDRINFO)
+endif (UNIX)
+
 check_library_exists(dl dlopen "" HAVE_LIBDL)
 if (HAVE_LIBDL)
     find_library(DLFCN_LIBRARY dl)
index 323a768048e8c1e9146925b4c2bb7d1685a0e1aa..adc348f42df60032ac5567e3e5ea55dbfbcbb13a 100644 (file)
@@ -7,6 +7,10 @@ include_directories(
   ${CMOCKA_INCLUDE_DIR}
 )
 
+# A simple DNS server for testing
+add_executable(dns_srv dns_srv.c)
+target_link_libraries(dns_srv ${RWRAP_REQUIRED_LIBRARIES})
+
 set(TESTSUITE_LIBRARIES ${RWRAP_REQUIRED_LIBRARIES} ${CMOCKA_LIBRARY})
 
 set(RWRAP_TESTS
diff --git a/tests/dns_srv.c b/tests/dns_srv.c
new file mode 100644 (file)
index 0000000..b2e6aa3
--- /dev/null
@@ -0,0 +1,647 @@
+/*
+ * Copyright (C) Jakub Hrozek 2014 <jakub.hrozek@gmail.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the author nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <resolv.h>
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <unistd.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include <getopt.h>
+
+#ifndef PIDFILE
+#define PIDFILE "dns_srv.pid"
+#endif  /* PIDFILE */
+
+#define DNS_PORT       53
+#define DFL_TTL         30
+
+#ifndef BUFSIZE
+#define BUFSIZE 1024
+#endif /* BUFSIZE */
+
+#ifndef discard_const
+#define discard_const(ptr) ((void *)((uintptr_t)(ptr)))
+#endif
+
+#ifndef discard_const_p
+#define discard_const_p(type, ptr) ((type *)discard_const(ptr))
+#endif
+
+#ifndef ZERO_STRUCT
+#define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x))
+#endif
+
+/* The macros below are taken from c-ares */
+#define DNS__16BIT(p)  ((unsigned short)((unsigned int) 0xffff & \
+                       (((unsigned int)((unsigned char)(p)[0]) << 8U) | \
+                       ((unsigned int)((unsigned char)(p)[1])))))
+
+#define DNS__SET16BIT(p, v)  (((p)[0] = (unsigned char)(((v) >> 8) & 0xff)), \
+                             ((p)[1] = (unsigned char)((v) & 0xff)))
+
+#define DNS__SET32BIT(p, v)  (((p)[0] = (unsigned char)(((v) >> 24) & 0xff)), \
+                             ((p)[1] = (unsigned char)(((v) >> 16) & 0xff)), \
+                             ((p)[2] = (unsigned char)(((v) >> 8) & 0xff)), \
+                             ((p)[3] = (unsigned char)((v) & 0xff)));
+
+/* Macros for parsing a DNS header */
+#define DNS_HEADER_QID(h)              DNS__16BIT(h)
+#define DNS_HEADER_OPCODE(h)           (((h)[2] >> 3) & 0xf)
+#define DNS_HEADER_TC(h)               (((h)[2] >> 1) & 0x1)
+#define DNS_HEADER_QDCOUNT(h)          DNS__16BIT((h) + 4)
+
+/* Macros for parsing the fixed part of a DNS question */
+#define DNS_QUESTION_TYPE(q)           DNS__16BIT(q)
+#define DNS_QUESTION_CLASS(q)          DNS__16BIT((q) + 2)
+
+/* Macros for constructing a DNS header */
+#define DNS_HEADER_SET_QID(h, v)       DNS__SET16BIT(h, v)
+#define DNS_HEADER_SET_QR(h, v)                ((h)[2] |= (unsigned char)(((v) & 0x1) << 7))
+#define DNS_HEADER_SET_RD(h, v)                ((h)[2] |= (unsigned char)((v) & 0x1))
+#define DNS_HEADER_SET_RA(h, v)                ((h)[3] |= (unsigned char)(((v) & 0x1) << 7))
+#define DNS_HEADER_SET_QDCOUNT(h, v)   DNS__SET16BIT((h) + 4, v)
+#define DNS_HEADER_SET_ANCOUNT(h, v)   DNS__SET16BIT((h) + 6, v)
+
+/* Macros for constructing the fixed part of a DNS question */
+#define DNS_QUESTION_SET_TYPE(q, v)    DNS__SET16BIT(q, v)
+#define DNS_QUESTION_SET_CLASS(q, v)   DNS__SET16BIT((q) + 2, v)
+
+/* Macros for constructing the fixed part of a DNS resource record */
+#define DNS_RR_SET_TYPE(r, v)          DNS__SET16BIT(r, v)
+#define DNS_RR_SET_CLASS(r, v)         DNS__SET16BIT((r) + 2, v)
+#define DNS_RR_SET_TTL(r, v)           DNS__SET32BIT((r) + 4, v)
+#define DNS_RR_SET_LEN(r, v)           DNS__SET16BIT((r) + 8, v)
+
+#define DEFAULT_A_REC   "127.0.10.10"
+
+struct dns_srv_opts {
+       char *bind;
+       bool daemon;
+       int port;
+       const char *pidfile;
+};
+
+struct dns_query {
+       char *query;
+
+       uint16_t id;
+       uint16_t qtype;
+       uint16_t qclass;
+
+       unsigned char *reply;
+       size_t reply_len;
+};
+
+static void free_dns_query(struct dns_query *query)
+{
+       free(query->query);
+       free(query->reply);
+       memset(query, 0, sizeof(struct dns_query));
+}
+
+static size_t encode_name(unsigned char *buffer, const char *name)
+{
+       const char *p, *dot;
+       unsigned char *bp;
+       size_t len;
+
+       p = name;
+       bp = buffer;
+       len = 0;
+
+       while ((dot = strchr(p, '.')) != NULL) {
+               *bp++ = dot - p;
+               len++;
+
+               while (p < dot) {
+                       *bp++ = *p++;
+                       len++;
+               }
+               p = dot + 1; /* move past the dot */
+       }
+
+       *bp = '\0';
+       len++;
+
+       return len;
+}
+
+static void fake_header(struct dns_query *query)
+{
+       DNS_HEADER_SET_QID(query->reply, query->id);
+       DNS_HEADER_SET_QR(query->reply, 1);
+       DNS_HEADER_SET_RD(query->reply, 1);
+       DNS_HEADER_SET_RA(query->reply, 1);
+       DNS_HEADER_SET_QDCOUNT(query->reply, 1);
+       DNS_HEADER_SET_ANCOUNT(query->reply, 1);
+}
+
+static size_t fake_question(struct dns_query *query, unsigned char **pout)
+{
+       unsigned char *p;
+       size_t len;
+
+       p = *pout;
+
+       len = encode_name(p, query->query);
+       p += len;
+       DNS_QUESTION_SET_TYPE(p, query->qtype);
+       len += sizeof(uint16_t);
+       DNS_QUESTION_SET_CLASS(p, query->qclass);
+       len += sizeof(uint16_t);
+
+       p += 2 * sizeof(uint16_t);
+
+       *pout = p;
+       return len;
+}
+
+static size_t fake_answer(struct dns_query *query, unsigned char **pout)
+{
+       unsigned char *p;
+       size_t len;
+       size_t rlen;
+       char *val;
+       struct in_addr a_rec;
+
+       p = *pout;
+
+       len = encode_name(p, query->query);
+       p += len;
+
+       DNS_RR_SET_TYPE(p, query->qtype);
+       len += sizeof(uint16_t);
+
+       DNS_RR_SET_CLASS(p, query->qclass);
+       len += sizeof(uint16_t);
+
+       DNS_RR_SET_TTL(p, DFL_TTL);
+       len += sizeof(uint32_t);
+
+       switch (query->qtype) {
+               case ns_t_a:
+                       val = getenv("RWRAP_TEST_A_REC");
+                       inet_pton(AF_INET,
+                                 val ? val : DEFAULT_A_REC,
+                                 &a_rec);
+                       rlen = sizeof(struct in_addr);
+                       break;
+               default:
+                       /* Unhandled record */
+                       return -1;
+       }
+
+       DNS_RR_SET_LEN(p, rlen);
+       len += sizeof(uint16_t);
+
+       /* Move to the RDATA section */
+       p += sizeof(uint16_t) + /* type */
+                sizeof(uint16_t) +     /* class */
+                sizeof(uint32_t) +     /* ttl */
+                sizeof(uint16_t);       /* rlen */
+
+       /* Copy RDATA */
+       memcpy(p, &a_rec, sizeof(struct in_addr));
+       len += rlen;
+
+       *pout = p;
+       return len;
+}
+
+static int fake_reply(struct dns_query *query)
+{
+       unsigned char *p;
+
+       query->reply = malloc(BUFSIZE);
+       if (query->reply == NULL) {
+               return ENOMEM;
+       }
+
+       memset(query->reply, 0, BUFSIZE);
+       p = query->reply;
+
+       fake_header(query);
+       query->reply_len = NS_HFIXEDSZ;
+       p += NS_HFIXEDSZ;
+
+       /* advances p internally */
+       query->reply_len += fake_question(query, &p);
+       query->reply_len += fake_answer(query, &p);
+
+       return 0;
+}
+
+static char *extract_name(char **buffer, size_t maxlen)
+{
+       char *query, *qp, *bp;
+       unsigned int len;
+       unsigned int i;
+
+       query = malloc(maxlen);
+       if (query == NULL) return NULL;
+
+       i = 0;
+       qp = query;
+       bp = *buffer;
+       do {
+               len = *bp;
+               bp++;
+
+               if (len > (maxlen - (qp - query))) {
+                       /* label is past the buffer */
+                       free(query);
+                       return NULL;
+               }
+
+               for (i = 0; i < len; i++) {
+                       *qp++ = *bp++;
+               }
+
+               if (len > 0) {
+                       *qp++ = '.';
+               } else {
+                       *qp = '\0';
+               }
+       } while (len > 0);
+
+       *buffer = bp;
+       return query;
+}
+
+static int parse_query(unsigned char *buffer,
+                      size_t len,
+                      struct dns_query *query)
+{
+       unsigned char *p;
+
+       p = buffer;
+
+       if (len < NS_HFIXEDSZ) {
+               /* Message too short */
+               return EBADMSG;
+       }
+
+       if (DNS_HEADER_OPCODE(p) != 0) {
+               /* Queries must have the opcode set to 0 */
+               return EBADMSG;
+       }
+
+       if (DNS_HEADER_QDCOUNT(p) != 1) {
+               /* We only support one query */
+               return EBADMSG;
+       }
+
+       if (len < NS_HFIXEDSZ + 2 * sizeof(uint16_t)) {
+               /* No room for class and type */
+               return EBADMSG;
+       }
+
+       /* Need to remember the query to respond with the same */
+       query->id = DNS_HEADER_QID(p);
+
+       /* Done with the header, move past it */
+       p += NS_HFIXEDSZ;
+       query->query = extract_name((char **) &p, len - NS_HFIXEDSZ);
+       if (query->query == NULL) {
+               return EIO;
+       }
+
+       query->qclass = DNS_QUESTION_CLASS(p);
+       if (query->qclass != ns_c_in) {
+               /* We only support Internet queries */
+               return EBADMSG;
+       }
+
+       query->qtype = DNS_QUESTION_TYPE(p);
+       return 0;
+}
+
+static void dns(int sock)
+{
+       struct sockaddr_storage css;
+       socklen_t addrlen = sizeof(css);
+       ssize_t bret;
+       unsigned char buf[BUFSIZE];
+       struct dns_query query;
+       int rv;
+
+       ZERO_STRUCT(query);
+
+       while (1) {
+               free_dns_query(&query);
+
+               /* for advanced features, use recvmsg here */
+               ZERO_STRUCT(buf);
+               bret = recvfrom(sock, buf, BUFSIZE, 0,
+                               (struct sockaddr *) &css, &addrlen);
+               if (bret == -1) {
+                       perror("recvfrom");
+                       continue;
+               }
+
+               /* parse query */
+               rv = parse_query(buf, bret, &query);
+               if (rv != 0) {
+                       continue;
+               }
+
+               /* Construct the reply */
+               rv = fake_reply(&query);
+               if (rv != 0) {
+                       continue;
+               }
+
+               /* send reply back */
+               bret = sendto(sock, query.reply, query.reply_len, 0,
+                               (struct sockaddr *) &css, addrlen);
+               if (bret == -1) {
+                       perror("sendto");
+                       continue;
+               }
+       }
+}
+
+static int pidfile(const char *path)
+{
+       int err;
+       int fd;
+       char pid_str[32] = { 0 };
+       ssize_t nwritten;
+       size_t len;
+
+       fd = open(path, O_RDONLY, 0644);
+       err = errno;
+       if (fd != -1) {
+               close(fd);
+               return EEXIST;
+       } else if (err != ENOENT) {
+               return err;
+       }
+
+       fd = open(path, O_CREAT | O_WRONLY | O_EXCL, 0644);
+       err = errno;
+       if (fd == -1) {
+               return err;
+       }
+
+       snprintf(pid_str, sizeof(pid_str) -1, "%u\n", (unsigned int) getpid());
+       len = strlen(pid_str);
+
+       nwritten = write(fd, pid_str, len);
+       close(fd);
+       if (nwritten != (ssize_t)len) {
+               return EIO;
+       }
+
+       return 0;
+}
+
+static int become_daemon(void)
+{
+       int ret;
+       pid_t child_pid;
+       int fd;
+       int i;
+
+       if (getppid() == 1) {
+               return 0;
+       }
+
+       child_pid = fork();
+       if (child_pid == -1) {
+               ret = errno;
+               perror("fork");
+               return ret;
+       } else if (child_pid > 0) {
+               exit(0);
+       }
+
+       /* If a working directory was defined, go there */
+#ifdef WORKING_DIR
+       chdir(WORKING_DIR);
+#endif
+
+       ret = setsid();
+       if (ret == -1) {
+               ret = errno;
+               perror("setsid");
+               return ret;
+       }
+
+       for (fd = getdtablesize(); fd >= 0; --fd) {
+               close(fd);
+       }
+
+       for (i = 0; i < 3; i++) {
+               fd = open("/dev/null", O_RDWR, 0);
+               if (fd < 0) {
+                       fd = open("/dev/null", O_WRONLY, 0);
+               }
+               if (fd < 0) {
+                       ret = errno;
+                       perror("Can't open /dev/null");
+                       return ret;
+               }
+               if (fd != i) {
+                       perror("Didn't get correct fd");
+                       close(fd);
+                       return EINVAL;
+               }
+       }
+
+       umask(0177);
+       return 0;
+}
+
+/*
+ * Returns 0 on success, errno on failure.
+ * If successful, sock is a ready to use socket.
+ */
+static int setup_srv(struct dns_srv_opts *opts, int *_sock)
+{
+       struct addrinfo hints;
+       struct addrinfo *res, *ri;
+       char svc[6];
+       int ret;
+       int sock;
+
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = SOCK_DGRAM;
+       hints.ai_flags = AI_PASSIVE;
+
+       snprintf(svc, sizeof(svc), "%d", opts->port);
+
+       ret = getaddrinfo(opts->bind, svc, &hints, &res);
+       if (ret != 0) {
+               return errno;
+       }
+
+       for (ri = res; ri != NULL; ri = ri->ai_next) {
+               sock = socket(ri->ai_family, ri->ai_socktype, ri->ai_protocol);
+               if (sock == -1) {
+                       ret = errno;
+                       freeaddrinfo(res);
+                       perror("socket");
+                       return ret;
+               }
+
+               ret = bind(sock, ri->ai_addr, ri->ai_addrlen);
+               if (ret == 0) {
+                       break;
+               }
+
+               close(sock);
+       }
+       freeaddrinfo(res);
+
+       if (ri == NULL) {
+               fprintf(stderr, "Could not bind\n");
+               return EFAULT;
+       }
+
+       *_sock = sock;
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       int ret;
+       int sock = -1;
+       struct dns_srv_opts opts;
+       int opt;
+       int optindex;
+       static struct option long_options[] = {
+               { discard_const_p(char, "bind-addr"),   required_argument,      0,  'b' },
+               { discard_const_p(char, "daemon"),      no_argument,            0,  'D' },
+               { discard_const_p(char, "port"),        required_argument,      0,  'p' },
+               { discard_const_p(char, "pid"),         required_argument,      0,  0 },
+               { 0,                                    0,                      0,  0 }
+       };
+
+       opts.bind = NULL;
+       opts.pidfile = PIDFILE;
+       opts.daemon = false;
+       opts.port = DNS_PORT;
+
+       while ((opt = getopt_long(argc, argv, "Db:p:",
+                                 long_options, &optindex)) != -1)
+       {
+               switch (opt) {
+               case 0:
+                       if (optindex == 3) {
+                               opts.pidfile = optarg;
+                       }
+                       break;
+               case 'b':
+                       opts.bind = optarg;
+                       break;
+               case 'D':
+                       opts.daemon = true;
+                       break;
+               case 'p':
+                       opts.port = atoi(optarg);
+                       break;
+               default: /* '?' */
+                       fprintf(stderr, "Usage: %s [-p port] [-b bind_addr] "
+                                       "[-D] [--pid pidfile]\n"
+                                       "-D tells the server to become a "
+                                       "deamon and write a PIDfile\n"
+                                       "The default PIDfile is '%s' "
+                                       "in the current directory\n",
+                                       PIDFILE, argv[0]);
+                       ret = 1;
+                       goto done;
+               }
+       }
+
+       if (opts.daemon) {
+               ret = become_daemon();
+               if (ret != 0) {
+                       fprintf(stderr, "Cannot become daemon: %s\n",
+                               strerror(ret));
+                       goto done;
+               }
+       }
+
+       ret = setup_srv(&opts, &sock);
+       if (ret != 0) {
+               fprintf(stderr, "Cannot setup server: %s\n", strerror(ret));
+               goto done;
+       }
+
+       if (opts.daemon) {
+               if (opts.pidfile == NULL) {
+                       fprintf(stderr, "Error: pidfile == NULL\n");
+                       ret = -1;
+                       goto done;
+               }
+
+               ret = pidfile(opts.pidfile);
+               if (ret != 0) {
+                       fprintf(stderr, "Cannot create pidfile %s: %s\n",
+                               opts.pidfile, strerror(ret));
+                       goto done;
+               }
+       }
+
+       dns(sock);
+       close(sock);
+
+       if (opts.daemon) {
+               unlink(opts.pidfile);
+       }
+
+       ret = 0;
+
+done:
+       return ret;
+}