Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save dmitryd/318f6d61933111cd9fb4 to your computer and use it in GitHub Desktop.
Save dmitryd/318f6d61933111cd9fb4 to your computer and use it in GitHub Desktop.
Use DHCP nameservers for dnsmasq in OS X. Update automatically when they change.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>KeepAlive</key>
<true/>
<key>Label</key>
<string>local.automasq</string>
<key>ProgramArguments</key>
<array>
<string>/opt/local/bin/automasq.py</string>
<string>/opt/local/etc/resolv.dnsmasq</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/automasq-out.log</string>
<key>StandardErrorPath</key>
<string>/tmp/automasq-err.log</string>
</dict>
</plist>
#!/usr/bin/python2.6
"""OSX-based script to watch for changes to network state and write out a
second resolv.conf file containing the DHCP provided nameservers, intended
for use with a local resolver such as dnsmasq. This is to workaround the
changes in Snow Leopard from Leopard with regards to DNS resolution.
ie: the inability to have both manually configured nameservers and
DHCP provided ones as well as the issues with split-DNS.
usage: python automasq.py /path/to/second/resolv.conf
Slightly modified version of https://github.com/edwardgeorge/automasq/blob/master/automasq.py
"""
import optparse
import os
#import psutil
#import signal
import sys
from SystemConfiguration import *
GLOBAL_KEY = 'State:/Network/Global/IPv4'
class Watcher(object):
def __init__(self, filename, defaults_filename=None,
append_defaults=False):
self.filename = filename
self.defaults = defaults_filename
self.append = append_defaults
store = self.store = SCDynamicStoreCreate(None, "automasq",
self.dynamicStoreChanged, None)
SCDynamicStoreSetNotificationKeys(store, None, [GLOBAL_KEY])
source = self.source = SCDynamicStoreCreateRunLoopSource(None,
store, 0)
self.write_file(self.get_primary_dns(store))
loop = self.loop = CFRunLoopGetCurrent()
CFRunLoopAddSource(loop, source, kCFRunLoopCommonModes)
CFRunLoopRun()
def write_file(self, servers=[]):
with open(self.filename, 'w+') as f:
for server in servers:
f.write('nameserver %s\n' % server)
if (self.append or not servers) and self.defaults is not None:
with open(self.defaults) as d:
f.write(d.read())
def process_dns_for_service(self, store, service):
key = 'State:/Network/Service/%s/DNS' % service
val = SCDynamicStoreCopyValue(store, key)
data = list(dict(val)['ServerAddresses'])
return data
def get_primary_dns(self, store=None):
store = store or self.store
val = SCDynamicStoreCopyValue(store, GLOBAL_KEY)
if val:
data = dict(val)
svcid = data['PrimaryService']
return self.process_dns_for_service(store, svcid)
else:
return []
def dynamicStoreChanged(self, store, changedKeys, info):
servers = []
for key in list(changedKeys):
#if key == GLOBAL_KEY:
servers = self.get_primary_dns(store)
self.write_file(servers)
os.system('/opt/local/bin/port unload dnsmasq');
os.system('/opt/local/bin/port load dnsmasq');
# pids = psutil.get_pid_list()
# for pid in pids:
# if psutil.Process(pid).name() == "dnsmasq":
# os.kill(pid, signal.SIGHUP)
# break
def dummy_timer(*args):
pass
def main(filename, options):
# this gives us a callback into python every 1s for signal handling
CFRunLoopAddTimer(CFRunLoopGetCurrent(),
CFRunLoopTimerCreate(None, CFAbsoluteTimeGetCurrent(), 1.0, 0, 0,
dummy_timer, None),
kCFRunLoopCommonModes)
try:
watcher = Watcher(filename, defaults_filename=options.default,
append_defaults=options.append_defaults)
except KeyboardInterrupt, e:
# exiting
pass
if __name__ == '__main__':
usage = "usage: %prog [options] output-file"
parser = optparse.OptionParser(usage)
parser.add_option('-d', '--default-conf', dest='default',
help='default conf if no resolvers provided', metavar='RESOLVCONF')
parser.add_option('-a', '--append', dest='append_defaults',
action='store_true',
help='always append defaults to generated resolv.conf')
opts, args = parser.parse_args()
if len(args) != 1:
parser.error("specify a single output-file")
if opts.append_defaults and not opts.default:
parser.error("default conf must be specified to be able to append")
main(args[0], opts)
#.... Around line 32 next code:
# Change this line if you want dns to get its upstream servers from
# somewhere other that /etc/resolv.conf
#resolv-file=/etc/resolv.conf
resolv-file=/opt/local/etc/resolv.dnsmasq
#...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment