Simple DNS Blackhole

By

Ads being sucked into a DNS blackhole

Tired of Annoying Ads and Privacy-Invading Trackers? Here’s How to Take Control

Are you frustrated with pop-up ads in your browser or ads cluttering news articles? Not to mention those pesky privacy-invading trackers? You’re not alone — but the good news is, you don’t have to put up with them.

The first step is to enable privacy settings in your browsers and install an ad-blocking extension like uBlock Origin. This will block ads in your browser, but that’s just one part of the solution. You can also take things a step further by setting up a DNS blackhole on your local network. This will send ads and trackers to a dead-end, keeping your devices protected across your entire home network.

A DNS blackhole works by redirecting requests for known ad-serving and tracker domains to a non-existent address, effectively blocking them. There are many options available, from free, DIY solutions to paid DNS subscription services.

If you’re running your own home network with a Unix-based server, you can easily integrate a DNS blackhole into a local BIND DNS server on your router — and best of all, it’s completely free. If you’re already using BIND and know your way around it, here’s how I set up a DNS blackhole on my FreeBSD server.

Setup

Note: Depending on how you have things set up on your server, you will likely need to provide the correct paths to things, as they surely differ from mine.

Quick overview: You’re going to create a directory, put two shell scripts into it, create a config file and two empty files, tweak your BIND settings, and you’re ready! Sample files are included below.

  1. Create /usr/local/etc/dns-blackhole directory (several things go in here)
  2. Copy dns-blackhole-update.sh into this new directory
  3. Copy the dns-blocker.sh utility script here as well
  4. Create a dns-blackhole.conf config file
  5. Create a allowed_hosts file (can be empty)
  6. Create a local_blocked_hosts file (can be empty)
  7. Adjust BIND Configuration (steps below)
  8. Add an entry in crontab or periodic to automate updates

Operation

  1. Run dns-blocker.sh off to initialize the control switch (symlink)
  2. Run dns-blackhole-update.sh to make sure it works before automating
  3. Run dns-blocker.sh on to enable the blackhole
  4. Run dns-blocker.sh off whenever you need to disable it (e.g., updating your LG smart TV)

Adjust BIND Configuration

  1. Add this entry inside of the options block in named.conf:
response-policy { zone "rpz"; };
  1. Add this to the end of named.conf:
include "/usr/local/etc/namedb/named.blocker";
  1. Create the named.rpz file:
zone "rpz" {
     type master;
     file "named.blacklist.rpz";
};
  1. Create the named.blacklist-disabled file:
zone "rpz" {
     type master;
     file "/dev/null";
};

dns-blackhole-update.sh

Copy this to your dns_blackhole_dir directory.

#!/bin/sh
#
# dns-blackhole-update.sh - Fetches and builds DNS Response Policy Zone (RPZ)
#
# Usage:
#   dns-blackhole-update.sh [config_file]
#
# Requirements:
#   - FreeBSD environment with BIND DNS server
#   - /usr/bin/fetch
#   - /usr/local/etc/dns-blackhole directory structure with:
#       - local_blocked_hosts - list of hosts to block ("0.0.0.0 hostname")
#       - allowed_hosts - list of hostnames to allow ("hostname")
#   - Service 'named' available to restart after updating
#
# Config file sample format:
#   dns_blackhole_dir="/usr/local/etc/dns-blackhole"
#   named_dir="/usr/local/etc/namedb"
#   dns_server_hostname="ns.space.lan"
#   retry_seconds="10"
#   max_attempts="3"
#
# Explanation of config settings:
#   dns_blackhole_dir   - Directory for input and output RPZ files
#   named_dir           - Directory for storing the bind zone files
#   dns_server_hostname - The DNS NS record to use in the RPZ
#   retry_seconds       - Seconds to wait between fetch retries (default: 10)
#   max_attempts        - Maximum number of fetch attempts (default: 3)

set -eu  # Exit on error and undefined variables

cd "$(dirname "$0")";
config_file="${1:-dns-blackhole.conf}"

if [ -f "$config_file" ]; then
    . "$config_file"
else
    echo "Config file '$config_file' not found. Exiting." >&2
    exit 1
fi

cd $dns_blackhole_dir

if [ -z "$named_dir" ] || [ -z "$dns_server_hostname" ] || [ -z "$retry_seconds" ] || [ -z "$max_attempts" ]; then
    echo "Missing required config settings. Exiting." >&2
    exit 1
fi

echo "Fetching Steven Black master hosts..."
attempt=0
url="https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
output="steven_black_hosts"

while [ "$attempt" -lt "$max_attempts" ]; do
    echo "Fetching (attempt $(($attempt + 1))/$max_attempts)..."
    if /usr/bin/fetch -q -m -T 30 -o "$output" "$url"; then
        echo "Fetch succeeded."
        break
    fi
    attempt=$((attempt + 1))
    if [ "$attempt" -lt "$max_attempts" ]; then
        echo "Fetch failed. Retrying in $retry_seconds seconds..." >&2
        sleep "$retry_seconds"
    else
        echo "Fetch failed after $max_attempts attempts. Giving up." >&2
        exit 1
    fi
done

echo "Optimizing hosts list..."
cat steven_black_hosts local_blocked_hosts | \
    sed -e 's/^[[:space:]]*//' | \
    grep -v '^#' | \
    awk '{print $2}' | \
    grep -Ev '^(0\.0\.0\.0|localhost)$' | \
    grep -v '^$' | \
    sort -u > optimized_hosts

echo "Excluding allowed hosts..."
sort allowed_hosts | comm -23 optimized_hosts - > blocked_hosts

