guinix TechNote: "Dualling" Nameservers on OpenBSD
Configuring tinydns to serve both public and local networks
from a single host
Introduction
Imagine you are ready to publish names for your own domain(s)
for DNS queries coming in from the Internet. You have already
read about
Dan Bernstein's
djbdns
suite of nameserving tools, and thanks to
this
article have had a basic tutorial for getting started. The question is, can you now publish domain name information for
the Internet, as well as for your local network, from the same host? Yes, very easily. The technique is known as "split-view"
or "split-horizon" DNS. The example here will show: - one instance of tinydns publishing DNS records for your
publicly accessible hosts, for public consumption
- another instance of tinydns publishing DNS records for
the hosts on your local network, for local consumption
- plus dnscache, a caching resolver
making and storing DNS lookups for clients on the local
network
All running concurrently on a single OpenBSD host. Background
The example here describes our own network at
guinix.com.
In this case we have a single public IP address, from which we
host multiple domains with web and email services. Our gateway host is called "nimba", running OpenBSD 3.2. It is
dual homed on 192.168.0.254 and 10.0.1.254. The 192.168.0.1 address
is considered the "external" interface, as it connects directly to
a port-forwarding router with our public IP address. The 10.0.1.254
address is the "internal" interface, to our local 10.0.1.0/24
network. On OpenBSD, packet forwarding is enabled in "/etc/sysctl.conf"
with the line:
net.inet.ip.forwarding=1
Our domain name registrar has been configured to point DNS queries
for our domains to our public IP address; our external router then
port-forwards these queries (port 53) on to "nimba". (For more
information about our network configuration and packet filter setup,
see
this TechNote.) The DNS services that we'll put up on "nimba" may then
be summarized as follows: Service |
Interface |
IP address |
---|
"tinydns-public" |
external |
192.168.0.254 | "tinydns-local" |
loopback |
127.53.0.1 | "dnscache" |
internal |
10.0.1.254 |
That is, "tinydns-public" will publish DNS records for queries
that are presented to the external interface. "tinydns-local" will
run on a private loopback address, publishing DNS records for the
local network, responding only to queries presented to it via
"dnscache". "dnscache" will resolve nameserver queries coming from
the local network, for either public or local DNS records, polling
either the Internet or "tinydns-local" as appropriate. Clients on
the local network may then all be configured to use 10.0.1.254 for
their caching resolver. Recall that on OpenBSD, to provide a private loopback address
of 127.53.0.1 for the "tinydns-local" configuration, you can set up
"/etc/hostname.lo1" with this entry:
inet alias 127.53.0.1 netmask 0xffffffff
That just about takes care of all the preliminaries.
Now we move on to the doing! Installation and Configuration
First, download and install daemontools.
We follow "the djb way" exactly here and use "/service" as the
daemontools service directory. Swipe a modified
start-up script from FreeBSD
here
and put it into "/usr/local/etc/rc.d/svscan.sh". Then add the
following stanza to the bottom of "/etc/rc.local":
## daemontools startup:
## --this starts up djbdns, qmail, etc.
if [ -x /usr/local/etc/rc.d/svscan.sh ]; then
/usr/local/etc/rc.d/svscan.sh start
sleep 5
fi
Grab and build
djbdns
next. The tutorial
found
here may be helpful background. Create the unpriveleged users
"tinydns", "dnscache" and "dnslog" as described in the article
before proceeding. Nameserver #1: tinydns-public
Now become super-user and configure the public nameserver:
# tinydns-conf tinydns dnslog \
# /usr/local/etc/djbdns/tinydns-public 192.168.0.254
Important: On OpenBSD you will need to increase the
"softlimit" argument in the tinydns run script just installed in
"/usr/local/etc/djbdns/tinydns-public/run". Edit this file and
change the -d parameter to read:
softlimit -d350000
Next create the DNS records that will be served out to the Real
World, adding the public nameserver, mail exchanger, host and alias
records as necessary. Change to
"/usr/local/etc/djbdns/tinydns-public/root" and use "add-ns",
"add-mx", "add-host", and "add-alias" to configure the database:
# ./add-ns guinix.com 199.104.115.195
# ./add-ns 115.104.199.in-addr.arpa 199.104.115.195
# ./add-mx guinix.com 199.104.115.195
# ./add-host nimba.guinix.com 199.104.115.195
# ./add-alias www.guinix.com 199.104.115.195
# ./add-alias dns1.guinix.com 199.104.115.195
# ./add-alias dns2.guinix.com 199.104.115.195
(...)
# make
(In this puny network, every DNS record will resolve to
our single lonely public IP address.) Nameserver #2: tinydns-local
Continuing as super-user, configure the local nameserver:
# tinydns-conf tinydns dnslog \
# /usr/local/etc/djbdns/tinydns-local 127.53.0.1
As described for tinydns-public above, edit the run script and
increase the softlimit parameter. Now change to "/usr/local/etc/djbdns/tinydns-local/root"
and install the DNS records for the local network:
# ./add-ns guinix.com 127.53.0.1
# ./add-ns 1.0.10.in-addr.arpa 127.53.0.1
# ./add-host nimba.guinix.com 10.0.1.254
# ./add-alias www.guinix.com 10.0.1.254
# ./add-alias smtp.guinix.com 10.0.1.254
# ./add-alias pop.guinix.com 10.0.1.254
# ./add-host rocky.guinix.com 10.0.1.8
# ./add-host alloy.guinix.com 10.0.1.10
# ./add-host slate.guinix.com 10.0.1.16
(...)
# make
You can see that some of the hosts are physically the same
machines as those on the public network--such as "nimba" and
"www"--but this nameserver will let us find them from the local
network, with their local network addresses. Then, when a local
client browses to
http://www.guinix.com/
this client will see the very same website as a visitor from the
Great Beyond. Caching resolver: dnscache
Finally, install a DNS resolver for clients on the local network:
# dnscache-conf dnscache dnslog \
# /usr/local/etc/djbdns/dnscache 10.0.1.254
Configure it to accept queries from clients on the local
network:
# cd /usr/local/etc/djbdns/dnscache/root/ip
# touch 10.0.1
Point queries regarding the local network to the tinydns-local
nameserver publishing on 127.53.0.1:
# cd /usr/local/etc/djbdns/dnscache/root/servers
# echo "127.53.0.1" > guinix.com
# echo "127.53.0.1" > 1.0.10.in-addr.arpa
Here's the magic "glue" that points dnscache to tinydns-local
for local DNS records. For all other lookups, dnscache will
recursively seek over the Internet. Start me up!
Now everything is configured and ready to go. To start up the
servers, just link them into the daemontools service directory:
# ln -s /usr/local/etc/djbdns/tinydns-public /service
# ln -s /usr/local/etc/djbdns/tinydns-local /service
# ln -s /usr/local/etc/djbdns/dnscache /service
daemontools will automatically start the new servers
within 5 seconds. Clients may now be configured to access the
dnscache resolver by editing "/etc/resolv.conf" (or equivalent) on
local hosts to read:
domain guinix.com
nameserver 10.0.1.254
Try some queries with djbdns utilities:
$ dnsip alloy.guinix.com
10.0.1.10
$ dnsip www.openbsd.org
129.128.5.91
$ dnsname 10.0.1.8
rocky.guinix.com
Browse and bop and email and ssh, inside or out. That's it!
"Dualling" nameservers on a single host.
ADDENDUM: Using a single nameserver with tagged records
It is possible to obtain similar results with a single
instance of tinydns, by "tagging" the DNS records in the
data file with a location code. The method is described here. Step 1. Configure a single tinydns similar to
Nameserver #1 above:
# tinydns-conf tinydns dnslog \
# /usr/local/etc/djbdns/tinydns 192.168.0.254
As before, increase the "softlimit" parameter in the
daemontools run script, "/usr/local/djbdns/tinydns/run". Step 2. Create/edit the data file in
"/usr/local/djbdns/tinydns/root/data" with your favorite text
editor. Enter DNS records tagged with location codes as follows:
# tinydns "data" file for split-view dns
#
# define external location code:
%ex
#
# define external records:
#
# NS:
.guinix.com:199.104.115.195:a:259200::ex
.115.104.199.in-addr.arpa:199.104.115.195:a:259200::ex
#
# HOST:
=nimba.guinix.com:199.104.115.195:86400::ex
#
# MX:
@guinix.com:199.104.115.195:a::86400::ex
#
# ALIAS:
+dns.guinix.com:199.104.115.195:86400::ex
+dns1.guinix.com:199.104.115.195:86400::ex
+dns2.guinix.com:199.104.115.195:86400::ex
+www.guinix.com:199.104.115.195:86400::ex
#
# -----
#
# define internal location code:
%in:192.168.0
%in:10.0.1
#
# define internal records:
#
# NS:
.guinix.com:192.168.0.254:a:259200::in
.0.168.192.in-addr.arpa:192.168.0.254:a:259200::in
#
# HOST:
=nimba.guinix.com:192.168.0.254:86400::in
#
# ALIAS:
+www.guinix.com:192.168.0.254:86400::in
+smtp.guinix.com:192.168.0.254:86400::in
+pop.guinix.com:192.168.0.254:86400::in
#
# MORE HOSTS:
=rocky.guinix.com:10.0.1.8:86400::in
=alloy.guinix.com:10.0.1.10:86400::in
=alloy.guinix.com:10.0.1.16:86400::in
# ...
Note here that in order to apply location codes, the data file
must be edited manually. That is, the "./add-*" utilities by
themselves will not be adequate. (See Bernstein's
tinydns-data
documentation page for the syntax of all tinydns record types.) In the example data above, two location codes are defined. The
syntax for a location code definition is: %lo:ipnet where "lo" is a location code identifier (one or two letters),
and "ipnet" is an IP address prefix. When a DNS record is tagged
with a location code lo in the last field, that record will
be made visible only to client requests originating from addresses
with an ipnet prefix. tinydns selects the location code for
an incoming DNS query according to the definition that matches the
longest ipnet prefix of the remote host. The first location code in the example is "ex", to identify
records to return from requests outside the local network. Here
the ipnet parameter is empty. This expresses a default match
with a request originating from any IP address. The second location code is "in", defined here to match addresses
originating from the local network. Now records tagged with "in"
will match requests coming from the local network (prefixes 192.168.0
and 10.0.1), and records tagged with "ex" will match all other
requests. Step 3. After creating/editing the data file, convert
it as before to the cdb format used by tinydns:
# cd /usr/local/etc/djbdns/tinydns/root
... (create/edit data file) ...
# make
Step 4. Set up the dnscache service as
described above, except now point dnscache to the tinydns running
on 192.168.0.254:
# cd /usr/local/etc/djbdns/dnscache/root/servers
# echo "192.168.0.254" > guinix.com
# echo "192.168.0.254" > 1.0.10.in-addr.arpa
Step 5. Link the services into the daemontools
"/service" directory:
# ln -s /usr/local/etc/djbdns/tinydns /service
# ln -s /usr/local/etc/djbdns/dnscache /service
The new services will automatically start under daemontools
within 5 seconds. Queries from the local network will then be
answered with DNS records tagged "in", while outside requests
will be answered with DNS records tagged "ex". Which method is better? That's for you to decide! I
prefer the dual nameserver approach. It is easy to set up and
maintain, the records are simpler and fewer. Moreover, it is a
modular approach and "conceptually cleaner" than running a single
nameserver, in that the segregation of services and DNS data matches
our intentions. Also, the external addresses in our network are
fairly static, whereas the internal network configuration changes
more frequently. With a separate nameserver for the local network,
we don't have to worry about unintentionally mucking up public
records in the external nameserver. Others may prefer to lump all DNS records in a single file. This
is the more "monolithic" approach, and may suit users who are
more accustomed to working with monolithic systems. Still other networks may benefit from a combination of these
methods. Whatever your own preference or network configuration,
the cool thing here is that you do have the choice!
|