CLI tool
zmap/zdns avatar
zmap/zdns

ZDNS writes DNS answers as JSON lines, and one Resolver per goroutine is the whole parallelism model

Fast DNS Lookup Library and CLI Tool

1,146 stars156 forksGoApache-2.0

At a glance

What is it?
A Go resolver and command line tool for large scale DNS measurement, with a module per record type and lookup modules that follow the extra queries a raw answer does not contain. The library API is small, and its one hard rule is that a Resolver is not thread safe.
Who is it for?
Adopt ZDNS if you are doing DNS measurement at scale and you want structured JSON output rather than dig text, especially if you need to script across many names and resolvers. Do not adopt it as a general purpose stub resolver for an application, because it is built for measurement and its own resolver behaviour is a design choice, not a recursive resolver you would deploy behind your service.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly Go, 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.

Editorial analysis

Built for measurement, not for serving

ZDNS is a high-speed DNS resolver and command line utility for performing large-scale DNS measurements, written in Go, and it contains its own recursive resolution code and a cache optimized for performing lookups of a diverse set of names.

The measurement framing is the key to the whole design and it explains the parts that would otherwise look strange in a general purpose resolver. The recursive code is there so that a measurement can control the resolution path rather than inherit whatever the host's resolver does. The cache is optimized for a diverse set of names, which is a measurement workload where most names are looked up once and the hit rate on a general cache would be poor.

The README points at a paper appearing at ACM's Internet Measurement Conference '22 for more on the architecture and performance, and there is a ZDNS Wiki with additional information and use cases and examples.

The package construction and parsing comes from the zmap/dns library, which is the same organisation's raw DNS packet library, so ZDNS is building on a parser it shares with the rest of the ZMap toolset.

The recent releases are v2.1.1 from 2026-05-28, v2.1.0 from the same day, and v2.0.5 from 2025-07-28. The module path carries a major version, `github.com/zmap/zdns/v2`, so the v1 to v2 transition was a breaking change, and the Go Report Card badge in the README also points at the v2 path.

The licence is Apache-2.0, the last push to the repository was on 2026-09-28, and the repository is not archived.

A Resolver is single threaded, and the library makes you do the right thing

ZDNS consists of a recursive resolver library and a CLI wrapper, and the library API is described precisely enough that you cannot misuse it by accident.

The library consists of a `ResolverConfig` struct which contains all config options for all lookups made. That config is used to create one or more `Resolver` structs, and a `Resolver` should only make a single lookup at a time, since it is not thread-safe. Multiple `Resolver` structs should be used for parallelism.

That is the whole concurrency model, and it is the detail a reader has to get right. A shared Resolver used from several goroutines is a data race, and the library does not prevent it. The cost of the correct pattern is one allocation per concurrent lookup, which for a measurement running hundreds of thousands of queries is not free but is not the bottleneck either.

The example directories in the repository reflect the three cases. There is `single_lookup/`, `multi_thread_lookup/` and `all_nameservers_lookup/`, so the shape of each usage is in the tree rather than only in prose.

Modules define the behaviour of the lookups, and there are three kinds of them. The raw DNS modules provide the raw DNS response from the server, similar to dig but in JSON, and there is a module for nearly every type of DNS record. The list is long and specific, and it includes the records that matter for measurement: A, AAAA, CNAME, MX, NS, SOA, TXT, SRV, PTR, DS, DNSKEY, RRSIG, NSEC, NSEC3, CAA, TLSA, SSHFP, SVCB and others, plus the more unusual ones such as ATMA, AVC and RT.

The lookup modules exist because raw answers frequently do not provide the data you want, with the example given being that an MX response may not include the associated A records in the additional section, requiring an additional lookup. `alookup` acts similar to nslookup and follows CNAME records, `mxlookup` does an A lookup for the IP addresses of an exchange record, and `nslookup` does an A or AAAA lookup for the IP addresses of an NS record. And there are misc modules for other ways of querying servers, with `bind.version` given as the example.

The CLI reads a list of names, and the output is one JSON object per line

The input formats are where a measurement tool has to be flexible, and ZDNS supports several.

The most basic input is a list of names separated by newlines, from stdin or from a file.

code
echo "google.com\nyahoo.com" | zdns A
cat list_of_domains.txt | zdns A

And there is a file form with a flag.

shell
zdns A --input-file=list_of_domains.txt

For convenience there is a dig-style input, where the domain is a command line argument, which is equivalent to `dig -t A google.com @1.1.1.1`.

bash
zdns A google.com --name-servers=1.1.1.1

And the most interesting one, per-domain name servers. Normally ZDNS will choose a random nameserver for each domain lookup from `--name-servers`. If you want to specify a different name server for each domain, you provide domain name and name server IP pairs separated by newlines, and this overrides any nameservers provided with `--name-servers`.

code
echo "google.com,1.1.1.1\nfacebook.com,8.8.8.8" | zdns A

The output confirms the behaviour, because the `resolver` field in each JSON object shows which server answered. That field, the protocol, the status, the duration and the timestamp are all in the output, which is what makes the data usable for measurement rather than just for a single lookup.

The raw module output has the shape you would expect from a structured dig. The results object is keyed by the module name, and inside it there is a data object holding the additionals and answers arrays with the flags, type, class, name, ttl and answer for each record, a duration, a status of NOERROR and a timestamp. The lookup module output is reshaped for the question, so an `MXLOOKUP` result has an exchanges array where each entry carries the exchange name, the preference, the ttl, the type and the ipv4 addresses that were looked up for it.

