MacOS VPN doesn’t use the VPN DNS

Continuing with my previous post … as part of configuring it I went to “Advanced” > “DNS” in the VPN connection and put in my remote end DNS server and domain name to search. On Windows 10 I didn’t even have to do this – remote DNS and domains were automatically configured as part of connecting. Anyways, once I put these in though I thought it should just work out of the box but it didn’t.

So turns out many others have noticed and complained about this. I couldn’t find a solution as such to this but learnt about scutil --dns in the process. Even though the Mac OS has a /etc/resolv.conf file it does not seem to be used; rather, the OS has its own way of DNS resolution and scutil --dns lets you see what is configured. (I am very very sketchy on the details and to be honest I didn’t make much of an effort to figure out the details either). In my case the output of this command showed that the VPN provided resolver for my custom domain was being seen by scutil and yet it wasn’t being used – no idea why.

I would like to point out this post though that shows how one can use scutil to override the DHCP or VPN assigned DNS servers with another. Good to know the kind of things scutil can do.

And while on this confusing topic it is worth pointing out that tools like nslookup and dig use the resolver provided in /etc/resolv.conf so these are not good tools if you want to test what an average Mac OS program might be resolving a particular name to. Best to just ping and see what IP a name resolves to.

Anyways, I didn’t want to go down a scripting route like in that nice blog post so I tried to find an alternative.

Oh, almost forgot! Scoped queries. If you check out this SuperUser post you can see the output of scutil --dns and come across the concept of scoped queries. The idea (I think) is that you can say domain xyz.com should be resolved using a particular name server, domain abc.com should be resolved via another, and so on. From that post I also got the impression you can scope it per interface … so the idea would be that you can scope the name server for my VPN interface to be one, while the name server for my other interfaces to be another. But this wasn’t working in my case (or I had configured something wrong – I dunno. I am a new Mac OS user). Here was my output btw so you can see my Azure hosted domain rakhesh.net has its own name server, while my home domain rakhesh.local has its own (and don’t ask me where the name server for general Internet queries is picked up from … I have no idea!).

Anyways, here’s a link to scutil for my future reference. And story 1 and story 2 on mDNSResponder, which seems to be the DNS resolver in Mac OS. And while on mDNSResponder, if you want to flush you local DNS cache you can do the following (thanks to this help page):

What a mouthful! :)

Also, not related to all this, but something I had to Google on as I didn’t know how to view the routing table in Mac OS. If you want to do the same then netstat -nr is your friend.

Ok, so going back to my problem. I was reading the resolver(5) man page and came across the following:

Mac OS X supports a DNS search strategy that may involve multiple DNS resolver clients.

Each DNS client is configured using the contents of a single configuration file of the format described below, or from a property list supplied from some other system configuration database. Note that the /etc/resolv.conf file, which contains configuration for the default (or “primary”) DNS resolver client, is maintained automatically by Mac OS X and should not be edited manually. Changes to the DNS configuration should be made by using the Network Preferences panel.

Mac OS X uses a DNS search strategy that supports multiple DNS client configurations. Each DNS client has its own set of nameserver addresses and its own set of operational parameters. Each client can perform DNS queries and searches independent of other clients. Each client has a symbolic name which is of the same format as a domain name, e.g. “apple.com”. A special meta-client, known as the “Super” DNS client acts as a router for DNS queries. The Super client chooses among all available clients by finding a best match between the domain name given in a query and the names of all known clients.

Queries for qualified names are sent using a client configuration that best matches the domain name given in the query. For example, if there is a client named “apple.com”, a search for “www.apple.com” would use the resolver configuration specified for that client. The matching algorithm chooses the client with the maximum number of matching domain components. For example, if there are clients named “a.b.c”, and “b.c”, a search for “x.a.b.c” would use the “a.b.c” resolver configuration, while a search for “x.y.b.c” would use the “b.c” client. If there are no matches, the configuration settings in the default client, generally corresponding to the /etc/resolv.conf file or to the “primary” DNS configuration on the system are used for the query.

If multiple clients are available for the same domain name, the clients ordered according to a search_order value (see above). Queries are sent to these resolvers in sequence by ascending value of search_order.

The configuration for a particular client may be read from a file having the format described in this man page. These are at present located by the system in the /etc/resolv.conf file and in the files found in the /etc/resolver directory. However, client configurations are not limited to file storage. The implementation of the DNS multi-client search strategy may also locate client configurations in other data sources, such as the System Configuration Database. Users of the DNS system should make no assumptions about the source of the configuration data.

If I understand this correctly, what it is saying is that:

  1. The settings defined in /etc/resolv.conf is kind of like the fall-back/ default?
  2. Each domain (confusingly referred to as “client”) in the man-page can have its own settings. You define these as files in /etc/resolver/. So I could have a file called /etc/resolver/google.com that defines how I want the “google.com” domain to be resolved – what name servers to use etc. (these are the typical options one finds in /etc/resolv.conf).
  3. The system combines all these individual definitions, along with dynamically created definitions such as when a VPN is established (or any DHCP provided definitions I’d say, including wired and wireless) into a configuration database. This is what scutil can query and manipulate.

What this means for me though is that I can create a file called /etc/resolver/rakhesh.net (my Azure domain is rakhesh.net) with something like these:

Thus any requests for rakhesh.net will go via this name server. When I am not connected to VPN these requests will fail as the DNS server is not reachable, but when connected it will work fine.

What if I want to take this one step further though? As in I want DNS requests for rakhesh.net to go to its proper external DNS server when I am not on VPN but go via the internal DNS server when I am on VPN? That too is possible. All I have to do is have multiple files – since I can’t call all of them /etc/resolver/rakhesh.net – and within each specify the domain name via the domain parameter and also define the preference via a search_order parameter. The one with the lower number gets tried first.

So I now have two files. For internal queries I have /etc/resolver/rakhesh.net.azure (the name doesn’t matter):

For external queries I have /etc/resolver/rakhesh.net.inet:

The internal file has higher priority. I also added a timeout of 5 seconds so it doens’t spend too much time trying to contact the name server if the VPN is not connected. Easy peasy. This way my queries work via the internal DNS servers if I am connected to VPN, and via external DNS servers if I am not on VPN.

If I now look at the output of scutil --dns I see all this info captured:

So that’s it. Hope this helps someone!

Update (26th April 2022): I was mistakently referring to /etc/resolver/xxx as /etc/resolvers/xxx in many places. Thanks to Seth Kingsley for pointing this out. Fixed this. 👍🏼