MassDNS: Achieving 350,000 DNS Queries Per Second with Zero-Copy C
Hook
Most DNS tools treat 1,000 queries per second as fast. MassDNS treats 350,000 queries per second as the baseline—all while using public resolvers and without specialized hardware.
Context
Subdomain enumeration and network reconnaissance have always been bottlenecked by DNS resolution speed. Traditional tools like nslookup or dig are synchronous—they wait for each query to complete before moving to the next. Even Python-based async alternatives rarely exceed a few thousand queries per second due to interpreter overhead and memory allocation patterns. When you're bruteforcing subdomains against a wordlist with millions of entries, or conducting security research across large IP ranges, this becomes prohibitively slow.
MassDNS emerged from this frustration. Written by Tobias Blechschmidt, it treats DNS resolution as a throughput problem rather than a convenience problem. Instead of wrapping existing DNS libraries, it implements a minimal DNS stub resolver in C with a singular focus: maximize queries per second by eliminating every unnecessary allocation, copy, and syscall. The result is a tool that can exhaustively bruteforce subdomains for Fortune 500 companies in hours instead of weeks, or enumerate PTR records for entire IPv4 netblocks in a single afternoon.
Technical Insight
The performance of MassDNS comes from three architectural decisions that most DNS tools avoid: a malloc-free hot path, epoll-based event loops with massive parallelism, and stateless query distribution across resolver pools.
At its core, MassDNS preallocates all memory during initialization. The query processing loop never calls malloc—instead, it uses fixed-size buffers and a custom hashmap to track in-flight queries. This eliminates allocator contention and keeps the CPU cache warm. Here's a simplified view of how queries flow through the system:
// Conceptual flow - actual implementation is optimized further
typedef struct {
char name[256];
uint16_t id;
uint16_t type;
struct timespec sent_time;
} query_t;
// Preallocated hashmap for tracking concurrent queries
query_t *queries[MAX_CONCURRENT]; // No runtime allocation
// Send loop: construct packet directly in stack buffer
void send_query(const char *domain, int resolver_sock) {
uint8_t packet[512]; // DNS max for UDP
size_t len = dns_construct_query(packet, domain, DNS_TYPE_A);
sendto(resolver_sock, packet, len, 0, &resolver_addr, sizeof(resolver_addr));
}
The second innovation is the event-driven architecture. MassDNS uses epoll (or kqueue on BSD systems) to monitor hundreds of UDP sockets simultaneously. Unlike traditional DNS clients that use one socket and wait for responses, MassDNS opens multiple sockets per resolver and fills them with thousands of concurrent queries. The epoll loop wakes only when responses arrive, immediately parsing them and freeing slots for new queries:
# Example: 10,000 concurrent queries across 50 resolvers
./bin/massdns -r resolvers.txt -t A -o S -w results.txt \
--hashmap-size 10000 \
domains.txt
Internally, this creates a sustained pressure system where the query hashmap stays nearly full. As soon as a response arrives (or times out), the slot is reused for the next domain from the input file. The busy-wait option (-b) can push this further by polling sockets instead of waiting for epoll events, trading CPU for latency.
The third piece is stateless resolver distribution. MassDNS doesn't maintain per-resolver state or track which resolvers are "good." It round-robins queries across all provided resolvers, relying on retry logic and timeout thresholds to handle failures. This simplicity means no lock contention and perfect CPU cache locality—the hot loop is just "read domain, construct packet, hash query ID, send to next resolver socket."
For output, MassDNS supports multiple formats optimized for different workflows. The simple text format (-o S) outputs one line per record, perfect for piping to other tools. JSON output (-o J) provides structured data with timing information. The binary format (-o B) skips parsing overhead entirely, dumping raw DNS response packets for post-processing. This flexibility makes it a building block rather than a monolithic tool:
# Chain with jq for filtering CNAME records only
./bin/massdns -r resolvers.txt -t A -o J domains.txt | \
jq -r 'select(.type == "CNAME") | .name + " -> " + .data'
The multi-process mode (-p flag) adds another dimension. Instead of one process managing all sockets, MassDNS can fork multiple workers that independently process chunks of the input file. Each worker maintains its own epoll loop and resolver sockets, scaling across CPU cores without shared state or locking. On modern servers, this pushes throughput past 500,000 queries per second when combined with fast resolvers.
Gotcha
MassDNS's performance comes at the cost of operational complexity and ethical hazard. The included resolver list is notoriously outdated—many resolvers are dead, rate-limited, or return garbage data. You'll need to curate your own list, test resolvers for reliability, and potentially run your own recursive resolvers to avoid abusing public infrastructure. Using public resolvers at MassDNS's scale will generate abuse complaints to your ISP and potentially land you in legal trouble depending on jurisdiction and use case.
The minimal DNS implementation also means limited protocol support. MassDNS handles A, AAAA, CNAME, PTR, TXT, and MX records well, but anything exotic (DNSSEC validation, EDNS0 extensions, TCP fallback) either doesn't work or requires manual handling. The tool is deliberately inflexible—it's optimized for the 95% use case of subdomain enumeration and PTR scanning, not for general DNS debugging or compliance-heavy environments. If your queries need TCP support for large responses, you'll need to post-process failures with a traditional DNS client.
Verdict
Use if: You're conducting authorized security research, network inventory, or academic studies that require millions of DNS queries and you have either private resolvers or explicit permission to load public infrastructure. The performance gain over alternatives is 10-100x, making previously impractical research feasible. Also use if you're building reconnaissance pipelines where MassDNS acts as the fast bulk resolver feeding into more sophisticated filtering and validation tools. Skip if: You need production-grade reliability, extensive DNS protocol support, or can't justify the ethical implications of large-scale public resolver abuse. For most subdomain enumeration, dnsx or puredns provide 90% of the speed with better maintained resolver lists and wildcard filtering. Skip if you're new to reconnaissance—MassDNS is a scalpel that requires understanding of DNS behavior, resolver dynamics, and responsible disclosure practices.