diff --git a/container/skills/pia-vpn/SKILL.md b/container/skills/pia-vpn/SKILL.md index 78fb200..d7554ad 100644 --- a/container/skills/pia-vpn/SKILL.md +++ b/container/skills/pia-vpn/SKILL.md @@ -9,8 +9,10 @@ Bring this container's traffic out through Private Internet Access over WireGuard, using the API PIA documents for headless use. Run `sudo ~/.claude/skills/pia-vpn/pia-wg.sh` with `up`, `up --full`, `down` or -`status`. Read the rest of this page before the first `up --full` — two of the -behaviours below are actively misleading if you meet them without warning. +`status`. Read the rest of this page before the first `up --full` — three of the +behaviours below are actively misleading if you meet them without warning, and +each one presents as "the VPN is fine" or "Claude is broken" rather than as +what it is. ## Before anything else: what the toggle does not do @@ -72,7 +74,27 @@ that way looks perfect and works for nothing. `status` resolves a real name for this reason. Trust its `DNS:` line, and if you check by hand, resolve a name rather than fetching an address. -## Trap 3: no tunnel survives a restart, and it fails open +## Trap 3: in test mode, the obvious probe is the one thing tunnelled + +`up` routes `1.1.1.1` and nothing else. So checking your address by fetching +`https://1.1.1.1/cdn-cgi/trace` reports a **PIA** address — not because your +traffic is going through PIA, but because that single probe is. Everything else +still leaves directly. + +This reads exactly like a working full tunnel, and it is the likeliest reason +someone concludes the VPN is on when it is not. `status` prints both exits in +test mode for this reason: + +``` +mode: test route only (1.1.1.1 through the tunnel, nothing else) + through the tunnel: 64.113.5.73 + everything else: 172.116.197.166 <- your real address +``` + +Two different addresses there is correct and expected in test mode. If you want +the second line to change, you want `up --full`. + +## Trap 4: no tunnel survives a restart, and it fails open The network namespace is rebuilt every time the container starts, and nothing inside reconnects anything. After a stop/start, Reset or any config change that @@ -115,18 +137,22 @@ curl -s https://serverlist.piaservers.net/vpninfo/servers/v6 \ ## Verifying -`status` prints three things — handshake, DNS, and the public address: +`status` prints the handshake, DNS, and which address traffic actually leaves +from — labelled by mode, so the answer cannot be misread: ``` latest handshake: 2 seconds ago transfer: 92 B received, 180 B sent DNS: ok (via 10.0.0.243 10.0.0.242) -public IP: 64.113.5.244 +mode: full tunnel + all traffic exits: 64.113.5.244 ``` -All three matter. A handshake with `DNS: BROKEN` is trap 1. A handshake with an -unchanged public address means routing did not take — you are probably in `up` -rather than `up --full`. +All of it matters. A handshake with `DNS: BROKEN` is trap 1. `mode: test route +only` with two different addresses is trap 3, and is correct — it means the +tunnel works and you have not asked for it to carry anything yet. Report the +mode line when telling someone the VPN is on; "the public IP is a PIA one" is +true in test mode too, and means much less than it sounds like. ## Tearing down diff --git a/container/skills/pia-vpn/pia-wg.sh b/container/skills/pia-vpn/pia-wg.sh index d1b5928..8e5c8c6 100644 --- a/container/skills/pia-vpn/pia-wg.sh +++ b/container/skills/pia-vpn/pia-wg.sh @@ -156,8 +156,17 @@ down() { echo "tunnel down" } +# Both are Cloudflare and both answer /cdn-cgi/trace over their bare address, so +# neither needs DNS. Only 1.1.1.1 is ever routed into the tunnel, which is what +# lets status tell the two exits apart. +TRACE_TUNNELLED=https://1.1.1.1/cdn-cgi/trace +TRACE_DIRECT=https://1.0.0.1/cdn-cgi/trace + +exit_ip() { curl -s -m 20 "$1" | sed -n 's/^ip=//p'; } + status() { wg show "$IFACE" 2>/dev/null | grep -E "latest handshake|transfer" || echo "no tunnel up" + # Resolve a name, not an IP literal. A curl to 1.1.1.1 succeeds while DNS is # completely broken, which is exactly how a dead resolver goes unnoticed. printf 'DNS: ' @@ -166,8 +175,22 @@ status() { else echo "BROKEN - cannot resolve api.anthropic.com" fi - echo -n "public IP: " - curl -s -m 20 https://1.1.1.1/cdn-cgi/trace | sed -n 's/^ip=//p' + + # Report the exit per mode. In test mode the probe address is itself the one + # thing inside the tunnel, so a single "public IP" line would print a PIA + # address while every other packet leaves directly -- the exact reading that + # makes a test tunnel look like a full one. + if ip route show 0.0.0.0/1 2>/dev/null | grep -q "$IFACE"; then + echo "mode: full tunnel" + echo " all traffic exits: $(exit_ip "$TRACE_TUNNELLED")" + elif ip link show "$IFACE" >/dev/null 2>&1; then + echo "mode: test route only (1.1.1.1 through the tunnel, nothing else)" + echo " through the tunnel: $(exit_ip "$TRACE_TUNNELLED")" + echo " everything else: $(exit_ip "$TRACE_DIRECT") <- your real address" + else + echo "mode: no tunnel" + echo " all traffic exits: $(exit_ip "$TRACE_DIRECT")" + fi } case "${1:-}" in