Files
ctrld/cmd/ctrld_library/netstack
2026-03-19 00:24:35 -04:00
..
2026-03-19 00:24:35 -04:00
2026-03-19 00:24:35 -04:00
2026-03-19 00:24:35 -04:00
2026-03-19 00:24:35 -04:00
2026-03-19 00:24:35 -04:00
2026-03-19 00:24:35 -04:00

Netstack - Full Packet Capture for Mobile VPN

Complete TCP/UDP/DNS packet capture implementation using gVisor netstack for Android and iOS VPN apps.

Overview

This module provides full packet capture capabilities for mobile VPN applications, handling:

  • DNS filtering through ControlD proxy
  • TCP forwarding for HTTP/HTTPS and all TCP traffic
  • UDP forwarding for games, video streaming, VoIP, etc.
  • Socket protection to prevent routing loops on Android/iOS

Architecture

Mobile Apps (Browser, Games, etc)
    ↓
VPN TUN Interface (10.0.0.2/24)
    ↓
PacketHandler (Read/Write/Protect)
    ↓
gVisor Netstack (TCP/IP Stack)
    ├─→ DNS Filter (Port 53)
    │   └─→ ControlD DNS Proxy (localhost:5354)
    ├─→ TCP Forwarder
    │   └─→ net.Dial("tcp") + protect(fd)
    └─→ UDP Forwarder
        └─→ net.Dial("udp") + protect(fd)
    ↓
Real Network (WiFi/Cellular) - Protected Sockets

Key Components

1. DNS Filter (dns_filter.go)

  • Detects DNS packets (UDP port 53)
  • Extracts DNS query payload
  • Sends to DNS bridge
  • Builds DNS response packets

2. DNS Bridge (dns_bridge.go)

  • Transaction ID tracking
  • Query/response matching
  • 5-second timeout per query
  • Channel-based communication

3. TCP Forwarder (tcp_forwarder.go)

  • Uses gVisor's tcp.NewForwarder()
  • Converts gVisor endpoints to Go net.Conn
  • Dials regular TCP sockets (no root required)
  • Protects sockets using VpnService.protect() callback
  • Bidirectional io.Copy() for data forwarding

4. UDP Forwarder (udp_forwarder.go)

  • Uses gVisor's udp.NewForwarder()
  • Per-session connection tracking
  • Dials regular UDP sockets (no root required)
  • Protected sockets prevent routing loops
  • 60-second idle timeout with automatic cleanup

5. Packet Handler (packet_handler.go)

  • Interface for reading/writing raw IP packets
  • Mobile platforms implement:
    • ReadPacket() - Read from TUN file descriptor
    • WritePacket() - Write to TUN file descriptor
    • ProtectSocket(fd) - Protect socket from VPN routing
    • Close() - Clean up resources

6. Netstack Controller (netstack.go)

  • Manages gVisor stack lifecycle
  • Coordinates DNS filter and TCP/UDP forwarders
  • Filters outbound packets (source=10.0.0.x)
  • Drops return packets (handled by forwarders)

Critical Design Decisions

Socket Protection

Why It's Critical: Without socket protection, outbound connections would route back through the VPN, creating infinite loops:

Bad (without protect):
App → VPN → TCP Forwarder → net.Dial() → VPN → TCP Forwarder → LOOP!

Good (with protect):
App → VPN → TCP Forwarder → net.Dial() → [PROTECTED] → WiFi → Internet ✅

Implementation:

// Protect socket BEFORE connect() is called
dialer.Control = func(network, address string, c syscall.RawConn) error {
    return c.Control(func(fd uintptr) {
        protectSocket(int(fd))  // Android: VpnService.protect()
    })
}

All Protected Sockets:

  1. TCP forwarder sockets (user traffic)
  2. UDP forwarder sockets (user traffic)
  3. ControlD API HTTP sockets (api.controld.com)
  4. DoH upstream sockets (freedns.controld.com)

Outbound vs Return Packets

Outbound packets (10.0.0.x → Internet):

  • Source IP: 10.0.0.x
  • Injected into gVisor netstack
  • Handled by TCP/UDP forwarders

Return packets (Internet → 10.0.0.x):

  • Source IP: NOT 10.0.0.x
  • Dropped by readPackets()
  • Return through forwarder's upstream connection automatically

Address Mapping in gVisor

For inbound connections to the netstack:

  • id.LocalAddress/LocalPort = Destination (where packet is going TO)
  • id.RemoteAddress/RemotePort = Source (where packet is coming FROM)

Therefore, we dial LocalAddress:LocalPort (the destination).

Usage Example (Android)

// In VpnService
val callback = object : PacketAppCallback {
    override fun readPacket(): ByteArray {
        // Read from TUN file descriptor
        val length = inputStream.channel.read(buffer)
        return packet
    }

    override fun writePacket(packet: ByteArray) {
        // Write to TUN file descriptor
        outputStream.write(packet)
    }

    override fun protectSocket(fd: Long) {
        // CRITICAL: Protect socket from VPN routing
        val success = protect(fd.toInt())  // VpnService.protect()
        if (!success) throw Exception("Failed to protect socket")
    }

    override fun closePacketIO() {
        inputStream?.close()
        outputStream?.close()
    }

    override fun exit(s: String) { }
    override fun hostname(): String = "android-device"
    override fun lanIp(): String = "10.0.0.2"
    override fun macAddress(): String = "00:00:00:00:00:00"
}

// Create packet capture controller
val controller = Ctrld_library.newPacketCaptureController(callback)

// Start packet capture
controller.startWithPacketCapture(
    callback,
    "your-cd-uid",
    "", "",  // provision ID, custom hostname
    filesDir.absolutePath,
    "doh",   // upstream protocol
    2,       // log level
    "$filesDir/ctrld.log"
)

// Stop when done
controller.stop(false, 0)

Protocol Support

Protocol Support Details
DNS Full Filtered through ControlD proxy
TCP Full All ports, bidirectional forwarding
UDP Full All ports except 53, session tracking
ICMP ⚠️ Partial Basic support (no forwarding yet)
IPv4 Full Complete support
IPv6 Full Complete support

Performance

Metric Value
DNS Timeout 5 seconds
TCP Dial Timeout 30 seconds
UDP Idle Timeout 60 seconds
UDP Cleanup Interval 30 seconds
MTU 1500 bytes
Overhead per TCP connection ~2KB
Overhead per UDP session ~1KB

Requirements

  • Go 1.23+
  • gVisor netstack v0.0.0-20240722211153-64c016c92987
  • For Android: API 24+ (Android 7.0+)
  • For iOS: iOS 12+

No Root Required

This implementation uses regular TCP/UDP sockets instead of raw sockets, making it compatible with non-rooted Android/iOS devices. Socket protection via VpnService.protect() (Android) or NEPacketTunnelFlow (iOS) prevents routing loops.

Files

  • packet_handler.go - Interface for TUN I/O and socket protection
  • netstack.go - Main controller managing gVisor stack
  • dns_filter.go - DNS packet detection and response building
  • dns_bridge.go - DNS query/response bridging
  • tcp_forwarder.go - TCP connection forwarding
  • udp_forwarder.go - UDP packet forwarding with session tracking

License

Same as parent ctrld project.