Commit 59c572f5 authored by Mark Andrews's avatar Mark Andrews
Browse files

add tcp support; add NS/A/AAAA lookup support; rate limit workload;

parent 0896396f
......@@ -9,18 +9,29 @@
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/uio.h>
#define ns_t_rrsig 46
#define ns_t_dnskey 48
int eof = 0;
static int eof = 0;
static int maxfd = -1;
static fd_set rfds, wfds;
static int outstanding;
static int maxoutstanding = 100;
static void(*rhandlers[FD_SETSIZE])(int);
static void(*whandlers[FD_SETSIZE])(int);
static int udp4 = -1;
static int udp6 = -1;
static int debug = 0;
static union res_sockaddr_union servers[10];
static int nservers = 0;
#define APPEND(list, item, link) do { \
if ((list).tail) \
......@@ -29,6 +40,7 @@ static int udp6 = -1;
(list).head = (item); \
(item)->link.prev = list.tail; \
(item)->link.next = NULL; \
(item)->link.linked = 1; \
(list).tail = (item); \
} while (0)
......@@ -42,60 +54,95 @@ static int udp6 = -1;
else \
list.head = (item)->link.next; \
(item)->link.next = (item)->link.prev = NULL; \
(item)->link.linked = 0; \
} while (0)
#define NEXT(item, link) (item)->link.next
#define PREV(item, link) (item)->link.next
#define LINKED(item, link) (item)->link.linked
#define HEAD(list) (list).head
#define TAIL(list) (list).tail
struct {
const char *name;
unsigned short rdlen;
const char *rdata;
unsigned short udpsize;
unsigned short flags;
unsigned short version;
unsigned int tcp;
unsigned int ignore;
unsigned short type;
} opts[] = {
{ "dns", 0, NULL, 0, 0, 0, 0, 0, ns_t_soa },
{ "edns", 0, "", 4096, 0, 0, 0, 0, ns_t_soa },
{ "edns1", 0, "" , 4096, 0, 1, 0, 0, ns_t_soa },
{ "edns@512", 0, "" , 512, 0, 0, 0, 1, ns_t_dnskey },
{ "ednsopt", 4, "\x00\x64\x00", 4096, 0, 0, 0, 0, ns_t_soa },
{ "edns1opt", 4, "\x00\x64\x00", 4096, 0, 1, 0, 0, ns_t_soa },
{ "do", 4, "\0\144\0", 4096, 0x8000, 0, 0, 0, ns_t_soa },
{ "ednsflags", 0, "", 4096, 0x80, 0, 0, 0, ns_t_soa },
{ "optlist", 4 + 8 + 4 + 12,
"\x00\x03\x00\x00" /* NSID */
"\x00\x08\x00\x04\x00\x01\x00\x00" /* ECS */
"\x00\x09\x00\x00" /* EXPIRE */
"\x00\x0a\x00\x08\x01\x02\x03\x04\x05\x06\x07\x08", /* COOKIE */
4096, 0, 0, 0, 0, ns_t_soa },
/*
{ "bind", 12, "\x00\x0a\x00\x08\x01\x02\x03\x04\x05\x06\x07\x08",
4096, 1, 0, 0, 0, ns_t_soa },
*/
{ "ednstcp", 0, "", 512, 1, 0, 1, 0, ns_t_dnskey }
};
struct summary {
char zone[1024];
char ns[1024];
struct sockaddr_storage storage;
int tests;
int done;
int type;
int nodataa;
int nodataaaa;
int nxdomain;
int nxdomaina;
int nxdomainaaaa;
int seenrrsig;
struct summary *xlink;
char results[sizeof(opts)/sizeof(opts[0])][100];
};
struct workitem {
struct {
struct workitem *next;
struct workitem *prev;
} link, idlink;
char zone[1024];
char ns[1024];
int linked;
} link, clink, rlink, idlink;
unsigned short id;
struct timeval when;
struct sockaddr_storage storage;
int type;
int test;
int sends;
int buflen;
int tcpfd;
int outstanding;
int havelen;
int readlen;
int read;
unsigned char buf[512];
unsigned char tcpbuf[0x10000];
char result[100];
struct summary *summary;
};
static struct {
struct workitem *head;
struct workitem *tail;
} work, ids[0x10000];
} work, connecting, reading, ids[0x10000];
struct {
const char *name;
unsigned short rdlen;
const char *rdata;
unsigned short udpsize;
unsigned short flags;
unsigned short version;
unsigned int tcp;
unsigned int ignore;
unsigned short type;
} opts[] = {
{ "dns", 0, NULL, 0, 0, 0, 0, 0, ns_t_soa },
{ "edns", 0, "", 4096, 0, 0, 0, 0, ns_t_soa },
{ "edns@512", 0, "" , 512, 0, 0, 0, 1, ns_t_dnskey },
{ "edns1", 0, "" , 4096, 0, 1, 0, 0, ns_t_soa },
{ "ednsopt", 4, "\0\144\0", 4096, 0, 0, 0, 0, ns_t_soa },
{ "edns1opt", 4, "\0\144\0", 4096, 0, 1, 0, 0, ns_t_soa },
{ "do", 4, "\0\144\0", 4096, 1, 0, 0, 0, ns_t_soa },
{ "ednsflags", 4, "", 4096, 0x80, 0, 0, 0, ns_t_soa },
{ "optlist", 4 + 8 + 4 + 12, "\0\3\0\0" "\0\10\0\4\0\1\0\0"
"\0\11\0\0" "\0\12\0\10\0\0\0\0\0\0\0\0", 4096, 0, 0, 0, 0,
ns_t_soa },
{ "ednstcp", 0, "", 512, 1, 0, 1, 0, ns_t_dnskey }
};
static void
connecttoserver(struct workitem *item);
static int
storage_equal(struct sockaddr_storage *s1, struct sockaddr_storage *s2) {
......@@ -132,18 +179,203 @@ checkid(struct sockaddr_storage *storage, int id) {
item = HEAD(ids[id]);
while (item != NULL &&
!storage_equal(storage, &item->storage))
!storage_equal(storage, &item->summary->storage))
item = NEXT(item, idlink);
return ((item == NULL) ? 1 : 0);
}
static void
report(struct summary *summary) {
struct sockaddr_in *s = (struct sockaddr_in *)&summary->storage;
struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)&summary->storage;;
char addrbuf[64];
void *addr;
unsigned int i;
int x;
summary->tests--;
if (summary->tests)
return;
if (summary->xlink) {
if (summary->nodataa)
summary->xlink->nodataa = 1;
if (summary->nodataaaa)
summary->xlink->nodataaaa = 1;
if (summary->nxdomaina)
summary->xlink->nxdomaina = 1;
if (summary->nxdomainaaaa)
summary->xlink->nxdomainaaaa = 1;
summary->xlink->xlink = NULL;
summary->xlink = NULL;
summary->done = 1;
}
if ((summary->type == ns_t_a || summary->type == ns_t_aaaa) &&
summary->nodataa && summary->nodataaaa) {
printf("%s. %s: no address records found\n",
summary->zone, summary->ns);
free(summary);
return;
}
if ((summary->type == ns_t_a || summary->type == ns_t_aaaa) &&
summary->nxdomaina && summary->nxdomainaaaa) {
printf("%s. %s: no address records found (NXDOMAIN)\n",
summary->zone, summary->ns);
free(summary);
return;
}
if (summary->done) {
free(summary);
return;
}
if (summary->storage.ss_family == AF_INET)
addr = &s->sin_addr;
else
addr = &s6->sin6_addr;
inet_ntop(summary->storage.ss_family, addr, addrbuf, sizeof(addrbuf));
if (summary->type != 0 && summary->nxdomain) {
if (summary->type == ns_t_ns) {
printf("%s.: NS nxdomain\n", summary->zone);
free(summary);
return;
}
printf("%s. %s:", summary->zone, summary->ns);
if (summary->type == ns_t_a) printf(" A");
if (summary->type == ns_t_aaaa) printf(" AAAA");
printf(" nxdomain\n");
free(summary);
return;
}
if (summary->type == ns_t_a) {
printf("%s. %s:", summary->zone, summary->ns);
printf(" A lookup failed\n");
free(summary);
return;
}
if (summary->type == ns_t_aaaa) {
printf("%s. %s:", summary->zone, summary->ns);
printf(" AAAA lookup failed\n");
free(summary);
return;
}
if (summary->type == ns_t_ns) {
printf("%s. %s:", summary->zone, summary->ns);
printf(" NS lookup failed\n");
free(summary);
return;
}
x = -1;
printf("%s. @%s (%s.):", summary->zone, addrbuf, summary->ns);
for (i = 0; i < sizeof(opts)/sizeof(opts[0]); i++) {
if (summary->results[i][0] == 0)
strcpy(summary->results[i], "skipped");
if (strcmp(opts[i].name, "do") == 0)
x = i;
if (strcmp(opts[i].name, "ednstcp") == 0 && x != -1) {
printf(" signed=%s", summary->results[x]);
if (summary->seenrrsig)
printf(",yes");
}
printf(" %s=%s", opts[i].name, summary->results[i]);
}
printf("\n");
free(summary);
}
static void
freeitem(struct workitem * item) {
report(item->summary);
if (item->tcpfd != 0) {
FD_CLR(item->tcpfd, &rfds);
FD_CLR(item->tcpfd, &wfds);
rhandlers[item->tcpfd] = NULL;
whandlers[item->tcpfd] = NULL;
close(item->tcpfd);
}
if (item->outstanding)
outstanding--;
if (LINKED(item, link))
UNLINK(work, item, link);
if (LINKED(item, rlink))
UNLINK(reading, item, rlink);
if (LINKED(item, clink))
UNLINK(connecting, item, clink);
if (LINKED(item, idlink))
UNLINK(ids[item->id], item, idlink);
free(item);
}
static void
addtag(struct workitem *item, char *tag) {
char *result = item->summary->results[item->test];
int i = strlen(result);
if (i)
result[i++] = ',';
strcpy(result + i, tag);
}
static void
resend(struct workitem *item) {
unsigned int ttl;
int n, fd = -1;
switch (item->summary->storage.ss_family) {
case AF_INET:
fd = udp4;
break;
case AF_INET6:
fd = udp6;
break;
}
if (fd == -1) {
addtag(item, "skipped");
freeitem(item);
return;
}
if (outstanding > maxoutstanding) {
gettimeofday(&item->when, NULL);
item->when.tv_sec += 1;
UNLINK(work, item, link);
APPEND(work, item, link);
return;
}
n = sendto(fd, item->buf, item->buflen, 0,
(struct sockaddr *)&item->summary->storage,
item->summary->storage.ss_len);
if (n > 0) {
if (debug)
printf("resend %s rdlen=%u udpsize=%u flags=%04x "
"version=%u tcp=%u ignore=%u id=%u\n",
opts[item->test].name, opts[item->test].rdlen,
opts[item->test].udpsize, opts[item->test].flags,
opts[item->test].version, opts[item->test].tcp,
opts[item->test].ignore, item->id);
if (!item->outstanding++)
outstanding++;
gettimeofday(&item->when, NULL);
item->when.tv_sec += 1;
item->sends++;
UNLINK(work, item, link);
APPEND(work, item, link);
} else {
addtag(item, "failed");
freeitem(item);
}
}
static void
dotest(struct workitem *item) {
unsigned char *cp;
unsigned int ttl;
int n, fd, id, tries = 0;
switch (item->storage.ss_family) {
switch (item->summary->storage.ss_family) {
case AF_INET:
fd = udp4;
break;
......@@ -153,33 +385,51 @@ dotest(struct workitem *item) {
}
if (fd == -1) {
free(item);
addtag(item, "skipped");
freeitem(item);
return;
}
n = res_mkquery(ns_o_query, item->zone, ns_c_in, opts[item->test].type,
NULL, 0, NULL, item->buf, sizeof(item->buf));
if (n > 0 && opts[item->test].rdata != NULL) {
n = res_mkquery(ns_o_query, item->summary->zone, ns_c_in,
opts[item->test].type, NULL, 0, NULL,
item->buf, sizeof(item->buf));
item->buf[2] &= ~0x1; /* clear rd */
if (n > 0) {
char name[1024];
dn_expand(item->buf, item->buf + n, item->buf + 12,
name, sizeof(name));
strcpy(item->summary->zone, name);
}
if (n > 0 && opts[item->test].udpsize > 0) {
cp = item->buf + n;
ns_put16(ns_t_opt, cp);
ns_put16(opts[item->test].udpsize, cp);
ttl = (opts[item->test].version << 16) | opts[item->test].flags;
ns_put32(ttl, cp);
ns_put16(opts[item->test].rdlen, cp);
*cp++ = 0; /* name */
ns_put16(ns_t_opt, cp); /* type */
cp += 2;
ns_put16(opts[item->test].udpsize, cp); /* class */
cp += 2;
ttl = (opts[item->test].version << 16) |
opts[item->test].flags;
ns_put32(ttl, cp); /* ttl */
cp += 4;
ns_put16(opts[item->test].rdlen, cp); /* rdlen */
cp += 2;
memcpy(cp, opts[item->test].rdata, opts[item->test].rdlen);
cp += opts[item->test].rdlen;
n = cp - item->buf;
item->buf[11] = 1; /* adcount */
n = cp - item->buf; /* total length */
}
if (n > 0) {
id = item->buf[0] << 8 | item->buf[1];
while (!checkid(&item->storage, id) && tries++ < 0xffff)
while (!checkid(&item->summary->storage, id) &&
tries++ < 0xffff)
id = (id + 1) & 0xffff;
if (tries == 0xffff) {
free(item);
addtag(item, "skipped");
freeitem(item);
return;
}
......@@ -188,24 +438,43 @@ dotest(struct workitem *item) {
item->id = id;
item->buflen = n;
if (opts[item->test].tcp) {
connecttoserver(item);
return;
}
if (outstanding > maxoutstanding) {
gettimeofday(&item->when, NULL);
item->when.tv_sec += 1;
APPEND(work, item, link);
APPEND(ids[item->id], item, idlink);
return;
}
n = sendto(fd, item->buf, item->buflen, 0,
(struct sockaddr *)&item->storage,
item->storage.ss_len);
(struct sockaddr *)&item->summary->storage,
item->summary->storage.ss_len);
}
if (n > 0) {
printf("%s rdlen=%u udpsize=%u flags=%04x version=%u "
"tcp=%u ignore=%u id=%u\n",
opts[item->test].name, opts[item->test].rdlen,
opts[item->test].udpsize, opts[item->test].flags,
opts[item->test].version, opts[item->test].tcp,
opts[item->test].ignore, item->id);
if (debug)
printf("%s rdlen=%u udpsize=%u flags=%04x version=%u "
"tcp=%u ignore=%u id=%u\n",
opts[item->test].name, opts[item->test].rdlen,
opts[item->test].udpsize, opts[item->test].flags,
opts[item->test].version, opts[item->test].tcp,
opts[item->test].ignore, item->id);
if (!item->outstanding++)
outstanding++;
gettimeofday(&item->when, NULL);
item->when.tv_sec += 1;
item->sends = 1;
APPEND(work, item, link);
APPEND(ids[item->id], item, idlink);
} else
free(item);
} else {
addtag(item, "failed");
freeitem(item);
}
}
static void
......@@ -215,6 +484,7 @@ check(char *zone, char *ns, char *address) {
struct in_addr addr;
struct in6_addr addr6;
struct sockaddr_storage storage;
struct summary *summary;
memset(&storage, 0, sizeof(storage));
if (inet_pton(AF_INET6, address, &addr6) == 1) {
......@@ -237,29 +507,688 @@ check(char *zone, char *ns, char *address) {
if (fd == -1)
return;
summary = calloc(1, sizeof(*summary));
if (summary == NULL)
return;
summary->storage = storage;
ns_makecanon(zone, summary->zone, sizeof(summary->zone));
i = strlen(summary->zone);
if (i) summary->zone[i-1] = 0;
ns_makecanon(ns, summary->ns, sizeof(summary->ns));
i = strlen(summary->ns);
if (i) summary->ns[i-1] = 0;
for (i = 0; i < sizeof(opts)/sizeof(opts[0]); i++) {
struct workitem *item = calloc(1, sizeof(*item));
if (item == NULL)
if (item == NULL) {
summary->tests++;
report(summary);
break;
strcpy(item->zone, zone);
strcpy(item->ns, ns);
item->storage = storage;
}
summary->tests++;
item->summary = summary;
item->test = i;
dotest(item);
}
}
static char *
rcodetext(int code) {
static char buf[64];
switch(code) {
case 0: return("noerror");
case 1: return("formerr");
case 2: return("servfail");
case 3: return("nxdomain");
case 4: return("notimpl");
case 5: return("refused");
case 6: return("yxdomain");
case 7: return("yxrrset");
case 8: return("nxrrset");
case 9: return("notauth");
case 10: return("notzone");
case 16: return("badvers");
case 23: return("badcookie");
default:
snprintf(buf, sizeof(buf), "?%u", code);
return (buf);
}
}
static void
dolookup(struct workitem *item, int type) {
char name[1024];
int n, fd = -1;
item->summary->tests++;
item->summary->type = item->type = type;
if (servers[item->test].sin.sin_family == AF_INET)
memcpy(&item->summary->storage, &servers[item->test].sin,
sizeof(servers[item->test].sin));
else
memcpy(&item->summary->storage, &servers[0].sin6,
sizeof(servers[item->test].sin6));
switch (item->summary->storage.ss_family) {
case AF_INET:
fd = udp4;
break;
case AF_INET6:
fd = udp6;
break;
}
if (fd == -1) {
addtag(item, "skipped");
freeitem(item);
return;
}
if (type == ns_t_ns)
n = res_mkquery(ns_o_query, item->summary->zone, ns_c_in,
type, NULL, 0, NULL,
item->buf, sizeof(item->buf));
else
n = res_mkquery(ns_o_query, item->summary->ns, ns_c_in,
type, NULL, 0, NULL,
item->buf, sizeof(item->buf));
if (n > 0) {
int id = item->buf[0] << 8 | item->buf[1];
int tries = 0;
dn_expand(item->buf, item->buf + n, item->buf + 12,
name, sizeof(name));
if (type == ns_t_ns)
strcpy(item->summary->zone, name);
else
strcpy(item->summary->ns, name);
while (!checkid(&item->summary->storage, id) &&
tries++ < 0xffff)
id = (id + 1) & 0xffff;
if (tries == 0xffff) {
addtag(item, "skipped");
freeitem(item);
return;
}
item->buf[0] = id >> 8;
item->buf[1] = id & 0xff;
item->id = id;
item->buflen = n;
if (outstanding > maxoutstanding) {
gettimeofday(&item->when, NULL);
item->when.tv_sec += 1;
APPEND(work, item, link);