Chrooted BIND on OS X
5th Feb 2008, 21:09:50
Updated: 13th December 2008.
It seems that more often than not, when an ISP is having an outage, it's caused by the DNS servers being unresponsive. Let's cut them out of the loop.
In this article, I hope to show you how to set up BIND both as an authoritative and caching DNS server. These instructions will work with Mac OS 10.4.x and 10.5.x.
On my home network, BIND does two jobs: Firstly, it is authoritative for the domain stocksy.co.uk. and secondly, I use it as a recursive name server to look up addresses of other computers, to do web browsing for example.
If you don't want to be authoritative for your domain, for example because someone else like your ISP or hosting provider already looks after your domain, that's fine - you can just set up the caching name server.
At your firewall, you must open TCP and UDP port 53 incoming. The DNS server will need to make outgoing connections from any source port to destination port 53.
Starting BIND
The BIND daemon (called named
) is already installed on OS X, you can confirm this:
$ named -v BIND 9.2.2
Before BIND will start, it's necessary to create a new /etc/named.conf
, since the
Apple-supplied one does not work well out of the box:
# cp /etc/named.conf /etc/poo.named.conf # echo "" > /etc/named.conf # vi /etc/named.conf
// Config file for BIND on Mac OS X, James Stocks 13/12/08 options { directory "/var/named"; }; //This tells BIND where to find the root servers (called ".") zone "." { type hint; file "root.hints"; }; //Tells BIND what 'localhost' is zone "localhost" { type master; file "db.localhost"; allow-update { none; }; }; //This is a reverse mapping from 127.0.0.1 to 'localhost' zone "0.0.127.in-addr.arpa" { type master; file "db.localhost.rev"; };
Now, we must go to the /var/named
directory and create some more files:
# cd /var/named # curl ftp://ftp.rs.internic.net/domain/named.root > root.hints # rm named.ca # mv localhost.zone db.localhost # mv named.local db.localhost.rev
At this point, we can start BIND with:
# named -c /etc/named.conf
You can verify that it has started by tailing /var/log/system.log
:
$ tail /var/log/system.log | grep named Jun 10 13:32:00 Power-Mac named[772]: starting BIND 9.2.2 -c /etc/named.conf Jun 10 13:32:00 Power-Mac named[772]: none:0: open: /private/etc/rndc.key: file not found Jun 10 13:32:00 Power-Mac named[772]: couldn't add command channel 127.0.0.1#953: file not found Jun 10 13:32:00 Power-Mac named[772]: none:0: open: /private/etc/rndc.key: file not found Jun 10 13:32:00 Power-Mac named[772]: couldn't add command channel ::1#953: file not found
Update, 13th December 2008: Please take note that in previous versions
of this article, I used the option query-source address * port 53; in
my named.conf. Because of
the DNS vulnerability discovered by Dan Kaminsky, this is a very bad idea, since
the randomness of the UDP port is an important mitigating factor in the attack. If you
are currently using this option, you should disable it as soon as possible.Many thanks go to Rick Moen for explaining this to me. |
||
Don't worry about named[772]: none:0: open: /private/etc/rndc.key: file not found
and friends,
we don't need rndc right now. Try querying the name server:
$ dig www.stocksy.co.uk @127.0.0.1 ; <<>> DiG 9.2.2 <<>> www.stocksy.co.uk @127.0.0.1 ;; global options: printcmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52336 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 2 ;; QUESTION SECTION: ;www.stocksy.co.uk. IN A ;; ANSWER SECTION: www.stocksy.co.uk. 7200 IN A 194.106.52.245 ;; AUTHORITY SECTION: stocksy.co.uk. 7200 IN NS ns2.stocksy.co.uk. stocksy.co.uk. 7200 IN NS ns1.stocksy.co.uk. ;; ADDITIONAL SECTION: ns1.stocksy.co.uk. 7200 IN A 194.106.52.245 ns2.stocksy.co.uk. 7200 IN A 69.93.127.12 ;; Query time: 3 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Sat Jun 10 14:45:14 2006 ;; MSG SIZE rcvd: 119
With luck, you'll get an answer like the above.
Those of you that have been paying attention will notice that named is running as root, this is a Really Bad Idea. Lets take care of that...
Securing BIND
BIND has had a lot of security problems in the past, but its recent record is better. It's still considered prudent to jail BIND and run it as its own user so that any vulnerabilities can't affect the rest of the system.
For OS X 10.4.x use niutil:
Use nireport to check for unused uid and gid values. I have chosen to start at 200 to avoid conflicts.
# nireport . /users uid # nireport . /groups gid
Use niutil to create a new user and group:
# niutil -create / /users/named # niutil -createprop / /users/named uid 200 # niutil -createprop / /users/named shell /usr/bin/false # niutil -createprop / /users/named home /var/chroot/named # niutil -create / /groups/named # niutil -createprop / /users/named gid 200 # niutil -createprop / /groups/named gid 200 # niutil -createprop / /groups/named users named
In 10.5.x, Apple changed things a bit, so do this instead:
Check for unused uid and gid values:
# dscl . -list /Users UniqueID # dscl . -list /Groups PrimaryGroupID
Create a user and group:
# dscl . -create /Users/named # dscl . -createprop /Users/named UniqueID 200 # dscl . -createprop /Users/named UserShell /usr/bin/false # dscl . -createprop /Users/named home /var/chroot/named # dscl . -create /Groups/named # dscl . -createprop /Users/named PrimaryGroupID 200 # dscl . -createprop /Groups/named PrimaryGroupID 200 # dscl . -createprop /Groups/named users named
Now we can create the chroot evironment - this is the same for both OS X 10.4.x and 10.5.x
# mkdir -p /var/chroot/named # cd /var/chroot/named # mkdir dev # mknod dev/null c 2 2 # mknod dev/zero c 2 12 # ln -s /dev/random dev/random # chmod 666 dev/null dev/random dev/zero # mkdir -p private/var/named # ln -s private/var var # mkdir var/run # mkdir private/etc # ln -s private/etc etc # cp /etc/named.conf etc/ # cp -r /var/named* var/ # chown -R named:named ../named # rm -r /etc/named.conf # rm -r /var/named # ln -s /var/chroot/named/var/named /var/named # ln -s /var/chroot/named/etc/named.conf /etc/named.conf
Find the named process you started earlier and kill it:
# kill -9 `cat /var/run/named.pid`
Start named
in its new home
# named -c /etc/named.conf -u named -t /var/chroot/named
Tail /var/log/system.log
once again to check it started OK.
You might notice that uncached queries are very slow, in the order of seconds rather than milliseconds, compare the below:
Mac OS X: ;; Query time: 4522 msec ;; SERVER: 172.16.0.81#53(172.16.0.81) ;; WHEN: Sat Jun 10 12:32:33 2006 ;; MSG SIZE rcvd: 148 NetBSD: ;; Query time: 405 msec ;; SERVER: 172.16.0.82#53(172.16.0.82) ;; WHEN: Sat Jun 10 12:33:01 2006 ;; MSG SIZE rcvd: 164
I dug around Google a bit and the prevailing theory seems to be that on OS X, named
attempts an IPv6 lookup,
waits for this to timeout and then tries an IPv4 lookup. I tried adding listen-on-v6 { none; };
to the
/etc/named.conf
options, but it made little difference. At this point, I lost patience and just compiled my own copy of BIND without IPv6 support:
Retrieve, unpack and compile the newest version of BIND. At the time of writing this was 9.4.3.
# curl -O http://ftp.isc.org/isc/bind9/9.4.2/bind-9.4.3.tar.gz # tar zxf bind-9.4.3.tar.gz && rm bind-9.4.3.tar.gz # cd bind-9.4.3 # ./configure --disable-ipv6 && make && make install
Now, BIND is started by specifying the full path to the named
binary:
# /usr/local/sbin/named -c /etc/named.conf -u named -t /var/chroot/named
To avoid confusion, I moved the old named
:
# mv /usr/sbin/named /usr/sbin/9.2.2.named.poo
Being Authoritative
BIND keeps DNS records for each zone in a different file. Create one for your domain and save it as something
like /var/named/db.stocksy.co.uk
:
$ORIGIN . $TTL 7200 ; 2 hours stocksy.co.uk IN SOA ns1.stocksy.co.uk. stocksy.stocksy.co.uk. ( 09060600 ; serial 14400 ; refresh (4 hours) 7200 ; retry (2 hours) 950400 ; expire (1 week 4 days) 7200 ; minimum (2 hours) ) NS ns1.stocksy.co.uk NS ns2.stocksy.co.uk A 194.106.52.245 MX 0 smtp.toastputer.net. MX 5 mail2.toastputer.net. $ORIGIN stocksy.co.uk. ns1 A 194.106.52.245 ns2 A 69.93.127.12 ftp A 194.106.52.245 www A 194.106.52.245
Now add the zone to /etc/named.conf
:
zone "stocksy.co.uk" { type master; file "db.stocksy.co.uk"; };
You'll need to restart named
for the change to take effect.
Using Views
My web server is behind a firewall which NATs my internal network to private subnet. As a result, if I try and visit my own web site to test it, the DNS server returns my external IP which does not work from inside. The way around this is to use views, allowing BIND to return different answers depending on which IP is querying it.
Essentially, just create a new directory, /var/named/internal
and copy all of your zone files into it.
All zones have to be in all views, regardless of whether you actually want to
offer different views for the zone:
// Config file for BIND on Mac OS X, James Stocks 13/12/08 options { directory "/var/named"; }; // Define the LAN acl "sprucewaylan" { // Replace this with the network address of your LAN (in CIDR notation) 172.16.0.0/24; }; // Begin LOCAL view "sprucewaylan" { //This view matches 172.16.0.0/24 match-clients { sprucewaylan; }; //We want to allow recursion (look up any domain) from the LAN recursion yes; //This tells BIND where to find the root servers (called ".") zone "." { type hint; file "internal/root.hints"; }; //Tells BIND what 'localhost' is zone "localhost" { type master; file "internal/db.localhost"; allow-update { none; }; }; //This is a reverse mapping from 127.0.0.1 to 'localhost' zone "0.0.127.in-addr.arpa" { type master; file "internal/db.localhost.rev"; }; //This is your zone zone "stocksy.co.uk" { type master; file "internal/db.stocksy.co.uk"; }; }; // End LOCAL // Begin INTERNET view "inet" { //This view matches anything not matched by the sprucewaylan ACL match-clients { any; }; //We don't want to allow recursion from the internet recursion no; //This tells BIND where to find the root servers (called ".") zone "." { type hint; file "root.hints"; }; //Tells BIND what 'localhost' is zone "localhost" { type master; file "db.localhost"; allow-update { none; }; }; //This is a reverse mapping from 127.0.0.1 to 'localhost' zone "0.0.127.in-addr.arpa" { type master; file "db.localhost.rev"; }; //This is your zone zone "stocksy.co.uk" { type master; file "db.stocksy.co.uk"; }; }; // End INTERNET
Update, 13th December 2008: Please take note that in previous versions
of this article, I used the option query-source address * port 53; in
my named.conf. Because of
the DNS vulnerability discovered by Dan Kaminsky, this is a very bad idea, since
the randomness of the UDP port is an important mitigating factor in the attack. If you
are currently using this option, you should disable it as soon as possible.Many thanks go to Rick Moen for explaining this to me. |
||
Now you're free to replace the external IPs in the internal zone files with your internal IP addresses.
launchd
Now you've got BIND working so well, you'll probably want it to start on boot. This is fairly easy to
accomplish using launchd
. First, unload the named
launchdaemon:
# cd /System/Library/LaunchDaemons # launchctl unload ./org.isc.named.plist
launchctl
may respond with 'No such process' - that's fine. Move the named
launchdaemon
out of the way and create a new one:
# cp org.isc.named.plist dist.org.isc.named.plist # echo "" > org.isc.named.plist # vi org.isc.named.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Disabled</key> <false/> <key>Label</key> <string>org.isc.named</string> <key>OnDemand</key> <false/> <key>ProgramArguments</key> <array> <string>/usr/local/sbin/named</string> <string>-c</string> <string>/etc/named.conf</string> <string>-u</string> <string>named</string> <string>-t</string> <string>/var/chroot/named</string> <string>-f</string> </array> <key>ServiceIPC</key> <false/> </dict> </plist>
This is just the /usr/local/sbin/named -c /etc/named.conf -u named -t /var/chroot/named
but XMLised.
The -f
option in the program arguments is needed to keep named
in the foreground (i.e. don't daemonise),
otherwise launchd
loses control of it.
Kill any existing named
daemon and use launchd
to kick off a new one:
# kill -9 `cat /var/run/named.pid` # launchctl load org.isc.named.plist
As usual, check /var/log/system.log
for any errors. If you feel like it, you could reboot to double check that
it really starts on boot.
1 Archived Comment
20th Jul 2010, 10:53:53 by huzia
This article's works to in SL (Snow Leopard) within my MBP (Macbook Pro), I've just tried it, though with a slight differences in killing named process. In the first named process killing, it should be replaced with:
kill -9 `cat /var/run/named/named.pid`
And in the second named process killing, it should be replaced with:
kill -9 `cat /var/chroot/named/var/run/named/named.pid`
After using launchd, if you want to kill named process, you should run this to minimize typing:
rm -r /var/run/named
rm -r /var/chroot/named/var/run/named
ln -s /var/chroot/named/var/run/named.pid /var/run/named.pid
Then if you want to kill/restart named, rather than going through the full path, you can go through the symbolic link instead:
kill -9 `cat /var/run/named.pid`
Oups, just don't forget to use sudo, or you can enable root if you want to.
Last word, Great post! :D