# wg-wrap Agent Guidelines This document defines the architectural conventions, project layout, and system assumptions for the development of `wg-wrap`. ## Project Goals `wg-wrap` is a transparent userspace VPN wrapper that allows native Linux processes to communicate over WireGuard without requiring host-level root privileges. It utilizes User and Network namespaces to isolate traffic and a userspace WireGuard implementation to handle encryption. ## Project Layout The project follows a modern Go project structure to ensure scalability and a clear separation between public APIs and internal implementation details. ```text . ├── Makefile # Handles the C-to-Go embedding build chain ├── cmd/ │ └── wg-wrap/ # CLI Entry point. Handles flag parsing and subcommand routing. ├── internal/ │ ├── cli/ # Command line interface and bootstrap orchestration. │ ├── config/ # Application-wide configuration types. │ ├── namespace/ # Linux namespace management (unshare, setns, pinning). │ │ └── launcher_src/ # C source for the rootless bootstrap helper. │ └── wireguard/ # Userspace WireGuard controller and TUN device binding. ├── pkg/ │ └── wgconf/ # WireGuard .conf parsing logic. Reusable library. ├── tests/ │ └── e2e/ # End-to-End "Data Plane" tests using Virtual Peers. ├── go.mod # Module definition. └── README.md # Project overview and design specification. ``` ## Development Conventions ### 1. Quality Assurance Pipeline No piece of code is considered "done" until it has passed the full verification pipeline. Every implementation cycle must conclude with the following checks: 1. **Formatting**: `go fmt ./...` 2. **Static Analysis**: `go vet ./...` 3. **Linting**: `golangci-lint run` 4. **Verification**: Run the test suite via the Makefile: - **All tests**: `make test` - **Specific tests**: `make test TEST_ARGS="-run TestName"` (e.g., `make test TEST_ARGS="-run TestDNSLeak"`) - **Custom flags**: `make test TEST_ARGS="-count=10"` If any of these tools report an error or warning, the code must be corrected before the task is marked as complete. ### 2. Testing & Stubbing Conventions To maintain a high-velocity development cycle without sacrificing correctness, we follow these rules for incomplete code: - **Code Stubs**: Any unimplemented logic path must be explicitly marked with a `// TODO` comment and return a descriptive error (e.g., `fmt.Errorf("feature X not yet implemented")`). - **Test Stubs**: Any test that is planned but not yet implementable must use `t.Skip("not implemented")` and include a comment describing the specific scenario the test is intended to verify. - **Hermetic Configuration**: Tests involving profiles, settings, or filesystem state must not touch the actual user home directory. Use the `ConfigDir` injection pattern in the `App` struct combined with `t.TempDir()` to create isolated, temporary test environments. - **Path Portability**: NEVER hardcode absolute paths (e.g., `/home/user/...`) in the source code or test suites. Always use relative paths, `os.Getwd()`, or environment-aware discovery to locate binaries and configuration files. - **Performance & Reliability**: - **Parallelism**: Use `t.Parallel()` in integration and E2E tests. Use `t.TempDir()` to ensure resource isolation. - **Granular Timeouts**: All system calls, network operations, and external command executions must be wrapped in a `context.WithTimeout` (typically 2-5 seconds) to prevent hanging tests. - **Interface Mocking**: Use interfaces for "heavy" system operations (e.g., routing, namespace creation) to allow fast unit testing of logic via mocks, reserving real syscalls for the integration tier. ### 2. Testing & Stubbing Conventions ... - **Interface Mocking**: Use interfaces for "heavy" system operations (e.g., routing, namespace creation) to allow fast unit testing of logic via mocks, reserving real syscalls for the integration tier. - **Shared Fixtures**: Use `sync.Once` or `TestMain` for expensive setup (e.g., Virtual Peer) to avoid redundant boot-ups across tests. - **Argument Integrity**: To prevent shell injection and argument splitting, never concatenate arguments into a single string for execution. Always use "vector-based" execution (e.g., `os/exec.Command` in Go or `execv`/`execvp` in C) to ensure that arguments containing spaces or special characters are preserved as discrete literals. ### 3. Platform Compatibility & Build Constraints ... `wg-wrap` is fundamentally a Linux system tool. To ensure the module remains compilable on other platforms while restricting Linux-specific syscalls, we use the following patterns: - **Build Tags**: All files interacting with `golang.org/x/sys/unix`, network namespaces, or TUN devices must start with `//go:build linux`. - **The Stub Pattern**: For any platform-specific logic in `internal/`, we provide a matching stub file tagged with `//go:build !linux`. This allows the project to compile on non-Linux OSs, returning "not supported" errors at runtime rather than failing at build time. - **Platform Agnosticism**: `pkg/` libraries (e.g., `pkg/wgconf`) must remain platform-agnostic and avoid any OS-specific imports to ensure they are reusable across all environments. ### 4. Testing Strategy We employ a three-tier testing approach to balance speed and reliability: | Tier | Location | Type | Scope | Requirement | | :--- | :--- | :--- | :--- | :--- | | **Unit** | Package dirs | `go test` | Pure logic (e.g., parsing, validation) | None | | **Integration** | `internal/**/*_test.go` | `go test -tags=integration` | Syscalls, Namespace creation, Routing | Linux | | **E2E** | `tests/e2e/` | `go test ./tests/e2e/...` | Full data path (Connect $\rightarrow$ Curl $\rightarrow$ Peer) | Linux + TUN access | ### 2. Platform Constraints - **Target OS**: Linux only. - **Build Tags**: Use `//go:build linux` or `//go:build linux,integration` for any code interacting with `golang.org/x/sys/unix` or network namespaces. - **MTU**: Always default the TUN device MTU to `1420` to account for WireGuard overhead. ### 3. Namespace Lifecycle - **Creation**: `CLONE_NEWUSER` $\rightarrow$ `CLONE_NEWNS` $\rightarrow$ `CLONE_NEWNET` inside an embedded C launcher. - **Persistence & Sharing**: Namespaces are pinned and shared rootlessly. Processes record active runs inside a profile's `pids/` directory. Subsequent wrapping calls discover the active PID and re-execute through our single-threaded C launcher to call `setns` (joining User, Mount, and Network namespaces) in $\approx 10\text{ms}$ before the Go runtime starts, bypassing Go's multi-threaded `CLONE_NEWNS` limitation. - **Cleanup**: When the last active process registers its exit, the reference counting detects 0 remaining sessions, automatically unpins state files, and releases resources cleanly. ## System Assumptions The project assumes the target environment is a modern Linux system configured for rootless container operations (e.g., Podman is installed and functional): - **User Namespaces**: `kernel.unprivileged_userns_clone=1` is assumed. - **UID Mapping**: SubUIDs/SubGIDs are configured. - **TUN Access**: The user has permission to access `/dev/net/tun`. - **Tooling**: The `ip` command (iproute2) is available in the environment. ## Roadmap Priority (Completed) 1. **Configuration**: Parse robust `.conf` files in `pkg/wgconf`. 2. **Bootstrapping**: Unshare Mount, User, and Network namespaces safely using an embedded static C launcher. 3. **Host Socket Preservation**: Open UDP sockets on the host before isolation and pass them (`WG_WRAP_HOST_SOCKET_FD`) to `wireguard-go` using `FDBind` to bypass kernel security boundaries. 4. **Data Path**: Integrate `wireguard-go` with `tun` devices seamlessly inside the namespace. 5. **Routing**: Automatically build default routing gateway tables in the isolated network namespace. 6. **Namespace Sharing**: Connect concurrent wrapping runs to the active tunnel rootlessly via `setns` inside the single-threaded C launcher.