mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-28 09:39:05 +02:00
pqkem: recover from persistent rekey failure by re-bootstrapping over signal
OnRekeyFailed now re-runs the KEM bootstrap over Signal (conn.RequestReoffer -> handshaker.SendOffer) instead of only logging: a fresh signalling offer starts a new exchange that overwrites the stalled PSK on both sides, resyncing after a persistent data-path desync. Chosen over a responder-side awaitingAck revert (which fights the confirm-less ack timing) and a full tunnel teardown (heavier). The tunnel stays up on the previous PSK meanwhile since Signal is independent of the broken data path.
This commit is contained in:
@@ -689,7 +689,18 @@ func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, mgmtURL *url.URL)
|
|||||||
if pqErr != nil {
|
if pqErr != nil {
|
||||||
log.Errorf("pqkem: transport bind failed, exchange disabled: %v", pqErr)
|
log.Errorf("pqkem: transport bind failed, exchange disabled: %v", pqErr)
|
||||||
} else {
|
} else {
|
||||||
e.pqkemManager = pqkem.NewManager(pqkem.LocalID(publicKey.String()), pqCallbackHandler{wg: e.wgInterface}, pqkem.NewLogger())
|
cbHandler := pqCallbackHandler{
|
||||||
|
wg: e.wgInterface,
|
||||||
|
// On a persistent rekey failure, re-bootstrap the KEM over Signal: a
|
||||||
|
// fresh signalling offer starts a new exchange that overwrites the
|
||||||
|
// stalled PSK on both sides, recovering from a data-path desync.
|
||||||
|
reoffer: func(remoteKey string) {
|
||||||
|
if conn, ok := e.peerStore.PeerConn(remoteKey); ok {
|
||||||
|
conn.RequestReoffer()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
e.pqkemManager = pqkem.NewManager(pqkem.LocalID(publicKey.String()), cbHandler, pqkem.NewLogger())
|
||||||
e.pqkemManager.Start(tr)
|
e.pqkemManager.Start(tr)
|
||||||
log.Infof("pqkem: enabled (udp port %d on overlay %s)", e.pqkemManager.LocalPort(), e.config.WgAddr.IP)
|
log.Infof("pqkem: enabled (udp port %d on overlay %s)", e.pqkemManager.LocalPort(), e.config.WgAddr.IP)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -726,6 +726,22 @@ func (conn *Conn) onGuardEvent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestReoffer sends a fresh signalling offer for the peer, re-running the
|
||||||
|
// post-quantum bootstrap over Signal. Used to recover from a persistent data-path
|
||||||
|
// rekey failure: a new exchange overwrites the stalled PSK on both sides. No-op if the
|
||||||
|
// connection is not open yet.
|
||||||
|
func (conn *Conn) RequestReoffer() {
|
||||||
|
conn.mu.Lock()
|
||||||
|
h := conn.handshaker
|
||||||
|
conn.mu.Unlock()
|
||||||
|
if h == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.SendOffer(); err != nil {
|
||||||
|
conn.Log.Debugf("pqkem: recovery re-offer failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (conn *Conn) onWGDisconnected(watcherCtx context.Context) {
|
func (conn *Conn) onWGDisconnected(watcherCtx context.Context) {
|
||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
defer conn.mu.Unlock()
|
defer conn.mu.Unlock()
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ type pqPresharedKeySetter interface {
|
|||||||
// engine-side implementation of pqkem.CallbackHandler.
|
// engine-side implementation of pqkem.CallbackHandler.
|
||||||
type pqCallbackHandler struct {
|
type pqCallbackHandler struct {
|
||||||
wg pqPresharedKeySetter
|
wg pqPresharedKeySetter
|
||||||
|
// reoffer re-bootstraps the KEM over Signal for a peer (a fresh signalling offer)
|
||||||
|
// to recover from a persistent data-path rekey failure. Nil disables recovery.
|
||||||
|
reoffer func(remoteKey string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnNewPSKReady programs the freshly derived PSK for the peer (updateOnly: a no-op
|
// OnNewPSKReady programs the freshly derived PSK for the peer (updateOnly: a no-op
|
||||||
@@ -32,10 +35,16 @@ func (h pqCallbackHandler) OnNewPSKReady(remoteID pqkem.RemoteID, psk pqkem.PSK)
|
|||||||
return h.wg.SetPresharedKey(string(remoteID), wgtypes.Key(psk), true)
|
return h.wg.SetPresharedKey(string(remoteID), wgtypes.Key(psk), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnRekeyFailed reports a failed PQ (re)key convergence.
|
// OnRekeyFailed reports a failed PQ (re)key convergence and re-bootstraps the KEM over
|
||||||
// TODO(NET-1406): tear the peer connection down / trigger ICE reconnect.
|
// Signal to recover: a fresh signalling offer starts a new exchange that overwrites the
|
||||||
|
// stalled PSK on both sides, resyncing after a persistent data-path desync. The tunnel
|
||||||
|
// stays up on the previous PSK meanwhile (the Signal channel is independent of the
|
||||||
|
// broken data path).
|
||||||
func (h pqCallbackHandler) OnRekeyFailed(remoteID pqkem.RemoteID) error {
|
func (h pqCallbackHandler) OnRekeyFailed(remoteID pqkem.RemoteID) error {
|
||||||
log.Warnf("pqkem: post-quantum rekey failed for peer %s", remoteID)
|
log.Warnf("pqkem: post-quantum rekey failed for peer %s, re-bootstrapping over signal", remoteID)
|
||||||
|
if h.reoffer != nil {
|
||||||
|
h.reoffer(string(remoteID))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user