A DNS Server to Call My Own

Posted by Christopher Wojno Sun, 30 Dec 2007 23:56:00 GMT

I’ve been itching to set up my own DNS server for a while now. Why? I’ve come up with three reasons:

  1. Speed
  2. Convenience
  3. Security

The first one is pure fluff. My home network doesn’t have nearly enough traffic to make it worth it. The second has merit. It would be nice if I could name machines on the network and have them resolve correctly. I could also use it to mask external addresses. So I could make stuff up and have it resolve locally. So I could make, oh, doubleclick.net resolve to 127.0.0.1. Now, no one on my network will get those advertisements anymore. Sure, I have it set up in the hosts file now, but I’m like any other network administrator… No, not lazy, but clever.

I’m working with Linux Gentoo 2.6.19 here on my local network. There is no chance that I will corrupt any legitimate records as nobody outside my network will be able to query my DNS server. I have my favorite editor: Vim at my side. Named (Bind) is currently at version BIND 9.4.1-P1.

Install bind

First, edit your /etc/portage/packages.use file. Add a line that says:

net-dns/bind -ipv6 -ldap postgres -ssl threads -mysql -bind-mysql -odbc

This means: I don’t want IPV6 support (my router doesn’t support it… sadly). Don’t use ldap. Add support for postgres (my favorite database). Don’t include SSL support (I’m assuming everyone trusts my server on the local network). Use threads to handle many requests simultaneously (I suppose I could turn this off as the server load will not be very large). Finally, don’t include mysql bindings or ODBC. Save that file.

Emerge

Using Gentoo’s emerge system:

%emerge net-dns/bind

It should install without any further intervention.

Firewall (IPTables)

I use the IPTables firewall to protect my server from local and foreign attacks. I like it because it gives me a lot of control over what goes in and out. I also don’t like it because it is very complicated. If you have a firewall, you need to poke holes in it for port 53 in the following ways:

  1. Outgoing UDP connections TO port 53 from your server to the DNS servers you normally use
  2. Incoming UDP connections TO your server on any port from the DNS servers you normally use for established UDP connections
  3. Same as #1 with TCP connections
  4. Same as #2 with TCP connections
  5. Incoming UDP connections from the local network on port 53
  6. Outgoing UDP connections to the local network on any port for established UDP connections
  7. Same as #5 with TCP connections
  8. Same as #6 with TCP connections

The above table is derrived from nixCraft’s article: Linux Iptables block or open DNS / bind service port 53 I added some modifications, however. Here’s an example configuration:

firewall.sh

#!/bin/bash
IPTABLES='/sbin/iptables'
LOCALNET='--src-range 192.168.1.2-192.168.1.254'
INTIF1='eth0'
DNSSERVERS='a.b.c.d a.b.c.e' # 2 IP addresses for your ISP's DNS servers

# PREAMBLE
$IPTABLES -F #flush old rules
$IPTABLES -X #clear old chains
$IPTABLES -A INPUT -s 127.0.0.1 -j ACCEPT #always trust requests from the server
$IPTABLES -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT #always trust active connections

# ... (other rules here)

# BIND/NAMED
# Outgoing Recursive Requests
for ip in $DNSSERVERS
do
        iptables -A OUTPUT -p udp -d $ip --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
        iptables -A INPUT -i $INTIF1 -s $ip -p udp --dport 53 -m state --state ESTABLISHED -j ACCEPT
        iptables -A OUTPUT -p tcp -d $ip --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
done
# incoming request configuration
# accept local queries
iptables -A INPUT -i $INTIF1 -m iprange $LOCALNET -p udp --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT

# block out all other Internet access
$IPTABLES -A INPUT -j DROP

The for loop was nixCraft’s idea. Very clever, however, instead of using the IP address of the server, I fell back to the interface card. Then, no matter what your IP address, you’ll be able to control access to the DNS server.

Update your firewall rules by executing your firewall script.

%./firewall.sh

IPTables example explained

This example actually works as is. But if you’re using SSH for access, you need to add in an SSH hole:

$IPTABLES -A INPUT --protocol tcp --dport 22 -j ACCEPT

Now, the beginning of the script defines (in order) the script used to interpret the file (#!/bin/bash), the iptables program (stored as a variable for ease in name or location changes), LOCALNET (an IP address range specifying your local network, generally, 192.168.1.1 is the gate way and packets may appear to originate from your router if it’s a piece of junk. 192.168.1.255 is broadcast, so no need to include that address either), INTIF1 is the NIC card on my server. Use ifconfig to figure out what yours is (or iwconfig if you have a wireless server because you’re crazy that way). Next, I define the DNS servers from my ISP. I didn’t list them here because my ISP probably would not appreciate that. Use OpenDNS if you’re really in a bind (pun not intended).

Let’s skip to the #BIND/NAMED section. Here, I’ve looped through each DNS server from my ISP. I’ve opened up the UPD and TCP ports to allow recursive look ups generated by my local network traffic. After that loop, I open up port 53 locally. Notice I’ve used the interface (-i #INTIF1). This restricts where requests may originate. You really only need this if you have more than one NIC card on the box.

Configuration

Before we can test, there’s one more thing we need to do: configure named/bind to listen to us. By default, bind is configured to only accept connections made ON THE SERVER. That doesn’t help when you want other computers on your network to be able to make requests. Open: /etc/bind/named.conf in vim. You need to change the listen-on directive to include your local network address. I just used “any” as my firewall will deny any other requests.

...
options {
  /* ... other configurations here ... */
  listen-on {any;};
};

Make sure you include the semi-colon after “any” or you’ll get named complaining at you. Restart named (bind).

%sudo /etc/init.d/named restart

Testing your server

You should now be able to test DNS lookups from another machine. From you local (non-DNS server), type:

%dig @DNSSERVER_IP_ADDRESS www.google.com
Replace DNSSERVER_IP_ADDRESS with the IP address of the server on which you just configured and installed bind/named. You should get something similar:
; <<>> DiG 9.4.1-P1 <<>> @DNSSERVER_IP_ADDRESS www.google.com -t A
; (1 server found)
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 13465
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 7, ADDITIONAL: 0

;; QUESTION SECTION:
;www.google.com.            IN    A

;; ANSWER SECTION:
www.google.com.        604748    IN    CNAME    www.l.google.com.
www.l.google.com.    249    IN    A    64.233.169.104
www.l.google.com.    249    IN    A    64.233.169.103
www.l.google.com.    249    IN    A    64.233.169.147
www.l.google.com.    249    IN    A    64.233.169.99

If you’re using Windows, you need to use nslookup (command line application). The syntax is slightly different. If you’re using Gentoo, you need to first install the bind tools (emerge net-dns/bind-tools).

That’s it! You now have a working local DNS server merely acting as a cache.

Posted in  | Tags , , , , , , , , ,  | 1 comment