707fcee5b6
Pre-fix, PathValidator.checkValidationTimeout queued a
RETIRE_CONNECTION_ID for the failed sequence number while leaving
QuicConnection.destinationConnectionId stamping that same CID. The
strict servers (quic-go / picoquic / msquic / mvfst) processed the
RETIRE, dropped their routing entry, then closed us with
PROTOCOL_VIOLATION the next time a packet arrived stamped with
the just-retired CID — visible in qlog as two RETIRE_CONNECTION_ID
frames within ~500 ms followed by "retired connection ID N, which
was used as the Destination Connection ID on this packet".
Reproducer: setting proactiveDcidRotationMillis=4_000L on
QuicConnection drove the trigger every 4 s; rebind-port vs quic-go
then failed at ~10 s with the close above. Reverted in this commit;
the experiment fields are gone.
The fix splits the timeout outcome into three cases:
- NotTimedOut — budget hasn't elapsed.
- RecoveredOnSpare — rotated active to the lowest spare CID;
queued RETIRE for the failed seq; the
QuicConnection wrapper synchronously
updates destinationConnectionId so the
next outbound stamps the new CID, atomic
under streamsLock with the validator
state change.
- StuckOnFailedCid — no spare available; KEEP the failed seq
active and DO NOT queue retire. The
writer keeps stamping the unvalidated
CID; if the path is genuinely dead the
connection idles out, and once a
NEW_CONNECTION_ID arrives the next
trigger rotates cleanly.
After the fix, rebind-port vs quic-go fails at the 60 s test
timeout with server-side trigger=idle_timeout (the Task 2 issue —
strict servers gate on fresh DCID at new src port, which we can't
synchronize with the sim's rebind cadence) instead of the
PROTOCOL_VIOLATION close.
Tests:
- PathValidatorTest:
validationTimeoutAfter3PtoTransitionsToFailedAndRetiresFailedCidWithSpare
timeoutWithoutSpareKeepsFailedSeqActiveAndDoesNotRetire
twoConsecutiveFailedValidationsRetireAllAbandonedSequencesWithSpare
- ClientPathMigrationTest:
backToBackSuccessfulMigrationsRetireExactlyOnePerRotationAndStampNewDcid
validationTimeoutWithSpareRotatesDcidAndRetiresFailedSeq
validationTimeoutWithoutSpareKeepsActiveCidAndDoesNotRetire
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>