Skip to content

Instantly share code, notes, and snippets.

@patrickfuller
Last active January 29, 2024 16:29
  • Star 36 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save patrickfuller/08d3dffec086845d3a3249629677ffce to your computer and use it in GitHub Desktop.
Enables local DNS resolution of Unifi aliases
"""
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
Copy link
Author

@forty2 thanks for the fix! I updated the code with something that should be equivalent.

@waffle-stomper
Copy link

@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).

@timcrockford
Copy link

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"))

@patrickfuller
Copy link
Author

@timcrockford thanks for the info! I made a quick edit here that should be cross-compatible. Let me know if it works!

@timcrockford
Copy link

@patrickfuller yup, it works, thank you! You also need to apply the same fix when reading the config file.

@patrickfuller
Copy link
Author

patrickfuller commented Jul 26, 2019

@timcrockford done. There's probably a better way to do this but it works for a script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment