Protect your DNS with dnscrypt-proxy
Recently, I encountered an article detailing the installation of Pi-Hole on a Raspberry Pi. I come across articles like this every once in a while, and am time and again surprised that more lightweight and effective (albeit a little more complex) tools do not get the same coverage. Here, I aim to change that a little, even though the chosen medium might not have the same reach as the others (feel free to share and repost this in order to change that, though).
Pi-Hole?
So first of all: what is Pi-Hole? Well, it’s an open-source tool that provides an easy way to manage your own DNS sinkhole, which allows you to block DNS requests to websites of your choosing. DNS is a protocol used by client devices on a network (say, your LAN or WiFi at home) to find out where certain websites are hosted on the internet in order to retrieve information from them (such as webpages). Think of DNS like a phone book of the internet.
The principal use case for Pi-Hole is blocking ads on websites, although it can also be used to block any other unwanted sites. This is done by directing all DNS requests at the Pi-Hole server, which then goes out to DNS servers on the internet, and retrieves the requested IP addresses from other DNS servers. It then gives the client device answers for any DNS queries, minus those it’s supposed to filter out.
For example: say we want to surf to somesite.com, which also has some ads embedded in the homepage that are hosted by ads4u.net. We tell Pi-Hole to sinkhole anything that comes from ads4u.net. We then surf to somesite.com, which causes our client device to issue a DNS request for the IP address where the site is hosted. Pi-Hole receives the query, redirects it to the internet, and get the answer back: 1.2.3.4. When we load the page for somesite.com it also attempts to tell our client device where to load the ads from, resulting in a second DNS query for ads4u.net. Pi-Hole receives this query, sees it is present in the blocklist, and returns an invalid answer. This results in the homepage for somesite.com loading, minus the ads from ads4u.net.
Why would you want to do this? Well aside from the obvious that ads are annoying, ads are increasingly used in malvertising campaigns even on trusted platforms, meaning that keeping ads away from your computers is becoming more and more important. Not only that, but you can utilize DNS sinkholing to preempt known DNS C&C channels used by malware, or even for parental control.
Pi-Hole is a nifty tool that makes administering your own DNS server with a sinkhole easy, even if you’re not a veteran network administrator. It also provides you with a nice dashboard so you can see how many domains are blocked, how many requests have been blocked in the past X days, which domains have been blocked most, and all sorts of other information you may find interesting. You can learn more about Pi-Hole here and here.
Missing features
Don’t get me wrong. Pi-Hole is cool. However, it is missing integration with some features that you may want to employ. This is not to say you couldn’t use them with Pi-Hole at all, just that you’ll have to take extra steps, utilize additional tools, and add extra complexity to your build if you do.
dnscrypt-proxy
The tool I eventually settled on for my own use is dnscrypt-proxy. It’s a command line tool that does practically everything that Pi-Hole does, although it doesn’t provide you with the web-based dashboard that Pi-Hole does (there are ways to get something similar going with tools like Grafana). Personally, I don’t mind not having the dashboard, as it is something I would rarely look at anyway. Additionally, being a command line tool offers lots of options for automation and scripting. There’s even the possibility of integrating it into your own OPNsense and pfSense firewalls.
In the following sections I’ll show you how to setup dnscrypt-proxy to work like Pi-Hole in a Linux environment, including the use of DoH and DNSSEC to secure your queries and answers. Some steps could be different depending on your operating system, but the general ideas remain the same.
This should result in plaintext DNS queries not leaving your own network, and your dnscrypt-proxy only forwarding encrypted queries and receiving cryptographically signed answers, as well as filtering unwanted DNS queries, such as those for internet ads. As an additional bonus, you’ll have your own DNS resolver for internal use, which you could make all your internal clients use for secure resolving (and which you can use for monitoring your network for security threats).
Installing
I’m using Arch Linux, so installation is fairly simple:
sudo pacman -S dnscrypt-proxy
Installation steps for your Linux distro and for Windows, Mac, and a host of other operating systems can be found here.
Main configuration
Next, we’ll have to configure dnscrypt-proxy to do the things we want. To do this, we have to set some settings in the main configuration file, dnscrypt-proxy.toml. In Arch Linux, this file is located in the /etc/dnscrypt-proxy directory:
cd /etc/dnscrypt-proxy
The files in this directory are owned by the root user, so we’ll have to edit them with root permissions. I prefer nano for file editing:
sudo nano -l dnscrypt-proxy.toml
The configuration file is fairly well-documented. However, I will touch on some important settings here. If you need more information on configuring dnscrypt-proxy, you can find comprehensive documentation here.
dnscrypt-proxy uses upstream DNS resolvers in order to return answers to DNS queries. By default, it uses a list curated by the developers, although you could create your own. Also by default, dnscrypt-proxy is setup to select a set of resolvers based on their properties, such as whether they provide DoH resolving, if they are pre-filtered, whether DNSSEC is available, and so on. These settings can be found in lines 58-88 of dnscrypt-proxy.toml:
## Require servers (from remote sources) to satisfy specific properties
# Use servers reachable over IPv4
ipv4_servers = true
# Use servers reachable over IPv6 -- Do not enable if you don't have IPv6 connectivity
ipv6_servers = false
# Use servers implementing the DNSCrypt protocol
dnscrypt_servers = true
# Use servers implementing the DNS-over-HTTPS protocol
doh_servers = true
# Use servers implementing the Oblivious DoH protocol
odoh_servers = false
## Require servers defined by remote sources to satisfy specific properties
# Server must support DNS security extensions (DNSSEC)
require_dnssec = false
# Server must not log user queries (declarative)
require_nolog = true
# Server must not enforce its own blocklist (for parental control, ads blocking...)
require_nofilter = true
# Server names to avoid even if they match all criteria
disabled_server_names = []
Since I want to use DoH resolving only along with DNSSEC, I must change two settings:
# Use servers implementing the DNSCrypt protocol
dnscrypt_servers = false
and
# Server must support DNS security extensions (DNSSEC)
require_dnssec = true
If you have specific requirements with regard to which server to use, you can set a static list in line 32, by removing the # and specifying the servers to use (this will override any property-based selection settings):
# server_names = ['scaleway-fr', 'google', 'yandex', 'cloudflare']
If you require additional anonymity, you could route all your requests over the TOR network, by removing the # and setting a proxy address in line 111:
# proxy = 'socks5://127.0.0.1:9050'
Another setting of note is that of the bootstrap resolvers (line 270). These are used to load the initial list of external resolvers that dnscrypt-proxy will use for resolving internal queries. As these must be downloaded from the internet (if not already cached), dnscrypt-proxy will initially need to send a single plaintext DNS query to these servers in order to be able to retrieve the list and the server signatures:
领英推荐
bootstrap_resolvers = ['9.9.9.11:53', '8.8.8.8:53']
You may have specific requirements or preferences as to where to send these. As I prefer not using Google servers, I will change the default 8.8.8.8 to something else:
bootstrap_resolvers = ['9.9.9.11:53', '1.1.1.1:53']
At the end of the main configuration there is a section that deals with logging and automatic log rotation (lines 326-335). These logs are handy tools to have to use for troubleshooting and tracking down security threats within your network:
## Automatic log files rotation
# Maximum log files size in MB - Set to 0 for unlimited.
log_files_max_size = 10
# How long to keep backup files, in days
log_files_max_age = 7
# Maximum log files backups to keep (or 0 to keep all backups)
log_files_max_backups = 1
As I would like to keep all backups for the last month, I’ll have to change these settings:
# How long to keep backup files, in days
log_files_max_age = 30
# Maximum log files backups to keep (or 0 to keep all backups)
log_files_max_backups = 0
Use the settings in lines 488-530 to set the file names you want to use for your log files:
###############################
# Query logging #
###############################
## Log client queries to a file
[query_log]
## Path to the query log file (absolute, or relative to the same directory as the config file)
## Can be set to /dev/stdout in order to log to the standard output.
# file = 'query.log'
## Query log format (currently supported: tsv and ltsv)
format = 'tsv'
## Do not log these query types, to reduce verbosity. Keep empty to log everything.
# ignored_qtypes = ['DNSKEY', 'NS']
############################################
# Suspicious queries logging #
############################################
## Log queries for nonexistent zones
## These queries can reveal the presence of malware, broken/obsolete applications,
## and devices signaling their presence to 3rd parties.
[nx_log]
## Path to the query log file (absolute, or relative to the same directory as the config file)
# file = 'nx.log'
## Query log format (currently supported: tsv and ltsv)
format = 'tsv'
I will set these by removing the # in the relevant lines:
[query_log]
## Path to the query log file (absolute, or relative to the same directory as the config file)
## Can be set to /dev/stdout in order to log to the standard output.
file = 'query.log'
and
[nx_log]
## Path to the query log file (absolute, or relative to the same directory as the config file)
file = 'nx.log'
These log files can subsequently be found in the /var/log/dnscrypt-proxy directory.
If you are not using IPv6 routing on your own network, you may want to block IPv6 related DNS queries. This makes the resolution process quicker, and prevents useless queries from being sent out of the network. The setting on line 353 can be used to do this:
block_ipv6 = false
To block IPv6 queries set:
block_ipv6 = true
Filtering
Now we get to the pattern-based blocking section of the dnscrypt-proxy.toml file. dnscrypt-proxy supports two ways of blocking queries: name-based and IP-based. In both cases it uses lists (of domain names and IP addresses, respectively) to block queries for unwanted domains or IP addresses. These are pattern-based lists, meaning you can use wildcards and some regex for matching. Each blocklist has a corresponding allow-list that enables overriding blocked domains.
For example: we exclude .com domains from being resolved by adding .com to the blocklist. However, we want to allow traffic to somesite.com. In order to do this we add *.somesite.com to our allow-list. Now the only .com domain that will resolve will be somesite.com.
We can edit the blocklist for names by editing lines 551-565 of dnscrypt-proxy.toml:
[blocked_names]
## Path to the file of blocking rules (absolute, or relative to the same directory as the config file)
# blocked_names_file = 'blocked-names.txt'
## Optional path to a file logging blocked queries
# log_file = 'blocked-names.log'
## Optional log format: tsv or ltsv (default: tsv)
# log_format = 'tsv'
I want to enable the blocklist and enable logging for it, so I'll uncomment the following settings:
[blocked_names]
## Path to the file of blocking rules (absolute, or relative to the same directory as the config file)
blocked_names_file = 'blocked-names.txt'
## Optional path to a file logging blocked queries
log_file = 'blocked-names.log'
## Optional log format: tsv or ltsv (default: tsv)
log_format = 'tsv'
The blocked-names.txt file can be found in the /etc/dnscrypt-proxy directory. In this file you can insert all the different domains you wish to block. dnscrypt-proxy will filter out these queries when receiving them. The log file can be found in the /var/log/dnscrypt-proxy directory, along with the rest of the log files.
Similarly, we can add domains to the allow-list by editing lines 607-621:
[allowed_names]
## Path to the file of allow list rules (absolute, or relative to the same directory as the config file)
# allowed_names_file = 'allowed-names.txt'
## Optional path to a file logging allowed queries
# log_file = 'allowed-names.log'
## Optional log format: tsv or ltsv (default: tsv)
# log_format = 'tsv'
Since I'm allowing these specifically, I don't need to log these (they will also be available in the general log), although you may wish to do so anyway. Therefore, I'll only need to specify an allow-list file and no logs:
[allowed_names]
## Path to the file of allow list rules (absolute, or relative to the same directory as the config file)
allowed_names_file = 'allowed-names.txt'
## Optional path to a file logging allowed queries
# log_file = 'allowed-names.log'
## Optional log format: tsv or ltsv (default: tsv)
# log_format = 'tsv'
Similar to the blocklist, we can use this file to specify domains we want to have resolved in any case, regardless of any hits on the blocklist.
Enabling dnscrypt-proxy
Finally, we'll need to enable the service to run on our system. We can use systemd to run it as a service, and have it restart automatically when the machine reboots:
systemctl enable dnscrypt-proxy
systemctl start dnscrypt-proxy
Now, with the service running, we only need to instruct our client machines to use it as their DNS resolver. This can be done manually by editing the relevant DNS configuration files in your operating system, or by using DHCP. The latter is by far the more straightforward option, and can be done by setting the relevant settings on your router. Don't forget to make sure your system running dnscrypt-proxy has a static IP address.
Conclusion
DNS sinkholes are great, and can protect you from malware or just plain annoyance at ever-present internet ads. While solutions like Pi-Hole offer an easy basic setup, you're probably better off using a solution like dnscrypt-proxy if you want to utilize some additional features that are absent in the base installation of Pi-Hole, like DoH and DNSSEC. In my opinion, dnscrypt-proxy gives you extra control over what your resolver can and cannot do, without outside dependencies (other than the DNS infrastructure itself) and installation of additional tools.
This piece doesn't go into automation, but by their nature blocklists and allow-lists are subject to frequent changes, meaning you'd probably not want to administer them manually. If you'd like to see a follow-up in which automation of blocklists and allow-lists is discussed, let me know in the comments. I'd also like to hear if you thought this article was useful, or if you have any suggestions.
Give unbound a try instead of dnscrypt-proxy. Similar functionality, except unbound has been ‘rigorously audited’. See https://nlnetlabs.nl/projects/unbound/about/ for more info.