How gVisor Handles Network Interfaces in OpenFlux: Virtual NIC Implementation

OpenFlux leverages gVisor's userspace TCP/IP stack to create isolated virtual network interfaces that bridge encrypted tunnels to the internet, implementing a dual-NIC architecture with programmable routing for exit nodes and clients.

The p1neappleXpress/OpenFlux project implements a TCP tunnel using gVisor's userspace networking stack to establish a fully virtualized network layer. By programmatically managing Network Interface Cards (NICs), routing tables, and protocol handlers within the gVisor framework, OpenFlux creates secure communication channels without requiring modifications to the host's native network configuration. This architecture supports two distinct operating modes—proxy (portable) and raw (Linux-only)—each leveraging virtual interfaces differently to balance compatibility against performance.

Initializing the gVisor Stack

The foundation of OpenFlux's networking begins in tunnel/tunnel.go with the instantiation of a new gVisor stack configured for IPv4 and TCP protocols. At lines 92-95, the implementation calls stack.New() with protocol factories for the network and transport layers:

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

Immediately following initialization, the code tunes TCP performance by invoking SetTCPBuffers(t.gvisorStack) at lines 97-98, optimizing send and receive buffer sizes for the virtualized environment.

Creating Virtual Network Interfaces (NICs)

OpenFlux dynamically creates logical NICs based on whether the instance operates as a client or an exit node. The architecture separates tunnel traffic from internet-bound traffic through distinct virtual interfaces.

The Tunnel NIC (NIC 1)

Every OpenFlux instance creates a primary virtual interface representing the tunnel endpoint. In tunnel/tunnel.go at lines 107-109, the stack initializes this interface by binding a TunnelLinkEndpoint to NIC ID 1:

tunnelNIC := tcpip.NICID(1)
t.gvisorStack.CreateNIC(tunnelNIC, tunnelEP)

The TunnelLinkEndpoint—defined in tunnel/endpoint.go—serves as the virtual link layer that injects and receives raw packets between the gVisor stack and the transport layer abstraction.

The Internet NIC (NIC 2) for Raw Mode

When operating as an exit node in raw mode (Linux only), OpenFlux creates a second virtual interface to interface directly with the host's physical network stack. At lines 210-212, the code instantiates a RawSocketEndpoint and attaches it as NIC 2:

rawEP, _ := NewRawSocketEndpoint(tcpip.NICID(2))
t.gvisorStack.CreateNIC(tcpip.NICID(2), rawEP)

This second NIC, implemented in the platform-specific tunnel/rawsocket_*.go files, enables the exit node to bypass gVisor's forwarding logic and inject packets directly into the transport layer for high-performance networking.

Configuring IP Addresses and Routing Tables

Address assignment and route configuration differ significantly between client and exit node modes, using gVisor's AddProtocolAddress and AddRoute methods to establish connectivity.

Address Assignment Strategies

For client mode, OpenFlux assigns the static address 10.10.10.2/24 to the tunnel NIC (ID 1) via t.gvisorStack.AddProtocolAddress() at lines 45-53. In exit node raw mode, the system detects the host's real IP address using getLocalIP() and assigns it to the internet NIC (ID 2) at lines 18-25, allowing the virtual stack to participate in the physical network with the host's actual identity.

Route Table Management

Routing logic varies by operational mode to ensure proper packet flow:

  • Proxy mode: Establishes a default route (0.0.0.0/0) directing all outbound traffic through the tunnel NIC. This configuration appears at lines 135-141 in tunnel/tunnel.go using header.IPv4EmptySubnet as the destination.

  • Raw mode: Creates explicit routing entries including a default route via NIC 2 and a specific route for the tunnel subnet (10.10.10.0/24) via NIC 1, configured at lines 226-240.

Packet Forwarding and Transport Integration

OpenFlux leverages gVisor's packet handling mechanisms to move traffic between the virtual stack and physical networks, implementing different strategies for proxy and raw modes.

TCP Forwarding in Proxy Mode

