diff options
| -rw-r--r-- | internal/cli/cli.go | 24 | ||||
| -rw-r--r-- | internal/wireguard/wireguard.go | 17 | ||||
| -rw-r--r-- | tests/e2e/config_hotswap_test.go | 2 | ||||
| -rw-r--r-- | tests/e2e/lifecycle_test.go | 6 |
4 files changed, 38 insertions, 11 deletions
diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 7d5a05c..0e3b8ad 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -167,8 +167,13 @@ func (a *App) ExecuteCommand(cfg *config.Config) error { // Acquire execution lock during configuration and startup inside the namespace lockFile, lockErr := namespace.AcquireProfileLock(pm, cfg.Profile) + var lockFileReleased bool if lockErr == nil { - defer namespace.ReleaseProfileLock(lockFile) + defer func() { + if !lockFileReleased { + namespace.ReleaseProfileLock(lockFile) + } + }() } if err := namespace.PruneStalePids(pm, cfg.Profile); err != nil { @@ -179,8 +184,17 @@ func (a *App) ExecuteCommand(cfg *config.Config) error { } defer func() { - // Re-acquire lock for the entire cleanup sequence to ensure atomic unregister and unpin - cleanupLock, cleanupErr := namespace.AcquireProfileLock(pm, cfg.Profile) + var cleanupLock *os.File + var cleanupErr error + + if lockErr == nil && !lockFileReleased { + // We already hold the lock, so we can just reuse lockFile for cleanup! + cleanupLock = lockFile + } else { + // Re-acquire lock for the entire cleanup sequence to ensure atomic unregister and unpin + cleanupLock, cleanupErr = namespace.AcquireProfileLock(pm, cfg.Profile) + } + if cleanupErr == nil { // 1. Unregister the process first. if err := namespace.UnregisterProcess(pm, cfg.Profile); err != nil { @@ -200,6 +214,9 @@ func (a *App) ExecuteCommand(cfg *config.Config) error { fmt.Printf("failed to unpin namespace: %v\n", err) } } + if lockErr == nil && !lockFileReleased { + lockFileReleased = true + } namespace.ReleaseProfileLock(cleanupLock) } else { // Fallback if lock fails to ensure we still unregister @@ -270,6 +287,7 @@ func (a *App) ExecuteCommand(cfg *config.Config) error { } // We can now release the startup lock and execute the command + lockFileReleased = true namespace.ReleaseProfileLock(lockFile) cmd := exec.Command(cfg.Command[0], cfg.Command[1:]...) diff --git a/internal/wireguard/wireguard.go b/internal/wireguard/wireguard.go index 3c293b4..e250dab 100644 --- a/internal/wireguard/wireguard.go +++ b/internal/wireguard/wireguard.go @@ -98,15 +98,17 @@ func StartTunnel(cfg *wgconf.Config, dnsServer string) (t *Tunnel, err error) { return nil, fmt.Errorf("failed to configure network interface %s: %w", tunName, err) } + var dnsFile string if path, err := ConfigureResolvConf(dnsServer); err != nil { fmt.Printf("warning: failed to configure DNS resolver: %v\n", err) } else { - t.dnsFile = path + dnsFile = path } return &Tunnel{ - Device: wgDev, - Tun: tunDev, + Device: wgDev, + Tun: tunDev, + dnsFile: dnsFile, }, nil } @@ -194,10 +196,17 @@ func configureInterface(name, address string, mtu int) error { } } + var dst *net.IPNet + if addr.IP.To4() != nil { + _, dst, _ = net.ParseCIDR("0.0.0.0/0") + } else { + _, dst, _ = net.ParseCIDR("::/0") + } + route := &netlink.Route{ Scope: netlink.SCOPE_UNIVERSE, LinkIndex: link.Attrs().Index, - Dst: nil, + Dst: dst, } if err := netlink.RouteAdd(route); err != nil { diff --git a/tests/e2e/config_hotswap_test.go b/tests/e2e/config_hotswap_test.go index 5c483ac..b4a7ca5 100644 --- a/tests/e2e/config_hotswap_test.go +++ b/tests/e2e/config_hotswap_test.go @@ -42,7 +42,7 @@ Endpoint = 1.1.1.1:51820 } // Start a process to establish the session - cmdA := exec.Command(binaryPath, "--profile", profile, "--", "sleep", "0.1") + cmdA := exec.Command(binaryPath, "--profile", profile, "--", "sleep", "1.0") cmdA.Env = append(os.Environ(), fmt.Sprintf("XDG_RUNTIME_DIR=%s", tmpRuntimeDir), fmt.Sprintf("XDG_CONFIG_HOME=%s", tmpConfigDir), diff --git a/tests/e2e/lifecycle_test.go b/tests/e2e/lifecycle_test.go index 4b452da..67b1370 100644 --- a/tests/e2e/lifecycle_test.go +++ b/tests/e2e/lifecycle_test.go @@ -47,7 +47,7 @@ func waitForLifecycle(t *testing.T, binaryPath, runtimeDir, profile string, expe case <-timeout: t.Fatalf("Timed out waiting for lifecycle state: expected active=%v", expectedActive) case <-tick.C: - cmd := exec.Command(binaryPath, "--profile", profile, "test-lifecycle") + cmd := exec.Command(binaryPath, "test-lifecycle", "--profile", profile) cmd.Env = append(os.Environ(), fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)) err := cmd.Run() @@ -78,7 +78,7 @@ func TestNamespaceLifecycleAutomation(t *testing.T) { t.Run("ReferenceCounting", func(t *testing.T) { // Start a process that exits quickly - cmd1 := exec.Command(binaryPath, "--profile", "default", "--", "sleep", "0.1") + cmd1 := exec.Command(binaryPath, "--profile", "default", "--", "sleep", "1.0") cmd1.Env = append(os.Environ(), fmt.Sprintf("XDG_RUNTIME_DIR=%s", tmpRuntimeDir)) if err := cmd1.Start(); err != nil { t.Fatalf("Failed to start cmd1: %v", err) @@ -88,7 +88,7 @@ func TestNamespaceLifecycleAutomation(t *testing.T) { waitForLifecycle(t, binaryPath, tmpRuntimeDir, "default", true) // Start a second process using the same profile - cmd2 := exec.Command(binaryPath, "--profile", "default", "--", "sleep", "0.1") + cmd2 := exec.Command(binaryPath, "--profile", "default", "--", "sleep", "1.0") cmd2.Env = append(os.Environ(), fmt.Sprintf("XDG_RUNTIME_DIR=%s", tmpRuntimeDir)) if err := cmd2.Start(); err != nil { t.Fatalf("Failed to start cmd2: %v", err) |
