# How OpenFlux Tunnels TCP Traffic Using gVisor and Raw Sockets

> Discover how OpenFlux tunnels TCP traffic using gVisor and raw sockets. Learn about its proxy and raw modes for efficient network connections. Explore the p1neappleXpress/OpenFlux repository.

- Repository: [p1neappleXpress/OpenFlux](https://github.com/p1neappleXpress/OpenFlux)
- Tags: how-to-guide
- Published: 2026-09-14

---

**OpenFlux tunnels TCP traffic by embedding a userspace gVisor network stack that intercepts connections through a virtual NIC, forwarding packets over pluggable transports using either proxy mode (socket-level forwarding) or raw mode (layer-3 packet injection with SNAT).**

OpenFlux is an open-source tunneling tool that creates a transparent TCP proxy without kernel modifications. The project at `p1neappleXpress/OpenFlux` implements a complete IPv4/TCP stack in userspace using Google's gVisor, enabling it to tunnel arbitrary TCP streams over various underlying transports while supporting two distinct forwarding strategies for the exit node.

## Architectural Foundation

The tunnel architecture centers on a **virtual network stack** that runs entirely in userspace, decoupling TCP logic from the host operating system.

### gVisor Stack Initialization

The core tunnel logic in [`tunnel/tunnel.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/tunnel/tunnel.go) instantiates a gVisor `stack.Stack` configured for IPv4 and TCP protocols. During initialization in `NewTCPTunnelMode` (lines 87‑105), the code creates the stack with protocol factories:

```go
t.gvisorStack = stack.New(stack.Options{
    NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol},
    TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol},
})

```

This userspace stack handles the entire TCP state machine, congestion control, and reassembly independently of the host kernel.

### Virtual NIC and Transport Bridge

OpenFlux bridges the gVisor stack to the underlying transport (such as Yandex or OneMe) through a custom `TunnelLinkEndpoint` defined in [`tunnel/endpoint.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/tunnel/endpoint.go). This virtual NIC implements two critical paths:

- **Outbound**: The stack calls `tunnelEP.onOutgoingPacket` to hand packets to the transport layer.
- **Inbound**: The transport injects received packets via `tunnelEP.InjectInbound`.

This design allows the tunnel to operate over any reliable transport while maintaining standard TCP semantics.

## Tunnel Operating Modes

OpenFlux supports two exit-node modes selected via the `--mode` flag: **Proxy** (`ExitModeProxy`) and **Raw** (`ExitModeRaw`).

### Proxy Mode (ExitModeProxy)

In proxy mode, the exit node terminates TCP connections inside the gVisor stack and opens new outbound sockets to the destination. Implemented in `setupExitNodeProxy` (lines 33‑44) and `handleExitTCP` (lines 46‑84) of [`tunnel/tunnel.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/tunnel/tunnel.go), this mode:

1. Puts the stack into promiscuous and spoofing mode.
2. Registers a `tcp.Forwarder` to intercept outbound connection attempts.
3. Accepts the gVisor-side connection via `gonet.TCPConn`.
4. Dials the real destination using `net.DialTimeout`.
5. Shuffles data bidirectionally between the two streams.

This mode requires no special privileges and works on all operating systems, making it the default portable option.

### Raw Mode (ExitModeRaw)

Raw mode operates at layer 3, forwarding packets without terminating TCP connections. Available only on Linux and requiring root privileges, this mode is implemented in `setupExitNodeRaw` (lines 89‑143):

- Creates a `RawSocketEndpoint` (defined in [`tunnel/rawsocket_linux.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/tunnel/rawsocket_linux.go)) that binds to a raw socket.
- Discovers the exit node's local IP via `getLocalIP` and registers it as a protocol address on a second NIC (ID 2).
- Configures routing so internet-bound traffic leaves via the raw NIC while tunnel-subnet traffic uses NIC 1.
- Performs source-IP rewriting (SNAT) before injecting packets into the host network.

To prevent the host kernel from sending RST packets in response to unsolicited packets, raw mode typically requires an iptables rule to drop outbound TCP RSTs targeting the tunnel subnet.

## Packet Flow and Lifecycle

### Client Mode Operation

When running as a client, OpenFlux assigns the virtual IP `10.10.10.2` to its NIC, adds a default route pointing to the exit node, and uses the stack's `gonet.DialTCP` to initiate connections. All TCP handshakes and window management occur within the gVisor stack, with payload data encapsulated in the chosen transport protocol.

### Exit Node Packet Processing

**Proxy flow**: Incoming packets from the transport are injected into the gVisor stack via `InjectInbound`. The stack processes them through the `tcp.Forwarder`, which triggers `handleExitTCP` to proxy the connection to the real destination.

