@@ -51,15 +51,65 @@ lws_quic_queue_path_challenge(struct lws *nwsi)
5151}
5252
5353/*
54- * Client preferred_address path-validation deadline (RFC 9000 §9.6).
54+ * Client preferred_address active migration (RFC 9000 §9.5/ §9.6).
5555 *
56- * If the server's PATH_RESPONSE to our PATH_CHALLENGE toward the preferred
57- * address does not arrive in time, revert the socket to the original server
58- * address so the connection is not stranded on an unvalidated path. A success
59- * case cancels this sul from the PATH_RESPONSE handler.
56+ * Swap the nwsi onto a fresh UDP socket (new source port) connected to the
57+ * server's preferred address, and start using the new DCID the server
58+ * advertised. This is "break-before-make": the old path is abandoned at probe
59+ * start and in-flight data is recovered by QUIC loss detection. A PATH_RESPONSE
60+ * validates the new path; a timeout reverts to a fresh socket on the original
61+ * server address with the original DCID.
6062 */
6163#define LWS_QUIC_PREFADDR_DEADLINE_US (3 * LWS_USEC_PER_SEC)
6264
65+ static int
66+ lws_quic_prefaddr_swap_socket (struct lws * nwsi , const lws_sockaddr46 * to_sa46 )
67+ {
68+ struct lws_context * cx = nwsi -> a .context ;
69+ struct lws_context_per_thread * pt = & cx -> pt [(int )nwsi -> tsi ];
70+ lws_sock_file_fd_type new_sock ;
71+ int fam = to_sa46 -> sa4 .sin_family ;
72+ int n_fd ;
73+
74+ n_fd = socket (fam , SOCK_DGRAM , 0 );
75+ if (!lws_socket_is_valid (n_fd )) {
76+ lwsl_wsi_warn (nwsi , "prefaddr: socket fail errno=%d" , LWS_ERRNO );
77+ return 1 ;
78+ }
79+ if (lws_plat_set_nonblocking (n_fd ) ||
80+ lws_plat_apply_FD_CLOEXEC (n_fd )) {
81+ compatible_close (n_fd );
82+ return 1 ;
83+ }
84+ {
85+ int opt = 4 * 1024 * 1024 ;
86+ setsockopt (n_fd , SOL_SOCKET , SO_RCVBUF , (const void * )& opt , sizeof (opt ));
87+ setsockopt (n_fd , SOL_SOCKET , SO_SNDBUF , (const void * )& opt , sizeof (opt ));
88+ }
89+ if (connect (n_fd , sa46_sockaddr ((lws_sockaddr46 * )to_sa46 ),
90+ sa46_socklen ((lws_sockaddr46 * )to_sa46 )) < 0 )
91+ lwsl_wsi_warn (nwsi , "prefaddr: connect fail errno=%d" , LWS_ERRNO );
92+
93+ new_sock .sockfd = n_fd ;
94+
95+ lws_pt_lock (pt , __func__ );
96+ if (lws_socket_is_valid (nwsi -> desc .sockfd ))
97+ __remove_wsi_socket_from_fds (nwsi );
98+ if (lws_socket_is_valid (nwsi -> desc .sockfd ))
99+ compatible_close (nwsi -> desc .sockfd );
100+ nwsi -> desc = new_sock ;
101+ if (__insert_wsi_socket_into_fds (cx , nwsi )) {
102+ lws_pt_unlock (pt );
103+ compatible_close (n_fd );
104+ return 1 ;
105+ }
106+ lws_pt_unlock (pt );
107+
108+ nwsi -> udp -> sa46 = * to_sa46 ;
109+
110+ return 0 ;
111+ }
112+
63113static void
64114lws_quic_prefaddr_sul_cb (lws_sorted_usec_list_t * sul )
65115{
@@ -71,26 +121,24 @@ lws_quic_prefaddr_sul_cb(lws_sorted_usec_list_t *sul)
71121 return ;
72122
73123 lwsl_wsi_notice (nwsi , "QUIC: preferred_address path validation timed out, "
74- "reverting to original server address " );
124+ "reverting to original server path " );
75125
76126 qn -> probing_sa46_valid = 0 ;
77127 qn -> path_challenge_pending = 0 ;
78128 qn -> prefaddr_active = 0 ;
79- nwsi -> udp -> sa46 = qn -> prefaddr_original_sa46 ;
129+ qn -> rem_cid = qn -> prefaddr_original_rem_cid ;
80130
81- if (lws_socket_is_valid (nwsi -> desc .sockfd ) &&
82- connect (nwsi -> desc .sockfd ,
83- sa46_sockaddr (& qn -> prefaddr_original_sa46 ),
84- sa46_socklen (& qn -> prefaddr_original_sa46 )) < 0 )
85- lwsl_wsi_warn (nwsi , "QUIC: failed to re-connect to original addr, errno=%d" ,
86- LWS_ERRNO );
131+ if (lws_quic_prefaddr_swap_socket (nwsi , & qn -> prefaddr_original_sa46 ))
132+ lwsl_wsi_err (nwsi , "prefaddr: revert socket failed" );
87133
88134 lws_callback_on_writable (nwsi );
89135}
90136
91137int
92138lws_quic_client_probe_preferred_address (struct lws * nwsi ,
93- const lws_sockaddr46 * pref_sa46 )
139+ const lws_sockaddr46 * pref_sa46 ,
140+ const struct lws_quic_cid * pref_cid ,
141+ const uint8_t * pref_token )
94142{
95143 struct lws_quic_netconn * qn ;
96144
@@ -101,28 +149,24 @@ lws_quic_client_probe_preferred_address(struct lws *nwsi,
101149 if (qn -> is_server )
102150 return 1 ;
103151
104- /*
105- * Save the current peer so a failed validation (or loss of the
106- * PATH_RESPONSE) can restore the socket to the original server path.
107- */
108152 qn -> prefaddr_original_sa46 = nwsi -> udp -> sa46 ;
153+ qn -> prefaddr_original_rem_cid = qn -> rem_cid ;
154+ if (pref_cid ) {
155+ qn -> prefaddr_rem_cid = * pref_cid ;
156+ if (pref_token )
157+ memcpy (qn -> prefaddr_rem_token , pref_token , 16 );
158+ }
159+
109160 qn -> probing_sa46 = * pref_sa46 ;
110161 qn -> probing_sa46_valid = 1 ;
111162 qn -> prefaddr_active = 1 ;
163+ qn -> prefaddr_committed = 0 ;
112164
113- /*
114- * Re-target the existing UDP socket at the preferred address. The
115- * client source 4-tuple does not change (RFC 9000 §9.6: this is not a
116- * client connection migration), so the server sees no migration and
117- * must not flap. A connected UDP socket can be re-connect()ed.
118- */
119- if (lws_socket_is_valid (nwsi -> desc .sockfd ) &&
120- connect (nwsi -> desc .sockfd , sa46_sockaddr ((lws_sockaddr46 * )pref_sa46 ),
121- sa46_socklen ((lws_sockaddr46 * )pref_sa46 )) < 0 )
122- lwsl_wsi_warn (nwsi , "QUIC: failed to connect preferred_address, errno=%d" ,
123- LWS_ERRNO );
165+ if (lws_quic_prefaddr_swap_socket (nwsi , pref_sa46 ))
166+ return 1 ;
124167
125- nwsi -> udp -> sa46 = * pref_sa46 ;
168+ if (pref_cid && pref_cid -> len )
169+ qn -> rem_cid = * pref_cid ;
126170
127171 lws_quic_queue_path_challenge (nwsi );
128172
@@ -663,6 +707,13 @@ rops_handle_POLLIN_quic(struct lws_context_per_thread *pt, struct lws *wsi,
663707 lwsl_debug ("QUIC RX: found connection by orig_dcid! nwsi=%s\n" , lws_wsi_tag (nwsi ));
664708 break ;
665709 }
710+ /* Match the preferred_address CID we advertised (RFC 9000 §9.6) */
711+ if (w -> quic .qn && w -> quic .qn -> prefaddr_rem_cid .len == dcid_len &&
712+ !memcmp (w -> quic .qn -> prefaddr_rem_cid .id , dcid .id , dcid_len )) {
713+ nwsi = w ;
714+ lwsl_debug ("QUIC RX: found connection by prefaddr cid! nwsi=%s\n" , lws_wsi_tag (nwsi ));
715+ break ;
716+ }
666717 w = w -> mux .sibling_list ;
667718 }
668719 }
@@ -1003,6 +1054,11 @@ rops_handle_POLLIN_quic(struct lws_context_per_thread *pt, struct lws *wsi,
10031054 * pb ++ = 8 ; /* Generating an 8-byte CID */
10041055 /* CID Bytes */
10051056 if (lws_get_random (wsi -> a .context , pb , 8 ) != 8 ) goto tp_overflow ;
1057+ /* Store so server can route migrated client packets addressed to it */
1058+ if (nwsi -> quic .qn ) {
1059+ nwsi -> quic .qn -> prefaddr_rem_cid .len = 8 ;
1060+ memcpy (nwsi -> quic .qn -> prefaddr_rem_cid .id , pb , 8 );
1061+ }
10061062 pb += 8 ;
10071063 /* Stateless Reset Token */
10081064 if (lws_get_random (wsi -> a .context , pb , 16 ) != 16 ) goto tp_overflow ;
@@ -1491,11 +1547,14 @@ rops_handle_POLLIN_quic(struct lws_context_per_thread *pt, struct lws *wsi,
14911547 nwsi = lws_get_quic_network_wsi (nwsi );
14921548 }
14931549
1494- /* RFC 9000 Section 9.3: Receiving non-probing frames (STREAM, ACK, etc.) from a new address
1495- * indicates that the peer has migrated (e.g. due to NAT rebinding or active client migration).
1496- * The server MUST commit its active path to the new address. */
1550+ /*
1551+ * RFC 9000 Section 9.3: commit only when the non-probing packet
1552+ * actually came FROM the address being probed, otherwise ordinary
1553+ * traffic on the existing path makes us flap between paths.
1554+ */
14971555 if (nwsi && nwsi -> quic .qn && nwsi -> quic .qn -> is_server &&
1498- nwsi -> quic .qn -> probing_sa46_valid && nwsi -> quic .qn -> rx_has_non_probing ) {
1556+ nwsi -> quic .qn -> probing_sa46_valid && nwsi -> quic .qn -> rx_has_non_probing &&
1557+ !lws_sa46_compare_ads (& sa46 , & nwsi -> quic .qn -> probing_sa46 )) {
14991558#if (_LWS_ENABLED_LOGS & LLL_NOTICE )
15001559 char buf_old [64 ], buf_new [64 ];
15011560 uint16_t port_old , port_new ;
0 commit comments