Overriding DHCP- or VPN-assigned DNS servers in Mac OS X Leopard

Submitted by Hannes Schmidt on Sun, 05/03/2009 - 13:13.

I'll have to break sad news to you: /etc/resolv.conf has been made redundant in Mac OS X. The dig and nslookup utilities still read it but most applications use a different mechanism for picking DNS servers when resolving host names. They generally go through Darwin's resolver library which instead of reading /etc/resolv.conf looks up DNS servers via the SystemConfiguration framework backed by configd. Survival of the fittest, I guess, or, Darwin's intelligent design.

Ok, ok, I'll stop trying to be funny ... Anyways, this would be all swell if there wasn't the occasional need for manually specifying DNS servers. For me this need typically arises when I connect to a VPN managed by an operator from hell. After hours and hours of hard work (my fingertips still hurt from all the googling) I present to you a solution:

With the VPN connected, launch scutil with root privileges:

hannes-mbp:~ Sysop$ sudo scutil
Password:
List all network services with DNS configuration:
> list State:/Network/Service/[^/]+/DNS
  subKey [0] = State:/Network/Service/A3551F2D-62CE-1234-B79A-6EE50CA7AE30/DNS
  subKey [1] = State:/Network/Service/F194302A-846C-4321-9325-6813DAE148F2/DNS
Pick one and show its contents.
> show State:/Network/Service/A3551F2D-62CE-1234-B79A-6EE50CA7AE30/DNS
<dictionary> {
  SupplementalMatchDomains : <array> {
    0 : 
  }
  ServerAddresses : <array> {
    0 : 192.168.1.74
    1 : 217.0.43.81
  }
  SupplementalMatchOrders : <array> {
    0 : 100000
  }
}
Ahh, this is the one! So let's get rid of those pesky servers. Obtain a working copy of the DNS configuration entry. It's called ... drum roll ... well, obviously: "d" (rolls eyes).
> d.init
> get State:/Network/Service/A3551F2D-62CE-1234-B79A-6EE50CA7AE30/DNS
> d.show
<dictionary> {
  SupplementalMatchDomains : <array> {
    0 : 
  }
  ServerAddresses : <array> {
    0 : 192.168.1.74
    1 : 217.0.43.81
  }
  SupplementalMatchOrders : <array> {
    0 : 100000
  }
}
Reset the ServerAddresses entry to an empty array:
> d.add ServerAddresses *
> d.show
<dictionary> {
  ServerAddresses : <array> {
  }
  SupplementalMatchDomains : <array> {
    0 : 
  }
  SupplementalMatchOrders : <array> {
    0 : 100000
  }
}
Write the working copy back:
> set State:/Network/Service/A3551F2D-62CE-1234-B79A-6EE50CA7AE30/DNS
Note, that the line
d.add ServerAddresses *
clears the ServerAddresses array, thereby removing all DNS-servers tied to that particular connection ("service" in Apple-talk). Without service-specific DNS servers, Mac OS will fall back to DNS servers from other network services. Not sure how exactly that works. If you want to specify particular DNS servers, use
d.add ServerAddresses * 10.0.1.2 112.21.44.66

By the way, the "*" signifies array values, so it's not some kind of wild card.

Check the Networking Preference panel and you will notice those damn grayed out DNS server entries are gone! They will come back next time you connect and they will be first in the list. It would be fairly straight-forward to script the above scutil session but you'd still have to run the script manually after a network connection comes up.

( categories: Mac OS X | Administrator )
Submitted by Anonymous on Tue, 08/16/2011 - 16:08.
Hi Thanks for figuring this out. It worked flawlessly. I was wondering if there's a way to make this more permanent? I'd like to not have to do this everytime i connect to my vpn. Is there a way for one of my dns servers to be "permanently" added to the list and all dynamic entries will be added in addition to the server that i specify.
Submitted by Anonymous on Tue, 05/31/2011 - 08:15.

It isn't just vpn's managed by an operator from hell. If the local network uses split horizon, ie: internal DNS that lists hosts that don't need to be resolved externally, you lose that when you connect the vpn because you lose the internal DNS server for your local network.

For instance, when I connect to my home office VPN, I can resolve the internal ip addresses there fine (and external ip addresses work fine), however I lose the ability to resolve the internal ip addresses at our main office, so my email, bug tracking, call management and pretty much everything else that depends on DNS goes away. This completely breaks the standards for DNS where it would and should fall back to the other listed DNS servers to try when earlier ones fail to resolve.

Using the raw solution here would just make me lose the ability to resolve internal hosts on the VPN. While I haven't tried it yet, I presume a similar script to *add* local DNS servers to the list when connected would work. Either that or doing manual DNS entries for the VPN setup that include the remote DNS and local DNS, then you don't need to run the script at all.

