-
Notifications
You must be signed in to change notification settings - Fork 126
Home
A common question is "Why usezdns
over dig
or nslookup
?" For one-off queries, these tools or the DNS lookup functionality in your language of choice are more than sufficient.
However, zdns
is designed to efficiently handle large numbers of DNS queries in parallel as well as support more complex DNS queries like --all-nameservers
that aren't supported with dig
.
Several features to improve the performance of large volumes of DNS queries are:
- Shared Cache - each thread shares an LRU cache to store the most common domain name -> IP mappings. This greatly speeds up iterative lookups.
- UDP socket re-use -
dig
and friends will open new socket for each query. ZDNS re-uses the same socket for multiple queries, reducing networking overhead. - Efficient Multi-threading + Parallelism - ZDNS is designed to be multi-threaded by default leveraging light-weight goroutines.
You'll need to have the following installed in order to install ZDNS
Both of these should return the version if they're properly installed
git --version
go version
git clone https://github.com/zmap/zdns.git
cd zdns
make install
zdns --version
You can use ZDNS similar to dig
.
For example, if you want to query the A
record for google.com
from Cloudflare's recursive resolver, 1.1.1.1
.
zdns A google.com --name-servers=1.1.1.1
$ zdns A google.com --name-servers=1.1.1.1
{"name":"google.com","results":{"A":{"data":{"additionals":[{"flags":"","type":"EDNS0","udpsize":1232,"version":0}],"answers":[{"answer":"142.250.189.174","class":"IN","name":"google.com","ttl":173,"type":"A"}],"protocol":"udp","resolver":"1.1.1.1:53"},"duration":0.003042781,"status":"NOERROR","timestamp":"2024-12-26T17:06:31Z"}}}
00h:00m:00s; Scan Complete; 1 names scanned; 293.19 names/sec; 100.0% success rate; NOERROR: 1
When you specify --name-servers=1.1.1.1
, these nameservers will be used for all lookups made with that invocation of zdns
. If multiple are provided, a random one will be chosen for each name lookup.
Without specifying --name-servers
, ZDNS will read /etc/resolv.conf
to get your OS's default external resolvers. You can see in the below that this host had 127.0.0.53:53
configured as the local resolver (denoted with "resolver": "127.0.0.53:53"
).
$ zdns A google.com
00h:00m:00s; Scan Complete; 1 names scanned; 746.57 names/sec; 100.0% success rate; NOERROR: 1
{"name":"google.com","results":{"A":{"data":{"additionals":[{"flags":"","type":"EDNS0","udpsize":65494,"version":0}],"answers":[{"answer":"142.250.189.206","class":"IN","name":"google.com","ttl":145,"type":"A"}],"protocol":"udp","resolver":"127.0.0.53:53"},"duration":0.001102308,"status":"NOERROR","timestamp":"2024-12-26T17:10:08Z"}}}
Output is streamed to stdout
by default, so you can pipe into jq
for prettier output. You'll notice that the per-second status updates are not processed by jq
, those and any logs are sent to stderr
by default.
$ zdns A google.com | jq
00h:00m:00s; Scan Complete; 1 names scanned; 54.55 names/sec; 100.0% success rate; NOERROR: 1
{
"name": "google.com",
"results": {
"A": {
"data": {
"additionals": [
{
"flags": "",
"type": "EDNS0",
"udpsize": 65494,
"version": 0
}
],
"answers": [
{
"answer": "142.250.189.206",
"class": "IN",
"name": "google.com",
"ttl": 300,
"type": "A"
}
],
"protocol": "udp",
"resolver": "127.0.0.53:53"
},
"duration": 0.017919065,
"status": "NOERROR",
"timestamp": "2024-12-26T17:13:26Z"
}
}
}
This section outlines how to pass in names to query and how output is handled.
You can query names in similar fashion to the dig
CLI tool by passing in names as arguments after the lookup type (in this case an A
query).
Ex:
zdns A google.com yahoo.com
For querying large sets of names, ZDNS can also read names from StdIn
. Names must be new-line delimited, one name per line.
Using echo
:
echo "google.com\nyahoo.com" | zdns A
Using cat
to read 1+ files consisting of newline-delimited names:
$ cat list1.txt
google.com
yahoo.com
$ cat list2.txt
apple.com
apnews.com
$ cat list1.txt list2.txt | zdns A
...
{"name":"apple.com","results":{"A":{"data":{"additionals":[{"flags":"","type":"EDNS0","udpsize":1232,"version":0}],"answers":[{"answer":"17.253.144.10","class":"IN","name":"apple.com","ttl":593,"type":"A"}],"protocol":"udp","resolver":"1.1.1.1:53"},"duration":0.069962125,"status":"NOERROR","timestamp":"2025-01-06T16:05:44-07:00"}}}
{"name":"google.com","results":{"A":{"data":{"additionals":[{"flags":"","type":"EDNS0","udpsize":1232,"version":0}],"answers":[{"answer":"142.250.189.238","class":"IN","name":"google.com","ttl":287,"type":"A"}],"protocol":"udp","resolver":"1.1.1.1:53"},"duration":0.071940958,"status":"NOERROR","timestamp":"2025-01-06T16:05:44-07:00"}}}
{"name":"apnews.com","results":{"A":{"data":{"additionals":[{"flags":"","type":"EDNS0","udpsize":1232,"version":0}],"answers":[{"answer":"104.16.22.8","class":"IN","name":"apnews.com","ttl":85,"type":"A"},{"answer":"104.16.23.8","class":"IN","name":"apnews.com","ttl":85,"type":"A"}],"protocol":"udp","resolver":"1.1.1.1:53"},"duration":0.07367475,"status":"NOERROR","timestamp":"2025-01-06T16:05:44-07:00"}}}
{"name":"yahoo.com","results":{"A":{"data":{"additionals":[{"flags":"","type":"EDNS0","udpsize":1232,"version":0}],"answers":[{"answer":"74.6.231.21","class":"IN","name":"yahoo.com","ttl":1730,"type":"A"},{"answer":"98.137.11.163","class":"IN","name":"yahoo.com","ttl":1730,"type":"A"},{"answer":"74.6.143.25","class":"IN","name":"yahoo.com","ttl":1730,"type":"A"},{"answer":"74.6.143.26","class":"IN","name":"yahoo.com","ttl":1730,"type":"A"},{"answer":"98.137.11.164","class":"IN","name":"yahoo.com","ttl":1730,"type":"A"},{"answer":"74.6.231.20","class":"IN","name":"yahoo.com","ttl":1730,"type":"A"}],"protocol":"udp","resolver":"1.1.1.1:53"},"duration":0.08348,"status":"NOERROR","timestamp":"2025-01-06T16:05:44-07:00"}}}
00h:00m:00s; Scan Complete; 4 names scanned; 47.37 names/sec; 100.0% success rate; NOERROR: 4
You can also specify a file of input names using CLI flags to override the default behavior of reading from stdin
.
$ cat list1.txt
google.com
yahoo.com
$ zdns A --input-file=list1.txt
...
{"name":"google.com","results":{"A":{"data":{"additionals":[{"flags":"","type":"EDNS0","udpsize":1232,"version":0}],"answers":[{"answer":"142.250.189.206","class":"IN","name":"google.com","ttl":280,"type":"A"}],"protocol":"udp","resolver":"1.1.1.1:53"},"duration":0.061279167,"status":"NOERROR","timestamp":"2025-01-06T16:11:55-07:00"}}}
{"name":"yahoo.com","results":{"A":{"data":{"additionals":[{"flags":"","type":"EDNS0","udpsize":1232,"version":0}],"answers":[{"answer":"74.6.143.26","class":"IN","name":"yahoo.com","ttl":1528,"type":"A"},{"answer":"74.6.143.25","class":"IN","name":"yahoo.com","ttl":1528,"type":"A"},{"answer":"98.137.11.163","class":"IN","name":"yahoo.com","ttl":1528,"type":"A"},{"answer":"74.6.231.20","class":"IN","name":"yahoo.com","ttl":1528,"type":"A"},{"answer":"98.137.11.164","class":"IN","name":"yahoo.com","ttl":1528,"type":"A"},{"answer":"74.6.231.21","class":"IN","name":"yahoo.com","ttl":1528,"type":"A"}],"protocol":"udp","resolver":"1.1.1.1:53"},"duration":0.063159292,"status":"NOERROR","timestamp":"2025-01-06T16:11:55-07:00"}}}
00h:00m:00s; Scan Complete; 2 names scanned; 31.29 names/sec; 100.0% success rate; NOERROR: 2
By default, zdns
outputs its results to stdout
and all logs (both event-based and the per-second status updates) to stderr
.
You can control the verbosity of event-based logs with --verbosity=N
where:
-
--verbosity=1
Fatal logs only -
--verbosity=2
Above and Error logs -
--verbosity=3
Above and Warning logs (default) -
--verbosity=4
Above and Info logs -
--verbosity=5
Above and Debug logs
Below we can see examples of all 3 types of output. An INFO
log explains that zdns
chose a local IP address to send queries from. A per-second and an end-of-scan log show progress, success rate, and DNS statuses encountered. Finally, each of the 2 input names have a respective output line showing the result of the DNS query.
$ zdns A --input-file=list1.txt --threads=1 --iterative --verbosity=4
INFO[0000] none of the default local addresses could connect to name server 198.97.190.53:53, using local address 10.216.70.2
{"name":"google.com","results":{"A":{"data":{"answers":[{"answer":"142.250.191.46","class":"IN","name":"google.com","ttl":300,"type":"A"}],"protocol":"udp","resolver":"216.239.38.10:53"},"duration":0.270621125,"status":"NOERROR","timestamp":"2025-01-06T16:23:58-07:00"}}}
{"name":"yahoo.com","results":{"A":{"data":{"answers":[{"answer":"98.137.11.163","class":"IN","name":"yahoo.com","ttl":1800,"type":"A"},{"answer":"74.6.231.20","class":"IN","name":"yahoo.com","ttl":1800,"type":"A"},{"answer":"74.6.143.26","class":"IN","name":"yahoo.com","ttl":1800,"type":"A"},{"answer":"74.6.143.25","class":"IN","name":"yahoo.com","ttl":1800,"type":"A"},{"answer":"74.6.231.21","class":"IN","name":"yahoo.com","ttl":1800,"type":"A"},{"answer":"98.137.11.164","class":"IN","name":"yahoo.com","ttl":1800,"type":"A"}],"protocol":"udp","resolver":"202.165.97.53:53"},"duration":0.226075667,"status":"NOERROR","timestamp":"2025-01-06T16:23:58-07:00"}}}
00h:00m:01s; 2 names scanned; 2.00 names/sec; 100.0% success rate; NOERROR: 2
00h:00m:01s; Scan Complete; 2 names scanned; 1.33 names/sec; 100.0% success rate; NOERROR: 2
Caution
Each of the below file output redirects will create/overwrite the file specified if it exists. Be sure to use a new filename if you don't want to overwrite anything.
--status-updates-file=
redirects the status updates to the file specified instead of the default, stderr
.
Example:
zdns A google.com --status-updates-file=status.out
--log-file=
redirects the event-based logs to the file specified instead of the default, stderr
.
Example:
zdns A google.com --log-file=log.out
--output-file=
redirects the per-name output to the file specified instead of the default, stdout
.
Example:
zdns A google.com --output-file=out
--quiet
will disable the per-second and end-of-scan status updates.
$ zdns A google.com --iterative --quiet
{"name":"google.com","results":{"A":{"data":{"answers":[{"answer":"142.250.191.46","class":"IN","name":"google.com","ttl":300,"type":"A"}],"protocol":"udp","resolver":"216.239.34.10:53"},"duration":0.425099125,"status":"NOERROR","timestamp":"2025-01-06T16:46:25-07:00"}}}
You may want to lookup a specific name using a specific nameserver. For example:
$ cat /tmp/specific_nameserver.txt
google.com,1.1.1.1
yahoo.com,8.8.8.8
apnews.com,1.0.0.1
$ cat /tmp/specfic_nameservers.txt| zdns A | jq -r '.name + ", " + .results.A.data.resolver'
google.com, 1.1.1.1:53
apnews.com, 1.0.0.1:53
yahoo.com, 8.8.8.8:53
00h:00m:00s; Scan Complete; 3 names scanned; 109.90 names/sec; 100.0% success rate; NOERROR: 3
If a nameserver is provided to zdns
after the name (comma-delimited), this will take precedence over --name-servers
or /etc/resolv.conf
.
zdns
can perform it's own iterative name resolution which can be faster than relying on external recursive resolvers that can rate-limit you.
zdns
will start at the root servers, choose a random one to query, and iteratively work through all layers of the DNS tree.
!Pasted image 20250109104616.png
zdns
will cache the .com
nameservers so subsequent lookups for X.com
can skip querying the root nameservers (which will simply refer to .com
NSes).
zdns A google.com --iterative
{"name":"google.com","results":{"A":{"data":{"answers":[{"answer":"142.250.191.78","class":"IN","name":"google.com","ttl":300,"type":"A"}],"protocol":"udp","resolver":"216.239.32.10:53"},"duration":0.05233175,"status":"NOERROR","timestamp":"2025-01-09T10:47:55-08:00"}}}
00h:00m:01s; 1 names scanned; 1.00 names/sec; 100.0% success rate; NOERROR: 1
00h:00m:01s; Scan Complete; 1 names scanned; 0.95 names/sec; 100.0% success rate; NOERROR: 1
If you want to expose the lookup process, similar to how dig -t A google.com +trace
will do:
zdns A google.com --iterative --result-verbosity=trace | jq
$ zdns A google.com --iterative --result-verbosity=trace | jq
{
"class": "IN",
"name": "google.com",
"results": {
"A": {
"data": {
"answers": [
{
"answer": "142.250.191.78",
"class": "IN",
"name": "google.com",
"ttl": 300,
"type": "A"
}
],
"flags": {
"authenticated": false,
"authoritative": true,
"checking_disabled": false,
"error_code": 0,
"opcode": 0,
"recursion_available": false,
"recursion_desired": false,
"response": true,
"truncated": false
},
"protocol": "udp",
"resolver": "216.239.32.10:53"
},
"duration": 0.073534,
"status": "NOERROR",
"timestamp": "2025-01-09T10:51:29-08:00",
"trace": [
{
"cached": false,
"class": 1,
"depth": 1,
"layer": ".",
"name": "google.com",
"name_server": "192.5.5.241:53",
"results": {
"additionals": [
{
"answer": "192.43.172.30",
"class": "IN",
"name": "i.gtld-servers.net",
"ttl": 172800,
"type": "A"
},
...
The behavior of --all-nameservers
differs depending on whether zdns
is operating in --iterative
mode or not.
With both --iterative --all-nameservers
flags, zdns
exposes what each nameserver in a name's lookup chain has. Where --iterative
alone explores a single path thru the tree, --iterative --all-nameservers
queries all of the nameservers.
!Pasted image 20250109110430.png
Internally, it does this by querying all nameservers in a layer, building a de-duplicated list of nameservers in the subsequent layer, then querying those. In this way, zdns
stores what each nameserver has for a name, but doesn't query a nameserver more than once.
zdns A google.com --all-nameservers --iterative | jq
Note
--all-nameservers --iterative
will output the response for every server in a name's lookup chain to the root, 7.2 KB for google.com
.
Without the --iterative
flag, zdns
queries all external resolvers either passed in thru --name-servers
or picked up from /etc/resolv.conf
.
!Pasted image 20250109111949.png
zdns A google.com --all-nameservers --name-servers=1.1.1.1,8.8.8.8,1.0.0.1,8.8.4.4
$ zdns A google.com --all-nameservers --name-servers=1.1.1.1,8.8.8.8,1.0.0.1,8.8.4.4 | jq
{
"name": "google.com",
"results": {
"A": {
"data": [
{
"additionals": [
{
"flags": "",
"type": "EDNS0",
"udpsize": 1232,
"version": 0
}
],
"answers": [
{
"answer": "142.250.191.78",
"class": "IN",
"name": "google.com",
"ttl": 198,
"type": "A"
}
],
"protocol": "udp",
"resolver": "1.1.1.1:53"
},
{
"additionals": [
{
"flags": "",
"type": "EDNS0",
"udpsize": 512,
"version": 0
}
],
"answers": [
{
"answer": "142.250.191.78",
"class": "IN",
"name": "google.com",
"ttl": 119,
"type": "A"
}
],
"protocol": "udp",
"resolver": "8.8.8.8:53"
},
{
"additionals": [
{
"flags": "",
"type": "EDNS0",
"udpsize": 1232,
"version": 0
}
],
"answers": [
{
"answer": "142.250.189.206",
"class": "IN",
"name": "google.com",
"ttl": 187,
"type": "A"
}
],
"protocol": "udp",
"resolver": "1.0.0.1:53"
},
{
"additionals": [
{
"flags": "",
"type": "EDNS0",
"udpsize": 512,
"version": 0
}
],
"answers": [
{
"answer": "142.250.191.78",
"class": "IN",
"name": "google.com",
"ttl": 119,
"type": "A"
}
],
"protocol": "udp",
"resolver": "8.8.4.4:53"
}
],
"duration": 0.084098375,
"status": "NOERROR",
"timestamp": "2025-01-09T11:20:10-08:00"
}
}
}
00h:00m:01s; 1 names scanned; 1.00 names/sec; 100.0% success rate; NOERROR: 1
00h:00m:01s; Scan Complete; 1 names scanned; 0.92 names/sec; 100.0% success rate; NOERROR: 1
Several CLI flags are available to control the --iterative
process:
Network Options:
--4 utilize IPv4 query transport only, incompatible with --6
--6 utilize IPv6 query transport only, incompatible with --4
--prefer-ipv4-iteration Prefer IPv4/A record lookups during iterative resolution. Ignored unless used with both IPv4 and IPv6 query transport
--prefer-ipv6-iteration Prefer IPv6/AAAA record lookups during iterative resolution. Ignored unless used with both IPv4 and IPv6 query transport
During a name's iterative resolution, non-authoritative nameservers may provide referrals in theadditional
or authority
sections.
For example, the .com
NS a.gtld-servers.net
is not authoritative for google.com
and so it refers us to the google.com
NSes.
dig -t A google.com @a.gtld-servers.net
; <<>> DiG 9.10.6 <<>> -t A google.com @a.gtld-servers.net
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 35838
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 4, ADDITIONAL: 9
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;google.com. IN A
;; AUTHORITY SECTION:
google.com. 172800 IN NS ns2.google.com.
google.com. 172800 IN NS ns1.google.com.
google.com. 172800 IN NS ns3.google.com.
google.com. 172800 IN NS ns4.google.com.
;; ADDITIONAL SECTION:
ns2.google.com. 172800 IN AAAA 2001:4860:4802:34::a
ns2.google.com. 172800 IN A 216.239.34.10
ns1.google.com. 172800 IN AAAA 2001:4860:4802:32::a
ns1.google.com. 172800 IN A 216.239.32.10
ns3.google.com. 172800 IN AAAA 2001:4860:4802:36::a
ns3.google.com. 172800 IN A 216.239.36.10
ns4.google.com. 172800 IN AAAA 2001:4860:4802:38::a
ns4.google.com. 172800 IN A 216.239.38.10
;; Query time: 24 msec
;; SERVER: 192.5.6.30#53(192.5.6.30)
;; WHEN: Thu Jan 09 11:46:13 PST 2025
;; MSG SIZE rcvd: 287
If either --4
or --6
are used zdns
will only follow A
or AAAA
records, respectively, during iteration.
Without either flag, zdns
will use both IPv4 and IPv6 query transport.
--prefer-ipv4-iteration
and --prefer-ipv6-iteration
will prioritize using the respective query transport mode if possible, only falling back if the requisite Additional
records aren't provided or the nameservers aren't reachable.