wgengine{,/magicsock}: add DERP hooks for filtering+sending packets
Add two small APIs to support out-of-tree projects to exchange custom signaling messages over DERP without requiring disco protocol extensions: - OnDERPRecv callback on magicsock.Options / wgengine.Config: called for every non-disco DERP packet before the peer map lookup, allowing callers to intercept packets from unknown peers that would otherwise be dropped. - SendDERPPacketTo method on magicsock.Conn: sends arbitrary bytes to a node key via a DERP region, creating the connection if needed. Thin wrapper around the existing internal sendAddr. Also allow netstack.Start to accept a nil LocalBackend for use cases that wire up TCP/UDP handlers directly without a full LocalBackend. Updates tailscale/corp#24454 Change-Id: I99a523ef281625b8c0024a963f5f5bf5d8792c17 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
4c7c1091ba
commit
073a9a8c9e
@@ -725,6 +725,10 @@ func (c *Conn) processDERPReadResult(dm derpReadResult, b []byte) (n int, ep *en
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.onDERPRecv != nil && c.onDERPRecv(regionID, dm.src, b[:n]) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
var ok bool
|
var ok bool
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
ep, ok = c.peerMap.endpointForNodeKey(dm.src)
|
ep, ok = c.peerMap.endpointForNodeKey(dm.src)
|
||||||
@@ -745,6 +749,15 @@ func (c *Conn) processDERPReadResult(dm derpReadResult, b []byte) (n int, ep *en
|
|||||||
return n, ep
|
return n, ep
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendDERPPacketTo sends an arbitrary packet to the given node key via
|
||||||
|
// the DERP relay for the given region. It creates the DERP connection
|
||||||
|
// to the region if one doesn't already exist.
|
||||||
|
func (c *Conn) SendDERPPacketTo(dstKey key.NodePublic, regionID int, pkt []byte) (sent bool, err error) {
|
||||||
|
return c.sendAddr(
|
||||||
|
netip.AddrPortFrom(tailcfg.DerpMagicIPAddr, uint16(regionID)),
|
||||||
|
dstKey, pkt, false, false)
|
||||||
|
}
|
||||||
|
|
||||||
// SetOnlyTCP443 set whether the magicsock connection is restricted
|
// SetOnlyTCP443 set whether the magicsock connection is restricted
|
||||||
// to only using TCP port 443 outbound. If true, no UDP is allowed,
|
// to only using TCP port 443 outbound. If true, no UDP is allowed,
|
||||||
// no STUN checks are performend, etc.
|
// no STUN checks are performend, etc.
|
||||||
|
|||||||
@@ -163,10 +163,11 @@ type Conn struct {
|
|||||||
derpActiveFunc func()
|
derpActiveFunc func()
|
||||||
idleFunc func() time.Duration // nil means unknown
|
idleFunc func() time.Duration // nil means unknown
|
||||||
testOnlyPacketListener nettype.PacketListener
|
testOnlyPacketListener nettype.PacketListener
|
||||||
noteRecvActivity func(key.NodePublic) // or nil, see Options.NoteRecvActivity
|
noteRecvActivity func(key.NodePublic) // or nil, see Options.NoteRecvActivity
|
||||||
netMon *netmon.Monitor // must be non-nil
|
onDERPRecv func(int, key.NodePublic, []byte) bool // or nil, see Options.OnDERPRecv
|
||||||
health *health.Tracker // or nil
|
netMon *netmon.Monitor // must be non-nil
|
||||||
controlKnobs *controlknobs.Knobs // or nil
|
health *health.Tracker // or nil
|
||||||
|
controlKnobs *controlknobs.Knobs // or nil
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// No locking required to access these fields, either because
|
// No locking required to access these fields, either because
|
||||||
@@ -502,6 +503,13 @@ type Options struct {
|
|||||||
// leave it zero, in which case a new disco key is generated per
|
// leave it zero, in which case a new disco key is generated per
|
||||||
// Tailscale start and kept only in memory.
|
// Tailscale start and kept only in memory.
|
||||||
ForceDiscoKey key.DiscoPrivate
|
ForceDiscoKey key.DiscoPrivate
|
||||||
|
|
||||||
|
// OnDERPRecv, if non-nil, is called for every non-disco packet
|
||||||
|
// received from DERP before the peer map lookup. If it returns
|
||||||
|
// true, the packet is considered handled and is not passed to
|
||||||
|
// WireGuard. The pkt slice is borrowed and must be copied if
|
||||||
|
// the callee needs to retain it.
|
||||||
|
OnDERPRecv func(regionID int, src key.NodePublic, pkt []byte) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Options) logf() logger.Logf {
|
func (o *Options) logf() logger.Logf {
|
||||||
@@ -640,6 +648,7 @@ func NewConn(opts Options) (*Conn, error) {
|
|||||||
c.idleFunc = opts.IdleFunc
|
c.idleFunc = opts.IdleFunc
|
||||||
c.testOnlyPacketListener = opts.TestOnlyPacketListener
|
c.testOnlyPacketListener = opts.TestOnlyPacketListener
|
||||||
c.noteRecvActivity = opts.NoteRecvActivity
|
c.noteRecvActivity = opts.NoteRecvActivity
|
||||||
|
c.onDERPRecv = opts.OnDERPRecv
|
||||||
|
|
||||||
// Set up publishers and subscribers. Subscribe calls must return before
|
// Set up publishers and subscribers. Subscribe calls must return before
|
||||||
// NewConn otherwise published events can be missed.
|
// NewConn otherwise published events can be missed.
|
||||||
|
|||||||
@@ -603,15 +603,25 @@ type LocalBackend = any
|
|||||||
|
|
||||||
// Start sets up all the handlers so netstack can start working. Implements
|
// Start sets up all the handlers so netstack can start working. Implements
|
||||||
// wgengine.FakeImpl.
|
// wgengine.FakeImpl.
|
||||||
|
//
|
||||||
|
// The provided LocalBackend interface can be either nil, for special case users
|
||||||
|
// of netstack that don't have a LocalBackend, or a non-nil
|
||||||
|
// *ipnlocal.LocalBackend. Any other type will cause Start to panic.
|
||||||
|
//
|
||||||
|
// Start currently (2026-03-11) never returns a non-nil error, but maybe it did
|
||||||
|
// in the past and maybe it will in the future.
|
||||||
func (ns *Impl) Start(b LocalBackend) error {
|
func (ns *Impl) Start(b LocalBackend) error {
|
||||||
if b == nil {
|
switch b := b.(type) {
|
||||||
panic("nil LocalBackend interface")
|
case nil:
|
||||||
|
// No backend, so just continue with ns.lb unset.
|
||||||
|
case *ipnlocal.LocalBackend:
|
||||||
|
if b == nil {
|
||||||
|
panic("nil LocalBackend")
|
||||||
|
}
|
||||||
|
ns.lb = b
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unexpected type for LocalBackend: %T", b))
|
||||||
}
|
}
|
||||||
lb := b.(*ipnlocal.LocalBackend)
|
|
||||||
if lb == nil {
|
|
||||||
panic("nil LocalBackend")
|
|
||||||
}
|
|
||||||
ns.lb = lb
|
|
||||||
tcpFwd := tcp.NewForwarder(ns.ipstack, tcpRXBufDefSize, maxInFlightConnectionAttempts(), ns.acceptTCP)
|
tcpFwd := tcp.NewForwarder(ns.ipstack, tcpRXBufDefSize, maxInFlightConnectionAttempts(), ns.acceptTCP)
|
||||||
udpFwd := udp.NewForwarder(ns.ipstack, ns.acceptUDPNoICMP)
|
udpFwd := udp.NewForwarder(ns.ipstack, ns.acceptUDPNoICMP)
|
||||||
ns.ipstack.SetTransportProtocolHandler(tcp.ProtocolNumber, ns.wrapTCPProtocolHandler(tcpFwd.HandlePacket))
|
ns.ipstack.SetTransportProtocolHandler(tcp.ProtocolNumber, ns.wrapTCPProtocolHandler(tcpFwd.HandlePacket))
|
||||||
|
|||||||
@@ -272,6 +272,13 @@ type Config struct {
|
|||||||
// leave it zero, in which case a new disco key is generated per
|
// leave it zero, in which case a new disco key is generated per
|
||||||
// Tailscale start and kept only in memory.
|
// Tailscale start and kept only in memory.
|
||||||
ForceDiscoKey key.DiscoPrivate
|
ForceDiscoKey key.DiscoPrivate
|
||||||
|
|
||||||
|
// OnDERPRecv, if non-nil, is called for every non-disco packet
|
||||||
|
// received from DERP before the peer map lookup. If it returns
|
||||||
|
// true, the packet is considered handled and is not passed to
|
||||||
|
// WireGuard. The pkt slice is borrowed and must be copied if
|
||||||
|
// the callee needs to retain it.
|
||||||
|
OnDERPRecv func(regionID int, src key.NodePublic, pkt []byte) (handled bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFakeUserspaceEngine returns a new userspace engine for testing.
|
// NewFakeUserspaceEngine returns a new userspace engine for testing.
|
||||||
@@ -441,6 +448,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
|||||||
ControlKnobs: conf.ControlKnobs,
|
ControlKnobs: conf.ControlKnobs,
|
||||||
PeerByKeyFunc: e.PeerByKey,
|
PeerByKeyFunc: e.PeerByKey,
|
||||||
ForceDiscoKey: conf.ForceDiscoKey,
|
ForceDiscoKey: conf.ForceDiscoKey,
|
||||||
|
OnDERPRecv: conf.OnDERPRecv,
|
||||||
}
|
}
|
||||||
if buildfeatures.HasLazyWG {
|
if buildfeatures.HasLazyWG {
|
||||||
magicsockOpts.NoteRecvActivity = e.noteRecvActivity
|
magicsockOpts.NoteRecvActivity = e.noteRecvActivity
|
||||||
|
|||||||
Reference in New Issue
Block a user