by: Helge, published: Jun 9, 2024, in

Unbound: Conditionally Include Only Existing (Docker) Interfaces

This article presents a simple and reliable solution to flexibly configure Unbound with interfaces that may not exist when Unbound starts. The setup described below is based on my home server Unbound installation but should work well in other scenarios, too.

Problem: Unbound Doesn’t Start if Interfaces Don’t Exist

I need Unbound to bind to and listen on the Caddy Docker interface. That interface is created at boot time when the Docker service is started. Unbound, however, is started before Docker. At that point in time, however, the Docker interface doesn’t exist yet, and Unbound obstinately refuses do start if one its configured interface doesn’t exist.

Solution: Include Only Existing Interfaces

After quite a bit of deliberation I came up with the following solution.

We’re adding an include file to Unbound’s configuration. The content of the include file is generated dynamically by a pre start script of Unbound’s systemd service. Said script checks for the Docker interface’s existence and only adds it to the include file if it is found.

Additionally, we’re adding a post start command to Docker’s systemd service. This command restarts Unbound to ensure that Unbound re-checks for the Docker interface’s existence once Docker is up.

Preparation

Pre Start Script

Create the file /usr/local/bin/unbound-conditional-include.sh with the following content:

#!/bin/bash

# The Unbound config include file this script creates
INCLUDE_FILE=/etc/unbound/unbound.conf.d/conditional-include.conf

# Interfaces to test for existence
declare -a interfaces2test=("docker_caddy" "dummy_nx_if_2")

# Create/empty the include file
echo "# Conditional include file for Unbound."  > ${INCLUDE_FILE}
echo "# Automatically generated. Do not modify.\\n"  >> ${INCLUDE_FILE}

# Loop through and test all interfaces defined above
for i in "${interfaces2test[@]}"
do
   ip a show $i up 1>/dev/null 2>&1
   if [ $? -eq 0 ]; then
      # Interface is up
      echo "   interface: $i"  >> ${INCLUDE_FILE}
   fi
done

Make unbound-conditional-include.sh executable:

chmod a+x /usr/local/bin/unbound-conditional-include.sh

Unbound Service Override

Create a unit file snippet for the Unbound service (under the hood, this creates the override file /etc/systemd/system/unbound.service.d/override.conf):

systemctl edit unbound

Paste the following two lines in the editor window that opened and save the file:

[Service]
ExecStartPre=+/usr/local/bin/unbound-conditional-include.sh

Note the plus (+) sign before the command. It instructs systemd to run the command with full root privileges (docs). Without this we couldn’t write to /etc/unbound

Test

Restart Unbound and make sure that /etc/unbound/unbound.conf.d/conditional-include.conf is created:

systemctl restart unbound
la /etc/unbound/unbound.conf.d/

Unbound Configuration

Edit your Unbound configuration file /etc/unbound/unbound.conf, adding an include directive after the interface definitions:

   # Include additional interfaces from a config file that is dynamically generated to only contain existing interfaces.   
   include: /etc/unbound/unbound.conf.d/conditional-include.conf

Check and Restart

Check the new Unbound configuration and restart the daemon to make sure everything works:

unbound-checkconf
systemctl restart unbound

Re-Evaluation When Docker Is Up

Once Docker is started, Unbound should bind to Docker’s interface. To achieve that, we add a command to its service configuration that restarts Unbound.

systemctl edit docker

Paste the following two lines in the editor window that opened and save the file:

[Service]
ExecStartPost=+systemctl restart unbound

Notes

Docker Interfaces When Docker is Stopped

When the Docker service/daemon is stopped, its interfaces do not go away. This means that there is not need to restart Unbound in order to trigger a re-run of the script that generates the configuration include.

Previous Article No Bluetooth Audio In Zoom Android App: Solution and Explanation
Next Article Samba Active Directory in a Docker Container: Installation Guide