Skip to content

Instantly share code, notes, and snippets.

Last active January 29, 2024 16:29
Show Gist options
  • Save patrickfuller/08d3dffec086845d3a3249629677ffce to your computer and use it in GitHub Desktop.
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': '', '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']
# Connect to gateway to start configuration.
client = paramiko.SSHClient()
sftp_client = client.open_sftp()
# 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]+);.*?}',
with['leases']) as in_file:
leases =
leases = leases.decode('utf-8')
except AttributeError:
for match in regex.finditer(leases):
ip, mac =,
mac_map[mac] = ip
regex = re.compile(r'static-mapping [-a-f0-9]+ {.*' +
r'?ip-address ([0-9.]+).*?' +
r'mac-address ([:a-f0-9]+).*?}',
with['config']) as in_file:
cfg =
cfg = cfg.decode('utf-8')
except AttributeError:
for match in regex.finditer(cfg):
ip, mac =,
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.
with['dnsmasq']) as in_file:
current =
except IOError:
current = ''
if conf.strip() != current.strip():
print("Reloading dnsmasq.")
with'/tmp/dnsmasq', 'w') as out_file:
client.exec_command('sudo cp /tmp/dnsmasq ' + paths['dnsmasq'])
client.exec_command('sudo /etc/init.d/dnsmasq force-reload')
Copy link

patrickfuller commented Jun 27, 2017


  • Enables LAN access of devices by their Unifi alias names


  • Edit an alias on the Unifi web UI (Clients tab)
  • Wait a minute
  • Go to e.g. http://my-alias

(from an empty docker container)

apt install python-pymongo python-paramiko openssh-client cron
ssh-keygen && ssh-copy-id user@
echo "* * * * * /usr/bin/python /usr/lib/unifi/" >> /path/to/crontab
service cron start

Copy link

Bhlowe commented Aug 29, 2017

Looks like just what I need. (until ubnt gets their act together!) Thanks for writing!

How is the mongo database supposed to get populated? I assumed the db is not on the USG/router? But I only see it being read, so I am not sure.

Also, current = None should be current= "None" in quotes?

So for me, the script runs, but the conf is empty, due to no aliases/host names.

Copy link

forty2 commented Oct 7, 2017

@patrickfuller Thanks a lot for this script, it's exactly what I was looking for!
@Bhlowe It's been a while, but in case you're still trying to get this working (or in case others come across this and have the same questions), the Mongo DB is where all the config info on your Unifi controller gets stored. The script should ideally be run as a cron job on the controller itself, though with some extra effort you may be able to arrange for it to run somewhere else.

As for current = None , that's fine as-is; instead, change the line that says:

     if conf.strip() != current.strip():

to this, which will update if there is a new configuration and there either isn't an old one, or the old one is different:

     if conf and (not current or conf.strip() != current.strip()):

With that one small change, I'm up and running and it's working great!

Copy link

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

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

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( -> regex.finditer("UTF-8"))

Copy link

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

Copy link

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

Copy link

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