wgengine/magicsock: only run derpActiveFunc after connecting to DERP (#18814)

derpActiveFunc was being called immediately as a bare goroutine,
before startGate was resolved. For the firstDerp case, startGate
is c.derpStarted which only closes after dc.Connect() completes,
so derpActiveFunc was firing before the DERP connection existed.

We now block it with the same logic used by runDerpReader and by
runDerpWriter.

Updates: #18810

Signed-off-by: Fernando Serboncini <fserb@tailscale.com>
main
Fernando Serboncini 2 months ago committed by GitHub
parent 15836e5624
commit da90ea664d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 9
      wgengine/magicsock/derp.go
  2. 51
      wgengine/magicsock/magicsock_test.go

@ -436,7 +436,14 @@ func (c *Conn) derpWriteChanForRegion(regionID int, peer key.NodePublic) chan de
go c.runDerpReader(ctx, regionID, dc, wg, startGate)
go c.runDerpWriter(ctx, dc, ch, wg, startGate)
go c.derpActiveFunc()
go func() {
select {
case <-ctx.Done():
return
case <-startGate:
c.derpActiveFunc()
}
}()
return ad.writeCh
}

@ -530,6 +530,57 @@ func TestPickDERPFallback(t *testing.T) {
// have fixed DERP fallback logic.
}
// TestDERPActiveFuncCalledAfterConnect verifies that DERPActiveFunc is not
// called until the DERP connection is actually established (i.e. after
// startGate / derpStarted is closed).
func TestDERPActiveFuncCalledAfterConnect(t *testing.T) {
derpMap, cleanup := runDERPAndStun(t, t.Logf, localhostListener{}, netaddr.IPv4(127, 0, 0, 1))
defer cleanup()
bus := eventbustest.NewBus(t)
netMon, err := netmon.New(bus, t.Logf)
if err != nil {
t.Fatal(err)
}
resultCh := make(chan bool, 1)
var conn *Conn
conn, err = NewConn(Options{
Logf: t.Logf,
NetMon: netMon,
EventBus: bus,
HealthTracker: health.NewTracker(bus),
Metrics: new(usermetric.Registry),
DisablePortMapper: true,
TestOnlyPacketListener: localhostListener{},
EndpointsFunc: func([]tailcfg.Endpoint) {},
DERPActiveFunc: func() {
// derpStarted should already be closed when DERPActiveFunc is called.
select {
case <-conn.derpStarted:
resultCh <- true
default:
resultCh <- false
}
},
})
if err != nil {
t.Fatal(err)
}
defer conn.Close()
conn.SetDERPMap(derpMap)
if err := conn.SetPrivateKey(key.NewNode()); err != nil {
t.Fatal(err)
}
if ok := <-resultCh; !ok {
t.Error("DERPActiveFunc was called before DERP connection was established")
}
}
// TestDeviceStartStop exercises the startup and shutdown logic of
// wireguard-go, which is intimately intertwined with magicsock's own
// lifecycle. We seem to be good at generating deadlocks here, so if

Loading…
Cancel
Save