A few weeks ago when I upgraded this site to a new server, I spent the time to overhaul my DNS setup. I host my own nameservers for several domains and wanted to automate their configuration more than the edit-in-nano hand management I’d been doing. While I was at it, I set up DNSSEC for the domains and even DNS-over-TLS.
The latter was nice because with the latest version of Android I was able to set it up to have my phone use my DNS over TLS, thus providing an encrypted tunnel for my DNS lookups, as well as forcing all DNS lookups from my phone to go through my own server.
Today, I was able to cash in on that effort. I was looking through the DNS-over-TLS logs and noticed that one of my apps was phoning home to graph.facebook.com
, even though the app did not disclose this. In something of a smoldering fury I decided to implement response policy zones (RPZ) on my nameserver. In effect, this allows me to blackhole any domains that I want: whenever any app on my phone tries to find Facebook or any other domain I’ve blacklisted, they get told there’s no such domain.
rpz QNAME NXDOMAIN rewrite graph.facebook.com via graph.facebook.com.blackhole
I did all this with Ansible, making for a very simple script:
--- - hosts: dns become: yes vars_files: - '../../secrets.yml' vars: dns_serial: "{{ ansible_date_time.year }}{{ ansible_date_time.month }}{{ ansible_date_time.day }}{{ ansible_date_time.hour }}" dns_rpz_zone: blackhole dns_rpz_domains: - doublelick.net - app-measurement.com - facebook.com - facebook.net - fbcdn.com - fbsbx.com - fbcdn.net - messenger.com - m.me - instagram.com - instagramstatic-a.akamaihd.net - cdninstagram.com - tfbnw.net - whatsapp.com - fb.me - fb.com dns_rpz_cdns: - edgesuite.net - edgekey.net tasks: - name: named.conf.options become: yes template: src: named.conf.options.j2 dest: /etc/bind/named.conf.options owner: root group: bind - name: Hosts file become: yes template: src: rpz.hosts.j2 dest: "{{ dns_path }}/{{ dns_rpz_zone }}.hosts" owner: bind group: bind - name: Zone file become: yes template: src: rpz.zone.j2 dest: "{{ dns_path }}/{{ dns_rpz_zone }}.zone" owner: bind group: bind - name: Ensure zone file reference lineinfile: dest: /etc/bind/named.conf.local line: "include \"{{ dns_path }}/blackhole.zone\";" state: present
The hosts template is the only interesting one:
$ttl 86400 @ IN SOA {{ dns_rpz_zone }}. root.{{ dns_rpz_zone }}. {{ dns_serial }} 10800 3600 604800 86400 @ IN NS {{ dns_rpz_zone }}. @ IN A 127.0.0.66 {% for domain in dns_rpz_domains %} {{ domain }} IN CNAME . *.{{ domain }} IN CNAME . {% for cdn in dns_rpz_cdns %} {{ domain }}.{{ cdn }} IN CNAME . *.{{ domain }}.{{ cdn }} IN CNAME . {% endfor %} {% endfor %}
The trick here is that the RPZ hosts file, unlike regular domains, has hosts without a trailing ‘.’, even though they are fully-qualified names. I spent a half hour trying to figure out why Bind was giving me errors about “ignoring out-of-zone data” and not working, because I had ‘.’ at the end of my domains.
This produces an output with lines that look like:
facebook.com IN CNAME . *.facebook.com IN CNAME . facebook.com.edgesuite.net IN CNAME . *.facebook.com.edgesuite.net IN CNAME . facebook.com.edgekey.net IN CNAME . *.facebook.com.edgekey.net IN CNAME .
Check to ensure your config looks good, then restart:
$ named-checkzone blackhole /etc/bind/blackhole.hosts $ named-checkconf $ /etc/init.d/bind9 restart
(Or have ansible do this for you.)
You can test it by trying to find the domain:
$ dig +noall +answer +additional @localhost facebook.com blackhole. 86400 IN SOA blackhole. root.blackhole. 2019021618 10800 3600 604800 86400
And, of course, by trying to pull up facebook.com on your phone.