Building a Debian Based VPN Router - Part 2 - DNS/DHCP


DNS

So lets get a DNS server set up for your network, I'm using unbound for this as it's way easier than setting up a recursive BIND server and really I'm not wanting to do anything advanced other than serve out DNS requests and maybe serve out a local zone.

The configuration file syntax also lends itself quite nicely to automation which I'll demonstrate later.

So simple stuff first, lets install unbound and ensure the service is enabled for boot:

1
2
apt install unbound
systemctl enable unbound

Now you'll want to set some default configuration for unbound:

/etc/unbound/unbound.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Unbound configuration file for Debian.
#
# See the unbound.conf(5) man page.
#
# See /usr/share/doc/unbound/examples/unbound.conf for a commented
# reference config file.
#

server:

do-udp: yes
do-tcp: yes
do-ip6: no
do-ip4: yes

harden-glue: yes
hide-version: yes

private-address: 192.168.0.0/16
private-address: 172.16.0.0/12
private-address: 10.0.0.0/8

logfile: /var/log/unbound/unbound.conf

hide-identity: yes
prefetch: yes

interface: 127.0.0.1
interface: 192.168.1.1
interface: 192.168.2.1

harden-dnssec-stripped: yes

access-control: 127.0.0.1/32 allow
access-control: 192.168.0.1/16 allow

include: "/etc/unbound/unbound.conf.d/*.conf

Here I'm basically setting up a very simple unbound server. The main parts to take from this are that I'm listening on 53, allowing TCP and UDP for the following interfaces:

  • 127.0.0.1
  • 192.168.1.1
  • 192.168.2.1

I'm also setting up access control so only requests from 127.0.0.1 and 192.168.0.1/16 are allowed. So if somehow my DNS server was opened up to the net, it still wouldn't facilitate requests.

So at present these requests will still be going out over the WAN, but in Part 3 I'll explain how we ensure these requests go out over the VPN tunnels.

So I also like to serve a local zone out of this unbound server, and you might like to do the same, so it's as simple then as adding the zone conf into /etc/unbound/unbound.conf.d/zone.conf.

Here's my local synforge.net zone as an example:

/etc/unbound/unbound.conf.d/synforge.net.conf

1
2
3
4
local-zone: "synforge.net." static

local-data: "router-1.synforge.net      A      192.168.1.1"
local-data: "server-1.synforge.net      A      192.168.1.100"

Don't forget to restart unbound to load the changes

1
systemctl restart unbound

If you now do a dig against this server you should find you should be able to resolve requests, both internal and external.

Now we have a DNS server set up we need to now set up DHCP to give our devices DHCP reservations.

Before you continue, change your router's resolv.conf to point to 127.0.0.1. and this is REALLY IMPORTANT, you want to disable dhclient from assigning your ISP DNS servers!

1
echo "nameserver 127.0.0.1" > /etc/resolv.conf

Then open up /etc/dhcp/dclient.conf and ensure the following exists:

1
supersede domain-name-servers 127.0.0.1;

This will ensure that dhclient updates resolv.conf to use 127.0.0.1. There is an alternative here in that you can remove domain-name-servers from the request which will mean it will never be updated. Use whichever feels nicer for you.

You can test this has taken effect by using the following and checking your /etc/resolv.conf afterwards.

1
dhclient -v enp1s0f0

Check your resolv.conf after and if it still has nameserver 127.0.0.1 then you're good.

One last thing we need to do is we need to allow our subnets to connect into port 53 (TCP and UDP) to make DNS requests.

1
2
3
4
5
6
iptables -t filter -A INPUT -s 192.168.1.0/24 -d 192.168.1.1/32 -p tcp --dport 53 -j ACCEPT
iptables -t filter -A INPUT -s 192.168.1.0/24 -d 192.168.1.1/32 -p udp --dport 53 -j ACCEPT
iptables -t filter -A INPUT -s 192.168.2.0/24 -d 192.168.2.1/32 -p tcp --dport 53 -j ACCEPT
iptables -t filter -A INPUT -s 192.168.2.0/24 -d 192.168.2.1/32 -p udp --dport 53 -j ACCEPT
iptables -t filter -A INPUT -s 192.168.1.1/32 -d 192.168.1.0/24 -p udp --sport 53 -m state --state ESTABLISHED -j ACCEPT
iptables -t filter  -A INPUT -s 192.168.2.1/32 -d 192.168.2.0/24 -p udp --sport 53 -m state --state ESTABLISHED -j ACCEPT

