Introduction

Welcome to the Hickory DNS User Manual. This book will be updated as issues and questions arise.

In some cases the best source of information about how to use the project is in the source repository, Hickory DNS.

Contributions to this book will always be welcome, Hickory Docs.

Thank you to the Rust community for making this project possible.

Installation

There are two options to install hickory-dns. Either install hickory-dns via cargo using

cargo install hickory-dns --features=recursor,dnssec-openssl

or build it from source

cargo build --package hickory-dns --features=recursor,dnssec-openssl

Hickory uses Cargo features to enable or disable certain functionalities. Alternatively use feature dnssec-ring to use the cryptographic library ring instead of OpenSSL.

The recursor feature allows Hickory to run as a recursive resolver, for example to activate DNSSEC validation.

The list of features is explained in Hickory's Readme.

Authoritative Name server

One of the roles Hickory supports is as an authoritative name server. This type of name server has authoritty over its own zones and can answer queries for which it is responsible.

Configuration

To configure Hickory as an authoritative name server the setup requires a few steps.

  • set up one or more zone files to have authority over
  • generate a zone signing key (ZSK)
  • configure Hickory to define zones and key

Zone File(s)

First at least one zone file has to be created, Hickory has the authority over. An example of a zone file (e.g. root.zone) looks as follows:

.	86400	IN	SOA	primary0.example.com. admin0.example.com. 2024010101 1800 900 604800 86400
.	86400	IN	NS	primary0.example.com.
primary0.example.com.	86400	IN	A	127.0.0.1

This file defines a SOA record (Start of Authority), a NS record (Namespace) and an A record (IPv4 Address).

A list of zone file examples can be found in the test_configs/default folder as part of Hickory's test suite.

Zone Signing Key

The second step is to generate a zone signing key (ZSK). Hickory will use this key to sign all zones with during startup. Additionally a key signing key is generated as well internally.

To generate a compatible ZSK we use the openssl command line tool:

openssl genpkey -quiet -algorithm RSA -out zsk.key

This generates a new key using the RSASHA256 algorithm and stores the private key in zsk.key.

Note: Other tools to generate keys exist, but not all key formats are currently supported by Hickory.

config.toml

The last step is to create a config.toml file for Hickory.

# config.toml
listen_addrs_ipv4 = ["0.0.0.0"]

[[zones]]
zone = "."
zone_type = "Primary"
file = "root.zone"
enable_dnssec = true

[[zones.keys]]
key_path = "zsk.key"
algorithm = "RSASHA256"
is_zone_signing_key = true

This configuration consists of the following fields:

  • listen_addrs_ipv4 - specifies the list of addresses the DNS server will accept connections on.
  • [[zones]] - A block to define a zone.
    • zone - The zone to sign, in this case root ".".
    • zone_type - Primary indicates that hickory is the authority.
    • file - The name of the zone file that contains all DNS records.
    • enable_dnssec - When true Hickory generates additional DNSSEC records for all records in the zone file on startup.
  • [[zones.keys]] - A block to define a zone key.
    • key_path - The path to the signing key, e.g. zsk.key
    • algorithm - The cryptographic algorithm the key was generated with.
    • is_zone_signing_key - When true marks the key as zone signing key.

Important: The flag enable_dnssec in this context does not mean DNSSEC validation is active, it's used to generate all relevant DNSSEC records during startup.

Multiple zones can be specified by repeated [[zones]] blocks that point to separate zone files.

Run Hickory

Let's start hickory-dns now, we assume zone file(s), ZSK and config.toml are all in the same folder.

hickory-dns --port 2345 --debug --config=./config.toml --zone-dir=.

This starts hickory-dns on port 2345 with debug log level. Feel free to pick a different port, typically port 53 is already taken by the DNS service of the operating system. The --config option specifies the location of the config.toml otherwise it checks the default file path at /etc/named.toml. The --zone-dir option specifies the path to check zone files in, e.g. to find root.zone, the default directoy is /var/named.

The debug log of Hickory should contain output that loads a ZoneConfig, the authority loads zone records and signs the zone "." using the generated zone signing key. The hickory-dns server should now run and accept DNS queries, for example via dig or delv.

Querying Records

To fetch the A record for domain primary0.example.com. use the dig command:

dig @127.0.0.1 -p 2345 primary0.example.com. +norecurse

which returns

; <<>> DiG 9.20.2 <<>> @127.0.0.1 -p 2345 primary0.example.com. +norecurse
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39369
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
; OPT=5: 08 0d 0e 0f ("....")
; OPT=6: 08 0d 0e 0f ("....")
;; QUESTION SECTION:
;primary0.example.com.	IN	A

