Programmatically changing DNS resolvers, IP addresses and proxies on OSX isn’t rocket science but finding documentation on that is like looking for a needle in a haystack.
Changing the DNS configuration could have been as simple as updating
/etc/resolv.conf
, as OSX does provide an /etc/resolv.conf
file. But
unlike every other Unix variant out there, OSX doesn’t actually read
this file, as it is only there for compatibility with some legacy
tools.
Meet configd
“The configd daemon is responsible for many configuration aspects of the local system. configd maintains data reflecting the desired and current state of the system, provides notifications to applications when this data changes, and hosts a number of configuration agents in the form of loadable bundles.”
Think about it as a centralized key/value registry. Applications can watch for changes, add new key/value pairs and modify existing entries.
The scutil(8) command provides a command-line interface to this registry:
$ scutil > list subKey [0] = Plugin:IPConfiguration subKey [1] = Plugin:InterfaceNamer subKey [2] = Setup: subKey [3] = Setup:/ subKey [4] = Setup:/Network/BackToMyMac subKey [5] = Setup:/Network/Global/IPv4 subKey [6] = Setup:/Network/HostNames subKey [7] = Setup:/Network/Interface/en0/AirPort subKey [8] = Setup:/Network/Service/0CE7A64C-9174-41AC-B634-41F4B6B5FE02 subKey [9] = Setup:/Network/Service/0CE7A64C-9174-41AC-B634-41F4B6B5FE02/IPv4 subKey [10] = Setup:/Network/Service/0CE7A64C-9174-41AC-B634-41F4B6B5FE02/IPv6 subKey [11] = Setup:/Network/Service/0CE7A64C-9174-41AC-B634-41F4B6B5FE02/Interface subKey [12] = Setup:/Network/Service/0CE7A64C-9174-41AC-B634-41F4B6B5FE02/Proxies ... subKey [32] = Setup:/Network/Service/527FC752-8B1D-4111-A205-4E81F757704E subKey [33] = Setup:/Network/Service/527FC752-8B1D-4111-A205-4E81F757704E/IPv4 subKey [34] = Setup:/Network/Service/527FC752-8B1D-4111-A205-4E81F757704E/IPv6 subKey [35] = Setup:/Network/Service/527FC752-8B1D-4111-A205-4E81F757704E/Interface subKey [36] = Setup:/Network/Service/527FC752-8B1D-4111-A205-4E81F757704E/Proxies ... subKey [58] = Setup:/System subKey [59] = State:/IOKit/LowBatteryWarning subKey [60] = State:/IOKit/Power/CPUPower subKey [61] = State:/IOKit/PowerAdapter subKey [62] = State:/IOKit/PowerManagement/Assertions subKey [63] = State:/IOKit/PowerManagement/CurrentSettings subKey [64] = State:/IOKit/PowerManagement/SystemLoad subKey [65] = State:/IOKit/PowerManagement/SystemLoad/Detailed subKey [66] = State:/IOKit/PowerSources/InternalBattery-0 subKey [67] = State:/IOKit/SystemPowerCapabilities subKey [68] = State:/Network/BackToMyMac subKey [69] = State:/Network/Connectivity subKey [70] = State:/Network/Global/DNS subKey [71] = State:/Network/Global/IPv4 subKey [72] = State:/Network/Global/Proxies subKey [73] = State:/Network/Interface subKey [74] = State:/Network/Interface/en0/AirPort subKey [75] = State:/Network/Interface/en0/CaptiveNetwork subKey [76] = State:/Network/Interface/en0/IPv4 subKey [77] = State:/Network/Interface/en0/IPv6 subKey [78] = State:/Network/Interface/en0/Link subKey [79] = State:/Network/Interface/lo0/IPv4 subKey [80] = State:/Network/Interface/lo0/IPv6 subKey [81] = State:/Network/Interface/p2p0/Link subKey [82] = State:/Network/Interface/utun0/IPv6 subKey [83] = State:/Network/MulticastDNS subKey [84] = State:/Network/NetBIOS subKey [85] = State:/Network/PrivateDNS subKey [86] = State:/Network/Service/527FC752-8B1D-4111-A205-4E81F757704E/DHCP subKey [87] = State:/Network/Service/527FC752-8B1D-4111-A205-4E81F757704E/DNS subKey [88] = State:/Network/Service/527FC752-8B1D-4111-A205-4E81F757704E/IPv4 subKey [89] = State:/Users/ConsoleUser subKey [90] = com.apple.DirectoryService.NotifyTypeStandard:DirectoryNodeAdded subKey [91] = com.apple.network.identification subKey [92] = com.apple.opendirectoryd.node:/Search > get State:/Network/Global/DNS > d.show <dictionary> { ServerAddresses : <array> { 0 : 208.67.220.220 1 : 208.67.222.222 } }
As we can see, the registry holds most of the network settings,
including DNS settings. The Setup:/*
entries contain every available
network configuration (as configured in the preferences pane), whereas
State:/*
entries include only what’s currently active.
There are global DNS settings and configuration-specific DNS setings.
Programmatically accessing the registry
The overlooked DynamicStore
API, which is part of the SystemConfiguration
framework, let us access the configd
registry.
The following code snippet opens the registry, looks for active DNS-related entries, and updates them with a new property list containing OpenDNS resolvers.
Of course, changing the registry requires root privileges, but that’s all it takes.
As new entries can be seamlessly created, you can use the registry for centralizing configuration in your own apps, or just to save previous entries before altering them.
Changing network services
The technique describe above works fine. But the new settings aren’t persistent. More often than once, this actually comes in handy, in order to apply temporary settings and to rest assured that once the user reboots his machine, network settings will always be back to a sane state.
Tweaking the system configuration database is not recomended by Apple because some daemons may not be aware of a configuration change. In addition, you may want to permanently apply the new settings.
Another way to tackle this problem is to actually alter the network services.
Under OSX, a network service is a configuration for a network interface. To apply a change globally, we need to step over all network services, check whether DNS (or whatever we want to change) is a support protocol, and make the related changes.
This achieves the same thing as what one can do from the Network preferences pane.
Almost.
After changing settings this way, you will notice that ths Network
preferences pane properly reflects them. However, some apps won’t have
caught up with the changes. For example, the /etc/resolv.conf
isn’t
automatically updated.
The trick here is to open the system configuration registry as
described above, and then to close it without having done any actual
change. Just “touching” the registry will trigger observers like
configd
so that they can do their own magic.
An empty DNS servers list or an empty IP/network address means that
DHCP will be used in order to retrieve this information.
But this doesn’t happen automatically as the new settings are applied.
Explicitly renewing the DHCP lease is required, by calling
SCNetworkInterfaceForceConfigurationRefresh()
on each interface.