Know-How: updown script for cascaded VPN connections with Linux

PP Stephan

Staff member
Hi,

The following is a script to use for cascaded VPN connections with Linux. Detailed instructions and examples inside. Just copy and paste the code below and save as updown.sh

Code:
#!/bin/bash
#
# This script can be used as an up and down script for OpenVPN.
# It adds and removes the routes required for a VPN connection cascaded over
# multiple vpn servers provided by Perfect Privacy.
#
# Usage:
#   Start OpenVPN with the following options:
#
#   openvpn
#       --config <config file>
#       --script-security 2
#       --route remote_host
#       --persist-tun
#       --up updown.sh
#       --down updown.sh
#       --route-noexec
#       --setenv hopid <hop number>
#       --setenv prevgw <gateway>
#
#       config file
#           The *.ovpn config file, ie. "London.ovpn".
#       hop number
#           The number of the hop in the cascading chain, starting with 1.
#           For the first hop, you can omit the hop number (default: 1).
#           It is limited to 5, to limit the amount of total routes.
#       gateway
#          The gateway (internal server IP) of the previous hop as provided
#          in the log of the previous hop.
#          For the first hop ('hop number' = 1), you can omit the gateway.
#          In that case, the gateway of your local network will be used.
#
#   TL;DR: Just start openvpn with all parameters listed above except the last
#   two (--setenv). You will find the command line for the next hop in the log,
#    you will just need to change <config.ovpn> for the configuration file of 
#    the server of your choice.
#
# Example:
#   Hop #1:
#     sudo openvpn --config London.ovpn --script-security 2 --route remote_host
#         --persist-tun --up updown.sh --down updown.sh --route-noexec
#
#   Hop #2 (copy-paste this command line from the log of the hop #1):
#     sudo openvpn --config Rotterdam.ovpn --script-security 2 --route remote_host
#         --persist-tun --up updown.sh --down updown.sh --route-noexec
#         --setenv hopid 2 --setenv prevgw 10.1.13.1
#
#   Hop #3 (copy-paste from hop #2):
#     sudo openvpn --config Reykjavik.ovpn --script-security 2 --route remote_host
#         --persist-tun --up updown.sh --down updown.sh --route-noexec
#         --setenv hopid 3 --setenv prevgw 10.18.12.1
#
# NOTE:
#    We recommend using a dedicated terminal window for each hop. That way the log
#    outputs for each hop can be easily kept apart. It also guarantees that the 
#    routes can be added and removed again in the right order.
#
# Advanced Options:
#   --setenv disable_resolvconf 1
#      By default, this script executes '/etc/openvpn/update-resolv-conf'
#      to update your DNS settings. You can disable this behaviour by
#      setting the environment variable 'disable_resolvconf'.
#
# Support:
#   Perfect Privacy <support@perfect-privacy.com>
#
# Maintainer:
#   Simon Lange (Perfect Privacy)
#
# Changelog:
#   1.0
#     * initial version
#   1.1
#     * nice log output and hint for the next connection
#     * you can omit the --setenv parameter for the first hop
#   1.2
#     * backwards compatible to openvpn version 2.2
#       (uses --up and --down instead of --route-up and --route-pre-down)
#     * two environment variables for hopid and prevgw instead of one
#     * use iproute2 (ip route add) instead of net-tools suite (route add)
#   1.3
#     * added /etc/openvpn/update-resolv-conf (can be disabled by
#       --setenv disable_resolvconf 1)


# remember script name
script_name=$0

# maximum number of hops
# be careful: number of additional routes per hop: 2^hopid + 1
MAX_HOPID=5


# print $1 with prefix to STDOUT
function updown_print {
    echo "updown.sh: $1"
}


# print $1 with prefix to STDERR
function updown_print_error {
    echo "updown.sh: ERROR: $1">&2
}


# check whether $1 is an IPv4 address (exit on error)
# $1: the IP address to check
# $2: the environment variable name for error message
function check_ipv4_regex {
    regex_255="([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])" # 0-255
    regex_ipv4="^($regex_255.){3}$regex_255$"
    if ! [[ $1 =~ $regex_ipv4 ]]
    then
        updown_print_error "$2 ('$1') is not an IPv4 address"
        updown_print "ABORT"
        exit 1
    fi
}


