USG VPN’s and Dynamic IP’s

Running a Unifi USG gateway does have its challenges every now and then. One of my friends asked me how I would solve the problem of dynamic IP addresses being used in a S2S VPN configuration. Now normally, when you look at the USG documentation, if the S2S is managed by a single USG controller (between two sites) this happens automatically, but if the S2S is not between two USG’s or the USG’s are managed by 2 different Cloud Keys (Controllers), you need to manually update the configuration after each IP change.

We thought of a better way of doing this using scripts and dynamic DNS. Now you can’t just use the hostnames in the VPN configuration as you need to put in the raw IP addresses in the configuration.What we ended up with was the following:

Create the IPSEC tunnel through the GUI (using the dynamic IP’s) as if the IP addresses where static. Creating the configuration through the GUI, creates the configuration on the device itself.

Next, SSH into the device and pick the following lines of the configuration:

configure
show vpn ipsec site-to-site

The result should look something like:

site-to-site {
peer 1.2.3.4 {
authentication {
mode pre-shared-secret
pre-shared-secret SecretPassword
}
connection-type initiate
ike-group IKE_1.2.3.4
local-address 6.7.8.9
vti {
bind vti64
esp-group ESP_1.2.3.4
}
}
}

(I’ve changed the external IP’s to 1.2.3.4 and 6.7.8.9)

If you have multiple internal IP ranges, you will see multiple tunnels defined in the configuration, but that’s not the point. Now using VSCode (or any other editor that supports LineFeed), create the following file:

#!/bin/vbash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
declare -A RemoteIPRaw
declare -A RemoteIP
declare -A MyIP
MyIP=$(curl http://wtfismyip.com/text)
MyFullIP=$(host hostA.sytes.net)
MyIP=$(echo $MyFullIP | grep -Pom 1 '[0-9.]{7,15}')
RemoteIPRaw=$(host hostB.sytes.net)
RemoteIP=$(echo $RemoteIPRaw | grep -Pom 1 '[0-9.]{7,15}')
WR="/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper"
#Validating the existing config, and comparing it to retrieved remote IP
$WR begin
Validate=$($WR show vpn ipsec site-to-site peer $RemoteIP)
if [[ $Validate == "empty" ]]; then
echo "New Remote IP found"
#retrieving old configured IP from config file and deleting existing peer
OldRemoteIP=$(< /config/currentVPN.config)
$WR delete vpn ipsec site-to-site peer $OldRemoteIP
echo "Deleted old configuration"
$WR commit
set new configuration
echo "Writing new configuration"

Now for each line that you copied from the initial configuration add them to the script, in the following manner, converting from JSON to CLI commands:

 $WR set vpn ipsec site-to-site peer $RemoteIP description "CUSTOM_BY_SCRIPT"
$WR set vpn ipsec site-to-site peer $RemoteIP authentication mode pre-shared-secret
$WR set vpn ipsec site-to-site peer $RemoteIP authentication pre-shared-secret SecretPassword
$WR set vpn ipsec site-to-site peer $RemoteIP connection-type initiate
$WR set vpn ipsec site-to-site peer $RemoteIP ike-group IKE_1.2.3.4
$WR set vpn ipsec site-to-site peer $RemoteIP local-address $MyIP
$WR set vpn ipsec site-to-site peer $RemoteIP vti bind vti64
$WR set vpn ipsec site-to-site peer $RemoteIP vti esp-group ESP_1.2.3.4
$WR commit  
$WR save
$WR end

(Important to keep in mind that the IKE_1.2.3.4/ESP_1.2.3.4 lines need to match your old (configured) IP address. These are objects in the configuration of the FW under vpn ipsec ike-group and vpn ipsec esp-group)

And finally, you need to close off the script with the following lines:

#setting new remote IP as the default - needed for cleanup upon new IP for peer later-on
echo $RemoteIP > /config/currentVPN.config
exit 0
else
echo "No Remote IP change detected"
fi
 
#Validating if local IP has changed, only need to change local-address in existing config
ValidateLocal=$($WR show vpn ipsec site-to-site peer $RemoteIP local-address)
MyOldIP=$(echo $ValidateLocal | grep -Pom 1 '[0-9.]{7,15}')
if [[ "$MyOldIP" != "$MyIP" ]] ; then
    echo "Local IP address change detected - updating local config"
    $WR set vpn ipsec site-to-site peer $RemoteIP local-address $MyIP 
    $WR commit  
    $WR save
    $WR end
else
echo "No Local IP change detected"
fi

make sure to convert the file to LF, save it and upload it to the device. Save it in the device under /config/scripts/reconfigvpn.sh

Create the scripts (change the hostA to hostB for the 2nd site) and upload them to the USG under /config/scripts and make them executable using chmod +x (you will probably need sudo su for permissions)

And create the initial /var/log/currentvpn.config by issuing

echo 1.2.3.4 > /config/currentVPN.config

where 1.2.3.4 is the remote IP

On the USG Controller, create the dynamic DNS configurations as per below on both sites:

This ensures that each gateway registers itself in the Dynamic DNS and that the hostnames can actually be found by the script. Last thing to do is to schedule the script to run every x minutes. In order to do this, a JSON file needs to be created on the USG cloud key. If you do not already have a custom JSON, please check: https://help.ubnt.com/hc/en-us/articles/215458888-UniFi-USG-Advanced-Configuration

 {
    "system": {
        "task-scheduler": {
            "task": {
                "runonprovision": {
                    "executable": {
                        "path": "/config/scripts/reconfigvpn.sh"
                    },
                    "interval": "40m"
                }
            }
        }
    }
}

And thats it!.. the code above ensurs the script is executed every 40 minutes .

When a new IP address is detected locally, it will rewrite the local-address line in the configuration, and if the peer IP address has changed (as detected per DynDNS) it will delete the old config and write a complete new config and re-establish the link..