**Raw flow**: The `RawSocketEndpoint` receives packets from the transport and forwards them directly to the raw socket via `SetTransportSender`. The host kernel processes these as if they originated locally, preserving the original TCP ports and sequence numbers.

## Public API and Usage

The `TCPTunnel` type exposes a simple API for callers, implemented in [`tunnel/tunnel.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/tunnel/tunnel.go).

### Creating a Tunnel

As used in [`main/main.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/main/main.go) (lines 46‑48), initialization follows this pattern:

```go
// Parse the desired exit mode from the CLI flag
exitMode, _ := tunnel.ParseExitMode(*mode)

// Build the transport (e.g., Yandex Docs)
trans := transport.NewCompressedTransport(innerTransport)

// Initialise the tunnel; *exitNode indicates whether this instance is the exit
tun := tunnel.NewTCPTunnelMode(trans, *exitNode, exitMode)

```

### Dialing Remote Hosts

The `DialTCP` method (lines 60‑82) provides a standard `net.Conn` interface:

```go
conn, err := tun.DialTCP("example.com:443")
if err != nil {
    log.Fatalf("dial error: %v", err)
}
defer conn.Close()

// Now conn behaves like a normal net.Conn (TLS, HTTP, etc.)

```

### Listening for Connections

Exit nodes can accept inbound tunnel connections using `ListenTCP` (lines 86‑90):

```go
listener, err := tun.ListenTCP(8443)
if err != nil {
    log.Fatalf("listen error: %v", err)
}
defer listener.Close()

for {
    client, err := listener.Accept()
    if err != nil {
        continue // handle error as needed
    }
    go func(c net.Conn) {
        defer c.Close()
        // Proxy the connection to a real destination
        remote, _ := net.Dial("tcp", "target.internal:22")
        io.Copy(c, remote)
        io.Copy(remote, c)
    }(client)
}

```

### Running Raw Mode

To start the exit node in raw mode with SNAT (as referenced in [`main.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/main.go) lines 23‑30):

```bash
sudo ./openflux -exit-node -mode raw -local-ip 10.0.0.42

```

## Key Source Files

- **[`tunnel/tunnel.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/tunnel/tunnel.go)**: Core implementation including `NewTCPTunnelMode`, `setupExitNodeProxy`, `setupExitNodeRaw`, `handleExitTCP`, `DialTCP`, and `ListenTCP`.
- **[`tunnel/rawsocket_linux.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/tunnel/rawsocket_linux.go)**: Linux-specific `RawSocketEndpoint` for layer-3 packet forwarding.
- **[`tunnel/endpoint.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/tunnel/endpoint.go)**: Definition of `TunnelLinkEndpoint` bridging gVisor and the transport layer.
- **[`main/main.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/main/main.go)**: CLI entry point that wires transports, tunnel modes, and the SOCKS5 server.

## Summary

- **OpenFlux implements TCP tunneling through a userspace gVisor stack** that runs independently of the host kernel, providing portable TCP semantics.
- **Two exit modes provide flexibility**: Proxy mode terminates TCP for compatibility across all platforms, while raw mode preserves end-to-end TCP behavior on Linux using raw sockets and SNAT.
- **The virtual NIC architecture** decouples the network stack from underlying transports via `TunnelLinkEndpoint`, enabling operation over protocols like Yandex Docs or OneMe.
- **The public API** exposes standard Go networking primitives (`DialTCP`, `ListenTCP`) while internally managing complex gVisor interactions and routing tables.

## Frequently Asked Questions

### What underlying transports does OpenFlux support?

OpenFlux abstracts the network layer through a transport interface defined in [`transport/transport.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/transport/transport.go). The codebase includes implementations that can operate over various carriers (referenced as "Yandex," "OneMe," etc.), with support for compression and encryption layers wrapped via `NewCompressedTransport`.

### Why does raw mode require root privileges while proxy mode does not?

Raw mode needs to create raw sockets (`syscall.SOCK_RAW`) to inject packets at the IP layer and typically requires iptables rules to manage TCP RST suppression. Proxy mode operates entirely at the socket layer using standard `net.Dial` and `gonet` calls, which any unprivileged process can execute.

### How does OpenFlux handle TCP connection state?

All TCP state—including handshakes, window scaling, and congestion control—is managed within the gVisor stack instance created in [`tunnel/tunnel.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/tunnel/tunnel.go). The host kernel only sees either proxied connections (proxy mode) or raw IP packets (raw mode), never the internal TCP state of the tunnel clients.

### What IP addressing does the tunnel use internally?

The client node assigns itself the hardcoded virtual IP `10.10.10.2` (as seen in the client initialization code), while the exit node discovers its local IP via `getLocalIP`. Traffic destined outside the tunnel subnet routes through NIC 1 (tunnel) or NIC 2 (raw internet interface) depending on the configured mode and destination.