In proxy mode, the exit node uses gVisor's tcp.NewForwarder to intercept incoming TCP connections. At lines 142-144, the code registers the forwarder with the stack via SetTransportProtocolHandler:

fwd := tcp.NewForwarder(t.gvisorStack, defaultWndSize, maxConnAttempts, t.handleExitTCP)
t.gvisorStack.SetTransportProtocolHandler(tcp.ProtocolNumber, fwd.HandlePacket)

The handleExitTCP callback establishes connections to the external internet using standard net.Dial, effectively NATing traffic from the tunnel through the host's network interface.

Raw Socket Packet Injection

For raw mode operation, the RawSocketEndpoint in tunnel/rawsocket_*.go implements direct packet injection. Outbound packets bypass the gVisor forwarding layer and enter the transport layer directly through t.transport.Send, while inbound packets from the raw socket are fed back into the stack via InjectInbound. The endpoint initialization at lines 2-8 establishes this transport sender linkage through rawEP.SetTransportSender.

Enabling Advanced NIC Capabilities

To support flexible packet crafting required for tunnel operations, OpenFlux configures advanced NIC capabilities immediately after creation. At lines 135-136 in tunnel/tunnel.go, the code enables promiscuous mode and spoofing on the tunnel NIC:

t.gvisorStack.SetPromiscuousMode(tunnelNIC, true)
t.gvisorStack.SetSpoofing(tunnelNIC, true)

These settings allow the virtual stack to receive all packets regardless of destination MAC address and to transmit packets with arbitrary source addresses, essential for implementing the virtual tunnel overlay.

Summary

  • OpenFlux initializes a dedicated gVisor stack in tunnel/tunnel.go (lines 92-95) with IPv4 and TCP protocol factories, immediately tuning buffer sizes via SetTCPBuffers.
  • The architecture implements a dual-NIC design: NIC 1 (TunnelLinkEndpoint) handles tunnel traffic for all modes, while NIC 2 (RawSocketEndpoint) provides direct network access in raw mode only.
  • Address assignment adapts to topology: clients receive 10.10.10.2/24 on the tunnel interface, while exit nodes in raw mode bind the host's actual IP to the internet interface.
  • Routing tables are programmatically configured using AddRoute, with proxy mode using a default tunnel route and raw mode maintaining separate routes for the tunnel subnet and default gateway.
  • Promiscuous mode and address spoofing are explicitly enabled on the tunnel NIC to support arbitrary packet generation, while TCP forwarding utilizes tcp.NewForwarder for proxy mode connections.

Frequently Asked Questions

What is the purpose of having two NICs in OpenFlux?

OpenFlux uses two NICs to separate concerns between tunnel communication and internet access. NIC 1 (the tunnel interface) always exists and handles encrypted traffic between OpenFlux nodes. NIC 2 (the internet interface) only exists in raw mode on exit nodes, providing direct access to the physical network through raw sockets for high-performance packet forwarding without kernel networking overhead.

How does address assignment differ between client and exit node modes?

In client mode, OpenFlux assigns the static private address 10.10.10.2/24 to the tunnel NIC. For exit nodes running in raw mode, the system detects the host's actual IP address via getLocalIP() and assigns it to NIC 2, enabling the virtual stack to present the host's real network identity to external services while maintaining the tunnel subnet on NIC 1.

Why does OpenFlux enable promiscuous mode and spoofing on the tunnel NIC?

The tunnel NIC enables promiscuous mode to receive all packets regardless of destination MAC address, and spoofing to transmit packets with arbitrary source IP addresses. These capabilities are essential because the virtual network stack must craft packets that appear to originate from or be destined for the tunnel subnet (10.10.10.0/24) rather than the host's physical interfaces.

Where does the raw socket endpoint implementation reside in the OpenFlux codebase?

The raw socket endpoint implementation resides in the platform-specific files tunnel/rawsocket_*.go (with Linux-specific implementation for raw mode and stubs for other operating systems). This endpoint bridges the gVisor stack directly to the host's transport layer, enabling the performance optimizations available exclusively to exit nodes running on Linux.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →