Skip to content
Phillip Stephens edited this page Jan 10, 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"}}}