echo "Building zone file..."
serial_number=$(date +%Y%m%d%H%M%S)

cat <<EOF > named.blacklist.rpz
; [built by $0]

\$TTL    604800

@               IN      SOA     localhost. root.localhost. (
;                               YYYYMMDDHHMMSS
                                $serial_number   ; Serial
                                        604800   ; Refresh
                                         86400   ; Retry
                                       2419200   ; Expire
                                        604800 ) ; Minimum

                IN      NS      $dns_server_hostname.

EOF

sed -E 's/^(.*)$/\1 CNAME ./' blocked_hosts >> named.blacklist.rpz

cp named.blacklist.rpz "$named_dir"

service named restart

Sample dns-blackhole.conf

Create this in your dns_blackhole_dir directory.

dns_blackhole_dir="/usr/local/etc/dns-blackhole"
named_dir="/usr/local/etc/namedb"
dns_server_hostname="ns.yourdomain.local"
retry_seconds="10"
max_attempts="3"

Explanation:

dns_blackhole_dir
Directory for various input/output support files
named_dir
Directory for storing the named.blacklist.rpz file
dns_server_hostname
The hostname of your DNS server for the NS record in the RPZ
retry_seconds
Seconds to wait between fetch retries (default: 10)
max_attempts
Maximum number of fetch attempts (default: 3)

Sample allowed_hosts

This file holds hostnames you want to omit from the DNS blackhole. This goes in your dns_blackhole_dir directory.

api.ipstack.com
freeipapi.com
geolocation-db.com
ip-api.com
ipapi.co
ipinfo.io
ipstack.com

Sample local_blocked_hosts

This file holds custom host entries you want to add to the DNS blackhole. This goes in your dns_blackhole_dir directory.

#
# Block LG TV ads
#
# (see https://gist.github.com/wassname/78eeaaad299dc4cddd04e372f20a9aa7)

0.0.0.0 lgappstv.com
0.0.0.0 ngfts.lge.com
0.0.0.0 e6114.e21.akamaiedge.net
0.0.0.0 us.ibs.lgappstv.com
0.0.0.0 us.lgtvsdp.com
0.0.0.0 us.rdx2.lgtvsdp.com
0.0.0.0 eic.recommend.lgtvcommon.com

dns-blocker.sh

Copy this to your dns_blackhole_dir directory. To easily run it at any time without having to give the full path, add dns_blackhole_dir to your $PATH or create a symlink to this in your ~/bin directory:

#!/bin/sh

# dns-blocker.sh
#
# Toggles the DNS blocker state on a FreeBSD server running BIND.
#
# This script allows you to enable or disable a DNS blocker by switching
# the symbolic link for the `named.blocker` file. It expects one argument:
# "on" to enable the DNS blocker or "off" to disable it.
#
# Usage:
#   ./dns-blocker.sh <on|off>
#
#   on   - Enables the DNS blocker by linking named.rpz to named.blocker
#   off  - Disables the DNS blocker by linking named.blacklist-disabled to named.blocker
#
# Example:
#   ./dns-blocker.sh on   - Turns the DNS blocker on
#   ./dns-blocker.sh off  - Turns the DNS blocker off
#
# Assumptions:
#   - The script is run on a FreeBSD server with BIND and the necessary files:
#     - named.rpz (DNS blocker zone file)
#     - named.blacklist-disabled (disabled DNS blocker file)
#   - The script operates in the directory: /usr/local/etc/namedb
#   - The "named.blocker" symlink exists and can be toggled between these two files.
#
# Note:
#   This script requires root privileges to restart the BIND service.

# Ensure that option is provided
option="$1"

if [ -z "$option" ]; then
    echo "Usage: $0 <on|off>"
    exit 1
fi

cd "/usr/local/etc/namedb" || exit 1  # Exit if the directory can't be accessed

# Get the current symlink target (handle case if symlink does not exist)
current=$(readlink "named.blocker")

if [ -z "$current" ]; then
    echo "$0: Error: named.blocker symlink does not exist."
    exit 1
fi

case "$option" in
    on)
        if [ "$current" != "named.rpz" ]; then
            echo "Turning DNS blocker on..."
            ln -sf named.rpz named.blocker
        else
            echo "$0: DNS blocker is already on."
            exit 0  # Success, no change needed
        fi
        ;;
    off)
        if [ "$current" != "named.blacklist-disabled" ]; then
            echo "Turning DNS blocker off..."
            ln -sf named.blacklist-disabled named.blocker
        else
            echo "$0: DNS blocker is already off."
            exit 0  # Success, no change needed
        fi
        ;;
    *)
        echo "Usage: $0 <on|off>"
        exit 1
        ;;
esac

# Restart the service after making the change
service named restart

Sample Output

# /usr/local/etc/dns-blackhole/dns-blackhole-update.sh
Fetching Steven Black master hosts...
Fetching (attempt 1/3)...
Fetch succeeded.
Optimizing hosts list...
Excluding allowed hosts...
Building zone file...
Stopping named.
Waiting for PIDS: 10669.
Starting named.

# ~/bin/dns-blocker.sh off
/home/mdavis/bin/dns-blocker.sh: DNS blocker is already off.

# ~/bin/dns-blocker.sh on
Turning DNS blocker on...
Stopping named.
Waiting for PIDS: 26637.
Starting named.

Sample Crontab Entry

#
#minute hour    mday    month   wday    command
#

15      4       *       *       *       /usr/local/etc/dns-blackhole/dns-blackhole-update.sh 2>&1 | mail -s "update DNS blackhole zone" root
Explore