Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
Z

zdns

> 编程语言
Open source

Fast DNS Lookup Library and CLI Tool

1.1K stars0 likes0 views
WebsiteGitHub

About

Fast DNS Lookup Library and CLI Tool

ZDNS

ZDNS is a high-speed DNS resolver and command line utility for performing large-scale DNS measurements. ZDNS is written in Go and contains its own recursive resolution code and a cache optimized for performing lookups of a diverse set of names. We use https://github.com/zmap/dns to construct and parse raw DNS packets. For more information about ZDNS's architecture and performance, check out the following paper appearing at ACM's Internet Measurement Conference '22.

[!TIP] The ZDNS Wiki contains additional information on ZDNS and walks thru use-cases and examples.

Install

ZDNS can be installed by checking out the repository and running make install.

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

Usage

ZDNS consists of a recursive resolver library and CLI wrapper.

The library consists of a ResolverConfig struct which will contain all config options for all lookups made. The ResolverConfig is used to create 1+ Resolver struct(s) which will make all lookups. A Resolver should only make a single lookup at a time (it is not thread-safe) and multiple Resolver structs should be used for parallelism. See our examples for how to use the library. Modules are used to define the behavior of the lookups.

ZDNS provides several types of modules:

  • Raw DNS modules provide the raw DNS response from the server similar to dig, but in JSON. There is a module for (nearly) every type of DNS record

  • Lookup modules provide more helpful responses when multiple queries are required (e.g., completing additional A lookup for IP addresses if a NS is received in NSLOOKUP)

  • Misc modules provide other additional means of querying servers (e.g., bind.version)

We detail the modules below:

Raw DNS Modules

The A, AAAA, AFSDB, ANY, ATMA, AVC, AXFR, BINDVERSION, CAA, CDNSKEY, CDS, CERT, CNAME, CSYNC, DHCID, DMARC, DNSKEY, DS, EID, EUI48, EUI64, GID, GPOS, HINFO, HIP, HTTPS, ISDN, KEY, KX, L32, L64, LOC, LP, MB, MD, MF, MG, MR, MX, NAPTR, NID, NINFO, NS, NSAPPTR, NSEC, NSEC3, NSEC3PARAM, NSLOOKUP, NULL, NXT, OPENPGPKEY, PTR, PX, RP, RRSIG, RT, SVCBS, MIMEA, SOA, SPF, SRV, SSHFP, TALINK, TKEY, TLSA, TXT, UID, UINFO, UNSPEC, and URI modules provide the raw DNS response in JSON form, similar to dig.

For example, the command:

echo "censys.io" | zdns A

returns:

…

Lookup Modules

Raw DNS responses frequently do not provide the data you want. For example, an MX response may not include the associated A records in the additional section requiring an additional lookup. To address this gap and provide a friendlier interface, we also provide several lookup modules: alookup, mxlookup, and nslookup.

alookup acts similar to nslookup and will follow CNAME records. mxlookup will additionally do an A lookup for the IP addresses that correspond with an exchange record. nslookup will additionally do an A/AAAA lookup for IP addresses that correspond with an NS record

For example,

echo "censys.io" | zdns mxlookup --ipv4-lookup

returns:

…

Other DNS Modules

ZDNS also supports special "debug" DNS queries. Modules include: BINDVERSION.

Input Formats

ZDNS supports providing input in a variety of formats depending on the desired behavior.

Basic Input

The most basic input is a list of names separated by newlines. For example:

From stdin:

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

From a file

zdns A --input-file=list_of_domains.txt

Dig-style Input

If you don't need to resolve many domains, providing the domain as CLI argument, similar to dig, is supported for ease-of-use.

For example:

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

Equivalent to dig -t A google.com @1.1.1.1

Name Servers per-domain

Normally, ZDNS will choose a random nameserver for each domain lookup from --name-servers. If instead you want to specify a different name server for each domain, you can do so by providing domainName,nameServerIP pairs seperated by newlines. This will override any nameservers provided with --name-servers.

For example:

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

You can see the resolver is as specified for each domain in the output (additionals/answers redacted for brevity):

$ echo "google.com,1.1.1.1\nfacebook.com,8.8.8.8" | zdns A
{"name":"google.com","results":{"A":{"data":{"additionals":...,"answers":[...],"protocol":"udp","resolver":"1.1.1.1:53"},"duration":0.030490042,"status":"NOERROR","timestamp":"2024-09-13T09:51:34-04:00"}}}
{"name":"facebook.com","results":{"A":{"data":{"additionals":[...],"answers":[...],"protocol":"udp","resolver":"8.8.8.8:53"},"duration":0.061365459,"status":"NOERROR","timestamp":"2024-09-13T09:51:34-04:00"}}}

Zone Files

Zone files (from ICANN CZDS or similar) can be used as an input source for ZDNS with the --zone-file flag. This enables parsing zone files from either stdin (default) or a file with the --input-file flag.

By default, ZDNS will extract only the name from each zone file record. If you'd also like to resolve the names referenced in the answer section of record types such as CNAMEs or NS records, you can use the --zone-file-include-targets CLI flag.

For example, with --zone-file-include-targets and this DNS zone file entry:

example.com. 3600 IN NS ns1.example.com

