
Per-domain resolvers in macOS
macOS, DNS, and you
First things first: I don’t fully understand the DNS resolution scheme that Apple has dreamed up in macOS. Furthermore, I don’t really want to- I just want things to work in a manner that let’s me get on with my life.
The Problem
I run a fairly extensive home lab, with lots of projects and resources. I also
run the private DNS suffix .lan
to access all of these resources. I make
regular use of these resources for work. This comes into direct conflict with
our access control system for work. If I have the ACL software active, my
resolver address is replaced and I’m unable to access my lab via hostnames.
However, this isn’t an outright block, I can still resolve things if I specify
the nameserver that I want to use. With this knowledge, I set out to fix access
to my internal hosts. It did not go well. Multiple calls with our ACL provider,
and lots of tampering with things in /etc/resolv.conf
I was no further ahead.
Deploying dnsmasq
via brew
for conditional upstream DNS resolution suffered
from the same issues of being run over when the ACL software was active.
I eventually stumbled across this article from 2019 about this same thing. I’m not a huge fan of Medium, and the paywall model, so here’s the gist of the solution and a handy script to help you out!
The solution
The solution is available via /etc/resolver/<Private Namespace>
. In my case,
my internal DNS is using the suffix .lan
. Edit: this works for any arbitrary
domain or subdomain. You could employ the same solution for foo.bar.com
or
bar.com
and all requests that match the namespace will be routed to that
resolver.
sudo mkdir -p /etc/resolver/lan
# 172.18.0.2 is my internal pihole instance, which has an upstream rule that
# points to my authoritative internal DNS server. There are some shell nuances
# about redirection that mean you need to actually either be root, or fully wrap
# the `echo` call in a subshell call to sudo.
echo "nameserver 172.18.0.2" >> /etc/resolver/lan
To verify that you now have a private namespace resolver, use the following command:
scutil --dns
# ... many other results ...
resolver #10
domain : lan
nameserver[0] : 172.18.0.2
flags : Request A records
reach : 0x00000002 (Reachable)
As an aside dig
/host
are using /etc/resolv.conf
which makes
troubleshooting difficult. Make sure that you’re using dnscacheutil -q host -a
name
instead.
# will use /etc/reslov.conf, which is contains the ACL resolver address
dig pihole.lan +short
# using the resolution chain from scutil --dns
dscacheutil -q host -a name pihole.lan
name: pihole.lan
ip_address: 172.18.0.2
That’s it! It’s done!
A script
This script is the quickest path to a resolution(!). Enjoy!
#!/bin/bash
RES_PATH=/etc/resolver
ZONE=$1
NS=$2
if [[ -z $1 || -z $2 ]]; then
echo "Usage: $0 <zone> <nameserver IP address>"
exit 1
fi
# Don't clobber an existing configuration
if [ -e "$RES_PATH/$1" ]; then
RESOLVER=$(cat $RES_PATH/$1| awk '{print $2}')
echo "$1 already has a custom resolver at $RES_PATH/$1 using $RESOLVER"
exit 1
fi
# I forget if this exists in a clean install, so make sure it exists
if [ ! -d "$RES_PATH" ]; then
sudo mkdir -p $RES_PATH
fi
# make the file
sudo touch $RES_PATH/$1
# this needs to be encapsulated because redirection is a shell function of the
# current UID
sudo bash -c "echo \"nameserver $2\" > $RES_PATH/$1"
# verify that things have worked
RESOLVER=$(cat $RES_PATH/$1| awk '{print $2}')
echo "$1 now has a custom resolver at $RES_PATH/$1 using $RESOLVER"
scutil --dns |grep -B1 -A3 $1