This guide replaces the Cisco AnyConnect client with
openconnect + openconnect-sso +
vpn-slice. The result is a more stable connection with
split tunneling (only Dalhousie traffic goes through the VPN),
persistent SSO sessions (no re-authentication for ~2 weeks), and fast
automatic reconnection after network interruptions.
sudo apt update
sudo apt install openconnect pipx
pipx install vpn-slice
pipx ensurepath
source ~/.bashrc
sudo ln -s ~/.local/bin/vpn-slice /usr/local/bin/vpn-sliceopenconnect --version # should print version info
vpn-slice --version # should print e.g. "vpn-slice 0.16.1"
sudo which vpn-slice # should print /usr/local/bin/vpn-slicepipx install openconnect-sso
# Fix Python 3.12 compatibility (pkg_resources is missing from newer setuptools)
~/.local/share/pipx/venvs/openconnect-sso/bin/python -m pip install "setuptools<72"openconnect-sso --version # should print version info without errors
~/.local/share/pipx/venvs/openconnect-sso/bin/python -c "import pkg_resources; print('ok')"Two bugs need to be fixed in openconnect-sso 0.8.1. Both patches may be upstreamed eventually; check before applying.
Without this patch, you must re-authenticate every time you connect. With it, your SSO session persists for ~2 weeks (matching the Cisco client behavior).
Edit:
~/.local/share/pipx/venvs/openconnect-sso/lib/python3.12/site-packages/openconnect_sso/browser/webengine_process.py
Change 1: In WebBrowser.__init__, replace:
cookie_store = self.page().profile().cookieStore()With:
self._profile = QWebEngineProfile("openconnect-sso", self)
self.setPage(QWebEnginePage(self._profile, self))
cookie_store = self.page().profile().cookieStore()Change 2: In _on_cookie_added, add two lines after the existing content:
cookie2 = QNetworkCookie()
self.page().profile().cookieStore().deleteCookie(cookie2)The full method should look like:
def _on_cookie_added(self, cookie):
logger.debug("Cookie set", name=to_str(cookie.name()))
self._on_update(SetCookie(to_str(cookie.name()), to_str(cookie.value())))
cookie2 = QNetworkCookie()
self.page().profile().cookieStore().deleteCookie(cookie2)openconnect-sso --server vpn.its.dal.ca --authgroup DAL # connect, authenticate when prompted
# (wait for "ring size 32" message, then Ctrl-C to disconnect)
openconnect-sso --server vpn.its.dal.ca --authgroup DAL # reconnect: should skip authentication
# (Ctrl-C to disconnect when done)Without this patch, arguments intended for openconnect (such as
--script) are incorrectly rejected by openconnect-sso's
argument parser. This fix allows them to be passed on the command line,
which is how the shell alias in Section 6 supplies the
--script argument for split tunneling.
Edit:
~/.local/share/pipx/venvs/openconnect-sso/lib/python3.12/site-packages/openconnect_sso/cli.py
In main(), replace:
args = parser.parse_args()With:
args, extra = parser.parse_known_args()
args.openconnect_args = (args.openconnect_args or []) + extraNote: This fix has been submitted as PR #213 and may already be included in newer versions of openconnect-sso. Check before applying.
openconnect-sso --help -- --script "vpn-slice 129.173.0.0/16"
# should print help without "unrecognized arguments" errorsudo visudo -f /etc/sudoers.d/openconnectAdd (replace YOUR_USERNAME with your username):
YOUR_USERNAME ALL=(ALL) NOPASSWD: /usr/sbin/openconnect
YOUR_USERNAME ALL=(ALL) NOPASSWD: /usr/bin/pkill openconnect
sudo openconnect --version # should not prompt for password
sudo pkill openconnect # should not prompt for password (exit code 1 if not running is fine)This script ensures the VPN server route is restored after a network interruption, and signals openconnect to reconnect immediately.
sudo nano /etc/NetworkManager/dispatcher.d/99-vpn-route#!/bin/bash
IF=$1
ACTION=$2
if [ "$ACTION" = "up" ] && [ "$IF" = "wlp2s0" ]; then
# Restore direct route to VPN server (lost when wifi reconnects)
ip route add 129.173.0.12 via 192.168.0.1 dev wlp2s0 2>/dev/null || true
# Signal openconnect to reconnect immediately (SIGUSR2 = reconnect)
pkill -USR2 openconnect
fisudo chmod +x /etc/NetworkManager/dispatcher.d/99-vpn-routeReplace wlp2s0 with your wifi interface name (check with ip link).
ls -la /etc/NetworkManager/dispatcher.d/99-vpn-route # should be executable
sudo /etc/NetworkManager/dispatcher.d/99-vpn-route wlp2s0 up # should run without errorsAdd to ~/.bashrc or ~/.bash_aliases:
alias vpn='pgrep openconnect > /dev/null && echo "VPN already running" || (openconnect-sso --server vpn.its.dal.ca --authgroup DAL -- -b --script "vpn-slice 129.173.0.0/16" --reconnect-timeout 30 --force-dpd 10 >/tmp/vpn.log 2>&1)'
alias novpn='sudo pkill openconnect'The -b flag tells openconnect to stay in the foreground
during the connection handshake, then background itself once connected.
This means vpn can be used in scripts that need the VPN to
be up before proceeding:
vpn && ssh chase.mathstat.dal.caVPN activity is logged to /tmp/vpn.log for troubleshooting. Then:
source ~/.bashrctype vpn # should print the alias definition
type novpn # should print the alias definitionRun vpn. A browser window will open asking for:
After successful authentication, the window closes and the VPN connects silently in the background. Subsequent connections within ~2 weeks will not require re-authentication.
To disconnect: novpn
# Should show your home IP, not a Dalhousie IP
curl ifconfig.me
# Should work (routed through VPN)
ssh chase.mathstat.dal.ca
# Should show default route via wlp2s0 (not tun0), plus 129.173.0.0/16 via tun0
ip route shownovpn
vpn # should connect without asking for credentialswebengine_process.py and cli.py will be lost if openconnect-sso is upgraded via pipx. Reapply after upgrades.setuptools<72 fix may become unnecessary in future versions of openconnect-sso.Using the same data path for profile, WebEnginePage still not deleted) are harmless.Server certificate verify failed: signer not found is harmless — Dalhousie uses an internal CA not in the system trust store.