About 3.5 years ago when my family and I moved to our current home, we had to hire a plumber to come in and rerun all of the pipes in the house (if you’re curious why, check out this link.). While it was a pain in the ass to do there were a few good things about this.
1. We got the seller to pay for it.
2. We did it before we occupied the house so it really didn’t impact us and they got it done in about 1.5 days.
3. They opened up holes in the walls throughout the house which gave me a chance to run CAT6 cables throughout the entire house pretty easily.
As part of this “network modernization,” I decided to set up a Ubiquiti network with a managed switch, three access points, and a pfSense firewall. It’s been working fine for the most part, but one annoying thing is that to take full advantage of everything, I need a network controller running 24/7 so the access points can migrate devices gracefully. You can buy a purpose-built device for this, but I was cheap and just ran it on my desktop when I needed to make changes.
Last weekend, I finally decided to fix that. In my utility closet, I have two small servers—one used as a sensor and one for a Splunk deployment—so I set up a containerized version of the controller to run 24/7.
LinuxServer.io hosts a variety of Docker images, including one for the latest version of the UniFi Network Application. The documentation is straightforward, and I had it running in about 15 minutes.
services: unifi-network-application: image: lscr.io/linuxserver/unifi-network-application:latest container_name: unifi-network-application environment: - PUID=1001 - PGID=1001 - TZ=Etc/UTC - MONGO_USER=unifi - MONGO_PASS=VYr3hC2XcZGs2fURBkkg4QhoL2JqMx - MONGO_HOST=unifi-db - MONGO_PORT=27017 - MONGO_DBNAME=unifi - MONGO_AUTHSOURCE=admin - MEM_LIMIT=1024 #optional - MEM_STARTUP=1024 #optional - MONGO_TLS= #optional volumes: - /opt/unifi/config:/config ports: - 8443:8443 - 3478:3478/udp - 10001:10001/udp - 8080:8080 - 1900:1900/udp #optional - 8843:8843 #optional - 8880:8880 #optional - 5514:5514/udp #optional restart: unless-stopped unifi-db: image: docker.io/mongo:8.0-rc container_name: unifi-db environment: - MONGO_INITDB_ROOT_USERNAME=root - MONGO_INITDB_ROOT_PASSWORD=HGRAwQcJvnmqoLmKxcH6ky2UxYHDCM - MONGO_USER=unifi - MONGO_PASS=VYr3hC2XcZGs2fURBkkg4QhoL2JqMx - MONGO_DBNAME=unifi - MONGO_AUTHSOURCE=admin volumes: - /opt/unifi/mongo/data:/data/db - /opt/unifi/mongo/init-mongo.sh:/docker-entrypoint-initdb.d/init-mongo.sh:ro restart: unless-stopped
It started without a hitch—or so I thought. Once logged in, I waited for all my UniFi devices to connect automatically, just like they had when I ran the controller on my desktop. Ten minutes later… nothing. My suspicion: the control plane for my devices is on a different VLAN than the server, so they couldn’t see each other.
A while back, I noticed a periodic DNS query for unifi.<network>.home
in my Zeek logs but didn’t think much of it. When the devices didn’t connect this time, I decided to investigate. Since I’d never configured a DNS record for it, the query returned nothing. I fixed that in pfSense by going to Services > DNS Resolver > General Settings and adding a Host Override entry.
That worked for all but one device. A packet capture revealed it was only querying for unifi
(no domain suffix). A hard reset didn’t fix it, and pfSense still wasn’t resolving it. I was stuck.
Enter DHCP Option 43, which allows “Vendor Specific Information.” For Ubiquiti devices, this can include the controller’s IP address (sadly, not its DNS name). Easy fix, right? Except pfSense’s DHCP server GUI doesn’t let you add it directly. According to the docs, you can add unsupported options using custom JSON—except my pfSense version didn’t have the necessary field. So… I upgraded my firewall.
Post-upgrade, I added the option under Services > DHCP Server > Settings:
{ "option-def": [ { "name": "unifi", "code": 1, "space": "vendor-encapsulated-options-space", "type": "string" } ] }
Then, under Services > DHCP Server > <Network Control VLAN> , I added:
{ "option-data": [ { "name": "vendor-encapsulated-options" }, { "name": "unifi", "space": "vendor-encapsulated-options-space", "csv-format": false, "data": "0xc0a84202" } ] }
The data
field is the hex-encoded IP address of the UniFi controller. Why not just use dotted-decimal notation? Who knows. But once I rebooted the device, it picked up the Option 43 data, and a few minutes later it joined the controller with the rest of its friends.