BIND dns

Getting bind right can be a bit if a bind (if you pardon the pun) so in this example we are going to start be outlining what our zones actually do.

servicedomain.com is the domain name that we purchased from our domain name supplier.
mail.zone1.servicedomain.com will be the public name of our mail server (The service domain)
mail.zone2. servicedomain.com will be a second backup mail server at a different location. (A second service domain)
privatenet.local will be the internal domain used for local client to client connectivity.
nameserverdomain.com will be a second domain purely so that we can name the name servers.

Subnets and IP addresses

172.17.0.0/16 will be the local subnet on our master (server1)
10.8.1.0/24 will be the subnet for openvpn on our master (server1)
172.18.0.0/16 will be the local subnet on our master (server2)
10.8.2.0/24 will be the subnet for openvpn on our master (server2)
172.17.0.0/16 will be the local subnet on our master (server1)
10.8.2.0/24 will be the subnet for openvpn on our master (server1)
123.123.123.1 will be the public ip address of the master (server1)
169.169.169.1 will be the public ip address of the slave (server2)

Servers

Internally the servers and clients will be called

server1   This will be our master DNS server
server2   This will be our slave DNS server
client1
client2
client3

None of the clients will be accessible from the internet and the servers will not be accessible by their internal names

 

First install bind

#apt-get install bind9

This will install bind and add a user and group, both called “bind”.

Now check that bind is not running – This is particularly important if your router/modem allows access from the internet as you don’t want to become a free DNS server for all and sundry. Us the command line to check and stop the service if necessary.

# service bind9 status
# service bind9 stop

 

conf.options

The next thing to do is edit named.conf.options in order to set the global options for our dns server.

 

Access Control List

The first thing we do at the top of the file is to create an access control list. We are going to call this list “trusted_anywhere” but you can call it anything just as long as you reference it correctly. You can also create multiple access control lists for more complex configurations.

In our example we want to allow our local intranet, our vpn and our local server and networks full access

NOTE: The following special acl-name values are built into BIND:

“none” – matches no hosts

“any” – matches all hosts

“localhost” – matches all the IP address(es) of the server on which BIND is running e.g. if the server has a single interface with an IP address of 192.168.2.3 then localhost will match 192.168.2.3 and 127.0.0.1 (the loopback address is always present).

“localnets” – matches all the IP address(es) and subnetmasks of the server on which BIND is running i.e. if the server has a single interface with an IP address of 192.168.2.3 and a netmask of 255.255.255.0 (or 192.168.2.2/24) then localnets will match 192.168.2.0 to 192.168.2.255 and 127.0.0.1 (the loopback is assumed to be a single address). Some systems do not provide a way to determine the prefix lengths of local IPv6 addresses. In such a case, localnets only matches the local IPv6 addresses, just like localhost.

In our example we want to permit local clients and servers to perform recursive lookps, we also want to allow clients connecting by our VPN to do the same. So with reference to our the parameters given in our example above here is our “trusted_anywhere” access control list:

Server 1:

acl trusted_anywhere {
    172.17.0.0/16;
    10.8.1.0/24;
    localhost;
};

Server 2:

acl trusted_anywhere {
    172.17.0.0/16;
    10.8.2.0/24;
    localhost;
};

which is to be placed at the top of /etc/bind/named.conf.options. We can also restrict certain addresses or groups if we like by prefixing their addresses with a ‘!’ eg.

acl trusted_anywhere {
    172.17.0.0/16;
    10.8.2.0/24;
    localhost;
    !111.111.111.111
};

This will allow all hosts with the exception of 111.111.111.111 to do something (as yet unspecified) Note that we do not want to do this in our configuration, it is just an example.

If we want to we can create multiple ACL’s grant permissions to various groups to perform various tasks the it can be helpful. It can also make your DNS records more manageable even if you have only one address or group in the list.

A second ACL that lists the servers can be handy for setting transfer and notify options so we will go ahead and create one now:

acl servers {
    123.123.123.1;
    169.169.169.1
    localhost;
};

