Hairpin NAT (or NAT reflection) on Ubiquiti Edgerouter

During Winter break I deployed a couple of services in my LAN that I want to make accessible from the internet. I did not want to use Cloudflare tunnel but I still want to use my own domain without using a dynamic IP service.

So after I’ve deployed the services, I created the DNS record and a scheduled job to keep those records updated with the current public IPv4 of my home connectivity.

When I tried to access those services using the DNS name from within my LAN i realized that I was redirected to the Edgerouter Web GUI, my LAN gateway, and of course this was unexpected. After some thinking I recall that this is a common issue that is called NAT reflection or haripin NAT.

After some research I found this article in the Ubiquiti documentation that explains how to configure the hairpin NAT on an Edgerouter. Awesome. I went through it and I realized that this works as long as you have a static public IP, but if the IP of your internet connectivity change, then hairpin NAT is not going to work.

Then I remembered I found somewhere that you can run bash script as a scheduled job in the Edgerouter devices, so after poking around I was able to setup a bash script to update the public IP of the NAT rule in an automated fashion, but in order to be able to do this I had to use a dynamic DNS service, so that I can resolve the DNS record associated with my internet connectivity and compare it to the one that is configured.

So in the end I used a dynamic IP service, but just to keep track of my internet IP address. To access the service I use a domain I own, as planned.

Bash script

I leave here the script i wrote, in case someone may need it. It can also be found on my Github

#/bin/bash  
 
# Hostnames to look up 
hostname=example.ddns.net

# NAT rule to update
nat_rule="7"
 
# get current address configured in the hairpin NAT rule 
wan_addr=$(/opt/vyatta/bin/vyatta-op-cmd-wrapper show configuration commands | grep "set service nat rule $nat_rule destination address" | awk '{ print $8 }' )
 
# resolve the hostname
resolved_ip=$(getent hosts $hostname | awk '{ print $1 }')

# Update NAT rule if the IP changed 
if [[ $resolved_ip == $wan_addr ]] 
then 
 # IP did not change. Do nothing 
 : 
else 
 #if addresses have changed, update the NAT rule in place
 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 
 /opt/vyatta/sbin/vyatta-cfg-cmd-wrapper begin 
 /opt/vyatta/sbin/vyatta-cfg-cmd-wrapper set service nat rule "$nat_rule" destination address "$resolved_ip"  
 /opt/vyatta/sbin/vyatta-cfg-cmd-wrapper commit 
 /opt/vyatta/sbin/vyatta-cfg-cmd-wrapper save 
 /opt/vyatta/sbin/vyatta-cfg-cmd-wrapper end 
fi