diff options
| author | James O'Doherty <james@theodohertyfamily.com> | 2026-06-04 22:57:35 -0400 |
|---|---|---|
| committer | James O'Doherty <james@theodohertyfamily.com> | 2026-06-04 22:57:35 -0400 |
| commit | 04dca5dada8c2d971ff3b54eeedc5ab6e53a29ac (patch) | |
| tree | a9890073a0eb21bc7db3aef2fcbe66cdc2fc9ceb /internal/namespace | |
| parent | 66b782e261f1cd928ad6a8482788a65fb484db45 (diff) | |
refactor: decouple namespace operations and improve test coverage
- Introduce `namespace.Ops` interface to decouple `Manager` from system-level namespace operations, enabling easier unit testing via mocks.
- Add unit tests for `internal/paths` to verify path resolution logic across different environment configurations.
- Implement `EnsureBinary` helper in E2E tests to gracefully skip tests when `WG_WRAP_BIN` is not set, allowing `go test ./...` to pass in non-build environments.
- Apply project-wide formatting and fix linting issues.
Diffstat (limited to 'internal/namespace')
| -rw-r--r-- | internal/namespace/namespace.go | 18 | ||||
| -rw-r--r-- | internal/namespace/ops.go | 80 |
2 files changed, 89 insertions, 9 deletions
diff --git a/internal/namespace/namespace.go b/internal/namespace/namespace.go index b05dea2..a50f70a 100644 --- a/internal/namespace/namespace.go +++ b/internal/namespace/namespace.go @@ -6,15 +6,15 @@ // scheduler, and to maintain encrypted UDP socket connectivity over the host's network, // wg-wrap employs an advanced bootstrap loop: // -// 1. Host-Bound Socket Creation: During the initial host-level start, a UDP socket is opened -// on 0.0.0.0:0 on the host, and its FD is stored in the environment (WG_WRAP_HOST_SOCKET_FD). -// 2. Helper Deployment: An embedded single-threaded C launcher is used to bridge the transition. -// 3. Namespace Transition: The process replaces itself with the C launcher via syscall.Exec. -// 4. Isolation: The launcher performs the unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWNET) -// sequence to isolate Mount, User, and Network environments. -// 5. Re-entry: The launcher then execvp's the original wg-wrap binary. -// 6. FDBind Tunnel Initialization: The second instance of wg-wrap wraps the host socket FD -// inside a custom FDBind struct to initialize wireguard-go. +// 1. Host-Bound Socket Creation: During the initial host-level start, a UDP socket is opened +// on 0.0.0.0:0 on the host, and its FD is stored in the environment (WG_WRAP_HOST_SOCKET_FD). +// 2. Helper Deployment: An embedded single-threaded C launcher is used to bridge the transition. +// 3. Namespace Transition: The process replaces itself with the C launcher via syscall.Exec. +// 4. Isolation: The launcher performs the unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWNET) +// sequence to isolate Mount, User, and Network environments. +// 5. Re-entry: The launcher then execvp's the original wg-wrap binary. +// 6. FDBind Tunnel Initialization: The second instance of wg-wrap wraps the host socket FD +// inside a custom FDBind struct to initialize wireguard-go. // // User Namespace Sequence: // To create a network namespace without root, wg-wrap follows the sequence: diff --git a/internal/namespace/ops.go b/internal/namespace/ops.go new file mode 100644 index 0000000..b2b5e10 --- /dev/null +++ b/internal/namespace/ops.go @@ -0,0 +1,80 @@ +package namespace + +import ( + "os" + + "git.theodohertyfamily.com/wg-wrap/internal/paths" +) + +// Ops defines the set of operations required by the Manager to handle +// namespace isolation, lifecycle, and synchronization. +type Ops interface { + IsIsolated() bool + Bootstrap() error + BootstrapJoin(pid int) error + RegisterProcess(pm *paths.PathManager, profile string) error + UnregisterProcess(pm *paths.PathManager, profile string) error + PruneStalePids(pm *paths.PathManager, profile string) error + IsLastProcess(pm *paths.PathManager, profile string) (bool, error) + PinNamespace(pm *paths.PathManager, profile string) error + UnpinNamespace(pm *paths.PathManager, profile string) error + FindActiveProfilePid(pm *paths.PathManager, profile string) (int, error) + AcquireProfileLock(pm *paths.PathManager, profile string) (*os.File, error) + ReleaseProfileLock(file *os.File) +} + +// linuxOps is the concrete implementation of Ops for Linux systems. +type linuxOps struct{} + +// NewLinuxOps returns a new instance of the Linux-specific namespace operations. +func NewLinuxOps() Ops { + return &linuxOps{} +} + +func (l *linuxOps) IsIsolated() bool { + return IsIsolated() +} + +func (l *linuxOps) Bootstrap() error { + return Bootstrap() +} + +func (l *linuxOps) BootstrapJoin(pid int) error { + return BootstrapJoin(pid) +} + +func (l *linuxOps) RegisterProcess(pm *paths.PathManager, profile string) error { + return RegisterProcess(pm, profile) +} + +func (l *linuxOps) UnregisterProcess(pm *paths.PathManager, profile string) error { + return UnregisterProcess(pm, profile) +} + +func (l *linuxOps) PruneStalePids(pm *paths.PathManager, profile string) error { + return PruneStalePids(pm, profile) +} + +func (l *linuxOps) IsLastProcess(pm *paths.PathManager, profile string) (bool, error) { + return IsLastProcess(pm, profile) +} + +func (l *linuxOps) PinNamespace(pm *paths.PathManager, profile string) error { + return PinNamespace(pm, profile) +} + +func (l *linuxOps) UnpinNamespace(pm *paths.PathManager, profile string) error { + return UnpinNamespace(pm, profile) +} + +func (l *linuxOps) FindActiveProfilePid(pm *paths.PathManager, profile string) (int, error) { + return FindActiveProfilePid(pm, profile) +} + +func (l *linuxOps) AcquireProfileLock(pm *paths.PathManager, profile string) (*os.File, error) { + return AcquireProfileLock(pm, profile) +} + +func (l *linuxOps) ReleaseProfileLock(file *os.File) { + ReleaseProfileLock(file) +} |
