Skip to Content
ReferenceActionsDNS

DNS Action

The dns action asks a DNS server for the records of a name, and returns what the server answered. Use it to check that a name resolves to the addresses expected, that the MX, SPF, DKIM and DMARC records a mail flow depends on are in place, or that a change to a zone has reached a name server.

Basic Syntax

A DNS step gives the name to ask for, and the type of record when it is not an address.

steps: - name: Where the mail of example.com goes uses: dns with: name: example.com type: MX test: res.rcode == "NOERROR" && res.answers[0].host == "mail.example.com"

Parameters

KeyRequiredDefaultDescription
nameYesThe domain name to ask for. A dot at its end is optional. With type: PTR, an IPv4 or IPv6 address may be given, and is asked for as its reverse name
typeNoAThe type of record, in any case: A, AAAA, CNAME, MX, TXT, NS, SOA, SRV, PTR, CAA, or any other type DNS has. AXFR and IXFR, which transfer a zone, are not queries the action sends
serverNoThe resolvers of the machineThe server to ask, as host or host:port. An IPv6 address is given bare or in brackets, and in brackets with a port
protocolNoudpudp, tcp, or tls for DNS over TLS
timeoutNo5sHow long the whole query may take, as a duration such as 500ms or 10s, or a number of seconds

Without server, the action reads the resolvers from /etc/resolv.conf and asks them in order until one answers. The port is 53, or 853 with protocol: tls.

An answer cut short over UDP is asked for again over TCP, as dig does, and res.protocol then says tcp. With protocol: tls, the certificate of the server is checked against the host given in server.

Response

FieldDescription
res.rcodeThe response code of the server: NOERROR, NXDOMAIN, SERVFAIL, REFUSED and so on
res.answersThe records of the answer, in the order the server gave them
res.valuesThe data of each answer of the type asked for. An alias the answer came through is left out, so res.values of an A query holds only addresses
res.authoritativetrue when the server answered as the one that holds the zone
res.serverThe server that answered, as host:port
res.protocolThe protocol the answer came over
status0 when res.rcode is NOERROR, and 1 otherwise

Each entry of res.answers has name, type, ttl and data. Host names are given without the dot they end with. The types with several parts have them by name too:

TypedataOther fields
A, AAAAThe address
CNAME, NS, PTRThe host name
TXTThe text. A text sent in pieces is joined into one
MX10 mail.example.compreference, host
SRV10 60 5060 sip.example.compriority, weight, port, target
SOAns1.example.com hostmaster.example.com 2026101001 7200 3600 1209600 300ns, mbox, serial, refresh, retry, expire, minimum
CAA0 issue letsencrypt.orgflag, tag, value
Any otherThe record as a zone file writes it

A name that exists with no record of the type asked for is NOERROR with no answers, as DNS has it.

Examples

The Addresses of a Name

- name: api.example.com points at the load balancer uses: dns with: name: api.example.com test: res.values == ["203.0.113.10"]

A name behind an alias answers with the alias and the addresses. res.answers holds both, and res.values only the addresses:

- name: www is an alias of the CDN uses: dns with: name: www.example.com test: res.answers[0].type == "CNAME" && res.answers[0].data == "example.cdn.net" && len(res.values) > 0

Mail Records

- name: SPF allows the mail service uses: dns with: name: example.com type: TXT test: 'any(res.values, {# startsWith "v=spf1" && # contains "include:_spf.example.net"})' - name: DMARC rejects what fails uses: dns with: name: _dmarc.example.com type: TXT test: res.values[0] contains "p=reject"

A test that starts with a quote or holds # is written in quotes, so that YAML does not take # for a comment.

A Change Reached Every Name Server

Ask each name server of the zone itself, and check that it answers as the one that holds it:

- name: ns1 serves the new address uses: dns with: name: api.example.com server: ns1.example.com test: res.authoritative && res.values == ["203.0.113.10"]

A Name That Should Not Exist

A server that answers with an error is a result the test can check, not a failed step:

- name: The old name is gone uses: dns with: name: old.example.com test: res.rcode == "NXDOMAIN"

The Name of an Address

- name: The mail server has a reverse name uses: dns with: name: 203.0.113.25 type: PTR test: res.values == ["mail.example.com"]

DNS over TLS

- name: Ask a public resolver over TLS uses: dns with: name: example.com server: dns.google protocol: tls test: res.rcode == "NOERROR" && res.protocol == "tls"

Under a Guard

A query writes nothing, so the action runs under --read-only as it is. Run with --allow-host, it sends the query only to a server the run allows, at port 53 when server names none, or 853 with protocol: tls. Without server, every resolver of the machine must be allowed, since the query may go to any of them; a step is refused otherwise, and says to give server. The name asked for is not a host the action connects to, and is not checked. A refused step fails with the kind refused. See Guard.

probe --allow-host 1.1.1.1 workflow.yml

Error Handling

A server that answers, whatever it answers, gives a result: res.rcode says what, and status is 1 unless it is NOERROR. A step fails with an action error when no server answers, when the timeout runs out, and when a parameter is not valid, such as a type DNS does not have.

Updated at