# How gVisor Handles Network Interfaces in OpenFlux: Virtual NIC Implementation

> Discover how OpenFlux uses gVisor to create virtual network interfaces, bridging encrypted tunnels to the internet with programmable routing for exit nodes and clients.

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

---

**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`](https://github.com/p1neappleXpress/OpenFlux/blob/main/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:

```go
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`](https://github.com/p1neappleXpress/OpenFlux/blob/main/tunnel/tunnel.go) at lines 107-109, the stack initializes this interface by binding a `TunnelLinkEndpoint` to NIC ID 1:

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

```

The `TunnelLinkEndpoint`—defined in [`tunnel/endpoint.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/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:

```go
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`](https://github.com/p1neappleXpress/OpenFlux/blob/main/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`:

```go
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`](https://github.com/p1neappleXpress/OpenFlux/blob/main/tunnel/tunnel.go), the code enables **promiscuous mode** and **spoofing** on the tunnel NIC:

```go
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`](https://github.com/p1neappleXpress/OpenFlux/blob/main/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.