mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-05-27 12:52:27 +02:00
223 lines
6.6 KiB
Markdown
223 lines
6.6 KiB
Markdown
# 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:**
|
|
```go
|
|
// 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)
|
|
|
|
```kotlin
|
|
// 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.
|