;; ANSWER SECTION:
primary0.example.com. 86400 IN	A	127.0.0.1

;; Query time: 0 msec
;; SERVER: 127.0.0.1#2345(127.0.0.1) (UDP)
;; WHEN: Mon Oct 21 15:15:19 CEST 2024
;; MSG SIZE  rcvd: 117

Note: The authoritative name server is configured to not send queries to other servers, therefore the +norecurse option is passed in.

The following lines provide a bit more information on the response.

;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39369
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

The DNS server responded with status NOERROR, indicating a valid response. The flags field is a bitset with different flags, displayed as qr aa.

  • qr - means it's a query response
  • aa - means it's an authoritative answer
  • rd - (when given) means recursion desired, the name server is allowed to forward the query to other upstream name servers

The dig CLI sets the +recurse flag as default, therefore the rd flag will appear in the response if not deactivated.

In order to check that the DNS server created the associated RRSIG record to the A record, use dig's +dnssec option to return both records.

dig @127.0.0.1 -p 2345 primary0.example.com. +dnssec +multiline

The +multiline option will wrap the text to a reasonable width. This will return the associated RRSIG record in the ANSWERS section as well.

;; ANSWER SECTION:
primary0.example.com. 86400 IN A 127.0.0.1
primary0.example.com. 86400 IN RRSIG A 8 3 86400 (
				20251020134506 20241021134506 57797 .
				FyZW3yHIdEfN0eakLvgsZQkzx5MhcLM24h8wNPiEcosX
				3TTOr0NwvXAHqtbxYTJssfjR3DZhG3EgBdlZ18FpBKoY
				+VA3Vg+NYtuKpGduXU7Dreh3La5L8GlKC6uFc1ay0hR6
				qTq8M07JyzlMWE+U6r1n2R9bATKiWufhuDtnoINJbDMi
				TwaJ/ZxE7lfttpQ1gUKoNoEcOGkZUP18JlnyXoKrNkVH
				DdD0J/K8LTp/lnZ7AuAQ7ixJRNxroth6meeCHAQHNqyL
				9H6zKAiSRw4RVi4swodhyzCzn+oXhXjGVDmZlHFz8+QO
				S43iTumVhKaI8Fe/8/tgNMGZM+m7Z9N1GA== )

The DNS server has a single zone config for zone ".". To return the associated DNSKEY record(s) we can query them:

dig @127.0.0.1 -p 2345 . DNSKEY +dnssec +multiline

The response contains two records in the ANSWERS section:

;; ANSWER SECTION:
.			86400 IN DNSKEY	257 3 8 (
				AwEAAZzIkGf9sTXfFFeHTSNjbw3gr4ESGA5CzPtLKTSW
				8rbEpJw2G+goVFRrIS9ieHUna59TEfBkM/8WQ/MVkQQD
				pTTP2Rqg/E0aHEBQ2xbQVIveYXcU9absPn+CPjM3+gq0
				9bv9CDzxsa0yl9B7xbeAM9V8zXqtXfFaQ3plSUs9Wtqo
				nu/mJwEOu8YMiu9K0eZ+Gju1amobaOBXkOwCro7o8wae
				MIC0vFjC/ghfEmFAK1V3TFZw/jQXYWG4I6BdULiiMeLL
				R6ESPCXMRjBcMiCIPy5WOzQ4iAjpSkLEHqrtc9EwnUCT
				C0tmihZPZh3dyy7TgB3YTaHw8KEQhnDmdfjPZpc=
				) ; KSK; alg = RSASHA256 ; key id = 57797
.			86400 IN RRSIG DNSKEY 8 0 86400 (
				20251020134506 20241021134506 57797 .
				Q4CeL96V2NDBJI6jF3wjjLUYrW/jGjOgTuT3D8mRFwPy
				0b6suHmIy+1XPSGgYMAu1bpyVUxcpvXSE7DMIO/eYB/E
				nA5ArjcuOpKIzN+m75pLOZXb204dD5DptBhgjn04zTDB
				ML1rzK5acjp2Lcbo3X5lFABCXpy4diQDZhfCupNVA5JV
				mVD2nJ+eXHQXovB1cYyv5/w+1oK/ojZ1BZbMjUBIQjlH
				hisc8b5Y+V8fDehau3hIOuSrosJb15ST9J7YNndkt5kT
				1nSbAocX3AFWuZEVqwhbou45UAb2NfuvPlbZT5lHReWQ
				5E+1JULfak+HDz/blHyBPzALYrOEn0s7MQ== )

