Requirements
v1.3.2 or newer must be installed in order to use this guide.
Overview
ctrld is a customizable DNS forwarding proxy server that runs on any operating system, most routers, and can be used to send DNS queries to any upstream, using any DNS protocol. NextDNS mode allows for a 1 liner command setup of ctrld with NextDNS service, while relaying client specific metadata (MAC address, IP address, Hostname) to a NextDNS upstream, similar to the native app.
As far as I can tell, ctrld is feature complete with the NextDNS analog (all functionality of the NextDNS app is supported as of v1.3.2), but also includes other functionality such as support for all DNS protocols, multiple listeners, richer configuration syntax, support for more routers, and highly advanced modes of operation.
Supported Platforms
- Asus Merlin
- DD-WRT
- Firewalla
- FreshTomato
- GL.iNet
- OpenWRT
- pfSense / OPNsense
- Synology
- Ubiquiti (UniFi, EdgeOS)
- Windows
- MacOS
- Linux (any)
Why this exists
We want you to try our app with NextDNS, love all the things you can do with it, and then maybe check out this Control D thing.
How to Use
You can get the binaries here, but the simplest way to get ctrld on your device is using the installer command:
Linux/MacOS/FreeBSD Install
sh -c 'sh -c "$(curl -sL https://api.controld.com/dl)"'
Windows Install
Run this in cmd not Powershell.
powershell -Command "(Invoke-WebRequest -Uri 'https://api.controld.com/dl' -UseBasicParsing).Content | Set-Content 'ctrld_install.bat'" && ctrld_install.bat
Usage
The above commands will install the binary into the correct directory (installer will show you where), based on your platform. Here is an example from pfSense, but all platforms are the same.
[2.7.0-RELEASE][root@pfSense.home.arpa]/root: sh -c 'sh -c "$(curl -sSL https://api.controld.com/dl)"'
__ .__ .___
_____/ |________| | __| _/
_/ ___\ __\_ __ \ | / __ |
\ \___| | | | \/ |__/ /_/ |
\___ >__| |__| |____/\____ |
\/ installer \/
---------------------
| System Info |
---------------------
OS Type : freebsd
OS Vendor : pfSense
Arch : amd64
CPU : 13th Gen Intel(R) Core(TM) i7-13700K
Free RAM : 70 MB / 448 MB
---------------------
| Install Details |
---------------------
Binary URL : https://assets.controld.com/ctrld/freebsd/amd64/ctrld
Install Path : /usr/local/bin
---------------------
Install binary and run it? (y/n): y
- Starting download
- Making binary executable
- Launching /usr/local/bin/ctrld
---------------------
__ .__ .___
_____/ |________| | __| _/
_/ ___\ __\_ __ \ | / __ |
\ \___| | | | \/ |__/ /_/ |
\___ >__| |__| |____/\____ |
\/ dns forwarding proxy \/
Usage:
ctrld [command]
Available Commands:
run Run the DNS proxy server
service Manage ctrld service
start Quick start service and configure DNS on interface
stop Quick stop service and remove DNS from interface
restart Restart the ctrld service
reload Reload the ctrld service
status Show status of the ctrld service
uninstall Stop and uninstall the ctrld service
clients Manage clients
Flags:
-h, --help help for ctrld
-s, --silent do not write any log output
-v, --verbose count verbose log output, "-v" basic logging, "-vv" debug level logging
--version version for ctrld
Use "ctrld [command] --help" for more information about a command.
[2.7.0-RELEASE][root@pfSense.home.arpa]/root:
The binary is installed, but currently not doing anything. In order for it to do stuff, use the start command with the --nextdns flag, while supplying your ID.
[2.7.0-RELEASE][root@pfSense.home.arpa]/root: ctrld start --nextdns 8cec72
Nov 20 22:44:21.000 NTC Starting service
Nov 20 22:44:21.000 NTC Generating nextdns config: /etc/controld/ctrld.toml
Nov 20 22:44:26.000 NTC Service started
That's it, you're done here. You can check that it works using a dig or nslookup command, or simply view your Logs in NextDNS.
[2.7.0-RELEASE][root@pfSense.home.arpa]/root: dig test.com
; <<>> DiG 9.18.14 <<>> test.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 25793
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;test.com. IN A
;; ANSWER SECTION:
test.com. 3600 IN A 67.225.146.248
;; Query time: 42 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Mon Nov 20 22:44:38 UTC 2023
;; MSG SIZE rcvd: 53
[2.7.0-RELEASE][root@pfSense.home.arpa]/root:
By default, ctrld will send all traffic from your device or router to the chosen NextDNS profile, using DOH3 protocol, while appending LAN metadata just like the native app. If you wish to get fancy, read on.
Advanced Usages
Everything is config driven, and the start command will tell you where the config was written to, usually it's /etc/controld/ but the path can differ based on your router platform. You can edit this config to get ctrld to route your DNS queries in highly advanced ways. Here is some relevant material to get you started:
Once you crafted your perfect config, simply run: ctrld restart in order for changes to kick in. To see what LAN clients ctrld discovered using DHCP leases file, arp, mDNS, PTR probes or hosts file, you can run the ctrld clients list command. This client data will be appended to DNS queries. Be mindful, that this only works when DNS-over-HTTPS (any version) is used. DOT and DOQ don't support this.
[2.7.0-RELEASE][root@pfSense.home.arpa]/root: ctrld clients list
+-----------------------------------------+------------+-------------------+------------+
| IP | Hostname | Mac | Discovered |
+-----------------------------------------+------------+-------------------+------------+
| 10.0.10.1 | | 00:50:56:9f:0e:84 | arp |
| 10.0.10.209 | pfSense | 00:0c:29:f5:a3:55 | arp,dhcp |
| 10.0.10.238 | Office-Box | 74:56:3c:44:eb:5e | arp,mdns |
| 10.0.10.245 | Test-W11 | | mdns |
| 127.0.0.1 | pfSense | 00:0c:29:f5:a3:55 | dhcp |
| ::1 | pfSense | 00:0c:29:f5:a3:55 | dhcp |
| 2607:f0c8:8000:8210:10a:e664:e855:1f61 | Office-Box | | mdns |
| 2607:f0c8:8000:8210:20c:29ff:fef5:a355 | pfSense | 00:0c:29:f5:a3:55 | dhcp |
| 2607:f0c8:8000:8210:2d11:e044:9e90:a14c | Test-W11 | | mdns |
| 2607:f0c8:8000:8210:4d66:459f:6b76:1c16 | Test-W11 | | mdns |
| 2607:f0c8:8000:8210:dcf4:5b74:4f7e:bd7f | Office-Box | | mdns |
| fe80::20c:29ff:fef5:a355 | pfSense | 00:0c:29:f5:a3:55 | dhcp |
| fe80::1554:c4ab:cfba:189 | Office-Box | | mdns |
| fe80::ab3f:8a1c:df6b:91b9 | Test-W11 | | mdns |
+-----------------------------------------+------------+-------------------+------------+
[2.7.0-RELEASE][root@pfSense.home.arpa]/root:
By the way, ctrld will resolve any LAN-local A or PTR record for you, using the data it has in the client list.
[2.7.0-RELEASE][root@pfSense.home.arpa]/var: dig +short Test-W11
10.0.10.245
[2.7.0-RELEASE][root@pfSense.home.arpa]/var: dig +short -x 10.0.10.245
Test-W11.
DNS Steering Rules
Much like in the native app, you can steer DNS queries to different NextDNS Profiles based on:
- hostnames
- MAC addresses
- IP networks
This is accomplished using policies. Let's take a look at an example config file. Before you panic, it's simpler (and more powerful) than it looks.
[listener]
[listener.0]
ip = '0.0.0.0'
port = 53
[listener.0.policy]
name = 'My Policy'
networks = [
{'network.0' = ['upstream.0']},
{'network.1' = ['upstream.1']}
]
rules = [
{ '*.cool.domain' = ['upstream.1']},
{ '*.in-addr.arpa' = ['upstream.1']}
]
macs = [
{"14:54:4a:8e:08:2d" = ["upstream.1"]}
]
[network]
[network.0]
name = 'Main Subnets'
cidrs = ['10.0.0.0/24', '10.0.1.0/24']
[network.1]
name = 'Secret Subnet'
cidrs = ['10.0.99.0/24']
[upstream]
[upstream.0]
name = 'My NextDNS Resolver'
type = 'doh3'
endpoint = 'https://dns.nextdns.io/qwerty'
timeout = 5000
[upstream.1]
name = 'My Fancy Control D Resolver'
type = 'doh3'
endpoint = 'https://dns.controld.com/abcd1234'
timeout = 5000
Not all params are needed, and shown for illustrative purposes only. Let's go over the config section by section.
- In the
[listener]block we define our.... listener with an IP + port. - In the
[listener.0.policy]block we define the policy of how DNS traffic should be routed, let's skip that over for a second. - In the
[network]block we define our subnets if you want to leverage source IP based routing. If you do not, don't define any. - In the
[upstream]block we define our DNS upstreams where DNS traffic should be sent. You should have at least one of these, but here we have 2. - Coming back to the
[listener.0.policy]block. It strings together the definednetworksandupstreams, as well as new concepts likerulesandmacsand defines which upstream should be used if there is a match. - The matching order is: rules => macs => networks
So for example:
- A DNS query from
10.0.0.5would be sent toupstream.0, while a query from10.0.99.123would be sent toupstream.1 - A DNS query for
my-host.cool.domainfrom any subnet would be sent toupstream.1(since host rules match first) - A DNS query from a device with MAC address
14:54:4a:8e:08:2dfrom any subnet would be sent toupstream.1(since MAC rules match 2nd). - All PTR queries would be sent to
upstream.1
You can find more example configs for different use cases in the Wiki.
That's all there is to it. If you spot any issues with this guide, let us know.