1

Background

I use a private network router which is a separate host to my desktop. This private ("corporate") network comes and goes (mostly under my control), and I want to make sure that when it is available my desktop will use the corporate network DNS, but stop using that DNS when the corporate network is not available.

For the sake of concrete examples to work with, my local network uses 172.16.1.0/24 with a DNS server at 172.16.1.1, while the corporate network uses 10.10.0.0/16 with a DNS server at 10.10.1.1.

The private network router uses dnsmasq which I've configured something like this:

# /etc/dnsmasq.d/default.dnsmasq
server=172.16.1.1
server=/mycompany.com/it.mycompany.com/mycompany.org/10.10.1.1

Where 172.16.1.1 is my local DNS (on my internet router) and 10.10.1.1 is the corporate DNS which handles DNS for private networks such as our intranet. Thus if I look up "apple.stackexchange.com" it will be resolved by 172.16.1.1 while "confluence.mycompany.com" will be resolved by 10.10.1.1.

So I've written a script which basically takes a bunch of CIDRs and configures route for them, then switches to the private network DNS. Here's a sanitised version of the script for completeness:

#!/bin/sh
# Alters routing and DNS to use private network routes and DNS for
# private network destinations

networks=( "10.10.0.0/16" "192.168.20.0/24"  )
gateway=172.16.1.10

if [ "$1" = "up" -o "$1" = "on" -o "$1" = "start" ] ; then
    echo "Setting up private configuration"
    for net in ${networks[@]}; do
        /sbin/route add -net $net -gateway $gateway
    done
    /usr/sbin/networksetup -setdnsservers 'Ethernet' $gatewayip
elif [ "$1" = "down" -o "$1" = "off" -o "$1" = "stop" ] ; then
    echo "Shutting down private configuration"
    /usr/sbin/networksetup -setdnsservers 'Ethernet' 'Empty'
    for net in ${networks[@]}; do
        /sbin/route delete -net $net -gateway $gateway
    done
else
    echo "Usage: $0 (start|stop)"
fi
dscacheutil -flushcache

This leads to a slightly bizarre situation where I'm passing all DNS requests through the private router, when the private router is just going to bounce most DNS requests right back to the Internet router. It works, but I don't think it's particularly tidy or robust.

The Question

Is there any way to configure macOS DNS similarly to my dnsmasq configuration, where I simply tell my Mac to keep using 172.16.1.1 for DNS except when the domain is mycompany.com or mycompany.org, in which case it it should use 10.10.1.1?

3
  • It sounds like creating /etc/resolver/mycompany.com and mycompany.org files with nameserver 10.10.1.1 in them (see my answer to this question) would do what you need. Sep 17, 2020 at 3:49
  • @GordonDavisson thank you, that solved my problem!
    – ManicDee
    Sep 17, 2020 at 22:54
  • There's also a solution using scutil here: apple.stackexchange.com/a/385218/141050 — so Real Soon Now™ I'll sort out if I can use that in my script.
    – ManicDee
    Sep 18, 2020 at 1:11

1 Answer 1

1

macOS uses a system configuration database to maintain things like which DNS servers to use, and what domains those serves are used for. This database is exposed to the user through the interactive scutil program.

To update DNS servers and search domains, the simplest invocation of this tool would happen like this:

[root@host]$ scutil
> d.init
> d.add ServerAddresses * 172.16.1.10
> d.set State:/Network/Service/UniqueKey/DNS
> quit

To perform this configuration change, I created two expect scripts, one to load the configuration and the other to unload the configuration.

Here's the script to load the DNS configuration (note the "Corporate-VPN" part of the key which is the unique identifier for this configuration element):

#!/opt/local/bin/expect -f
set timeout -1
spawn /usr/sbin/scutil
match_max 100000
expect "> "
send -- "d.init\r"
expect "> "
send -- "d.add ServerAddresses * $env(VPNHOST)\r"
expect "> "
send -- "d.add SupplementalMatchDomains * intranet.company.com services.company.com\r"
expect "> "
send -- "set State:/Network/Service/Corporate-VPN/DNS\r"
expect "> "
send -- "quit\r"
expect eof

And then the script to unload the configuration:

#!/opt/local/bin/expect -f
set timeout -1
spawn /usr/sbin/scutil
match_max 100000
expect "> "
send -- "remove State:/Network/Service/Corporate-VPN/DNS\r"
expect "> "
send -- "quit\r"
expect eof

There is still plenty of tidy-up to do here (notably handling errors).

The previous method I wrote using the Resolvers system remains below. It still works but it's not the recommended method.


As per Gordon's comment, using the resolver system provided a solution:

#!/bin/sh
# Alters routing and DNS to use private network routes and DNS for
# private network destinations

networks=( "10.10.0.0/16" "192.168.20.0/24"  )
work_domains=( "intranet.company.com" "services.company.com" "customer.org" )
gateway=172.16.1.10

if [ "$1" = "up" -o "$1" = "on" -o "$1" = "start" ] ; then
    echo "Setting up private configuration"
    for net in ${networks[@]}; do
        /sbin/route add -net $net -gateway $gateway
    done
    for netname in ${work_domains[@]}; do
        echo "nameserver $gateway" >> /etc/resolver/$netname
    done
elif [ "$1" = "down" -o "$1" = "off" -o "$1" = "stop" ] ; then
    echo "Shutting down private configuration"
    for netname in ${work_domains[@]}; do
        rm /etc/resolver/$netname
    done
    for net in ${networks[@]}; do
        /sbin/route delete -net $net -gateway $gateway
    done
else
    echo "Usage: $0 (start|stop)"
fi
dscacheutil -flushcache

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .