Setup NAT DNS rules on UniFi Security Gateway
For the past few years I have used Steven Black's hosts files in my machine to block adware, trackers, etc. Recently I was made aware of the Pi-Hole project and want to move to that solution.
While learning more I discovered that many IoT devices (e.g., WyzeCam v3, Withings Aura, ...) have hardcoded DNS settings (e.g., 8.8.8.8) and bypass whatever values the DHCP server recommended. Who knew! But I get it, from their perspective it's one less thing to worry about.
Thankfully, I can setup NAT rules in Unifi Security Gateway (USG) to intercept all DNS requests (i.e., TCP and UDP on port 53) and reroute them wherever I want (the gateway, in my case).
There is lots of documentation and forum posts on how to do it but since it took me a couple of hours to figure things out I decided to write it down here hoping to save time to others (or myself in the future!).
IMPORTANT: CloudKey must be on separate VLAN
When I applied the approach below I had my CloudKey and laptops on the main LAN network, my IoT devices on VLAN 200, and guest devices on VLAN 300. Devices on the LAN network could not resolve domains while any on the VLANs could.
In lots of forum threads people suggest to add a "masquerading" rule to make things work. That does solve the problem but also hides the origin of the DNS request in Pi-Hole. That was not good enough for me.
After various trials and errors I discovered that simply moving my devices on a separated VLAN (and adding/adjusting the NAT rules appropriately) solved the issue. Currently, my USG and CloudKey are on LAN (192.168.1.x), laptops/phones on VLAN 100, IoT devices on VLAN 200, etc.
You will need a few things: 1. The password of your Unifi CloudKey (e.g., ubnt/...) 2. Enable SSH access to your USG (Controller -> System Settings -> Device SSH Authentication) and set user/password 3. The list of the VLAN IDs that you want to affect (e.g., default one, 200, 300, etc) 4. The name of your site - you can find it in the URL of the Controller Dashboard after "site", it is "default" for the main/first one
Inspect Controller's Traffic
First thing to learn is how to check on the traffic going on through your router. To do that, SSH into the controller (e.g., ssh [email protected]) and then do tail -f /var/log/messages. You will see lots of messages, to isolate the DNS ones you can run
tail -f /var/log/messages | grep 'DPT=53 '
it will display things like
Dec 30 20:32:46 ubnt kernel: [LAN_LOCAL-default-A]IN=eth1.200 OUT= MAC=cc:ee:dd:77:55:aa:55:55:33:ff:77:88:88:00:55:00:00:45 SRC=192.168.5.93 DST=8.8.8.8 LEN=64 TOS=0x00 PREC=0x00 TTL=64 ID=51477 DF PROTO=TCP SPT=65371 DPT=53 WINDOW=32768 RES=0x00 SYN URGP=0
in the case above, the interface is eth1 the VLAN ID is 200, the request was made by 192.168.5.93 and it was directed to 8.8.8.8 (instead of the local 192.168.5.1).
Add the configuration in the CloudKey
Now SSH into the CloudKey (e.g., ssh [email protected]) and go edit the config.gateway.json file. The right one to edit is located at /srv/unifi/data/sites/<sitename> where <sitename> will be default for the main site created.
You can use vim config.gateway.json to edit/add the file if it does not exist. Remember to execute chwon unifi:unifi config.gateway.json after editing and saving it.
Here is what to add to redirect all "foreign" DNS requests on eth1.200 (network eth1, vlan 200) to the internal DNS server (192.168.5.1)
{ "service": { "nat": { "rule": { "1": { "description": "Redirect all DNS requests to 192.168.5.1", "destination": { "address": "!192.168.5.1", "port": "53" }, "inbound-interface": "eth1.200", "inside-address": { "address": "192.168.5.1", "port": "53" }, "protocol": "tcp_udp", "type": "destination", "log": "enable" } } } } }
Once you saved it (vim key :x), go in the Controller Dashboard, click on Devices -> USG -> Config -> Manage Device -> Provision to force the configuration to be propagated.
To check if the configuration has been applied, SSH back into the USG and launch the following commands
configure show service nat
If your rule(s) show there, congratulations, they have been propagated correctly!
To see if they are applied, monitor for NAT- messages in the logs with tail -f /var/log/messages | grep 'NAT-'. To force it to happen, go on your computer and try to lookup some domain using a specified DNS, e.g.,
it should trigger the rule, e.g.,
Dec 30 20:34:16 ubnt kernel: [NAT-2-DNAT] IN=eth1.200 OUT= MAC=cc:ee:dd:77:55:aa:55:55:33:ff:77:88:88:00:55:00:00:45 SRC=192.168.5.93 DST=8.8.8.8 LEN=64 TOS=0x00 PREC=0x00 TTL=64 ID=51516 DF PROTO=TCP SPT=65362 DPT=53 WINDOW=32768 RES=0x00 SYN URGP=0
and it should be followed by an appropriate network connection, e.g.,
Dec 30 20:34:16 ubnt kernel: [LAN_LOCAL-default-A]IN=eth1.200 OUT= MAC=cc:ee:dd:77:55:aa:55:55:33:ff:77:88:88:00:55:00:00:45 SRC=192.168.5.93 DST=192.168.5.1 LEN=74 TOS=0x00 PREC=0x00 TTL=64 ID=37206 DF PROTO=UDP SPT=46620 DPT=53 LEN=54
If you see all this, you also know they are being applied!