both example.com and ns1.example.com would be resolved.

Per-Module Triggers

ZDNS also supports passing in per-input-line "triggers" that map input lines to specific modules. With these, you can specify that certain domains be looked up with specific modules.

The input format is: domain_name,name_server,trigger,trigger_2,etc, where nameServer can be empty to use the default nameservers and 1+ triggers can be specified.

An example input.csv file:

example.com,,a-trigger
google.com,,a-trigger,cname-trigger
example.com,1.1.1.1,aaaa-trigger
yahoo.com
apnews.com,1.1.1.1

And corresponding multiple.ini:

; Specify Global Options here
[Application Options]
iterative=true
prefer-ipv6-iteration="true"
; List out modules and their respective module-specific options here. A module can only be listed once
[A]
trigger = "a-trigger"
[AAAA]
trigger = "aaaa-trigger"
[CNAME]
trigger = "cname-trigger"

This will lookup:

  • example.com with the A module using default nameservers
  • google.com with the A + CNAME modules using default nameservers
  • example.com with the AAAA module using Cloudflare's 1.1.1.1 resolver
  • yahoo.com with all modules specified and using default nameservers
  • apnews.com with all modules specified and using Cloudflare's 1.1.1.1 resolver

Running the command:

zdns MULTIPLE --multi-config-file="./multiple.ini" --input-file="input.csv"

Local Recursion

ZDNS can either operate against a recursive resolver (e.g., an organizational DNS server) [default behavior] or can perform its own recursion internally. If you are performing a small number of lookups (i.e., millions) and using a less than 10,000 go routines, it is typically fastest to use one of the common recursive resolvers like Cloudflare or Google. Cloudflare is nearly always faster than Google. This is particularly true if you're looking up popular names because they're cached and can be answered in a single round trip. When using tens of thousands of concurrent threads, consider performing iteration internally in order to avoid DOS'ing and/or rate limiting your recursive resolver.

To perform local recursion, run zdns with the --iterative flag. When this flag is used, ZDNS will round-robin between the published root servers (e.g., 198.41.0.4). In iterative mode, you can control the size of the local cache by specifying --cache-size and the timeout for individual iterations by setting --iteration-timeout. The --timeout flag controls the timeout of the entire resolution for a given input (i.e., the sum of all iterative steps).

Threads, Sockets, and Performance

ZDNS performance stems from massive parallelization using light-weight Go routines. This architecture has several caveats:

  • Every Go routine uses its own dedicated network socket. Thus, you need to be able to open as many sockets (in terms of both max file descriptors and ephemeral ports) as you have threads specified (via --threads). By default, ZDNS uses 1,000 threads, which is less than Linux's default max number of 1024 open FDs. However, it is greater than Mac OS's default of 256. You can view the maximum number of open FDs (and thus sockets) permitted by running ulimit -n. If you want to run with a greater number of threads than this number, you need to increase the number of open files at the OS level. If you fail to do this, you'll encounter a fatal error similar to FATA[0000] unable to create socketlisten udp :0: socket: too many open files. If you want to run more threads than you have ephemeral ports available, you will need to use multiple client IP addresses: --local-addr=A,B,C.

  • By default, ZDNS "reuses" UDP sockets by creating an unbound UDP socket for each light-weight routine at launch and using it for all queries (regardless of destination IP). This dramatically improves performance because ZDNS and the host OS don't need to setup and tear down a socket to send each individual packet (since DNS queries/responses tend to be one packet each). However, this means that ZDNS will preallocate a socket for each thread at launch. This may not be optimal if you're only looking up a small number of names. For example, if you only need to lookup 100 names, but use the default 1,000 threads, you'll bind but never use 900 UDP sockets. Instead, of worrying about recycling sockets, we recommend that you specify a reasonable number of threads for your use case (since this also foregoes any work to start those threads in the first place). This is why, though, you can get an error about being unable to open a large number of sockets even though you're only looking up a single name. If it's important to create a fresh socket for each query, you can disable this reuse by specifying --recycle-sockets=false.

  • Go is happy to use all CPU cores that are available to it, and can use a tremendous amount of CPU if you specify a large number of threads. CPU is primarily used for parsing and JSON encoding. If you want to limit the number of CPU cores, you can do so by including the --go-processes=n flag or setting the GOMAXPROCS environment variable.

  • It's difficult to recommend a precise amount of --threads as it depends on several factors. The graph below shows how a sample workflow has lower runtime but higher rates of name resolution failure as the number of threads increases.

    Much of the performance that you'll see depends on your workflow, hardware, and how many name servers the load is spread out on. If you're looking to maximize performance for your workflow/hardware, we recommend starting at 100 threads and increasing until you start to see an increase in name resolution failures. To help with this, you can use --output-file=output.jsonl and grep -v "NOERROR" output.jsonl | wc -l to count the number of names that failed to resolve. Flags that may be of use in tuning performance are:

    • --timeout The maximum amount of time ZDNS will spend on a single name
    • --iteration-timeout The maximum amount of time ZDNS will spend on a single iteration step (ex: resolving google.com at the .com layer)
    • --network-timeout The maximum amount of time ZDNS will wait for a response from a nameserver
    • --retries=N If a conn

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

Godnsresearchscanner

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言