Skip to content

Commit

Permalink
Improve ndjson output
Browse files Browse the repository at this point in the history
  • Loading branch information
blechschmidt committed Mar 22, 2020
1 parent e5d5eb1 commit 6b74007
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 65 deletions.
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
# MassDNS 0.3 (experimental)
# MassDNS 0.3
## A high-performance DNS stub resolver

MassDNS is a simple high-performance DNS stub resolver targetting those who seek to resolve a massive amount of domain
names in the order of millions or even billions. Without special configuration, MassDNS is capable of resolving over
350,000 names per second using publicly available resolvers.

## Major changes
This version of MassDNS is currently experimental. In order to speed up the resolving process, the `ldns` dependency has
been replaced by a custom stack-based DNS implementation (which currently only supports the text representation of the
most common DNS records). Furthermore, epoll has been introduced in order to lighten CPU usage when operating with a low
concurrency which may have broken compatibility with some platforms. In case of bugs, please create an issue and
[switch to the more mature version 0.2](https://github.com/blechschmidt/massdns/tree/v0.2).
**The NDJSON output format has changed in order to provide more detailed information and allow better filtering.**

Also note that the command line interface has changed slightly due to criticism of the output complexity. Additionally,
the default values of the `-s` and `-i` parameters have been changed. The repository structure has been changed as well.
Expand Down
17 changes: 17 additions & 0 deletions dns.h
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,7 @@ ssize_t dns_str2namebuf(const char *name, uint8_t *buffer)
*lenptr = label_len;
if (total_len == 1)
{
total_len--;
break;
}
if (*name == 0)
Expand Down Expand Up @@ -1452,6 +1453,22 @@ char *dns_section2str(dns_section_t section)
return "UNKNOWN";
}

char *dns_section2str_lower_plural(dns_section_t section)
{
switch(section)
{
case DNS_SECTION_ANSWER:
return "answers";
case DNS_SECTION_ADDITIONAL:
return "additionals";
case DNS_SECTION_AUTHORITY:
return "authorities";
case DNS_SECTION_QUESTION:
return "questions";
}
return "unknowns";
}

bool dns_in_zone(dns_name_t *name, dns_name_t *zone)
{
return zone->length == 1 // Provided that the label is a FQDN, this is the root zone containing everything else
Expand Down
54 changes: 42 additions & 12 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -973,7 +973,7 @@ void do_read(uint8_t *offset, size_t len, struct sockaddr_storage *recvaddr)
static uint8_t *parse_offset;
static lookup_t *lookup;
static resolver_t* resolver;
static char json_buffer[0xFFFF];
static char json_buffer[5 * 0xFFFF];

context.stats.current_rate++;
context.stats.numreplies++;
Expand Down Expand Up @@ -1037,6 +1037,8 @@ void do_read(uint8_t *offset, size_t len, struct sockaddr_storage *recvaddr)
dns_record_t rec;
size_t non_add_count = packet.head.header.ans_count + packet.head.header.auth_count;
dns_section_t section = DNS_SECTION_ANSWER;
size_t section_index = 0;
bool section_emitted = false;

switch(context.cmd_args.output)
{
Expand All @@ -1056,23 +1058,51 @@ void do_read(uint8_t *offset, size_t len, struct sockaddr_storage *recvaddr)
break;

case OUTPUT_NDJSON: // Only print records from answer section that match the query name (in ndjson)

for(size_t rec_index = 0; dns_parse_record_raw(offset, next, offset + len, &next, &rec); rec_index++)
json_escape(json_buffer, sizeof(json_buffer), packet.head.question.name.name, packet.head.question.name.length);
fprintf(context.outfile,
"{\"name\":\"%s\",\"type\":\"%s\",\"class\":\"%s\",\"status\":\"%s\",\"data\":{",
json_buffer,
dns_record_type2str((dns_record_type) packet.head.question.type),
dns_class2str((dns_class) packet.head.question.class),
dns_rcode2str((dns_rcode) packet.head.header.rcode));
for(size_t rec_index = 0; dns_parse_record_raw(offset, next, offset + len, &next, &rec); rec_index++, section_index++)
{
fprintf(context.outfile,
"{\"query_name\":\"%s\",\"query_type\":\"%s\",",
dns_name2str(&packet.head.question.name),
dns_record_type2str((dns_record_type) packet.head.question.type));

json_escape(json_buffer, dns_raw_record_data2str(&rec, offset, offset + short_len), sizeof(json_buffer));
if(section == DNS_SECTION_ANSWER && section_index >= packet.head.header.ans_count) {
section_index = 0;
section++;
}
if(section == DNS_SECTION_AUTHORITY && section_index >= packet.head.header.auth_count) {
section_index = 0;
section++;
}
if(section == DNS_SECTION_ADDITIONAL && section_index >= packet.head.header.add_count) {
section_index = 0;
section++;
}
if(section_index == 0) {
fprintf(context.outfile, "%s\"%s\":[", section_emitted ? "]," : "",
dns_section2str_lower_plural(section));
}
else
{
fputs(",", context.outfile);
}
json_escape(json_buffer, sizeof(json_buffer), rec.name.name, rec.name.length);

fprintf(context.outfile,
"\"resp_name\":\"%s\",\"resp_type\":\"%s\",\"resp_ttl\":%" PRIu32 ",\"data\":\"%s\"}\n",
dns_name2str(&rec.name),
dns_record_type2str((dns_record_type) rec.type),
"{\"ttl\":%" PRIu32 ",\"type\":\"%s\",\"class\":\"%s\",\"name\":\"%s\",\"data\":\"",
rec.ttl,
dns_record_type2str((dns_record_type) rec.type),
dns_class2str((dns_class) rec.class),
json_buffer);
section_emitted = true;
json_escape_str(json_buffer, sizeof(json_buffer),
dns_raw_record_data2str(&rec, offset, offset + short_len));
fputs(json_buffer, context.outfile);
fprintf(context.outfile, "\"}");
}
fprintf(context.outfile, "%s},\"resolver\":\"%s\"}\n", section_emitted ? "]" : "",
sockaddr2str(recvaddr));

break;

Expand Down
105 changes: 58 additions & 47 deletions string.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,56 +112,67 @@ bool startswith(char* haystack, char* needle, bool case_sensitive) // Supports A
}
}

