How OpenFlux Handles Cross-Platform Raw Sockets for Exit Nodes

OpenFlux implements raw socket support exclusively on Linux through platform-specific source files with build tags, while Windows and macOS use stub implementations that automatically fall back to proxy mode.

OpenFlux is an open-source networking tunnel that demonstrates how to handle cross-platform raw sockets for exit nodes using Go's build constraints. The architecture cleanly separates Linux-specific raw IP socket handling from portable proxy-based operation, ensuring functional exit node behavior across all supported operating systems while maximizing performance on Linux through kernel-level packet control.

Platform-Specific Raw Socket Implementations

OpenFlux uses Go build tags (//go:build linux, //go:build windows, //go:build darwin) to compile platform-specific implementations of the RawSocketEndpoint interface. This design isolates privileged raw socket operations to Linux only, where root access and IP_HDRINCL capabilities are available.

Linux Raw Socket Support

In tunnel/rawsocket_linux.go, the NewRawSocketEndpoint function creates a pair of raw sockets using syscall.Socket with syscall.SOCK_RAW. The implementation enables IP_HDRINCL to allow manual IP header construction, then spawns a background readLoop goroutine for packet ingress.

The readLoop receives inbound IP packets via syscall.Recvfrom, rewrites the destination IP address to the exit node's local egress address (e.g., 10.10.10.2), recomputes IP and transport-layer checksums, and forwards the packet into the gVisor stack through a callback registered via SetTransportSender. For egress traffic, WritePackets rewrites source addresses to the local egress IP, recalculates checksums, and transmits via syscall.Sendto.

Windows and macOS Stub Implementations

On non-Linux platforms, tunnel/rawsocket_windows.go and tunnel/rawsocket_darwin.go contain stub implementations. The NewRawSocketEndpoint function in these files immediately returns an error stating that raw socket mode is unsupported, while all other interface methods are no-ops. This compile-time isolation ensures the binary builds successfully on Windows and macOS without requiring platform-specific socket privileges.

Exit Mode Selection and Architecture

The ExitMode type defined in tunnel/tunnel.go enumerates the two operational modes: ExitModeProxy (default, cross-platform) and ExitModeRaw (Linux only). The command-line flag --mode is parsed by ParseExitMode, which maps the string "raw" to ExitModeRaw.

When initializing a TCPTunnel with isExitNode set to true, the NewTCPTunnelMode function in tunnel/tunnel.go selects between setupExitNodeRaw and setupExitNodeProxy based on the parsed exit mode.

Setting Up a Raw Socket Exit Node

The setupExitNodeRaw function in tunnel/tunnel.go orchestrates the Linux-specific raw socket initialization through the following sequence:

  1. Endpoint instantiation: Calls NewRawSocketEndpoint to create the raw socket pair.
  2. Transport wiring: Sets the endpoint's transport-sender callback to tunnel.transport.Send using SetTransportSender.
  3. Dual-NIC configuration: Creates a second Network Interface Card (NIC ID 2) in the gVisor stack bound to the raw endpoint, while NIC ID 1 remains dedicated to the internal tunnel link.
  4. Protocol address assignment: Obtains the local egress IP via getLocalIP() and adds it to the stack as a protocol address with a /24 subnet (IPv4).
  5. Routing configuration: Enables IPv4 forwarding and installs routes directing Internet-bound traffic through NIC 2 (raw socket) while maintaining the internal tunnel subnet (10.10.10.0/24) on NIC 1.

Fallback to Proxy Mode

If NewRawSocketEndpoint fails due to insufficient privileges or platform incompatibility, setupExitNodeRaw catches the error, logs a warning, and automatically invokes setupExitNodeProxy. This fallback mechanism guarantees that exit nodes function correctly on Windows, macOS, or unprivileged Linux environments by using standard Go net.Dial and TCP proxying instead of raw sockets.

In proxy mode, the gVisor stack terminates TCP connections normally, and outgoing traffic is proxied through conventional socket operations without kernel-level packet manipulation.

Code Examples

Creating a Raw Socket Exit Node (Linux)

// Initialize a tunnel with raw socket mode on Linux
tunnel := tunnel.NewTCPTunnelMode(myTransport, true, tunnel.ExitModeRaw)
defer tunnel.Close()

// Dial through the exit node
conn, err := tunnel.DialTCP("93.184.216.34:80")
if err != nil {
    log.Fatalf("dial failed: %v", err)
}
defer conn.Close()

Automatic Fallback on Unsupported Platforms

// On Windows or macOS, the same code automatically switches to proxy mode
tunnel := tunnel.NewTCPTunnelMode(myTransport, true, tunnel.ExitModeRaw)
fmt.Println("Running in mode:", tunnel.ExitMode) // prints "proxy"

Raw Packet Processing Loop

The background packet processing in tunnel/rawsocket_linux.go demonstrates the IP rewriting logic:

func (e *RawSocketEndpoint) readLoop() {
    buf := make([]byte, 65535)
    for {
        n, _, err := syscall.Recvfrom(e.recvFd, buf, 0)
        if err != nil {
            continue
        }
        pktCopy := make([]byte, n)
        copy(pktCopy, buf[:n])
        
        // Rewrite destination to local egress IP
        copy(pktCopy[16:20], []byte{10, 10, 10, 2})
        
        // Recompute checksums and forward to stack
        // ...
        e.sendToTransport(pktCopy)
    }
}

Summary

  • OpenFlux implements raw sockets exclusively on Linux through build-tagged files (rawsocket_linux.go), while Windows and macOS use stubs (rawsocket_windows.go, rawsocket_darwin.go).
  • The ExitMode enum in tunnel/tunnel.go selects between raw mode (Linux-only) and proxy mode (universal).
  • Raw mode creates dual-NIC gVisor stacks where NIC 2 handles raw IP packets and NIC 1 manages the tunnel link, enabling true IP-level SNAT.
  • Automatic fallback to proxy mode ensures cross-platform compatibility when raw socket creation fails or is unsupported.
  • Packet processing includes IP header rewriting, checksum recalculation, and integration with the gVisor network stack via transport callbacks.

Frequently Asked Questions

Why does OpenFlux only support raw sockets on Linux?

Raw IP sockets require IP_HDRINCL capability and root privileges to create sockets with syscall.SOCK_RAW. While Linux exposes these primitives directly, Windows and macOS impose additional restrictions and different APIs that would require platform-specific kernel drivers or complex workarounds. OpenFlux prioritizes a clean, secure implementation that uses standard Go networking on non-Linux platforms.

What happens if I try to use raw mode on Windows or macOS?

On Windows and macOS, the stub implementations in rawsocket_windows.go and rawsocket_darwin.go cause NewRawSocketEndpoint to return an error. The tunnel initialization code catches this error and automatically falls back to setupExitNodeProxy, allowing the exit node to operate using standard TCP proxying without raw socket privileges.

How does OpenFlux handle packet rewriting in raw socket mode?

The readLoop in rawsocket_linux.go receives raw IP packets from the kernel, validates them, rewrites the destination IP address to the local egress interface (e.g., 10.10.10.2), recalculates the IP and TCP/UDP checksums, and injects the modified packet into the gVisor stack. For outbound traffic, WritePackets rewrites the source IP to the public egress address before transmission.

Can I force proxy mode even on Linux?

Yes. You can explicitly set the exit mode to ExitModeProxy when calling NewTCPTunnelMode, or omit the --mode raw command-line flag to use the default proxy mode. This is useful when running without root privileges or when the additional overhead of raw socket handling is unnecessary for your use case.

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 →