How OpenFlux Configures the gVisor Stack for Client Mode
When OpenFlux runs with the --client flag, it initializes a lightweight userspace TCP/IP stack using gVisor in tunnel/tunnel.go, assigns the fixed IPv4 address 10.10.10.2/24, and installs a default route to forward all outbound traffic through a virtual NIC backed by TunnelLinkEndpoint.
OpenFlux is an open-source tunneling solution that implements a pure userspace networking layer using Google's gVisor. When operating in client mode, the application creates an isolated TCP/IP environment that encapsulates and forwards connections through various transport layers toward an exit node.
Client Mode Architecture Overview
In client mode, OpenFlux behaves like a tiny virtual machine that processes all TCP connections internally before sending them across the wire. The architecture centers on a single virtual NIC with ID 1 that connects the gVisor stack to the underlying transport mechanism.
Unlike exit node mode, the client configuration does not create raw sockets or secondary NICs. Instead, it relies entirely on the TunnelLinkEndpoint to move packets between the gVisor stack and the selected transport provider (Yandex, MAX, Cups.online, etc.).
Step-by-Step gVisor Stack Configuration
The client-side initialization occurs inside the (*TCPTunnel) setupClient method in tunnel/tunnel.go. This function performs three critical operations to establish network connectivity.
Virtual NIC Initialization
The stack comes pre-configured with a NIC using ID 1. This interface is backed by the in-process TunnelLinkEndpoint structure defined in tunnel/endpoint.go. The endpoint acts as the bridge between gVisor's network layer and OpenFlux's transport abstraction, allowing packets to flow from the userspace stack into the encrypted tunnel.
IPv4 Address Assignment
OpenFlux assigns a fixed internal address to the client: 10.10.10.2/24. This address resides on the same subnet as the exit node (10.10.10.0/24), enabling direct Layer 3 communication between the two endpoints.
The implementation calls gvisorStack.AddProtocolAddress to register the address with the stack:
clientAddr := tcpip.AddrFrom4([4]byte{10, 10, 10, 2})
t.gvisorStack.AddProtocolAddress(tunnelNIC, tcpip.ProtocolAddress{
Protocol: ipv4.ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: clientAddr,
PrefixLen: 24,
},
}, stack.AddressProperties{})
Default Route Installation
To ensure all outbound traffic traverses the virtual tunnel, OpenFlux installs a default route covering the entire IPv4 address space. The route uses header.IPv4EmptySubnet (0.0.0.0/0) as its destination and points to the virtual NIC:
t.gvisorStack.AddRoute(tcpip.Route{
Destination: header.IPv4EmptySubnet,
NIC: tunnelNIC,
})
This configuration forces every TCP connection initiated by applications using the SOCKS5 proxy to flow through the gVisor stack and out via the transport layer.
Implementation Details
The complete client setup logic resides in tunnel/tunnel.go between lines 44-58. The resulting stack provides full TCP semantics including retransmission, flow control, and independent buffer sizing via SetTCPBuffers.
// tunnel/tunnel.go – client-side stack setup
func (t *TCPTunnel) setupClient(tunnelNIC tcpip.NICID) {
// 1️⃣ Fixed client address: 10.10.10.2/24
clientAddr := tcpip.AddrFrom4([4]byte{10, 10, 10, 2})
t.gvisorStack.AddProtocolAddress(tunnelNIC, tcpip.ProtocolAddress{
Protocol: ipv4.ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: clientAddr,
PrefixLen: 24,
},
}, stack.AddressProperties{})
// 2️⃣ Default route – send everything via the virtual NIC
t.gvisorStack.AddRoute(tcpip.Route{
Destination: header.IPv4EmptySubnet,
NIC: tunnelNIC,
})
}
Because the client operates entirely in userspace, it never requires elevated privileges for raw socket creation. The TunnelLinkEndpoint handles all packet encapsulation, making the stack portable across different operating systems and network environments.
Practical Usage Examples
To start a client tunnel from your application, initialize the TCPTunnel with isExitNode set to false:
// Example: start a client tunnel (used by the CLI)
tunnel := NewTCPTunnel(transport, /*isExitNode=*/ false)
// Dial a remote host through the gVisor client stack
conn, err := tunnel.DialTCP("example.com:80")
if err != nil {
log.Fatalf("dial error: %v", err)
}
defer conn.Close()
// Simple HTTP request over the client-side gVisor stack
fmt.Fprintf(conn, "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
io.Copy(os.Stdout, conn)
For SOCKS5 proxy support, the client listens on the virtual NIC and forwards connections through the tunnel:
// Example: client-side SOCKS5 listener (used by the CLI)
listener, _ := tunnel.ListenTCP(1080) // listen on virtual NIC
for {
client, _ := listener.Accept()
go func(c net.Conn) {
// handle SOCKS5 handshake, then forward to remote via tunnel.DialTCP
}(client)
}
Summary
- Primary configuration file:
tunnel/tunnel.gocontains thesetupClientmethod that configures the gVisor stack. - Fixed client address: The stack uses
10.10.10.2/24to ensure subnet compatibility with the exit node. - Default routing: All IPv4 traffic routes through the virtual NIC using
header.IPv4EmptySubnet. - Transport integration: The
TunnelLinkEndpointbridges the gVisor stack with OpenFlux's transport layer, eliminating the need for raw sockets. - SOCKS5 compatibility: The client stack supports local SOCKS5 servers that transparently tunnel connections through the gVisor implementation.
Frequently Asked Questions
What IP address does OpenFlux assign to the client gVisor stack?
OpenFlux assigns the fixed address 10.10.10.2/24 to the client stack. This address is hardcoded in tunnel/tunnel.go within the setupClient method and places the client on the same /24 subnet as the exit node (10.10.10.0/24).
How does the client stack route traffic to the exit node?
The client installs a default route covering header.IPv4EmptySubnet (0.0.0.0/0) via gvisorStack.AddRoute, pointing to the virtual NIC. This ensures all outbound packets flow through the TunnelLinkEndpoint, which encapsulates them for transmission through the selected transport layer to the exit node.
What is the difference between client mode and exit node mode in OpenFlux?
Client mode creates a userspace TCP/IP stack with a single virtual NIC for forwarding connections through a SOCKS5 proxy, while exit node mode typically requires raw socket access to handle incoming traffic from the transport layer. The client configuration in setupClient does not create raw sockets or secondary NICs, unlike the server-side initialization.
Which source files are essential to the client stack configuration?
The primary files involved are tunnel/tunnel.go (containing setupClient and stack initialization), tunnel/endpoint.go (implementing TunnelLinkEndpoint), and transport/transport.go (defining the interface used to send and receive raw packets). The CLI entry point in main.go parses the --client flag to trigger this configuration path.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →