diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md deleted file mode 100644 index affb887..0000000 --- a/IMPLEMENTATION.md +++ /dev/null @@ -1,236 +0,0 @@ -# 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/ diff --git a/examples/netstack-proxying/main.go b/examples/netstack-proxying/main.go deleted file mode 100644 index 9d97b44..0000000 --- a/examples/netstack-proxying/main.go +++ /dev/null @@ -1,181 +0,0 @@ -// 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() -} diff --git a/netstack2/tun.go b/netstack2/tun.go index f40e26c..ea58330 100644 --- a/netstack2/tun.go +++ b/netstack2/tun.go @@ -114,6 +114,10 @@ func CreateNetTUNWithOptions(localAddresses, dnsServers []netip.Addr, mtu int, o return nil, nil, fmt.Errorf("CreateNIC: %v", tcpipErr) } + if err := dev.stack.SetForwardingDefaultAndAllNICs(ipv4.ProtocolNumber, true); err != nil { + return nil, nil, fmt.Errorf("set ipv4 forwarding: %s", err) + } + for _, ip := range localAddresses { var protoNumber tcpip.NetworkProtocolNumber if ip.Is4() { @@ -155,52 +159,70 @@ func CreateNetTUNWithOptions(localAddresses, dnsServers []netip.Addr, mtu int, o // 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) + tcpipErr = dev.stack.CreateNICWithOptions(2, dev.proxyEp, stack.NICOptions{ + Disabled: false, + // If no queueing discipline was specified + // provide a stub implementation that just + // delegates to the lower link endpoint. + QDisc: nil, + }) if tcpipErr != nil { return nil, nil, fmt.Errorf("CreateNIC 2 (proxy): %v", tcpipErr) } // Enable promiscuous mode ONLY on NIC 2 + // This allows the NIC to accept packets destined for any IP address 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 + // This allows the stack to send packets from any address, not just owned addresses 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) - } + // // Add a wildcard IPv4 address covering the 10.0.0.0/8 space so the stack can + // // synthesize temporary endpoints for any 10.x.y.z destination. This mimics + // // the tun2socks behaviour and is required once promiscuous+spoofing are turned on. + // wildcardAddr := tcpip.ProtocolAddress{ + // Protocol: ipv4.ProtocolNumber, + // AddressWithPrefix: tcpip.AddressWithPrefix{ + // Address: tcpip.AddrFrom4([4]byte{10, 0, 0, 1}), + // PrefixLen: 8, + // }, + // } + // if tcpipErr = dev.stack.AddProtocolAddress(2, wildcardAddr, stack.AddressProperties{ + // PEB: stack.CanBePrimaryEndpoint, + // }); tcpipErr != nil { + // return nil, nil, fmt.Errorf("Add wildcard proxy address: %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) - } + // // Keep the real service IP (10.20.20.1/24) so existing clients that target the + // // gateway explicitly still resolve as before. + // proxyAddr := netip.MustParseAddr("10.20.20.1") + // protoAddr := tcpip.ProtocolAddress{ + // Protocol: ipv4.ProtocolNumber, + // AddressWithPrefix: tcpip.AddressWithPrefix{ + // Address: tcpip.AddrFromSlice(proxyAddr.AsSlice()), + // PrefixLen: 24, + // }, + // } + // if tcpipErr = dev.stack.AddProtocolAddress(2, protoAddr, stack.AddressProperties{}); tcpipErr != nil { + // return nil, nil, fmt.Errorf("Add proxy service address: %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, + Destination: header.IPv4EmptySubnet, NIC: 2, }) } @@ -268,34 +290,21 @@ func (tun *netTun) Write(buf [][]byte, offset int) (int, error) { switch packet[0] >> 4 { case 4: - // Parse IPv4 header to check destination - if len(packet) >= header.IPv4MinimumSize { - ipv4Header := header.IPv4(packet) - dstIP := ipv4Header.DestinationAddress() + // // 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) - } - } - } + // // 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 + // } + // } + // } targetEp.InjectInbound(header.IPv4ProtocolNumber, pkb) case 6: // For IPv6, always use NIC 1 for now @@ -398,14 +407,16 @@ func (tun *netTun) WriteNotify() { } // Handle notifications from proxy endpoint (NIC 2) if it exists - if tun.proxyEp != nil { - pkt = tun.proxyEp.Read() - if pkt != nil { - view := pkt.ToView() - pkt.DecRef() - tun.incomingPacket <- view - } - } + // // These are response packets from the proxied connections that need to go back to WireGuard + // if tun.proxyEp != nil { + // pkt = tun.proxyEp.Read() + // if pkt != nil { + // view := pkt.ToView() + // pkt.DecRef() + // tun.incomingPacket <- view + // return + // } + // } } func (tun *netTun) Close() error {