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-Primaryindicates that hickory is the authority.file- The name of the zone file that contains all DNS records.enable_dnssec- WhentrueHickory 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.keyalgorithm- The cryptographic algorithm the key was generated with.is_zone_signing_key- Whentruemarks the key as zone signing key.
Important: The flag
enable_dnssecin 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
+norecurseoption 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- TheHintvalue indicates a zone with recursive resolver abilities.stores- A block that defines a store type.type- Indicates arecursorconfigurationroots- 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,
rootandValidateWithStaticKey.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.