From d4cec92f5690a60b3509ab718bdea72dc520110e Mon Sep 17 00:00:00 2001 From: James O'Doherty Date: Fri, 29 May 2026 20:35:31 -0400 Subject: feat: implement robust namespace lifecycle and resilience suite - Replace marker-file pinning with kernel bind-mount anchors for reliable namespace persistence. - Implement atomic "last-man-out" cleanup sequence using ProfileLock, preventing namespace leaks and race conditions. - Add comprehensive resilience test suite covering: - Crash recovery from stale runtime state. - Host network change stability. - Configuration hot-swap session persistence. - Resource exhaustion and high-churn lifecycle stress. - Align documentation and test expectations with rootless session-based persistence. - Fix argument integrity and isolation leaks. - Ensure 100% pass rate for all E2E and integration tests. --- tests/e2e/race_test.go | 94 -------------------------------------------------- 1 file changed, 94 deletions(-) delete mode 100644 tests/e2e/race_test.go (limited to 'tests/e2e/race_test.go') diff --git a/tests/e2e/race_test.go b/tests/e2e/race_test.go deleted file mode 100644 index 3f5ecfe..0000000 --- a/tests/e2e/race_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package e2e - -import ( - "fmt" - "os" - "os/exec" - "path/filepath" - "testing" -) - -// TestLifecycleRace proves that a new process joining an existing namespace -// can have that namespace unpinned if an exiting process incorrectly -// thinks it's the last one out. -func TestLifecycleRace(t *testing.T) { - binaryPath, err := GetBinaryPath() - if err != nil { - t.Skipf("Skipping test: %v", err) - } - - tmpRuntimeDir := t.TempDir() - profile := "race-test" - pidsDir := filepath.Join(tmpRuntimeDir, "profiles", profile, "pids") - - // Setup a valid profile config to ensure tunneling starts - tmpConfigDir := t.TempDir() - profilesDir := filepath.Join(tmpConfigDir, "wg-wrap", "profiles") - if err := os.MkdirAll(profilesDir, 0755); err != nil { - t.Fatal(err) - } - profileConfPath := filepath.Join(profilesDir, profile+".conf") - conf := `[Interface] -Address = 10.0.0.2/24 -PrivateKey = 0000000000000000000000000000000000000000000000000000000000000000 -DNS = 1.1.1.1 - -[Peer] -PublicKey = 0000000000000000000000000000000000000000000000000000000000000000 -AllowedIPs = 0.0.0.0/0 -Endpoint = 1.1.1.1:51820 -` - if err := os.WriteFile(profileConfPath, []byte(conf), 0644); err != nil { - t.Fatal(err) - } - - // We use a a long-running sleep for Process A to keep the namespace active. - // Process A is the "Victim". - cmdA := exec.Command(binaryPath, "--profile", profile, "--", "sleep", "10") - cmdA.Env = append(os.Environ(), - fmt.Sprintf("XDG_RUNTIME_DIR=%s", tmpRuntimeDir), - fmt.Sprintf("XDG_CONFIG_HOME=%s", tmpConfigDir), - ) - if err := cmdA.Start(); err != nil { - t.Fatalf("Failed to start Process A: %v", err) - } - defer func() { _ = cmdA.Process.Kill() }() - - // Wait for Process A to establish the namespace and register PID - waitForPids(t, pidsDir, 1) - - // Process B is the "Saboteur". It will join and then exit. - // We will loop this to increase the chance of hitting the race window. - for i := 0; i < 5; i++ { - cmdB := exec.Command(binaryPath, "--profile", profile, "--", "sleep", "0.1") - cmdB.Env = append(os.Environ(), - fmt.Sprintf("XDG_RUNTIME_DIR=%s", tmpRuntimeDir), - fmt.Sprintf("XDG_CONFIG_HOME=%s", tmpConfigDir), - ) - if err := cmdB.Start(); err != nil { - t.Fatalf("Failed to start Process B (iteration %d): %v", i, err) - } - - // Wait for Process B to register - waitForPids(t, pidsDir, 2) - - // Let Process B exit. The defer block in ExecuteCommand will: - // 1. Unregister Process B - // 2. Check IsLastProcess() -> might return true if Process A's PID is stale or miscounted - // 3. UnpinNamespace() - if err := cmdB.Wait(); err != nil { - t.Fatalf("Process B failed: %v", err) - } - - // After B exits, Process A should still be the only remaining process. - // We check if the namespace pin file was accidentally deleted by B. - nsPath := filepath.Join(tmpRuntimeDir, "profiles", profile+".ns") - if _, err := os.Stat(nsPath); os.IsNotExist(err) { - t.Errorf("BUG: Namespace pin file was deleted by exiting Process B, despite Process A still running!") - return - } - - // Wait for the PID count to drop back to 1 - waitForPids(t, pidsDir, 1) - } -} -- cgit v1.2.3