Skip to Content

Restart dnsmasq without sudo

Posted on

In one of my previous posts we discussed setting up dnsmasq to manage local domain names for development or testing purposes on your macOS machine. This makes it easier to manage these domains but you still need to restart the service with sudo after making changes.

The reason we need sudo is because dnsmasq runs on port 53 by default, which is a privileged port and requires root to bind to it. We can get rid of the sudo requirement simply by running dnsmasq on another port. Then we can use MacOS’s PF firewall to redirect all DNS requests on port 53 to dnsmasq.

Note that MacOS Mojave no longer enforces these requirements, so any user can now have a process listen to these formerly prileged ports (any port less than 1024). I’m still on MacOS High Sierra at the time of writing and have not verified this myself.

Before we start, make sure to halt the dnsmasq service:

sudo brew services stop dnsmasq

Update dnsmasq configuration

Edit the dnsmasq configuration at /usr/local/etc/dnsmasq.conf and change the port to a non-privileged one. I’ve set it to 8053:

port = 8053

Make sure this port is not in use by any other process. You can check with lsof -i :8053 | grep LISTEN: if nothing is listening to this port there will be no output.

After updating the configuration file, start dnsmasq again. This time, don’t use sudo:

brew services start dnsmasq

Check that its running with this quick test:

pgrep dnsmasq >/dev/null && echo "running" || echo "not running"

Forward requests with PF

DNS requests sent to port 53 will now need to be forwarded to dnsmasq on port 8053. We can make this happen by adding a redirect rule to PF.

Execute the following command:

echo "
rdr pass inet proto { tcp, udp }  from any to 127.0.0.1 port 53 -> 127.0.0.1 port 8053
" | sudo pfctl -ef -

Give it a quick try by resolving one of your local test domains using dig:

dig foobar.box @127.0.0.1

The correct IP should be shown in the output.

Make these changes persistent

Unfortunately, these changes will be lost when you reboot. To remedy this, we need to add this redirect rule to the PF config file and make sure PF is enabled on boot.

Create a separate anchor file with our new redirect rules at /etc/pf.anchors/org.dns.forward with the following contents:

rdr pass inet proto { tcp, udp }  from any to 127.0.0.1 port 53 -> 127.0.0.1 port 8053

Now we need to load this new file in the main PF configuration file, located at /etc/pf.conf. Because updates to your MacOS system could overwrite this file without warning, I prefer to work on a copy. We can then tell PF to load its configuration from our new file instead of the original one.

Create the copy:

sudo cp /etc/pf.conf /etc/pf.custom.conf

and edit the new file. Change its contents from this:

scrub-anchor "com.apple/*"
nat-anchor "com.apple/*"
rdr-anchor "com.apple/*"
dummynet-anchor "com.apple/*"
anchor "com.apple/*"
load anchor "com.apple" from "/etc/pf.anchors/com.apple"

to:

scrub-anchor "com.apple/*"
nat-anchor "com.apple/*"
rdr-anchor "com.apple/*"
rdr-anchor "org.dns.forward"
dummynet-anchor "com.apple/*"
anchor "com.apple/*"
load anchor "com.apple" from "/etc/pf.anchors/com.apple"
load anchor "org.dns.forward" from "/etc/pf.anchors/org.dns.forward"

This change adds a new rdr-anchor and load anchor directive.

Before we continue, parse and validate the new configuration to make sure there are no errors:

sudo pfctl -vnf /etc/pf.custom.conf

All that’s left to do is update the plist file at /System/Library/LaunchDaemons/com.apple.pfctl.plist so PF will read from our new configuration file and enable PF using the -e flag. Edit the file and look for:

<array>
  <string>pfctl></string>
  <string>-f</string>
  <string>/etc/pf.conf</string>
</array> 

Replace with the updated command:

<array>
    <string>pfctl</string>
    <string>-e</string>
    <string>-f</string>
    <string>/etc/pf.custom.conf</string>
</array>

Note: to be able to change the plist file, you need to disable SIP on your Mac! See this StackExchange answer on how to do this. Don’t forget to enable it again when you’re done!

Reboot your machine to apply these changes. Once restarted, send another DNS request to port 53:

dig foobar.box @127.0.0.1

You should get the correct response back!

From now on, you no longer need sudo to restart dnsmasq after making changes to its configuration. Just run brew services restart dnsmasq and you’re done.

comments powered by Disqus