One is the DNSKEY (KSK) and the other the associated RRSIG for that DNSKEY record. Nearly all signed records have an associated RRSIG record to describe their signature.

Recursive Resolver

Hickory supports the role of a Recursive Resolver. A recursive resolver is a DNS server that accepts recursive queries and is able to resolve these queries by fetching additional records from other known authoritative name servers or from its cache. A recursive resolver can validate the recursive query to answer the question if the chain of trust is valid.

Configuration

To run Hickory as a recursive resolver with DNSSEC validation the following steps are necessary:

  • create a root hints file named root.hints
  • create a trust anchor file named trusted-key.key
  • configure Hickory via config.toml

Root Hints

The root hints file is used to define a set of authoritative name servers Hickory can query to fetch records for which it has no authority over. For example the Internet Assigned Numbers Authority (IANA) provides a set of files for their root name servers (see here). IANA is the authority for the root zone ".", they are responsible for assigning operators for top level domains (e.g. com, de).

For our example we will use the root.hints file provided by IANA and copy that to a local file.

Trust Anchor

In order to validate the chain of records successfully Hickory needs the trust anchor for the root zone ".". The keys can be fetched via dig.

dig DNSKEY . +answer

The command returns two DNSKEY records (abbreviated) in the ANSWERS section of the response:

. 19347	IN  DNSKEY  256 3 8 AwEAAc0SunbHdS0KFEyZbYII/+tzsrNzIwurKxmJA+0fhAYlTPA/5LrM ...
. 19347	IN  DNSKEY  257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3 ...

Create a new file named trusted-key.key, copy the content of the ANSWERS section into it. These keys are involved in the validation of the root zone.

config.toml

The last step is to configure Hickory as a recursive resolver.

# config.toml
[[zones]]
zone = "."
zone_type = "Hint"
stores = { type = "recursor", roots = "/absolute/path/root.hints", dnssec_policy.ValidateWithStaticKey.path = "/absolute/path/trusted-key.key" }

The configuration consists of the following fields:

  • zone - The zone to configure.
  • zone_type - The Hint value indicates a zone with recursive resolver abilities.
  • stores - A block that defines a store type.
    • type - Indicates a recursor configuration
    • roots - The file path to the root hints file.
    • dnssec_policy - Configues the DNSSEC validation policy.
      • ValidateWithStaticKey.path - The file path to the trusted key used for DNSSEC validation.

Note: Both path fields, root and ValidateWithStaticKey.path, need to be absolute paths.

Run Hickory

To start Hickory run:

hickory-dns --port 2345 --debug --config=./config.toml

This runs the DNS server on port 2345 with the resolver configuration. This time the DNS server can forward requests to the root name servers specified in the root.hints file.

DNSSEC Validation

Hickory started as recursive resolver and will execute DNSSEC validation for a query.

Using dig

In order to answer a recursive query the client needs to send the RD (Recursion Desired) flag. Using dig we can see that for an existing domain no records will be returned if the flag is disabled.

dig @127.0.0.1 -p 2345 example.com. +norecurse

This is because the recursive resolver cannot answer this query on its own. By setting the RD flag in dig (default) the query will return the A record for domain example.com. The command

dig @127.0.0.1 -p 2345 example.com. +recurse

returns

; <<>> DiG 9.20.2 <<>> @127.0.0.1 -p 2345 example.com. +recurse
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30300
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
; OPT=5: 08 0d 0e 0f ("....")
; OPT=6: 08 0d 0e 0f ("....")
;; QUESTION SECTION:
;example.com.			IN	A

;; ANSWER SECTION:
example.com.		3600	IN	A	93.184.215.14

;; Query time: 851 msec
;; SERVER: 127.0.0.1#2345(127.0.0.1) (UDP)
;; WHEN: Tue Oct 22 11:09:47 CEST 2024
;; MSG SIZE  rcvd: 83

Specifiying the +dnssec option in the dig command will additionally return the RRSIG record. Check the hickory-dns log to learn how the recursive query is processed, the DNSSEC validation, and what other name servers were involved.

The recursive resolver performs DNSSEC validation for each client query to validate the chain of trust. This validates all records that are involved in the query resolution, and returns the appropriate response. The DNS server automatically sets the ad flag (Authentic Data) in its response to indicate successful validation.

To return all records even when DNSSEC validation fails set the checking disabled flag CD using dig's +cdflag option. For example the following query uses dig to fetch the A record for a domain that fails DNSSEC validation:

dig @127.0.0.1 -p 2345 www.dnssec-failed.org. +dnssec +cdflag

Without the +cdflag the query would not return any records. The flags field in the answer will not contain the authenticated data (AD) bit to indicate there was a problem with the DNSSEC validation.

Using delv

Another tool to query DNS servers is delv. It can be used to return more information on the DNSSEC validation process. Let's try the same query as above using delv:

delv @127.0.0.1 -p 2345 example.com. A

returns

; fully validated
example.com.		3447	IN	A	93.184.215.14
example.com.		3447	IN	RRSIG	A 13 2 3600 20241102170341 20241012065317 19367 example.com. XMyTWC8y9WecF5ST67DyRUK3Ptvfpy/+Oetha9r6ZU0RJ4aclvY32uKC ojUsjCUHaejma032va/7Z4Yd3Krq8Q==

The important line here is ; fully validated, the remainig output is similar to dig, the returned records are the same. Let's query a record for a domain that does not exist.

delv @127.0.0.1 -p 2345 doesnotexist.com. A

returns

;; no valid RRSIG resolving 'doesnotexist.com/DS/IN': 127.0.0.1#2345
;; broken trust chain resolving 'doesnotexist.com/A/IN': 127.0.0.1#2345
;; resolution failed: broken trust chain

This is a good start, but it doesn't really provide the full picture of what's happening under the hood. To figure out more about the validation process either check the output of hickory-dns or alternatively display the intermediate steps using delv as well.

To display a brief list of validation steps use the +rtrace option:

delv @127.0.0.1 -p 2345 example.com. A +rtrace

that ouputs

;; fetch: example.com/A
;; fetch: example.com/DNSKEY
;; fetch: example.com/DS
;; fetch: com/DNSKEY
;; fetch: com/DS
;; fetch: ./DNSKEY
; fully validated
example.com.		2641	IN	A	93.184.215.14
example.com.		2641	IN	RRSIG	A 13 2 3600 20241102170341 20241012065317 19367 example.com. ...

The query returns information on intermediate steps, with the chain of trust fully validated. To get the full picture including all intermediate DNS queries and responses use the +mtrace option

delv @127.0.0.1 -p 2345 example.com. A +mtrace

dns

dns is a command line interface for performing low level DNS operations directly against a specific nameserver. It can be used for performing queries, notifications, and dynamic updates of records (create, append, delete, etc). It returns results in a similar manner to dig, using the RFC defined presentation format for the output. This is not intended to be compatible with dig, but is intended to be a simpler tool for performing any DNS operation needed.

The dns tool exposes the library functionality of Hickory DNS. It is meant generally to help with debugging zone or nameserver configurations. All of the commands supported are available with dns -h, here is a list:

Commands:
  query          Query a name server for the record of the given type
  notify         Notify a nameserver that a record has been updated
  create         Create a new record in the target zone
  append         Append record data to a record set
  delete-record  Delete a single record from a zone, the data must match the record
  help           Print this message or the help of the given subcommand(s)

Since the CLI is a direct implementation of Hickory DNS, it has support for all of the protocols that Hickory does, specifically: udp, tcp, tls, https, quic, h3. For the TLS based protocols, tls, https, quic, and h3, the tls-dns-name option is required for the TLS protocol. This is generally available in public documentation for various DNS services.

querying

Here is a query example to Google's nameservers for the google.com SOA record:

> dns -n 8.8.8.8:53 query google.com SOA
; using udp:8.8.8.8:53
; sending query: google.com IN SOA
; received response
; header 21285:RESPONSE:RD,RA:NoError:QUERY:1/0/1
; edns version: 0 dnssec_ok: false max_payload: 512 opts: 0
; query
;; google.com. IN SOA
; answers 1
google.com. 60 IN SOA ns1.google.com. dns-admin.google.com. 667287868 900 900 1800 60
; nameservers 0
; additionals 1

The output is hopefully self-explanatory, but here is a line by line explanation:

  • ; using udp:8.8.8.8:53 - tells us which DNS server is being queried
  • ; sending query: google.com IN SOA - shows us the query that was sent.
  • ; received response - tells us that we got a DNS response packet (as opposed to something else that would be unexpected)
  • ; header 21285:RESPONSE:RD,RA:NoError:QUERY:1/0/1 - this is the DNS header in the response, respectively, the message id, message type, request flags, response code, operation code, and number of records in each section (answers/nameservers/additionals)
  • ; edns version: 0 dnssec_ok: false max_payload: 512 opts: 0 - optionally, if the server supports extended DNS, these are the edns parameters
  • ; query - header for the query section
  • ;; google.com. IN SOA - exact query that was sent
  • ; answers 1 - count of answers recieved
  • google.com. 60 IN SOA ns1.google.com. dns-admin.google.com. 667287868 900 900 1800 60 - the SOA record for google.com.
  • ; nameservers 0 - count of the nameservers (or authorities) in the response
  • ; additionals 1 - the additional section count (this is 1 for the EDNS record which has no presentation format but was expanded above)

