Skip to content
On this page

Rule-based OpenConnect

OpenConnect supports Cisco AnyConnect SSL VPN, Juniper Network Connect, Palo Alto Networks (PAN) GlobalProtect SSL VPN, Pulse Connect Secure SSL VPN, F5 BIG-IP SSL VPN, FortiGate SSL VPN and Array Networks SSL VPN.

For example, there would be a use case where your company uses Cisco AnyConnect for internal network access. Here I'll show you how you can use OpenConnect with policy routing powered by Clash.

First, install vpn-slice. This tool overrides default routing table behaviour of OpenConnect. Simply saying, it stops the VPN from overriding your default routes.

Next you would have a script (let's say tun0.sh) similar to this:

sh
#!/bin/bash
ANYCONNECT_HOST="vpn.example.com"
ANYCONNECT_USER="john"
ANYCONNECT_PASSWORD="foobar"
ROUTING_TABLE_ID="6667"
TUN_INTERFACE="tun0"

# Add --no-dtls if the server is in mainland China. UDP in China is choppy.
echo "$ANYCONNECT_PASSWORD" | \
  openconnect \
    --non-inter \
    --passwd-on-stdin \
    --protocol=anyconnect \
    --interface $TUN_INTERFACE \
    --script "vpn-slice
if [ \"\$reason\" = 'connect' ]; then
  ip rule add from \$INTERNAL_IP4_ADDRESS table $ROUTING_TABLE_ID
  ip route add default dev \$TUNDEV scope link table $ROUTING_TABLE_ID
elif [ \"\$reason\" = 'disconnect' ]; then
  ip rule del from \$INTERNAL_IP4_ADDRESS table $ROUTING_TABLE_ID
  ip route del default dev \$TUNDEV scope link table $ROUTING_TABLE_ID
fi" \
    --user $ANYCONNECT_USER \
    https://$ANYCONNECT_HOST

After that, we configure it as a systemd service. Create /etc/systemd/system/tun0.service:

ini
[Unit]
Description=Cisco AnyConnect VPN
After=network-online.target
Conflicts=shutdown.target sleep.target

[Service]
Type=simple
ExecStart=/path/to/tun0.sh
KillSignal=SIGINT
Restart=always
RestartSec=3
StartLimitIntervalSec=0

[Install]
WantedBy=multi-user.target

Then we enable & start the service.

shell
chmod +x /path/to/tun0.sh
systemctl daemon-reload
systemctl enable tun0
systemctl start tun0

From here you can look at the logs to see if it's running properly. Simple way is to look at if tun0 interface has been created.

Similar to the Wireguard one, having an outbound to a TUN device is simple as adding a proxy group:

yaml
proxy-groups:
  - name: Cisco AnyConnect VPN
    type: select
    interface-name: tun0
    proxies:
      - DIRECT

... and it's ready to use! Add the desired rules:

yaml
rules:
  - DOMAIN-SUFFIX,internal.company.com,Cisco AnyConnect VPN

You should look at the debug level logs when something does not seem right.