Better ad blocking and safer DNS with unbound and CloudFlare_

August 27, 2018 @17:10

For a long time now the core of my ad blocking strategy has been squid and privoxy running on my OpenBSD routers. Mobile devices VPN into the network and receive a proxy.pac which routes all traffic to these proxies which reject connections to known ad hosts. With the growing adoption of HTTPS (thankfully) privoxy is becoming less and less useful so I have been trying to find better ways to block ads at the networking level.

I'm not going to get into the ethics of ad blocking, it's my choice to make but I will leave this here.

Tay Tay says block ads (source)

Around the same time CloudFlare announced 1.1.1.1, a privacy focused anycast DNS service. I've been using the Level 3 anycast DNS resolvers for a while now but that's not exactly optimal. With CloudFlare's resolvers you get not only a geographically distributed DNS resolver cluster but DNS-over-TLS and DNS-over-HTTPS support.

Now I run ISC BIND for resolvers, which at this point doesn't support either encrypted DNS method. I do support and validate DNSSEC but that doesn't keep people from eavesdropping on me.

Enter unbound

For a while now OpenBSD has had unbound as the recursive resolver in the base installation so I've been aware of it and trust it. Since I do both recursive and authorative DNS on the same servers I have not had a reason to introduce it. Until CloudFlare.

I added the unbound packages to my DNS server's puppet manifest so the default Debian package got installed. I then added the following configuration to /etc/unbound/unbound.conf.d/cloudflare.conf. Since I'm going to have BIND actually listen to and respond to queries from clients I bind only to localhost (::1 is the IPv6 loopback address) and listen on a non-standard DNS port (5300 since it was open and semi-obvious). This does mean that I have two layers of cache to worry about if I need to clear the DNS cache for any reason but I almost never have to do that so I will worry about that later.

unbound configuration

# This file is managed by Puppet.
#
# Forward DNS requests to CloudFlare using DNS over TLS.
server:
    verbosity: 1
    use-syslog: yes
    do-tcp: yes
    prefetch: yes
    port: 5300
    interface: ::1
    do-ip4: yes
    do-ip6: yes
    prefer-ip6: yes
    rrset-roundrobin: yes
    use-caps-for-id: yes
forward-zone:
    name: "."
    forward-addr: 2606:4700:4700::1111@853#cloudflare-dns.com
    forward-addr: 2606:4700:4700::1001@853#cloudflare-dns.com
    forward-addr: 1.1.1.1@853#cloudflare-dns.com
    forward-addr: 1.0.0.1@853#cloudflare-dns.com
    forward-ssl-upstream: yes

I then switched the forwarders section of my named.conf from:

    forwarders {
        4.2.2.2;
        4.2.2.1;
    };

to:

    // Unbound listens on [::1]:5300 and forwards to CloudFlare
    forwarders {
        ::1 port 5300;
    };

After letting puppet apply the new configuration I checked the outbound WAN interface of my router with tcpdump(8) and verified that all DNS resolution was heading off to CloudFlare.

Adding adblocking

unbound(8) has a really nice feature where you can override recursion fairly easily. This can be leveraged to block malicious sites at the DNS layer. I found a couple lists that I was able to plug in that so far have worked really well for me.

The first one is a malware block list that is already provided in the unbound config format. So I just used puppet-vcsrepo to ensure an up-to-date copy is always checked out in /usr/local/etc/unbound/blocks. I was then able to add include: "/usr/local/etc/unbound/blocks/blocks.conf" to the server: section of my unbound config.

Since I also wanted ad blocking I continued my search and came across Steven Black's curated list that consildates a number of difference sources into a hosts.txt format file. Since this isn't exactly the format unbound wants I had to do a little more work.

  1. Checked that repository out with puppet-vcsrepo into /usr/local/etc/unbound/stevenblack.
  2. Wrote the script below to convert the list format from a hosts file to an unbound configuration file.
  3. Configured puppet to exec that script when the vcsrepo pulls an update and then notify (restart) the unbound service.
  4. Added include: /usr/local/etc/unbound/stevenblack.conf to my unbound configuration.

unbound-blocklist script

#!/bin/sh
# unbound-blacklist (c) 2018 Matthew J Ernisse <matt@going-flying.com>
#
# Generate an unbound style config from a hosts list.

set -e

SRC="/usr/local/etc/unbound/stevenblack/hosts"
OUTPUT="/usr/local/etc/unbound/stevenblack.conf"


if [ ! -f "$SRC" ]; then
    echo "Could not open $SRC"
    exit 1
fi

awk '/^0\.0\.0\.0/ {
    print "local-zone: \""$2"\" redirect"
    print "local-data: \""$2" A 0.0.0.0\""
}' "$SRC" > "$OUTPUT"

The entire puppet manifest for the unbound configuration is as follows. It is included by the rest of the manifests that setup BIND on my name servers.

unbound Puppet manifest

# Unbound - This is the caching recursor.  Uses DNS-over-TLS
# to CloudFlare to provide secure and private DNS resolution.
class auth_dns::unbound {
    package { 'unbound':
        ensure => latest,
    }

    service { 'unbound':
        ensure => running,
    }

    file { '/etc/unbound/unbound.conf.d/cloudflare.conf':
        source => 'puppet:///modules/auth_dns/unbound.conf',
        owner => 'root',
        group => 'root',
        mode => '0644',
        require => [
            Package['unbound'],
        ],
        notify => [
            Service['unbound'],
        ],
    }

    exec { 'rebuild unbound blacklist':
        command => '/usr/bin/unbound-blacklist',
        refreshonly => true,
        require => [
            Package['unbound'],
            File['/usr/bin/unbound-blacklist'],
            Vcsrepo['/usr/local/etc/unbound/stevenblack'],
        ],
        notify => Service['unbound'],
    }

    file { '/usr/bin/unbound-blacklist':
        ensure => present,
        source => 'puppet:///modules/auth_dns/unbound-blacklist',
        owner => root,
        group => root,
        mode => '0755',
    }

    file { '/usr/local/etc/unbound':
        ensure => directory,
        owner => root,
        group => root,
        mode => '0755',
    }

    vcsrepo { '/usr/local/etc/unbound/blocks':
        ensure => present,
        provider => git,
        source => 'https://github.com/k0nsl/unbound-blocklist.git',
        revision => 'master',
        require => [
            Package['unbound'],
            File['/etc/unbound/unbound.conf.d/cloudflare.conf'],
            File['/usr/local/etc/unbound'],
        ],
        notify => Service['unbound'],
    }

    vcsrepo { '/usr/local/etc/unbound/stevenblack':
        ensure => present,
        provider => git,
        source => 'https://github.com/StevenBlack/hosts.git',
        revision => 'master',
        require => [
            Package['unbound'],
            File['/etc/unbound/unbound.conf.d/cloudflare.conf'],
            File['/usr/local/etc/unbound'],
        ],
        notify => Exec['rebuild unbound blacklist'],
    }
}

Conclusion

So far it feels like a lot of things load faster. I am noticing less requests being blocked by privoxy and squid, to the point that I'm thinking I may be able to completely depricate them. It is also nice that devices on the network that don't listen to proxy.pac files are now being protected from malware and malvertizing as well.

🍺

Subscribe via RSS. Send me a comment.