diff --git a/service/splittun/proxy/README.md b/service/splittun/proxy/README.md index 13da2e10..d99fed2e 100644 --- a/service/splittun/proxy/README.md +++ b/service/splittun/proxy/README.md @@ -28,13 +28,13 @@ shutdown. ```go // DeciderFunc is called once per new session to determine the upstream -// destination and an optional local address to bind the outgoing connection to. +// destination and an optional local IP to bind the outgoing connection to. // local is the proxy's listen address; peer is the connecting client's address. // Return a non-nil error to reject the session. type DeciderFunc func(local net.Addr, peer net.Addr) ( remoteIP net.IP, remotePort uint16, - localAddr string, // "host:port" to pin source address, or "" for OS default + localIP net.IP, // source IP to pin, or nil for OS default extraInfo any, // optional value attached to the session's ConnContext err error, ) @@ -87,6 +87,15 @@ func NewUDPProxyWithConfig(listenAddr string, network string, decider DeciderFun Both constructors bind the socket and start background goroutines immediately. They return an error if binding fails or if `decider` is nil. +### Address + +```go +func (p *TCPProxy) Addr() net.Addr +func (p *UDPProxy) Addr() net.Addr +``` + +Returns the address the proxy is currently listening on. + ### Configuration ```go @@ -159,8 +168,8 @@ func (p *UDPProxy) Metrics() Metrics ### Transparent TCP proxy (always route to a fixed backend) ```go -decider := func(local, peer net.Addr) (net.IP, uint16, string, any, error) { - return net.ParseIP("192.168.1.10"), 8080, "", nil, nil +decider := func(local, peer net.Addr) (net.IP, uint16, net.IP, any, error) { + return net.ParseIP("192.168.1.10"), 8080, nil, nil, nil } p, err := proxy.NewTCPProxy(":8080", "tcp4", decider, nil) @@ -181,13 +190,13 @@ p.Shutdown(ctx) ### Per-client routing with source-address binding (split tunnelling) ```go -decider := func(local, peer net.Addr) (net.IP, uint16, string, any, error) { +decider := func(local, peer net.Addr) (net.IP, uint16, net.IP, any, error) { host, _, _ := net.SplitHostPort(peer.String()) if isTunnelledIP(host) { // Route through VPN interface, binding source to its local address. - return vpnGatewayIP, 443, "10.0.0.1:0", nil, nil + return vpnGatewayIP, 443, net.ParseIP("10.0.0.1"), nil, nil } - return directGatewayIP, 443, "", nil, nil + return directGatewayIP, 443, nil, nil, nil } p, err := proxy.NewTCPProxy(":443", "tcp4", decider, myLogger) diff --git a/service/splittun/proxy/common.go b/service/splittun/proxy/common.go index 9cc6f326..1ba5acfd 100644 --- a/service/splittun/proxy/common.go +++ b/service/splittun/proxy/common.go @@ -20,10 +20,10 @@ import ( // It returns: // - remoteIP: required upstream IP address // - remotePort: required upstream port -// - localAddr: optional local "host:port" (empty string = OS chooses source) +// - localIP: optional local IP to use as the source address (nil = OS chooses) // - extraInfo: optional user-defined object attached to the session context // - err: non-nil rejects the session -type DeciderFunc func(local net.Addr, peer net.Addr) (remoteIP net.IP, remotePort uint16, localAddr string, extraInfo any, err error) +type DeciderFunc func(local net.Addr, peer net.Addr) (remoteIP net.IP, remotePort uint16, localIP net.IP, extraInfo any, err error) // Logger is the minimal structured logging interface expected by the proxies. // Pass nil to disable all logging. diff --git a/service/splittun/proxy/proxy_test.go b/service/splittun/proxy/proxy_test.go index 48bc829a..7ad9ea51 100644 --- a/service/splittun/proxy/proxy_test.go +++ b/service/splittun/proxy/proxy_test.go @@ -16,17 +16,17 @@ import ( // passThroughDecider always routes to dest. func passThroughDecider(dest string) DeciderFunc { addr, _ := net.ResolveTCPAddr("tcp", dest) - return func(_, _ net.Addr) (net.IP, uint16, string, any, error) { + return func(_, _ net.Addr) (net.IP, uint16, net.IP, any, error) { if addr == nil { - return nil, 0, "", nil, fmt.Errorf("invalid dest %q", dest) + return nil, 0, nil, nil, fmt.Errorf("invalid dest %q", dest) } - return addr.IP, uint16(addr.Port), "", nil, nil + return addr.IP, uint16(addr.Port), nil, nil, nil } } // refuseDecider always rejects sessions. -func refuseDecider(_ net.Addr, _ net.Addr) (net.IP, uint16, string, any, error) { - return nil, 0, "", nil, fmt.Errorf("rejected") +func refuseDecider(_ net.Addr, _ net.Addr) (net.IP, uint16, net.IP, any, error) { + return nil, 0, nil, nil, fmt.Errorf("rejected") } // startTCPEchoServer starts a TCP echo server on a random port. @@ -434,12 +434,12 @@ func TestUDPProxy_MaxSessions(t *testing.T) { // Count how many sessions the decider accepts; reject beyond limit. var accepted atomic.Int32 const limit = 2 - decider := func(local, peer net.Addr) (net.IP, uint16, string, any, error) { + decider := func(local, peer net.Addr) (net.IP, uint16, net.IP, any, error) { if accepted.Load() >= limit { - return nil, 0, "", nil, fmt.Errorf("max sessions") + return nil, 0, nil, nil, fmt.Errorf("max sessions") } accepted.Add(1) - return nil, 0, "", nil, fmt.Errorf("no upstream needed for this test") + return nil, 0, nil, nil, fmt.Errorf("no upstream needed for this test") } cfg := DefaultConfig() diff --git a/service/splittun/proxy/tcp_proxy.go b/service/splittun/proxy/tcp_proxy.go index 7bd8c400..34835309 100644 --- a/service/splittun/proxy/tcp_proxy.go +++ b/service/splittun/proxy/tcp_proxy.go @@ -216,13 +216,8 @@ func (p *TCPProxy) handleConn(clientConn net.Conn) { // DialContext is cancelled immediately if the proxy is shut down. dialer := net.Dialer{Timeout: p.cfg.DialTimeout} - if localAddr != "" { - tcpAddr, resolveErr := net.ResolveTCPAddr(p.network, localAddr) - if resolveErr != nil { - p.log.Errorf("tcp proxy: resolve local addr %q: %v", localAddr, resolveErr) - return - } - dialer.LocalAddr = tcpAddr + if localAddr != nil { + dialer.LocalAddr = &net.TCPAddr{IP: localAddr} } upstreamConn, err := dialer.DialContext(p.shutdownCtx, p.network, destAddr) if err != nil { diff --git a/service/splittun/proxy/udp_proxy.go b/service/splittun/proxy/udp_proxy.go index c68bcae6..7096131e 100644 --- a/service/splittun/proxy/udp_proxy.go +++ b/service/splittun/proxy/udp_proxy.go @@ -235,15 +235,8 @@ func (p *UDPProxy) handlePacket(clientAddr *net.UDPAddr, data []byte) { remoteAddr := &net.UDPAddr{IP: destIP, Port: int(destPort)} var localUDPAddr *net.UDPAddr - if localAddr != "" { - var resolveErr error - localUDPAddr, resolveErr = net.ResolveUDPAddr("udp", localAddr) - if resolveErr != nil { - p.cache.remove(connCtx) - cancel() - p.log.Errorf("udp proxy: resolve local addr %q: %v", localAddr, resolveErr) - return - } + if localAddr != nil { + localUDPAddr = &net.UDPAddr{IP: localAddr} } remoteConn, err := net.DialUDP("udp", localUDPAddr, remoteAddr) if err != nil {