2

I'm currently trying to write a script that validates, whether a given record attached to a name is available via all nameservers that are responsible for that name.

E.g. I would like to check whether there is an A record for foo.example.com available at all NS entries for the example.com domain (a.iana-servers.net and b.iana-servers.net)

The script works by first querying the NS records for the given name (or its parents if that fails; e.g. since foo.example.com. doesn't have an NS entry, we try example.com. next and finally .com.), and then checking the A record with all nameservers.

 name=foo.example.com
 # get the nameservers for ${name}
 sname=${name}
 until [ -z "x${sname}" ]; do
    dns=$(dig +short NS "${sname}")
    if [ "x${dns}" != "x" ]; then
      break
    fi
    sname=${sname#*.}
done
# now that we have a list of nameservers in $dns, we query them
for ns in $dns; do
    dig +short A "${name}" @$"{ns}"
done

This kind of works, unless the name is actually a CNAME. In this case, a dig NS will return the CNAME record (rather than the NS record or no record)

$ dig +noall +answer NS foo.example.com
foo.example.com. 300 IN CNAME bar.example.com.
$ dig +short NS foo.example.com
bar.example.com.
$ dig A foo.example.com @bar.example.com
;; global options: +cmd
;; connection timed out; no servers could be reached
$

Instead I would like to have something like:

$ dig +short NS foo.example.com
$ dig +short NS example.com
a.iana-servers.net.
b.iana-servers.net.
$ dig +short A foo.example.com @a.iana-servers.net.
93.184.216.34
$

So my question is: how can I force dig to only return the NS records, and not some other record which points to a host that is not a nameserver?

One obvious solution is to parse the output of dig +noall +answer to see whether it actually contains an NS record, but this seems rather clumsy and error prone...

umläute
  • 469
  • 1
  • 7
  • 26
  • Although some systems violate the standard, officially there should NEVER be NS records for a name that has a CNAME record, nor any others except for DNSSEC; see https://en.wikipedia.org/wiki/CNAME_record#Restrictions . If foo.example.com CNAMEs to bar.example.com, you need to find the nameservers and thus A record(s) (or others) **for bar.example.com** (via its nearest apex). – dave_thompson_085 Dec 21 '16 at 12:10
  • @dave_thompson_085 fine with me; however i'm interested in finding out the nameservers that are responsible for a given name (e.g. `foo.example.com`), not necessarily the `NS` record that is attached to that name literally. So I don't think that CNAME-restriction applies (after all, some nameserver must be authoritative to tell that `foo.example.com` *is* a `CNAME`; how do I find out which?) – umläute Dec 21 '16 at 13:10
  • `dig` has no option to filter specific types of records from the result set. – Andrew B Dec 21 '16 at 16:12
  • Since NS records can't be at the same level as CNAME they must be at a higher level. (This is usually stated as "CNAME cannot be at the [zone] apex" or "The apex cannot be a CNAME".) You already have the logic for this, since it is _often_ true of 'real' names as well. – dave_thompson_085 Dec 25 '16 at 09:54
  • @dave_thompson_085 so how do I detect from the output of `dig` that I actually queried a `CNAME`? neither `grep -w CNAME` nor using some other grep-strip (e.g. to match an IP) sounds particularily compelling or stable. – umläute Dec 26 '16 at 20:14

2 Answers2

2

If you're at all comfortable with Perl, you can save yourself a whole lot of pretty finicky work by writing your test as a plugin to Zonemaster. Its framework already has (correct) code to find the right set of name servers and send a query to all of them.

Calle Dybedahl
  • 2,083
  • 12
  • 17
  • I would encourage using a solution like this as well, simply because the problem can more complicated than is currently being tested for. (glue record mismatch anyone?) – Andrew B Dec 21 '16 at 16:09
0

Here is modified version of your script, which should work. It isn't exactly nice, but should work.

Warning - as Andrew B suggested in comments in Perl answer, this could be wrong way. There might be other edge cases than CNAME.

#!/bin/bash

domain=$1
soa_len=0

# first we find SOA to get domain for which we will find name servers
until [[ -z "${domain}" ]] || [[ "$soa_len" -gt 1 ]] ; do
    SOA=$(dig +short SOA "${domain}")
    soa_len=$(echo $SOA | awk '{ print NF }')

    # Returned SOA is not SOA but CNAME
    if [ "$soa_len" -eq 1 ]; then
        # set returned SOA (CNAME) as new domain and check again
        domain=$SOA
    # SOA is Empty
    elif [ "$soa_len" -eq 0 ]; then
        # remove one subdomain and check again
        domain=${domain#*.}
    fi
done

# Get NS records
NS=$(dig +short NS "${domain}")

# Print NS records each record on new line
for ns in $NS; do
    echo $ns
done
Kepi
  • 101
  • 1