diff --git a/Library/LaunchAgents/org.insectsarerubbish.automasq.plist b/Library/LaunchAgents/org.insectsarerubbish.automasq.plist new file mode 100644 index 0000000..e5f0dc8 --- /dev/null +++ b/Library/LaunchAgents/org.insectsarerubbish.automasq.plist @@ -0,0 +1,17 @@ + + + + + KeepAlive + + Label + org.insectsarerubbish.automasq + ProgramArguments + + /Users/dani/.local/bin/automasq + /usr/local/etc/resolv.conf + + RunAtLoad + + + diff --git a/local/bin/automasq b/local/bin/automasq new file mode 100755 index 0000000..16c9074 --- /dev/null +++ b/local/bin/automasq @@ -0,0 +1,104 @@ +#!/usr/bin/python +"""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 + +original source: https://gist.github.com/edwardgeorge/270506 + +""" +import optparse +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) + + +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)