As a counter example of an unsuccessful query for a TXT record named doesnotexist.google.com:

> dns -n 8.8.8.8:53 query doesnotexist.google.com TXT
; using udp:8.8.8.8:53
; sending query: doesnotexist.google.com IN TXT
; received response
; header 53338:RESPONSE:RD,RA:NXDomain:QUERY:0/1/1
; edns version: 0 dnssec_ok: false max_payload: 512 opts: 0
; query
;; doesnotexist.google.com. IN TXT
; answers 0
; nameservers 1
google.com. 60 IN SOA ns1.google.com. dns-admin.google.com. 667090956 900 900 1800 60
; additionals 1

Notice the NXDomain in the header saying tha the record does not exist, nor does any of another type. Additionally there is a single nameserver in the response that tells us the SOA.

Conclusion

dns is a low level command for interacting with name servers. Consider the resolve command for a simple to use stub resolver.

resolve

resolve is a command line utility that exposes the functionality of the Hickory DNS stub-resolver library, hickory-resolver. The resolve command is similar in function to host. It can be useful for getting the IP addresses of particular domain names, or seeing the CNAME chain of a record. It will return the results in the record's presentation format. Like the hickory-resolver library, CNAME chains and other lookups that require a small amount of recursion can be performed.

A stub-resolver does not perform recursive resolutions. It expects the upstream resolver used to perform all necessary recursive looks to traverse the DNS zone registry for necessary information. In other words, the stub-resolver will only every contact the configured nameserver for results.

The resolve command currently only supports the udp and tcp protocols, though the Hickory Resolver supports others. Please file a feature request for additional protocol support if desired (such as tls, https, h3, or quic).

Example, get AAAA record

This will get the final AAAA record and all CNAME intermediates for www.un.org, which can sometimes be an easy way of discovering hosting providers:

Querying for www.un.org AAAA from udp:8.8.8.8:53, tcp:8.8.8.8:53, udp:8.8.4.4:53, tcp:8.8.4.4:53, udp:[2001:4860:4860::8888]:53, tcp:[2001:4860:4860::8888]:53, udp:[2001:4860:4860::8844]:53, tcp:[2001:4860:4860::8844]:53
Success for query www.un.org IN AAAA
        www.un.org. 1646 IN CNAME d1z8tokz9k79tw.cloudfront.net.
        d1z8tokz9k79tw.cloudfront.net. 60 IN AAAA 2600:9000:25ef:2a00:14:176d:6100:93a1
        d1z8tokz9k79tw.cloudfront.net. 60 IN AAAA 2600:9000:25ef:b200:14:176d:6100:93a1
        d1z8tokz9k79tw.cloudfront.net. 60 IN AAAA 2600:9000:25ef:f000:14:176d:6100:93a1
        d1z8tokz9k79tw.cloudfront.net. 60 IN AAAA 2600:9000:25ef:fc00:14:176d:6100:93a1
        d1z8tokz9k79tw.cloudfront.net. 60 IN AAAA 2600:9000:25ef:2000:14:176d:6100:93a1
        d1z8tokz9k79tw.cloudfront.net. 60 IN AAAA 2600:9000:25ef:b400:14:176d:6100:93a1
        d1z8tokz9k79tw.cloudfront.net. 60 IN AAAA 2600:9000:25ef:ac00:14:176d:6100:93a1
        d1z8tokz9k79tw.cloudfront.net. 60 IN AAAA 2600:9000:25ef:2800:14:176d:6100:93a1

Line by line explanation of output:

  • Querying for www.un.org AAAA from ... - tells us the exact query being sent to the upstream resolvers, and is followed by the list of resolvers to try
  • Success for query www.un.org IN AAAA - tells us the query the server responded with (this should match the original), and that it was successful
  • Then the records returned from the query are returned, this starts with any intermediates in order, and then the final record requested

Conclusion

The resolve command is good for understanding how the Hickory Resolver performs lookups. It can be useful as a CLI to easily verify how the hickory-resolver library will work when that is embedded in a program and hard to change behavior of.