1
0
mirror of https://github.com/jedisct1/dnsblast.git synced 2024-10-05 18:30:54 +01:00

Initial import

This commit is contained in:
Frank Denis 2012-05-05 16:41:52 -07:00
parent 4ef61ea9f9
commit e26b3863b7
8 changed files with 633 additions and 15 deletions

17
.gitignore vendored
View File

@ -1,12 +1,7 @@
# Compiled Object files
*.slo
*.lo
*.dSYM
*.log
*.o
# Compiled Dynamic libraries
*.so
# Compiled Static libraries
*.lai
*.la
*.a
*.s
*~
.DS_Store
dnsblast

15
COPYING Normal file
View File

@ -0,0 +1,15 @@
/*
* Copyright (c) 2012 Frank Denis <j at pureftpd dot org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

21
Makefile Normal file
View File

@ -0,0 +1,21 @@
OPTIMIZATION ?= -O2
STDFLAGS ?= -std=c99
DEBUGFLAGS ?= -Waggregate-return -Wcast-align -Wcast-qual \
-Wchar-subscripts -Wcomment -Wimplicit -Wmissing-declarations \
-Wmissing-prototypes -Wnested-externs -Wparentheses -Wwrite-strings \
-Wformat=2 -Wall -Wextra
CFLAGS ?= $(OPTIMIZATION) $(STDFLAGS) $(DEBUGFLAGS)
all: dnsblast
dnsblast: Makefile dnsblast.o
$(CC) dnsblast.o -o dnsblast $(LDFLAGS)
dnsblast.o: Makefile dnsblast.c dns.h dnsblast.h
$(CC) -c dnsblast.c -o dnsblast.o $(CFLAGS)
clean:
rm -f dnsblast *.a *.d *.o
rm -rf *.dSYM

105
README.markdown Normal file
View File

@ -0,0 +1,105 @@
DNSBlast
========
`dnsblast` is a simple and really stupid load testing tool for DNS resolvers.
Give it the IP address of a resolver, the total number of queries you
want to send, the rate (number of packets per second), and `dnsblast`
will tell you how well the resolver is able to keep up.
What it is:
-----------
- a tool to spot bugs in DNS resolvers.
- a tool to help you tune and tweak DNS resolver code in order to
improve it in some way.
- a tool to help you tune and tweak the operating system so that it
can properly cope with a slew of UDP packets.
- a tool to test a resolver with real queries sent to the real and
scary interwebz, not to a sandbox.
What it is not:
---------------
- a tool for DoS'ing resolvers. There are way more efficient ways to
achieve this.
- a benchmarking tool.
- a tool for testing anything but how the server behaves under load.
If you need a serious test suite, take a look at what Unbound
provides.
What it does:
-------------
It sends queries for names like
`<random char><random char><random char><random char>.com`.
Yes, that's 4 random characters dot com. Doing that achieves a
NXDOMAIN vs "oh cool, we got a reply" ratio that is surprisingly close
to the one you get from real queries made by real users.
Different query types are sent. Namely SOA, A, AAA, MX and TXT, and
the probability that a query type gets picked is also close to its
probability in the real world.
Names are occasionally repeated, also to get closer to what happens in
the real world. That triggers resolver code responsible for queuing
and merging queries.
The test is deterministic: the exact same sequence of packets is sent
every time you fire up `dnsblast`. The magic resides in the power of
the `rand()` function with a fixed seed.
What it does not:
-----------------
It doesn't support DNSSec, it doesn't send anything using TCP, it
doesn't pay attention to the content the resolver sents.
Fuzzing:
--------
In addition, `dnsblast` can send malformed queries.
Most resolvers just ignore these, so don't expect a high
replies/queries ratio. But this feature can also help spotting bugs.
The fuzzer is really, really, really simple, though. It just changes
some random bytes. It doesn't even pay attention to the server's
behavior.
How do I compile it?
--------------------
Type: `make`.
The code it trivial and should be fairly portable, although it only
gets tested on OSX and OpenBSD.
How do I use it?
----------------
To send a shitload of queries to 127.0.0.1:
dnsblast 127.0.0.1
To send 50,000 queries to 127.0.0.1:
dnsblast 127.0.0.1 50000
To send 50,000 queries at a rate of 100 queries per second:
dnsblast 127.0.0.1 50000 100
To send 50,000 queries at a rate of 100 qps to a non standard-port, like 5353:
dnsblast 127.0.0.1 50000 100 5353
To send malformed packets, prepend "fuzz":
dnsblast fuzz 127.0.0.1
dnsblast fuzz 127.0.0.1 50000
dnsblast fuzz 127.0.0.1 50000 100
dnsblast fuzz 127.0.0.1 50000 100 5353
If you think that it desperately cries for `getopt()`, you're absolutely correct.

View File

@ -1,4 +0,0 @@
dnsblast
========
A simple and stupid load testing tool for DNS resolvers

33
dns.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef __DNS_H__
#define __DNS_H__
#include <sys/types.h>
#include <inttypes.h>
#define TYPE_A 1U
#define TYPE_SOA 6U
#define TYPE_MX 15U
#define TYPE_TXT 16U
#define TYPE_AAAA 28U
#define FLAGS_OPCODE_QUERY 0x0
#define FLAGS_RECURSION_DESIRED 0x100
#define CLASS_IN 1U
typedef struct {
uint16_t id;
uint16_t flags;
uint16_t qdcount;
uint16_t ancount;
uint16_t nscount;
uint16_t arcount;
} __attribute__((__packed__)) DNS_Header;
#define PUT_HTONS(dst, val) do { \
*dst++ = val >> 8; \
*dst++ = val & 0xff; \
} while (0)
#endif

379
dnsblast.c Normal file
View File

@ -0,0 +1,379 @@
#include "dnsblast.h"
static unsigned long long
get_nanoseconds(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000000000LL + tv.tv_usec * 1000LL;
}
static int
init_context(Context * const context, const int sock,
const struct addrinfo * const ai, const _Bool fuzz)
{
const unsigned long long now = get_nanoseconds();
*context = (Context) {
.received_packets = 0UL, .sent_packets = 0UL,
.last_status_update = now, .startup_date = now,
.sock = sock, .ai = ai, .fuzz = fuzz, .sending = 1
};
DNS_Header * const question_header = (DNS_Header *) context->question;
*question_header = (DNS_Header) {
.flags = htons(FLAGS_OPCODE_QUERY | FLAGS_RECURSION_DESIRED),
.qdcount = htons(1U), .ancount = 0U, .nscount = 0U, .arcount = 0U
};
return 0;
}
static int
find_name_component_len(const char *name)
{
int name_pos = 0;
while (name[name_pos] != '.' && name[name_pos] != 0) {
if (name_pos >= UCHAR_MAX) {
return EOF;
}
name_pos++;
}
return name_pos;
}
static int
encode_name(unsigned char ** const encoded_ptr, size_t encoded_size,
const char * const name)
{
unsigned char *encoded = *encoded_ptr;
const char *name_current = name;
int name_current_pos;
assert(encoded_size > (size_t) 0U);
encoded_size--;
for (;;) {
name_current_pos = find_name_component_len(name);
if (name_current_pos == EOF ||
encoded_size <= (size_t) name_current_pos) {
return -1;
}
*encoded++ = (unsigned char) name_current_pos;
memcpy(encoded, name_current, name_current_pos);
encoded_size -= name_current_pos - (size_t) 1U;
encoded += name_current_pos;
if (name_current[name_current_pos] == 0) {
break;
}
name_current += name_current_pos + 1U;
}
*encoded++ = 0;
*encoded_ptr = encoded;
return 0;
}
static int
fuzz(unsigned char * const question, const size_t packet_size)
{
int p = REFUZZ_PROBABILITY;
do {
question[rand() % packet_size] = rand() % 0xff;
} while (rand() < p && (p = p / 2) > 0);
return 0;
}
static int
blast(Context * const context, const char * const name, const uint16_t type)
{
unsigned char * const question = context->question;
DNS_Header * const question_header = (DNS_Header *) question;
unsigned char * const question_data = question + sizeof *question_header;
const size_t sizeof_question_data =
sizeof question - sizeof *question_header;
question_header->id = context->id++;
unsigned char *msg = question_data;
assert(sizeof_question_data > (size_t) 2U);
encode_name(&msg, sizeof_question_data - (size_t) 2U, name);
PUT_HTONS(msg, type);
PUT_HTONS(msg, CLASS_IN);
const size_t packet_size = (size_t) (msg - question);
if (context->fuzz != 0) {
fuzz(question, packet_size);
}
while (sendto(context->sock, question, packet_size, 0,
context->ai->ai_addr, context->ai->ai_addrlen)
!= (ssize_t) packet_size) {
if (errno != EAGAIN && errno != EINTR) {
perror("sendto");
exit(EXIT_FAILURE);
}
}
context->sent_packets++;
return 0;
}
static void
usage(void) {
puts("\nUsage: dnsblast [fuzz] <host> [<count>] [<pps>] [<port>]\n");
exit(EXIT_SUCCESS);
}
static struct addrinfo *
resolve(const char * const host, const char * const port)
{
struct addrinfo *ai, hints;
memset(&hints, 0, sizeof hints);
hints = (struct addrinfo) {
.ai_family = AF_UNSPEC, .ai_flags = 0, .ai_socktype = SOCK_DGRAM,
.ai_protocol = IPPROTO_UDP
};
const int gai_err = getaddrinfo(host, port, &hints, &ai);
if (gai_err != 0) {
fprintf(stderr, "[%s:%s]: [%s]\n", host, port, gai_strerror(gai_err));
exit(EXIT_FAILURE);
}
return ai;
}
static int
get_random_name(char * const name, size_t name_size)
{
const char charset_alnum[36] = "abcdefghijklmnopqrstuvwxyz0123456789";
assert(name_size > (size_t) 8U);
const int r1 = rand(), r2 = rand();
name[0] = charset_alnum[(r1) % sizeof charset_alnum];
name[1] = charset_alnum[(r1 >> 16) % sizeof charset_alnum];
name[2] = charset_alnum[(r2) % sizeof charset_alnum];
name[3] = charset_alnum[(r2 >> 16) % sizeof charset_alnum];
name[4] = '.'; name[5] = 'c'; name[6] = 'o'; name[7] = 'm';
name[8] = 0;
return 0;
}
static uint16_t
get_random_type(void)
{
const size_t weighted_types_len =
sizeof weighted_types / sizeof weighted_types[0];
size_t i = 0U;
const int rnd = rand();
int pos = RAND_MAX;
do {
pos -= weighted_types[i].weight;
if (rnd > pos) {
return weighted_types[i].type;
}
} while (++i < weighted_types_len);
return weighted_types[rand() % weighted_types_len].type;
}
static int
get_sock(const char * const host, const char * const port,
struct addrinfo ** const ai_ref)
{
int flag = 1;
int sock;
*ai_ref = resolve(host, port);
sock = socket((*ai_ref)->ai_family, (*ai_ref)->ai_socktype,
(*ai_ref)->ai_protocol);
if (sock == -1) {
return -1;
}
setsockopt(sock, SOL_SOCKET, SO_RCVBUFFORCE,
&(int[]) { MAX_UDP_BUFFER_SIZE }, sizeof (int));
setsockopt(sock, SOL_SOCKET, SO_SNDBUFFORCE,
&(int[]) { MAX_UDP_BUFFER_SIZE }, sizeof (int));
#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
setsockopt(sock, IPPROTO_IP, IP_MTU_DISCOVER,
&(int[]) { IP_PMTUDISC_DONT }, sizeof (int));
#elif defined(IP_DONTFRAG)
setsockopt(sock, IPPROTO_IP, IP_DONTFRAG, &(int[]) { 0 }, sizeof (int));
#endif
assert(ioctl(sock, FIONBIO, &flag) == 0);
return sock;
}
static int
receive(Context * const context)
{
unsigned char buf[MAX_UDP_DATA_SIZE];
while (recv(context->sock, buf, sizeof buf, 0) == (ssize_t) -1) {
if (errno == EAGAIN) {
return 1;
}
assert(errno == EINTR);
}
context->received_packets++;
return 0;
}
static int
update_status(const Context * const context)
{
const unsigned long long now = get_nanoseconds();
const unsigned long long elapsed = now - context->startup_date;
unsigned long long rate =
context->received_packets * 1000000000ULL / elapsed;
if (rate > context->pps) {
rate = context->pps;
}
printf("Sent: [%lu] - Received: [%lu] - Reply rate: [%llu pps] - "
"Ratio: [%.2f%%] \r",
context->sent_packets, context->received_packets, rate,
(double) context->received_packets * 100.0 /
(double) context->sent_packets);
fflush(stdout);
return 0;
}
static int
periodically_update_status(Context * const context)
{
unsigned long long now = get_nanoseconds();
if (now - context->last_status_update < UPDATE_STATUS_PERIOD) {
return 1;
}
update_status(context);
context->last_status_update = now;
return 0;
}
static int
empty_receive_queue(Context * const context)
{
while (receive(context) == 0)
;
periodically_update_status(context);
return 0;
}
static int
throttled_receive(Context * const context)
{
unsigned long long now = get_nanoseconds(), now2;
const unsigned long long elapsed = now - context->startup_date;
const unsigned long long max_packets =
context->pps * elapsed / 1000000000UL;
if (context->sending == 1 && context->sent_packets <= max_packets) {
empty_receive_queue(context);
}
const unsigned long long excess = context->sent_packets - max_packets;
const unsigned long long time_to_wait = excess / context->pps;
int remaining_time = (int) (time_to_wait * 1000ULL);
int ret;
struct pollfd pfd = { .fd = context->sock,
.events = POLLIN | POLLERR };
if (context->sending == 0) {
remaining_time = -1;
} else if (remaining_time < 0) {
remaining_time = 0;
}
do {
ret = poll(&pfd, (nfds_t) 1, remaining_time);
if (ret == 0) {
periodically_update_status(context);
return 0;
}
if (ret == -1) {
if (errno != EAGAIN && errno != EINTR) {
perror("poll");
exit(EXIT_FAILURE);
}
continue;
}
assert(ret == 1);
empty_receive_queue(context);
now2 = get_nanoseconds();
remaining_time -= (now2 - now) / 1000;
now = now2;
} while (remaining_time > 0);
return 0;
}
int
main(int argc, char *argv[])
{
char name[100U] = ".";
Context context;
struct addrinfo *ai;
const char *host;
const char *port = "domain";
unsigned long pps = ULONG_MAX;
unsigned long send_count = ULONG_MAX;
int sock;
uint16_t type;
_Bool fuzz = 0;
if (argc < 2 || argc > 6) {
usage();
}
if (strcasecmp(argv[1], "fuzz") == 0) {
fuzz = 1;
argv++;
argc--;
}
if (argc < 1) {
usage();
}
host = argv[1];
if (argc > 2) {
send_count = strtoul(argv[2], NULL, 10);
}
if (argc > 3) {
pps = strtoul(argv[3], NULL, 10);
}
if (argc > 4) {
port = argv[4];
}
if ((sock = get_sock(host, port, &ai)) == -1) {
perror("Oops");
exit(EXIT_FAILURE);
}
init_context(&context, sock, ai, fuzz);
context.pps = pps;
srand(0U);
assert(send_count > 0UL);
do {
if (rand() > REPEATED_NAME_PROBABILITY) {
get_random_name(name, sizeof name);
}
type = get_random_type();
blast(&context, name, type);
throttled_receive(&context);
} while (--send_count > 0UL);
update_status(&context);
context.sending = 0;
while (context.sent_packets != context.received_packets) {
throttled_receive(&context);
}
freeaddrinfo(ai);
assert(close(sock) == 0);
update_status(&context);
putchar('\n');
return 0;
}

74
dnsblast.h Normal file
View File

@ -0,0 +1,74 @@
#ifndef __DNSBLAST_H__
#define __DNSBLAST_H__ 1
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netdb.h>
#include <netinet/in.h>
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "dns.h"
#define MAX_UDP_DATA_SIZE (0xffff - 20U - 8U)
#ifndef UPDATE_STATUS_PERIOD
# define UPDATE_STATUS_PERIOD 500000000ULL
#endif
#ifndef MAX_UDP_BUFFER_SIZE
# define MAX_UDP_BUFFER_SIZE 2097152
#endif
#define REPEATED_NAME_PROBABILITY (int) ((RAND_MAX * 13854LL) / 100000LL)
#define REFUZZ_PROBABILITY (int) ((RAND_MAX * 500LL) / 100000LL)
typedef struct Context_ {
unsigned char question[MAX_UDP_DATA_SIZE];
const struct addrinfo *ai;
unsigned long long last_status_update;
unsigned long long startup_date;
unsigned long pps;
unsigned long received_packets;
unsigned long sent_packets;
int sock;
uint16_t id;
_Bool fuzz;
_Bool sending;
} Context;
typedef struct WeightedType_ {
int weight;
uint16_t type;
} WeightedType;
const WeightedType weighted_types[] = {
{ .type = TYPE_A, .weight = (int) ((RAND_MAX * 77662LL) / 100000LL) },
{ .type = TYPE_SOA, .weight = (int) ((RAND_MAX * 803LL) / 100000LL) },
{ .type = TYPE_MX, .weight = (int) ((RAND_MAX * 5073LL) / 100000LL) },
{ .type = TYPE_TXT, .weight = (int) ((RAND_MAX * 2604LL) / 100000LL) },
{ .type = TYPE_AAAA, .weight = (int) ((RAND_MAX * 13858LL) / 100000LL) }
};
#ifndef SO_RCVBUFFORCE
# define SO_RCVBUFFORCE SO_RCVBUF
#endif
#ifndef SO_SNDBUFFORCE
# define SO_SNDBUFFORCE SO_SNDBUF
#endif
#endif