summaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
authorJames O'Doherty <james@theodohertyfamily.com>2026-05-22 08:48:02 -0400
committerJames O'Doherty <james@theodohertyfamily.com>2026-05-22 08:48:02 -0400
commita4cd7de209fe90006b3e6e67c69dea5ed0c9f832 (patch)
tree2e8d3446e3d880b398e42da38b67c7988cc0f3ff /README.md
Initial commit
Diffstat (limited to 'README.md')
-rw-r--r--README.md133
1 files changed, 133 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..26d7c22
--- /dev/null
+++ b/README.md
@@ -0,0 +1,133 @@
+# wg-wrap: Transparent Userspace VPN Wrapper
+
+## Overview
+wg-wrap is a design for a tool that allows a native Linux process to communicate over a WireGuard VPN without requiring the host kernel to manage the VPN tunnel or requiring global root privileges. It achieves this by bridging a Linux network namespace's TUN device directly to a userspace WireGuard implementation.
+
+## Profile Management
+To simplify usage, `wg-wrap` implements a profile system for managing WireGuard configurations.
+- **Storage**: Profiles are stored as standard `.conf` files in `~/.config/wg-wrap/profiles/`.
+- **Selection**: Users can reference a profile by its filename (without the extension).
+ - Example: `wg-wrap --profile home-vpn curl google.com` (looks for `~/.config/wg-wrap/profiles/home-vpn.conf`).
+- **Command Separation**: Supports the `--` delimiter to explicitly separate `wg-wrap` flags from the target command.
+ - Example: `wg-wrap --profile home-vpn -- curl --option-that-looks-like-a-flag`
+- **Default**: An optional `default.conf` in the same directory can be used if no profile is specified.
+
+### Profile Management Commands
+Beyond wrapping commands, `wg-wrap` provides management sub-commands to handle profiles:
+- `wg-wrap profile list`: Lists all available profiles in the config directory.
+- `wg-wrap profile import <path>`: Imports a `.conf` file into the profiles directory, prompting for a profile name.
+- `wg-wrap profile configure <name>`: Opens the selected profile in the system's default editor.
+- `wg-wrap profile delete <name>`: Removes the specified profile.
+
+
+## The Core Architecture
+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`.
+
+### 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>`).
+- **Workflow**:
+ 1. When a profile is called, `wg-wrap` checks if the namespace bind-mount already exists.
+ 2. If it exists, it simply joins the existing namespace.
+ 3. If not, it creates a new one, initializes WireGuard, and creates the bind-mount.
+- **Benefit**: This allows multiple terminal windows or separate processes to share the same WireGuard tunnel and internal IP, enabling true "VPN-as-a-service" for specific applications.
+
+### 1. Components
+
+#### Network Namespace Manager
+- **Isolation**: Uses `unshare -r -n` to create a new network namespace. This maps the current user to root within the namespace, allowing the creation of network interfaces without host-level root access.
+- **Interface Setup**: Creates a virtual TUN device (e.g., `tun0`).
+- **Kernel Routing**: Configures the Linux kernel's routing table inside the namespace to ensure the target traffic is directed into the TUN device.
+ - `ip addr add <internal-ip> dev tun0`
+ - `ip link set tun0 up`
+ - `ip route add <vpn-subnet> dev tun0` (or a default route `0.0.0.0/0`).
+
+#### Userspace WireGuard Controller
+- **Engine**: Utilizes a userspace WireGuard implementation.
+ - Recommended: `golang.zx2c4.com/wireguard` (the official Go implementation).
+ - For advanced network stack isolation or userspace TCP/IP, consider `gvisor.dev/gvisor` (specifically `pkg/tcpip` for its `NetworkDispatcher` and `PacketBuffer` logic).
+- **Binding**: The WireGuard device is bound directly to the TUN device.
+ - Use `golang.zx2c4.com/wireguard/tun` to interact with the Linux TUN device.
+ - Consider implementing a bridge similar to a userspace network stack to connect GVisor's network stack to the TUN device.
+- **Configuration**: Parses standard WireGuard `.conf` files.
+ - **Implementation Note**: Use the `IpcSet` method of the `device.Device` to apply keys and endpoints. This avoids complex internal state management and is the standard way to interact with the userspace engine.
+ - Profiles must resolve to: Local Private Key, Remote Peer Public Keys, Remote Endpoints (UDP IP:Port), and Allowed IPs.
+
+
+
+#### Execution Wrapper
+- **Bootstrap**: Orchestrates the namespace creation and WireGuard initialization.
+- **Command Execution**: Uses `exec` to replace the bootstrap process with the user's requested command (e.g., `curl`, `ssh`), ensuring the command runs within the configured network environment.
+
+## Data Flow Detail
+1. **Egress**: A process (e.g., `curl`) sends a packet to a remote IP. The Linux kernel sees the route via `tun0` and writes the raw IP packet to the TUN device. The userspace WireGuard device reads this packet, encrypts it according to the configured peer, and sends it as a UDP packet to the remote endpoint.
+2. **Ingress**: A UDP packet arrives at the local port. The userspace WireGuard device decrypts it and writes the resulting raw IP packet back into the TUN device. The Linux kernel receives this packet and delivers it to the waiting application.
+
+## Comparison: Userspace vs. Kernel VPN
+| Feature | Kernel WireGuard | WGWRAP (Userspace) |
+| :--- | :--- | :--- |
+| **Privileges** | Requires `CAP_NET_ADMIN` (Root) | No root required (`unshare`) |
+| **Deployment** | Requires Kernel Module | Standalone Binary |
+| **Isolation** | Global | Per-process/Namespace |
+| **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.
+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
+
+### 1. MTU Management
+WireGuard adds overhead. If the TUN device is set to 1500, encrypted packets may be dropped.
+- **Action**: Set the TUN device MTU to `1420` bytes to prevent fragmentation/drops.
+
+### 2. DNS Handling
+Routing traffic to the VPN doesn't guarantee DNS is routed.
+- **Action**: The wrapper should update `/etc/resolv.conf` within the namespace or use the GVisor stack to intercept and redirect DNS queries to the VPN's designated DNS server.
+- **Fallback Strategy**: **Never use the host's DNS servers**, as this creates DNS leaks and undermines the isolation of the network namespace. If no DNS server is specified in the WireGuard config:
+ - **Action**: Fall back to trusted, encrypted-capable public providers (e.g., Cloudflare `1.1.1.1` or Google `8.8.8.8`) via the VPN tunnel.
+ - **User Control**: Provide a flag (e.g., `--dns-server <IP>`) to allow the user to override the fallback and specify their own trusted resolver.
+
+### 3. Namespace Lifecycle
+Network namespaces can leak if not managed.
+- **Action**: The controller must monitor the target process and explicitly tear down the TUN device and close the namespace on exit.
+
+### 4. User Namespace Sequence
+To create a network namespace without root, you must create a user namespace first.
+- **Sequence**: `CLONE_NEWUSER` $\rightarrow$ `CLONE_NEWNET` $\rightarrow$ `Setuid/Setgid` $\rightarrow$ Configure Interfaces.
+
+## Testing Strategy
+
+To ensure the correctness of the network isolation and the stability of the userspace tunnel, the following testing approach will be used:
+
+### 1. Component Testing (Unit/Integration)
+- **Config Parser**: Test against a variety of WireGuard `.conf` files (edge cases: missing fields, invalid keys, extreme MTUs).
+- **Profile Manager**: Verify that import/delete/edit operations correctly manipulate `~/.config/wg-wrap/profiles/`.
+- **Namespace Logic**: Verify that `unshare` and `setns` calls correctly transition the process into the target namespace.
+
+### 2. Functional Testing (The "Data Plane" Test)
+To avoid dependence on external infrastructure, we will implement a "Virtual Peer" test harness. This involves creating a standalone Go process that acts as the VPN server using a userspace network stack (e.g., GVisor).
+- **Self-Contained Peer**: The test peer will instantiate its own userspace WireGuard device and an embedded TCP/IP stack to host virtual services (e.g., an HTTP server) on a private VPN IP.
+- **Dynamic Config Generation**: The harness will generate a matching WireGuard `.conf` profile on the fly, allowing `wg-wrap` to connect to the virtual peer.
+- **Connectivity Test**: `wg-wrap --profile test curl <virtual-peer-ip>` should successfully retrieve data from the embedded service.
+- **Isolation Test**: `curl` executed *without* `wg-wrap` should not be able to reach the virtual peer's internal IP.
+- **DNS Leak Test**: Use `dig` or `nslookup` to verify that DNS queries are routed through the VPN and not the host's resolver.
+- **MTU Test**: Send large packets (e.g., using `ping -s 1400`) to ensure no fragmentation or packet loss occurs.
+
+### 3. Persistence & Concurrency Testing
+- **Shared Namespace Test**: Start one command with a profile, then start another. Verify that both share the same internal IP address.
+- **Lifecycle Test**: Terminate the first process and verify that the second process maintains connectivity (proving the namespace is pinned).
+- **Cleanup Test**: Implement a `wg-wrap profile stop <name>` command to unpin the namespace and kill the associated userspace WG process.
+
+### 4. Environment Testing
+- **Rootless Verification**: Run the entire suite as a non-privileged user to ensure no `CAP_NET_ADMIN` requirements are accidentally introduced.
+- **Kernel Compatibility**: Test on multiple Linux kernels to ensure `CLONE_NEWUSER` and `CLONE_NEWNET` behave consistently.