Finally getting VPN to work again on Ubuntu
A journey in BASH
By Zhian N. Kamvar in example
January 6, 2024
For various reasons, I’ve been using a VPN for the last 6 years. The one I’ve been using is VyprVPN, but there are several others out there that do the job. There was an app for Windows, Mac, iPhone, Android, and even a CLI utility for ubuntu, and all of them appeared to work pretty well. Some time in the last year or so, I was finding that Google knew my geolocation when I would use the VPN on my Ubuntu machine, but not on any of the other devices, which meant that my IP address was leaking.
It turns out that my internet provider started using IPv6 and that most VPN services do not support IPv6. The best they can do is to disable IPv6, which is what the apps on my other devices do. However, for some reason, the CLI utility that VyprVPN provided was not doing this, so I needed to do three things:
- Set up OpenVPN for Ubuntu
- Download the OpenVPN configuration files for the VyprVPN servers
- Write a script that would disable IPv6 before turning on VPN.
I also want to check that my ipv6 is actually disabled by using https://test-ipv6.com/ or https://ipleak.net/.
This post shows not only the solution, but also demonstrates BASH concepts of arrays, control flow, functions, and process substitution.
Simple Solution
The bare bones solution would be to implement two functions that
toggle ipv6 and
connect to VPN via the command line via
nmcli
vpn_on
: disables ipv6 usingsysctl
and then turns on the VPN vianmcli con up id <VPN ID>
vpn_off
checks for an active VPN connection withnmcli con show --active
, re-enables ipv6, and then turns off the VPN (if needed) vianmcli con down id <Active VPN ID>
THis implementation looks like this and would default to a VPN ID called “Seattle”:
vpn_on function () {
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1 \
&& sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1 \
&& sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=1
nmcli con up id "${1:-Seattle}"
return 0
}
vpn_off function () {
local active
active=$(nmcli -f NAME,TYPE con show --active \
| grep 'vpn\s*$' \
| awk -F' ' '{print $1}'
)
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=0 \
&& sudo sysctl -w net.ipv6.conf.default.disable_ipv6=0 \
&& sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=0
if [ -z "${active}" ]
then
return 0
else
nmcli con down id "${active}"
return 0
fi
}
Full Solution
Of course, the simple solution above requires me to know what VPN networks I can
connect to and it requires me to spell the connections correctly, so I needed
to write a few more functions. You can find what I came up with in
my .bash_funs.sh
configuration file.
Getting a List of VPN networks available.
Getting a list of vpn networks is as straightforward as filtering the vpn
type
from the connection list from
nmcli
:
vpn_list () {
nmcli -f NAME,TYPE con show \
| grep 'vpn\s*$' \
| awk -F ' ' '{print $1}'
}
Note that some VPN network names will have spaces in the titles (such as South Korea), so that’s why I need to set two spaces for awk
.
This allows me to get a column of network names:
$ vpn_list | awk -F ' ' '{print NR") "$1}'
1) Seattle
2) Chicago
3) LA
4) South Korea
Choosing a VPN network from a list interactively
Of course, the next challenge comes with the spelling aspect. I am lazy. I don’t
want to have to type something multiple times, especially if I have to put it in
quotes if I don’t have to. If I could choose from a list, that would make me so
happy. Luckily, I found out about
the BASH select
construct, which would allow me to
select a specific item from an array:
local connections
connections=$( vpn_list )
select vpn in "${connections[@]}"
do
echo ${vpn}
return 0
done
However, it turns out that if there are any spaces in the array elements, BASH will interpret those words as separate elements:
1) Seattle
2) Chicago
3) LA
4) South
5) Korea
Luckily, there is the
readarray
BASH builtin that helps with this. If we
read in the newline-separated array from
a process
substitution,
then we can be sure that it will be read in as an array with each element
correctly placed:
local connections
readarray -t connections < <( vpn_list )
select vpn in "${connections[@]}"
do
echo ${vpn}
return 0
done
1) Seattle
2) Chicago
3) LA
4) South Korea
Manually supplying a VPN network name
There are times when I know the name of the VPN network and I don’t want to select from a list. In these cases, I know that I’m a terrible speller, so I want to make sure that the VPN ID matches what is on my computer. It turns out this is a not an uncommon question and I lifted the answer directly from Stack Overflow:
# https://stackoverflow.com/a/8574392
#
# Returns success if the element exists in an array, a failure otherwise
#
# containsElement <element> <array>
function containsElement () {
set +e
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}
This then allows me to then construct a vpn_choose
function that will
optionally take a declared VPN network of choice. If the VPN network exists,
then the function exits early, and if not, I am provided with a list of options.
function vpn_choose () {
local connections
local declared="${1:-none}"
# https://www.baeldung.com/linux/reading-output-into-array
readarray -t connections < <( vpn_list )
if containsElement "${declared}" "${connections[@]}"
then
echo "${declared}"
return 0
fi
# https://linuxize.com/post/bash-select/
select con in "${connections[@]}":
do
echo "${con}"
return 0
done
}
And this is all reflected in
the vpn
function I wrote. It
has been something I’ve been meaning to implement for a while, but haven’t
really had the time to do so until now. I’m going to keep writing these little
blog posts as I go along so that I can get better at writing and find my voice.
If you’ve followed along this far, thank you!
- Posted on:
- January 6, 2024
- Length:
- 5 minute read, 1006 words
- Categories:
- example
- See Also:
- Installing the ABySS