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
- Whentrue
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
- Whentrue
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 responseaa
- means it's an authoritative answerrd
- (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
- TheHint
value indicates a zone with recursive resolver abilities.stores
- A block that defines a store type.type
- Indicates arecursor
configurationroots
- 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
andValidateWithStaticKey.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 recievedgoogle.com. 60 IN SOA ns1.google.com. dns-admin.google.com. 667287868 900 900 1800 60
- the SOA record forgoogle.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 trySuccess 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.