@@ -60,7 +60,7 @@ lws_quic_queue_path_challenge(struct lws *nwsi)
6060 * validates the new path; a timeout reverts to a fresh socket on the original
6161 * server address with the original DCID.
6262 */
63- #define LWS_QUIC_PREFADDR_DEADLINE_US (3 * LWS_USEC_PER_SEC)
63+ #define LWS_QUIC_PREFADDR_DEADLINE_US (10 * LWS_USEC_PER_SEC)
6464
6565static int
6666lws_quic_prefaddr_swap_socket (struct lws * nwsi , const lws_sockaddr46 * to_sa46 )
@@ -149,16 +149,29 @@ lws_quic_client_probe_preferred_address(struct lws *nwsi,
149149 if (qn -> is_server )
150150 return 1 ;
151151
152+ /* Save the migration parameters */
152153 qn -> prefaddr_original_sa46 = nwsi -> udp -> sa46 ;
153154 qn -> prefaddr_original_rem_cid = qn -> rem_cid ;
155+ qn -> probing_sa46 = * pref_sa46 ;
156+ qn -> probing_sa46_valid = 1 ;
154157 if (pref_cid ) {
155158 qn -> prefaddr_rem_cid = * pref_cid ;
156159 if (pref_token )
157160 memcpy (qn -> prefaddr_rem_token , pref_token , 16 );
158161 }
159162
160- qn -> probing_sa46 = * pref_sa46 ;
161- qn -> probing_sa46_valid = 1 ;
163+ /*
164+ * Defer the actual socket swap + DCID change until the handshake is
165+ * complete: the preferred_address TP arrives during the handshake, and
166+ * swapping before both sides have APP keys strands the migration (the
167+ * server can't decrypt APP-level packets from the new source port).
168+ */
169+ if (!qn -> handshake_done ) {
170+ qn -> prefaddr_pending = 1 ;
171+ return 0 ;
172+ }
173+
174+ qn -> prefaddr_pending = 0 ;
162175 qn -> prefaddr_active = 1 ;
163176 qn -> prefaddr_committed = 0 ;
164177
@@ -1434,9 +1447,19 @@ rops_handle_POLLIN_quic(struct lws_context_per_thread *pt, struct lws *wsi,
14341447 nwsi -> quic .qn -> highest_rx_pn [pn_space ] = full_pn ;
14351448 }
14361449
1437- /* Connection Migration: Execute pending migration now that the packet is cryptographically verified */
1450+ /*
1451+ * Connection Migration: Execute pending migration now that
1452+ * the packet is cryptographically verified. Skip if a probe
1453+ * is already in flight for this address to avoid overwriting
1454+ * the pending path_challenge (which would make the client's
1455+ * PATH_RESPONSE not match).
1456+ */
14381457 if (pending_migration ) {
1439- if (is_out_of_order ) {
1458+ if (nwsi -> quic .qn -> probing_sa46_valid &&
1459+ !lws_sa46_compare_ads (& migration_sa46 ,
1460+ & nwsi -> quic .qn -> probing_sa46 )) {
1461+ pending_migration = 0 ;
1462+ } else if (is_out_of_order ) {
14401463 lwsl_notice ("QUIC: Ignoring connection migration from out-of-order packet (PN %llu <= highest %llu)\n" ,
14411464 (unsigned long long )full_pn , (unsigned long long )highest );
14421465 pending_migration = 0 ;
@@ -1464,20 +1487,21 @@ rops_handle_POLLIN_quic(struct lws_context_per_thread *pt, struct lws *wsi,
14641487 buf_old , (unsigned int )ntohs (port_old ),
14651488 buf_new , (unsigned int )ntohs (port_new ));
14661489#endif
1490+ /*
1491+ * Commit the peer address immediately: the
1492+ * client has moved to a new source port, the
1493+ * old path is dead. Since we defer client
1494+ * migration until handshake_done, there should
1495+ * be no pending Handshake-level TX to race.
1496+ * Queue PATH_CHALLENGE at the HEAD of APP
1497+ * pending_tx so it is the first APP frame
1498+ * emitted to the new path.
1499+ */
1500+ nwsi -> udp -> sa46 = migration_sa46 ;
14671501 nwsi -> quic .qn -> rx_has_non_probing = 0 ;
14681502 nwsi -> quic .qn -> probing_sa46 = migration_sa46 ;
14691503 nwsi -> quic .qn -> probing_sa46_valid = 1 ;
14701504
1471- /*
1472- * Queue PATH_CHALLENGE immediately, tagged to
1473- * the new 4-tuple via has_dest. Do NOT commit
1474- * udp->sa46 yet — keep ordinary traffic (incl.
1475- * Handshake ACKs) flowing to the OLD path until
1476- * PATH_RESPONSE validates the new one. This
1477- * ensures the first server datagram to the new
1478- * client port carries PATH_CHALLENGE (RFC 9000
1479- * §8.2 / QIR connectionmigration requirement).
1480- */
14811505 if (!nwsi -> quic .qn -> path_challenge_pending ) {
14821506 struct lws_quic_tx_frame * f_pc =
14831507 lws_zalloc (sizeof (* f_pc ) + 8 ,
@@ -1494,8 +1518,6 @@ rops_handle_POLLIN_quic(struct lws_context_per_thread *pt, struct lws *wsi,
14941518 memcpy (nwsi -> quic .qn -> path_challenge ,
14951519 f_pc -> data , 8 );
14961520 nwsi -> quic .qn -> path_challenge_pending = 1 ;
1497- f_pc -> has_dest = 1 ;
1498- f_pc -> dest_sa46 = migration_sa46 ;
14991521 lws_dll2_add_head (& f_pc -> list ,
15001522 & nwsi -> quic .qn -> pending_tx [LWS_QUIC_LEVEL_APP ]);
15011523 }
@@ -1582,47 +1604,13 @@ rops_handle_POLLIN_quic(struct lws_context_per_thread *pt, struct lws *wsi,
15821604 }
15831605
15841606 /*
1585- * RFC 9000 Section 9.3: commit only when the non-probing packet
1586- * actually came FROM the address being probed, otherwise ordinary
1587- * traffic on the existing path makes us flap between paths.
1607+ * The server commits the new path ONLY on PATH_RESPONSE
1608+ * validation (handled in parse-quic.c). Do NOT commit
1609+ * here on non-probing packets: doing so would make the
1610+ * first server datagram to the new client port be a
1611+ * regular ACK/STREAM frame instead of the PATH_CHALLENGE
1612+ * that QIR connectionmigration requires.
15881613 */
1589- if (nwsi && nwsi -> quic .qn && nwsi -> quic .qn -> is_server &&
1590- nwsi -> quic .qn -> probing_sa46_valid && nwsi -> quic .qn -> rx_has_non_probing &&
1591- !lws_sa46_compare_ads (& sa46 , & nwsi -> quic .qn -> probing_sa46 )) {
1592- #if (_LWS_ENABLED_LOGS & LLL_NOTICE )
1593- char buf_old [64 ], buf_new [64 ];
1594- uint16_t port_old , port_new ;
1595- lws_sa46_write_numeric_address (& nwsi -> udp -> sa46 , buf_old , sizeof (buf_old ));
1596- lws_sa46_write_numeric_address (& nwsi -> quic .qn -> probing_sa46 , buf_new , sizeof (buf_new ));
1597- #if defined(LWS_WITH_IPV6 )
1598- port_old = nwsi -> udp -> sa46 .sa4 .sin_family == AF_INET ? nwsi -> udp -> sa46 .sa4 .sin_port : nwsi -> udp -> sa46 .sa6 .sin6_port ;
1599- port_new = nwsi -> quic .qn -> probing_sa46 .sa4 .sin_family == AF_INET ? nwsi -> quic .qn -> probing_sa46 .sa4 .sin_port : nwsi -> quic .qn -> probing_sa46 .sa6 .sin6_port ;
1600- #else
1601- port_old = nwsi -> udp -> sa46 .sa4 .sin_port ;
1602- port_new = nwsi -> quic .qn -> probing_sa46 .sa4 .sin_port ;
1603- #endif
1604- lwsl_notice ("QUIC Server: Connection Migration committed via non-probing packet! Peer address updated from %s:%u to %s:%u\n" ,
1605- buf_old , (unsigned int )ntohs (port_old ),
1606- buf_new , (unsigned int )ntohs (port_new ));
1607- #endif
1608-
1609- nwsi -> udp -> sa46 = nwsi -> quic .qn -> probing_sa46 ;
1610- nwsi -> quic .qn -> probing_sa46_valid = 0 ;
1611-
1612- /* Reset Congestion Control State (RFC 9000 9.3.3) */
1613- if (nwsi -> quic .qn -> cc_ops && nwsi -> quic .qn -> cc_ops -> init )
1614- nwsi -> quic .qn -> cc_ops -> init (nwsi );
1615-
1616- /* Reset RTT estimator */
1617- nwsi -> quic .qn -> smoothed_rtt = 0 ;
1618- nwsi -> quic .qn -> rttvar = 0 ;
1619- nwsi -> quic .qn -> latest_rtt = 0 ;
1620-
1621- /* Reset PMTUD */
1622- nwsi -> quic .qn -> current_mtu = 1280 ;
1623- nwsi -> quic .qn -> probed_mtu = 1380 ;
1624- nwsi -> quic .qn -> pmtud_state = 1 ;
1625- }
16261614
16271615 if (nwsi ) {
16281616 struct lws * w = nwsi -> mux .child_list ;
0 commit comments