Files

238 lines
8.1 KiB
Bash
Executable File

#!/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" <<IPSEOF
config setup
charondebug="all"
uniqueids=yes
conn $L2TP_NAME
auto=add
keyexchange=ikev1
authby=secret
type=transport
left=%defaultroute
leftprotoport=17/1701
right=$SERVER_IP
rightprotoport=17/1701
ike=aes128-sha1-modp1024
esp=aes128-sha1-modp1024
dpddelay=30
dpdtimeout=120
dpdaction=clear
keyingtries=%forever
ikelifetime=24h
lifetime=8h
margin=10m
IPSEOF
# Write ipsec.secrets — specify exact IPs for strongSwan
printf '5.161.114.8 %s : PSK "%s"\n' "$SERVER_IP" "$PSK" > "$IPSEC_SECRETS"
# Write xl2tpd.conf
cat > "$XL2TPD_CONF" <<XL2EOF
[global]
port = 1701
[lac $L2TP_NAME]
lns = $SERVER_IP
ppp debug = yes
pppoptfile = /etc/ppp/options.$L2TP_NAME
length bit = yes
require pap = no
require chap = yes
autodial = yes
redial = yes
redial timeout = 5
max redials = 3
XL2EOF
# Write ppp options — NO replacedefaultroute (keeps internet on eth0)
# Also: no defaultroute at all — we'll add the necessary routes manually
# after the connection is established to avoid ppp0 recursion.
cat > "/etc/ppp/options.$L2TP_NAME" <<PPPOEOF
ipcp-accept-local
ipcp-accept-remote
refuse-eap
noccp
noauth
idle 0
maxfail 3
debug
name $USERNAME
password $PASSWORD
mtu 1400
mru 1400
usepeerdns
lcp-echo-interval 10
lcp-echo-failure 5
PPPOEOF
# Write or update chap-secrets entry
if grep -q "^\"$USERNAME\"" "$L2TP_SECRETS" 2>/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