[client] pqkem: don't let the responder's delayed update revert the PSK

The responder configures WireGuard with endpoint=nil first, then a delayed
update (scheduleDelayedUpdate) applies the real endpoint after fallbackDelay.
It captured the preshared key at schedule time and re-applied it. With the
post-quantum exchange the PSK can change within that window (a fresher PSK
derived and applied via SetPresharedKey), so re-applying the captured one
reverted WireGuard to a key the remote peer no longer used — a mismatch that
stalled the handshake until the WGWatcher timeout forced a retry (~30s).

Pass a nil PSK in the delayed update so it only sets the endpoint and leaves
the current PSK in place; the latest SetPresharedKey wins.
This commit is contained in:
riccardom
2026-08-07 13:09:06 +02:00
parent 887ecf88dd
commit 64127155f6

View File

@@ -88,7 +88,7 @@ func (e *EndpointUpdater) configureAsResponder(addr *net.UDPAddr, presharedKey *
var ctx context.Context
ctx, e.cancelFunc = context.WithCancel(context.Background())
e.updateWg.Add(1)
go e.scheduleDelayedUpdate(ctx, addr, presharedKey)
go e.scheduleDelayedUpdate(ctx, addr)
if err := e.updateWireGuardPeer(nil, presharedKey); err != nil {
e.waitForCloseTheDelayedUpdate()
@@ -107,8 +107,14 @@ func (e *EndpointUpdater) waitForCloseTheDelayedUpdate() {
e.updateWg.Wait()
}
// scheduleDelayedUpdate waits for the fallback period before updating the endpoint
func (e *EndpointUpdater) scheduleDelayedUpdate(ctx context.Context, addr *net.UDPAddr, presharedKey *wgtypes.Key) {
// scheduleDelayedUpdate waits for the fallback period, then sets the responder's real
// endpoint. It deliberately passes a nil preshared key so it only updates the endpoint
// and leaves the current PSK untouched: the PSK captured when this was scheduled may be
// stale by now (e.g. the post-quantum bootstrap derived a fresher PSK within the
// fallback window, applied via SetPresharedKey), and re-applying the captured one would
// revert WireGuard to a key the remote peer no longer uses — a mismatch that stalls the
// handshake until the next retry.
func (e *EndpointUpdater) scheduleDelayedUpdate(ctx context.Context, addr *net.UDPAddr) {
defer e.updateWg.Done()
t := time.NewTimer(fallbackDelay)
defer t.Stop()
@@ -117,7 +123,7 @@ func (e *EndpointUpdater) scheduleDelayedUpdate(ctx context.Context, addr *net.U
case <-ctx.Done():
return
case <-t.C:
if err := e.updateWireGuardPeer(addr, presharedKey); err != nil {
if err := e.updateWireGuardPeer(addr, nil); err != nil {
e.log.Errorf("failed to update WireGuard peer, address: %s, error: %v", addr, err)
}
}