// Buffer needs to have at least one byte.
size_t json_escape(char *dst, const char *src, size_t dst_len)
{
#define require_space(N) if(dst_idx >= dst_len - (N)) goto json_escape_finalize;
const char complex_chars[] = "abtnvfr";
size_t dst_idx = 0;

for(size_t i = 0; src[i] != 0; i++)
{
size_t complex_idx = 0;
switch(src[i])
{
case '\\':
case '\"':
require_space(2);
dst[dst_idx++] = '\\';
dst[dst_idx++] = src[i];
break;
case '\r': complex_idx++;
case '\f': complex_idx++;
case '\v': complex_idx++;
case '\n': complex_idx++;
case '\t': complex_idx++;
case '\b': complex_idx++;
case '\a':
require_space(2);
dst[dst_idx++] = '\\';
dst[dst_idx++] = complex_chars[complex_idx];
break;
default:
if(isprint(src[i]))
{
require_space(1);
dst[dst_idx++] = src[i];
}
else
{
require_space(4);
dst[dst_idx++] = '\\';
dst[dst_idx++] = ((src[i] & 0300) >> 6) + '0';
dst[dst_idx++] = ((src[i] & 0070) >> 3) + '0';
dst[dst_idx++] = ((src[i] & 0007) >> 0) + '0';
}
break;
}
}
#undef require_space
json_escape_finalize:
dst[dst_idx++] = 0;
#define json_escape_body(BREAK_COND) \
const char complex_chars[] = "abtnvfr"; \
size_t dst_idx = 0; \
\
for(size_t i = 0; (BREAK_COND); i++) \
{ \
size_t complex_idx = 0; \
switch(src[i]) \
{ \
case '\\': \
case '\"': \
require_space(2); \
dst[dst_idx++] = '\\'; \
dst[dst_idx++] = src[i]; \
break; \
case '\r': complex_idx++; \
case '\f': complex_idx++; \
case '\v': complex_idx++; \
case '\n': complex_idx++; \
case '\t': complex_idx++; \
case '\b': complex_idx++; \
case '\a': \
require_space(2); \
dst[dst_idx++] = '\\'; \
dst[dst_idx++] = complex_chars[complex_idx]; \
break; \
default: \
if(isprint(src[i])) \
{ \
require_space(1); \
dst[dst_idx++] = src[i]; \
} \
else \
{ \
require_space(4); \
dst[dst_idx++] = '\\'; \
dst[dst_idx++] = (char)(((src[i] & 0300) >> 6) + '0'); \
dst[dst_idx++] = (char)(((src[i] & 0070) >> 3) + '0'); \
dst[dst_idx++] = (char)(((src[i] & 0007) >> 0) + '0'); \
} \
break; \
} \
} \
json_escape_finalize: \
dst[dst_idx++] = 0; \
return dst_idx;

size_t json_escape_str(char *dst, size_t dst_len, const char *src)
{
json_escape_body(src[i] != 0);
}

// Buffer needs to have at least one byte.
size_t json_escape(char *dst, size_t dst_len, const uint8_t *src, size_t src_len)
{
json_escape_body(i != src_len);
}

#undef require_space
#undef json_escape_body

#endif

0 comments on commit 6b74007

Please sign in to comment.