From 5dbc46f3c1c75bf922bcc1c3df342323c23c04ce Mon Sep 17 00:00:00 2001 From: James O'Doherty Date: Fri, 22 May 2026 10:14:03 -0400 Subject: docs: update README and AGENTS.md to reflect embedded launcher architecture --- .gitignore | 2 +- AGENTS.md | 3 +++ README.md | 16 ++++++++++------ go.mod | 2 -- go.sum | 2 -- internal/namespace/launcher_src/launcher.c | 11 +++++++---- internal/namespace/namespace.go | 5 ----- 7 files changed, 21 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index b074b5a..6f5498a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ bin/ wg-wrap **/launcher **/launcher.bin +**/launcher_test # Go build artifacts *.exe @@ -16,7 +17,6 @@ wg-wrap coverage.out # OS generated files -.DS_PStore .DS_Store Thumbs.db diff --git a/AGENTS.md b/AGENTS.md index 3cdb6bd..8886c0b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,11 +10,14 @@ The project follows a modern Go project structure to ensure scalability and a cl ```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. diff --git a/README.md b/README.md index 26d7c22..1b09bb7 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,15 @@ Beyond wrapping commands, `wg-wrap` provides management sub-commands to handle p The tool focuses on a direct, transparent data path: `Linux Application` $\rightarrow$ `Linux Kernel Routing` $\rightarrow$ `TUN Device` $\rightarrow$ `Userspace WireGuard` $\rightarrow$ `UDP Socket` $\rightarrow$ `Internet`. +### Rootless Bootstrap Loop +To achieve rootless network isolation without interfering with the Go runtime's multi-threaded scheduler, `wg-wrap` employs a bootstrap pattern: +1. **Initial Launch**: The Go binary starts and detects it is not in an isolated network namespace. +2. **Helper Deployment**: It writes an embedded C launcher binary to a secure temporary location. +3. **Namespace Transition**: It uses `syscall.Exec` to replace itself with the C launcher. +4. **Isolation**: The C launcher performs the `unshare(CLONE_NEWUSER | CLONE_NEWNET)` sequence, maps the current user to root (UID 0) inside the namespace, and disables supplementary groups. +5. **Re-entry**: The launcher then `execvp`s the original `wg-wrap` binary. +6. **Execution**: The second instance of `wg-wrap` detects it is now isolated and proceeds to initialize the VPN and execute the target application. + ### Persistent Namespaces To support multiple commands using the same VPN profile without re-establishing the tunnel, `wg-wrap` utilizes persistent network namespaces. - **Mechanism**: Instead of relying on the process lifecycle to keep the namespace alive, `wg-wrap` "bind-mounts" the network namespace file to a known location on disk (e.g., `/run/user/$UID/wg-wrap/profiles/`). @@ -73,15 +82,10 @@ To support multiple commands using the same VPN profile without re-establishing | **Routing** | Host Routing Table | Isolated Namespace Table | ## Implementation Roadmap -1. **Bootstrap Logic**: Implement the `unshare` and re-execution flow. - - Use `golang.org/x/sys/unix` for `Unshare` and user mapping. - - **Strategy**: Follow the "pasta/slirp4netns" model—create the network namespace first. This grants the process root-like privileges *within the namespace*, allowing the creation of the TUN device without requiring global `CAP_NET_ADMIN` on the host. +1. **Bootstrap Logic**: Implement the `unshare` and re-execution flow via an embedded C launcher. (DONE) 2. **TUN/WG Integration**: Integrate the `tun` device with the `wireguard-go` device. - - Leverage `golang.zx2c4.com/wireguard/tun` for interface management. 3. **Routing Automation**: Automate the `ip` command sequence for interface and route setup. 4. **Config & Profile Management**: Implement a robust parser for WireGuard configuration files and a full profile management system (import, list, edit, delete) targeting `~/.config/wg-wrap/profiles/`. - - 5. **Lifecycle Management**: Ensure the TUN and WireGuard devices are cleaned up upon process termination. ## Technical Gotchas & Implementation Details diff --git a/go.mod b/go.mod index 3c0acb3..c5ef9d2 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ module git.theodohertyfamily.com/tools/wg-wrap go 1.26.3 - -require golang.org/x/sys v0.45.0 // indirect diff --git a/go.sum b/go.sum index ae2cabb..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +0,0 @@ -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= diff --git a/internal/namespace/launcher_src/launcher.c b/internal/namespace/launcher_src/launcher.c index 70737e4..63dd6ff 100644 --- a/internal/namespace/launcher_src/launcher.c +++ b/internal/namespace/launcher_src/launcher.c @@ -69,9 +69,12 @@ int main(int argc, char **argv) { return 1; } - fprintf(stderr, "[launcher] Executing binary: %s\n", argv[0]); - execvp(argv[0], argv); + // Use execv instead of execvp to avoid PATH search issues + // since we are providing an absolute path from Go. + if (execv(argv[0], argv) == -1) { + perror("execv failed"); + return 1; + } - perror("execvp"); - return 1; + return 0; } diff --git a/internal/namespace/namespace.go b/internal/namespace/namespace.go index 98d73b6..5e31b9d 100644 --- a/internal/namespace/namespace.go +++ b/internal/namespace/namespace.go @@ -84,14 +84,9 @@ func Bootstrap() error { // 3. Prepare arguments for the launcher. // The launcher expects: launcher [args...] - // syscall.Exec's second argument is the argv array. - // argv[0] is set by the kernel to the launcherPath. - // So our first slice element becomes argv[1]. args := []string{self} args = append(args, os.Args[1:]...) - fmt.Printf("[bootstrap] Execing launcher with args: %v\n", args) - // 4. Replace the current process with the launcher. err = syscall.Exec(launcherPath, args, os.Environ()) if err != nil { -- cgit v1.2.3