This commit is contained in:
riccardom
2026-08-03 12:48:46 +02:00
parent be128f5bec
commit 96b756d6bf
2 changed files with 20 additions and 26 deletions
+10 -19
View File
@@ -140,12 +140,12 @@ func (m *Manager) ackConverged(remoteID string, ackID ExchangeID) {
m.mu.Unlock() m.mu.Unlock()
} }
// initiatorLoop enforces the convergence deadline and retransmits the initiator's // initiatorLoop enforces the offer->answer convergence deadline and retransmits the
// outstanding data-path offer while awaiting the answer (a signalling-bootstrapped // initiator's outstanding data-path offer while awaiting the answer (a
// offer is retransmitted by the host, so it is not resent here). It then waits, // signalling-bootstrapped offer is retransmitted by the host, so it is not resent
// counting toward the deadline, in stateAwaitingRekey until OnDataPathRekeyed chains // here). Exhausting the deadline before the answer arrives is a failure. Once the
// the next exchange (which supersedes and cancels this loop). Exhausting the deadline // answer is in (state past awaitingAnswer) the loop exits: the next rotation is driven
// is a failure. // by OnDataPathRekeyed, and the idle wait for it has no deadline.
func (m *Manager) initiatorLoop(ctx context.Context, remoteID string, id ExchangeID) { func (m *Manager) initiatorLoop(ctx context.Context, remoteID string, id ExchangeID) {
defer m.wait.Done() defer m.wait.Done()
t := time.NewTicker(m.retryInterval) t := time.NewTicker(m.retryInterval)
@@ -183,20 +183,11 @@ func (m *Manager) initiatorLoop(ctx context.Context, remoteID string, id Exchang
} }
} }
case stateAwaitingRekey:
// Waiting for OnDataPathRekeyed to chain the next exchange; the
// deadline still applies (the data path may never adopt the new key).
if attempts >= m.maxRetries {
delete(m.exchanges, remoteID)
fail := m.registerFailureLocked(remoteID)
m.mu.Unlock()
m.raiseFailure(remoteID, fail)
return
}
attempts++
m.mu.Unlock()
default: default:
// Past awaiting the answer (converged) or superseded: the loop's job
// is done. The next rotation is driven externally by OnDataPathRekeyed,
// so there is no deadline while idle-waiting for it (that wait can be
// as long as the transport's natural rekey interval).
m.mu.Unlock() m.mu.Unlock()
return return
} }
+10 -7
View File
@@ -11,10 +11,10 @@ import (
// dropTransport is a pqkem.Transport that silently discards everything. // dropTransport is a pqkem.Transport that silently discards everything.
type dropTransport struct{} type dropTransport struct{}
func (dropTransport) Send(netip.AddrPort, []byte) error { return nil } func (dropTransport) Send(netip.AddrPort, []byte) error { return nil }
func (dropTransport) LocalPort() int { return 0 } func (dropTransport) LocalPort() int { return 0 }
func (dropTransport) Run(func(netip.AddrPort, []byte)) {} func (dropTransport) Run(func(netip.AddrPort, []byte)) {}
func (dropTransport) Close() error { return nil } func (dropTransport) Close() error { return nil }
func failedCount(f *fakeWG) int { func failedCount(f *fakeWG) int {
f.mu.Lock() f.mu.Lock()
@@ -44,6 +44,11 @@ func TestManager_RekeyToleratesKFailures(t *testing.T) {
defer dA.Stop() defer dA.Stop()
defer dB.Stop() defer dB.Stop()
// Tighten B's timings before any exchange loop spawns (the loop reads these
// fields, so writing them after a loop is running would race).
dB.retryInterval = 5 * time.Millisecond
dB.maxRetries = 2
// Establish: bootstrap + data-path-rekeyed so B becomes established and its data // Establish: bootstrap + data-path-rekeyed so B becomes established and its data
// path is usable. // path is usable.
bootstrap(t, dA, dB) bootstrap(t, dA, dB)
@@ -51,9 +56,7 @@ func TestManager_RekeyToleratesKFailures(t *testing.T) {
dB.OnDataPathRekeyed("aaaa") dB.OnDataPathRekeyed("aaaa")
require.NotEqual(t, PSK{}, wgB.psk("aaaa")) require.NotEqual(t, PSK{}, wgB.psk("aaaa"))
// Tighten timings and drop B's outbound so rekeys can no longer converge. // Drop B's outbound so rekeys can no longer converge.
dB.retryInterval = 5 * time.Millisecond
dB.maxRetries = 2
lbB.drop.Store(true) lbB.drop.Store(true)
// K-1 data-path rekeys must NOT raise OnRekeyFailed. // K-1 data-path rekeys must NOT raise OnRekeyFailed.