Steven Davies
Posted on October 2, 2021
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:
- fetch a fresh list of IP’s from my internal DNS server.
- Update
UFW
. - 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.
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.
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
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.
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.
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.
*/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.
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 ❤
Posted on October 2, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.