OpenVPN & Network Manager: selecting a random VPN target each time you start the Virtual Private Network (UNIX/Linux) SOLVED

I sometimes perform some IT work for a nonprofit organization. They use OpenVPN for their network but since they reside in different locations, they have multiple OpenVPN servers set up rather than just one point of entry. The problem I’ve noticed is that at times one or another will be slower. While I don’t have a mechanism to identify which is faster, I can roll the dice and have my vpn start script pick a random server instead of me having to randomly pick one myself.

#!/bin/bash

# If the network card is unavailable, we're not going to bring up the vpn
REQUIRED_CONNECTION_NAME="enp0s8"

# VPN_LIST is just a simple array
declare -a VPN_LIST

# BASH arrays start with index 0
i=0

# read the vpn list into an array
while read TMP_VPN; do
    VPN_LIST[$i]="$TMP_VPN"
    ((i++))
done < vpns.txt 

# if the vpns.txt is NOT empty
if (( i >= 0 )); then
    # Choose a random VPN index from the TMP_VPN array
    if (( i > 0 )); then
        ((i - 1))
        ((RANDOM_VPN = $RANDOM % $i))
    else
        RANDOM_VPN=$i
    fi

    # We set the VPN_CONNECTION_NAME to the VPN we chose
    VPN_CONNECTION_NAME=${VPN_LIST[$RANDOM_VPN]}

    DEFAULT_CONNECTION=$( nmcli con show --active |grep "${REQUIRED_CONNECTION_NAME}" )
    VPN_CONNECTION=$( nmcli con show --active | grep "${VPN_CONNECTION_NAME}" )

    # Make sure that the vpn connection isn't already up
    if [[ "${DEFAULT_CONNECTION}" != "${VPN_CONNECTION}" ]]; then
        echo -n "Connecting to ${VPN_CONNECTION_NAME} ... "

        # The credentials are stored in my Gnome keyring so I run the nmcli command as jason
        su - jason -c "nmcli con up id \"${VPN_CONNECTION_NAME}\""

        RC=$?

        if (( RC == 0 )); then
            echo "SUCCESS"
        else
            echo "FAILED"
        fi
    else
        echo "configuration mismatch"
        RC=1
    fi
fi

exit $RC

The file vpns.txt is simply a text file with the names of the VPNs as they are listed in OpenVPN (see /etc/NetworkManager/system-connections for the list of defined VPNs). One VPN per line.

vpn-east.example.org
vpn-west.example.org
vpn-europe.example.org
vpn-tokyo.example.org
Share Button

Video: Korn Shell A Little About Arrays/Lists

Video by bjamesm70

Share Button

Intro To Korn Shell – Lesson 7g – Pattern Matching *( )


Video by bjamesm70

Share Button

HOWTO: Korn Shell / BASH: How to determine if a string is numeric or not

Occasionally we run across something that should be simple. Checking whether a string is a number or not for example.

special_CHAR ‘(‘ pattern ‘)’

The special_CHAR is a prefix that changes the number of characters expected:

‘*’ for zero or more matches
‘+’ at least one match
‘@’ for exactly one match
‘?’ for zero or one matches
‘!’ for negation

The following will match one or more digits:

+([[:digit:]])

See POSIX Character Classes regarding the use of [:digit:]

Below is an example function to determine if a string is numeric written in Korn Shell 93 but will also work in BASH.

The criteria we’re using is based on the US standard of numbers:
1
12.345
and so on

Obviously we could expand this out to handle commas or other separators without too much difficulty.

#!/bin/ksh93

function is_numeric {
    typeset TMP_STR="$1"
    typeset -i TMP_IS_NUMERIC

    if [[ "$TMP_STR" == +([[:digit:]])?(.*([[:digit:]])) ]]; then
        echo "'$TMP_STR' is numeric"
        TMP_IS_NUMERIC=1
    else
        echo "'$TMP_STR' is not numeric"
        TMP_IS_NUMERIC=0
    fi
}

for TMP_STRING in "TEST_VAR" "22" "TRUE1" "TRUE3TRUE" "12.345" "7.8.9.0"; do
    is_numeric "$TMP_STRING"
done

OUTPUT:

$ ./test_regex.ksh
'TEST_VAR' is not numeric
'22' is numeric
'TRUE1' is not numeric
'TRUE3TRUE' is not numeric
'12.345' is numeric
'7.8.9.0' is not numeric

See Finnbarr P. Murphy’s blog for more examples of using regular expressions in Korn Shell 93

Share Button

Korn Shell 93: A better if structure with many tests

Writing korn shell scripts you will often come across if structures that look something like the following. It works well but the if structure doesn’t lend itself for quick reading.

if [[ MYVAR != "potato" ]] && [[ MYVAR != "acorn" ]] && [[ MYVAR != "pizza" ]] && [[ MYVAR != "apple" ]]; then

We can make it far more readable without losing the functionality:

if [[ MYVAR != @(potato|acorn|pizza|apple) ]]; then

The “@(potato|acorn|pizza|apple)” is effectively a short cut to a case structure. So, let’s expand that to the full case statement:

case ${MYVAR} in
    !potato|acorn|pizza|apple)
        # commands go here
        ;;
esac
Share Button