summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames O'Doherty <james@theodohertyfamily.com>2026-05-22 10:14:03 -0400
committerJames O'Doherty <james@theodohertyfamily.com>2026-05-22 10:14:03 -0400
commit5dbc46f3c1c75bf922bcc1c3df342323c23c04ce (patch)
tree1a2840366e5d39aa3d9e8d868dd5893f5c488373
parent764d3e67fc783c487f42d398d1b85a5a1f0d8ef0 (diff)
docs: update README and AGENTS.md to reflect embedded launcher architecture
-rw-r--r--.gitignore2
-rw-r--r--AGENTS.md3
-rw-r--r--README.md16
-rw-r--r--go.mod2
-rw-r--r--go.sum2
-rw-r--r--internal/namespace/launcher_src/launcher.c11
-rw-r--r--internal/namespace/namespace.go5
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/<profile-name>`).
@@ -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 <command_to_run> [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 {