mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-01 04:21:29 +02:00
[client] pqkem: gate controller re-offer to kick the KEM exactly once
When the controller receives the responder's (KEM-less) offer it replies with its own KEM offer instead of answering, so the only transaction that brings the tunnel up is the one that also carries the PSK. Guard that reply with ShouldSendBootstrapOffer so it fires only when no exchange is in flight: without it, every responder offer triggered another offer (an offer-per-offer runaway). The whole behaviour is isolated to the KEM path (config.PQ != nil); non-PQ connections answer as before.
This commit is contained in:
@@ -84,6 +84,10 @@ type PQHandshaker interface {
|
||||
// OfferPayload returns the KEM offer to embed in an outgoing offer (nil if this
|
||||
// peer is not the KEM initiator) and the local PQ data-path port to announce.
|
||||
OfferPayload(remoteKey string) (payload []byte, port int)
|
||||
// ShouldSendBootstrapOffer reports whether, as the controller, we should reply to a
|
||||
// received responder offer with our own KEM offer (true only when no exchange is
|
||||
// already in flight — so we kick the KEM once and ignore further offers).
|
||||
ShouldSendBootstrapOffer(remoteKey string) bool
|
||||
// AnswerPayload processes a received KEM offer (nil if absent) and returns the KEM
|
||||
// answer to embed in the outgoing answer (nil if none) and the local PQ port.
|
||||
AnswerPayload(remoteKey string, recvOffer []byte) (payload []byte, port int)
|
||||
|
||||
@@ -18,6 +18,7 @@ type fakePQ struct {
|
||||
}
|
||||
|
||||
func (f fakePQ) OfferPayload(string) ([]byte, int) { return nil, 0 }
|
||||
func (f fakePQ) ShouldSendBootstrapOffer(string) bool { return false }
|
||||
func (f fakePQ) AnswerPayload(string, []byte) ([]byte, int) { return nil, 0 }
|
||||
func (f fakePQ) OnAnswer(string, []byte) {}
|
||||
func (f fakePQ) PSK(string) (wgtypes.Key, bool) { return f.psk, f.ok }
|
||||
|
||||
@@ -149,9 +149,16 @@ func (h *Handshaker) Listen(ctx context.Context) {
|
||||
// responder-initiated wake still triggers a KEM offer (no stuck responder).
|
||||
// The re-offer reuses our stable ICE session id, so the peer dedups repeats.
|
||||
if h.config.PQ != nil && isController(h.config) {
|
||||
h.log.Debugf("pqkem: controller received a responder offer, replying with our KEM offer instead of an answer")
|
||||
if err := h.sendOffer(); err != nil {
|
||||
h.log.Errorf("failed to send KEM offer in response to peer offer: %s", err)
|
||||
// Reply with our KEM offer exactly once, to kick the exchange. If one is
|
||||
// already in flight, ignore this offer — re-sending on every responder
|
||||
// offer would be an offer-per-offer runaway.
|
||||
if h.config.PQ.ShouldSendBootstrapOffer(h.config.Key) {
|
||||
h.log.Debugf("pqkem: controller received a responder offer, replying with our KEM offer instead of an answer")
|
||||
if err := h.sendOffer(); err != nil {
|
||||
h.log.Errorf("failed to send KEM offer in response to peer offer: %s", err)
|
||||
}
|
||||
} else {
|
||||
h.log.Debugf("pqkem: controller received a responder offer but a KEM exchange is already in flight, ignoring")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user