""" | |
When run in cron, automatically adds compliant alias names to local DNS. | |
Use at your own risk. | |
Patrick Fuller, 25 June 17 | |
""" | |
import re | |
import paramiko | |
import pymongo | |
paths = { | |
'mongo': ('localhost', 27117), | |
'gateway': {'hostname': '192.168.1.1', 'username': 'user'}, | |
'leases': '/var/run/dhcpd.leases', | |
'config': '/config/config.boot', | |
'dnsmasq': '/etc/dnsmasq.d/dnsmasq.static.conf' | |
} | |
# Get alias-mac map through mongodb data store | |
alias_map = {} | |
db = pymongo.MongoClient(*paths['mongo']) | |
for client in db.ace.user.find({'name': {'$exists': True}}): | |
if re.sub(r'[-.]', '', client['name']).isalnum(): | |
alias_map[client['name']] = client['mac'] | |
db.close() | |
# Connect to gateway to start configuration. | |
client = paramiko.SSHClient() | |
client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) | |
client.connect(**paths['gateway']) | |
sftp_client = client.open_sftp() | |
try: | |
# Get mac-ip map by reading DHCP leases and reservations from config files | |
mac_map = {} | |
regex = re.compile(r'lease ([0-9.]+) {.*?' + | |
r'hardware ethernet ([:a-f0-9]+);.*?}', | |
re.MULTILINE | re.DOTALL) | |
with sftp_client.open(paths['leases']) as in_file: | |
leases = in_file.read() | |
try: | |
leases = leases.decode('utf-8') | |
except AttributeError: | |
pass | |
for match in regex.finditer(leases): | |
ip, mac = match.group(1), match.group(2) | |
mac_map[mac] = ip | |
regex = re.compile(r'static-mapping [-a-f0-9]+ {.*' + | |
r'?ip-address ([0-9.]+).*?' + | |
r'mac-address ([:a-f0-9]+).*?}', | |
re.MULTILINE | re.DOTALL) | |
with sftp_client.open(paths['config']) as in_file: | |
cfg = in_file.read() | |
try: | |
cfg = cfg.decode('utf-8') | |
except AttributeError: | |
pass | |
for match in regex.finditer(cfg): | |
ip, mac = match.group(1), match.group(2) | |
mac_map[mac] = ip | |
# Generate dnsmasq config file | |
conf = ''.join(['address=/{hn}/{ip}\n'.format(hn=alias, ip=mac_map[mac]) | |
for alias, mac in sorted(alias_map.items()) | |
if mac in mac_map]) | |
# Compare with current config. Update and reload if needed. | |
try: | |
with sftp_client.open(paths['dnsmasq']) as in_file: | |
current = in_file.read() | |
except IOError: | |
current = '' | |
if conf.strip() != current.strip(): | |
print("Reloading dnsmasq.") | |
with sftp_client.open('/tmp/dnsmasq', 'w') as out_file: | |
out_file.write(conf) | |
client.exec_command('sudo cp /tmp/dnsmasq ' + paths['dnsmasq']) | |
client.exec_command('sudo /etc/init.d/dnsmasq force-reload') | |
finally: | |
sftp_client.close() | |
client.close() |
@patrickfuller Thank you so much for this script! This is something that's frustrated me for a long time.
The only change I had to make was the location of the leases file (/var/run/dnsmasq-dhcp.leases).
I was playing with this today inside a FreeBSD jail using Python 3.6.9. For me, I had to add decode command to the file read lines in order to avoid an error about using string functions on a byte array. Might not be needed on a Linux system, but I've verified that the script is working under FreeBSD with this change (at least on my system!)
regex.finditer(in_file.read()) -> regex.finditer(in_file.read().decode("UTF-8"))
@timcrockford thanks for the info! I made a quick edit here that should be cross-compatible. Let me know if it works!
@patrickfuller yup, it works, thank you! You also need to apply the same fix when reading the config file.
@timcrockford done. There's probably a better way to do this but it works for a script.
@forty2 thanks for the fix! I updated the code with something that should be equivalent.