MassDNS: a malloc-free DNS stub resolver built for millions of names
A high-performance DNS stub resolver for bulk lookups and reconnaissance (subdomain enumeration)
At a glance
- What is it?
- A C stub resolver that sends queries to thousands of public resolvers at once and is used mainly for subdomain enumeration. The speed comes from stripping allocations out of the DNS path, and the honest caveats come from the resolver list it depends on.
- Who is it for?
- MassDNS earns its place in a reconnaissance pipeline or a large-scale migration check, not as a general-purpose resolver you point a workstation at. It is a stub resolver with no cache of its own, tuned to hold a fixed number of concurrent lookups and fire them at third-party resolvers, so it produces results at a rate your own nameserver cannot.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 25 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Star history
- blechschmidt/massdns3,654 ★ · +2
| Repository | 2026-10-07 | 2026-10-22 |
|---|---|---|
| blechschmidt/massdns | 3.7k | 3.7k |
2 Stars in the last 15 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Where the throughput claim comes from
The README states that without special configuration MassDNS can resolve over 350,000 names per second using publicly available resolvers. The mechanism behind that number is not parallelism alone, it is the removal of memory allocation from the DNS path. The v0.3 release notes record the switch explicitly: MassDNS adopted a custom DNS implementation to improve performance by minimizing memory allocations, and dropped its `ldns` dependency as a consequence. A resolver that allocates per packet pays for the allocator on the hot path, and a bulk resolver does that millions of times.
The concurrency model is a fixed budget rather than a queue. `-s` sets the number of concurrent lookups and defaults to 10000, which is the dial you turn to control how much pressure you put on anything downstream. `--processes` splits work across forked processes, `--socket-count` sets sockets per process, and `-i` sets the interval in milliseconds between repeated resolves of the same domain, defaulting to 500. `--hashmap-size` is the lookup table, `--rcvbuf` and `--sndbuf` size the socket buffers, and `--busy-poll` swaps epoll for busy-wait polling if you would rather trade CPU for latency.
Two IPv6 options stand out because they address a real and underexplored problem: `--rand-src-ipv6` picks a random address from a given subnet for each query, and `--rand-src-ipv6-file` does the same from a file. Source address diversity stops a single client prefix from being the visible signature of a whole scan, which matters when the target is rate-limiting by source.
Building from source and what nolinux changes
There is no package manager path in the README. You clone the repository, `cd` into the project root and run `make`. The Makefile compiles a single translation unit, `src/main.c`, straight to `bin/massdns`:
makeThe default `all` target uses `-O3 -std=c11` and defines `HAVE_EPOLL` and `HAVE_SYSINFO`, plus `-fstack-protector-strong`. Those two defines are the whole platform story. On Linux you get epoll for event handling and `sysinfo` for process statistics; everywhere else you run `make nolinux`, which is the same command line minus both defines and minus `-DHAVE_EPOLL`. On Windows the README asks for the Cygwin packages `gcc-core`, `git` and `make`.
There is a `CMakeLists.txt` next to the `Makefile` if you prefer CMake, and the `Dockerfile` builds from `alpine:edge`, compiles with `make`, and sets the entrypoint to `./bin/massdns`. `make install` copies the binary to `/usr/local/bin` and the man page to `share/man/man1`, honouring `DESTDIR` and `PREFIX` if you need to redirect both.
The `tests/` directory is present in the tree, and the Makefile's `debug` target rebuilds at `-O0` with `-DDEBUG`, which is the combination you want if you intend to step through it in gdb.
The bundled resolver list is the weak link, and the README says so
MassDNS has no cache and no authoritative data of its own. It is a stub resolver: it forwards queries to resolvers you supply and reports what comes back. Which means the quality of your results is the quality of the resolver list, and the repository ships one that the README openly criticises.
The wording is unusually blunt. `resolvers.txt` is described as a filtered subset of the resolvers provided by the subbrute project, and the README states that the list is currently outdated with a large share of resolvers being dysfunctional, along with a warning that the resolvers provided are not guaranteed to be trustworthy. It also warns that usage may cause significant load on the used resolvers and result in abuse complaints being sent to your ISP. That last sentence is the one to take seriously, because it describes a real and common outcome: thousands of strangers doing the same scan turn your residential resolver into an attack source in someone else's log.
Treat this as a licence condition on how you use the tool, not a disclaimer to skim. Point `-r` at resolvers you operate or have permission to use, keep `-s` well below its default when the target is an authoritative server rather than a resolver, and treat a high NXDOMAIN ratio as evidence of a bad resolver rather than of a quiet network. `--verify-ip` exists to validate that replies actually came from the address they claim, which is a blunt defence against a poisoned response.
Five output formats and why the resolver IP is on every line
Output is chosen with `-o` and a set of single-letter flags. `L` is domain list output, `S` simple text, `F` full text, `B` binary, and `J` ndjson. Each has advanced flags, and the ndjson mode has just one, `e`, which writes a record for each terminal query failure. That failure record matters more than it looks: in a scan of millions of names, the distinction between a name that does not exist and a name you never got an answer for is the difference between a clean result and a hole in your coverage.
The default full text output resembles what dig prints, with server, size, Unix timestamp and the four sections spelled out. The simplest useful invocation resolves one record type across a file of names:
$ ./bin/massdns -r lists/resolvers.txt -t AAAA domains.txt > results.txtThe README explains the resolver IP in the output by design: it is included so you can filter out results from any resolver that turns out to be producing bad answers, once you have spotted one. The `-w` form writes to a file instead of standard output, and the same invocation with `-w results.txt` is stated to be equivalent to the redirect above. Simple mode adds `n` for answer records, `u` for authority, `d` for additional, `t` for TTL and class, `m` to keep only records matching the question name, and `i` and `s` for indentation and section separators.
Filtering happens before output, which is the more useful half for large runs. `--filter` keeps only responses with a given code and `--ignore` drops them, with `-c` setting how many resolves to attempt per name before giving up at 50 and `--retry` setting the codes worth retrying, defaulting to every code except NOERROR and NXDOMAIN. `--sticky` stops the resolver switching on retry, `--predictable` walks resolvers incrementally, and `--norecurse` sends non-recursive queries, which the README points at cache snooping.
Subdomain brute forcing and the responsibility note attached to it
The reconnaissance use case gets its own section in the README, with a bolded instruction: perform reconnaissance scans responsibly and adjust the `-s` parameter to not overwhelm authoritative name servers. That warning sits directly above the `subbrute.py` usage, which tells you where the tool's real users are and what the failure mode looks like.
The pattern is the same shape as subbrute, which the README credits as the origin of the included resolver list. You feed a wordlist of candidate labels through a Python script that expands it against a target domain, and massdns does the resolving. Because the heavy lifting is in the resolver rather than in the generator, massdns is the component that scales, and swapping it for `dig` in a loop turns a fast scan into a slow one.
Two details reward attention. The PTR path uses a script that prints queries to standard output so they can be piped straight into the binary, and the README notes that the labels inside `in-addr.arpa` are reversed, so resolving the name of `1.2.3.4` needs the input `4.3.2.1.in-addr.arpa`. It also explains that as a side effect the script does not resolve in ascending order, which the README presents as a feature: sudden heavy spikes at the nameservers of an IPv4 subnet are avoided.
The honest limitation is the record type coverage. The custom malloc-free implementation supports only the most common records, and the README invites help changing that. v1.1.0 added SVCB and HTTPS query support, which tells you where the coverage boundary sat as of that release.
Where MassDNS sits against dig, dnsx and a normal resolver
The comparison that matters is with the tools you would otherwise reach for. `dig` is a debugging tool: correct, standards-following, and one query at a time. Ask it to resolve a million names and you have written a loop that will take hours and hammer your own resolver.
Purpose-built enumeration tools such as dnsx sit closer, and the difference is one of architecture. Those tools usually own their HTTP and resolution pipeline, speak wildcard detection and status-code triage, and are convenient when a single host list is the job. MassDNS is a resolver engine with almost no opinion about what a name means, which makes it the piece you pipe into when the input is millions of candidates and the output is text or ndjson for something else to consume.
Against a normal recursive resolver the distinction is fundamental rather than a matter of degree. A recursive resolver caches, so repeated lookups are cheap, and it answers from a recursive hierarchy you pay for. MassDNS deliberately does none of that. Its speed is a direct consequence of skipping cache semantics and sending work to resolvers you have not paid for. Where a cached resolver is the right answer, such as an application server doing lookups, massdns is the wrong tool entirely. Where you need breadth across a domain you are authorised to assess, it is the only one of the four that will finish.
Editorial conclusion
MassDNS earns its place in a reconnaissance pipeline or a large-scale migration check, not as a general-purpose resolver you point a workstation at. It is a stub resolver with no cache of its own, tuned to hold a fixed number of concurrent lookups and fire them at third-party resolvers, so it produces results at a rate your own nameserver cannot. Before running it against infrastructure you do not own, read the paragraph about abuse complaints and replace `lists/resolvers.txt` with resolvers you have permission to use. The build is one `make`, the man page at `doc/massdns.1` documents flags the README only lists, and v1.1.0 from 2024-03-09 is the most recent tagged release to look at.
Frequently asked questions
What is a stub resolver?
A stub resolver forwards queries to other resolvers and returns their answers without recursing or caching on its own. MassDNS is described in its own README as a high-performance DNS stub resolver, and that is why its throughput depends on the resolver list you pass to `-r` rather than on anything it stores locally.
What tools can be used to enumerate DNS records?
MassDNS ships a `subbrute.py` script for brute-forcing subdomains, in the same shape as the subbrute project it credits for its resolver list. The `-t` flag sets the record type, so A, AAAA and PTR queries are all one invocation, and the ndjson output mode with its `e` flag records terminal failures so gaps in coverage stay visible.
Is it safe to use the resolvers.txt file that ships with MassDNS?
The README says not to rely on it. It describes the list as outdated, with a large share of resolvers dysfunctional and no guarantee that the resolvers are trustworthy, and it warns that usage can cause significant load and lead to abuse complaints being sent to your ISP.
How do I build MassDNS on Windows or macOS?
The README asks for `make` from the project root on Linux, `make nolinux` elsewhere, and the Cygwin packages `gcc-core`, `git` and `make` on Windows. The `nolinux` target is the same compile line without the `HAVE_EPOLL` and `HAVE_SYSINFO` defines, and a Dockerfile is included if you would rather build on alpine.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/blechschmidt-massdns)Star history chart for your README
[](https://hysenlabs.com/projects/blechschmidt-massdns)