Thumbnail: gravatar

Per-domain resolvers in macOS

by on under technology
4 minute read

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

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
macos, technology, dns