Finally, although not strictly an ACL we need to create a masters This only needs to be done on servers with slave zones:

On our slave server (Server2 ONLY) we want to create our masters list. I call it a list, it is really a list of one, but it makes it much easier to change the servers ip if we do it this way:

masters master_servers {
    123.123.123.1;
};

 

Options

Ok, so now we have our access control lists configured we now move on to our options. There are some directives in options that can be overridden by the individual zone files. We are not going to go through every available option as that would overly complicate things so we are just going to stick with the ones that we need for our configuration.

The recursion directive is what allows the server to forward requests to other servers for domains that it cannot resolve itself. Although we want to limit who can perform recursive queries, we do need to enable it with

options {
    .......
    recursion yes;
    .......
}

The next directives limit the scope of who can do what; from a security point of view these are the most important directives to get right.

The first one is allow-query{ }. Setting this will determine who can submit requests to your server. In our case servers and clients on the internet will need to use our server to determing the ip addresses for the mail servers. as we have no idea which servers or clents may which to obtain this information we want to permit all traffic to query our dns servers so we set

options {
    .......
    allow-query { any; };
    .......
}

Ok, at this point we are allowing clients access to our server and we are also allowing our server to perform recursive queries (in essence what we have created is an open public DNS server – which is not really what we want). In order to prevent this we need to set some more directives: “allow-recursion” and “allow-query-cache”. This time we want to restrict recursion so that only certain devices can perform recursive queries. This is where our access control list comes in:

options {
    .......
    allow-recursion { trusted_anywhere; };
    allow-query-cache { trusted_anywhere; };
    .......
}

The next configuration (setting forwarder) is optional. If you do not set any forwarders then Bind will use root hints to perform lookups insetead. Root hints are special dns servers that are basically the grandparents of all other DNS servers on the internet. These days using root hints alone is not particularly slow, but it may be a little slower than using forwarders (although if the forward server doesn’t have your address in its cache it will have to forward the request on anyway so the advantage of using them is debatable). In our example we will use forwarders just to show how it is done.

I am not the greatest fan of companies slurping your data in order to build a profile of you; usually to sell you things (basically I am not a fan of google) and believe that care must be taken when and if you choose a forwarder for this reason. The public DNS servers we will use for this example are provided by WatchDns (who claim that they do not collect, monetise or cache your data) The ip addresses for the WatchDns servers are correct at the time that this was written.

options {
    .......
    forwarders {
        84.200.69.80;
        84.200.70.40;
    };
    .......
}

The next 2 directives should alredy be set but you can check them, they are:

dnssec-validation auto;
auth-nxdomain no;

Now we want to tell our server which servers it should notify about what it is doing. This is where our servers ACL comes:

options {
    .......
    allow-notify {
        servers;
    };
    .......
}

IPV6 is on a very slow boat form china and at the moment and while no doubt one day it will take over for now we simply want to disable it. There is more to disabling IPV6 than just this (we will deal with the rest later) but as far as the options configuration goes all we have to do is tell bind not to listen on IPV6.

options {
    .......
    listen-on-v6 { none; };
    .......
}

The listen on directive tells bind what addresses to listen for requests on. This is another way of restricting traffic (at least it is if you have multiple ports and are routing everything through your server) as we could tell the server to listen to requests from a certain ip address. eg. listen-on { 127.0.0.1; 192.168.1.254; };

As we are using Access Control Lists we can set our server to listen out for all traffic from all ports, it will then use the ACL to determine how to handle it. To do this we need to set

options {
    .......
    listen-on { any; };
    .......
}

And that is it for our options file; all being well you should have something that looks like this:

acl trusted_anywhere {
    172.17.0.0/16;
    10.8.2.0/24;
    localhost;
};


acl servers {
    123.123.123.1;
    169.169.169.1;
    localhost;
};


//Slave server only
masters master_servers {
    123.123.123.1;
};


