Skip to content

Commit 6e1eaa9

Browse files
committed
connectionmigration
1 parent 13f1a47 commit 6e1eaa9

3 files changed

Lines changed: 133 additions & 52 deletions

File tree

lib/roles/quic/ops-quic.c

Lines changed: 93 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
63113
static void
64114
lws_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

91137
int
92138
lws_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;

lib/roles/quic/parse-quic.c

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,7 @@ lws_quic_parse_frames(struct lws *nwsi, int level, uint8_t *payload, size_t payl
10411041
nwsi->quic.qn->probing_sa46;
10421042
nwsi->quic.qn->probing_sa46_valid = 0;
10431043
nwsi->quic.qn->prefaddr_active = 0;
1044+
nwsi->quic.qn->prefaddr_committed = 1;
10441045
lws_sul_cancel(&nwsi->quic.qn->prefaddr_sul);
10451046
lwsl_wsi_notice(nwsi,
10461047
"QUIC: PATH_RESPONSE validated, %s migration committed",
@@ -1672,10 +1673,15 @@ lws_quic_parse_transport_parameters(struct lws *wsi, const uint8_t *buf, size_t
16721673
*/
16731674
if (param_len >= 4 + 2 + 16 + 2 + 1) {
16741675
lws_sockaddr46 pref;
1676+
struct lws_quic_cid pcid;
1677+
uint8_t token[16];
16751678
uint16_t port;
16761679
const uint8_t *v4 = &buf[pos];
1680+
size_t off = pos + 4 + 2 + 16 + 2;
1681+
int have_cid = 0;
16771682

16781683
memset(&pref, 0, sizeof(pref));
1684+
memset(&pcid, 0, sizeof(pcid));
16791685

16801686
if (v4[0] | v4[1] | v4[2] | v4[3]) {
16811687
pref.sa4.sin_family = AF_INET;
@@ -1696,16 +1702,22 @@ lws_quic_parse_transport_parameters(struct lws *wsi, const uint8_t *buf, size_t
16961702
}
16971703
sa46_sockport(&pref, htons(port));
16981704

1705+
if (off < pos + param_len) {
1706+
uint8_t clen = buf[off++];
1707+
if (clen && clen <= LWS_QUIC_MAX_CID_LEN &&
1708+
off + clen + 16 <= pos + param_len) {
1709+
pcid.len = clen;
1710+
memcpy(pcid.id, &buf[off], clen);
1711+
memcpy(token, &buf[off + clen], 16);
1712+
have_cid = 1;
1713+
}
1714+
}
1715+
16991716
if (port && qn->nwsi)
1700-
/*
1701-
* Validate the new server path on the
1702-
* EXISTING connection (RFC 9000 §9.6):
1703-
* the client does not migrate its own
1704-
* address, it only re-targets the server
1705-
* destination after PATH_RESPONSE.
1706-
*/
17071717
lws_quic_client_probe_preferred_address(
1708-
qn->nwsi, &pref);
1718+
qn->nwsi, &pref,
1719+
have_cid ? &pcid : NULL,
1720+
have_cid ? token : NULL);
17091721
}
17101722
break;
17111723
#endif

lib/roles/quic/private-lib-roles-quic.h

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -318,15 +318,21 @@ struct lws_quic_netconn {
318318
uint8_t rx_has_non_probing:1;
319319

320320
/*
321-
* Client preferred_address migration (RFC 9000 Section 9.6).
321+
* Client preferred_address active migration (RFC 9000 Section 9.5/9.6).
322322
*
323-
* Unlike server-side passive migration, the client does NOT change its
324-
* source address: it only re-targets the server destination. We save
325-
* the original peer address so a failed path validation can revert the
326-
* socket, and arm prefaddr_sul to bound the wait for PATH_RESPONSE.
323+
* The client migrates onto the server's preferred address using a NEW
324+
* source socket (so the server observes a second client 4-tuple) and
325+
* the new DCID the server advertised in the preferred_address TP. We
326+
* save the original peer address + DCID so a failed path validation can
327+
* revert cleanly.
327328
*/
328329
lws_sockaddr46 prefaddr_original_sa46;
330+
struct lws_quic_cid prefaddr_original_rem_cid;
331+
struct lws_quic_cid prefaddr_rem_cid; /* new DCID from TP */
332+
uint8_t prefaddr_rem_token[16];/* stateless reset token */
333+
lws_sockfd_type prefaddr_sockfd; /* new probe socket */
329334
uint8_t prefaddr_active:1;
335+
uint8_t prefaddr_committed:1; /* PATH_RESPONSE done */
330336
lws_sorted_usec_list_t prefaddr_sul;
331337

332338
/* ECN (Explicit Congestion Notification) */
@@ -380,14 +386,18 @@ void
380386
lws_quic_queue_path_challenge(struct lws *nwsi);
381387

382388
/*
383-
* Client preferred_address migration (RFC 9000 Section 9.6): re-target the
384-
* existing client connection at the server's preferred address and validate
385-
* the new path with PATH_CHALLENGE / PATH_RESPONSE. The client source address
386-
* is unchanged; only the server destination moves. Returns 0 on success.
389+
* Client preferred_address active migration (RFC 9000 Section 9.5/9.6):
390+
* open a new source socket toward the server's preferred address, swap to the
391+
* new DCID the server advertised, and validate the new path with
392+
* PATH_CHALLENGE / PATH_RESPONSE. On success the connection moves onto the
393+
* new socket; on timeout it reverts. pref_cid/pref_token may be NULL.
394+
* Returns 0 on success.
387395
*/
388396
int
389397
lws_quic_client_probe_preferred_address(struct lws *nwsi,
390-
const lws_sockaddr46 *pref_sa46);
398+
const lws_sockaddr46 *pref_sa46,
399+
const struct lws_quic_cid *pref_cid,
400+
const uint8_t *pref_token);
391401

392402
void lws_quic_keys_free(struct lws_quic_keys *keys);
393403

0 commit comments

Comments
 (0)