diff --git a/client/iface/wgproxy/bind/proxy.go b/client/iface/wgproxy/bind/proxy.go index be690ed4f..a786957cb 100644 --- a/client/iface/wgproxy/bind/proxy.go +++ b/client/iface/wgproxy/bind/proxy.go @@ -114,6 +114,10 @@ func (p *ProxyBind) Pause() { } func (p *ProxyBind) RedirectAs(endpoint *net.UDPAddr) { + if p.remoteConn == nil { + return + } + ep, err := addrToEndpoint(endpoint) if err != nil { log.Errorf("failed to start package redirection: %v", err) @@ -125,6 +129,12 @@ func (p *ProxyBind) RedirectAs(endpoint *net.UDPAddr) { p.wgCurrentUsed = ep + // start here (not only in Work) so the first packet already carries the redirected address + if !p.isStarted { + p.isStarted = true + go p.proxyToLocal(p.ctx) + } + p.pausedCond.Signal() p.pausedCond.L.Unlock() } diff --git a/client/iface/wgproxy/ebpf/wrapper.go b/client/iface/wgproxy/ebpf/wrapper.go index a6156a661..56b92e6b9 100644 --- a/client/iface/wgproxy/ebpf/wrapper.go +++ b/client/iface/wgproxy/ebpf/wrapper.go @@ -188,6 +188,10 @@ func (p *ProxyWrapper) Pause() { } func (p *ProxyWrapper) RedirectAs(endpoint *net.UDPAddr) { + if p.remoteConn == nil { + return + } + if endpoint == nil || endpoint.IP == nil { log.Errorf("failed to start package redirection, endpoint is nil") return @@ -215,6 +219,12 @@ func (p *ProxyWrapper) RedirectAs(endpoint *net.UDPAddr) { p.headerCurrentUsed = header p.rawConn = p.selectRawConn(header) + // start here (not only in Work) so the first packet already carries the rewritten headers + if !p.isStarted { + p.isStarted = true + go p.proxyToLocal(p.ctx) + } + p.pausedCond.Signal() p.pausedCond.L.Unlock() } diff --git a/client/iface/wgproxy/proxy.go b/client/iface/wgproxy/proxy.go index 40346bc15..4fa6ce26f 100644 --- a/client/iface/wgproxy/proxy.go +++ b/client/iface/wgproxy/proxy.go @@ -12,9 +12,10 @@ type Proxy interface { Work() // Work start or resume the proxy Pause() // Pause to forward the packages from remote connection to WireGuard. The opposite way still works. - //RedirectAs resume the forwarding the packages from relayed connection to WireGuard interface if it was paused - //and rewrite the src address to the endpoint address. - //With this logic can avoid the package loss from relayed connections. + //RedirectAs forwards the packages from the relayed connection to the WireGuard interface + //with the src address rewritten to the endpoint address, starting the proxy if needed and + //resuming it if it was paused. Never delivers a packet with the relayed fake address — + //WireGuard would roam to it. RedirectAs(endpoint *net.UDPAddr) CloseConn() error SetDisconnectListener(disconnected func()) diff --git a/client/iface/wgproxy/udp/proxy.go b/client/iface/wgproxy/udp/proxy.go index 783843aba..d0aa6b92e 100644 --- a/client/iface/wgproxy/udp/proxy.go +++ b/client/iface/wgproxy/udp/proxy.go @@ -123,6 +123,10 @@ func (p *WGUDPProxy) Pause() { // RedirectAs start to use the fake sourced raw socket as package sender func (p *WGUDPProxy) RedirectAs(endpoint *net.UDPAddr) { + if p.remoteConn == nil { + return + } + p.pausedCond.L.Lock() defer func() { p.pausedCond.Signal() @@ -145,6 +149,13 @@ func (p *WGUDPProxy) RedirectAs(endpoint *net.UDPAddr) { } p.srcFakerConn = srcFakerConn p.sendPkg = p.srcFakerConn.SendPkg + + // start here (not only in Work) so the first packet already carries the faked source + if !p.isStarted { + p.isStarted = true + go p.proxyToRemote(p.ctx) + go p.proxyToLocal(p.ctx) + } } // InjectPacket writes b to the remote peer over the underlying transport. diff --git a/client/internal/peer/conn.go b/client/internal/peer/conn.go index 09a4e8b02..70117c0fb 100644 --- a/client/internal/peer/conn.go +++ b/client/internal/peer/conn.go @@ -134,6 +134,9 @@ type Conn struct { wgProxyRelay wgproxy.Proxy handshaker *Handshaker + // endpoint of the active ICE path, for redirecting a later relay conn. Guarded by mu. + iceWgEndpoint *net.UDPAddr + guard *guard.Guard wg sync.WaitGroup @@ -470,6 +473,7 @@ func (conn *Conn) onICEConnectionIsReady(priority conntype.ConnPriority, iceConn conn.handleConfigurationFailure(err, wgProxy) return } + conn.iceWgEndpoint = ep wgConfigWorkaround() if conn.wgProxyRelay != nil { @@ -495,6 +499,8 @@ func (conn *Conn) onICEStateDisconnected(sessionChanged bool) { conn.Log.Tracef("ICE connection state changed to disconnected") + conn.iceWgEndpoint = nil + if conn.wgProxyICE != nil { if err := conn.wgProxyICE.CloseConn(); err != nil { conn.Log.Warnf("failed to close deprecated wg proxy conn: %v", err) @@ -577,6 +583,17 @@ func (conn *Conn) onRelayConnectionIsReady(rci RelayConnInfo) { if conn.isICEActive() { conn.Log.Debugf("do not switch to relay because current priority is: %s", conn.currentConnPriority.String()) conn.setRelayedProxy(wgProxy) + + // The remote may already be sending WireGuard traffic over the relay; + // keep consuming it, attributed to the ICE endpoint. Work() would + // attribute it to the relayed fake address and WireGuard would roam there. + if ep := conn.iceWgEndpoint; ep != nil { + conn.Log.Debugf("redirect packets from relayed conn to WireGuard as %s", ep) + wgProxy.RedirectAs(ep) + } else { + conn.Log.Warnf("ICE is active but its wg endpoint is unknown, leaving relayed proxy parked") + } + conn.statusRelay.SetConnected() conn.updateRelayStatus(rci.relayedConn.RemoteAddr().String(), rci.rosenpassPubKey, time.Now()) return