Add enforcement verification, zombie-tunnel detection, single-instance guard, exit-IP observation - #39
Open
Behnam-RK wants to merge 3 commits into
Open
Add enforcement verification, zombie-tunnel detection, single-instance guard, exit-IP observation#39Behnam-RK wants to merge 3 commits into
Behnam-RK wants to merge 3 commits into
Conversation
…e guard, exit-IP observation Adopts four gaps found comparing dezhban against ClaudeVpnGuardian, a similar Windows kill-switch project: nothing ever re-checked that firewall rules were still installed after Apply, a hung-but-interface-up tunnel had no diagnosis or recovery path, two `dezhban run` processes could race to apply rules, and exit-IP failovers within an allowed country had no signal. - Enforcement verification (vpn.advanced.verifyInterval, default 1m): the run loop periodically confirms its rules are still installed and re-applies the standing posture if they're not. An unreadable backend is never treated as evidence the rules are gone. - Zombie-tunnel detection: diagnosed unconditionally (state.zombie, doctor, rendered posture) when exit lookups fail through an interface that reports up. Acting on it — opening an automatic redial window — is a separate, off-by-default key (vpn.advanced.livenessRedial, ADR-0010), since a censoring exit produces the identical symptom on a tunnel that was never down. - Single-instance guard: `run` takes an exclusive lock over the state directory for its lifetime (flock on Unix, a named mutex on Windows), released by the OS on process exit by any means. panic/unblock/service commands stay lock-free, as the escape hatch must. - Exit-IP change observation (state.exitIpChangedAt): purely informational, never touches posture or the hysteresis streak. - A startup self-test log line summarizing backend/state-dir/tunnel/endpoint reachability. Full docs pass: config.md, cli.md, glossary.md, troubleshooting.md, testing.md, ADR-0010 + README index, CHANGELOG. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…entry dg.verify is only ever set or cleared by the verifyC tick handler, which is skipped whenever standby is true. Without an explicit reset, a "rules missing" finding from before a tunnel drop would keep being republished forever after a control-driven unblock into standby, misreporting an enforcement problem while the daemon is correctly idle. Add resetVerify(), call it at the standby-entry transition and when a live reload disables verifyInterval, and add a regression test that fails without the fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…check lock_windows_test.go mirrors lock_unix_test.go's two cases for the Windows side of the single-instance guard (CreateMutexW/ERROR_ALREADY_EXISTS instead of flock), which PR #39 shipped without. Also switch buildLivenessCheck's Missing/Err branch to an exhaustive switch, documenting that the two are mutually exclusive by construction. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adopts four ideas from comparing dezhban against ClaudeVpnGuardian, a similar Windows-only kill-switch project — closing gaps dezhban had, without adopting anything that would weaken its stronger default-deny/enforce-at-rest model.
vpn.advanced.verifyInterval, default1m) — the run loop periodically confirmsBackend.IsBlocked()still says the rules are installed, and re-applies the standing posture the instant they are not. Previously, nothing ever noticed a ruleset removed from outside the daemon (another firewall tool,pfctl -F all,nft flush ruleset, an OS reset) — the daemon kept reporting GUARD while the host was open. An unreadable backend is never treated as evidence the rules are gone, mirroring the "undeterminable country HOLDS" discipline.state.zombie,dezhban doctor, the rendered posture sentence), always on. Acting on it — opening an automatic redial window — is a separate, off-by-default key (vpn.advanced.livenessRedial), because an exit that censors the geo providers produces the identical symptom on a tunnel that was never actually down. Full trade-off and alternatives in ADR-0010.runnow takes an exclusive lock over the state directory for its whole lifetime (flock on Unix, a named mutex on Windows), so a seconddezhban run(with or without--no-daemon) can't race an already-running daemon to callBackend.Apply. Released by the OS on process exit by any means, so a killed daemon never wedges the next start.panic/unblock/service-lifecycle commands deliberately take no such lock — they stay the escape hatch.state.exitIpChangedAt) — purely informational, logged and published when the exit IP differs from the previous successful reading; never touchesblocked,countryCode, or the hysteresis streak.Infoline at daemon start: firewall backend reachable, state dir writable, tunnels configured/detected, endpoints known, whether this host has ever observed a tunnel up.Deliberately not adopted from the comparison: CVG's DNS-guard/IPv6-disable (dezhban already blocks both at layer 3 with no persistent OS mutation to restore), its backup-bundle system (only needed because it does mutate OS state), per-check blocking policy profiles (would recreate the à-la-carte security ADR-0001 removed), and killing the protected app (a second enforcement model).
Test plan
go build ./...,go vet ./...,go test ./...— clean on macOSGOOS=windows GOARCH=amd64andGOOS=linux GOARCH=amd64build + vet — cleango test -race ./...— cleaninternal/runner/verify_test.go,liveness_test.go,exitip_test.go,cmd/dezhban/lock_unix_test.gointernal/firewall/*.gotoucheddocs/contribute/testing.md(flush ruleset by hand and confirm repair; black-hole a tunnel's traffic and confirm zombie diagnosis with no window by default; secondrunrefuses) — not run in this environment, flagged for manual verification before merge🤖 Generated with Claude Code