___________________________________________ title: Running dnscrypt-proxy on OpenBSD tags: openbsd raspberrypi hack dns 100daystooffload date: 2021-03-12 ___________________________________________ Intro A couple of weeks ago I took a spare RaspberryPi 3 leftover from my old k3s cluster and installed OpenBSD on it using my Pocket C.H.I.P.. While getting it installed was fun, I wanted to do more with it and use it on a more regular basis to continue learning about OpenBSD in general. [old k3s cluster]: https://www.ecliptik.com/Raspberry-Pi-Kubernetes-Cluster/ [OpenBSD]: https://www.openbsd.org/ [Pocket C.H.I.P.]: https://www.ecliptik.com/CHIP-Serial-Console/ [OpenBSD on RaspberryPi] [OpenBSD on RaspberryPi]: /assets/images/posts/openbsd-rpi/openbsd-rpi.png OpenBSD 4.9 Sticker on RaspberryPi 3 I’ve had a Pi-hole running on an older Raspberry Pi B with Debian for a few years, but wanted a few additional features, notably using DNSCRYPT to encrypt DNS traffic so our ISP wouldn’t be able to use it for anything and/or using DNS-over-HTTPS. I originally was going to setup Pi-hole on the new OpenBSD Pi, but quickly found out that Pi-hole doesn’t work on OpenBSD. [Pi-hole]: https://pi-hole.net/ [DNSCRYPT]: https://www.dnscrypt.org/ [DNS-over-HTTPS]: https://en.wikipedia.org/wiki/DNS_over_HTTPS A quick search, turned up an excellent Pi-hole on OpenBSD guide, which cleverly uses the vmm hypervisor to run a Linux VM and install Pi-hole there. This however was also a dead-end since the guide was assuming an x86 install and not arm64. Unfortunately it seems that the OpenBSD arm64 port doesn’t have vmm so installing a VM wouldn’t work, and probably wasn’t a great idea for performance anyway. [Pi-hole on OpenBSD]: https://bghost.xyz/post/pihole_on_openbsd/ [vmm hypervisor]: https://www.openbsd.org/faq/faq16.html The guide did include a reference to using dnscrypt-proxy, which is available as a package for OpenBSD arm64. Reading through the features it can do almost everything Pi-hole can and more, [dnscrypt-proxy]: https://github.com/DNSCrypt/dnscrypt-proxy - Local DNS caching - Filtering for Ads and Malware - DNSCRYPT - DNS-over-HTTPS - Anonymized DNS The only thing that was missing was the nicer GUI interface of Pi-hole, but I rarely used that anyway after initially setting it up and was more eye-candy that utilitarian. I decided to setup dnscrypt-proxy to mimic the blocking capabilities of the Pi-hole and enabled some more of the advanced features. Installing dnscrypt-proxy on OpenBSD Because dnscrypt-proxy is in the OpenBSD package repo, installation was a simple as, $ doas pkg_add dnscrypt-proxy quirks-3.442 signed on 2021-03-09T20:09:44Z dnscrypt-proxy-2.0.44: ok This installs version 2.0.44 which is slightly older than upstream, which is 2.0.45. Looking in openbsd snapshots, 2.0.45 is packaged in preparation for OpenBSD 6.9 and can install without issue on 6.8. [openbsd snapshots]: https://cdn.openbsd.org/pub/OpenBSD/snapshots/packages/aarch64/ $ wget https://cdn.openbsd.org/pub/OpenBSD/snapshots/packages/aarch64/dnscrypt-proxy-2.0.45.tgz $ doas pkg_add dnscrypt-proxy-2.0.45.tgz Configuring dnscrypt-proxy on OpenBSD The default configuration is in /etc/dnscrypt-proxy.toml, and will need to be updated before starting dnscrypt-proxy since it will give one of these errors, [FATAL] Unable to clone file descriptor: [bad file descriptor] or [FATAL] Duplicated file descriptors are above base It also contains deprecated references to blacklist and whitelist which will needs replacing. A known good configuration to start with is here: https://github.com/DNSCrypt/dnscrypt-proxy/blob/master/dnscrypt-proxy/example-dnscrypt-proxy.toml. Cloudflare DNS-over-HTTPS I am using the Cloudflare 1.1.1.1 DNS revolvers since they provide DNS-over-HTTPS and their response times are extremely fast. My ISP and network is also setup for IPv6, and that is configured to allow IPv6 clients and lookups. For fallback DNS, Quad9 is used since it’s separate from Cloudflare and has a relatively decent privacy and security features. [Cloudflare 1.1.1.1]: https://1.1.1.1/ [Quad9]: https://www.quad9.net/ #Use cloudflare DNS server_names = ['cloudflare', 'cloudflare-ipv6'] #Listen on local and LAN addresses for DNS listen_addresses = ['127.0.0.1:53', '[::1]:53', '192.168.7.221:53', '[fd82:738a:110d:1:2259:a6b:cd78:733b]:53'] max_clients = 250 user_name = '_dnscrypt-proxy' #Enable ipv4 and ipv6 ipv4_servers = true ipv6_servers = true #Include resolvers with the following configuration dnscrypt_servers = true doh_servers = true require_dnssec = true require_nolog = true require_nofilter = true #Allow TCP and UDP force_tcp = false timeout = 2500 keepalive = 30 #Logging log_level = 2 use_syslog = true #Certs cert_refresh_delay = 240 dnscrypt_ephemeral_keys = true tls_disable_session_tickets = true #Fallback to a non CloudFlare DNS if things aren't happy fallback_resolver = '9.9.9.9:53' ignore_system_dns = false Sources To reference revolvers and relays, sources are used to find publicly resources. These are setup by default in dnscrypt-proxy, but I’ve added a few changes like caching them to /var/dnscrypt-proxy and point to the latest v3. [sources]: https://github.com/DNSCrypt/dnscrypt-proxy/wiki/Configuration-Sources #Sources for resolvers and relays [sources] [sources.'public-resolvers'] urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md', 'https://download.dnscrypt.info/resolvers-list/v3/public-resolvers.md'] minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3' cache_file = '/var/dnscrypt-proxy/public-resolvers.md' refresh_delay = 72 [sources.'relays'] urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/relays.md', 'https://download.dnscrypt.info/resolvers-list/v3/relays.md', 'https://ipv6.download.dnscr ypt.info/resolvers-list/v3/relays.md', 'https://download.dnscrypt.net/resolvers-list/v3/relays.md'] minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3' cache_file = '/var/dnscrypt-proxy/relays.md' refresh_delay = 72 prefix = '' Block and Allow Lists Another powerful features of dnscrypt-proxy are filters, which can take on the role Pi-hole was doing with having a list of domains to block for ads, malware, and other reasons. To generate these lists, the generate-domains-blocklist.py is used. [filters]: https://github.com/DNSCrypt/dnscrypt-proxy/wiki/Filters [generate-domains-blocklist.py]: https://github.com/DNSCrypt/dnscrypt-proxy/blob/master/utils/generate-domains-blocklist/generate-domains-blocklist.py I took the blocklists that Pi-hole was using, and created a domains-blocklist.conf configuration to match, which gives it the same blocking sources as the Pi-hole was. # Local additions file:domains-blocklist-local-additions.txt # Peter Lowe's Ad and tracking server list https://pgl.yoyo.org/adservers/serverlist.php?hostformat=nohtml # Ads filter list by Disconnect https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt # Basic tracking list by Disconnect https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt # Sysctl list (ads) http://sysctl.org/cameleon/hosts # BarbBlock list (spurious and invalid DMCA takedowns) https://paulgb.github.io/BarbBlock/blacklists/domain-list.txt # NoTracking's list - blocking ads, trackers and other online garbage https://raw.githubusercontent.com/notracking/hosts-blocklists/master/dnscrypt-proxy/dnscrypt-proxy.blacklist.txt # Geoffrey Frogeye's block list of first-party trackers - https://hostfiles.frogeye.fr/ https://hostfiles.frogeye.fr/firstparty-trackers.txt # Steven Black hosts file https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts # Pihole Lists https://zeustracker.abuse.ch/blocklist.php?download=domainblocklist https://raw.githubusercontent.com/nickspaargaren/pihole-google/master/categories/androidparsed https://raw.githubusercontent.com/nickspaargaren/pihole-google/master/categories/analyticsparsed https://raw.githubusercontent.com/anudeepND/blacklist/master/facebook.txt This file also references domains-blocklist-local-additions.txt, which is setup to protect against DNS rebinding protection, [DNS rebinding protection]: https://en.wikipedia.org/wiki/DNS_rebinding#Protection # Localhost rebinding protection 0.0.0.0 127.0.0.* # RFC1918 rebinding protection 10.* 172.16.* 172.17.* 172.18.* 172.19.* 172.20.* 172.21.* 172.22.* 172.23.* 172.24.* 172.25.* 172.26.* 172.27.* 172.28.* 172.29.* 172.30.* 172.31.* 192.168.* The generate-domains-blocklist.py script will also require the files domains-allowlist.txt and domains-time-restricted.txt, which I just created as empty files to allow the blocklist creation to proceed $ touch domains-time-restricted.txt $ touch domains-allowlist.txt Generating the blocklist can be done manually or as a cronjob, $ python3 generate-domains-blocklist.py -o blocklist.txt Since I use Plex, work for a Account Based Marketing company, and Google reCaptcha is usually in blocklists, I created an allowlist.txt as well, [Plex]: https://www.plex.tv/ plex.direct demandbase.com recaptcha.google.com gc.zgo.a Putting this all together into the /etc/dnscrypt-proxy.toml blocking configuration, #Blocking configuration [blocked_names] ## Path to the file of blocking rules (absolute, or relative to the same directory as the executable file) blocked_names_file = '/home/micheal/dnscrypt-proxy/blocklist.txt' log_file = '/var/tmp/blocked.log' log_format = 'tsv' #Allow configuration [allowed_names] ## Path to the file of blocking rules (absolute, or relative to the same directory as the executable file) allowed_names_file = '/home/micheal/dnscrypt-proxy/allowlist.txt' Anonymized DNS One of the features I liked about dnscrypt-proxy is it offers is Anonymized DNS, which I setup to test out. [Anonymized DNS]: https://github.com/DNSCrypt/dnscrypt-proxy/wiki/Anonymized-DNS #Anonymized DNS relays [anonymized_dns] routes = [ { server_name='zackptg5-us-il-ipv4', via=['anon-cs-usca', 'anon-cs-usga'] }, { server_name='freetsa.org-ipv6', via=['anon-zackptg5-us-il-ipv6', 'anon-acsacsar-ams-ipv6'] } ] While this did work well, unfortunately the DNS query times were consistently 200ms+, which can appear as lag when browsing things. Since DNS-over-HTTPS is encrypted and filtering is setup, this was more of a nice-to-have instead of a requirements, so I ended up commenting it out. If/when Cloudflare provides DNSCRYPT I may re-visit it and see if response times have improved. Local DNS-over-HTTPS Another feature that Pi-hole didn’t support was DNS-over-HTTPS both for resolving and for serving requests locally. This is something built-in to dnscrypt-proxy with Local DoH that Firefox support for DoH can then use. By default Firefox will use Cloudflare DoH directly and was previously bypassing the Pi-hole, not getting the filtering features the rest of the network was. Now it can use the same filtering and continue to use DoH. [Local DoH]: https://github.com/DNSCrypt/dnscrypt-proxy/wiki/Local-DoH [Firefox support for DoH]: https://support.mozilla.org/en-US/kb/firefox-dns-over-https To setup DoH on dnscrypt-proxy, a self-signed certificate is required, openssl req -x509 -nodes -newkey rsa:2048 -days 5000 -sha256 -keyout localhost.pem -out localhost.pem This certificate is then used to listen on IPv4 and IPv6 addresses for DoH on port 3000, #DNS over HTTPS configuration [local_doh] listen_addresses = ['127.0.0.1:3000', '[::1]:3000', '192.168.7.221:3000', '[fd82:738a:110d:1:2259:a6b:cd78:733b]:3000'] path = "/dns-query" cert_file = "/home/micheal/dnscrypt-proxy/localhost.pem" cert_key_file = "/home/micheal/dnscrypt-proxy/localhost.pem" Firefox is then configured to use dnscrypt-proxy, for example over IPv6, https://fd82:738a:110d:1:2259:a6b:cd78:733b:3000/dns-query. Enabling dnscrypt-proxy Now that the configuration is all setup and dnscrypt-proxy is installed on OpenBSD, enable the service and start it, $ doas rcctl enable dnscrypt_proxy $ doas rcctl start dnscrypt_proxy This should start and /var/log/messages will show it starting, Mar 12 11:15:59 majora dnscrypt-proxy[1888]: dnscrypt-proxy 2.0.45 Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Network connectivity detected Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Dropping privileges Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Network connectivity detected Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Now listening to 127.0.0.1:53 [UDP] Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Now listening to 127.0.0.1:53 [TCP] Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Now listening to [::1]:53 [UDP] Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Now listening to [::1]:53 [TCP] Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Now listening to 192.168.7.221:53 [UDP] Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Now listening to 192.168.7.221:53 [TCP] Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Now listening to [fd82:738a:110d:1:2259:a6b:cd78:733b]:53 [UDP] Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Now listening to [fd82:738a:110d:1:2259:a6b:cd78:733b]:53 [TCP] Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Now listening to https://127.0.0.1:3000/dns-query [DoH] Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Now listening to https://[::1]:3000/dns-query [DoH] Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Now listening to https://192.168.7.221:3000/dns-query [DoH] Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Now listening to https://[fd82:738a:110d:1:2259:a6b:cd78:733b]:3000/dns-query [DoH] Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Source [public-resolvers] loaded Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Source [relays] loaded Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Loading the set of whitelisting rules from [/home/micheal/dnscrypt-proxy/allowlist.txt] Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Firefox workaround initialized Mar 12 11:15:59 majora dnscrypt-proxy[1888]: Loading the set of blocking rules from [/home/micheal/dnscrypt-proxy/blocklist.txt] Mar 12 11:16:04 majora dnscrypt-proxy[1888]: [cloudflare-ipv6] OK (DoH) - rtt: 14ms Mar 12 11:16:04 majora dnscrypt-proxy[1888]: [cloudflare] OK (DoH) - rtt: 83ms Mar 12 11:16:04 majora dnscrypt-proxy[1888]: Sorted latencies: Mar 12 11:16:04 majora dnscrypt-proxy[1888]: - 14ms cloudflare-ipv6 Mar 12 11:16:04 majora dnscrypt-proxy[1888]: - 83ms cloudflare Mar 12 11:16:04 majora dnscrypt-proxy[1888]: Server with the lowest initial latency: cloudflare-ipv6 (rtt: 14ms) Logging Initially to test things are working well, setup the query logs to write to /var/tmp/query.log, #Query logging, commented out unless for troubleshooting [query_log] file = '/var/tmp/query.log' format = 'tsv' As requests come in they will show up here. Blocked requests will also show up in /var/tmp/blocked.log. Depending on the number of devices making DNS requests, the query.log can get quite large it’s recommended to keep it enabled when initially testing something, and turning it off when not in use. Leaving blocked.log on is a good idea to help know what to add to a allowlist.txt in case something is blocked that you want to allow. Since a Raspberry Pi is most likely using an MicroSD card and by default OpenBSD will mount /tmp as a filesystem, it’s a good idea to to set /tmp as a memory filesystem to avoid excessive writes to the SD Card. OpenBSD has the mfs filesytem that can be used to mount a filesystem in-memory to help avoid this, [mfs]: https://man.openbsd.org/mount_mfs.8 mount_mfs is used to build a file system in virtual memory and then mount it on a specified node. Setup /tmp as mfs, first by unmounting it. This may require killing sndio processes and using the console instead of ssh as it will give a resource busy error when trying to unmount /tmp. $ doas umount /tmp $ chmod 1777 /tmp In /etc/fstab, comment out the old /tmp mount and add the mfs mount, #1400ced5c75f17ee.d /tmp ffs rw,nodev,nosuid 1 2 swap /tmp mfs rw,nodev,nosuid,-s=256M 0 0 Reboot, and /tmp will now show up as a mfs type, $ mount | grep /tmp mfs:73353 on /tmp type mfs (asynchronous, local, nodev, nosuid, size=524288 512-blocks) Full Configuration Here is the full configuration of /etc/dnscrypt-proxy combined from all the snippets above, #Use cloudflare DNS server_names = ['cloudflare', 'cloudflare-ipv6'] #Listen on local and LAN addresses for DNS listen_addresses = ['127.0.0.1:53', '[::1]:53', '192.168.7.221:53', '[fd82:738a:110d:1:2259:a6b:cd78:733b]:53'] max_clients = 250 user_name = '_dnscrypt-proxy' #Enable ipv4 and ipv6 ipv4_servers = true ipv6_servers = true #Include resolvers with the following configuration dnscrypt_servers = true doh_servers = true require_dnssec = true require_nolog = true require_nofilter = true #Allow TCP and UDP force_tcp = false timeout = 2500 keepalive = 30 #Logging log_level = 2 use_syslog = true #Certs cert_refresh_delay = 240 dnscrypt_ephemeral_keys = true tls_disable_session_tickets = true #Fallback to a non CloudFlare DNS if things arne't happy fallback_resolver = '9.9.9.9:53' ignore_system_dns = false #[query_log] # file = '/var/tmp/query.log' # format = 'tsv' #Sources for resolvers and relays [sources] [sources.'public-resolvers'] urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md', 'https://download.dnscrypt.info/resolvers-list/v3/public-resolvers.md'] minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3' cache_file = '/var/dnscrypt-proxy/public-resolvers.md' refresh_delay = 72 [sources.'relays'] urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/relays.md', 'https://download.dnscrypt.info/resolvers-list/v3/relays.md', 'https://ipv6.download.dnscrypt.info/resolvers-list/v3/relays.md', 'https://download.dnscrypt.net/resolvers-list/v3/relays.md'] minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3' cache_file = '/var/dnscrypt-proxy/relays.md' refresh_delay = 72 prefix = '' #Blocking configuration [blocked_names] ## Path to the file of blocking rules (absolute, or relative to the same directory as the executable file) blocked_names_file = '/home/micheal/dnscrypt-proxy/blocklist.txt' log_file = '/var/tmp/blocked.log' log_format = 'tsv' #Allow configuration [allowed_names] ## Path to the file of blocking rules (absolute, or relative to the same directory as the executable file) allowed_names_file = '/home/micheal/dnscrypt-proxy/allowlist.txt' #Anonymized DNS relays #[anonymized_dns] #routes = [ # { server_name='zackptg5-us-il-ipv4', via=['anon-cs-usca', 'anon-cs-usga'] }, # { server_name='freetsa.org-ipv6', via=['anon-zackptg5-us-il-ipv6', 'anon-acsacsar-ams-ipv6'] } #] #DNS over HTTPS configuration [local_doh] listen_addresses = ['127.0.0.1:3000', '[::1]:3000', '192.168.7.221:3000', '[fd82:738a:110d:1:2259:a6b:cd78:733b]:3000'] path = "/dns-query" cert_file = "/home/micheal/dnscrypt-proxy/localhost.pem" cert_key_file = "/home/micheal/dnscrypt-proxy/localhost.pem"