tunnel: add -keepalive-timeout-secs for dead connection detection

Tracks last server receive time (atomic) and runs a watchdog goroutine
that closes the connection if no data arrives within the timeout,
triggering reconnection. Also fixes Close() to guard Conn.Close()
inside sync.Once so concurrent/repeated calls are safe.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Git Sagar 2026-07-11 11:54:12 -03:00
parent 1d919aafc5
commit 1a397c363f
5 changed files with 57 additions and 18 deletions

View file

@ -57,7 +57,7 @@ pkg/tap/
## CLI flags
Required: `-host`, `-user`
Optional: `-pass`, `-port` (443), `-hub` (DEFAULT), `-tap` (auto), `-mac`, `-plain-password`, `-insecure`, `-no-dhcp`, `-accept-default-gateway`, `-accept-static-routes`, `-accept-dns`, `-policy-route-table` (0=disabled), `-connmark`, `-reconnect-delay` (5s)
Optional: `-pass`, `-port` (443), `-hub` (DEFAULT), `-tap` (auto), `-mac`, `-plain-password`, `-insecure`, `-no-dhcp`, `-accept-default-gateway`, `-accept-static-routes`, `-accept-dns`, `-policy-route-table` (0=disabled), `-connmark`, `-reconnect-delay` (5s), `-keepalive-timeout-secs` (0=disabled)
## SoftEther protocol pitfalls
@ -99,6 +99,9 @@ Before connecting, resolves server hostname and adds `/32` route via current def
### CONNMARK for DNAT reply routing
`-connmark` (requires `-policy-route-table`) adds iptables CONNMARK rules so DNAT'd connections (port forwards to namespaces/VMs) have replies routed back through the tunnel. Without this, replies from DNAT targets use the default route because their source IP doesn't match the `from <VPN_IP>` policy rule. CONNMARK marks incoming VPN connections and restores the mark on reply packets.
### Keepalive timeout watchdog
`-keepalive-timeout-secs N` enables a receive-side watchdog. The server sends keepalives every ~3s. The watchdog checks at `N/3` intervals whether any data (frames or keepalives) was received within the last N seconds. If not, it closes the connection, triggering the reconnect loop. Without this, a half-open TCP connection can go undetected for minutes (depends on kernel `tcp_retries2`). `Close()` is guarded by `sync.Once` so the watchdog, Bridge error path, and `defer tunnel.Close()` can all call it safely.
## Performance
- **RAM**: 4.6 MB RSS idle, flat under 97 Mbit/s load (vs SoftEther C client: ~23 MB across 4 processes)