The design decision here is that the JSON is a stable data format rather than a log line, which is what lets a researcher join the results with other measurements afterwards.

Dependencies reveal what the tool really is

The `go.mod` is a good description of the engineering, and the module name is `github.com/zmap/zdns/v2` requiring Go 1.25.0 with the toolchain pinned at go1.25.8.

The ZMap organisation dependencies are the interesting ones. `github.com/zmap/dns` at 1.1.70 is the raw packet construction and parsing library. `github.com/zmap/zcrypto` provides the cryptographic primitives, which for a DNS resolver means DNSSEC validation with the root anchors. `github.com/zmap/go-dns-root-anchors` is the pinned trust anchor set, dated to 2025. `github.com/zmap/go-iptree` is a prefix tree, and `github.com/zmap/zflags` is a flag parsing library, which is the same kind of thing the ZMap toolset uses for its own command line interfaces. `github.com/zmap/zgrab2` at 1.0.0 is also a required dependency.

The general purpose dependencies fill in the rest. `github.com/jellydator/ttlcache/v3` is the cache, which is the component the README says is optimized for a diverse set of names. `github.com/liip/sheriff` is a library for marshalling Go structs to JSON, which is how the raw DNS records become JSON. `github.com/schollz/progressbar/v3` is the progress bar for a long run. `github.com/sirupsen/logrus` is the logging. `github.com/hashicorp/go-version` handles version comparison, which is what a tool that needs to work out which nameserver software it is talking to would use. `github.com/prometheus/client_golang` and `github.com/weppos/publicsuffix-go` are there, the latter for the public suffix list, and `golang.org/x/time` for rate limiting.

`github.com/stretchr/testify` and `gotest.tools/v3` are the two test libraries, which is unusual to see together and suggests the codebase is in transition between them.

Building it, and what the release process looks like

Installation is a clone and a make target.

bash
git clone https://github.com/zmap/zdns.git
cd zdns
make install

The repository is a standard Go project layout with a `makefile` in lowercase, a `main.go` at the root, and `src/` holding the library and the CLI. There is a `benchmark/` directory, a `testing/` directory, and an `examples/` directory with the three usage shapes already mentioned.

The release machinery is visible in the tree. There is a `.goreleaser.yaml`, so the binaries are built with GoReleaser rather than a hand written script, and a `RELEASE.md` describing the process. There is a `CITATION.cff`, which is what a research tool needs so that a paper can cite a specific version. There is an `AUTHORS` file, a `CONTRIBUTING.md` and a `LICENSE`.

The quality tooling is worth noting. There is a `.golangci.yml` for the linter configuration, and a `.test-cover.sh` script, which tells you that coverage is measured as part of the workflow rather than being an aspiration. There is a `docs/` directory alongside the wiki, and a `.github/` directory for the workflows.

The last push was on 2026-09-28 and the repository is not archived, so the project is being maintained even though the last tagged release was in May 2026. The topic list is short, dns, research and scanner, which is an accurate description of what the tool is for.

Editorial conclusion

Adopt ZDNS if you are doing DNS measurement at scale and you want structured JSON output rather than dig text, especially if you need to script across many names and resolvers. Do not adopt it as a general purpose stub resolver for an application, because it is built for measurement and its own resolver behaviour is a design choice, not a recursive resolver you would deploy behind your service. Verify first that the module covering the record types you need exists, that you allocate a Resolver per concurrent lookup rather than sharing one, and that the nameserver selection policy you rely on is the one you think, since ZDNS picks a random nameserver per lookup by default.

Frequently asked questions

What is ZDNS used for?

It is a high-speed DNS resolver and command line utility for performing large-scale DNS measurements. It contains its own recursive resolution code and a cache optimized for lookups of a diverse set of names, and it is written in Go using the zmap/dns library to construct and parse raw DNS packets.

How do I install ZDNS?

Check out the repository and run make install, after git clone https://github.com/zmap/zdns.git and cd zdns. The library lives in src/zdns and the CLI wrapper in src/cli.

Can I use a different nameserver for each domain in ZDNS?

Yes. Provide domain name and nameserver IP pairs separated by newlines, which overrides the nameservers given with --name-servers, for example echo "google.com,1.1.1.1\nfacebook.com,8.8.8.8" | zdns A. The resolver field in the output shows which server answered.

Is the ZDNS Resolver safe to use from multiple goroutines?

No. The README states a Resolver should only make a single lookup at a time because it is not thread-safe, and that multiple Resolver structs should be used for parallelism. The examples directory has single_lookup, multi_thread_lookup and all_nameservers_lookup to show the shapes.

Which DNS record types does ZDNS support?

There is a raw module for nearly every record type, including A, AAAA, CNAME, MX, NS, SOA, TXT, SRV, PTR, DS, DNSKEY, RRSIG, NSEC, CAA, TLSA, SSHFP, SVCB and others, plus lookup modules alookup, mxlookup and nslookup that perform the follow up queries a raw answer does not contain.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. Releases
  5. zmap/zdns on GitHub
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/zmap-zdns.svg)](https://hysenlabs.com/projects/zmap-zdns)

Was this useful?