Enable automasq service to permit using Dnsmasq as global resolver without losing upstream DNS
This commit is contained in:
parent
3383077393
commit
1d66781c8d
2 changed files with 121 additions and 0 deletions
17
Library/LaunchAgents/org.insectsarerubbish.automasq.plist
Normal file
17
Library/LaunchAgents/org.insectsarerubbish.automasq.plist
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?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>org.insectsarerubbish.automasq</string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>/Users/dani/.local/bin/automasq</string>
|
||||||
|
<string>/usr/local/etc/resolv.conf</string>
|
||||||
|
</array>
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
104
local/bin/automasq
Executable file
104
local/bin/automasq
Executable file
|
@ -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)
|
Loading…
Reference in a new issue