Auto OpenVPN reconnect and killswitch for VPNs with dynamic IPs

nullabletype

Steven Davies

Posted on October 2, 2021

Auto OpenVPN reconnect and killswitch for VPNs with dynamic IPs

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 OpenVPNconnection 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.

ipoutput=$(dig $1 +short)

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

echo “Found ips $ipoutput”

readarray -t ips <<<$ipoutput
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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

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”
Enter fullscreen mode Exit fullscreen mode

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.

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

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

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

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
Enter fullscreen mode Exit fullscreen mode

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.

*/1 * * * * cd /home/account/openvpn && ./auto-connect.sh “my.vpn.domain” “tun0” > out.out
Enter fullscreen mode Exit fullscreen mode

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.

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 and pop in a merge request ❤

💖 💪 🙅 🚩
nullabletype
Steven Davies

Posted on October 2, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related