Improve VPN performance on ODROID N2

I like to secure my home network with a VPN, so I have used single board computers for years for that. I started with the Raspberry PI 1, upgraded to BananaPi 1, then upgraded to an ODROID C2, tried out the new Raspberry PI 3, before ending up with my current ODROID N2. I upgraded frequently, as running encryption on these small computers maxes out their CPU capability with ease, and OpenVPN only uses 1 CPU Core, so having a multi-core CPU does not help a lot in this use case. Also, even though my ODROID N2 has hardware accelerated AES encryption, unfortunately, the SHA512 hash algorithm used by my VPN provider is not hardware accelerated. In addition to the VPN, I use the ODROID as PiHole to filter ads and trackers on a DNS network level and also as a network backup server that emulates an Apple Time Capsule.

In this post I talk about how I was able to get better VPN performance.

It has happened several times to me that openvpn was slow, and not delivering more then 40 MBit/s on the connection. Instead I saw a rather high cpu/interrupt load for ksoftirqd/0. After a lot of searching, I figured out that it was using one of the 2 “little” ARM A53 cores instead of the faster ARM A73 cores. On the ODROID N2 the little cores are cores 0-1, the big cores are 2-5 (for some details on the processor architecture, see https://www.hardkernel.com/blog-2/odroid-n2/).

The solution? Setting the CPU affinity, meaning the preference where the openvpn process runs, to the faster CPU cores by changing the startup script (/etc/init.d/openvpn) to startup the openvpn process using taskset:

DAEMON="taskset --cpu-list 2-5 /usr/sbin/openvpn"

Depending on your system, you may also want to change the Systemd service to execute the openvpn process with taskset, too. You can prepend the taskset command from above in the service definition file in:

/lib/systemd/system/[email protected]

and then run:

systemctl daemon-reload
systemctl restart openvpn

After a short speedtest, I now reach more than 300 MBit/s with OpenVPN running with AES-256-GCM and SHA-512 encryption.

6 Replies to “Improve VPN performance on ODROID N2”

  1. Hi, I am having troubles to setup a shared vpn connection on the odroid n2. I have a surfshark vpn client installed but then sharing seems not be possible. Also surfshark helpdesk answered that a shared vpn client on linux is not possible.
    I doubt, should be possible, and hopefully also with use of the fast core. Maybe you have some suggestions how to setup?

    Would be great 😊

  2. Thanks for your fast support, really appreciated.
    I’ll work on this tomorrow and let you know the result.

  3. Hi, I got the openvpn client running and shared!
    Above processor allocation proces is as I understand when a vpn server is used.
    Would this also be an improvement when only using openvpn client?
    If yes, where to put the commands? I could not find a config file for the client (I setup the client via the GUI of mate 20.04, import ovpn file).

    1. Hi, yes, the openvpn client will profit – you will be able to download faster (around 400MBit/s). Have you tried using the locations I mentioned in the blog post? Unfortunately, I have never used the UI from Ubuntu Mate, so I don’t know, how it configures the client.

  4. Ok, that is good news!
    I installed surfshark client via de GUI.
    The strange thing is that there is nothing related to the surfshark client in the /etc/init.d/openvpn config file.
    So that probably is defined somewhere else.

    The /lib/systemd/system/[email protected] file is present
    but also a [email protected] and a openvpn.service.

    I copied the text of all files below. Hopefully you are able to see if these are the right ones and where to put the lines in.

    ——————————————-
    /etc/init.d/openvpn

    #!/bin/sh -e

    ### BEGIN INIT INFO
    # Provides: openvpn
    # Required-Start: $network $remote_fs $syslog
    # Required-Stop: $network $remote_fs $syslog
    # Should-Start: network-manager
    # Should-Stop: network-manager
    # X-Start-Before: $x-display-manager gdm kdm xdm wdm ldm sdm nodm
    # X-Interactive: true
    # Default-Start: 2 3 4 5
    # Default-Stop: 0 1 6
    # Short-Description: Openvpn VPN service
    # Description: This script will start OpenVPN tunnels as specified
    # in /etc/default/openvpn and /etc/openvpn/*.conf
    ### END INIT INFO

    # Original version by Robert Leslie
    # , edited by iwj and cs
    # Modified for openvpn by Alberto Gonzalez Iniesta
    # Modified for restarting / starting / stopping single tunnels by Richard Mueller

    . /lib/lsb/init-functions

    test $DEBIAN_SCRIPT_DEBUG && set -v -x

    DAEMON=/usr/sbin/openvpn
    DESC=”virtual private network daemon”
    CONFIG_DIR=/etc/openvpn
    test -x $DAEMON || exit 0
    test -d $CONFIG_DIR || exit 0

    # Source defaults file; edit that file to configure this script.
    AUTOSTART=”all”
    STATUSREFRESH=10
    OMIT_SENDSIGS=0
    if test -e /etc/default/openvpn ; then
    . /etc/default/openvpn
    fi

    start_vpn () {
    if grep -q ‘^[ ]*daemon’ $CONFIG_DIR/$NAME.conf ; then
    # daemon already given in config file
    DAEMONARG=
    else
    # need to daemonize
    DAEMONARG=”–daemon ovpn-$NAME”
    fi

    if grep -q ‘^[ ]*status ‘ $CONFIG_DIR/$NAME.conf ; then
    # status file already given in config file
    STATUSARG=””
    elif test $STATUSREFRESH -eq 0 ; then
    # default status file disabled in /etc/default/openvpn
    STATUSARG=””
    else
    # prepare default status file
    STATUSARG=”–status /run/openvpn/$NAME.status $STATUSREFRESH”
    fi

    # tun using the “subnet” topology confuses the routing code that wrongly
    # emits ICMP redirects for client to client communications
    SAVED_DEFAULT_SEND_REDIRECTS=0
    if grep -q ‘^[[:space:]]*dev[[:space:]]*tun’ $CONFIG_DIR/$NAME.conf && \
    grep -q ‘^[[:space:]]*topology[[:space:]]*subnet’ $CONFIG_DIR/$NAME.conf ; then
    # When using “client-to-client”, OpenVPN routes the traffic itself without
    # involving the TUN/TAP interface so no ICMP redirects are sent
    if ! grep -q ‘^[[:space:]]*client-to-client’ $CONFIG_DIR/$NAME.conf ; then
    sysctl -w net.ipv4.conf.all.send_redirects=0 > /dev/null

    # Save the default value for send_redirects before disabling it
    # to make sure the tun device is created with send_redirects disabled
    SAVED_DEFAULT_SEND_REDIRECTS=$(sysctl -n net.ipv4.conf.default.send_redirects)

    if [ “$SAVED_DEFAULT_SEND_REDIRECTS” -ne 0 ]; then
    sysctl -w net.ipv4.conf.default.send_redirects=0 > /dev/null
    fi
    fi
    fi

    log_progress_msg “$NAME”
    STATUS=0

    start-stop-daemon –start –quiet –oknodo \
    –pidfile /run/openvpn/$NAME.pid \
    –exec $DAEMON — $OPTARGS –writepid /run/openvpn/$NAME.pid \
    $DAEMONARG $STATUSARG –cd $CONFIG_DIR \
    –config $CONFIG_DIR/$NAME.conf || STATUS=1

    [ “$OMIT_SENDSIGS” -ne 1 ] || ln -s /run/openvpn/$NAME.pid /run/sendsigs.omit.d/openvpn.$NAME.pid

    # Set the back the original default value of send_redirects if it was changed
    if [ “$SAVED_DEFAULT_SEND_REDIRECTS” -ne 0 ]; then
    sysctl -w net.ipv4.conf.default.send_redirects=$SAVED_DEFAULT_SEND_REDIRECTS > /dev/null
    fi
    }
    stop_vpn () {
    start-stop-daemon –stop –quiet –oknodo \
    –pidfile $PIDFILE –exec $DAEMON –retry 10
    if [ “$?” -eq 0 ]; then
    rm -f $PIDFILE
    [ “$OMIT_SENDSIGS” -ne 1 ] || rm -f /run/sendsigs.omit.d/openvpn.$NAME.pid
    rm -f /run/openvpn/$NAME.status 2> /dev/null
    fi
    }

    case “$1” in
    start)
    log_daemon_msg “Starting $DESC”

    # first create /run directory so it’s present even
    # when no VPN are autostarted by this script, but later
    # by systemd [email protected]
    mkdir -p /run/openvpn
    # autostart VPNs
    if test -z “$2” ; then
    # check if automatic startup is disabled by AUTOSTART=none
    if test “x$AUTOSTART” = “xnone” -o -z “$AUTOSTART” ; then
    log_warning_msg ” Autostart disabled.”
    exit 0
    fi
    if test -z “$AUTOSTART” -o “x$AUTOSTART” = “xall” ; then
    # all VPNs shall be started automatically
    for CONFIG in `cd $CONFIG_DIR; ls *.conf 2> /dev/null`; do
    NAME=${CONFIG%%.conf}
    start_vpn
    done
    else
    # start only specified VPNs
    for NAME in $AUTOSTART ; do
    if test -e $CONFIG_DIR/$NAME.conf ; then
    start_vpn
    else
    log_failure_msg “No such VPN: $NAME”
    STATUS=1
    fi
    done
    fi
    #start VPNs from command line
    else
    while shift ; do
    [ -z “$1″ ] && break
    if test -e $CONFIG_DIR/$1.conf ; then
    NAME=$1
    start_vpn
    else
    log_failure_msg ” No such VPN: $1″
    STATUS=1
    fi
    done
    fi
    log_end_msg ${STATUS:-0}

    ;;
    stop)
    log_daemon_msg “Stopping $DESC”

    if test -z “$2” ; then
    for PIDFILE in `ls /run/openvpn/*.pid 2> /dev/null`; do
    NAME=`echo $PIDFILE | cut -c14-`
    NAME=${NAME%%.pid}
    stop_vpn
    log_progress_msg “$NAME”
    done
    else
    while shift ; do
    [ -z “$1” ] && break
    if test -e /run/openvpn/$1.pid ; then
    PIDFILE=`ls /run/openvpn/$1.pid 2> /dev/null`
    NAME=`echo $PIDFILE | cut -c14-`
    NAME=${NAME%%.pid}
    stop_vpn
    log_progress_msg “$NAME”
    else
    log_failure_msg ” (failure: No such VPN is running: $1)”
    fi
    done
    fi
    log_end_msg 0
    ;;
    # Only ‘reload’ running VPNs. New ones will only start with ‘start’ or ‘restart’.
    reload|force-reload)
    log_daemon_msg “Reloading $DESC”
    for PIDFILE in `ls /run/openvpn/*.pid 2> /dev/null`; do
    NAME=`echo $PIDFILE | cut -c14-`
    NAME=${NAME%%.pid}
    # If openvpn if running under a different user than root we’ll need to restart
    if egrep ‘^[[:blank:]]*user[[:blank:]]’ $CONFIG_DIR/$NAME.conf > /dev/null 2>&1 ; then
    stop_vpn
    start_vpn
    log_progress_msg “(restarted)”
    else
    kill -HUP `cat $PIDFILE` || true
    log_progress_msg “$NAME”
    fi
    done
    log_end_msg 0
    ;;

    # Only ‘soft-restart’ running VPNs. New ones will only start with ‘start’ or ‘restart’.
    soft-restart)
    log_daemon_msg “$DESC sending SIGUSR1”
    for PIDFILE in `ls /run/openvpn/*.pid 2> /dev/null`; do
    NAME=`echo $PIDFILE | cut -c14-`
    NAME=${NAME%%.pid}
    kill -USR1 `cat $PIDFILE` || true
    log_progress_msg “$NAME”
    done
    log_end_msg 0
    ;;

    restart)
    shift
    $0 stop ${@}
    $0 start ${@}
    ;;
    cond-restart)
    log_daemon_msg “Restarting $DESC.”
    for PIDFILE in `ls /run/openvpn/*.pid 2> /dev/null`; do
    NAME=`echo $PIDFILE | cut -c14-`
    NAME=${NAME%%.pid}
    stop_vpn
    start_vpn
    done
    log_end_msg 0
    ;;
    status)
    GLOBAL_STATUS=0
    if test -z “$2” ; then
    # We want status for all defined VPNs.
    # Returns success if all autostarted VPNs are defined and running
    if test “x$AUTOSTART” = “xnone” ; then
    # Consider it a failure if AUTOSTART=none
    log_warning_msg “No VPN autostarted”
    GLOBAL_STATUS=1
    else
    if ! test -z “$AUTOSTART” -o “x$AUTOSTART” = “xall” ; then
    # Consider it a failure if one of the autostarted VPN is not defined
    for VPN in $AUTOSTART ; do
    if ! test -f $CONFIG_DIR/$VPN.conf ; then
    log_warning_msg “VPN ‘$VPN’ is in AUTOSTART but is not defined”
    GLOBAL_STATUS=1
    fi
    done
    fi
    fi
    for CONFIG in `cd $CONFIG_DIR; ls *.conf 2> /dev/null`; do
    NAME=${CONFIG%%.conf}
    # Is it an autostarted VPN ?
    if test -z “$AUTOSTART” -o “x$AUTOSTART” = “xall” ; then
    AUTOVPN=1
    else
    if test “x$AUTOSTART” = “xnone” ; then
    AUTOVPN=0
    else
    AUTOVPN=0
    for VPN in $AUTOSTART; do
    if test “x$VPN” = “x$NAME” ; then
    AUTOVPN=1
    fi
    done
    fi
    fi
    if test “x$AUTOVPN” = “x1” ; then
    # If it is autostarted, then it contributes to global status
    status_of_proc -p /run/openvpn/${NAME}.pid openvpn “VPN ‘${NAME}'” || GLOBAL_STATUS=1
    else
    status_of_proc -p /run/openvpn/${NAME}.pid openvpn “VPN ‘${NAME}’ (non autostarted)” || true
    fi
    done
    else
    # We just want status for specified VPNs.
    # Returns success if all specified VPNs are defined and running
    while shift ; do
    [ -z “$1” ] && break
    NAME=$1
    if test -e $CONFIG_DIR/$NAME.conf ; then
    # Config exists
    status_of_proc -p /run/openvpn/${NAME}.pid openvpn “VPN ‘${NAME}'” || GLOBAL_STATUS=1
    else
    # Config does not exist
    log_warning_msg “VPN ‘$NAME’: missing $CONFIG_DIR/$NAME.conf file !”
    GLOBAL_STATUS=1
    fi
    done
    fi
    exit $GLOBAL_STATUS
    ;;
    *)
    echo “Usage: $0 {start|stop|reload|restart|force-reload|cond-restart|soft-restart|status}” >&2
    exit 1
    ;;
    esac

    exit 0

    # vim:set ai sts=2 sw=2 tw=0:

    —————————–
    /lib/systemd/system/openvpn.service

    # This service is actually a systemd target,
    # but we are using a service since targets cannot be reloaded.

    [Unit]
    Description=OpenVPN service
    After=network.target

    [Service]
    Type=oneshot
    RemainAfterExit=yes
    ExecStart=/bin/true
    ExecReload=/bin/true
    WorkingDirectory=/etc/openvpn

    [Install]
    WantedBy=multi-user.target
    —————————————
    /lib/systemd/system/[email protected]

    [Unit]
    Description=OpenVPN connection to %i
    PartOf=openvpn.service
    ReloadPropagatedFrom=openvpn.service
    Before=systemd-user-sessions.service
    After=network-online.target
    Wants=network-online.target
    Documentation=man:openvpn(8)
    Documentation=https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage
    Documentation=https://community.openvpn.net/openvpn/wiki/HOWTO

    [Service]
    Type=notify
    PrivateTmp=true
    WorkingDirectory=/etc/openvpn
    ExecStart=/usr/sbin/openvpn –daemon ovpn-%i –status /run/openvpn/%i.status 10 –cd /etc/openvpn –script-security 2 ->PIDFile=/run/openvpn/%i.pid
    KillMode=process
    ExecReload=/bin/kill -HUP $MAINPID
    CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT >LimitNPROC=100
    DeviceAllow=/dev/null rw
    DeviceAllow=/dev/net/tun rw
    ProtectSystem=true
    ProtectHome=true
    RestartSec=5s
    ————————————-
    /lib/systemd/system/[email protected]

    [Unit]
    Description=OpenVPN tunnel for %I
    After=network-online.target
    Wants=network-online.target
    Documentation=man:openvpn(8)
    Documentation=https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage
    Documentation=https://community.openvpn.net/openvpn/wiki/HOWTO

    [Service]
    Type=notify
    PrivateTmp=true
    WorkingDirectory=/etc/openvpn/client
    ExecStart=/usr/sbin/openvpn –suppress-timestamps –nobind –config %i.conf
    CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_OVERRIDE
    LimitNPROC=10
    DeviceAllow=/dev/null rw
    DeviceAllow=/dev/net/tun rw
    ProtectSystem=true
    ProtectHome=true
    KillMode=process

    [Install]
    WantedBy=multi-user.target

Leave a Reply

Your email address will not be published. Required fields are marked *