options {
    directory "/var/named";
    recursion yes;
    allow-query { any; };
    allow-recursion { trusted_ anywhere; };
    allow-query-cache { trusted_ anywhere; };
    forwarders {
        84.200.69.80;
        84.200.70.40;
    };
    dnssec-validation auto;
    auth-nxdomain no;
    listen-on-v6 { none; };
    listen-on { any; };
    allow-notify { servers; };
};

 

Zones

Now we need to configure the zones, but first we may want to configure the names of the nameservers themselves.

The next bit of configuration will depend on how your domains are set up with your provider and what facilities are available.

The goal of this tutorial is to have 2 nameservers that will perform lookups for your domain. In order to do this you will need to have requests forwarded from another domain higher up the food chain (usually your domain name provider). Form experience most of them require you to provide a name for your domain name server rather than an IP address (which can create a paradox if you are not very careful).

In essence you cannot use the domain name that you are serving because the parent nameserver will not know where to look for that domain until it has contacted your name server.

In reality very few mail servers actually serve mail for their own domains so you could use one of the user domains if you are on a budget, if not you could purchase a cheap domain with just about any name you like. For our example we will use a separate domain which according to our example will be nameserverdomain.com. We will call our name servers ns1 and ns2.

How exactly you do this will depend on your provider, but you will need 2 ‘A’ records

ns1.nameserverdomain.com resolves to 123.123.123.1
ns2.nameserverdomain.com resolves to 169.169.169.1

The only other thing that you will need to do is your providers domain server to forward requests to your own DNS servers. Again it depends on who your provider is, but you want to forward requests for the domain com to the 2 nameservers listed above. You do not need to add any other records.

We are now going to add some zones which are

servicedomain.com
zone1.mailservicedomain.com
zone2.mailservicedomain.com

The configuration for each of these zones is identical so we are only going to demonstrate one here:

zone “servicedomain.com” IN {
    type master;
    file “named.servicedomain.com”;
    allow-update { none; }
    allow-transfer { servers; };
};

Here we are telling bind that it is a master server for the domain com. The naming convention for the zone files (which we will create later) is to prefix “named.” to the fully qualified domain name.

We do not want to allow any other servers to update the domain records so we set allow-update to none. We are happy to allow transfers though, but only to our own servers hence:

allow-transfer { servers; };

 

Zone files (master)

The zone files are kept in /var/cache/bind and will be called

named.servicedomain.com
named.zone1.servicedomain.com
named.zone2.servicedomain.com

We are only going to show how to configure 1 zone files here but before we do, a quick word on why we are creating zone files rather than just adding records to the servicedoamin.com zone. The reason is that later on when we get to our email validation configuration we will want to isolate certain records in order to prevent lookups from becoming convoluted. It will also be much easier to check our servers using online tools.

Ok so now for our first zone servicedomain.com. First we will create the file

#touch /var/cache/bind/named.servicedomain.com

There are several ways to configure bind, each with their own merits, but for better or worse, this is the way we are doing it.

The first line in the file is the “ORIGIN” directive which is simply a dot ‘.’ to denote that there is nothing prepending the domain name record. This is my preferred way to create zones files because they are easily readable. Strictly you do not need to do it this way, but if you don’t then it isn’t clear from reading the contents of the file which zone it is supposed to be attributed to. So the first line in our zone file is:

$ORIGIN .

Note that the space between the dot and the declaration is important, without it the zone will not load.

Now we can create our first record, which is our Start of Authority record. This is the record that tells other domain servers and clients which domain server is ultimately responsible for the domain. It also sets some parameters for the domain and assigns the record a serial number.   The serial number is what the slave servers rely upon to ensure they are up to date and it is usually a serialised version of the date. It is not important to know what the other paramters do, but the serial number must be incremented with every change.

Here is the Start of Authority record for our servicedomain.com domain.

servicedomain.com     IN   SOA ns1.nameserverdomain.com yourname.emailaddress.com (
                               2018060501;       serial
                               8H;               refresh
                               2H;               retry
                               4W;               expire
                               3600;             minimum
                           )

