Recursive DNS+AD-Blocker — Part 3: Installing Unbound with DNSSEC on Synology NAS with Docker

Installing SecNS Unbound with DNSSEC enabled is quite easy, let’s see how you can configure it as the Unbound instance previously configured on Raspberry in Part 1 of this series of articles.

Gianni Costanzi
Nerd For Tech
9 min readMay 4, 2021

--

Introduction

As we did in Recursive DNS+AD-Blocker — Part 2: Installing Pi-hole without caching on Synology NAS with Docker, in order to have a redundant Pi-hole+Unbound stack (detailed in Recursive DNS Resolver with AD-Blocking Features) in my home network, I doubled also the Unbound server, choosing again my Synology NAS as a great target.

Choosing the Right Image

Since in Part 1 we did not enable DNSSEC validation in Pi-hole, we want to enable it in our Recursive DNS Resolver Unbound. After digging a while and doing some tests, I’ve chosen as image the one provided by SecNS, which is secns/unbound. This image is already configured with DNSSEC validation enabled, so it is fine for our purposes.

Unbound Container First Run

As shown in Part 2 for Pi-hole installation on Synology Docker, browse the registry, search for secns/unbound and download (pull) that image.

Before starting the container, I’ve prepared the following folders, creating them within the File Station app:

(I used usr_local_etc_unbound because I will map some files contained within it on the /usr/local/etc/unbound folder within the container. You can choose the naming convention that best fits your tastes).

I suggest you to launch the image and create a new container without setting volume and network mappings, just to retrieve some files. Suppose that you created a container named secns-unbound1, then you can retrieve the main configuration file /usr/local/etc/unbound/unbound.conf from the CLI (as root) of your Synology NAS with the following command:

If you don’t want to mess with the CLI as root, you can mount /volume1/docker/unbound/usr_local_etc_unbound/ on a temporary path /mnt within the container. Then you can double click on the container within the Synology Docker app, click on the Terminal tab and then create a new bash session. From within that session you can run the following command to accomplish the same task as with docker cp:

Mount /volume1/unbound/usr_local_etc_unbound to container’s /mnt folder
Copy container’s unbound.conf to /mnt (which is /volume1/docker/unbound/usr_local_etc_unbound)

Now you can customize the unbound.conf file that later we will mount over /usr/local/etc/unbound/unbound.conf in Read Only mode.

Unbound Control Certificates and Keys

If you want to monitor your Unbound performance, you can query it with unbound_control (utility included in unbound Linux installations) or with Python module unbound_console (more detail will follow in another article). If you give a look at the contents of /etc/unbound (on linux) or /usr/local/etc/unbound (on SecNS Unbound image) you can see the following 4 files:

The first 3 files are necessary in order to connect to the Unbound Control service, so if you want to interact with it, you must get those 3 files out from the container. You can accomplish this task as described for unbound.conf (via docker cp or by mounting a temporary folder).

In my home installation, where I do not have particular security concerns, I’ve got those 4 files from my Unbound Raspberry installation and then I’ve copied them in /volume1/docker/unbound/usr_local_etc_unbound and mounted them one by one in RO mode over the SecNS Unbound image (i.e. each unbound_xxx.yyy file mounted over /usr/local/etc/unbound/unbound_xxx.yyy within the container). In this way, I can use unbound_control command on my Raspberry to monitor both Raspberry and Synology Docker Unbound instances without specifying different pem and key files (when you launch the unbound_control tool it uses by default the /etc/unbound.conf configuration file of the Unbound Server to understand where it can find the control and server pem files and the control key to use to communicate with the target Unbound control instance).

Customizing unbound.conf

Follows the configuration I’m using on my SecNS Unbound container (I’ll show only uncommented directives). For more detail about the configuration you can have a look at Recursive DNS Resolver with AD-Blocking Features where you can see my Raspberry unbound configuration with more inline comments (configuration is slightly different, I did not uniform the two configuration files and I’ve simply modified the container one with the customizations made on Raspberry).

Note that I’ve changed default TCP and UDP DNS ports from 53 to 50053 as on my Raspberry configuration, but you can perfectly leave it with the default port 53 and then expose it to the outside world as 50053 (or whatever you like) via Docker’s Network Mappings if port 53 is already exposed for Pi-hole DNS service.

SecNS Unbound container unbound.conf file

Reconfiguring the Container for Final Use

Before putting the container in production, let’s modify its configuration from Synology Docker app while stopped. Select the container and press Modify.

Volume Bindings

Select Volume tab and then press Add File for each of the files.

In the image you can see that I’ve mounted the configuration file and the 4 SSL-related files copied from my Raspberry installation (not required, as explained before). I’ll show the final results that you can see by double clicking on the container.

