dockerized DNS over HTTPS using pi-hole through cloudflared proxy-dns
takes 3 minutes to read
a few months ago I configured a thin client as my home server to replace the previous raspberry pi setup.
During that migration I moved over all native services within docker containers. One of those services being a pi-hole setup to block ad serving domains on dns level and to have a dns cache within our LAN to gain a bit of speed.
It has been running ever since without any issue and worked pretty well.
When cloudflare announced their fast and privacy based DNS resolver I got a bit intrigued by their DNS over HTTPS feature. Especially since our ISP telenet is using our web history for their advertisements too.
So I stumbled on some articles from Oliver Hough and Scott Helme that describe how you can combine a cloudflared proxy-dns with pi-hole to get your dns requests encrypted through HTTPS and still be able to filter out the advertisements.
Since I got everything in docker I configured a cloudflared container automated through travis with dgoss tests.
I got some inspiration from maartje who used a matrix to build multiple docker images for different architectures using travis. The main reason behind this was that after I got this setup up and running using this docker-compose file on my x86_64 machine I wanted to run it on a raspberry pi zero w.
For the pihole container I figured out you can easily pass by the custom DNS servers through docker environment variables so no need anymore for a custom pihole docker container to maintain!
$ cat docker-compose.yml
version: "3"
services:
cloudflared:
container_name: cloudflared
image: visibilityspots/cloudflared:amd64
restart: unless-stopped
networks:
pihole_net:
ipv4_address: 10.0.0.2
pi-hole:
container_name: pi-hole
image: pihole/pihole:v4.2.1_amd64
restart: unless-stopped
ports:
- "80:80/tcp"
- "53:53/tcp"
- "53:53/udp"
environment:
- ServerIP=10.0.0.3
- DNS1='10.0.0.2#5054'
- DNS2=''
- IPv6=false
- TZ=CEST-2
- DNSMASQ_LISTENING=all
- WEBPASSWORD=admin
networks:
pihole_net:
ipv4_address: 10.0.0.3
dns:
- 127.0.0.1
- 1.1.1.1
cap_add"
- NET_ADMIN
networks:
pihole_net:
driver: bridge
ipam:
config:
- subnet: 10.0.0.0/29
I remembered this project where a raspberry pi zero W was used together with a tiny display. In the meanwhile I have the DoH cloudflared/pi-hole combination running on such a tiny device using ArchLinux ARM and ordered the display :D
You can use the same dockerfile on a raspberry pi zero but with other tags for the container images:
image: visibilityspots/cloudflared:arm
image: pihole/pihole:v4.0_armhf
As you can see unfortunately I had to configure static ip's since the dnsmasq config needs the ip address of the cloudflared service. If someone has a better solution to implement it let me know!
I also opted to not store the data. Meaning that when the docker containers are restarted the data is gone.
So when you now bring up those 2 containers:
$ docker-compose up -d
Creating network "###_pihole_net" with driver "bridge"
Creating pi-hole ...
Creating cloudflared ...
Creating pi-hole
Creating cloudflared ... done
$ docker-compose logs cloudflared
Attaching to cloudflared
cloudflared | time="2018-04-16T20:01:14Z" level=info msg="Adding DNS upstream" url="https://1.1.1.1/.well-known/dns-query"
cloudflared | time="2018-04-16T20:01:14Z" level=info msg="Adding DNS upstream" url="https://1.0.0.1/.well-known/dns-query"
cloudflared | time="2018-04-16T20:01:14Z" level=info msg="Starting DNS over HTTPS proxy server" addr="dns://0.0.0.0:5054"
cloudflared | time="2018-04-16T20:01:14Z" level=info msg="Starting metrics server" addr="127.0.0.1:35973"
$ docker-compose logs pi-hole
Attaching to pi-hole
...
pi-hole | [services.d] starting services
pi-hole | Starting lighttpd
pi-hole | Starting dnsmasq
pi-hole | Starting crond
pi-hole | Starting pihole-FTL (no-daemon)
pi-hole | [services.d] done.
pi-hole | dnsmasq: started, version 2.76 cachesize 10000
pi-hole | dnsmasq: compile time options: IPv6 GNU-getopt DBus i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack ipset auth DNSSEC loop-detect inotify
pi-hole | dnsmasq: using nameserver 10.0.0.2#5054
pi-hole | dnsmasq: read /etc/hosts - 7 addresses
pi-hole | dnsmasq: read /etc/pihole/local.list - 2 addresses
pi-hole | dnsmasq: failed to load names from /etc/pihole/black.list: No such file or directory
pi-hole | dnsmasq: read /etc/pihole/gravity.list - 121065 addresses
pi-hole | dnsmasq: 1 127.0.0.1/48521 query[A] pi.hole from 127.0.0.1
pi-hole | dnsmasq: 1 127.0.0.1/48521 /etc/pihole/local.list pi.hole is 10.0.0.3
you should be able to query the containerized pi-hole DNS service from it's host or from within your netwerk using dig:
$ dig @localhost -p 53 visibilityspots.org
($ dig @IP-ADDRESS-OF-DOCKER-NODE -p 53 visibilityspots.org)
; <<>> DiG 9.12.1 <<>> @localhost -p 53 visibilityspots.org
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51155
;; flags: qr rd ra; QUERY: 1, ANSWER: 8, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1536
;; QUESTION SECTION:
;visibilityspots.org. IN A
;; ANSWER SECTION:
visibilityspots.org. 37 IN A 54.230.9.72
visibilityspots.org. 37 IN A 54.230.9.109
visibilityspots.org. 37 IN A 54.230.9.119
visibilityspots.org. 37 IN A 54.230.9.143
visibilityspots.org. 37 IN A 54.230.9.148
visibilityspots.org. 37 IN A 54.230.9.182
visibilityspots.org. 37 IN A 54.230.9.188
visibilityspots.org. 37 IN A 54.230.9.203
;; Query time: 223 msec
;; SERVER: ::1#53(::1)
;; WHEN: Mon Apr 16 22:05:37 CEST 2018
;; MSG SIZE rcvd: 328
Obviously I wanted to see myself that when sniffing the network the DNS requests aren't readable so I used tcp dump to prove myself the data was sent through HTTPS
pi-hole# tcpdump -i eth0 udp port 53
22:39:30.837594 IP 192.168.0.3.35765 > piholeContainerID.domain: 36972+ [1au] A? visibilityspots.com. (60)
22:39:31.009345 IP piholeContainerID.domain > 192.168.0.3.35765: 36972 8/0/1 A 54.230.228.38, A 54.230.228.42, A 54.230.228.54, A 54.230.228.68, A 54.230.228.69, A 54.230.228.84, A 54.230.228.92, A 54.230.228.104 (328)
cloudflared# tcpdump -i eth0 udp port 5054
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
20:39:29.029132 IP piholeContainerID.59189 > cloudflaredContainerID.5054: UDP, length 40
20:39:29.069864 IP cloudflaredContainerID.5054 > piholeContainerID.59189: UDP, length 116
20:39:30.838803 IP piholeContainerID.28892 > cloudflaredContainerID.5054: UDP, length 60
20:39:31.003756 IP cloudflaredContainerID.5054 > piholeContainerID.28892: UDP, length 328
20:39:31.352487 IP piholeContainerID.50291 > cloudflaredContainerID.5054: UDP, length 31
20:39:31.364073 IP piholeContainerID.16365 > cloudflaredContainerID.5054: UDP, length 31
20:39:31.411227 IP cloudflaredContainerID.5054 > piholeContainerID.50291: UDP, length 156
20:39:31.432364 IP cloudflaredContainerID.5054 > piholeContainerID.16365: UDP, length 218
So by now you can configure this new DNS service on your router or dhcp daemon within your local network.
Since the pi isn't running for a very long time I have no clue if it can cope with the load on our network but I'll keep you posted ;)