@@ -55,22 +55,28 @@ func (u *nodeUpdater) updateLoop() {
5555u .logger .Debug (context .Background (),"closing nodeUpdater updateLoop" )
5656return
5757}
58- node := u .nodeLocked ()
5958u .dirty = false
6059u .phase = configuring
6160u .Broadcast ()
6261
62+ callback := u .callback
63+ if callback == nil {
64+ u .logger .Debug (context .Background (),"skipped sending node; no node callback" )
65+ continue
66+ }
67+
6368// We cannot reach nodes without DERP for discovery. Therefore, there is no point in sending
6469// the node without this, and we can save ourselves from churn in the tailscale/wireguard
6570// layer.
71+ node := u .nodeLocked ()
6672if node .PreferredDERP == 0 {
6773u .logger .Debug (context .Background (),"skipped sending node; no PreferredDERP" ,slog .F ("node" ,node ))
6874continue
6975}
7076
7177u .L .Unlock ()
7278u .logger .Debug (context .Background (),"calling nodeUpdater callback" ,slog .F ("node" ,node ))
73- u . callback (node )
79+ callback (node )
7480u .L .Lock ()
7581}
7682}
@@ -155,7 +161,7 @@ func (u *nodeUpdater) setDERPForcedWebsocket(region int, reason string) {
155161}
156162
157163// setStatus handles the status callback from the wireguard engine to learn about new endpoints
158- // (e.g. discovered by STUN)
164+ // (e.g. discovered by STUN). u.L MUST NOT be held
159165func (u * nodeUpdater )setStatus (s * wgengine.Status ,err error ) {
160166u .logger .Debug (context .Background (),"wireguard status" ,slog .F ("status" ,s ),slog .Error (err ))
161167if err != nil {
@@ -181,6 +187,7 @@ func (u *nodeUpdater) setStatus(s *wgengine.Status, err error) {
181187u .Broadcast ()
182188}
183189
190+ // setAddresses sets the local addresses for the node. u.L MUST NOT be held.
184191func (u * nodeUpdater )setAddresses (ips []netip.Prefix ) {
185192u .L .Lock ()
186193defer u .L .Unlock ()
@@ -192,3 +199,13 @@ func (u *nodeUpdater) setAddresses(ips []netip.Prefix) {
192199u .dirty = true
193200u .Broadcast ()
194201}
202+
203+ // setCallback sets the callback for node changes. It also triggers a call
204+ // for the current node immediately. u.L MUST NOT be held.
205+ func (u * nodeUpdater )setCallback (callback func (node * Node )) {
206+ u .L .Lock ()
207+ defer u .L .Unlock ()
208+ u .callback = callback
209+ u .dirty = true
210+ u .Broadcast ()
211+ }