mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-19 23:11:29 +02:00
Scope first-packet reinjection to kernel mode and drop userspace bind capture
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user