Thursday, 4 August 2005
Overriding DNS for domains in OS X Tiger
ACM@UIUC has a bunch of machines behind NAT, which are all in the non-publicly-served domain internal.acm.uiuc.edu, and as I started to need these machines more for my research, I got annoyed at having to do a nested ssh every time I wanted to access them.
The obvious solution was a VPN. I set up OpenVPN on our OpenBSD router box. I’ve done enough OpenVPN evangelism here, so I won’t do more than say it’s worth checking out.
Next came the client side. OpenVPN, at least on Unix platforms, doesn’t automatically modify your nameserver configuration (it used to not modify your routing table either), so I wrote a Python script which automatically edits resolv.conf to add the appropriate nameserver address for internal.acm when you connect, and remove it when you disconnect.
This worked wonderfully until I upgraded to Tiger. OS X 10.4, except for a few tools like host, no longer directly reads /etc/resolv.conf to determine nameservers and search domains. Instead, Tiger has a much more flexible (but rather poorly documented) DNS interface as part of the System Configuration framework. Not to worry, it does keep resolv.conf up to date with the current state of things, but if you change that file, don’t expect Safari, Mail, or even ssh or ping to recognize your changes.
After some poking around and help from a mailing list, I developed a solution. Try running scutil --dns, where you’ll see multiple resolvers, each resolver with its own set of (search) domains and nameservers—it’s like a routing table for DNS. This architecture solves my problem much more elegantly than editing resolv.conf. You can even be connected to multiple VPNs simultaneously, with a private nameserver and associated domains for each VPN, and everything just works.
I rewrote the script to use this new mechanism, and had been using it a month or so without problems, when I found this article posted at macosxhints.com, which proposes a much more primitive method of altering the nameserver configuration. So I posted a comment saying anyone could email me and I’d send them my script—a sure-fire procrastination tactic, since I was sure nobody would email me immediately, and I planned on cleaning the script up for more general use, just not then because I had several deadlines.
I’ve now had a chance to clean up the script so it is more generically useful. Here is the script with its associated OpenVPN config file.
In order to get the script to work, you’ll need to install PyObjC (has an installer), then the SystemConfiguration wrapper.
If you’re not familiar with using Python’s distutils, to install the wrapper, cd into the SystemConfiguration-0.3 directory, then run python setup.py install. If you get permissions errors (which you shouldn’t on a default OS X install, but…) you might need to sudo python setup.py install instead.
When the VPN is up, OS X Tiger will use the OpenVPN DHCP option-provided nameserver(s) for the corresponding search domains, but will use your existing nameserver(s) for everything else. Use scutil --dns to see one or more resolvers appear after you’re connected, and disappear when you disconnect.
Version 3.0a2 of Tunnelblick, an OS X OpenVPN GUI, now seems to work with this script, as long as you change the cd command in the OpenVPN config file to use an absolute path. It’d be great if it or similar software could adopt this approach automatically, so I didn’t have to write a script at all.
That solution didn’t worked for me.
I’ve installed TunnerBrick and used this scripts:
http://openvpn.net/archive/openvpn-users/2005-08/msg00123.html
Now i’m happy!
Glad it works for you. The method that script uses is extremely fragile, is not recommended by Apple, and does not work with multiple simultaneous VPN connections, however. I haven’t tried it, but it seems that dropping the current connection on VPN disconnect may interrupt existing connections.
I tried Tunnelblick 3.0a2, and it works slightly better. openvpnstart process hangs, but once I kill it, it seems to connect. It doesn’t cd to my home directory so any relative paths don’t work either. Perhaps 3.0 final will be usable.
I’ve updated my script so it reads the DHCP options from the environment and can be used as is.
This is excellent information.
The link to your script is giving me a permission error, so I’m not able to try it. Have you modified it recently? (The link to the config file works fine.)
Whoops. Permissions fixed, thanks.
can you specify which DHCP options the server should send?
Thaks a lot,
Wolfgang
Two quick notes that weren’t obvious to me right away. I was setting this up on my wifes laptop, and you need to have the Developer Tools installed (included on the Mac OS X server). Also, don’t forget to change the permissions on the acm-client.py script. Tunnelblick kept disconnecting on me, and I could figure out why — it was due to acm-client.py not being executable. To fix this, just type “chmod 755 acm-clien.py” in terminal.
Hope that helps someone! Thanks for the script — works great!
Wolfgang:
Sorry for the long delay in responding. The server-side options would be something like:
push “dhcp-option DNS 128.174.251.34”
push “dhcp-option DOMAIN internal.acm.uiuc.edu”
[…] Being a frequent OpenVPN/TunnelBlick user these days, I sometimes need to change my DNS resolver configuration after having established a tunnel. Until very recently I was under the impression any decent Un*x-like OS has its resolver configuration in /etc/resolv.conf. Well, this doesn’t apply for OSX. The resolv.conf in /etc is only a read-only mirror of the resolver configuration; changes in this file will not be propagated back to the resolver. No, this setting needs to be manipulated in a “dynamic store maintainted by configd(8)” [AFP548: Using scutil to set DNS server]. Thank you very much, Apple. UPDATE: The above apparently only applies to Tiger; the issue has been discussed in several other places before […]
I’m really trying to get this working, I’ve installed the required software, set the correct permissions and it seems to do something….
nslookup give the right results, /etc/resolv.conf looks modified.
However scutils –dns doesn’t show the new DNS servers, and ping fails to resolve the hostname.
Also what modifications to the script would be required to route ALL DNS queries through the tunnel? For me this is a better solution……
Sean
Hi Sean,
check if the python objc bridge has been installed correctly … there is a file text/test.py in the distributen that should give you at least the hostname (error later on does not matter).
At (only one) of my systems the bridge was not installed correctly – same effects, no error messages, resolve.conf modified, but not scutil.
Have fun, Wolfgang
Yes, make sure you’re using the same copy of python to run the script as you did to install into. Fink and DarwinPorts like to install their own Pythons, but they live in their respective worlds and don’t do native Mac GUI stuff, preferring X11. For a Mac-friendly Python, either use the built-in Python 2.3.5 or a framework build of Python 2.4.1.
The multiple-Python situation on the Mac is a bit confusing, but if you read the mailing list recently, people are working hard to get it better documented.
I have your script setup and it always references the DNS server provided by the OpenVPN server. scutil –dns shows the following first 2 entries:
boston:~ derek$ scutil –dns
DNS configuration
resolver #1
domain : example.com
search domain[0] : mydomain.com
search domain[1] : example.com
nameserver[0] : 192.168.0.1
order : 200000
resolver #2
domain : mydomain.com
nameserver[0] : 10.1.1.10
nameserver[1] : 10.1.1.20
order : 100400
You state “When the VPN is up, OS X Tiger will use the OpenVPN DHCP option-provided nameserver(s) for the corresponding search domains, but will use your existing nameserver(s) for everything else.”
Any ideas as to why I’m not experiencing that behavior? I experience this while using both 2.01 and 3.0 RC1 of tunnelblick on 10.4.4.
Not sure. I just tried it on 10.4.5, and it still seems to work. Here’s my scutil output:
[p7:1013] ~%scutil –dns 10:42PM
DNS configuration
resolver #1
domain : cs.uiuc.edu
search domain[0] : internal.acm.uiuc.edu
search domain[1] : sabi.net
search domain[2] : acm.uiuc.edu
search domain[3] : cs.uiuc.edu
search domain[4] : uiuc.edu
nameserver[0] : 128.174.252.4
nameserver[1] : 128.174.252.5
nameserver[2] : 128.174.5.58
nameserver[3] : 128.174.5.102
order : 200000
resolver #2
domain : internal.acm.uiuc.edu
nameserver[0] : 128.174.251.34
order : 100400
and here are some DNS queries as logged by ethereal:
[p4:1083] ~%sudo tethereal -i en1 -f “port 53” 10:45PM
[…]
29 91.827117 128.174.253.190 -> 128.174.251.34 DNS Standard query A wunderdog.internal.acm.uiuc.edu
30 91.833328 128.174.251.34 -> 128.174.253.190 DNS Standard query response A 10.0.9.30
31 101.892120 128.174.253.190 -> 128.174.252.4 DNS Standard query A ldap.uiuc.edu
32 101.896095 128.174.252.4 -> 128.174.253.190 DNS Standard query response CNAME wizard.cso.uiuc.edu A 128.174.5.70
So it definitely seems to be working. Make sure you’re using something that actually uses OS X’s native DNS stuff to test with (i.e., not ‘host’ or ‘dig’); I used ‘ping’ in my example above.
DId the 10.4.6 update break this for you? I can’t get any address on the network that I am connecting to resolve.
Still works for me under 10.4.6 (though actually we moved all our machines to University-routable space, so I no longer need this!)
Does the output of scutil –dns look OK?
No. scutil –dns still shows resolvers with DNS information from my cable modem, but not the DNS entries from my VPN connection. The correct DNS information is in “State:/Network/Service/DHCP-tap0/DNS”
One thing to note is that when I connect to VPN I get error “write to TUN/TAP : Input/output error (code=5)” unless I explicitly set tap0 to use DHCP with “ipconfig set tap0 DHCP”. Any ideas on why I have to force it DHCP mode?
Hey,
I’m still on Panther :-(. Do you have a copy of the script you used to update resolv.conf? I’m just modifying with a shell script, but I’d like to take a look at what you did to make it work with multiple tunnels.
Thanks,
Prasanna.
Hi Prasanna,
Unfortunately, the Panther DNS stuff doesn’t handle multiple tunnels – you can only have a single list of nameservers.
[…] njr 3.0 » Overriding DNS for domains in OS X Tiger (tags: DNS VPN macosx OSX openvpn python) […]
Suggestion:
It should detect the version of OS X, and if it’s Tiger or later, failure to import SystemConfiguration should be considered an error condition. (It took me far too long to realize that although the script was exiting with a success code, it had actually failed to import SystemConfiguration, and hence failed to effect the desired DNS changes.)
Actually, I am unable to install the SystemConfiguration framework on Python 2.4 on OSX 10.4.8 :(
It doesn’t seem to be compatible with that version of OSX anymore. Anyone had the same problem and was able to fix it?
Fred – you didn’t describe your problem adequately. “X doesn’t work” is almost never enough information. What error message do you get?
I believe the problem is related to the SystemCnnfiguration module not compatible with Intel version of OS X.
Oh, that’s quite possible since I have an Intel Mac. I got the alternative shell script working that Nicholas also posted. Thanks for that as well!
I use SystemConfiguration on a MacBook Pro (Intel) frequently. It required a hack to the installation. I sent the below to Bob Ippolito. I tried adding it to the Python wiki page he links to from his download page, but that wiki seems totally dysfunctional, so here it is for all who may benefit from it.
Subject: Pythonic wrapper for the Apple’s SystemConfiguration API
Date: Dec 16, 2006 12:59 PM
Bob:
I was trying to install the SystemConfig python wrapper in order to use Nicholas Riley’s OpenVPN helper script
(http://njr.sabi.net/2005/08/04/overriding-dns-for-domains-in-os-x-tiger/ )
I’m not a python expert by any stretch, but when I ran “setup.py install”, I got this error:
warngin: no files found matching ‘*’ under directory ‘build/lib.macosx-10.3-fat-2.4/SystemConfiguration/UNSystemConfiguration.framework’
Changing this line in the setup.py script:
template = [‘recursive-include build/lib.%s-%s/%s/UNSystemConfiguration.framework *’ % (get_platform(), get_python_version(), NAME),],
to this:
template = [‘recursive-include build/lib.%s-%s/%s/Default/UNSystemConfiguration.framework *’ % (get_platform(), get_python_version(), NAME),],
finds the files and eliminates the warning.
Is this something which should be fixed? Or did I misunderstand something about the install process?
If I just failed to read some instructions, then forgive my oversight.
Hi,
user of openVPN for quite a long time, I recently changed from XP to OsX, and I’m the only one in the company :-(
All of a sudden, openVPN does not work anymore, and it seems to be because of a DNS issue. After investigation, I do indeed connect to the office, can ping 192.168.0.2, but cannot go any further.
I’ve therefore browsed the web and discovered the present article which seems to be exactly about what I need, but I can’t figure out what exactly to do with the provided scripts and how to have them executed (automatically or not). I’m using Leopard.
Anyone to help a newbie like me have this to work?
Thanks,
Xavier