Running Select Applications through a Cisco AnyConnect VPN
August 18, 2016This is outdated now. Please see the README of the nsdo
GitHub repository instead
In an earlier article specific to OpenVPN, I wrote:
To isolate VPN applications from applications running ‘bare,’ I use the following method, which involves nsdo. It has the handy effect of putting each instance of the openvpn client in its own network namespace, allowing you to run a bunch of VPNs at the same time, each available only to programs you choose.
This article covers the same idea, except for Cisco AnyConnect VPNs using OpenConnect, a libre implementation of AnyConnect. (I haven’t tried the official nonfree client, but I’m assuming it’s not worth the trouble, for this purpose at least.)
First, I use the same netns@.service
systemd service I wrote in
the OpenVPN article by putting it in /etc/systemd/system/
:
[Unit]
Description=network namespace %I
[Service]
Type=oneshot
ExecStart=/bin/ip netns add %I
ExecStop=/bin/ip netns del %I
RemainAfterExit=yes
By putting network namespace creation/deletion in a separate unit
(rather than a script launched by OpenConnect/OpenVPN at connection
time), the VPN uses the same network namespace across restarts. For
example, if I have a browser running in a VPN namespace and restart my
VPN client, my browser doesn’t stay in an outdated netns after my VPN
client starts up, creates a new netns, and changes the netns to which
/var/run/netns/X
points.
I also created an openconnect@.service
, which depends on
netns@.service
:
[Unit]
Description=OpenConnect VPN Connection: Profile %I
Requires=network.target netns@%i.service
After=network.target netns@%i.service
[Service]
Type=simple
WorkingDirectory=/usr/local/etc/openconnect/
ExecStart=/usr/local/etc/openconnect/openconnect-wrapper %I
[Install]
WantedBy=multi-user.target
Notice that it executes openconnect-wrapper
, a wrapper shell script,
which I (arbitrarily) chose to put in /usr/local/etc/openconnect/
.
Here it is:
#!/bin/bash
set -e
[[ -z $1 ]] && {
printf "$0: usage: $0 <preset>\n" >&2
exit 1
}
preset="$1"
[[ ! -f $preset.conf ]] && {
printf "$0: error: '$(pwd)/$preset.conf' does not exist!\n" >&2
exit 2
}
# Expect the hostname to be the first line in the file, immediately
# preceded by `# '
host="$(head -n 1 "$preset.conf" | cut -b 3-)"
pass="$(cat "$preset.pass" || systemd-ask-password "Password for AnyConnect VPN $preset:")"
# vpnc-script-netns expects this to be set
export NETNS="$preset"
exec openconnect --config "$preset.conf" --script ./vpnc-script-netns --passwd-on-stdin --non-inter "$host" <<<"$pass"
I wrote the script to allow me to have presets for different servers. It
reads the hostname from the first line of the config file (after #
and
a space) and calls systemd-ask-password
to ask for the password
so that I don’t have to write my password in plaintext anywhere. (When
you start the service, you can enter the password by running
systemd-tty-ask-password-agent
as root.)
Here’s my configuration file for Georgia Tech’s VPN, gatech.conf
(the wrapper script
reads the comment at the top to determine the hostname):
# https://anyc.vpn.gatech.edu
authgroup=gatech
user=aadams80
The wrapper script also tells OpenConnect to configure the network
interfaces using vpnc-script-netns
, another shell script I hacked
together:
#!/bin/bash
set -e
[[ -z $NETNS ]] && {
printf "$0"': $NETNS is not set! Please set it to the name of the desired namespace\n' >&2
exit 1
}
case $reason in
connect)
ip netns exec "$NETNS" ip link set dev lo up
ip link set dev "$TUNDEV" up netns "$NETNS" mtu "$INTERNAL_IP4_MTU"
# Setup only IPv4 for now
ip netns exec "$NETNS" ip addr add "$INTERNAL_IP4_ADDRESS" dev "$TUNDEV"
ip netns exec "$NETNS" ip route add default dev "$TUNDEV"
# Put DNS servers in /etc/netns/$NETNS/resolv.conf
mkdir -p "/etc/netns/$NETNS"
{
printf '# Generated by vpnc-script-netns\n'
for dns_server in $INTERNAL_IP4_DNS; do
printf 'nameserver %s\n' "$dns_server"
done
} >"/etc/netns/$NETNS/resolv.conf"
;;
esac
Currently, it handles only IPv4 because my university’s VPN doesn’t appear to support IPv6 yet.
I guess that’s it. With a config file named gatech.conf
in the same
directory as vpnc-script-netns
and openconnect-wrapper
, I start the
VPN with:
# systemctl start openconnect@gatech
# systemd-tty-password-agent
Password for AnyConnect VPN gatech: ***********************
and then run applications in it like:
$ nsdo gatech firefox
Or, using iproute2 instead of nsdo:
# ip netns exec gatech sudo -u $USER firefox
Or using nsenter
from util-linux:
# nsenter -n/var/run/netns/gatech sudo -u $USER firefox
Preventing Inactivity Timeouts
After a period of inactivity, the Georgia Tech VPN will close my
connection. To prevent this, I just leave an instance of ping
running
in the namespace:
$ nsdo gatech ping -i 60 gatech.edu &
But that could easily be a systemd unit, so I’ll try to update this post later with one.