Make sure you save your iptables rules after this so they will persist a reboot

1
iptables-save > /etc/iptables/rules.v4

DHCP

I'm using ISC DHCP server in this case, however I have toyed with the idea of migrating to dnsmasq to allow me to automatically create DNS names from the client identifier. Ultimately I quite like the control over DNS so I'm sticking with ISC and Unbound for now.

So, as usual you need to install the package and enable the service.

1
2
apt install isc-dhcp-server
systemctl enable isc-dhcp-server

Again we just want to simply configure this service so I'm going to give my basic configuration for you to decide what you need to implement.

/etc/dhcp/dhcpd.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
option domain-name "synforge.net";
option domain-name-servers ns1.synforge.net;
default-lease-time 21600;
max-lease-time 43200;
ddns-update-style none;

authoritative;

subnet 192.168.1.0 netmask 255.255.255.0 {
  range 192.168.1.10 192.168.1.200;
  option routers 192.168.1.1;
  option domain-name-servers 192.168.1.1;
}

subnet 192.168.2.0 netmask 255.255.255.0 {
  range 192.168.2.10 192.168.2.200;
  option routers 192.168.2.1;
  option domain-name-servers 192.168.2.1;
}

host server-1 {
  hardware ethernet 00:11:22:33:44:55;
  fixed-address server-1.synforge.net;
}

All we're setting up here is the subnets and what ranges are advertised out, it's important to note here that the router and domain-name-server advertised out is different per subnet.

I've also got an example here that shows a fixed address for a host, I'm using the DNS name.

Afterwards make sure this service has been restarted and check if you get a DHCP reservation.

1
systemctl restart isc-dhcp-server

That's basically DHCP set up, start getting your clients on the network and testing it out.

Ad Blocking

One feature of PFSense that was really good was the ability to use PFBlockerNG, admittedly I only ever use the DNS blacklisting part of it rather than the IP blocking so I'll show you how you can implement this yourself.

Since Unbounds syntax is pretty simplistic you can easily just grab Ad blocking lists from various sources and transform this into Unbound zones.

I'm currently using this blocklist however the principle is the same for any list, pull down the list, transform it into unbound's zone syntax, and restart unbound.

So lets set up a script to do this for us:

/opt/synroute/update-adblocker.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/bin/bash

TMP="/tmp/adblocking.conf"
CONF="/etc/unbound/unbound.conf.d/adblocking.conf"

echo "" > $TMP

HOSTNAMES="https://raw.githubusercontent.com/notracking/hosts-blocklists/master/hostnames.txt"
curl $HOSTNAMES | grep '^0\.0\.0\.0' | awk '{print "local-zone: \""$2"\" redirect\nlocal-data: \""$2" A 0.0.0.0\""}' >> $TMP

DOMAINS="https://raw.githubusercontent.com/notracking/hosts-blocklists/master/domains.txt"
curl $DOMAINS | grep '^address=' | awk -F "/" '{print "local-zone: \""$2"\" redirect\nlocal-data: \""$2" A 0.0.0.0\""}' >> $TMP

mv $TMP $CONF

systemctl restart unbound

Now whenever we run this it will create a config file that looks something like this:

1
2
3
4
5
6
local-zone: "dating.ezstatic.com" redirect
local-data: "dating.ezstatic.com A 0.0.0.0"
local-zone: "dating.majorwap.com" redirect
local-data: "dating.majorwap.com A 0.0.0.0"
local-zone: "daufans.cu.ma" redirect
local-data: "daufans.cu.ma A 0.0.0.0"

This will mean your DNS server will refuse requests for these domains.

Set this up as a cron and you'll have a constantly updated list of domains to block:

1
0 0 * * * /opt/synroute/update-adblocker.sh

Lets confirm that the blocker is working as expected:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
dig track.msadcenter.psof.com

; <<>> DiG 9.10.3-P4-Debian <<>> track.msadcenter.psof.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 39538
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

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

Summary

We're now good to continue with the next part, getting our VPN clients set up.

Comments