Skip to content

Repository files navigation

strongswan-roadwarrior-kit

English · 简体中文

Reproducible IKEv2 road-warrior VPN on Docker + host systemd, with the failure modes that cost me a day already fixed.

Phones connect, get a virtual IP, route all traffic out through the gateway's address. That is the whole scope.

Is this the right thing to use?

Probably not, if you just want a VPN. Use hwdsl2/setup-ipsec-vpn — broader protocol support, real client management (ikev2.sh --addclient/--revokeclient), certificate/EAP auth, tested across distributions.

This kit exists for a narrower case: you run vimagick/strongswan in network_mode: host on a Debian/Ubuntu box behind NAT, and you want the host path plane to be correct and durable. It is a reproducible record of one working deployment, not a general-purpose installer.

Worth stealing regardless of how you deploy:

  • scripts/vpn-doctor.sh — read-only diagnosis in the order that converges
  • the MSS clamp in scripts/vpn-path-persist.shwhy

The idea that matters

Three planes, and debugging them out of order is what wastes the day:

Plane Owns Lives in Survives docker restart?
Load charon read conf/secrets container yes if volumes are right
Tunnel SA, VIP, ESP counters container yes
Path forward hook, VTI, route, SNAT, MSS host no — must be re-applied

network_mode: host misleads people here. The container shares the host netns but does not own its routes or iptables. A perfectly healthy container with a dead path plane is exactly "connects, no internet" — and if the Load plane is down, every NAT theory you test is noise.

Install

Needs: Debian/Ubuntu, root, Docker, iptables, nftables.

git clone https://github.com/Jieoz/strongswan-roadwarrior-kit.git
cd strongswan-roadwarrior-kit
cp vpn.env.example vpn.env
$EDITOR vpn.env          # the only file you edit
sudo ./install.sh
sudo /root/ipsec-vpn/vpn-doctor.sh

Open UDP 500 and UDP 4500 to the host. Nothing else. 1701 is L2TP; this kit is IKEv2-only.

install.sh renders config from vpn.env, installs the units, gates on the load plane (fails loudly if charon read 0 bytes), and re-runs the path script to prove rules do not duplicate.

vpn.env

Key Notes
HOST_IP address actually on the uplink; the SNAT target. Behind NAT this is private
OUT_IF uplink interface (ip -br a)
PUBLIC_ID what clients dial; used for leftid
VIP_NET / VIP_GW must not overlap your LAN
MARK shared by mark_in/mark_out and the VTI key — must match
CLAMP_MSS ~40 below TUNNEL_MTU
VPN_CLIENTS id:psk pairs, space-separated

Generate keys: openssl rand -base64 24

Clients

Each device gets an ID and its own PSK:

VPN_CLIENTS="phone:xxxx laptop:yyyy spare:zzzz"

On the phone: type IKEv2/IPsec PSK, the ID in the identifier field, that device's key as the pre-shared key. Some UIs label the ID field "username" — there is no account system here; see docs/CLIENTS.md.

This is not decoration. Several live global : PSK lines do not work: IKEv2 must choose a signing key before it has seen IDi, so only the last line is ever used and every other device gets MAC mismatched. Named IDs are what make per-device logging and revocation possible.

Operating it

/root/ipsec-vpn/vpn-doctor.sh              # read-only, safe on a live box
docker exec strongswan-vpn ipsec statusall # SAs and byte counters
tail -f /var/log/vpn-status.log            # 15-minute snapshots
bash /root/ipsec-vpn/vpn-path-persist.sh   # re-apply path plane (idempotent)

Add or revoke a client: edit VPN_CLIENTS, re-run ./install.sh, then docker restart strongswan-vpn. This drops every active session.

Verifying persistence

Enabled units are not proof. Two failures pass every static check:

  1. Non-idempotent rules. A bare -A in a script that runs on every boot doubles rules each time (2 → 4 → 6) while grep, bash -n and systemctl is-enabled all stay happy. Every rule here is -C guarded, and install.sh re-runs the script and counts.
  2. Never actually rebooting. Only a real reboot proves it. unattended-upgrades can stall shutdown so it looks rebooted when it was not:
sudo reboot
# after it comes back:
uptime -p                                   # must be minutes
iptables -t mangle -S FORWARD | grep -c -i tcpmss   # must be 2, not 4
/root/ipsec-vpn/vpn-doctor.sh

Lessons

Nine failures, what each looked like, and why: docs/LESSONS.md.

The one that generalises furthest: "another client works fine" does not clear the server. It proves auth, tunnel, 0.0.0.0/0 and SNAT are good. It says nothing about whether the server compensates for client differences — MTU/PMTU, IPv6, split routing. A missing MSS clamp shows up as "one specific site never loads, on one specific phone", and looks exactly like a client bug.

Also: the address clients dial is not necessarily the address the world sees. Behind upstream NAT they differ, and any IP allowlist needs the egress one. vpn-doctor.sh prints both.

Layout

vpn.env.example        the only file you edit
install.sh             render config, install units, verify
scripts/
  vpn-path-persist.sh  THE authoritative path plane (idempotent)
  vpn-doctor.sh        read-only diagnosis, LOAD -> TUNNEL -> PATH -> MTU
  vpn-path-watch.sh    re-apply after docker restart / ipsec0 loss
  vpn-status.sh        quiet periodic snapshot
config/                ipsec.conf + compose templates
systemd/               boot unit, watchdog, status timer
docs/                  LESSONS.md, TROUBLESHOOTING.md, CLIENTS.md
tests/                 50 logic checks + 36 real path-plane checks (CI)

One authoritative path script. If something needs fixing, fix it there — a second "just this once" NAT script is how this becomes unmaintainable.

Scope

Does: IKEv2 PSK road-warrior, full-tunnel, per-device IDs, durable host path plane, MSS clamp, self-heal, read-only doctor.

Does not: certificates/EAP, IPv6 VIPs (IPv4 pool only), split tunnelling, L2TP/Cisco IPsec, web UI, distributions other than Debian/Ubuntu. For those, use hwdsl2.

License

MIT

About

Reproducible IKEv2 road-warrior VPN (strongSwan + Docker) with a durable host path plane, MSS clamping, per-device PSKs, and a read-only diagnostic doctor.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages