From a11c929b28914da592515bf37248fc19ec2c06b2 Mon Sep 17 00:00:00 2001 From: riccardom Date: Fri, 7 Aug 2026 13:09:06 +0200 Subject: [PATCH] [client] pqkem: don't let the responder's delayed update revert the PSK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- client/internal/peer/endpoint.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/client/internal/peer/endpoint.go b/client/internal/peer/endpoint.go index 9ba1efb6e..c17e59ff0 100644 --- a/client/internal/peer/endpoint.go +++ b/client/internal/peer/endpoint.go @@ -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) } }