Skip to content
Phillip Stephens edited this page Jan 9, 2025 · 10 revisions

Getting Started

Introduction

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.

Installing

Prerequisites

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

Clone ZDNS

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

Build

make install

Confirm Installation

zdns --version

Quick Start

Coming from dig

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"
    }
  }
}

I/O

This section outlines how to pass in names to query and how output is handled.

Input

Names as Arguments

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

Names thru StdIn

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

File Input

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

Output

Overview

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

Output Options

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=

--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=

--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=

--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

--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"}}}

Use Cases

Specific Nameserver Per Name

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.

Iterative Resolution

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"
              },
              ...

Querying All Nameservers

The behavior of --all-nameservers differs depending on whether zdns is operating in --iterative mode or not.

--iterative --all-nameservers

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.

--all-nameservers without --iterative

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

Finer-grain Control over Iteration

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.

Clone this wiki locally