Back to Articles

hakrevdns: Why Bulk Reverse DNS Lookups Need a Purpose-Built Tool

[ View on GitHub ]

hakrevdns: Why Bulk Reverse DNS Lookups Need a Purpose-Built Tool

Hook

A single reverse DNS lookup takes milliseconds. Performing a million of them can take days—unless you understand how to parallelize DNS queries without getting throttled by resolvers.

Context

Reverse DNS lookups—mapping IP addresses back to hostnames—are fundamental to network reconnaissance, security research, and infrastructure mapping. When a bug bounty hunter discovers a target company owns the 203.0.113.0/24 netblock, they need to quickly identify which IPs host actual services and what those services might be. Traditional tools like dig -x or host work perfectly for single lookups, but they're serial by nature. Running them in a bash loop against thousands of IPs means waiting for each DNS query to complete before starting the next one.

This is where the reconnaissance workflow breaks down. Security researchers and penetration testers regularly need to perform reverse DNS lookups against entire CIDR ranges—sometimes millions of IPs. Using standard Unix tools, even with xargs -P, becomes unwieldy. You're either writing custom scripts to manage parallelism and DNS resolver rotation, or you're waiting hours for results. hakrevdns emerged from this specific pain point in the bug bounty and offensive security communities, where speed directly correlates to finding vulnerabilities before other researchers do. It's a tool that does exactly one thing: read IP addresses from stdin, perform reverse DNS lookups with configurable parallelism, and output results to stdout.

Technical Insight

hakrevdns's architecture is deceptively simple, which is precisely why it's effective. Written in Go, it leverages goroutines for concurrency without the overhead of process-based parallelism. When you pipe IP addresses into hakrevdns, it spawns a configurable number of worker goroutines (default: 8 threads) that continuously pull from an input channel, perform PTR lookups, and write results to an output channel.

Here's a practical example of using hakrevdns in a reconnaissance workflow:

# Generate IPs for a /24 subnet and perform reverse DNS
prips 203.0.113.0/24 | hakrevdns -t 50

# Output format: IP<tab>hostname
203.0.113.1	mail.example.com.
203.0.113.5	web-prod-01.example.com.
203.0.113.12	vpn.example.com.

The -t flag controls thread count. More threads mean more concurrent DNS queries, but there's a practical limit. DNS resolvers implement rate limiting, and your local network has connection constraints. In testing, 50-100 threads typically provides optimal throughput without triggering resolver throttling. Beyond that, you start seeing diminishing returns or outright failures.

What makes hakrevdns particularly clever is its resolver flexibility. By default, it uses your system's configured DNS resolver (usually from /etc/resolv.conf). But you can specify custom resolvers:

# Use Google's DNS
prips 10.0.0.0/16 | hakrevdns -r 8.8.8.8 -t 100

# Use multiple resolvers from a file (round-robin)
prips 10.0.0.0/16 | hakrevdns -s resolvers.txt -t 200

The multiple resolver feature is crucial for large-scale enumeration. When you're querying hundreds of thousands of IPs, a single DNS resolver will rate-limit you. By distributing queries across multiple resolvers—public DNS services, your organization's internal resolvers, or even DNS servers discovered during reconnaissance—you can multiply your effective throughput.

Under the hood, hakrevdns uses Go's net.LookupAddr() function, which performs the actual PTR record query. This function is part of Go's standard library and handles the DNS protocol details. The tool supports both UDP (default) and TCP via the -p flag, though UDP is almost always sufficient for reverse DNS lookups. TCP becomes relevant when responses exceed 512 bytes (rare for PTR records) or when traversing firewalls that block UDP/53.

The stdin/stdout design pattern is quintessentially Unix. hakrevdns doesn't generate IPs, parse CIDR ranges, or filter results—it expects a list of IPs and outputs hostname mappings. This composability is powerful:

# Chain with other tools
cat targets.txt | \
  grep -v '^#' | \
  hakrevdns -t 50 | \
  awk -F'\t' '{print $2}' | \
  grep '\.amazonaws\.com$' | \
  sort -u

This pipeline reads targets, performs reverse DNS, extracts hostnames, filters for AWS domains, and deduplicates—each tool doing one thing well. The tab-separated output format makes parsing trivial for downstream tools or scripts.

From a performance perspective, Go's goroutines give hakrevdns a significant advantage over thread-based parallelism in languages like Python or Ruby. Goroutines are lightweight (a few KB of stack space versus 1-2MB for OS threads), allowing hakrevdns to efficiently manage hundreds of concurrent DNS queries on modest hardware. The Go runtime multiplexes these goroutines across available CPU cores, and since DNS lookups are I/O-bound rather than CPU-bound, the goroutine model provides excellent throughput.

Gotcha

hakrevdns's simplicity comes with tradeoffs that become apparent in production use. The most significant limitation is the absence of retry logic. When a DNS query fails—due to network hiccups, resolver timeouts, or rate limiting—hakrevdns silently drops that IP. You won't see errors on stderr; the IP simply won't appear in the output. For reconnaissance workflows where you're casting a wide net, this might be acceptable. But if you need comprehensive results for compliance audits or complete network mapping, you'll need to implement your own retry mechanism, perhaps by comparing input IPs against output IPs and re-running failures.

The tool also lacks built-in rate limiting. While you can control thread count, there's no option to throttle queries per second. If you're scanning IP ranges owned by your own organization, this is fine. But when querying public DNS resolvers or targets that might have IDS/IPS systems watching for reconnaissance, you risk detection or getting your source IP blocked. You'd need to wrap hakrevdns in a script that batches IPs and adds delays, which undermines some of its speed advantages. Additionally, there's no support for DNS-over-HTTPS (DoH) or DNS-over-TLS (DoT). In environments where standard DNS is restricted or monitored, hakrevdns becomes less useful. Modern alternatives like dnsx support these protocols, making them more versatile for diverse network environments. Finally, error visibility is minimal. When diagnosing why certain IPs aren't resolving, you're left guessing whether it's a resolver issue, a network problem, or simply that no PTR record exists. A verbose mode or structured error output would significantly improve operational use.

Verdict

Use hakrevdns if you're performing security reconnaissance, bug bounty research, or network enumeration where speed matters and you need a lightweight tool that integrates cleanly into Unix pipelines. It excels when you're scanning large IP ranges (thousands to millions of addresses) and can tolerate some dropped results, or when you're chaining it with other tools in automated workflows. The ability to rotate through multiple DNS resolvers makes it particularly valuable for large-scale operations where rate limiting is a concern. Skip it if you need comprehensive coverage with retry logic, detailed error reporting, or support for modern DNS protocols like DoH/DoT. For production network audits, compliance scanning, or situations where missing data has consequences, invest time in more robust alternatives like dnsx or massdns. Also skip hakrevdns for small-scale lookups (under a few hundred IPs)—standard tools like dig or host will be simpler and give you more control. If you're working in heavily monitored or restricted networks, the lack of encrypted DNS support and rate limiting makes hakrevdns a poor choice.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/hakluke-hakrevdns.svg)](https://starlog.is/api/badge-click/developer-tools/hakluke-hakrevdns)