7.4 KiB
Virtual DNS Proxy Implementation
Overview
This implementation adds a high-performance virtual DNS proxy that intercepts DNS queries destined for 10.30.30.30:53 before they reach the WireGuard tunnel. The proxy processes DNS queries using a gvisor netstack and forwards them to upstream DNS servers, bypassing the VPN tunnel entirely.
Architecture
Components
-
FilteredDevice (
olm/device_filter.go)- Wraps the TUN device with packet filtering capabilities
- Provides fast packet inspection without deep packet processing
- Supports multiple filtering rules that can be added/removed dynamically
- Optimized for performance - only extracts destination IP on fast path
-
DNSProxy (
olm/dns_proxy.go)- Uses gvisor netstack to handle DNS protocol processing
- Listens on
10.30.30.30:53within its own network stack - Forwards queries to Google DNS (8.8.8.8, 8.8.4.4)
- Writes responses directly back to the TUN device, bypassing WireGuard
Packet Flow
┌─────────────────────────────────────────────────────────────┐
│ Application │
└──────────────────────┬──────────────────────────────────────┘
│ DNS Query to 10.30.30.30:53
▼
┌─────────────────────────────────────────────────────────────┐
│ TUN Interface │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ FilteredDevice (Read) │
│ - Fast IP extraction │
│ - Rule matching (10.30.30.30) │
└──────────────┬──────────────────────────────────────────────┘
│
┌──────────┴──────────┐
│ │
▼ ▼
┌─────────┐ ┌─────────────────────────┐
│DNS Proxy│ │ WireGuard Device │
│Netstack │ │ (other traffic) │
└────┬────┘ └─────────────────────────┘
│
│ Forward to 8.8.8.8
▼
┌─────────────┐
│ Internet │
│ (Direct) │
└──────┬──────┘
│ DNS Response
▼
┌─────────────────────────────────────────────────────────────┐
│ DNSProxy writes directly to TUN │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Application │
└─────────────────────────────────────────────────────────────┘
Performance Considerations
Fast Path Optimization
-
Minimal Packet Inspection
- Only extracts destination IP (bytes 16-19 for IPv4, 24-39 for IPv6)
- No deep packet inspection unless packet matches a rule
- Zero-copy operations where possible
-
Rule Matching
- Simple IP comparison (not prefix matching for rules)
- Linear scan of rules (fast for small number of rules)
- Read-lock only for rule access
-
Packet Processing
- Filtered packets are removed from the slice in-place
- Non-matching packets passed through with minimal overhead
- No memory allocation for packets that don't match rules
Memory Efficiency
- Packet copies are only made when absolutely necessary
- gvisor netstack uses buffer pooling internally
- DNS proxy uses a separate goroutine for response handling
Usage
Configuration
The DNS proxy is automatically started when the tunnel is created. By default:
- DNS proxy IP:
10.30.30.30 - DNS port:
53 - Upstream DNS:
8.8.8.8(primary),8.8.4.4(fallback)
Testing
To test the DNS proxy, configure your DNS settings to use 10.30.30.30:
# Using dig
dig @10.30.30.30 google.com
# Using nslookup
nslookup google.com 10.30.30.30
Extensibility
The FilteredDevice architecture is designed to be extensible:
Adding New Services
To add a new service (e.g., HTTP proxy on 10.30.30.31):
- Create a new service similar to
DNSProxy - Register a filter rule with
filteredDev.AddRule() - Process packets in your handler
- Write responses back to the TUN device
Example:
// In your service
func (s *MyService) handlePacket(packet []byte) bool {
// Parse packet
// Process request
// Write response to TUN device
s.tunDevice.Write([][]byte{response}, 0)
return true // Drop from normal path
}
// During initialization
filteredDev.AddRule(myServiceIP, myService.handlePacket)
Adding Filtering Rules
Rules can be added/removed dynamically:
// Add a rule
filteredDev.AddRule(netip.MustParseAddr("10.30.30.40"), handleSpecialIP)
// Remove a rule
filteredDev.RemoveRule(netip.MustParseAddr("10.30.30.40"))
Implementation Details
Why Direct TUN Write?
The DNS proxy writes responses directly back to the TUN device instead of going through the filter because:
- Responses should go to the host, not through WireGuard
- Avoids infinite loops (response → filter → DNS proxy → ...)
- Better performance (one less layer)
Thread Safety
FilteredDeviceuses RWMutex for rule access (read-heavy workload)DNSProxygoroutines are properly synchronized- TUN device write operations are thread-safe
Error Handling
- Failed DNS queries fall back to secondary DNS server
- Malformed packets are logged but don't crash the proxy
- Context cancellation ensures clean shutdown
Future Enhancements
Potential improvements:
- DNS caching to reduce upstream queries
- DNS-over-HTTPS (DoH) support
- Custom DNS filtering/blocking
- Metrics and monitoring
- IPv6 support for DNS proxy
- Multiple upstream DNS servers with health checking
- HTTP/HTTPS proxy on different IPs
- SOCKS5 proxy support