Files
pipistrello ea9501f719 tailscale-proxy: add the missing IKEv2 VPN front, and correct the stale exit node
This file has been unsafe to deploy for two separate reasons. Both are fixed here,
and the result is now byte-identical in effective config to what actually runs on
the client-02 host (verified by diffing `docker compose config` on both).

1. ts-vpn + ikev2 were missing entirely.

The IKEv2 VPN front has run on the host since 2026-09-05, but was never mirrored
here — this file defined only ts-proxy, proxy and telemt. Deploying it as-is would
have taken the VPN down for the Windows and iOS clients. Added both services, plus
ikev2-egress.sh, which the ikev2 service mounts and cannot start without.

ts-vpn is a SECOND Tailscale node in kernel/TUN mode (unlike ts-proxy, which is
userspace): it forwards raw IP packets rather than TCP streams, so a userspace
SOCKS5 is useless for it. It owns the network namespace that ikev2 joins via
network_mode, which is also why the ports and sysctls live on ts-vpn and not on
ikev2 — Docker forbids net.* sysctls on a network_mode:service container.

2. ts-proxy still pointed at the pre-migration exit node.

  TS_EXTRA_ARGS  --exit-node=100.121.234.85  ->  --exit-node=100.106.197.99
  TS_HOSTNAME / hostname  technohim-lan-proxy -> 02-tekhnohim-lan-proxy

100.121.234.85 has not existed since the 2026-08-27 tailnet migration. This is
worse than a dead setting: with the exit node absent this stack does NOT fail
closed — it silently egresses through the local ISP, so a redeploy from this repo
would have turned a censorship-avoiding proxy into a plain local-egress one while
still reporting healthy. This drift was recorded as an open issue in the migration
write-up on 2026-08-27; these are the exact substitutions it prescribed.

Not included, deliberately: ts-vpn.env and ikev2.env, which hold TS_AUTHKEY and the
VPN credentials. Like every other config in this stack they are staged on the host
by hand — a git push does not deploy config here, and these must never enter git.
Both extracted files were scanned for secrets before being added.
2026-09-09 23:06:04 +03:00

77 lines
4.1 KiB
Bash
Executable File

#!/bin/sh
# ikev2-egress.sh - runs inside ts-lan-ikev2, which shares ts-vpn's network
# namespace. Tailscale (kernel mode, --netfilter-mode=off) only installs the
# policy routing (default -> tailscale0 via table 52); this loop owns the rest
# and is idempotent, so a container restart or a late tailscale0 is harmless.
#
# 1. ip rules ahead of Tailscale's 52xx rules: replies of the IKE/NAT-T/ESP
# server itself must leave via the docker bridge (main table), otherwise
# the SYN-ACK-equivalent goes into the tunnel and no client ever connects.
# 2. FORWARD accepts for VPN<->tailscale0 ahead of hwdsl2's final -j DROP, and
# MASQUERADE everything leaving tailscale0 to the node's own 100.x address -
# the exit node's WireGuard AllowedIPs drops any other source.
# 3. MSS clamp to the tunnel PMTU (tailscale0 is MTU 1280; hwdsl2 clamps 1360).
# 4. Pin the Libreswan conn to eth0's address: hwdsl2 writes left=%defaultroute,
# which resolves through Tailscale's policy routing to the tailscale0 address,
# so IKE packets arriving on the bridge match no conn (NO_PROPOSAL_CHOSEN).
# 5. LAN access: one ip rule sends LAN_NET out the docker bridge (main table)
# instead of table 52, plus FORWARD accepts and a MASQUERADE to the bridge
# address - LAN hosts have no route back to the VPN pool. Scoped to LAN_NET
# only: every other destination, including the docker bridges themselves,
# still falls through to table 52 = tailscale0. Set LAN_NET empty to restore
# the original egress-only behaviour.
# The LAN block sits OUTSIDE the tailscale0 guard on purpose: LAN access does not
# depend on the exit node, so a client still reaches the office if the tunnel is
# down (internet then fails closed, which is what we want).
TS_IF=tailscale0
LAN_IF=eth0
VPN_NET="192.168.43.0/24" # hwdsl2 IKEv2 address pool (rightaddresspool)
LAN_NET="192.168.0.0/22" # client-02 head office (Kirochnaya). Branch sites
# are NOT routed here - see the deployment note.
log() { echo "[ikev2-egress] $*"; }
ensure_ipt() { # ensure_ipt <table> <chain> <rule...>
t=$1; c=$2; shift 2
iptables -w -t "$t" -C "$c" "$@" 2>/dev/null || { iptables -w -t "$t" -A "$c" "$@" && log "iptables -t $t -A $c $*"; }
}
ensure_ipt_first() { # like ensure_ipt but inserts at the top (hwdsl2 ends FORWARD with -j DROP)
t=$1; c=$2; shift 2
iptables -w -t "$t" -C "$c" "$@" 2>/dev/null || { iptables -w -t "$t" -I "$c" 1 "$@" && log "iptables -t $t -I $c $*"; }
}
ensure_rule() { # ensure_rule <pref> <selector...>
p=$1; shift
ip rule list | grep -q "^$p:" || { ip rule add pref "$p" "$@" && log "ip rule $p: $*"; }
}
fix_left() {
conf=/etc/ipsec.d/ikev2.conf
[ -f "$conf" ] || return 0
ip4=$(ip -4 -o addr show dev eth0 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | head -1)
[ -n "$ip4" ] || return 0
grep -q "^ left=$ip4\$" "$conf" && return 0
sed -i "s|^ left=.*| left=$ip4|" "$conf" && log "ikev2.conf: left=$ip4 (was %defaultroute -> tailscale0)"
if ipsec auto --replace ikev2-cp >/dev/null 2>&1; then log "ipsec auto --replace ikev2-cp"; fi
}
while :; do
fix_left
if [ -n "$LAN_NET" ] && ip link show "$LAN_IF" >/dev/null 2>&1; then
ensure_rule 5010 to "$LAN_NET" lookup main
ensure_ipt_first filter FORWARD -s "$VPN_NET" -d "$LAN_NET" -o "$LAN_IF" -j ACCEPT
ensure_ipt_first filter FORWARD -d "$VPN_NET" -s "$LAN_NET" -i "$LAN_IF" \
-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
ensure_ipt nat POSTROUTING -s "$VPN_NET" -d "$LAN_NET" -o "$LAN_IF" -j MASQUERADE
fi
if ip link show "$TS_IF" >/dev/null 2>&1; then
ensure_rule 5001 ipproto udp sport 500 lookup main
ensure_rule 5002 ipproto udp sport 4500 lookup main
ensure_rule 5003 ipproto 50 lookup main
ensure_ipt_first filter FORWARD -s "$VPN_NET" -o "$TS_IF" -j ACCEPT
ensure_ipt_first filter FORWARD -d "$VPN_NET" -i "$TS_IF" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
ensure_ipt nat POSTROUTING -o "$TS_IF" -j MASQUERADE
ensure_ipt mangle FORWARD -o "$TS_IF" -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
fi
sleep 30
done