summaryrefslogtreecommitdiff
path: root/examples/ldns-testns.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/ldns-testns.c')
-rw-r--r--examples/ldns-testns.c582
1 files changed, 582 insertions, 0 deletions
diff --git a/examples/ldns-testns.c b/examples/ldns-testns.c
new file mode 100644
index 0000000..885453d
--- /dev/null
+++ b/examples/ldns-testns.c
@@ -0,0 +1,582 @@
+/*
+ * ldns-testns. Light-weight DNS daemon, gives canned replies.
+ *
+ * Tiny dns server, that responds with specially crafted replies
+ * to requests. For testing dns software.
+ *
+ * (c) NLnet Labs, 2005 - 2008
+ * See the file LICENSE for the license
+ */
+
+/*
+ * This program is a debugging aid. It can is not efficient, especially
+ * with a long config file, but it can give any reply to any query.
+ * This can help the developer pre-script replies for queries.
+ *
+ * It listens to IP4 UDP and TCP by default.
+ * You can specify a packet RR by RR with header flags to return.
+ *
+ * Missing features:
+ * - matching content different from reply content.
+ * - find way to adjust mangled packets?
+ */
+
+/*
+ The data file format is as follows:
+
+ ; comment.
+ ; a number of entries, these are processed first to last.
+ ; a line based format.
+
+ $ORIGIN origin
+ $TTL default_ttl
+
+ ENTRY_BEGIN
+ ; first give MATCH lines, that say what queries are matched
+ ; by this entry.
+ ; 'opcode' makes the query match the opcode from the reply
+ ; if you leave it out, any opcode matches this entry.
+ ; 'qtype' makes the query match the qtype from the reply
+ ; 'qname' makes the query match the qname from the reply
+ ; 'serial=1023' makes the query match if ixfr serial is 1023.
+ MATCH [opcode] [qtype] [qname] [serial=<value>]
+ MATCH [UDP|TCP]
+ MATCH ...
+ ; Then the REPLY header is specified.
+ REPLY opcode, rcode or flags.
+ (opcode) QUERY IQUERY STATUS NOTIFY UPDATE
+ (rcode) NOERROR FORMERR SERVFAIL NXDOMAIN NOTIMPL YXDOMAIN
+ YXRRSET NXRRSET NOTAUTH NOTZONE
+ (flags) QR AA TC RD CD RA AD
+ REPLY ...
+ ; any additional actions to do.
+ ; 'copy_id' copies the ID from the query to the answer.
+ ADJUST copy_id
+ ; 'sleep=10' sleeps for 10 seconds before giving the answer (TCP is open)
+ ADJUST [sleep=<num>] ; sleep before giving any reply
+ ADJUST [packet_sleep=<num>] ; sleep before this packet in sequence
+ SECTION QUESTION
+ <RRs, one per line> ; the RRcount is determined automatically.
+ SECTION ANSWER
+ <RRs, one per line>
+ SECTION AUTHORITY
+ <RRs, one per line>
+ SECTION ADDITIONAL
+ <RRs, one per line>
+ EXTRA_PACKET ; follow with SECTION, REPLY for more packets.
+ HEX_ANSWER_BEGIN ; follow with hex data
+ ; this replaces any answer packet constructed
+ ; with the SECTION keywords (only SECTION QUERY
+ ; is used to match queries). If the data cannot
+ ; be parsed, ADJUST rules for the answer packet
+ ; are ignored
+ HEX_ANSWER_END
+ ENTRY_END
+*/
+
+/* Example data file:
+$ORIGIN nlnetlabs.nl
+$TTL 3600
+
+ENTRY_BEGIN
+MATCH qname
+REPLY NOERROR
+ADJUST copy_id
+SECTION QUESTION
+www.nlnetlabs.nl. IN A
+SECTION ANSWER
+www.nlnetlabs.nl. IN A 195.169.215.155
+SECTION AUTHORITY
+nlnetlabs.nl. IN NS www.nlnetlabs.nl.
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH qname
+REPLY NOERROR
+ADJUST copy_id
+SECTION QUESTION
+www2.nlnetlabs.nl. IN A
+HEX_ANSWER_BEGIN
+; 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
+;-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+ 00 bf 81 80 00 01 00 01 00 02 00 02 03 77 77 77 0b 6b 61 6e ; 1- 20
+ 61 72 69 65 70 69 65 74 03 63 6f 6d 00 00 01 00 01 03 77 77 ; 21- 40
+ 77 0b 6b 61 6e 61 72 69 65 70 69 65 74 03 63 6f 6d 00 00 01 ; 41- 60
+ 00 01 00 01 50 8b 00 04 52 5e ed 32 0b 6b 61 6e 61 72 69 65 ; 61- 80
+ 70 69 65 74 03 63 6f 6d 00 00 02 00 01 00 01 50 8b 00 11 03 ; 81- 100
+ 6e 73 31 08 68 65 78 6f 6e 2d 69 73 02 6e 6c 00 0b 6b 61 6e ; 101- 120
+ 61 72 69 65 70 69 65 74 03 63 6f 6d 00 00 02 00 01 00 01 50 ; 121- 140
+ 8b 00 11 03 6e 73 32 08 68 65 78 6f 6e 2d 69 73 02 6e 6c 00 ; 141- 160
+ 03 6e 73 31 08 68 65 78 6f 6e 2d 69 73 02 6e 6c 00 00 01 00 ; 161- 180
+ 01 00 00 46 53 00 04 52 5e ed 02 03 6e 73 32 08 68 65 78 6f ; 181- 200
+ 6e 2d 69 73 02 6e 6c 00 00 01 00 01 00 00 46 53 00 04 d4 cc ; 201- 220
+ db 5b
+HEX_ANSWER_END
+ENTRY_END
+
+
+*/
+
+struct sockaddr_storage;
+#include "config.h"
+#include <ldns/ldns.h>
+#include "ldns-testpkts.h"
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_TIME_H
+#include <time.h>
+#endif
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_NETINET_UDP_H
+#include <netinet/udp.h>
+#endif
+#ifdef HAVE_NETINET_IGMP_H
+#include <netinet/igmp.h>
+#endif
+#include <errno.h>
+
+#define INBUF_SIZE 4096 /* max size for incoming queries */
+#define DEFAULT_PORT 53 /* default if no -p port is specified */
+#define CONN_BACKLOG 256 /* connections queued up for tcp */
+static const char* prog_name = "ldns-testns";
+static FILE* logfile = 0;
+static int do_verbose = 0;
+
+static void usage(void)
+{
+ printf("Usage: %s [options] <datafile>\n", prog_name);
+ printf(" -r listens on random port. Port number is printed.\n");
+ printf(" -p listens on the specified port, default %d.\n", DEFAULT_PORT);
+ printf(" -f forks given number extra instances, default none.\n");
+ printf(" -v more verbose, prints queries, answers and matching.\n");
+ printf(" -6 listen on IP6 any address, instead of IP4 any address.\n");
+ printf("The program answers queries with canned replies from the datafile.\n");
+ exit(EXIT_FAILURE);
+}
+
+static void log_msg(const char* msg, ...)
+{
+ va_list args;
+ va_start(args, msg);
+ vfprintf(logfile, msg, args);
+ fflush(logfile);
+ va_end(args);
+}
+
+static void error(const char* msg, ...)
+{
+ va_list args;
+ va_start(args, msg);
+ fprintf(logfile, "%s error: ", prog_name);
+ vfprintf(logfile, msg, args);
+ fprintf(logfile, "\n");
+ fflush(logfile);
+ va_end(args);
+ exit(EXIT_FAILURE);
+}
+
+void verbose(int lvl, const char* msg, ...) ATTR_FORMAT(printf, 2, 3);
+void verbose(int ATTR_UNUSED(lvl), const char* msg, ...)
+{
+ va_list args;
+ va_start(args, msg);
+ if(do_verbose)
+ vfprintf(logfile, msg, args);
+ fflush(logfile);
+ va_end(args);
+}
+
+static int bind_port(int sock, int port, int fam)
+{
+ struct sockaddr_in addr;
+#if defined(AF_INET6) && defined(HAVE_GETADDRINFO)
+ if(fam == AF_INET6) {
+ struct sockaddr_in6 addr6;
+ memset(&addr6, 0, sizeof(addr6));
+ addr6.sin6_family = AF_INET6;
+ addr6.sin6_port = (in_port_t)htons((uint16_t)port);
+# if HAVE_DECL_IN6ADDR_ANY
+ addr6.sin6_addr = in6addr_any;
+# else
+ memset(&addr6.sin6_addr, 0, sizeof(addr6.sin6_addr));
+# endif
+ return bind(sock, (struct sockaddr *)&addr6, (socklen_t) sizeof(addr6));
+ }
+#endif
+
+#ifndef S_SPLINT_S
+ addr.sin_family = AF_INET;
+#endif
+ addr.sin_port = (in_port_t)htons((uint16_t)port);
+ addr.sin_addr.s_addr = INADDR_ANY;
+ return bind(sock, (struct sockaddr *)&addr, (socklen_t) sizeof(addr));
+}
+
+struct handle_udp_userdata {
+ int udp_sock;
+ struct sockaddr_storage addr_him;
+ socklen_t hislen;
+};
+static void
+send_udp(uint8_t* buf, size_t len, void* data)
+{
+ struct handle_udp_userdata *userdata = (struct handle_udp_userdata*)data;
+ /* udp send reply */
+ ssize_t nb;
+ nb = sendto(userdata->udp_sock, (void*)buf, len, 0,
+ (struct sockaddr*)&userdata->addr_him, userdata->hislen);
+ if(nb == -1)
+ log_msg("sendto(): %s\n", strerror(errno));
+ else if((size_t)nb != len)
+ log_msg("sendto(): only sent %d of %d octets.\n",
+ (int)nb, (int)len);
+}
+
+static void
+handle_udp(int udp_sock, struct entry* entries, int *count)
+{
+ ssize_t nb;
+ uint8_t inbuf[INBUF_SIZE];
+ struct handle_udp_userdata userdata;
+ userdata.udp_sock = udp_sock;
+
+ userdata.hislen = (socklen_t)sizeof(userdata.addr_him);
+ /* udp recv */
+ nb = recvfrom(udp_sock, (void*)inbuf, INBUF_SIZE, 0,
+ (struct sockaddr*)&userdata.addr_him, &userdata.hislen);
+ if (nb < 1) {
+#ifndef USE_WINSOCK
+ log_msg("recvfrom(): %s\n", strerror(errno));
+#else
+ if(WSAGetLastError() != WSAEINPROGRESS &&
+ WSAGetLastError() != WSAECONNRESET &&
+ WSAGetLastError()!= WSAEWOULDBLOCK)
+ log_msg("recvfrom(): %d\n", WSAGetLastError());
+#endif
+ return;
+ }
+ handle_query(inbuf, nb, entries, count, transport_udp, send_udp,
+ &userdata, do_verbose?logfile:0);
+}
+
+static void
+read_n_bytes(int sock, uint8_t* buf, size_t sz)
+{
+ size_t count = 0;
+ while(count < sz) {
+ ssize_t nb = recv(sock, (void*)(buf+count), sz-count, 0);
+ if(nb < 0) {
+ log_msg("recv(): %s\n", strerror(errno));
+ return;
+ } else if(nb == 0) {
+ log_msg("recv: remote end closed the channel\n");
+ memset(buf+count, 0, sz-count);
+ return;
+ }
+ count += nb;
+ }
+}
+
+static void
+write_n_bytes(int sock, uint8_t* buf, size_t sz)
+{
+ size_t count = 0;
+ while(count < sz) {
+ ssize_t nb = send(sock, (void*)(buf+count), sz-count, 0);
+ if(nb < 0) {
+ log_msg("send(): %s\n", strerror(errno));
+ return;
+ }
+ count += nb;
+ }
+}
+
+struct handle_tcp_userdata {
+ int s;
+};
+static void
+send_tcp(uint8_t* buf, size_t len, void* data)
+{
+ struct handle_tcp_userdata *userdata = (struct handle_tcp_userdata*)data;
+ uint16_t tcplen;
+ /* tcp send reply */
+ tcplen = htons(len);
+ write_n_bytes(userdata->s, (uint8_t*)&tcplen, sizeof(tcplen));
+ write_n_bytes(userdata->s, buf, len);
+}
+
+static void
+handle_tcp(int tcp_sock, struct entry* entries, int *count)
+{
+ int s;
+ struct sockaddr_storage addr_him;
+ socklen_t hislen;
+ uint8_t inbuf[INBUF_SIZE];
+ uint16_t tcplen;
+ struct handle_tcp_userdata userdata;
+
+ /* accept */
+ hislen = (socklen_t)sizeof(addr_him);
+ if((s = accept(tcp_sock, (struct sockaddr*)&addr_him, &hislen)) < 0) {
+ log_msg("accept(): %s\n", strerror(errno));
+ return;
+ }
+ userdata.s = s;
+
+ /* tcp recv */
+ read_n_bytes(s, (uint8_t*)&tcplen, sizeof(tcplen));
+ tcplen = ntohs(tcplen);
+ if(tcplen >= INBUF_SIZE) {
+ log_msg("query %d bytes too large, buffer %d bytes.\n",
+ tcplen, INBUF_SIZE);
+#ifndef USE_WINSOCK
+ close(s);
+#else
+ closesocket(s);
+#endif
+ return;
+ }
+ read_n_bytes(s, inbuf, tcplen);
+
+ handle_query(inbuf, (ssize_t) tcplen, entries, count, transport_tcp,
+ send_tcp, &userdata, do_verbose?logfile:0);
+#ifndef USE_WINSOCK
+ close(s);
+#else
+ closesocket(s);
+#endif
+
+}
+
+/** shared by the service and main routine (forked and threaded) */
+static int udp_sock, tcp_sock;
+static struct entry* entries;
+
+/**
+ * Test DNS server service, uses global udpsock, tcpsock, reply entries
+ * The signature is kept void so the function can be used as a thread function.
+ */
+static void
+service(void)
+{
+ fd_set rset, wset, eset;
+ int count;
+ int maxfd;
+
+ /* service */
+ count = 0;
+ while (1) {
+#ifndef S_SPLINT_S
+ FD_ZERO(&rset);
+ FD_ZERO(&wset);
+ FD_ZERO(&eset);
+ FD_SET(udp_sock, &rset);
+ FD_SET(tcp_sock, &rset);
+#endif
+ maxfd = udp_sock;
+ if(tcp_sock > maxfd)
+ maxfd = tcp_sock;
+ if(select(maxfd+1, &rset, &wset, &eset, NULL) < 0) {
+ error("select(): %s\n", strerror(errno));
+ }
+ if(FD_ISSET(udp_sock, &rset)) {
+ handle_udp(udp_sock, entries, &count);
+ }
+ if(FD_ISSET(tcp_sock, &rset)) {
+ handle_tcp(tcp_sock, entries, &count);
+ }
+ }
+}
+
+static void
+forkit(int number)
+{
+ int i;
+ for(i=0; i<number; i++)
+ {
+#ifndef HAVE_FORK
+#ifndef USE_WINSOCK
+ log_msg("fork() not available.\n");
+ exit(1);
+#else /* USE_WINSOCK */
+ DWORD tid;
+ HANDLE id = CreateThread(NULL, 0,
+ (LPTHREAD_START_ROUTINE)service, NULL,
+ 0, &tid);
+ if(id == NULL) {
+ log_msg("error CreateThread: %d\n", GetLastError());
+ return;
+ }
+ log_msg("thread id: %d\n", (int)tid);
+#endif /* USE_WINSOCK */
+#else /* HAVE_FORK */
+ pid_t pid = fork();
+ if(pid == (pid_t) -1) {
+ log_msg("error forking: %s\n", strerror(errno));
+ return;
+ }
+ if(pid == 0)
+ return; /* child starts serving */
+ log_msg("forked pid: %d\n", (int)pid);
+#endif /* HAVE_FORK */
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ /* arguments */
+ int c;
+ int port = DEFAULT_PORT;
+ const char* datafile;
+ int forknum = 0;
+
+ /* network */
+ int fam = AF_INET;
+ bool random_port_success;
+
+#ifdef USE_WINSOCK
+ WSADATA wsa_data;
+#endif
+
+ /* parse arguments */
+ srandom(time(NULL) ^ getpid());
+ logfile = stdout;
+ prog_name = argv[0];
+ log_msg("%s: start\n", prog_name);
+ while((c = getopt(argc, argv, "6f:p:rv")) != -1) {
+ switch(c) {
+ case '6':
+#ifdef AF_INET6
+ fam = AF_INET6;
+#else
+ log_msg("cannot -6: no IP6 available\n");
+ exit(1);
+#endif
+ break;
+ case 'r':
+ port = 0;
+ break;
+ case 'f':
+ forknum = atoi(optarg);
+ if(forknum < 1)
+ error("invalid forkno %s, give number", optarg);
+ break;
+ case 'p':
+ port = atoi(optarg);
+ if (port < 1) {
+ error("Invalid port %s, use a number.", optarg);
+ }
+ break;
+ case 'v':
+ do_verbose++;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if(argc == 0 || argc > 1)
+ usage();
+
+ datafile = argv[0];
+ log_msg("Reading datafile %s\n", datafile);
+ entries = read_datafile(datafile, 0);
+
+#ifdef USE_WINSOCK
+ if(WSAStartup(MAKEWORD(2,2), &wsa_data) != 0)
+ error("WSAStartup failed\n");
+#endif
+
+ if((udp_sock = socket(fam, SOCK_DGRAM, 0)) < 0) {
+ error("udp socket(): %s\n", strerror(errno));
+ }
+ if((tcp_sock = socket(fam, SOCK_STREAM, 0)) < 0) {
+ error("tcp socket(): %s\n", strerror(errno));
+ }
+ c = 1;
+ if(setsockopt(tcp_sock, SOL_SOCKET, SO_REUSEADDR, (void*)&c, (socklen_t) sizeof(int)) < 0) {
+ error("setsockopt(SO_REUSEADDR): %s\n", strerror(errno));
+ }
+
+ /* bind ip4 */
+ if (port > 0) {
+ if (bind_port(udp_sock, port, fam)) {
+ error("cannot bind(): %s\n", strerror(errno));
+ }
+ if (bind_port(tcp_sock, port, fam)) {
+ error("cannot bind(): %s\n", strerror(errno));
+ }
+ if (listen(tcp_sock, CONN_BACKLOG) < 0) {
+ error("listen(): %s\n", strerror(errno));
+ }
+ } else {
+ random_port_success = false;
+ while (!random_port_success) {
+ port = (random() % 64510) + 1025;
+ log_msg("trying to bind to port %d\n", port);
+ random_port_success = true;
+ if (bind_port(udp_sock, port, fam)) {
+#ifdef EADDRINUSE
+ if (errno != EADDRINUSE) {
+#elif defined(USE_WINSOCK)
+ if (WSAGetLastError() != WSAEADDRINUSE) {
+#else
+ if (1) {
+#endif
+ perror("bind()");
+ return -1;
+ } else {
+ random_port_success = false;
+ }
+ }
+ if (random_port_success) {
+ if (bind_port(tcp_sock, port, fam)) {
+#ifdef EADDRINUSE
+ if (errno != EADDRINUSE) {
+#elif defined(USE_WINSOCK)
+ if (WSAGetLastError()!=WSAEADDRINUSE){
+#else
+ if (1) {
+#endif
+ perror("bind()");
+ return -1;
+ } else {
+ random_port_success = false;
+ }
+ }
+ }
+ if (random_port_success) {
+ if (listen(tcp_sock, CONN_BACKLOG) < 0) {
+ error("listen(): %s\n", strerror(errno));
+ }
+ }
+
+ }
+ }
+ log_msg("Listening on port %d\n", port);
+
+ /* forky! */
+ if(forknum > 0)
+ forkit(forknum);
+
+ service();
+
+ return 0;
+}