LibUnbound Tutorial part 2

Setup the context

In the second example we set additional useful options on the context, to enhance performance and utility. It is a modification of the example program from part 1.

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <unbound.h>

int main(void)
{
        struct ub_ctx* ctx;
        struct ub_result* result;
        int retval;

        /* create context */
        ctx = ub_ctx_create();
        if(!ctx) {
                printf("error: could not create unbound context\n");
                return 1;
        }
        /* read /etc/resolv.conf for DNS proxy settings (from DHCP) */
        if( (retval=ub_ctx_resolvconf(ctx, "/etc/resolv.conf")) != 0) {
                printf("error reading resolv.conf: %s. errno says: %s\n",
                        ub_strerror(retval), strerror(errno));
                return 1;
        }
        /* read /etc/hosts for locally supplied host addresses */
        if( (retval=ub_ctx_hosts(ctx, "/etc/hosts")) != 0) {
                printf("error reading hosts: %s. errno says: %s\n",
                        ub_strerror(retval), strerror(errno));
                return 1;
        }

        /* query for webserver */
        retval = ub_resolve(ctx, "www.nlnetlabs.nl",
                1 /* TYPE A (IPv4 address) */,
                1 /* CLASS IN (internet) */, &result);
        if(retval != 0) {
                printf("resolve error: %s\n", ub_strerror(retval));
                return 1;
        }

        /* show first result */
        if(result->havedata)
                printf("The address is %s\n",
                        inet_ntoa(*(struct in_addr*)result->data[0]));

        ub_resolve_free(result);
        ub_ctx_delete(ctx);
        return 0;
}

Download the source code

Invocation of this program yields the following:

$ example_2
The address is 213.154.224.1

The code is a modification of the first example. The context is set up, a single name is looked up, and the results and context are freed. The difference is that local settings are applied.

The local DNS server settings (acquired from DHCP perhaps) are read from /etc/resolv.conf with ub_ctx_resolvconf. Without reading this, unbound will use builtin root hints, this is a lot slower than using the DNS servers from resolv.conf. It makes a large difference, for me time example1 takes about 0.25 seconds, and time example2 takes about 0.05 seconds.

The difference is caused because the DNS proxy in resolv.conf has a cache of often used data, and thus can resolve queries much faster. If you perform many queries (and keep the unbound context around between calls to resolve) the time difference will grow smaller over time, since a cache of data is kept inside the context as well.

When you use ub_ctx_resolvconf libunbound becomes a stub resolver, not going to the internet itself, but relying on the servers listed. Without the call, by default, libunbound contacts the servers on the internet itself. A reason to not use the servers from resolv.conf is because you do not trust them, or because they lack support for DNSSEC, and you want to use DNSSEC validation.

Note: Some people have complained about DNSSEC validation changing between secure and bogus, randomly. Often these are because they read a resolv.conf that contains nameservers where some support DNSSEC and some do not. If unbound detects that signatures are stripped from the answer, it returns bogus.

The function ub_ctx_set_fwd(ctx, "192.168.0.1") (not shown in the example program) can be used to set an explicit IPv4 or IPv6 address for the DNS server to use. You can use this function to set DNS caching proxy server addresses that are not listed in /etc/resolv.conf.

If you wish to provide your own root-hints file, to override the builtin values, you can use the power-user interface ub_ctx_set_option(ctx, "root-hints:", "my-hints.root"), and the file my-hints.root is read in before the first name resolution.

The function ub_ctx_hosts is used to read /etc/hosts. This allows unbound to (very quickly) return addresses for hosts that are configured in /etc/hosts. If you do not trust the /etc/hosts file, you can avoid loading it. The addresses listed in the hosts file lack DNSSEC signatures, which may affect their validation status later on. The hosts file is a very useful configuration file to load, as it allows users to list addresses that are often used, or addresses for hosts on their local network.

If you do not want your program to fail if /etc/resolv.conf or /etc/hosts do not exist at all, you can check if errno == ENOENT when the reading functions fail, and act accordingly.