Reducing initial latency in your resolver using stub zones with Unbound

Assuming you do have an authoritative and a resolving nameserver. The resolving nameserver (my-resolver in the following) runs Unbound. If your resolver has jeanbruenn.info in it’s cache resolving is pretty fast:

root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 0 msec

Now let’s check 9 times what happens if it is not in the cache by issuing the following command on the resolver after each dig-command:

root@my-resolver:~# unbound-control flush jeanbruenn.info
ok

Here’s the output of dig:

root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 9 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 3 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 3 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 9 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 3 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 114 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 115 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 115 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 9 msec

So we’re talking about an average of 42ms. A minimum of 3ms and a maximum of 115ms. Now let’s create a stub-zone for jeanbruenn.info within our resolver which points to the authoritative nameserver:

stub-zone:
    name: jeanbruenn.info
    stub-addr: IP1.IP1.IP1.IP1

And re-do the same above test:

root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 2 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 2 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 2 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 2 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 2 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 2 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 2 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 2 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 2 msec

So we’re talking about an average of 2ms. A minimum of 2ms and a maximum of 2ms. That’s quite an improvement, isn’t it? However, these numbers might not mean anything, once cached the query time is 0ms anyway. You’re however reducing the initial lookup time drastically which could remove initial lag.

Now let’s improve the reliability of that by using stub-first: yes. For that we’re re-doing the above test once stub-first: yes has been set in the stub-zone (by the way, the first result of the dig command can be thrown away because the root-servers and such like have to be queried/cached first after a restart of unbound). The result is again 2ms. So stub-first will make the initial query go to the authoritative nameserver. Now let’s add an iptables rule which will block access from the resolver:

iptables -A INPUT -s IP.IP.IP.IP -j REJECT

Enter your resolvers IP for IP.IP.IP.IP and re-do the above check.

root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 3114 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 772 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 2955 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep "Query time"
;; Query time: 4925 msec

As you can see it does take a lot longer now (no need for 9 iterations to spot that) and sometimes dig does not return an answer (most likely due to a timeout) but – and that’s the important part (a) it is still resolvable (b) usually you do have two authoritative nameservers and not just one as used for testing here. So in fact you would use something like this:

stub-zone:
    name: jeanbruenn.info
    stub-addr: IP1.IP1.IP1.IP1
    stub-addr: IP2.IP2.IP2.IP2
    stub-first: yes

and our test (again no need for 9 tests to see what happens):

root@janice:~# dig www.jeanbruenn.info @my-resolver | grep Query\ time
;; Query time: 1 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep Query\ time
;; Query time: 2 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep Query\ time
;; Query time: 2 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep Query\ time
;; Query time: 1 msec
root@janice:~# dig www.jeanbruenn.info @my-resolver | grep Query\ time
;; Query time: 1 msec

By the way, the initial query time was 2584 msec (with the primary authoritative still rejecting everything). All further requests look like above between 1ms and 2ms. So Unbound noticed that the first stub-addr is not working and seems to use the second therefore. I can verify that by looking at the packets in my iptables rule which does not increase on further testing.

Obviously you might do this even for domains your authoritative nameserver just act as a slave for. With just a few hundred zones these are too many stub zones I would need to create manually, hence I wrote the following small script which will auto-create those

First of all I do need a list of domains. I do use BIND as authoritative Nameserver (what else?).

while read -r domain; do 
  echo $domain; 
done< <(grep "zone.*{" zones.conf | sed 's_.*"\(.*\)".*_\1_g' | sort)

Assuming that you do have a file called zones.conf all your domains are in. That will return a list of all zone-names. So in case you’re using some reverse-dns zones or special stuff you’ll likely need something else than my above grep/sed-combo.

Equipped with the domain name I can check the authoritative nameservers for a domain using dig $domain +nssearch. The 11th position is the authoritative nameserver and the 13th position even allows us to sort by query time. Which means

dig $domain +nssearch | awk '{print $13,$11}' | sort | cut -d' ' -f2

will return the authoritative nameservers sorted by query time. Another while in our while will allow us to form the stub zones:

while read -r domain; do 
  while read -r nameserver; do
    echo "$domain $nameserver";
  done< <(dig ${domain} +nssearch | awk '{print $13,$11}' | sort | cut -d' ' -f2);
done< <(grep "zone.*{" zones.conf | sed 's_.*"\(.*\)".*_\1_g' | sort);

The stub zone looks like this:

stub-zone:
  name: $domain
  stub-addr: $nameserver
  stub-first: yes

Modifying the while while construct to

while read -r domain; do
  echo "stub-zone:"
  echo "  name: ${domain}" 
  while read -r nameserver; do
    echo "  stub-addr: ${nameserver}"
  done< <(dig ${domain} +nssearch | awk '{print $13,$11}' | sort | cut -d' ' -f2);
  echo "  stub-first: yes"
  echo ""
done< <(grep "zone.*{" zones.conf | sed 's_.*"\(.*\)".*_\1_g' | sort) > stub-zones.conf

Now you can simply scp/rsync stub-zones.conf to your resolvers /etc/unbound/unbound.conf.d where it will be picked up right after issuing an unbound reload. unbound-control list_stubs will tell you if it does have all stubs.

No Comments

Post a Comment