Dnsmasq "Split DNS" On Single Interface

posted Sep 7, 2017

After adding Pi-hole to my Simple Home Internet Filter, I needed a way to configure separate "policies" applied to particular devices. Ideally, I'd want the kids devices to get all the blocking and the adults devices to have only the ads blocked (the Pi-hole awesomeness). Well it turns out you can't really do that sort of thing with the available DNS configuration alone. The DHCP configuration could direct certain devices to use a different DNS IP address, but that's not quite what I'm looking for. I want to do it all over a single network interface (the ethernet port on the Raspberry Pi).

The consensus seems to be that it can't be done by dnsmasq alone unless you want to run a second instance of dnsmasq on a separate network interface. Not quite good enough, I want one IP doing everything. So I'm going to run a second DNS server on port 5353 and use iptables to forward clients on a certain subnet to port 5353 when they connect in on port 53. The iptables configuration is really pretty straight forward, I'll detail that later. The real challenge here turned out to be how to best go about running the second dnsmasq instance.

Default DNS Server

The content filtering DNS server should probably be configured in the regular Raspbian maintained dnsmasq running on port 53. I performed a clean install of dnsmasq and Pi-hole, manually adding the Norton ConnectSafe "Policy C" IP addresses during the Pi-hole configuration wizard. Then placed my other custom DNS/DHCP configuration changes into /etc/dnsmasq.d/00-home.conf to keep my settings a little more portable.

Alternative DNS Server

The regular dnsmasq service is started via the /etc/init.d/dnsmasq script. It uses settings from /etc/default/dnsmasq to decide exactly what arguments to use when starting the service. We don't have to reverse engineer all that though, lets just look at the running process and read the man page to see what we need to do for this second service.

ps aux | grep dnsmasq

The binary is /usr/sbin/dnsmasq, I'll split the  arguments up and look at each of the ones that need to change.

I'm going to need a separate PID file...

-x /run/dnsmasq/dnsmasq.pid

Resolve file is not used anyway, so this can be omitted...

-r /run/dnsmasq/resolv.conf

The config directory will need to change...

-7 /etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new

Something else we need to override is the location of the default config file. I'm also going to include the port=5353 parameter. I'm going to create a new directory /opt/splitdnsmasq for this little project and put everything in there so I can easily tar the whole thing and copy it elsewhere as required.

1. Put all the new arguments together into /opt/splitdnsmasq/dnsmasq5353.sh

#!/bin/bash

/usr/sbin/dnsmasq -x /opt/splitdnsmasq/dnsmasq5353.pid -u dnsmasq --port=5353 --conf-file=/opt/splitdnsmasq/dnsmasq.conf -7 /opt/splitdnsmasq/conf.d --local-service --trust-anchor=.,19036,8,2,49aac11d7b6f6446702e54a1607371607a1a41855200fd2ce1cdde32f24e8fb5 --trust-anchor=.,20326,8,2,e06d44b80b8f1d39a95c0b0d7c65d08458e880409bbc683457104237c7f8ec8d

2. Created a copy of /etc/dnsmasq.d/01-pihole.conf and placed it in /opt/splitdnsmasq/conf.d. Replace the "server" options with ISP DNS servers, we don't want upstream DNS servers that are attempting to filter content. The config references hosts files maintained by Pi-hole, so any block list updates will be applied to this server too.

3. Created /opt/splitdnsmasq/dnsmasq.conf with just the local network DNS settings:

no-hosts        # Don't read the hostnames in /etc/hosts

domain=home     # Local domain

local=/home/    # Don't forward requests for the local domain upstream

addn-hosts=/etc/hosts.home       # Local domain hosts

expand-hosts    # Expand local hostnames with our domain, e.g. hostname.domain

4. Create a minimal init.d script /opt/splitdnsmasq/init.d/splitdnsmasq:

#! /bin/sh

### BEGIN INIT INFO

# Provides:       splitdnsmasq

# Required-Start: $network $remote_fs $syslog

# Required-Stop:  $network $remote_fs $syslog

# Default-Start:  2 3 4 5

# Default-Stop:   0 1 6

# Description:    Split DNS server

### END INIT INFO

case "$1" in

  start)

    /opt/splitdnsmasq/dnsmasq5353.sh

    ;;

  stop)

    pkill -F /opt/splitdnsmasq/dnsmasq5353.pid

    rm -f /opt/splitdnsmasq/dnsmasq5353.pid

    ;;

  *)

    echo "Usage: /etc/init.d/splitdnsmasq {start|stop}"

    exit 1

    ;;

esac

exit 0

5. Make it start on boot:

cp /opt/splitdnsmasq/init.d/splitdnsmasq /etc/init.d

chmod +x /etc/init.d/splitdnsmasq

update-rc.d splitdnsmasq defaults

Port Forwarding

I wanted to reserve about 10 addresses for unfiltered DNS. I went for the range 10.0.0.1 - 10.0.0.14 using a subnet calculator, which will be denoted by 10.0.0.0/28 for use in my iptables rules. Firstly I went back to my DHCP configuration to confirm that all the devices I wanted to benefit from ad blocking without the filtering would have lease reservations in this range, and nothing else. Then I put the iptables commands to do the appropriate DNS port forwarding in place...

/sbin/iptables --table nat -A PREROUTING -p udp --source 10.0.0.0/28 --dport 53 -j REDIRECT --to-ports 5353

/sbin/iptables --table nat -A PREROUTING -p tcp --source 10.0.0.0/28 --dport 53 -j REDIRECT --to-ports 5353

Then to save iptables, you'll probably want...

apt-get install iptables-persistent

If the package didn't already exist, it'll prompt you to save your current rules (bonus). If not then you'll just have to run the following...

iptables-save