#!/bin/bash # wisp-vpn.sh — Connect/disconnect L2TP/IPsec VPN to MicroTik gateway # Usage: wisp-vpn.sh [up|down|status] CONFIG_DIR="$(dirname "$0")" YAML="$CONFIG_DIR/config.yaml" # Parse YAML values get_cfg() { local key="$1" grep -A50 "^vpn:" "$YAML" | grep "^ $key:" | sed 's/.*: //' | tr -d '"' | sed 's/ *#.*//' | head -1 } SERVER_IP=$(get_cfg server_ip) PSK=$(get_cfg psk) USERNAME=$(get_cfg username) PASSWORD=$(get_cfg password) IFACE=$(get_cfg interface) IFACE="${IFACE:-ppp0}" L2TP_NAME="wisp-vpn" IPSEC_CONF="/etc/ipsec.conf" IPSEC_SECRETS="/etc/ipsec.secrets" XL2TPD_CONF="/etc/xl2tpd/xl2tpd.conf" L2TP_PEERS="/etc/ppp/peers/$L2TP_NAME" L2TP_SECRETS="/etc/ppp/chap-secrets" case "$1" in up) echo "[*] Bringing up L2TP/IPsec VPN to $SERVER_IP ..." # Write ipsec.conf for strongSwan compatibility cat > "$IPSEC_CONF" < "$IPSEC_SECRETS" # Write xl2tpd.conf cat > "$XL2TPD_CONF" < "/etc/ppp/options.$L2TP_NAME" </dev/null; then # Entry exists — remove old line and add new one (password may have changed) sed -i "/^\"$USERNAME\"/d" "$L2TP_SECRETS" 2>/dev/null fi echo "\"$USERNAME\" * \"$PASSWORD\" *" >> "$L2TP_SECRETS" # Ensure the L2TP server IP has a clean route through eth0 BEFORE ipsec starts # This prevents IPsec + pppd from creating a routing loop (ppp0 recursion) CURRENT_GW=$(ip route get 8.8.8.8 | awk '{print $3}' | head -1) if [ -n "$CURRENT_GW" ]; then ip route add "$SERVER_IP" via "$CURRENT_GW" dev eth0 metric 100 2>/dev/null || \ ip route change "$SERVER_IP" via "$CURRENT_GW" dev eth0 metric 100 2>/dev/null echo "[route] $SERVER_IP via $CURRENT_GW (avoids ppp0 recursion)" fi # Reload config — loads new connections added to ipsec.conf ipsec reload 2>/dev/null sleep 2 ipsec up $L2TP_NAME 2>/dev/null sleep 3 systemctl restart xl2tpd 2>/dev/null sleep 3 # Trigger the connection echo "c $L2TP_NAME" > /var/run/xl2tpd/l2tp-control 2>/dev/null sleep 5 # Verify if ip addr show "$IFACE" 2>/dev/null | grep -q "inet "; then LOCAL_IP=$(ip addr show "$IFACE" | grep "inet " | awk '{print $2}') echo "[+] VPN connected! Interface: $IFACE, IP: $LOCAL_IP" # Add route to peer's LAN subnet through ppp0 (if the peer address is known) # Without this, ppp0 recursion happens when pppd applies defaultroute PEER_IP=$(echo "$LOCAL_IP" | cut -d' ' -f1) echo "[*] Adding route to VPN peer's subnet via $IFACE ..." ip route add 192.168.88.0/24 dev "$IFACE" 2>/dev/null && \ echo " [route] Added 192.168.88.0/24 via $IFACE" || \ echo " [route] 192.168.88.0/24 already exists" # Add static routes for routed tower subnets from config echo "[*] Adding static routes for tower subnets ..." ROUTES=$(grep -A50 "^vpn:" "$YAML" | grep -A50 "^ routes:" | grep "^- " | sed 's/.*- //' | tr -d '"') if [ -n "$ROUTES" ]; then echo "$ROUTES" | while read -r subnet; do if [ -n "$subnet" ]; then # Check if route already exists if ip route show "$subnet" 2>/dev/null | grep -q .; then echo " [route] $subnet already exists" else ip route add "$subnet" dev "$IFACE" echo " [route] Added $subnet via $IFACE" fi fi done else echo " (no routes configured in config.yaml)" fi # Safety: verify internet connectivity still works # If ppp's replacedefaultroute kills internet, kill ppp immediately echo "[*] Checking internet connectivity ..." if ping -c 1 -W 3 8.8.8.8 >/dev/null 2>&1 || ping -c 1 -W 3 1.1.1.1 >/dev/null 2>&1; then echo "[+] Internet reachable — VPN is safe" else echo "[-] INTERNET LOST after VPN connect!" echo " Tearing down VPN to restore connectivity ..." echo "d $L2TP_NAME" > /var/run/xl2tpd/l2tp-control 2>/dev/null pkill -f "pppd.*$L2TP_NAME" 2>/dev/null ipsec down $L2TP_NAME 2>/dev/null sleep 2 echo "[-] VPN terminated — internet should be restored" exit 1 fi exit 0 else echo "[-] VPN connection failed (ppp0 not up)." echo " Cleaning up IPsec to avoid orphaned tunnel ..." echo "d $L2TP_NAME" > /var/run/xl2tpd/l2tp-control 2>/dev/null pkill -f "pppd.*$L2TP_NAME" 2>/dev/null ipsec down $L2TP_NAME 2>/dev/null sleep 2 echo " Check logs: journalctl -u xl2tpd -n 20" echo " ipsec status" exit 1 fi ;; down) echo "[*] Tearing down VPN ..." # Remove static routes for tower subnets ROUTES=$(grep -A50 "^vpn:" "$YAML" | grep -A50 "^ routes:" | grep "^- " | sed 's/.*- //' | tr -d '"') if [ -n "$ROUTES" ]; then echo "[*] Removing static routes ..." echo "$ROUTES" | while read -r subnet; do if [ -n "$subnet" ]; then ip route del "$subnet" dev "$IFACE" 2>/dev/null && \ echo " [route] Removed $subnet" || true fi done fi echo "d $L2TP_NAME" > /var/run/xl2tpd/l2tp-control 2>/dev/null ipsec down $L2TP_NAME 2>/dev/null sleep 2 # Kill the ppp interface pkill -f "pppd.*$L2TP_NAME" 2>/dev/null sleep 1 # Safety: ensure default route via eth0 is restored DEFAULT_GW=$(ip route show default | grep eth0 | head -1 || true) if [ -z "$DEFAULT_GW" ]; then GW=$(ip route get 8.8.8.8 | awk '{print $3}' | head -1) DEV=$(ip route get 8.8.8.8 | awk '{print $5}' | head -1) if [ -n "$GW" ] && [ -n "$DEV" ]; then echo " [safety] Default route lost — restoring via $DEV" ip route add default via "$GW" dev "$DEV" metric 100 2>/dev/null || true fi fi echo "[+] VPN disconnected" ;; status) if ip addr show "$IFACE" 2>/dev/null | grep -q "inet "; then LOCAL_IP=$(ip addr show "$IFACE" | grep "inet " | awk '{print $2}') echo "[+] VPN is UP — $IFACE: $LOCAL_IP" exit 0 else echo "[-] VPN is DOWN" exit 1 fi ;; *) echo "Usage: $0 {up|down|status}" exit 1 ;; esac