mirror of
https://github.com/fosrl/newt.git
synced 2026-02-08 05:56:40 +00:00
Using 2 nics not working
This commit is contained in:
236
IMPLEMENTATION.md
Normal file
236
IMPLEMENTATION.md
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
# TCP/UDP Proxying Implementation Summary
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This implementation adds transparent TCP and UDP connection proxying to newt's netstack2 package, inspired by tun2socks. Traffic entering through the WireGuard tunnel is terminated in netstack and automatically proxied to the actual target addresses.
|
||||||
|
|
||||||
|
## Key Changes
|
||||||
|
|
||||||
|
### 1. New File: `netstack2/handlers.go`
|
||||||
|
|
||||||
|
**Purpose**: Contains TCP and UDP handler implementations that proxy connections.
|
||||||
|
|
||||||
|
**Key Components**:
|
||||||
|
|
||||||
|
- `TCPHandler`: Manages TCP connection forwarding
|
||||||
|
- Installs TCP forwarder on netstack
|
||||||
|
- Performs TCP three-way handshake with clients
|
||||||
|
- Dials actual target addresses
|
||||||
|
- Bidirectionally copies data with proper half-close handling
|
||||||
|
|
||||||
|
- `UDPHandler`: Manages UDP packet forwarding
|
||||||
|
- Installs UDP forwarder on netstack
|
||||||
|
- Creates UDP endpoints for clients
|
||||||
|
- Forwards packets to actual targets
|
||||||
|
- Handles session timeouts
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
- Configurable timeouts (5s TCP connect, 60s TCP half-close, 60s UDP session)
|
||||||
|
- TCP keepalive support (60s idle, 30s interval, 9 probes)
|
||||||
|
- Optimized buffer sizes (32KB for TCP, 64KB for UDP)
|
||||||
|
- Proper error handling and connection cleanup
|
||||||
|
|
||||||
|
### 2. Modified File: `netstack2/tun.go`
|
||||||
|
|
||||||
|
**Changes**:
|
||||||
|
|
||||||
|
1. Added `tcpHandler` and `udpHandler` fields to `netTun` struct
|
||||||
|
2. Added `NetTunOptions` struct for configuration:
|
||||||
|
```go
|
||||||
|
type NetTunOptions struct {
|
||||||
|
EnableTCPProxy bool
|
||||||
|
EnableUDPProxy bool
|
||||||
|
}
|
||||||
|
```
|
||||||
|
3. Added `CreateNetTUNWithOptions()` function for explicit proxying control
|
||||||
|
4. Modified existing `CreateNetTUN()` to call `CreateNetTUNWithOptions()` with proxying disabled (backward compatible)
|
||||||
|
5. Added `EnableTCPProxy()` and `EnableUDPProxy()` methods on `*Net` for runtime activation
|
||||||
|
|
||||||
|
### 3. Documentation: `netstack2/README.md`
|
||||||
|
|
||||||
|
Comprehensive documentation covering:
|
||||||
|
- Architecture overview
|
||||||
|
- Usage examples (3 different approaches)
|
||||||
|
- Configuration parameters
|
||||||
|
- Performance considerations
|
||||||
|
- Limitations and debugging tips
|
||||||
|
|
||||||
|
### 4. Example: `examples/netstack-proxying/main.go`
|
||||||
|
|
||||||
|
Runnable examples demonstrating:
|
||||||
|
- Creating netstack with proxying enabled
|
||||||
|
- Enabling proxying after creation
|
||||||
|
- Standard netstack usage (no proxying)
|
||||||
|
|
||||||
|
## Usage Patterns
|
||||||
|
|
||||||
|
### Pattern 1: Enable During Creation
|
||||||
|
```go
|
||||||
|
tun, tnet, err := netstack2.CreateNetTUNWithOptions(
|
||||||
|
localAddresses, dnsServers, mtu,
|
||||||
|
netstack2.NetTunOptions{
|
||||||
|
EnableTCPProxy: true,
|
||||||
|
EnableUDPProxy: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 2: Enable After Creation
|
||||||
|
```go
|
||||||
|
tun, tnet, err := netstack2.CreateNetTUN(localAddresses, dnsServers, mtu)
|
||||||
|
tnet.EnableTCPProxy()
|
||||||
|
tnet.EnableUDPProxy()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 3: No Proxying (Backward Compatible)
|
||||||
|
```go
|
||||||
|
tun, tnet, err := netstack2.CreateNetTUN(localAddresses, dnsServers, mtu)
|
||||||
|
// Use standard tnet.DialTCP(), tnet.DialUDP() methods
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### TCP Flow:
|
||||||
|
1. Client sends TCP SYN to target address through WireGuard tunnel
|
||||||
|
2. Packet arrives at netstack
|
||||||
|
3. TCP forwarder intercepts and completes three-way handshake
|
||||||
|
4. Handler dials actual target address
|
||||||
|
5. Data copied bidirectionally until connection closes
|
||||||
|
6. Proper TCP half-close and FIN handling
|
||||||
|
|
||||||
|
### UDP Flow:
|
||||||
|
1. Client sends UDP packet to target address through WireGuard tunnel
|
||||||
|
2. Packet arrives at netstack
|
||||||
|
3. UDP forwarder creates endpoint for client
|
||||||
|
4. Handler creates UDP connection to actual target
|
||||||
|
5. Packets forwarded bidirectionally
|
||||||
|
6. Session closes after 60s timeout or explicit close
|
||||||
|
|
||||||
|
## Key Differences from tun2socks
|
||||||
|
|
||||||
|
| Aspect | tun2socks | newt |
|
||||||
|
|--------|-----------|------|
|
||||||
|
| Target | SOCKS proxy | Direct target addresses |
|
||||||
|
| Use Case | Route to proxy | Direct network access |
|
||||||
|
| Architecture | Proxy adapter | Direct dial |
|
||||||
|
| Complexity | Higher (SOCKS protocol) | Lower (direct TCP/UDP) |
|
||||||
|
|
||||||
|
## Integration with WireGuard
|
||||||
|
|
||||||
|
The handlers integrate seamlessly with existing WireGuard code:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// In wgnetstack.go:
|
||||||
|
func (s *WireGuardService) ensureWireguardInterface(wgconfig WgConfig) error {
|
||||||
|
// Create netstack with proxying
|
||||||
|
s.tun, s.tnet, err = netstack2.CreateNetTUNWithOptions(
|
||||||
|
[]netip.Addr{tunnelIP},
|
||||||
|
s.dns,
|
||||||
|
s.mtu,
|
||||||
|
netstack2.NetTunOptions{
|
||||||
|
EnableTCPProxy: true,
|
||||||
|
EnableUDPProxy: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rest of WireGuard setup...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Characteristics
|
||||||
|
|
||||||
|
- **Memory**: ~64KB per active connection (buffer space)
|
||||||
|
- **Goroutines**: 2 per connection (bidirectional copying)
|
||||||
|
- **Latency**: Minimal overhead (single netstack hop + direct dial)
|
||||||
|
- **Throughput**: Limited by buffer size and network bandwidth
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
To test the implementation:
|
||||||
|
|
||||||
|
1. Build the example:
|
||||||
|
```bash
|
||||||
|
cd /home/owen/fossorial/newt
|
||||||
|
go build -o /tmp/netstack-example examples/netstack-proxying/main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run the example:
|
||||||
|
```bash
|
||||||
|
/tmp/netstack-example
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Integration test with WireGuard:
|
||||||
|
- Enable proxying in wgnetstack
|
||||||
|
- Send TCP/UDP traffic through tunnel
|
||||||
|
- Verify connections reach actual targets
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
- **TCP**: Failed connections result in RST packets to client
|
||||||
|
- **UDP**: Failed sends are silently dropped (standard UDP behavior)
|
||||||
|
- **Timeouts**: Configurable per protocol
|
||||||
|
- **Resources**: Proper cleanup on connection close
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
1. **No Filtering**: All connections are proxied (no allow-list)
|
||||||
|
2. **Direct Access**: Assumes network access to all targets
|
||||||
|
3. **Resource Limits**: No per-connection rate limiting
|
||||||
|
4. **Logging**: No built-in connection logging (can be added)
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
Potential improvements:
|
||||||
|
1. Connection filtering/allow-listing
|
||||||
|
2. Per-connection rate limiting
|
||||||
|
3. Connection statistics and monitoring
|
||||||
|
4. Dynamic timeout configuration
|
||||||
|
5. Connection pooling
|
||||||
|
6. Logging and metrics
|
||||||
|
7. Connection replay prevention
|
||||||
|
|
||||||
|
## Backward Compatibility
|
||||||
|
|
||||||
|
✅ **Fully backward compatible**: Existing code using `CreateNetTUN()` continues to work without any changes. Proxying is opt-in via `CreateNetTUNWithOptions()` or `EnableTCPProxy()`/`EnableUDPProxy()`.
|
||||||
|
|
||||||
|
## Files Modified/Created
|
||||||
|
|
||||||
|
**Created**:
|
||||||
|
- `netstack2/handlers.go` (286 lines)
|
||||||
|
- `netstack2/README.md` (documentation)
|
||||||
|
- `examples/netstack-proxying/main.go` (example code)
|
||||||
|
- `IMPLEMENTATION.md` (this file)
|
||||||
|
|
||||||
|
**Modified**:
|
||||||
|
- `netstack2/tun.go` (added 40 lines)
|
||||||
|
- Added handler fields to `netTun` struct
|
||||||
|
- Added `NetTunOptions` type
|
||||||
|
- Added `CreateNetTUNWithOptions()` function
|
||||||
|
- Added `EnableTCPProxy()` and `EnableUDPProxy()` methods
|
||||||
|
- Modified `CreateNetTUN()` to call new function with disabled options
|
||||||
|
|
||||||
|
## Build Verification
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/owen/fossorial/newt
|
||||||
|
go build ./netstack2/
|
||||||
|
# Success - no compilation errors
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
To use this in newt:
|
||||||
|
|
||||||
|
1. **Test in isolation**: Run the example program to verify basic functionality
|
||||||
|
2. **Integrate with WireGuard**: Modify `wgnetstack.go` to use `CreateNetTUNWithOptions()`
|
||||||
|
3. **Add configuration**: Make proxying configurable via newt's config file
|
||||||
|
4. **Add logging**: Integrate with newt's logger for connection tracking
|
||||||
|
5. **Monitor performance**: Add metrics for connection count, throughput, errors
|
||||||
|
6. **Add tests**: Create unit and integration tests
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- **tun2socks**: https://github.com/xjasonlyu/tun2socks
|
||||||
|
- Referenced files: `core/tcp.go`, `core/udp.go`, `tunnel/tcp.go`, `tunnel/udp.go`
|
||||||
|
- **gVisor netstack**: https://gvisor.dev/docs/user_guide/networking/
|
||||||
|
- **WireGuard**: https://www.wireguard.com/
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/fosrl/newt/netstack2"
|
"github.com/fosrl/newt/netstack2"
|
||||||
"github.com/fosrl/newt/proxy"
|
"github.com/fosrl/newt/proxy"
|
||||||
"github.com/fosrl/newt/websocket"
|
"github.com/fosrl/newt/websocket"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
"github.com/fosrl/newt/wgnetstack"
|
"github.com/fosrl/newt/wgnetstack"
|
||||||
"github.com/fosrl/newt/wgtester"
|
"github.com/fosrl/newt/wgtester"
|
||||||
@@ -66,7 +67,7 @@ func setupClientsNetstack(client *websocket.Client, host string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func setDownstreamTNetstack(tnet *netstack2.Net) {
|
func setDownstreamTNetstack(tnet *netstack.Net) {
|
||||||
if wgService != nil {
|
if wgService != nil {
|
||||||
wgService.SetOthertnet(tnet)
|
wgService.SetOthertnet(tnet)
|
||||||
}
|
}
|
||||||
|
|||||||
181
examples/netstack-proxying/main.go
Normal file
181
examples/netstack-proxying/main.go
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
// Example of using netstack2 TCP/UDP proxying with WireGuard
|
||||||
|
//
|
||||||
|
// This example shows how to enable transparent TCP/UDP proxying
|
||||||
|
// through a WireGuard tunnel using netstack.
|
||||||
|
//
|
||||||
|
// Build: go build -o example examples/proxying/main.go
|
||||||
|
// Run: ./example
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/fosrl/newt/netstack2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("Netstack2 TCP/UDP Proxying Examples")
|
||||||
|
fmt.Println("====================================\n")
|
||||||
|
|
||||||
|
// Example 1: Recommended - Subnet-based proxying (dual-interface)
|
||||||
|
example1()
|
||||||
|
|
||||||
|
// Example 2: Single interface with proxying (may conflict with WireGuard)
|
||||||
|
example2()
|
||||||
|
|
||||||
|
// Example 3: Enable proxying after creation (single interface)
|
||||||
|
example3()
|
||||||
|
|
||||||
|
// Example 4: Standard netstack without proxying (backward compatible)
|
||||||
|
example4()
|
||||||
|
}
|
||||||
|
|
||||||
|
func example1() {
|
||||||
|
fmt.Println("=== Example 1: Subnet-Based Proxying (Recommended) ===")
|
||||||
|
fmt.Println("This approach avoids conflicts with WireGuard by using a secondary NIC")
|
||||||
|
|
||||||
|
localAddresses := []netip.Addr{
|
||||||
|
netip.MustParseAddr("10.0.0.2"),
|
||||||
|
}
|
||||||
|
dnsServers := []netip.Addr{
|
||||||
|
netip.MustParseAddr("1.1.1.1"),
|
||||||
|
}
|
||||||
|
mtu := 1420
|
||||||
|
|
||||||
|
// Create netstack normally (no proxying on main interface)
|
||||||
|
tun, tnet, err := netstack2.CreateNetTUN(localAddresses, dnsServers, mtu)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create netstack: %v", err)
|
||||||
|
}
|
||||||
|
defer tun.Close()
|
||||||
|
|
||||||
|
fmt.Println("✓ Netstack created (WireGuard uses NIC 1)")
|
||||||
|
|
||||||
|
// Define subnets that should be proxied
|
||||||
|
proxySubnets := []netip.Prefix{
|
||||||
|
netip.MustParsePrefix("192.168.1.0/24"), // Internal services
|
||||||
|
netip.MustParsePrefix("10.20.0.0/16"), // Application subnet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable proxying on a secondary NIC for these subnets
|
||||||
|
err = tnet.EnableProxyOnSubnet(proxySubnets, true, true)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to enable proxy on subnet: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("✓ TCP/UDP proxying enabled on NIC 2 for:")
|
||||||
|
for _, subnet := range proxySubnets {
|
||||||
|
fmt.Printf(" - %s\n", subnet)
|
||||||
|
}
|
||||||
|
fmt.Println("✓ Routing table updated to direct subnet traffic to proxy NIC")
|
||||||
|
fmt.Println(" → WireGuard on NIC 1: handles encryption/decryption")
|
||||||
|
fmt.Println(" → Proxy on NIC 2: handles TCP/UDP termination for specified subnets")
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func example2() {
|
||||||
|
fmt.Println("=== Example 2: Single Interface with Proxying (Not Recommended) ===")
|
||||||
|
fmt.Println("⚠️ May conflict with WireGuard packet handling!")
|
||||||
|
|
||||||
|
localAddresses := []netip.Addr{
|
||||||
|
netip.MustParseAddr("10.0.0.2"),
|
||||||
|
}
|
||||||
|
dnsServers := []netip.Addr{
|
||||||
|
netip.MustParseAddr("1.1.1.1"),
|
||||||
|
}
|
||||||
|
mtu := 1420
|
||||||
|
|
||||||
|
// Create netstack with both TCP and UDP proxying enabled
|
||||||
|
tun, tnet, err := netstack2.CreateNetTUNWithOptions(
|
||||||
|
localAddresses,
|
||||||
|
dnsServers,
|
||||||
|
mtu,
|
||||||
|
netstack2.NetTunOptions{
|
||||||
|
EnableTCPProxy: true,
|
||||||
|
EnableUDPProxy: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create netstack: %v", err)
|
||||||
|
}
|
||||||
|
defer tun.Close()
|
||||||
|
|
||||||
|
fmt.Println("✓ Netstack created with TCP and UDP proxying enabled")
|
||||||
|
fmt.Println(" → Any TCP/UDP traffic through the tunnel will be proxied to actual targets")
|
||||||
|
|
||||||
|
// Now any TCP or UDP connection made through the tunnel will be
|
||||||
|
// automatically terminated in netstack and proxied to the target
|
||||||
|
|
||||||
|
_ = tnet
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func example2() {
|
||||||
|
fmt.Println("=== Example 2: Enable proxying after creation ===")
|
||||||
|
|
||||||
|
localAddresses := []netip.Addr{
|
||||||
|
netip.MustParseAddr("10.0.0.3"),
|
||||||
|
}
|
||||||
|
dnsServers := []netip.Addr{
|
||||||
|
netip.MustParseAddr("1.1.1.1"),
|
||||||
|
}
|
||||||
|
mtu := 1420
|
||||||
|
|
||||||
|
// Create standard netstack first
|
||||||
|
tun, tnet, err := netstack2.CreateNetTUN(localAddresses, dnsServers, mtu)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create netstack: %v", err)
|
||||||
|
}
|
||||||
|
defer tun.Close()
|
||||||
|
|
||||||
|
fmt.Println("✓ Netstack created")
|
||||||
|
|
||||||
|
// Enable TCP proxying
|
||||||
|
if err := tnet.EnableTCPProxy(); err != nil {
|
||||||
|
log.Fatalf("Failed to enable TCP proxy: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Println("✓ TCP proxying enabled")
|
||||||
|
|
||||||
|
// Enable UDP proxying
|
||||||
|
if err := tnet.EnableUDPProxy(); err != nil {
|
||||||
|
log.Fatalf("Failed to enable UDP proxy: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Println("✓ UDP proxying enabled")
|
||||||
|
|
||||||
|
// Calling EnableTCPProxy again is safe (no-op)
|
||||||
|
if err := tnet.EnableTCPProxy(); err != nil {
|
||||||
|
log.Fatalf("Failed to re-enable TCP proxy: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Println("✓ Re-enabling TCP proxying is safe (no-op)")
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func example3() {
|
||||||
|
fmt.Println("=== Example 3: Standard netstack (no proxying) ===")
|
||||||
|
|
||||||
|
localAddresses := []netip.Addr{
|
||||||
|
netip.MustParseAddr("10.0.0.4"),
|
||||||
|
}
|
||||||
|
dnsServers := []netip.Addr{
|
||||||
|
netip.MustParseAddr("1.1.1.1"),
|
||||||
|
}
|
||||||
|
mtu := 1420
|
||||||
|
|
||||||
|
// Use standard CreateNetTUN - backward compatible
|
||||||
|
tun, tnet, err := netstack2.CreateNetTUN(localAddresses, dnsServers, mtu)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create netstack: %v", err)
|
||||||
|
}
|
||||||
|
defer tun.Close()
|
||||||
|
|
||||||
|
fmt.Println("✓ Standard netstack created (no proxying)")
|
||||||
|
fmt.Println(" → Use tnet.DialTCP(), tnet.DialUDP() for manual connections")
|
||||||
|
|
||||||
|
_ = tnet
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
6
main.go
6
main.go
@@ -20,7 +20,6 @@ import (
|
|||||||
"github.com/fosrl/newt/docker"
|
"github.com/fosrl/newt/docker"
|
||||||
"github.com/fosrl/newt/healthcheck"
|
"github.com/fosrl/newt/healthcheck"
|
||||||
"github.com/fosrl/newt/logger"
|
"github.com/fosrl/newt/logger"
|
||||||
"github.com/fosrl/newt/netstack2"
|
|
||||||
"github.com/fosrl/newt/proxy"
|
"github.com/fosrl/newt/proxy"
|
||||||
"github.com/fosrl/newt/updates"
|
"github.com/fosrl/newt/updates"
|
||||||
"github.com/fosrl/newt/websocket"
|
"github.com/fosrl/newt/websocket"
|
||||||
@@ -31,6 +30,7 @@ import (
|
|||||||
"golang.zx2c4.com/wireguard/conn"
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -531,7 +531,7 @@ func main() {
|
|||||||
|
|
||||||
// Create TUN device and network stack
|
// Create TUN device and network stack
|
||||||
var tun tun.Device
|
var tun tun.Device
|
||||||
var tnet *netstack2.Net
|
var tnet *netstack.Net
|
||||||
var dev *device.Device
|
var dev *device.Device
|
||||||
var pm *proxy.ProxyManager
|
var pm *proxy.ProxyManager
|
||||||
var connected bool
|
var connected bool
|
||||||
@@ -637,7 +637,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug(fmtReceivedMsg, msg)
|
logger.Debug(fmtReceivedMsg, msg)
|
||||||
tun, tnet, err = netstack2.CreateNetTUN(
|
tun, tnet, err = netstack.CreateNetTUN(
|
||||||
[]netip.Addr{netip.MustParseAddr(wgData.TunnelIP)},
|
[]netip.Addr{netip.MustParseAddr(wgData.TunnelIP)},
|
||||||
[]netip.Addr{netip.MustParseAddr(dns)},
|
[]netip.Addr{netip.MustParseAddr(dns)},
|
||||||
mtuInt)
|
mtuInt)
|
||||||
|
|||||||
217
netstack2/README.md
Normal file
217
netstack2/README.md
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
# Netstack2 TCP/UDP Proxying
|
||||||
|
|
||||||
|
This package provides transparent TCP and UDP connection proxying through WireGuard netstack, inspired by the tun2socks project.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The netstack implementation now supports terminating TCP and UDP connections directly in the netstack layer and transparently proxying them to their actual destination targets. This is useful when you want to intercept and forward traffic that enters through a WireGuard tunnel.
|
||||||
|
|
||||||
|
## ⚠️ Important: Dual-Interface Architecture
|
||||||
|
|
||||||
|
**WARNING**: Installing TCP/UDP handlers on the same interface used by WireGuard can cause packet handling conflicts, as WireGuard already manipulates packets at the transport layer.
|
||||||
|
|
||||||
|
**Recommended Approach**: Use `EnableProxyOnSubnet()` to create a **secondary NIC** (Network Interface Card) within the netstack that is dedicated to TCP/UDP proxying. This approach:
|
||||||
|
|
||||||
|
1. **Isolates proxying from WireGuard**: WireGuard operates on NIC 1, proxying on NIC 2
|
||||||
|
2. **Uses route-based steering**: Specific subnets are routed to the proxy NIC via routing table entries
|
||||||
|
3. **Avoids conflicts**: Each NIC has its own packet handling pipeline
|
||||||
|
|
||||||
|
### Architecture Comparison
|
||||||
|
|
||||||
|
#### ❌ Single Interface (Not Recommended)
|
||||||
|
```
|
||||||
|
Client → WireGuard Tunnel → NIC 1 (with TCP/UDP handlers) → Conflicts!
|
||||||
|
↓
|
||||||
|
Both WireGuard and handlers process same packets
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ✅ Dual Interface (Recommended)
|
||||||
|
```
|
||||||
|
Client → WireGuard Tunnel → NIC 1 (WireGuard traffic, no handlers)
|
||||||
|
↓
|
||||||
|
Routing Table
|
||||||
|
↓
|
||||||
|
NIC 2 (TCP/UDP proxy for specific subnets)
|
||||||
|
↓
|
||||||
|
Direct connection to targets
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Differences from tun2socks
|
||||||
|
|
||||||
|
While tun2socks proxies connections to an upstream SOCKS proxy, newt's implementation directly connects to the actual target addresses. This is because newt has direct network access to the targets.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### TCP Handling
|
||||||
|
|
||||||
|
1. **TCP Forwarder**: Installed on the netstack to intercept incoming TCP connections
|
||||||
|
2. **Connection Establishment**: Performs the TCP three-way handshake with the client through netstack
|
||||||
|
3. **Target Connection**: Establishes a direct TCP connection to the actual target
|
||||||
|
4. **Bidirectional Proxy**: Copies data bidirectionally between the netstack connection and the target connection
|
||||||
|
5. **Half-Close Support**: Properly handles TCP half-close semantics for graceful shutdown
|
||||||
|
|
||||||
|
### UDP Handling
|
||||||
|
|
||||||
|
1. **UDP Forwarder**: Installed on the netstack to intercept incoming UDP packets
|
||||||
|
2. **Connection Creation**: Creates a UDP endpoint in netstack for the client
|
||||||
|
3. **Target Connection**: Establishes a direct UDP connection to the actual target
|
||||||
|
4. **Packet Forwarding**: Forwards UDP packets bidirectionally with timeout handling
|
||||||
|
5. **Session Timeout**: UDP sessions timeout after 60 seconds of inactivity
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### ✅ Recommended: Subnet-Based Proxying (Dual-Interface)
|
||||||
|
|
||||||
|
This is the **recommended approach** to avoid conflicts with WireGuard:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/fosrl/newt/netstack2"
|
||||||
|
|
||||||
|
// Create netstack normally (no proxying on main interface)
|
||||||
|
tun, tnet, err := netstack2.CreateNetTUN(localAddresses, dnsServers, mtu)
|
||||||
|
|
||||||
|
// Define which subnets should be proxied
|
||||||
|
// These could be specific services or networks you want to intercept
|
||||||
|
proxySubnets := []netip.Prefix{
|
||||||
|
netip.MustParsePrefix("192.168.1.0/24"), // Internal network
|
||||||
|
netip.MustParsePrefix("10.20.0.0/16"), // Service network
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable proxying on a secondary NIC for these subnets only
|
||||||
|
err = tnet.EnableProxyOnSubnet(proxySubnets, true, true) // TCP=true, UDP=true
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to enable proxy on subnet: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now:
|
||||||
|
// - Traffic to 192.168.1.0/24 and 10.20.0.0/16 → Proxied via NIC 2
|
||||||
|
// - All other traffic → Handled normally by WireGuard on NIC 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Enable During Creation (Single-Interface - Use with Caution)
|
||||||
|
|
||||||
|
**⚠️ May conflict with WireGuard packet handling!**
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Enable proxying on the main interface
|
||||||
|
tun, tnet, err := netstack2.CreateNetTUNWithOptions(
|
||||||
|
localAddresses,
|
||||||
|
dnsServers,
|
||||||
|
mtu,
|
||||||
|
netstack2.NetTunOptions{
|
||||||
|
EnableTCPProxy: true,
|
||||||
|
EnableUDPProxy: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// All TCP/UDP traffic will be intercepted - may conflict with WireGuard
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3: Enable After Creation (Single-Interface - Use with Caution)
|
||||||
|
|
||||||
|
**⚠️ May conflict with WireGuard packet handling!**
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Create netstack normally
|
||||||
|
tun, tnet, err := netstack2.CreateNetTUN(localAddresses, dnsServers, mtu)
|
||||||
|
|
||||||
|
// Enable TCP proxying later on main interface
|
||||||
|
if err := tnet.EnableTCPProxy(); err != nil {
|
||||||
|
log.Fatalf("Failed to enable TCP proxy: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable UDP proxying later on main interface
|
||||||
|
if err := tnet.EnableUDPProxy(); err != nil {
|
||||||
|
log.Fatalf("Failed to enable UDP proxy: %v", err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 4: Backward Compatible (No Proxying)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Use the standard CreateNetTUN - no proxying enabled
|
||||||
|
tun, tnet, err := netstack2.CreateNetTUN(localAddresses, dnsServers, mtu)
|
||||||
|
// Connections will use standard netstack dial methods
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Parameters
|
||||||
|
|
||||||
|
### TCP Settings
|
||||||
|
|
||||||
|
- **TCP Connect Timeout**: 5 seconds for establishing connections to targets
|
||||||
|
- **TCP Keepalive Idle**: 60 seconds before first keepalive probe
|
||||||
|
- **TCP Keepalive Interval**: 30 seconds between keepalive probes
|
||||||
|
- **TCP Keepalive Count**: 9 probes before giving up
|
||||||
|
- **TCP Half-Close Timeout**: 60 seconds for graceful shutdown
|
||||||
|
|
||||||
|
### UDP Settings
|
||||||
|
|
||||||
|
- **UDP Session Timeout**: 60 seconds of inactivity before closing session
|
||||||
|
- **Max Packet Size**: 65535 bytes (standard UDP maximum)
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
1. **Buffer Sizes**: 32KB buffers for TCP, 64KB for UDP
|
||||||
|
2. **Goroutines**: Each connection spawns 2 goroutines for bidirectional copying
|
||||||
|
3. **Memory**: Buffer allocations are reused where possible
|
||||||
|
4. **Socket Options**: Optimized TCP send/receive buffer sizes from stack defaults
|
||||||
|
|
||||||
|
## Example: WireGuard Integration
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (s *WireGuardService) createNetstack() error {
|
||||||
|
// Create netstack WITHOUT proxying on the main interface
|
||||||
|
s.tun, s.tnet, err = netstack2.CreateNetTUN(
|
||||||
|
[]netip.Addr{tunnelIP},
|
||||||
|
s.dns,
|
||||||
|
s.mtu,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define subnets that should be proxied
|
||||||
|
// These are typically the target services you want to intercept
|
||||||
|
proxySubnets := []netip.Prefix{
|
||||||
|
netip.MustParsePrefix("192.168.100.0/24"), // Service subnet 1
|
||||||
|
netip.MustParsePrefix("10.50.0.0/16"), // Service subnet 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable proxying on a secondary NIC for specific subnets
|
||||||
|
// This avoids conflicts with WireGuard's packet handling
|
||||||
|
err = s.tnet.EnableProxyOnSubnet(proxySubnets, true, true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to enable proxy: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now:
|
||||||
|
// - WireGuard handles encryption/decryption on NIC 1
|
||||||
|
// - Traffic to proxySubnets is routed to NIC 2 for TCP/UDP proxying
|
||||||
|
// - All other traffic goes through normal WireGuard path
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
When proxying is enabled:
|
||||||
|
- Failed TCP connections will result in RST packets being sent back to the client
|
||||||
|
- Failed UDP connections will silently drop packets (standard UDP behavior)
|
||||||
|
- Connection timeouts follow standard TCP/UDP semantics
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
1. **No Filtering**: All connections are proxied, no filtering capability
|
||||||
|
2. **Direct Routing**: Assumes direct network access to all target addresses
|
||||||
|
3. **No NAT Traversal**: Does not handle complex NAT scenarios
|
||||||
|
4. **Memory Usage**: Each active connection uses ~64KB of buffer space
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
Potential improvements:
|
||||||
|
- Connection filtering/allow-listing
|
||||||
|
- Per-connection rate limiting
|
||||||
|
- Connection statistics and monitoring
|
||||||
|
- Dynamic timeout configuration
|
||||||
|
- Connection pooling for frequently accessed targets
|
||||||
301
netstack2/handlers.go
Normal file
301
netstack2/handlers.go
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package netstack2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fosrl/newt/logger"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||||
|
"gvisor.dev/gvisor/pkg/waiter"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// defaultWndSize if set to zero, the default
|
||||||
|
// receive window buffer size is used instead.
|
||||||
|
defaultWndSize = 0
|
||||||
|
|
||||||
|
// maxConnAttempts specifies the maximum number
|
||||||
|
// of in-flight tcp connection attempts.
|
||||||
|
maxConnAttempts = 2 << 10
|
||||||
|
|
||||||
|
// tcpKeepaliveCount is the maximum number of
|
||||||
|
// TCP keep-alive probes to send before giving up
|
||||||
|
// and killing the connection if no response is
|
||||||
|
// obtained from the other end.
|
||||||
|
tcpKeepaliveCount = 9
|
||||||
|
|
||||||
|
// tcpKeepaliveIdle specifies the time a connection
|
||||||
|
// must remain idle before the first TCP keepalive
|
||||||
|
// packet is sent. Once this time is reached,
|
||||||
|
// tcpKeepaliveInterval option is used instead.
|
||||||
|
tcpKeepaliveIdle = 60 * time.Second
|
||||||
|
|
||||||
|
// tcpKeepaliveInterval specifies the interval
|
||||||
|
// time between sending TCP keepalive packets.
|
||||||
|
tcpKeepaliveInterval = 30 * time.Second
|
||||||
|
|
||||||
|
// tcpConnectTimeout is the default timeout for TCP handshakes.
|
||||||
|
tcpConnectTimeout = 5 * time.Second
|
||||||
|
|
||||||
|
// tcpWaitTimeout implements a TCP half-close timeout.
|
||||||
|
tcpWaitTimeout = 60 * time.Second
|
||||||
|
|
||||||
|
// udpSessionTimeout is the default timeout for UDP sessions.
|
||||||
|
udpSessionTimeout = 60 * time.Second
|
||||||
|
|
||||||
|
// Buffer size for copying data
|
||||||
|
bufferSize = 32 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
// TCPHandler handles TCP connections from netstack
|
||||||
|
type TCPHandler struct {
|
||||||
|
stack *stack.Stack
|
||||||
|
}
|
||||||
|
|
||||||
|
// UDPHandler handles UDP connections from netstack
|
||||||
|
type UDPHandler struct {
|
||||||
|
stack *stack.Stack
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTCPHandler creates a new TCP handler
|
||||||
|
func NewTCPHandler(s *stack.Stack) *TCPHandler {
|
||||||
|
return &TCPHandler{stack: s}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUDPHandler creates a new UDP handler
|
||||||
|
func NewUDPHandler(s *stack.Stack) *UDPHandler {
|
||||||
|
return &UDPHandler{stack: s}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallTCPHandler installs the TCP forwarder on the stack
|
||||||
|
func (h *TCPHandler) InstallTCPHandler() error {
|
||||||
|
tcpForwarder := tcp.NewForwarder(h.stack, defaultWndSize, maxConnAttempts, func(r *tcp.ForwarderRequest) {
|
||||||
|
var (
|
||||||
|
wq waiter.Queue
|
||||||
|
ep tcpip.Endpoint
|
||||||
|
err tcpip.Error
|
||||||
|
id = r.ID()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Perform a TCP three-way handshake
|
||||||
|
ep, err = r.CreateEndpoint(&wq)
|
||||||
|
if err != nil {
|
||||||
|
// RST: prevent potential half-open TCP connection leak
|
||||||
|
r.Complete(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer r.Complete(false)
|
||||||
|
|
||||||
|
// Set socket options
|
||||||
|
setTCPSocketOptions(h.stack, ep)
|
||||||
|
|
||||||
|
// Create TCP connection from netstack endpoint
|
||||||
|
netstackConn := gonet.NewTCPConn(&wq, ep)
|
||||||
|
|
||||||
|
// Handle the connection in a goroutine
|
||||||
|
go h.handleTCPConn(netstackConn, id)
|
||||||
|
})
|
||||||
|
|
||||||
|
h.stack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleTCPConn handles a TCP connection by proxying it to the actual target
|
||||||
|
func (h *TCPHandler) handleTCPConn(netstackConn *gonet.TCPConn, id stack.TransportEndpointID) {
|
||||||
|
defer netstackConn.Close()
|
||||||
|
|
||||||
|
// Extract source and target address from the connection ID
|
||||||
|
srcIP := id.RemoteAddress.String()
|
||||||
|
srcPort := id.RemotePort
|
||||||
|
dstIP := id.LocalAddress.String()
|
||||||
|
dstPort := id.LocalPort
|
||||||
|
|
||||||
|
logger.Info("TCP Forwarder: Handling connection %s:%d -> %s:%d", srcIP, srcPort, dstIP, dstPort)
|
||||||
|
|
||||||
|
targetAddr := fmt.Sprintf("%s:%d", dstIP, dstPort)
|
||||||
|
|
||||||
|
// Create context with timeout for connection establishment
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Dial the actual target using standard net package
|
||||||
|
var d net.Dialer
|
||||||
|
targetConn, err := d.DialContext(ctx, "tcp", targetAddr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Info("TCP Forwarder: Failed to connect to %s: %v", targetAddr, err)
|
||||||
|
// Connection failed, netstack will handle RST
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer targetConn.Close()
|
||||||
|
|
||||||
|
logger.Info("TCP Forwarder: Successfully connected to %s, starting bidirectional copy", targetAddr)
|
||||||
|
|
||||||
|
// Bidirectional copy between netstack and target
|
||||||
|
pipeTCP(netstackConn, targetConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pipeTCP copies data bidirectionally between two connections
|
||||||
|
func pipeTCP(origin, remote net.Conn) {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
go unidirectionalStreamTCP(remote, origin, "origin->remote", &wg)
|
||||||
|
go unidirectionalStreamTCP(origin, remote, "remote->origin", &wg)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// unidirectionalStreamTCP copies data in one direction
|
||||||
|
func unidirectionalStreamTCP(dst, src net.Conn, dir string, wg *sync.WaitGroup) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
buf := make([]byte, bufferSize)
|
||||||
|
_, _ = io.CopyBuffer(dst, src, buf)
|
||||||
|
|
||||||
|
// Do the upload/download side TCP half-close
|
||||||
|
if cr, ok := src.(interface{ CloseRead() error }); ok {
|
||||||
|
cr.CloseRead()
|
||||||
|
}
|
||||||
|
if cw, ok := dst.(interface{ CloseWrite() error }); ok {
|
||||||
|
cw.CloseWrite()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set TCP half-close timeout
|
||||||
|
dst.SetReadDeadline(time.Now().Add(tcpWaitTimeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
// setTCPSocketOptions sets TCP socket options for better performance
|
||||||
|
func setTCPSocketOptions(s *stack.Stack, ep tcpip.Endpoint) {
|
||||||
|
// TCP keepalive options
|
||||||
|
ep.SocketOptions().SetKeepAlive(true)
|
||||||
|
|
||||||
|
idle := tcpip.KeepaliveIdleOption(tcpKeepaliveIdle)
|
||||||
|
ep.SetSockOpt(&idle)
|
||||||
|
|
||||||
|
interval := tcpip.KeepaliveIntervalOption(tcpKeepaliveInterval)
|
||||||
|
ep.SetSockOpt(&interval)
|
||||||
|
|
||||||
|
ep.SetSockOptInt(tcpip.KeepaliveCountOption, tcpKeepaliveCount)
|
||||||
|
|
||||||
|
// TCP send/recv buffer size
|
||||||
|
var ss tcpip.TCPSendBufferSizeRangeOption
|
||||||
|
if err := s.TransportProtocolOption(tcp.ProtocolNumber, &ss); err == nil {
|
||||||
|
ep.SocketOptions().SetSendBufferSize(int64(ss.Default), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
var rs tcpip.TCPReceiveBufferSizeRangeOption
|
||||||
|
if err := s.TransportProtocolOption(tcp.ProtocolNumber, &rs); err == nil {
|
||||||
|
ep.SocketOptions().SetReceiveBufferSize(int64(rs.Default), false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallUDPHandler installs the UDP forwarder on the stack
|
||||||
|
func (h *UDPHandler) InstallUDPHandler() error {
|
||||||
|
udpForwarder := udp.NewForwarder(h.stack, func(r *udp.ForwarderRequest) {
|
||||||
|
var (
|
||||||
|
wq waiter.Queue
|
||||||
|
id = r.ID()
|
||||||
|
)
|
||||||
|
|
||||||
|
ep, err := r.CreateEndpoint(&wq)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create UDP connection from netstack endpoint
|
||||||
|
netstackConn := gonet.NewUDPConn(&wq, ep)
|
||||||
|
|
||||||
|
// Handle the connection in a goroutine
|
||||||
|
go h.handleUDPConn(netstackConn, id)
|
||||||
|
})
|
||||||
|
|
||||||
|
h.stack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleUDPConn handles a UDP connection by proxying it to the actual target
|
||||||
|
func (h *UDPHandler) handleUDPConn(netstackConn *gonet.UDPConn, id stack.TransportEndpointID) {
|
||||||
|
defer netstackConn.Close()
|
||||||
|
|
||||||
|
// Extract source and target address from the connection ID
|
||||||
|
srcIP := id.RemoteAddress.String()
|
||||||
|
srcPort := id.RemotePort
|
||||||
|
dstIP := id.LocalAddress.String()
|
||||||
|
dstPort := id.LocalPort
|
||||||
|
|
||||||
|
logger.Info("UDP Forwarder: Handling connection %s:%d -> %s:%d", srcIP, srcPort, dstIP, dstPort)
|
||||||
|
|
||||||
|
targetAddr := fmt.Sprintf("%s:%d", dstIP, dstPort)
|
||||||
|
|
||||||
|
// Resolve target address
|
||||||
|
remoteUDPAddr, err := net.ResolveUDPAddr("udp", targetAddr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Info("UDP Forwarder: Failed to resolve %s: %v", targetAddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create UDP connection to target
|
||||||
|
targetConn, err := net.DialUDP("udp", nil, remoteUDPAddr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Info("UDP Forwarder: Failed to dial %s: %v", targetAddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer targetConn.Close()
|
||||||
|
|
||||||
|
logger.Info("UDP Forwarder: Successfully connected to %s, starting bidirectional copy", targetAddr)
|
||||||
|
|
||||||
|
// Bidirectional copy between netstack and target
|
||||||
|
pipeUDP(netstackConn, targetConn, remoteUDPAddr, udpSessionTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pipeUDP copies UDP packets bidirectionally
|
||||||
|
func pipeUDP(origin, remote net.PacketConn, to net.Addr, timeout time.Duration) {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
go unidirectionalPacketStream(remote, origin, to, "origin->remote", &wg, timeout)
|
||||||
|
go unidirectionalPacketStream(origin, remote, nil, "remote->origin", &wg, timeout)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// unidirectionalPacketStream copies packets in one direction
|
||||||
|
func unidirectionalPacketStream(dst, src net.PacketConn, to net.Addr, dir string, wg *sync.WaitGroup, timeout time.Duration) {
|
||||||
|
defer wg.Done()
|
||||||
|
_ = copyPacketData(dst, src, to, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyPacketData copies UDP packet data with timeout
|
||||||
|
func copyPacketData(dst, src net.PacketConn, to net.Addr, timeout time.Duration) error {
|
||||||
|
buf := make([]byte, 65535) // Max UDP packet size
|
||||||
|
|
||||||
|
for {
|
||||||
|
src.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
n, _, err := src.ReadFrom(buf)
|
||||||
|
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
||||||
|
return nil // ignore I/O timeout
|
||||||
|
} else if err == io.EOF {
|
||||||
|
return nil // ignore EOF
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = dst.WriteTo(buf[:n], to); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dst.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
}
|
||||||
|
}
|
||||||
270
netstack2/tun.go
270
netstack2/tun.go
@@ -22,6 +22,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fosrl/newt/logger"
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
@@ -41,41 +42,78 @@ import (
|
|||||||
|
|
||||||
type netTun struct {
|
type netTun struct {
|
||||||
ep *channel.Endpoint
|
ep *channel.Endpoint
|
||||||
|
proxyEp *channel.Endpoint // Separate endpoint for promiscuous mode
|
||||||
stack *stack.Stack
|
stack *stack.Stack
|
||||||
events chan tun.Event
|
events chan tun.Event
|
||||||
notifyHandle *channel.NotificationHandle
|
notifyHandle *channel.NotificationHandle
|
||||||
|
proxyNotifyHandle *channel.NotificationHandle // Notify handle for proxy endpoint
|
||||||
incomingPacket chan *buffer.View
|
incomingPacket chan *buffer.View
|
||||||
mtu int
|
mtu int
|
||||||
dnsServers []netip.Addr
|
dnsServers []netip.Addr
|
||||||
hasV4, hasV6 bool
|
hasV4, hasV6 bool
|
||||||
|
tcpHandler *TCPHandler
|
||||||
|
udpHandler *UDPHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
type Net netTun
|
type Net netTun
|
||||||
|
|
||||||
|
// NetTunOptions contains options for creating a NetTUN device
|
||||||
|
type NetTunOptions struct {
|
||||||
|
EnableTCPProxy bool
|
||||||
|
EnableUDPProxy bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNetTUN creates a new TUN device with netstack without proxying
|
||||||
func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int) (tun.Device, *Net, error) {
|
func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int) (tun.Device, *Net, error) {
|
||||||
opts := stack.Options{
|
return CreateNetTUNWithOptions(localAddresses, dnsServers, mtu, NetTunOptions{
|
||||||
|
EnableTCPProxy: true,
|
||||||
|
EnableUDPProxy: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNetTUNWithOptions creates a new TUN device with netstack and optional TCP/UDP proxying
|
||||||
|
func CreateNetTUNWithOptions(localAddresses, dnsServers []netip.Addr, mtu int, options NetTunOptions) (tun.Device, *Net, error) {
|
||||||
|
stackOpts := stack.Options{
|
||||||
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
||||||
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4},
|
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4},
|
||||||
HandleLocal: true,
|
HandleLocal: true,
|
||||||
}
|
}
|
||||||
dev := &netTun{
|
dev := &netTun{
|
||||||
ep: channel.New(1024, uint32(mtu), ""),
|
ep: channel.New(1024, uint32(mtu), ""),
|
||||||
stack: stack.New(opts),
|
proxyEp: channel.New(1024, uint32(mtu), ""),
|
||||||
|
stack: stack.New(stackOpts),
|
||||||
events: make(chan tun.Event, 10),
|
events: make(chan tun.Event, 10),
|
||||||
incomingPacket: make(chan *buffer.View),
|
incomingPacket: make(chan *buffer.View),
|
||||||
dnsServers: dnsServers,
|
dnsServers: dnsServers,
|
||||||
mtu: mtu,
|
mtu: mtu,
|
||||||
}
|
}
|
||||||
sackEnabledOpt := tcpip.TCPSACKEnabled(true) // TCP SACK is disabled by default
|
|
||||||
|
if options.EnableTCPProxy {
|
||||||
|
dev.tcpHandler = NewTCPHandler(dev.stack)
|
||||||
|
if err := dev.tcpHandler.InstallTCPHandler(); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to install TCP handler: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.EnableUDPProxy {
|
||||||
|
dev.udpHandler = NewUDPHandler(dev.stack)
|
||||||
|
if err := dev.udpHandler.InstallUDPHandler(); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to install UDP handler: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sackEnabledOpt := tcpip.TCPSACKEnabled(true) // TCP SACK is enabled by default
|
||||||
tcpipErr := dev.stack.SetTransportProtocolOption(tcp.ProtocolNumber, &sackEnabledOpt)
|
tcpipErr := dev.stack.SetTransportProtocolOption(tcp.ProtocolNumber, &sackEnabledOpt)
|
||||||
if tcpipErr != nil {
|
if tcpipErr != nil {
|
||||||
return nil, nil, fmt.Errorf("could not enable TCP SACK: %v", tcpipErr)
|
return nil, nil, fmt.Errorf("could not enable TCP SACK: %v", tcpipErr)
|
||||||
}
|
}
|
||||||
|
// Create NIC 1 (main interface, no promiscuous mode)
|
||||||
dev.notifyHandle = dev.ep.AddNotify(dev)
|
dev.notifyHandle = dev.ep.AddNotify(dev)
|
||||||
tcpipErr = dev.stack.CreateNIC(1, dev.ep)
|
tcpipErr = dev.stack.CreateNIC(1, dev.ep)
|
||||||
if tcpipErr != nil {
|
if tcpipErr != nil {
|
||||||
return nil, nil, fmt.Errorf("CreateNIC: %v", tcpipErr)
|
return nil, nil, fmt.Errorf("CreateNIC: %v", tcpipErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ip := range localAddresses {
|
for _, ip := range localAddresses {
|
||||||
var protoNumber tcpip.NetworkProtocolNumber
|
var protoNumber tcpip.NetworkProtocolNumber
|
||||||
if ip.Is4() {
|
if ip.Is4() {
|
||||||
@@ -98,10 +136,92 @@ func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int) (tun.Device,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if dev.hasV4 {
|
if dev.hasV4 {
|
||||||
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: 1})
|
// dev.stack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: 1})
|
||||||
|
// add 100.90.129.0/24
|
||||||
|
proxySubnet := netip.MustParsePrefix("100.90.129.0/24")
|
||||||
|
proxyTcpipSubnet, err := tcpip.NewSubnet(
|
||||||
|
tcpip.AddrFromSlice(proxySubnet.Addr().AsSlice()),
|
||||||
|
tcpip.MaskFromBytes(proxySubnet.Addr().AsSlice()),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create proxy subnet: %v", err)
|
||||||
}
|
}
|
||||||
if dev.hasV6 {
|
dev.stack.AddRoute(tcpip.Route{Destination: proxyTcpipSubnet, NIC: 1})
|
||||||
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: 1})
|
}
|
||||||
|
// if dev.hasV6 {
|
||||||
|
// // dev.stack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: 1})
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Add specific route for proxy network (10.20.20.0/24) to NIC 2
|
||||||
|
if options.EnableTCPProxy || options.EnableUDPProxy {
|
||||||
|
dev.proxyNotifyHandle = dev.proxyEp.AddNotify(dev)
|
||||||
|
tcpipErr = dev.stack.CreateNIC(2, dev.proxyEp)
|
||||||
|
if tcpipErr != nil {
|
||||||
|
return nil, nil, fmt.Errorf("CreateNIC 2 (proxy): %v", tcpipErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable promiscuous mode ONLY on NIC 2
|
||||||
|
if tcpipErr := dev.stack.SetPromiscuousMode(2, true); tcpipErr != nil {
|
||||||
|
return nil, nil, fmt.Errorf("SetPromiscuousMode on NIC 2: %v", tcpipErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable spoofing ONLY on NIC 2
|
||||||
|
if tcpipErr := dev.stack.SetSpoofing(2, true); tcpipErr != nil {
|
||||||
|
return nil, nil, fmt.Errorf("SetSpoofing on NIC 2: %v", tcpipErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the proxy network address (10.20.20.1/24) to NIC 2
|
||||||
|
// This allows the stack to accept connections to any IP in this range when in promiscuous mode
|
||||||
|
// Similar to how tun2socks adds 10.0.0.1/8 for multicast support
|
||||||
|
// The PEB: CanBePrimaryEndpoint is CRITICAL - it allows the stack to build routes
|
||||||
|
// and accept connections to any IP in this range when in promiscuous+spoofing mode
|
||||||
|
proxyAddr := netip.MustParseAddr("10.20.20.1")
|
||||||
|
protoAddr := tcpip.ProtocolAddress{
|
||||||
|
Protocol: ipv4.ProtocolNumber,
|
||||||
|
AddressWithPrefix: tcpip.AddressWithPrefix{
|
||||||
|
Address: tcpip.AddrFromSlice(proxyAddr.AsSlice()),
|
||||||
|
PrefixLen: 24, // /24 network
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tcpipErr = dev.stack.AddProtocolAddress(2, protoAddr, stack.AddressProperties{
|
||||||
|
PEB: stack.CanBePrimaryEndpoint, // Allow this to be used as primary endpoint
|
||||||
|
})
|
||||||
|
if tcpipErr != nil {
|
||||||
|
return nil, nil, fmt.Errorf("AddProtocolAddress for proxy NIC: %v", tcpipErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxySubnet := netip.MustParsePrefix("10.20.20.0/24")
|
||||||
|
proxyTcpipSubnet, err := tcpip.NewSubnet(
|
||||||
|
tcpip.AddrFromSlice(proxySubnet.Addr().AsSlice()),
|
||||||
|
tcpip.MaskFromBytes(net.CIDRMask(24, 32)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create proxy subnet: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dev.stack.AddRoute(tcpip.Route{
|
||||||
|
Destination: proxyTcpipSubnet,
|
||||||
|
NIC: 2,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// print the stack routes table and interfaces for debugging
|
||||||
|
logger.Info("Stack configuration:")
|
||||||
|
|
||||||
|
// Print NICs
|
||||||
|
nics := dev.stack.NICInfo()
|
||||||
|
for nicID, nicInfo := range nics {
|
||||||
|
logger.Info("NIC %d: %s (MTU: %d)", nicID, nicInfo.Name, nicInfo.MTU)
|
||||||
|
for _, addr := range nicInfo.ProtocolAddresses {
|
||||||
|
logger.Info(" Address: %s", addr.AddressWithPrefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print routing table
|
||||||
|
routes := dev.stack.GetRouteTable()
|
||||||
|
logger.Info("Routing table (%d routes):", len(routes))
|
||||||
|
for i, route := range routes {
|
||||||
|
logger.Info(" Route %d: %s -> NIC %d", i, route.Destination, route.NIC)
|
||||||
}
|
}
|
||||||
|
|
||||||
dev.events <- tun.EventUp
|
dev.events <- tun.EventUp
|
||||||
@@ -142,11 +262,44 @@ func (tun *netTun) Write(buf [][]byte, offset int) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buffer.MakeWithData(packet)})
|
pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buffer.MakeWithData(packet)})
|
||||||
|
|
||||||
|
// Determine which NIC to inject the packet into based on destination IP
|
||||||
|
targetEp := tun.ep // Default to NIC 1
|
||||||
|
|
||||||
switch packet[0] >> 4 {
|
switch packet[0] >> 4 {
|
||||||
case 4:
|
case 4:
|
||||||
tun.ep.InjectInbound(header.IPv4ProtocolNumber, pkb)
|
// Parse IPv4 header to check destination
|
||||||
|
if len(packet) >= header.IPv4MinimumSize {
|
||||||
|
ipv4Header := header.IPv4(packet)
|
||||||
|
dstIP := ipv4Header.DestinationAddress()
|
||||||
|
|
||||||
|
// Check if destination is in the proxy range (10.20.20.0/24)
|
||||||
|
// If so, inject into proxyEp (NIC 2) which has promiscuous mode
|
||||||
|
if tun.proxyEp != nil {
|
||||||
|
dstBytes := dstIP.As4()
|
||||||
|
// Check for 10.20.20.x
|
||||||
|
if dstBytes[0] == 10 && dstBytes[1] == 20 && dstBytes[2] == 20 {
|
||||||
|
targetEp = tun.proxyEp
|
||||||
|
// Log what protocol this is
|
||||||
|
proto := "unknown"
|
||||||
|
if len(packet) > header.IPv4MinimumSize {
|
||||||
|
switch ipv4Header.Protocol() {
|
||||||
|
case uint8(header.TCPProtocolNumber):
|
||||||
|
proto = "TCP"
|
||||||
|
case uint8(header.UDPProtocolNumber):
|
||||||
|
proto = "UDP"
|
||||||
|
case uint8(header.ICMPv4ProtocolNumber):
|
||||||
|
proto = "ICMP"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.Info("Routing %s packet to NIC 2 (proxy): dst=%s", proto, dstIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetEp.InjectInbound(header.IPv4ProtocolNumber, pkb)
|
||||||
case 6:
|
case 6:
|
||||||
tun.ep.InjectInbound(header.IPv6ProtocolNumber, pkb)
|
// For IPv6, always use NIC 1 for now
|
||||||
|
targetEp.InjectInbound(header.IPv6ProtocolNumber, pkb)
|
||||||
default:
|
default:
|
||||||
return 0, syscall.EAFNOSUPPORT
|
return 0, syscall.EAFNOSUPPORT
|
||||||
}
|
}
|
||||||
@@ -154,20 +307,117 @@ func (tun *netTun) Write(buf [][]byte, offset int) (int, error) {
|
|||||||
return len(buf), nil
|
return len(buf), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logPacketDetails parses and logs packet information
|
||||||
|
func logPacketDetails(pkt *stack.PacketBuffer, nicID int) {
|
||||||
|
netProto := pkt.NetworkProtocolNumber
|
||||||
|
var srcIP, dstIP string
|
||||||
|
var protocol string
|
||||||
|
var srcPort, dstPort uint16
|
||||||
|
|
||||||
|
// Parse network layer
|
||||||
|
switch netProto {
|
||||||
|
case header.IPv4ProtocolNumber:
|
||||||
|
if pkt.NetworkHeader().View().Size() >= header.IPv4MinimumSize {
|
||||||
|
ipv4 := header.IPv4(pkt.NetworkHeader().Slice())
|
||||||
|
srcIP = ipv4.SourceAddress().String()
|
||||||
|
dstIP = ipv4.DestinationAddress().String()
|
||||||
|
|
||||||
|
// Parse transport layer
|
||||||
|
switch ipv4.Protocol() {
|
||||||
|
case uint8(header.TCPProtocolNumber):
|
||||||
|
protocol = "TCP"
|
||||||
|
if pkt.TransportHeader().View().Size() >= header.TCPMinimumSize {
|
||||||
|
tcp := header.TCP(pkt.TransportHeader().Slice())
|
||||||
|
srcPort = tcp.SourcePort()
|
||||||
|
dstPort = tcp.DestinationPort()
|
||||||
|
}
|
||||||
|
case uint8(header.UDPProtocolNumber):
|
||||||
|
protocol = "UDP"
|
||||||
|
if pkt.TransportHeader().View().Size() >= header.UDPMinimumSize {
|
||||||
|
udp := header.UDP(pkt.TransportHeader().Slice())
|
||||||
|
srcPort = udp.SourcePort()
|
||||||
|
dstPort = udp.DestinationPort()
|
||||||
|
}
|
||||||
|
case uint8(header.ICMPv4ProtocolNumber):
|
||||||
|
protocol = "ICMPv4"
|
||||||
|
default:
|
||||||
|
protocol = fmt.Sprintf("Proto-%d", ipv4.Protocol())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case header.IPv6ProtocolNumber:
|
||||||
|
if pkt.NetworkHeader().View().Size() >= header.IPv6MinimumSize {
|
||||||
|
ipv6 := header.IPv6(pkt.NetworkHeader().Slice())
|
||||||
|
srcIP = ipv6.SourceAddress().String()
|
||||||
|
dstIP = ipv6.DestinationAddress().String()
|
||||||
|
|
||||||
|
// Parse transport layer
|
||||||
|
switch ipv6.TransportProtocol() {
|
||||||
|
case header.TCPProtocolNumber:
|
||||||
|
protocol = "TCP"
|
||||||
|
if pkt.TransportHeader().View().Size() >= header.TCPMinimumSize {
|
||||||
|
tcp := header.TCP(pkt.TransportHeader().Slice())
|
||||||
|
srcPort = tcp.SourcePort()
|
||||||
|
dstPort = tcp.DestinationPort()
|
||||||
|
}
|
||||||
|
case header.UDPProtocolNumber:
|
||||||
|
protocol = "UDP"
|
||||||
|
if pkt.TransportHeader().View().Size() >= header.UDPMinimumSize {
|
||||||
|
udp := header.UDP(pkt.TransportHeader().Slice())
|
||||||
|
srcPort = udp.SourcePort()
|
||||||
|
dstPort = udp.DestinationPort()
|
||||||
|
}
|
||||||
|
case header.ICMPv6ProtocolNumber:
|
||||||
|
protocol = "ICMPv6"
|
||||||
|
default:
|
||||||
|
protocol = fmt.Sprintf("Proto-%d", ipv6.TransportProtocol())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
protocol = fmt.Sprintf("Unknown-NetProto-%d", netProto)
|
||||||
|
}
|
||||||
|
|
||||||
|
packetSize := pkt.Size()
|
||||||
|
|
||||||
|
if srcPort > 0 && dstPort > 0 {
|
||||||
|
logger.Info("NIC %d packet: %s %s:%d -> %s:%d (size: %d bytes)",
|
||||||
|
nicID, protocol, srcIP, srcPort, dstIP, dstPort, packetSize)
|
||||||
|
} else {
|
||||||
|
logger.Info("NIC %d packet: %s %s -> %s (size: %d bytes)",
|
||||||
|
nicID, protocol, srcIP, dstIP, packetSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (tun *netTun) WriteNotify() {
|
func (tun *netTun) WriteNotify() {
|
||||||
|
// Handle notifications from main endpoint (NIC 1)
|
||||||
pkt := tun.ep.Read()
|
pkt := tun.ep.Read()
|
||||||
if pkt == nil {
|
if pkt != nil {
|
||||||
|
view := pkt.ToView()
|
||||||
|
pkt.DecRef()
|
||||||
|
tun.incomingPacket <- view
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle notifications from proxy endpoint (NIC 2) if it exists
|
||||||
|
if tun.proxyEp != nil {
|
||||||
|
pkt = tun.proxyEp.Read()
|
||||||
|
if pkt != nil {
|
||||||
view := pkt.ToView()
|
view := pkt.ToView()
|
||||||
pkt.DecRef()
|
pkt.DecRef()
|
||||||
|
|
||||||
tun.incomingPacket <- view
|
tun.incomingPacket <- view
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *netTun) Close() error {
|
func (tun *netTun) Close() error {
|
||||||
tun.stack.RemoveNIC(1)
|
tun.stack.RemoveNIC(1)
|
||||||
|
|
||||||
|
// Clean up proxy NIC if it exists
|
||||||
|
if tun.proxyEp != nil {
|
||||||
|
tun.stack.RemoveNIC(2)
|
||||||
|
tun.proxyEp.RemoveNotify(tun.proxyNotifyHandle)
|
||||||
|
tun.proxyEp.Close()
|
||||||
|
}
|
||||||
|
|
||||||
tun.stack.Close()
|
tun.stack.Close()
|
||||||
tun.ep.RemoveNotify(tun.notifyHandle)
|
tun.ep.RemoveNotify(tun.notifyHandle)
|
||||||
tun.ep.Close()
|
tun.ep.Close()
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ import (
|
|||||||
"github.com/fosrl/newt/internal/state"
|
"github.com/fosrl/newt/internal/state"
|
||||||
"github.com/fosrl/newt/internal/telemetry"
|
"github.com/fosrl/newt/internal/telemetry"
|
||||||
"github.com/fosrl/newt/logger"
|
"github.com/fosrl/newt/logger"
|
||||||
"github.com/fosrl/newt/netstack2"
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/metric"
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ type Target struct {
|
|||||||
|
|
||||||
// ProxyManager handles the creation and management of proxy connections
|
// ProxyManager handles the creation and management of proxy connections
|
||||||
type ProxyManager struct {
|
type ProxyManager struct {
|
||||||
tnet *netstack2.Net
|
tnet *netstack.Net
|
||||||
tcpTargets map[string]map[int]string // map[listenIP]map[port]targetAddress
|
tcpTargets map[string]map[int]string // map[listenIP]map[port]targetAddress
|
||||||
udpTargets map[string]map[int]string
|
udpTargets map[string]map[int]string
|
||||||
listeners []*gonet.TCPListener
|
listeners []*gonet.TCPListener
|
||||||
@@ -125,7 +125,7 @@ func classifyProxyError(err error) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewProxyManager creates a new proxy manager instance
|
// NewProxyManager creates a new proxy manager instance
|
||||||
func NewProxyManager(tnet *netstack2.Net) *ProxyManager {
|
func NewProxyManager(tnet *netstack.Net) *ProxyManager {
|
||||||
return &ProxyManager{
|
return &ProxyManager{
|
||||||
tnet: tnet,
|
tnet: tnet,
|
||||||
tcpTargets: make(map[string]map[int]string),
|
tcpTargets: make(map[string]map[int]string),
|
||||||
@@ -214,7 +214,7 @@ func NewProxyManagerWithoutTNet() *ProxyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Function to add tnet to existing ProxyManager
|
// Function to add tnet to existing ProxyManager
|
||||||
func (pm *ProxyManager) SetTNet(tnet *netstack2.Net) {
|
func (pm *ProxyManager) SetTNet(tnet *netstack.Net) {
|
||||||
pm.mutex.Lock()
|
pm.mutex.Lock()
|
||||||
defer pm.mutex.Unlock()
|
defer pm.mutex.Unlock()
|
||||||
pm.tnet = tnet
|
pm.tnet = tnet
|
||||||
|
|||||||
10
util.go
10
util.go
@@ -17,12 +17,12 @@ import (
|
|||||||
|
|
||||||
"github.com/fosrl/newt/internal/telemetry"
|
"github.com/fosrl/newt/internal/telemetry"
|
||||||
"github.com/fosrl/newt/logger"
|
"github.com/fosrl/newt/logger"
|
||||||
"github.com/fosrl/newt/netstack2"
|
|
||||||
"github.com/fosrl/newt/proxy"
|
"github.com/fosrl/newt/proxy"
|
||||||
"github.com/fosrl/newt/websocket"
|
"github.com/fosrl/newt/websocket"
|
||||||
"golang.org/x/net/icmp"
|
"golang.org/x/net/icmp"
|
||||||
"golang.org/x/net/ipv4"
|
"golang.org/x/net/ipv4"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ func fixKey(key string) string {
|
|||||||
return hex.EncodeToString(decoded)
|
return hex.EncodeToString(decoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ping(tnet *netstack2.Net, dst string, timeout time.Duration) (time.Duration, error) {
|
func ping(tnet *netstack.Net, dst string, timeout time.Duration) (time.Duration, error) {
|
||||||
logger.Debug("Pinging %s", dst)
|
logger.Debug("Pinging %s", dst)
|
||||||
socket, err := tnet.Dial("ping4", dst)
|
socket, err := tnet.Dial("ping4", dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -108,7 +108,7 @@ func ping(tnet *netstack2.Net, dst string, timeout time.Duration) (time.Duration
|
|||||||
}
|
}
|
||||||
|
|
||||||
// reliablePing performs multiple ping attempts with adaptive timeout
|
// reliablePing performs multiple ping attempts with adaptive timeout
|
||||||
func reliablePing(tnet *netstack2.Net, dst string, baseTimeout time.Duration, maxAttempts int) (time.Duration, error) {
|
func reliablePing(tnet *netstack.Net, dst string, baseTimeout time.Duration, maxAttempts int) (time.Duration, error) {
|
||||||
var lastErr error
|
var lastErr error
|
||||||
var totalLatency time.Duration
|
var totalLatency time.Duration
|
||||||
successCount := 0
|
successCount := 0
|
||||||
@@ -152,7 +152,7 @@ func reliablePing(tnet *netstack2.Net, dst string, baseTimeout time.Duration, ma
|
|||||||
return totalLatency / time.Duration(successCount), nil
|
return totalLatency / time.Duration(successCount), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func pingWithRetry(tnet *netstack2.Net, dst string, timeout time.Duration) (stopChan chan struct{}, err error) {
|
func pingWithRetry(tnet *netstack.Net, dst string, timeout time.Duration) (stopChan chan struct{}, err error) {
|
||||||
|
|
||||||
if healthFile != "" {
|
if healthFile != "" {
|
||||||
err = os.Remove(healthFile)
|
err = os.Remove(healthFile)
|
||||||
@@ -236,7 +236,7 @@ func pingWithRetry(tnet *netstack2.Net, dst string, timeout time.Duration) (stop
|
|||||||
return stopChan, fmt.Errorf("initial ping attempts failed, continuing in background")
|
return stopChan, fmt.Errorf("initial ping attempts failed, continuing in background")
|
||||||
}
|
}
|
||||||
|
|
||||||
func startPingCheck(tnet *netstack2.Net, serverIP string, client *websocket.Client, tunnelID string) chan struct{} {
|
func startPingCheck(tnet *netstack.Net, serverIP string, client *websocket.Client, tunnelID string) chan struct{} {
|
||||||
maxInterval := 6 * time.Second
|
maxInterval := 6 * time.Second
|
||||||
currentInterval := pingInterval
|
currentInterval := pingInterval
|
||||||
consecutiveFailures := 0
|
consecutiveFailures := 0
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
"golang.zx2c4.com/wireguard/conn"
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
"github.com/fosrl/newt/internal/telemetry"
|
"github.com/fosrl/newt/internal/telemetry"
|
||||||
@@ -90,7 +91,7 @@ type WireGuardService struct {
|
|||||||
onNetstackReady func(*netstack2.Net)
|
onNetstackReady func(*netstack2.Net)
|
||||||
// Callback for when netstack is closed
|
// Callback for when netstack is closed
|
||||||
onNetstackClose func()
|
onNetstackClose func()
|
||||||
othertnet *netstack2.Net
|
othertnet *netstack.Net
|
||||||
// Proxy manager for tunnel
|
// Proxy manager for tunnel
|
||||||
proxyManager *proxy.ProxyManager
|
proxyManager *proxy.ProxyManager
|
||||||
TunnelIP string
|
TunnelIP string
|
||||||
@@ -333,7 +334,7 @@ func (s *WireGuardService) removeTcpTarget(msg websocket.WSMessage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WireGuardService) SetOthertnet(tnet *netstack2.Net) {
|
func (s *WireGuardService) SetOthertnet(tnet *netstack.Net) {
|
||||||
s.othertnet = tnet
|
s.othertnet = tnet
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,16 +496,20 @@ func (s *WireGuardService) ensureWireguardInterface(wgconfig WgConfig) error {
|
|||||||
|
|
||||||
// Create TUN device and network stack using netstack
|
// Create TUN device and network stack using netstack
|
||||||
var err error
|
var err error
|
||||||
s.tun, s.tnet, err = netstack2.CreateNetTUN(
|
s.tun, s.tnet, err = netstack2.CreateNetTUNWithOptions(
|
||||||
[]netip.Addr{tunnelIP},
|
[]netip.Addr{tunnelIP},
|
||||||
s.dns,
|
s.dns,
|
||||||
s.mtu)
|
s.mtu,
|
||||||
|
netstack2.NetTunOptions{
|
||||||
|
EnableTCPProxy: true,
|
||||||
|
EnableUDPProxy: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
return fmt.Errorf("failed to create TUN device: %v", err)
|
return fmt.Errorf("failed to create TUN device: %v", err)
|
||||||
}
|
}
|
||||||
|
// s.proxyManager.SetTNet(s.tnet)
|
||||||
s.proxyManager.SetTNet(s.tnet)
|
|
||||||
s.TunnelIP = tunnelIP.String()
|
s.TunnelIP = tunnelIP.String()
|
||||||
|
|
||||||
// Create WireGuard device
|
// Create WireGuard device
|
||||||
@@ -1256,7 +1261,7 @@ func (s *WireGuardService) ReplaceNetstack() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update proxy manager with new tnet and restart
|
// Update proxy manager with new tnet and restart
|
||||||
s.proxyManager.SetTNet(s.tnet)
|
// s.proxyManager.SetTNet(s.tnet)
|
||||||
s.proxyManager.Start()
|
s.proxyManager.Start()
|
||||||
|
|
||||||
s.proxyManager.PrintTargets()
|
s.proxyManager.PrintTargets()
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ func (s *Server) handleConnections() {
|
|||||||
copy(responsePacket[5:13], buffer[5:13])
|
copy(responsePacket[5:13], buffer[5:13])
|
||||||
|
|
||||||
// Log response being sent for debugging
|
// Log response being sent for debugging
|
||||||
logger.Debug("%sSending response to %s", s.outputPrefix, addr.String())
|
// logger.Debug("%sSending response to %s", s.outputPrefix, addr.String())
|
||||||
|
|
||||||
// Send the response packet - handle both regular UDP and netstack UDP
|
// Send the response packet - handle both regular UDP and netstack UDP
|
||||||
if s.useNetstack {
|
if s.useNetstack {
|
||||||
@@ -235,7 +235,7 @@ func (s *Server) handleConnections() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("%sError sending response: %v", s.outputPrefix, err)
|
logger.Error("%sError sending response: %v", s.outputPrefix, err)
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("%sResponse sent successfully", s.outputPrefix)
|
// logger.Debug("%sResponse sent successfully", s.outputPrefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user