238 lines
8.1 KiB
Bash
Executable File
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
|