-
Notifications
You must be signed in to change notification settings - Fork 21
pfSense and OPNsense Operations Guide
This article will discuss how ctrld
can be operated on FreeBSD based router/firewall devices with support for advanced use cases.
Since pfSense and OPNsense are very similar, this single guide applies to both (for the most part).
The simplest and quickest way to get ctrld
on your router machine is to run the 1-liner install command.
sh -c 'sh -c "$(curl -sSL https://api.controld.com/dl)"'
Take this command and execute it in the shell. This will download a totally safe bash script, and execute it with system privilege.
If you feel antsy about blindly running random bash scripts off the Internet with system privilege (don't blame you), you can simply download the appropriate FreeBSD binary from the Releases section for your CPU architecture. Put it into a nice folder that's in your system path, like /usr/local/bin/
and make it executable.
Now that you got the binary downloaded, you can run it with no args and check out the commands you can use to operate it.
[2.7.0-RELEASE][root@pfSense.home.arpa]/var: 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.
I also strongly encourage you to RTFM which documents all the params you can use while crafting a custom config. More on this later.
The simplest way to run ctrld
is using this command: ctrld start --cd RESOLVER_ID_HERE
while templating your Device's unique DNS resolver ID from the web control panel. When you run this command, the following things will happen:
- Basic config file is fetched from the API and written to
/etc/controld/ctrld.toml
-
unbound
anddnsmasq
processes are terminated as they already listen on port 53 !!! -
ctrld
starts listeners on TCP/UDP port 53 on all interfaces -
/etc/resolv.conf
file is updated to point to the listener - DNS is updated on the main interface to use
ctrld
- Init script is added to
/usr/local/etc/rc.d
soctrld
can auto-start on reboot
The state of the machine will be something like this:
[2.7.0-RELEASE][root@pfSense.home.arpa]/var: sockstat -l | grep ctrld
root ctrld 85084 3 stream /etc/controld/ctrld_control.sock
root ctrld 85084 8 udp4 *:5353 *:*
root ctrld 85084 18 tcp46 *:53 *:*
root ctrld 85084 19 udp46 *:53 *:*
root ctrld 85084 21 udp6 *:5353 *:*
[2.7.0-RELEASE][root@pfSense.home.arpa]/var: cat /etc/resolv.conf
# resolv.conf(5) file generated by ctrld
# DO NOT EDIT THIS FILE BY HAND -- CHANGES WILL BE OVERWRITTEN
nameserver 127.0.0.1
[2.7.0-RELEASE][root@pfSense.home.arpa]/var: cat /etc/controld/ctrld.toml
# AUTO-GENERATED VIA CD FLAG - DO NOT MODIFY
[listener]
[listener.0]
ip = '0.0.0.0'
port = 53
[network]
[network.0]
name = 'Network 0'
cidrs = ['0.0.0.0/0']
[upstream]
[upstream.0]
type = 'doh'
endpoint = 'https://dns.controld.com/abcd1234'
timeout = 5000
[2.7.0-RELEASE][root@pfSense.home.arpa]/var: ctrld clients list
+-----------------------------------------+--------------------------------------+-------------------+------------+
| IP | Hostname | Mac | Discovered |
+-----------------------------------------+--------------------------------------+-------------------+------------+
| 0.0.0.0 | 64d4f0da-6012-4483-adff-d0d434ef6476 | | mdns |
| 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.222 | test-virtual-machine | 00:0c:29:4a:5c:57 | arp,mdns |
| 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 |
+-----------------------------------------+--------------------------------------+-------------------+------------+
If all you wanted was to receive DNS queries and send them all to a single Control D upstream using DNS-over-HTTPS, you have succeeded. If you didn't like the whole "kill unbound" thing as you use it for other purposes, then read on!
In order to be able to modify the generated config file on disk, you need to de-couple it from the API if you used the --cd RESOLVER_ID_HERE
flag (if not, then you're already in this mode and can skip doing this).
To do this, execute the following commands:
-
ctrld stop
- this will stop the service -
ctrld start
- this will start the service in "local config mode" which enforces the config on disk
Now you can make changes to it, and execute ctrld reload
command to enforce your changes.
If you're running ctrld
in local config mode, some "yolo behavior" will no longer occur. Namely, you are now responsible for listener conflicts. If unbound
and/or dnsmasq
are listening on port 53, ctrld
will not be able to start and you will be greeted with an error:
[2.7.0-RELEASE][root@pfSense.home.arpa]/var: sockstat -l | grep \:53
nobody dnsmasq 644 4 udp4 *:53 *:*
nobody dnsmasq 644 5 tcp4 *:53 *:*
nobody dnsmasq 644 6 udp6 *:53 *:*
nobody dnsmasq 644 7 tcp6 *:53 *:*
unbound unbound 98986 3 udp6 ::1:53 *:*
unbound unbound 98986 4 tcp6 ::1:53 *:*
unbound unbound 98986 5 udp4 127.0.0.1:53 *:*
unbound unbound 98986 6 tcp4 127.0.0.1:53 *:*
[2.7.0-RELEASE][root@pfSense.home.arpa]/var: ctrld start
Dec 8 01:51:35.000 NTC Reading config: /etc/controld/ctrld.toml
Dec 8 01:51:35.000 NTC Starting service
Dec 8 01:51:35.000 ERR ctrld service may not have started due to an error or misconfiguration, service log:
Dec 8 01:51:35.000 ??? ================================
Dec 8 01:51:35.000 ??? Dec 8 01:51:35.000 FTL listener.0 failed to listen: listen udp 0.0.0.0:53: bind: address already in use
listen tcp 0.0.0.0:53: bind: address already in use
Dec 8 01:51:35.000 ??? ================================
Dec 8 01:51:35.000 NTC Service uninstalled
As you're probably aware, if you need to still run either service at the same time as ctrld
, you need to put them on a different port (or run ctrld
on a different port instead).
One of the most common use cases is delegating local DNS resolution to unbound
for all your LAN-local domains and PTR records, while sending all external DNS queries to the Control D upstream.
If you're using v1.3.2 (or newer) of ctrld
you technically don't have to do anything, as ctrld
(with client discovery enabled) 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.
Well, that's pretty neat. However you may not be as impressed as I am, and still want to leverage your trusty unbound
instance, or any other LAN-local DNS resolver. That's fine, we can do that too.
I'm going to assume you already have unbound
or another DNS server running on a non-standard port, say 5555 which is capable of resolving local domains and serving PTR records for devices on a different vlan. Let's steer traffic to it using a custom config, which would look something like this:
[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 Fancy CD Resolver'
type = 'doh'
endpoint = 'https://dns.controld.com/abcd1234'
timeout = 5000
[upstream.1]
name = 'Custom Resolver'
type = 'legacy'
endpoint = '10.0.0.1:5555'
timeout = 1000
Not all params are needed, and shown for illustrative purposes only. It's not as scary as it looks. Let's go over it.
- 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 definednetworks
andupstreams
, as well as new concepts likerules
andmacs
and 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.5
would be sent toupstream.0
, while a query from10.0.99.123
would be sent toupstream.1
- A DNS query for
my-host.cool.domain
from 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:2d
from 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.
If your existing router configs are dear to your heart and you don't want no stinkin' 3rd party processes messing with them, I get you, and there is a solution to that as well.
If you simply want ctrld
to spawn a listener (or multiple listeners) in order to receive DNS queries and follow the config defined logic, while not making any changes to the system (modifying DNS on interface, editing resolv.conf, etc), then the service start
sub-command is your friend.
In this mode you're likely running ctrld
on a non-standard port, so modify your config and set it:
[listener]
[listener.0]
ip = '0.0.0.0'
port = 42069
.....
Then execute the service start
command (it's just like start
but with service
before it).
[2.7.0-RELEASE][root@pfSense.home.arpa]/var: ctrld service start
Dec 14 00:02:08.000 NTC Reading config: /etc/controld/ctrld.toml
Dec 14 00:02:08.000 NTC Starting service
Dec 14 00:02:13.000 NTC Service started
You can check the listeners, and notice that ctrld
is listening on the config defined port, as well as the mDNS port for client discovery (you can shut this off if you don't like it by setting the discover_mdns
config param to false):
[2.7.0-RELEASE][root@pfSense.home.arpa]/var: sockstat -l | grep ctrld
root ctrld 36674 3 stream /etc/controld/ctrld_control.sock
root ctrld 36674 8 udp4 *:5353 *:*
root ctrld 36674 11 udp6 *:5353 *:*
root ctrld 36674 19 tcp46 *:42069 *:*
root ctrld 36674 20 udp46 *:42069 *:*
If you send DNS queries to this port, they will be subject to your config defined rules, and will be sent to Control D (if that's what you want).
[2.7.0-RELEASE][root@pfSense.home.arpa]/var: dig verify.controld.com +short @127.0.0.1 -p 42069
api.controld.com.
147.185.34.1
Protip: verify.controld.com
will only resolve if you're using a Control D upstream, so that's a good domain to VERIFY that it's working.
If you're having trouble you can always contact us and we'll help you. But you can also wear your big boy pants and check out the troubleshooting guide and likely self-resolve the issue in no time.