Map external configuration files over the original ones within Unbound Container

Port Mappings

Then, I’ve exposed port 50053 for both TCP and UDP. I need to expose them because I want my Pi-hole running on Raspberry to point both Raspberry’s and Synology NAS Container’s Unbound instances, otherwise if you want to point Unbound only from a Pi-hole running on the same docker instance and on the same non-default bridge network, you don’t need to expose Unbound DNS ports to the outside world. I’ve exposed also port 8953/TCP which is the port required to monitor Unbound performances with unbound_control or unbound_console.

Publish DNS and Unbound Control ports

Verifying DNSSEC

Detailed DNSSEC tests are shown in Part 1, BTW I show a quick test here. I’ve run the following commands from my Raspberry pointing at the SecNS Unbound exposed on nas.homenetwork IP address on port 50053/UDP:

2xPi-hole + 2xUnbound Redundancy

Now that we have configured a Pi-hole + Unbound stack on Raspberry and one on Synology NAS, we can finally have a more redundant solution by pointing both Unbound instances from each Pi-hole. This can be done from the Settings → DNS configuration panel on Pi-hole GUI defining a second Custom Upstream DNS Server as desired.

Here I’ve found a big problem, I can’t specify a FQDN (Fully Qualified Domain Name) to point a Custom DNS, so on my Pi-hole running on Synology Docker I can’t point the Unbound running on the same Docker network by name (es: secns-unbound1). I could check the IP assigned by docker to secns-unbound1 with the command docker inspect secns-unbound1 on the network shared with my Pi-hole container, but it could change if the container is re-created from scratch. As a solution, I’ve chosen a private IP address 192.168.1.100 which is outside my home LAN, so the traffic toward that IP is directed via default route to my MikroTik router, as a special IP address to use as Upstream DNS.

Let’s suppose that my Home LAN is 10.0.0.0/24, the home router is 10.0.0.254 and the NAS is 10.0.0.100. I can configure Pi-hole running on 10.0.0.100 to point as Upstream DNS the IP 192.168.1.100.

A DNS Query from Pi-hole to SecNS Unbound exits the NAS with source IP 10.0.0.100 and destination IP 192.168.1.100. MikroTik router receives the traffic (it is necessary to use an IP outside the 10.0.0.0/24 otherwise the NAS will try to reach it directly without sending the traffic to its default gateway, unless you directly specify the router IP address as DNS) and then it replaces source 10.0.0.100 with 10.0.0.254 (SNAT on the router IP on the LAN) and destination 192.168.1.100 with 10.0.0.100 (DNAT to NAS). The NAS receives the query and sends it to the internal address of SecNS Unbound. Now Unbound replies to the source of the query: here you can see that it is necessary to have a Source NAT, otherwise if the source was 10.0.0.100 Pi-hole would receive a reply directly from the IP of the NAS (which is again 10.0.0.100) instead of the 192.168.1.100 it is expecting. So, thanks to the SNAT, the reply goes back to MikroTik router, the inverse of the previous SNAT/DNAT takes place and the response packet with source 192.168.1.100 and destination 10.0.0.100 arrives at the NAS which then in turn sends it to the internal IP of Pi-hole container.

You can implement this SNAT/DNAT with MikroTik with these two lines (the second one seems strange, because source and destination addresses are the same, but this is due to the fact that the destination has already been changed by DNAT, which happens before the SNAT):

Unfortunately the SNAT/DNAT solution can not be implemented on less configurable and more common home routers, so maybe using the ip shown by docker inspect can be the only solution, unless you have a linux box like Raspberry, in which case you can do the same trick by pointing the ip of the Raspberry and a specific port and then applying SNAT/DNAT with iptables, with the same logic as in my MikroTik example:

I’ve tried also a simpler solution by pointing as Upstream DNS the IP of the NAS 10.0.0.100 and the port 50053 which is mapped by Unbound container but then I’ve seen that the reply comes back to the Pi-hole container with the the GW IP of the inner bridge lan 172.18.1.1 instead of 10.0.0.100:

Conclusions

I hope you’ve found this article useful, and if you have an idea to avoid SNAT/DNAT to implement Pi-hole to Unbound communication between containers on the same Docker instance, let me know with a comment :)

In the next article of this series I’ll show you how I’ve implemented a monitoring script that collects metrics from Pi-hole servers and uploads to an InfluxDB2 server to allow me plotting useful info on a Grafana Dashboard:

--

--

Gianni Costanzi
Nerd For Tech

Network Engineer, Music Lover, Motorbike Rider, Amateur Photographer, Nerd-inside