Submitted by Anonymous on Tue, 07/06/2010 - 11:32.

Here is a script that I put together to run that rebuilds the /etc/resolv.conf from the scutil DNS dictionary.

-John

#
# Used to reconstruct the /etc/resolv.conf from the scutil dictionary of the current DNS configuration
# A backup copy of the file is made in /tmp with the date-time suffix added
#
RSLV=/etc/resolv.conf
BKUP=/tmp/resolv.conf.`date +%Y-%m-%d_%H%M%S`

# Backup copy of current resolv.conf file
cp $RSLV $BKUP

# Copy Mac OS comments to new version of file
egrep '^#' $BKUP > $RSLV

DOMAIN=$(scutil --dns | egrep '^  domain : ' | grep -v 'domain : local' | egrep -v '\.arpa$' | awk '{print $3}')
echo "domain $DOMAIN" >> $RSLV
echo "search $DOMAIN" >> $RSLV

IPS=$(scutil --dns | grep nameserver | awk '{print $3}')
for IP in $IPS
do
  echo "nameserver $IP" >> $RSLV
done

Submitted by Anonymous on Tue, 05/04/2010 - 08:05.
Thanks Hannes! This did the trick for me on Mac OSX 10.6.4 with a VPN connection.
Submitted by Anonymous on Thu, 04/01/2010 - 23:39.

You can also look for PPP services, find the one with the appropriate $IFNAME, and insert an empty dict in place of its DNS.

My 2cts .-)

SERVICES=$(echo "list State:/Network/Service/[^/]+/PPP" | scutil | cut -c 16- | cut -d / -f 1-4)

for SERVICE in $SERVICES
do
  if [ "$(echo show $SERVICE/PPP | scutil | grep InterfaceName | cut -c 19-)" == "$IFNAME" ]; then
    echo "set $SERVICE/DNS" | scutil
  fi
done

Submitted by Anonymous on Tue, 02/09/2010 - 07:17.
thx from me as well: had to adjust the DNS servers on the iPhone (2G, 3.1.2) using VPN. your script did the trick! cheers Harry
Submitted by Hannes Schmidt on Wed, 07/01/2009 - 18:01.
Thanks for sharing this, Julien! I'll drop your script on my system right away. For the other fellow readers: As Julien already pointed out, you will need to substitute the network service UUID in his script with the UUID that your system uses. -- Hannes
Submitted by Anonymous on Tue, 06/30/2009 - 03:26.
Hi again,

Following my previous comment, I'd like to share the script I added when the ppp connection goes up so this scutil session can be done automatically.

Create the following script as root /etc/ppp/ip-up and make it executable:

#!/bin/sh
# When the ppp link comes up, this script is called with the following
# parameters
#       $1      the interface name used by pppd (e.g. ppp3)
#       $2      the tty device name
#       $3      the tty device speed
#       $4      the local IP address for the interface
#       $5      the remote IP address
#       $6      the parameter specified by the 'ipparam' option to pppd

DEBUGFILE=/tmp/ip-up-debug.txt

# Here I create my routes using /sbin/route add ...

# Running the scutil session
`/usr/sbin/scutil < /etc/ppp/scutil_session.txt`
echo "vpn_no_dns => $?" >> $DEBUGFILE

Here are the needed lines to be executed within scutil, stored in /etc/ppp/scutil_session.txt

Note that /Network/Service/87FB33AE-EEBD-4675-A4FC-7BB0E576BC90/DNS is specific to my Network Service

d.init
get State:/Network/Service/87FB33AE-EEBD-4675-A4FC-7BB0E576BC90/DNS
d.add ServerAddresses *
set State:/Network/Service/87FB33AE-EEBD-4675-A4FC-7BB0E576BC90/DNS

I'm not expert at bash scripting, so if anyone could improve this script to dynamically retrieve the Network Service to reset, it would be nice

Hth

Julien

ps: Hannes, it seems format filters are not run through comments, even using p tags ...

Submitted by Anonymous on Mon, 06/29/2009 - 04:47.
Hi Hannes, My fingertips were also hurting after so much googling and ... finally, I found your post. Reading the first lines I was pleased to see we had the same problem. I did your scutil session and yeahhhhh, it seems to work. I will be totally sure after having spent a day or two behind my customer VPN without interruption. But still, thanks very much for identifying the problem and proposing a solution. I'm going to script the above scutil session and guess what, you can have this executed when the connection comes up by plugging into /etc/ppp/ip-up (you have /etc/ppp/ip-down when the connection goes down). Cheers from Paris Julien