control/controlclient: take mapsession and release lock early in sub (#19192)

The disco key subscriber could deadlock in a scenario where a self node
update came through the control path into the mapSession after the disco
key subscriber had taken the lock, but before it had pushed the netmap
change, as both the subscriber and onSelfNodeChanged needs the
controlclient lock.

The subscriber can safely take the mapsession as the changequeue has its
own lock for inserting records, and also checks if the queue has been
closed before inserting.

Updates #12639

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
main
Claus Lensbøl 2 weeks ago committed by GitHub
parent 61ac021c5d
commit 4334dfa7d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 15
      control/controlclient/direct.go

@ -361,11 +361,16 @@ func NewDirect(opts Options) (*Direct, error) {
c.controlTimePub = eventbus.Publish[ControlTime](c.busClient) c.controlTimePub = eventbus.Publish[ControlTime](c.busClient)
discoKeyPub := eventbus.Publish[events.PeerDiscoKeyUpdate](c.busClient) discoKeyPub := eventbus.Publish[events.PeerDiscoKeyUpdate](c.busClient)
eventbus.SubscribeFunc(c.busClient, func(update events.DiscoKeyAdvertisement) { eventbus.SubscribeFunc(c.busClient, func(update events.DiscoKeyAdvertisement) {
c.mu.Lock()
defer c.mu.Unlock()
c.logf("controlclient direct: got TSMP disco key advertisement from %v via eventbus", update.Src) c.logf("controlclient direct: got TSMP disco key advertisement from %v via eventbus", update.Src)
if c.streamingMapSession != nil { var nm *netmap.NetworkMap
nm := c.streamingMapSession.netmap() c.mu.Lock()
sess := c.streamingMapSession
if sess != nil {
nm = c.streamingMapSession.netmap()
}
c.mu.Unlock()
if sess != nil {
peer, ok := nm.PeerByTailscaleIP(update.Src) peer, ok := nm.PeerByTailscaleIP(update.Src)
if !ok { if !ok {
return return
@ -375,7 +380,7 @@ func NewDirect(opts Options) (*Direct, error) {
// If we update without error, return. If the err indicates that the // If we update without error, return. If the err indicates that the
// mapSession has gone away, we want to fall back to pushing the key // mapSession has gone away, we want to fall back to pushing the key
// further down the chain. // further down the chain.
if err := c.streamingMapSession.updateDiscoForNode( if err := sess.updateDiscoForNode(
peer.ID(), peer.Key(), update.Key, time.Now(), false); err == nil || peer.ID(), peer.Key(), update.Key, time.Now(), false); err == nil ||
!errors.Is(err, ErrChangeQueueClosed) { !errors.Is(err, ErrChangeQueueClosed) {
return return

Loading…
Cancel
Save