NOTE: the part before the bracket ‘(‘ should be on a single line. Just a quck word about the emailaddress.com section. This is to provide information about who you should email in the event of a problem however you will note that it does not use the standard email address naming convention. Suffice it to say yourname.emailaddress.com equateds to yourname@emailaddress.com. This email address does not need to be resolvable by your dns server so can be any valid email address.

Even though we have told bind who is responsible for the start of authority, we still need to expressly define the nameservers (both master and slave). To do this we simply need to add

NS         ns1.nameserverdomain.com
NS         ns2.nameserverdomain.com

underneath our SOA record.

Note: Style convention says that you should keep the record type descriptions in line so you would type “NS” in line with “SOA”.

The next thing to cerate is our ‘A’ records. Usually our top level domain is used for administration and perhaps a website so you may want pgadmin and www as your ‘A’ records, but this is very much dependent on your desired configuration.

To save typing the full domain over and over again we create another ORIGIN:

$ORIGIN servicedomain.com.

Of particular interest and easy to miss is the dot ‘.’ after the “.com”. This is important because we are telling bind that there will be something appending this declaration.

For our first record we do not want to append anything however because we want to resolve our whole domain, To tell bind to look no further we use the ‘@’ symbol.

@         A       123.123.123.1

after “$ORIGIN servicedomain.com.” will cause bind to resolve “servicedomain.com” to the ip address 123.123.123.1. Now say we wanted to resolve pgadmin.servicedomain.com we would add

pgadmin       A       123.123.123.1

to our list of ‘A’ records.

You will note that the we are using the pubic IP address of the master server here but the address could be any server you desire (whether on your network or not).

The other zone files will have an SOA record pointing to their respective fully qualified domains and there will be at least one ‘A’ record which will be

mail       A       123.123.123.1

on the master and

mail       A       169.169.169.1

on the slave.

At this point we should have a working dns server. Run

#service bind9 start

and check the error logs. Bind logs errors to /var/log/syslog so you can either run

#tail -f /var/log/syslog

or

#cat /var/log/syslog | grep bind

 

Slave server zones

With the slave server it is only necessary to configure named.conf.default-zones as the zone files themselves will be grabbed from the master.

The slave servers are very similar to the masters other than they declare their type to be “slave” and have a “masters” directive. Each zone will have

zone " servicedomain.com " IN {
    type slave;
    masters { master_servers; };
    file " named.servicedomain.com ";
    allow-transfer { servers; };
}

allow-transfers is the directive that permits the devices in the “servers” ACL to provide each other with administrative information.

Once you have configured all your zones on your slave server, all you have to do is restart bind and provided that you have configured your servers properly the zone files will automatically be created (if not then the first place to start is to check the file permissions).

 

Private zones

As well as serving your public DNS zones it is more than likely that you will want to resolve local intranet zones for services such as samba and ntp.

With the exception that local zones will resolve to local ip addresses the creation of the zone files themselves is identical to the public zones. The major difference is that you only want to permit your local clients and servers to carry out the lookups (i.e. the trusted_anywhre ACL). In order to do this we need to override the configuration set in the “options” directive by adding “allow-query { trusted_anywhere; };” to our zone

zone "privatenet.local" IN {
    type master;
    file "named. privatenet.local"
    allow-update { none; };
    allow-transfer { none; };
    allow-query { trusted_anywhere; };
};

Now when we create our zone our A records can use the private subnet and our local private network cannot be resolved from a public client or server (which is exactly what we want).

There is no good reason for your private domain to have master and slave server (although you could create a backup server if you have a very complex configuration – this would be a slave server of sorts but it would not ordinarily resolve any queries, it would just store the zone information).

 

Testing

We will start with the easy tests first. While logged on to a the terminal of your dns server:

#dig google.com @localhost

This should work at both the master and the slave server.

NOTE: If you haven’t got dig run apt-get install dnsutils

and make sure that something is returned like

; <<>> DiG 9.9.5-9+deb8u3-Debian <<>> google.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 60632
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 5



;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;google.com.                         IN      A



;; ANSWER SECTION:
google.com.                 300   IN      A       216.58.210.78



;; AUTHORITY SECTION:
google.com.                 172799      IN      NS     ns1.google.com.
google.com.                 172799      IN      NS     ns3.google.com.
google.com.                 172799      IN      NS     ns4.google.com.
google.com.                 172799      IN      NS     ns2.google.com.



;; ADDITIONAL SECTION:
ns1.google.com.          172799      IN      A       216.239.32.10
ns2.google.com.          172799      IN      A       216.239.34.10
ns3.google.com.          172799      IN      A       216.239.36.10
ns4.google.com.          172799      IN      A       216.239.38.10



;; Query time: 663 msec
;; SERVER: 172.17.1.20#53(172.17.1.20)
;; WHEN: Sat Nov 28 01:20:46 GMT 2015
;; MSG SIZE rcvd: 191

 

Testing recursion

Testing recursion can be a little difficult if you are behind a NAT because if the server your are configuring is behind the same NAT or is trusted then you may not get the results that you are expecting. The best way to check for recursion if this is the case is to use your phone. If you install termux on your phone you can then install dns utilities so that you can run dig from the command line. (if you type dig and it is not installed it will directy you on how to install it)

With termux and dns utilities installed on your phone and wifi disabled when you run

#dig google.com @ns1.nameserverdomain.com

if your server is configured correctly the result shoud return a warning

;; WARNING: recursion requested but not available

Now if you run

#dig servicedomain @ns1.nameserverdomain.com

you should get a result in the answer section.

Finally you can test name resolution from your private domain

#dig privatenet.local @ns1.nameserverdomain.com

should return a warning however if you run the same command from the server or a local client then you should get a proper result.

 

Logging

Bind uses the standard syslog so if you are debugging bind it is probably best to use grep named to filter out unwanted messages

#tail -f /var/log/syslog | grep named

 

RNDC

Next check that rndc is working. If it is then ignore the RNDC steps:

#rndc status

should return something like

version: 9.9.5-9+deb8u3-Debian <id:f9b8a50e>
CPUs found: 4
worker threads: 4
UDP listeners per interface: 4
number of zones: 103
debug level: 0
xfers running: 0
xfers deferred: 0
soa queries in progress: 0
query logging is OFF
recursive clients: 0/0/1000
tcp clients: 0/100
server is up and running

if it does not then it is probably not configured.

NOTE: If rndc is working out of the box then you will not find any of the directives below in the configuration files, you only need to add them if it is not working. I have built 2 machines so far and in one it just worked and in the other it didn’t and I had to go through this manual configuration. First run:

#rndc-confgen -a

which should return:

wrote key file "/etc/bind/rndc.key"

now edit you /etc/bind/named.conf by adding the following:

include "/etc/bind/rndc.key";

controls {
    inet 127.0.0.1 allow { localhost; } keys { "rndc-key"; };
};

At this point you should be able to restart bind and run rndc status.

#service bind restart
#rndc status

Now further check that bind is running properly. I had some permission issues at this point, namely:

/etc/bind/managed-keys.bind.jnl: create: permission denied

in order to resolve this you need to make sure that /etc/bind has the correct permissions. This should already have been done if you have followed this guide, but otherwise

#chown -R bind:bind /etc/bind
#chown -R bind:bind /var/cache/bind

Now edit the /etc/named.conf file by adding

include "/etc/bind/rndc.key";

and named.conf.options by adding

managed-keys-directory "/etc/bind";

and finally bind was working as it should be. If you restart bind and have a load of ipv6 name resolution errors then ignore them, stop bind and wait for a few seconds then start it and get its status, it should be ok.

 

FURTHER INFORMATION:

For slave servers you do not need to make the individual zone files (they will be created when bind is started (or at least they should be) you will have to configure and set the /var/cache/bind directory and permissions though.

If you want to reload a domain on a master then you will need to run the following:

#rndc freeze mydomain.com
#rndc reload mydomain.com
#rndc thaw mydomain.com

If you want to do the same thing but you have different “views” then you will need to append IN viewname to the commands.

eg.

#rndc freeze mydomain.com IN myview
#rndc reload mydomain.com IN myview
#rndc thaw mydomain.com IN myview