updown_print "STARTED"
updown_print "hop number:              $hopid (default: 1)"
updown_print "gateway of previous hop: $prevgw (default: local gateway)"
updown_print "local gateway:           $route_net_gateway"
updown_print "VPN: int. IP address:    $ifconfig_local"
updown_print "VPN: netmask:            $ifconfig_netmask"
updown_print "VPN: gateway:            $route_vpn_gateway"
updown_print "VPN: public IP address:  $route_network_1"

# if hopid is not set, assume it to be 1
if [[ ${hopid} == "" ]]
then
    updown_print "Notice: You didn't set 'hopid'. Assuming this to be the first hop (hopid=1)."
    hopid=1
fi

# check whether environment variable hopid is a number
regex_number="^[0-9]+$"
if ! [[ ${hopid} =~ $regex_number ]]
then
    updown_print_error "hopid ('$hopid') is not a number!"
    updown_print_error "See updown.sh for the usage of this script."
    updown_print "ABORT"
    exit 1
fi

# check whether hopid <= MAX_HOPID
if [[ ${hopid} -gt ${MAX_HOPID} ]]
then
    updown_print_error "You shouldn't use more than $MAX_HOPID hops. Otherwise it will result in a massive amount of routes."
    updown_print "ABORT"
    exit 1
fi

# check whether all environment variables needed are IPv4 addresses
check_ipv4_regex ${route_vpn_gateway} "route_vpn_gateway"
check_ipv4_regex ${route_network_1} "route_network_1"
vpn_server_ip=${route_network_1}

# make sure we have a valid gateway
# (prevgw is the route_vpn_gateway from the previous hop)
if [[ ${prevgw} == "" ]]
then
    if [[ ${hopid} -eq 1 ]]
    then
        # for the first hop, use the local gateway
        updown_print "Notice: You didn't set the previous gateway. The gateway of your local network ('$route_net_gateway') will be used."
        prevgw=${route_net_gateway}
    else
        updown_print_error "You didn't set the previous gateway."
        updown_print_error "See updown.sh for the usage of this script."
    fi
fi
check_ipv4_regex ${prevgw} "prevgw"

# determine whether to add or del our routes
if [[ "$script_type" == "up" ]]
then
    add_del="add"
elif [[ "$script_type" == "down" ]]
then
    add_del="delete"
else
    updown_print_error "script_type is not 'up' or 'down'!"
    updown_print_error "See updown.sh for the usage of this script."
    updown_print "ABORT"
    exit 1
fi

# add route to (next) vpn server via previous gateway
IP=$(which ip)
route_cmd="$IP route $add_del $vpn_server_ip via $prevgw"
updown_print "executing: '$route_cmd'"
eval ${route_cmd}

# calculate and execute routes
for (( i=0; i < $((2 ** $hopid)); i++ ))
do
    net="$(( $i << $((8 - $hopid)) )).0.0.0"
    route_cmd="$IP route $add_del $net/$hopid via $route_vpn_gateway"
    updown_print "executing: '$route_cmd'"
    eval ${route_cmd}
done

# print hint for the next connection (on up)
if [[ "$script_type" == "up" ]]
then
    if [[ ${hopid} -le ${MAX_HOPID} ]]
    then
        next_hop_number=$((hopid+1))
        next_gateway=${route_vpn_gateway}
        updown_print "HINT: For the next hop, start openvpn with the following options:"
        updown_print "HINT: openvpn --config <config.ovpn> --script-security 2 --route remote_host --persist-tun --up $script_name --down $script_name --route-noexec --setenv hopid $next_hop_number --setenv prevgw $next_gateway"
    else
        updown_print "Notice: Maximum numbers of hops reached. Don't start another connection."
    fi
fi

# update DNS settings
if ! [ "$disable_resolvconf" ]
then
    resolvconf_cmd="/etc/openvpn/update-resolv-conf"
    updown_print "execuding: '$resolvconf_cmd'"
    eval ${resolvconf_cmd}
fi

updown_print "FINISHED"
 
Last edited:
Top