If you're using DNS to block malicious domains (NextDNS, Cloudflare, Quad9, etc), you'll find that it doesn't always block something that it should.
I always thought it would be neat to have a way to query multiple DNS providers and block the domain if any of them filter it, essentially combining the responses and responding appropriately based on a set of conditions. Now I can! You can see the application flow in the diagram below.
This project builds on the solution I put in place for our family about a year ago, when our school division started blocking DoH on my kids devices. I launched a Cloudflare Worker and used that to proxy to NextDNS, using a custom domain that they wouldn't easily detect and block. That solution proxied back to a single DNS provider (NextDNS). This project takes that idea and adds support for proxying, in parallel, to any number of DNS providers and leveraging the filtering capability that you want in all of them.
You can also send request logs to Grafana Loki to perform your own analytics and monitoring.
- Tests need to be written to validate various response scenarios
- Add support for allowlist and blocklist directly in the worker to override provider responses
- NodeJS
- Cloudflare Account
- The free tier is adequate for us but you will need to determine that for youself.
- Grafana Loki (optional)
- Send request logs to a Grafana Loki instance. The free tier on Grafana Cloud is adequate for us.
- Docker (optional)
- A docker compose manifest and dockerfile are included if you wish to run the worker locally.
name = "doh"
workers_dev = true
# Using an older version as this is the lastest one supported by workerd used in docker
# https://github.com/cloudflare/workerd
compatibility_date = "2022-09-26"
# Use a custom route if you have your own domain on Cloudflare
# route = "www.mydomain.com/top/level/path/to/entrypoints/*"
main = "./worker/script.js"
[build]
command = "npm run build"
[vars]
# Secrets managed outside of the file
# Run `npx wrangler secret put <NAME>` for each of these
# - LOKI_USERNAME
# - LOKI_PASSWORD
Modify the configuration for your needs. You can create as many endpoints as you need and they can each be configured to proxy to specific DNS providers. If you want to include headers, you can do that like I'm doing in the NextDNS example.
Only one provider should have main: true
. If more than one in each path is marked as main, you will receive an error on your DNS requests.
const debug = false
const loki = {
enabled: false,
url: "",
}
const endpoints = {
"/my/doh/path": {
dohProviders: [
{
host: "dns11.quad9.net",
path: "/dns-query",
},
{
host: "cloudflare-dns.com",
path: "/dns-query",
},
{
main: true,
host: "dns.nextdns.io",
path: "/abc123",
headers: {
"X-Device-Name": "My Device",
"X-Device-Model": "My Device Model",
},
},
],
},
}
export { debug, loki, endpoints }
If you plan to run the worker locally in docker, you'll need to perform this step. Otherwise ignore it.
If you want logs from the docker instance to be sent to your Grafana Loki instance, set your API username and password in this file.
using Workerd = import "/workerd/workerd.capnp";
const config :Workerd.Config = (
services = [
(name = "main", worker = .mainWorker),
],
sockets = [
( name = "http",
address = "*:8080",
http = (),
service = "main"
),
]
);
const mainWorker :Workerd.Worker = (
serviceWorkerScript = embed "worker/script.js",
compatibilityDate = "2022-09-26",
bindings = [
( name = "LOKI_USERNAME", text = "" ),
( name = "LOKI_PASSWORD", text = "" ),
],
);
npm install -u
The first time you run this, it will need to log into your Cloudflare account and provide permission for Wrangler.
npx wrangler publish
That's it, you should be able to use the worker along with your endpoint paths to configure DoH on your devices.
You can send logs for each DNS request to Grafana Loki. You'll need to add the credentials to your Cloudflare Worker, enable Loki and add the URL to src/config.js
.
npx wrangler secret put LOKI_USERNAME
npx wrangler secret put LOKI_PASSWORD
- The script does support
application/dns-json
but not all providers implement that properly. It is recommended you useapplication/dns-message
for your DoH queries to the endpoint. This should be the default on many of the clients.
A docker compose file is provided to get you started. You'll need to generate an SSL certificate and save the certificate as nginx/ssl/tls.crt
and the private key as nginx/ssl/tls.key
.
You can generate a self-signed certificate using the sample command below.
mkdir nginx/ssl
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes -keyout nginx/ssl/tls.key -out nginx/ssl/tls.crt -days 3650 -subj '/CN=doh-worker'