mirror of
https://github.com/fosrl/olm.git
synced 2026-02-12 07:56:44 +00:00
187 lines
7.4 KiB
Markdown
187 lines
7.4 KiB
Markdown
# 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
|
|
|
|
1. **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
|
|
|
|
2. **DNSProxy** (`olm/dns_proxy.go`)
|
|
- Uses gvisor netstack to handle DNS protocol processing
|
|
- Listens on `10.30.30.30:53` within 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
|
|
|
|
1. **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
|
|
|
|
2. **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
|
|
|
|
3. **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`:
|
|
|
|
```bash
|
|
# 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):
|
|
|
|
1. Create a new service similar to `DNSProxy`
|
|
2. Register a filter rule with `filteredDev.AddRule()`
|
|
3. Process packets in your handler
|
|
4. Write responses back to the TUN device
|
|
|
|
Example:
|
|
|
|
```go
|
|
// 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:
|
|
|
|
```go
|
|
// 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:
|
|
1. Responses should go to the host, not through WireGuard
|
|
2. Avoids infinite loops (response → filter → DNS proxy → ...)
|
|
3. Better performance (one less layer)
|
|
|
|
### Thread Safety
|
|
|
|
- `FilteredDevice` uses RWMutex for rule access (read-heavy workload)
|
|
- `DNSProxy` goroutines 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:
|
|
1. DNS caching to reduce upstream queries
|
|
2. DNS-over-HTTPS (DoH) support
|
|
3. Custom DNS filtering/blocking
|
|
4. Metrics and monitoring
|
|
5. IPv6 support for DNS proxy
|
|
6. Multiple upstream DNS servers with health checking
|
|
7. HTTP/HTTPS proxy on different IPs
|
|
8. SOCKS5 proxy support
|