Auto OpenVPN reconnect and killswitch for VPNs with dynamic IPs
Tags: openvpn, vpn, security, linux
Published: 2021-10-02 22:22:32

### The Problem

If you’re like me and run a home Linux server, you may wish to add some extra security in the form of a VPN connection to ensure traffic is encrypted. In my case, I use a Raspberry Pi to backup things like RAW images from my camera to an off-site location. The extra security gives me a little peace of mind, especially as the server generally uses a cellular connection.

I’ve been using `UFW` to work as a killswitch and only allow traffic via the VPN connection, but one issue I've had is with my trusted VPN provider is that they regularly rotate IP addresses. `UFW` uses `iptables` under the hood, meaning it can only be configured to use IP addresses explicitly and not a domain name. This has left my backup machine without an internet connection fairly frequently.

### The Solution

Just to set this straight from the start, I’m no UNIX genius. I spend most of my time working with Windows machines and use Linux more as a hobbyist. That been said, I’ve written my fair share of code/scripts.

My solution is pretty simple; when disconnects happen:

1.  fetch a fresh list of IP’s from my internal DNS server.
2.  Update `UFW` .
3.  Update the `OpenVPN`connection file and reconnect.

I thought I’d share my approach, as I failed finding any other solution online so this may be of help to others.

The first part is fetching the list of domains. To do this, I use [dig](https://downloads.isc.org/isc/bind9/cur/9.17/doc/arm/html/manpages.html#dig-dns-lookup-utility).

```bash
ipoutput=$(dig $1 +short)

if [ -z “$ipoutput” ]; then  
  echo “No ips found for $1”  
  exit 1  
fi

echo “Found ips $ipoutput”

readarray -t ips <<<”$ipoutput”
```

This gives me a nice list of IP address for the specified domain.

Now I know the correct ip addresses, I need to make use of them. The first job is to update `UFW`. I decided to use a template file so I can set it up as I want for each machine. This may not be the most elegant solution, but its pretty flexible.

```bash
ufwoutput=’’

for i in “${ips[@]}”  
do  
  :  
  ufwoutput+=$”### tuple ### allow udp any $i any 0.0.0.0\\/0 out\\n”  
  ufwoutput+=$”-A ufw-user-output -p udp -d $i -j ACCEPT\\n\\n”  
done

if test -f “$dir_path/user.rules.tmp”; then  
  echo “Deleting tmp file…”  
  rm “$dir_path/user.rules.tmp”  
fi

cp “$dir_path/user.rules.template” “$dir_path/user.rules.tmp”

sed -i -e “s/\[content\]/${ufwoutput}/g” “$dir_path/user.rules.tmp” “$dir_path/user.rules.tmp”

mv “$dir_path/user.rules.tmp” /etc/ufw/user.rules

echo “Reloading ufw”  
ufw reload
```

The script builds up a string containing the correct formatted lines for the `user.rules` file for `UFW`, creates a new copy of the template, replaces `[content]` in the file using `sed` then moves the file into the correct place (for ubuntu). The final thing to do is `ufw reload` to reload the `user.rules` file.

That’s `UFW` taken care of, time for `OpenVPN` using a similar method

```bash
openvpnoutput=’’

for i in “${ips[@]}”  
do  
:  
  openvpnoutput+=$”remote $i 1198\\n”  
done

if test -f “$dir_path/openvpn.ovpn.tmp”; then  
  echo “Deleting tmp file…”  
  rm “$dir_path/openvpn.ovpn.tmp”  
fi

cp “$dir_path/openvpn.ovpn.template” “$dir_path/openvpn.ovpn.tmp”

sed -i -e “s/\[content\]/${openvpnoutput}/g” “$dir_path/openvpn.ovpn.tmp” “$dir_path/openvpn.ovpn.tmp”

mv “$dir_path/openvpn.ovpn.tmp” “openvpn.ovpn”
```

This works in a similar way to the `UFW` code, replacing a `[content]` placeholder in the `.opvn` template file.

So that’s all the code to update `UFW` and `OpenVPN`. The next thing to do is to manage the connection. I do this via a simple script called from a `cron` job.

```bash
function getStatus () {  
  echo “Attempting to get device status…”  
  ip address show | grep $1 && return 1  
  return 0  
}
```

This is a pretty straightforward function. `grep` the output from `ip address show` to see if the `OpenVPN` connection is live. Return either `1`for success or `0`for failure.

The last thing to do is to check the result and connect if required.

```bash
getStatus $device  
if [[ $? == 0 ]]; then  
  echo “openvpn is not connected!”  
  echo “Reconnecting!”

  #Update config  
  updateIps $domain &> $dir_path/renew.out &disown

  openvpn — config “$dir_path/openvpn.ovpn” &>$dir_path/out.out &disown  
else  
  echo “openvpn is connected!”  
fi
```

If the output from `getStatus` is `0`, then it will call to update the IP addresses and trigger a new `OpenVPN` connection with `&disown` to not wait for the connection to exit.

Now that’s all in place, all that’s needed to run the script automatically is a `cron` job to check as frequently as you wish.

```bash
*/1 * * * * cd /home/account/openvpn && ./auto-connect.sh “my.vpn.domain” “tun0” > out.out
```

If this is useful to you, rather than having to copy & paste the code I’ve wrapped all this up into a [single script available on GitHub](https://github.com/nullabletype/openvpntools).

### Wrap Up

As I say, I’m no Linux sysadmin but this script solves a problem for me and has been working well for the last few months. If anyone has any suggestions for improvements or comments I’d love to hear them. Even better, [fork the repo on GitHub](https://github.com/nullabletype/openvpntools) and pop in a merge request ❤