diff --git a/client/iface/wgproxy/bind/proxy.go b/client/iface/wgproxy/bind/proxy.go index 4f26eec1a..be690ed4f 100644 --- a/client/iface/wgproxy/bind/proxy.go +++ b/client/iface/wgproxy/bind/proxy.go @@ -136,14 +136,8 @@ func (p *ProxyBind) CloseConn() error { return p.close() } -// InjectPacket writes b to the remote peer over the underlying transport. -func (p *ProxyBind) InjectPacket(b []byte) error { - if p.remoteConn == nil { - return errors.New("proxy not started") - } - if _, err := p.remoteConn.Write(b); err != nil { - return err - } +// InjectPacket is a no-op for the userspace proxy: first-packet reinjection is kernel-only. +func (p *ProxyBind) InjectPacket(_ []byte) error { return nil } diff --git a/client/iface/wgproxy/proxy.go b/client/iface/wgproxy/proxy.go index 42568158d..40346bc15 100644 --- a/client/iface/wgproxy/proxy.go +++ b/client/iface/wgproxy/proxy.go @@ -20,6 +20,7 @@ type Proxy interface { SetDisconnectListener(disconnected func()) // InjectPacket writes a raw packet directly to the remote peer over the underlying transport, - // bypassing WireGuard. Used to replay the captured lazyconn handshake initiation. + // bypassing WireGuard. Used to replay the captured lazyconn handshake initiation. Only the + // kernel-mode proxies act on it; the userspace proxy is a no-op since reinjection is kernel-only. InjectPacket(b []byte) error } diff --git a/client/internal/lazyconn/activity/lazy_conn.go b/client/internal/lazyconn/activity/lazy_conn.go index 4fcf2a8e8..2564a9905 100644 --- a/client/internal/lazyconn/activity/lazy_conn.go +++ b/client/internal/lazyconn/activity/lazy_conn.go @@ -4,22 +4,15 @@ import ( "context" "io" "net" - "slices" - "sync" "time" ) // lazyConn detects activity when WireGuard attempts to send packets. -// It does not deliver packets, only signals that activity occurred. The first packet WG-go writes -// is captured so the lazyconn manager can reinject it through the real bind endpoint, avoiding the -// handshake-retry wait when the real connection comes up. +// It does not deliver packets, only signals that activity occurred. type lazyConn struct { activityCh chan struct{} ctx context.Context cancel context.CancelFunc - - mu sync.Mutex - capturedPacket []byte } // newLazyConn creates a new lazyConn for activity detection. @@ -38,19 +31,12 @@ func (c *lazyConn) Read(_ []byte) (n int, err error) { return 0, io.EOF } -// Write signals activity detection when ICEBind routes packets to this endpoint. The first packet -// is cloned and stored so it can be replayed on the real endpoint once ICE/relay comes up. +// Write signals activity detection when ICEBind routes packets to this endpoint. func (c *lazyConn) Write(b []byte) (n int, err error) { if c.ctx.Err() != nil { return 0, io.EOF } - c.mu.Lock() - if c.capturedPacket == nil && len(b) > 0 { - c.capturedPacket = slices.Clone(b) - } - c.mu.Unlock() - select { case c.activityCh <- struct{}{}: default: @@ -59,14 +45,6 @@ func (c *lazyConn) Write(b []byte) (n int, err error) { return len(b), nil } -// CapturedPacket returns the first packet written to this connection, or nil. The slice aliases -// the internal buffer, which is written once and never mutated, so the caller must not modify it. -func (c *lazyConn) CapturedPacket() []byte { - c.mu.Lock() - defer c.mu.Unlock() - return c.capturedPacket -} - // ActivityChan returns the channel that signals when activity is detected. func (c *lazyConn) ActivityChan() <-chan struct{} { return c.activityCh diff --git a/client/internal/lazyconn/activity/listener_bind.go b/client/internal/lazyconn/activity/listener_bind.go index eceb8728e..72a0cfc76 100644 --- a/client/internal/lazyconn/activity/listener_bind.go +++ b/client/internal/lazyconn/activity/listener_bind.go @@ -118,21 +118,15 @@ func (d *BindListener) ReadPackets() { d.peerCfg.Log.Infof("exit from activity listener") } - // Leave the peer in place. ConfigureWGEndpoint will UpdatePeer with the real endpoint; - // removing the peer here wipes wireguard-go's staged queue and drops the user packet that - // triggered activation. + d.peerCfg.Log.Debugf("removing lazy endpoint for peer %s", d.peerCfg.PublicKey) _ = d.lazyConn.Close() d.bind.RemoveEndpoint(d.fakeIP) d.done.Done() } -// CapturedPacket returns the first packet that triggered activity, or nil if none was captured. -// Safe to call after ReadPackets returns. +// CapturedPacket is unused in userspace bind mode: first-packet reinjection is kernel-only. func (d *BindListener) CapturedPacket() []byte { - if d.lazyConn == nil { - return nil - } - return d.lazyConn.CapturedPacket() + return nil } // Close stops the listener and cleans up resources. diff --git a/client/internal/lazyconn/activity/listener_bind_test.go b/client/internal/lazyconn/activity/listener_bind_test.go index 6e45bc605..0288e1d49 100644 --- a/client/internal/lazyconn/activity/listener_bind_test.go +++ b/client/internal/lazyconn/activity/listener_bind_test.go @@ -213,7 +213,7 @@ func TestManager_BindMode(t *testing.T) { select { case ev := <-mgr.OnActivityChan: assert.Equal(t, cfg.PeerConnID, ev.PeerConnID, "Received peer connection ID should match") - assert.Equal(t, []byte{0x01, 0x02, 0x03}, ev.FirstPacket, "First packet should be captured for reinjection") + assert.Nil(t, ev.FirstPacket, "Bind mode does not capture packets: reinjection is kernel-only") case <-time.After(2 * time.Second): t.Fatal("timeout waiting for activity notification") }