Mercurial > hg > nginx-quic
comparison src/event/quic/ngx_event_quic_migration.c @ 8797:1e2f4e9c8195 quic
QUIC: reworked migration handling.
The quic connection now holds active, backup and probe paths instead
of sockets. The number of migration paths is now limited and cannot
be inflated by a bad client or an attacker.
The client id is now associated with path rather than socket. This allows
to simplify processing of output and connection ids handling.
New migration abandons any previously started migrations. This allows to
free consumed client ids and request new for use in future migrations and
make progress in case when connection id limit is hit during migration.
A path now can be revalidated without losing its state.
The patch also fixes various issues with NAT rebinding case handling:
- paths are now validated (previously, there was no validation
and paths were left in limited state)
- attempt to reuse id on different path is now again verified
(this was broken in 40445fc7c403)
- former path is now validated in case of apparent migration
author | Vladimir Homutov <vl@nginx.com> |
---|---|
date | Wed, 19 Jan 2022 22:39:24 +0300 |
parents | b7284807b4fa |
children | 077a1e403446 |
comparison
equal
deleted
inserted
replaced
8796:7106a918a277 | 8797:1e2f4e9c8195 |
---|---|
14 ngx_quic_path_t *path); | 14 ngx_quic_path_t *path); |
15 static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c, | 15 static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c, |
16 ngx_quic_path_t *path); | 16 ngx_quic_path_t *path); |
17 static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, | 17 static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, |
18 ngx_quic_path_t *path); | 18 ngx_quic_path_t *path); |
19 static ngx_int_t ngx_quic_path_restore(ngx_connection_t *c); | 19 static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag); |
20 static ngx_quic_path_t *ngx_quic_alloc_path(ngx_connection_t *c); | |
21 | 20 |
22 | 21 |
23 ngx_int_t | 22 ngx_int_t |
24 ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, | 23 ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, |
25 ngx_quic_path_challenge_frame_t *f) | 24 ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) |
26 { | 25 { |
27 ngx_quic_path_t *path; | |
28 ngx_quic_frame_t frame, *fp; | 26 ngx_quic_frame_t frame, *fp; |
29 ngx_quic_socket_t *qsock; | |
30 ngx_quic_connection_t *qc; | 27 ngx_quic_connection_t *qc; |
31 | 28 |
32 qc = ngx_quic_get_connection(c); | 29 qc = ngx_quic_get_connection(c); |
33 | 30 |
34 ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); | 31 ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); |
41 * RFC 9000, 8.2.2. Path Validation Responses | 38 * RFC 9000, 8.2.2. Path Validation Responses |
42 * | 39 * |
43 * A PATH_RESPONSE frame MUST be sent on the network path where the | 40 * A PATH_RESPONSE frame MUST be sent on the network path where the |
44 * PATH_CHALLENGE frame was received. | 41 * PATH_CHALLENGE frame was received. |
45 */ | 42 */ |
46 qsock = ngx_quic_get_socket(c); | |
47 path = qsock->path; | |
48 | 43 |
49 /* | 44 /* |
50 * An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame | 45 * An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame |
51 * to at least the smallest allowed maximum datagram size of 1200 bytes. | 46 * to at least the smallest allowed maximum datagram size of 1200 bytes. |
52 */ | 47 */ |
53 if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) { | 48 if (ngx_quic_frame_sendto(c, &frame, 1200, pkt->path) != NGX_OK) { |
54 return NGX_ERROR; | 49 return NGX_ERROR; |
55 } | 50 } |
56 | 51 |
57 if (qsock == qc->socket) { | 52 if (pkt->path == qc->path) { |
58 /* | 53 /* |
59 * RFC 9000, 9.3.3. Off-Path Packet Forwarding | 54 * RFC 9000, 9.3.3. Off-Path Packet Forwarding |
60 * | 55 * |
61 * An endpoint that receives a PATH_CHALLENGE on an active path SHOULD | 56 * An endpoint that receives a PATH_CHALLENGE on an active path SHOULD |
62 * send a non-probing packet in response. | 57 * send a non-probing packet in response. |
99 q != ngx_queue_sentinel(&qc->paths); | 94 q != ngx_queue_sentinel(&qc->paths); |
100 q = ngx_queue_next(q)) | 95 q = ngx_queue_next(q)) |
101 { | 96 { |
102 path = ngx_queue_data(q, ngx_quic_path_t, queue); | 97 path = ngx_queue_data(q, ngx_quic_path_t, queue); |
103 | 98 |
104 if (path->state != NGX_QUIC_PATH_VALIDATING) { | 99 if (!path->validating) { |
105 continue; | 100 continue; |
106 } | 101 } |
107 | 102 |
108 if (ngx_memcmp(path->challenge1, f->data, sizeof(f->data)) == 0 | 103 if (ngx_memcmp(path->challenge1, f->data, sizeof(f->data)) == 0 |
109 || ngx_memcmp(path->challenge2, f->data, sizeof(f->data)) == 0) | 104 || ngx_memcmp(path->challenge2, f->data, sizeof(f->data)) == 0) |
110 { | 105 { |
111 goto valid; | 106 goto valid; |
112 } | 107 } |
113 } | 108 } |
114 | 109 |
115 ngx_log_error(NGX_LOG_INFO, c->log, 0, | 110 ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, |
116 "quic stale PATH_RESPONSE ignored"); | 111 "quic stale PATH_RESPONSE ignored"); |
117 | 112 |
118 return NGX_OK; | 113 return NGX_OK; |
119 | 114 |
120 valid: | 115 valid: |
128 * unless the only change in the peer's address is its port number. | 123 * unless the only change in the peer's address is its port number. |
129 */ | 124 */ |
130 | 125 |
131 rst = 1; | 126 rst = 1; |
132 | 127 |
133 if (qc->backup) { | 128 prev = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); |
134 prev = qc->backup->path; | 129 |
130 if (prev != NULL) { | |
135 | 131 |
136 if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen, | 132 if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen, |
137 path->sockaddr, path->socklen, 0) | 133 path->sockaddr, path->socklen, 0) |
138 == NGX_OK) | 134 == NGX_OK) |
139 { | 135 { |
162 if (ngx_quic_send_new_token(c, path) != NGX_OK) { | 158 if (ngx_quic_send_new_token(c, path) != NGX_OK) { |
163 return NGX_ERROR; | 159 return NGX_ERROR; |
164 } | 160 } |
165 | 161 |
166 ngx_log_error(NGX_LOG_INFO, c->log, 0, | 162 ngx_log_error(NGX_LOG_INFO, c->log, 0, |
167 "quic path #%uL successfully validated", path->seqnum); | 163 "quic path #%uL addr:%V successfully validated", |
168 | 164 path->seqnum, &path->addr_text); |
169 path->state = NGX_QUIC_PATH_VALIDATED; | 165 |
166 ngx_quic_path_dbg(c, "is validated", path); | |
167 | |
168 path->validated = 1; | |
169 path->validating = 0; | |
170 path->limited = 0; | 170 path->limited = 0; |
171 | 171 |
172 return NGX_OK; | 172 return NGX_OK; |
173 } | 173 } |
174 | 174 |
175 | 175 |
176 static ngx_quic_path_t * | 176 ngx_quic_path_t * |
177 ngx_quic_alloc_path(ngx_connection_t *c) | 177 ngx_quic_new_path(ngx_connection_t *c, |
178 struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid) | |
178 { | 179 { |
179 ngx_queue_t *q; | 180 ngx_queue_t *q; |
180 struct sockaddr *sa; | |
181 ngx_quic_path_t *path; | 181 ngx_quic_path_t *path; |
182 ngx_quic_connection_t *qc; | 182 ngx_quic_connection_t *qc; |
183 | 183 |
184 qc = ngx_quic_get_connection(c); | 184 qc = ngx_quic_get_connection(c); |
185 | 185 |
188 q = ngx_queue_head(&qc->free_paths); | 188 q = ngx_queue_head(&qc->free_paths); |
189 path = ngx_queue_data(q, ngx_quic_path_t, queue); | 189 path = ngx_queue_data(q, ngx_quic_path_t, queue); |
190 | 190 |
191 ngx_queue_remove(&path->queue); | 191 ngx_queue_remove(&path->queue); |
192 | 192 |
193 sa = path->sockaddr; | |
194 ngx_memzero(path, sizeof(ngx_quic_path_t)); | 193 ngx_memzero(path, sizeof(ngx_quic_path_t)); |
195 path->sockaddr = sa; | |
196 | 194 |
197 } else { | 195 } else { |
198 | 196 |
199 path = ngx_pcalloc(c->pool, sizeof(ngx_quic_path_t)); | 197 path = ngx_pcalloc(c->pool, sizeof(ngx_quic_path_t)); |
200 if (path == NULL) { | 198 if (path == NULL) { |
201 return NULL; | 199 return NULL; |
202 } | 200 } |
203 | 201 } |
204 path->sockaddr = ngx_palloc(c->pool, NGX_SOCKADDRLEN); | 202 |
205 if (path->sockaddr == NULL) { | 203 ngx_queue_insert_tail(&qc->paths, &path->queue); |
206 return NULL; | 204 |
207 } | 205 path->cid = cid; |
208 } | 206 cid->used = 1; |
209 | 207 |
210 return path; | |
211 } | |
212 | |
213 | |
214 ngx_quic_path_t * | |
215 ngx_quic_add_path(ngx_connection_t *c, struct sockaddr *sockaddr, | |
216 socklen_t socklen) | |
217 { | |
218 ngx_quic_path_t *path; | |
219 ngx_quic_connection_t *qc; | |
220 | |
221 qc = ngx_quic_get_connection(c); | |
222 | |
223 path = ngx_quic_alloc_path(c); | |
224 if (path == NULL) { | |
225 return NULL; | |
226 } | |
227 | |
228 path->state = NGX_QUIC_PATH_NEW; | |
229 path->limited = 1; | 208 path->limited = 1; |
230 | 209 |
231 path->seqnum = qc->path_seqnum++; | 210 path->seqnum = qc->path_seqnum++; |
232 path->last_seen = ngx_current_msec; | 211 |
233 | 212 path->sockaddr = &path->sa.sockaddr; |
234 path->socklen = socklen; | 213 path->socklen = socklen; |
235 ngx_memcpy(path->sockaddr, sockaddr, socklen); | 214 ngx_memcpy(path->sockaddr, sockaddr, socklen); |
236 | 215 |
237 path->addr_text.data = path->text; | 216 path->addr_text.data = path->text; |
238 path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text, | 217 path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text, |
239 NGX_SOCKADDR_STRLEN, 1); | 218 NGX_SOCKADDR_STRLEN, 1); |
240 | 219 |
241 ngx_queue_insert_tail(&qc->paths, &path->queue); | |
242 | |
243 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, | 220 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, |
244 "quic path #%uL created src:%V", | 221 "quic path #%uL created addr:%V", |
245 path->seqnum, &path->addr_text); | 222 path->seqnum, &path->addr_text); |
246 | |
247 return path; | 223 return path; |
248 } | 224 } |
249 | 225 |
250 | 226 |
251 ngx_quic_path_t * | 227 static ngx_quic_path_t * |
252 ngx_quic_find_path(ngx_connection_t *c, struct sockaddr *sockaddr, | 228 ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag) |
253 socklen_t socklen) | |
254 { | 229 { |
255 ngx_queue_t *q; | 230 ngx_queue_t *q; |
256 ngx_quic_path_t *path; | 231 ngx_quic_path_t *path; |
257 ngx_quic_connection_t *qc; | 232 ngx_quic_connection_t *qc; |
258 | 233 |
262 q != ngx_queue_sentinel(&qc->paths); | 237 q != ngx_queue_sentinel(&qc->paths); |
263 q = ngx_queue_next(q)) | 238 q = ngx_queue_next(q)) |
264 { | 239 { |
265 path = ngx_queue_data(q, ngx_quic_path_t, queue); | 240 path = ngx_queue_data(q, ngx_quic_path_t, queue); |
266 | 241 |
267 if (ngx_cmp_sockaddr(sockaddr, socklen, | 242 if (path->tag == tag) { |
268 path->sockaddr, path->socklen, 1) | |
269 == NGX_OK) | |
270 { | |
271 return path; | 243 return path; |
272 } | 244 } |
273 } | 245 } |
274 | 246 |
275 return NULL; | 247 return NULL; |
276 } | 248 } |
277 | 249 |
278 | 250 |
279 ngx_int_t | 251 ngx_int_t |
280 ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt) | 252 ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt) |
281 { | 253 { |
282 off_t len; | 254 off_t len; |
283 ngx_quic_path_t *path; | 255 ngx_queue_t *q; |
256 ngx_quic_path_t *path, *probe; | |
284 ngx_quic_socket_t *qsock; | 257 ngx_quic_socket_t *qsock; |
285 ngx_quic_client_id_t *cid; | 258 ngx_quic_client_id_t *cid; |
286 ngx_quic_connection_t *qc; | 259 ngx_quic_connection_t *qc; |
287 | 260 |
288 qc = ngx_quic_get_connection(c); | 261 qc = ngx_quic_get_connection(c); |
289 qsock = ngx_quic_get_socket(c); | 262 qsock = ngx_quic_get_socket(c); |
290 | 263 |
264 len = pkt->raw->last - pkt->raw->start; | |
265 | |
291 if (c->udp->dgram == NULL) { | 266 if (c->udp->dgram == NULL) { |
292 /* 1st ever packet in connection, path already exists */ | 267 /* first ever packet in connection, path already exists */ |
293 path = qsock->path; | 268 path = qc->path; |
294 goto update; | 269 goto update; |
295 } | 270 } |
296 | 271 |
297 path = ngx_quic_find_path(c, c->udp->dgram->sockaddr, | 272 probe = NULL; |
298 c->udp->dgram->socklen); | |
299 | |
300 if (path == NULL) { | |
301 path = ngx_quic_add_path(c, c->udp->dgram->sockaddr, | |
302 c->udp->dgram->socklen); | |
303 if (path == NULL) { | |
304 return NGX_ERROR; | |
305 } | |
306 | |
307 if (qsock->path) { | |
308 /* NAT rebinding case: packet to same CID, but from new address */ | |
309 | |
310 ngx_quic_unref_path(c, qsock->path); | |
311 | |
312 qsock->path = path; | |
313 path->refcnt++; | |
314 | |
315 goto update; | |
316 } | |
317 | |
318 } else if (qsock->path) { | |
319 goto update; | |
320 } | |
321 | |
322 /* prefer unused client IDs if available */ | |
323 cid = ngx_quic_next_client_id(c); | |
324 if (cid == NULL) { | |
325 | |
326 /* try to reuse connection ID used on the same path */ | |
327 cid = ngx_quic_used_client_id(c, path); | |
328 if (cid == NULL) { | |
329 | |
330 qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; | |
331 qc->error_reason = "no available client ids for new path"; | |
332 | |
333 ngx_log_error(NGX_LOG_ERR, c->log, 0, | |
334 "no available client ids for new path"); | |
335 | |
336 return NGX_ERROR; | |
337 } | |
338 } | |
339 | |
340 ngx_quic_connect(c, qsock, path, cid); | |
341 | |
342 update: | |
343 | |
344 if (path->state != NGX_QUIC_PATH_NEW) { | |
345 /* force limits/revalidation for paths that were not seen recently */ | |
346 if (ngx_current_msec - path->last_seen > qc->tp.max_idle_timeout) { | |
347 path->state = NGX_QUIC_PATH_NEW; | |
348 path->limited = 1; | |
349 path->sent = 0; | |
350 path->received = 0; | |
351 } | |
352 } | |
353 | |
354 path->last_seen = ngx_current_msec; | |
355 | |
356 len = pkt->raw->last - pkt->raw->start; | |
357 | |
358 /* TODO: this may be too late in some cases; | |
359 * for example, if error happens during decrypt(), we cannot | |
360 * send CC, if error happens in 1st packet, due to amplification | |
361 * limit, because path->received = 0 | |
362 * | |
363 * should we account garbage as received or only decrypting packets? | |
364 */ | |
365 path->received += len; | |
366 | |
367 ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, | |
368 "quic packet via #%uL:%uL:%uL" | |
369 " size:%O path recvd:%O sent:%O limited:%ui", | |
370 qsock->sid.seqnum, qsock->cid->seqnum, path->seqnum, | |
371 len, path->received, path->sent, path->limited); | |
372 | |
373 return NGX_OK; | |
374 } | |
375 | |
376 | |
377 static void | |
378 ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path) | |
379 { | |
380 size_t len; | |
381 | |
382 ngx_memcpy(c->sockaddr, path->sockaddr, path->socklen); | |
383 c->socklen = path->socklen; | |
384 | |
385 if (c->addr_text.data) { | |
386 len = ngx_min(c->addr_text.len, path->addr_text.len); | |
387 | |
388 ngx_memcpy(c->addr_text.data, path->addr_text.data, len); | |
389 c->addr_text.len = len; | |
390 } | |
391 | |
392 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, | |
393 "quic send path set to #%uL addr:%V", | |
394 path->seqnum, &path->addr_text); | |
395 } | |
396 | |
397 | |
398 ngx_int_t | |
399 ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) | |
400 { | |
401 ngx_quic_path_t *next; | |
402 ngx_quic_socket_t *qsock; | |
403 ngx_quic_send_ctx_t *ctx; | |
404 ngx_quic_connection_t *qc; | |
405 | |
406 /* got non-probing packet via non-active socket with different path */ | |
407 | |
408 qc = ngx_quic_get_connection(c); | |
409 | |
410 /* current socket, different from active */ | |
411 qsock = ngx_quic_get_socket(c); | |
412 | |
413 next = qsock->path; /* going to migrate to this path... */ | |
414 | |
415 ngx_log_error(NGX_LOG_INFO, c->log, 0, | |
416 "quic migration from #%uL:%uL:%uL (%s)" | |
417 " to #%uL:%uL:%uL (%s)", | |
418 qc->socket->sid.seqnum, qc->socket->cid->seqnum, | |
419 qc->socket->path->seqnum, | |
420 ngx_quic_path_state_str(qc->socket->path), | |
421 qsock->sid.seqnum, qsock->cid->seqnum, next->seqnum, | |
422 ngx_quic_path_state_str(next)); | |
423 | |
424 if (next->state == NGX_QUIC_PATH_NEW) { | |
425 if (ngx_quic_validate_path(c, qsock->path) != NGX_OK) { | |
426 return NGX_ERROR; | |
427 } | |
428 } | |
429 | |
430 ctx = ngx_quic_get_send_ctx(qc, pkt->level); | |
431 | |
432 /* | |
433 * RFC 9000, 9.3. Responding to Connection Migration | |
434 * | |
435 * An endpoint only changes the address to which it sends packets in | |
436 * response to the highest-numbered non-probing packet. | |
437 */ | |
438 if (pkt->pn != ctx->largest_pn) { | |
439 return NGX_OK; | |
440 } | |
441 | |
442 /* switching connection to new path */ | |
443 | |
444 ngx_quic_set_connection_path(c, next); | |
445 | |
446 /* | |
447 * RFC 9000, 9.5. Privacy Implications of Connection Migration | |
448 * | |
449 * An endpoint MUST NOT reuse a connection ID when sending to | |
450 * more than one destination address. | |
451 */ | |
452 | |
453 /* preserve valid path we are migrating from */ | |
454 if (qc->socket->path->state == NGX_QUIC_PATH_VALIDATED) { | |
455 | |
456 if (qc->backup) { | |
457 ngx_quic_close_socket(c, qc->backup); | |
458 } | |
459 | |
460 qc->backup = qc->socket; | |
461 | |
462 ngx_log_error(NGX_LOG_INFO, c->log, 0, | |
463 "quic backup socket is now #%uL:%uL:%uL (%s)", | |
464 qc->backup->sid.seqnum, qc->backup->cid->seqnum, | |
465 qc->backup->path->seqnum, | |
466 ngx_quic_path_state_str(qc->backup->path)); | |
467 } | |
468 | |
469 qc->socket = qsock; | |
470 | |
471 ngx_log_error(NGX_LOG_INFO, c->log, 0, | |
472 "quic active socket is now #%uL:%uL:%uL (%s)", | |
473 qsock->sid.seqnum, qsock->cid->seqnum, | |
474 qsock->path->seqnum, ngx_quic_path_state_str(qsock->path)); | |
475 | |
476 return NGX_OK; | |
477 } | |
478 | |
479 | |
480 static ngx_int_t | |
481 ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path) | |
482 { | |
483 ngx_msec_t pto; | |
484 ngx_quic_send_ctx_t *ctx; | |
485 ngx_quic_connection_t *qc; | |
486 | |
487 qc = ngx_quic_get_connection(c); | |
488 | |
489 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, | |
490 "quic initiated validation of new path #%uL", | |
491 path->seqnum); | |
492 | |
493 path->state = NGX_QUIC_PATH_VALIDATING; | |
494 | |
495 if (RAND_bytes(path->challenge1, 8) != 1) { | |
496 return NGX_ERROR; | |
497 } | |
498 | |
499 if (RAND_bytes(path->challenge2, 8) != 1) { | |
500 return NGX_ERROR; | |
501 } | |
502 | |
503 if (ngx_quic_send_path_challenge(c, path) != NGX_OK) { | |
504 return NGX_ERROR; | |
505 } | |
506 | |
507 ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); | |
508 pto = ngx_quic_pto(c, ctx); | |
509 | |
510 path->expires = ngx_current_msec + pto; | |
511 path->tries = NGX_QUIC_PATH_RETRIES; | |
512 | |
513 if (!qc->path_validation.timer_set) { | |
514 ngx_add_timer(&qc->path_validation, pto); | |
515 } | |
516 | |
517 return NGX_OK; | |
518 } | |
519 | |
520 | |
521 static ngx_int_t | |
522 ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) | |
523 { | |
524 ngx_quic_frame_t frame; | |
525 | |
526 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, | |
527 "quic path #%uL send path challenge tries:%ui", | |
528 path->seqnum, path->tries); | |
529 | |
530 ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); | |
531 | |
532 frame.level = ssl_encryption_application; | |
533 frame.type = NGX_QUIC_FT_PATH_CHALLENGE; | |
534 | |
535 ngx_memcpy(frame.u.path_challenge.data, path->challenge1, 8); | |
536 | |
537 /* | |
538 * RFC 9000, 8.2.1. Initiating Path Validation | |
539 * | |
540 * An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame | |
541 * to at least the smallest allowed maximum datagram size of 1200 bytes, | |
542 * unless the anti-amplification limit for the path does not permit | |
543 * sending a datagram of this size. | |
544 */ | |
545 | |
546 /* same applies to PATH_RESPONSE frames */ | |
547 if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) { | |
548 return NGX_ERROR; | |
549 } | |
550 | |
551 ngx_memcpy(frame.u.path_challenge.data, path->challenge2, 8); | |
552 | |
553 if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) { | |
554 return NGX_ERROR; | |
555 } | |
556 | |
557 return NGX_OK; | |
558 } | |
559 | |
560 | |
561 void | |
562 ngx_quic_path_validation_handler(ngx_event_t *ev) | |
563 { | |
564 ngx_msec_t now; | |
565 ngx_queue_t *q; | |
566 ngx_msec_int_t left, next, pto; | |
567 ngx_quic_path_t *path; | |
568 ngx_connection_t *c; | |
569 ngx_quic_send_ctx_t *ctx; | |
570 ngx_quic_connection_t *qc; | |
571 | |
572 c = ev->data; | |
573 qc = ngx_quic_get_connection(c); | |
574 | |
575 ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); | |
576 pto = ngx_quic_pto(c, ctx); | |
577 | |
578 next = -1; | |
579 now = ngx_current_msec; | |
580 | 273 |
581 for (q = ngx_queue_head(&qc->paths); | 274 for (q = ngx_queue_head(&qc->paths); |
582 q != ngx_queue_sentinel(&qc->paths); | 275 q != ngx_queue_sentinel(&qc->paths); |
583 q = ngx_queue_next(q)) | 276 q = ngx_queue_next(q)) |
584 { | 277 { |
585 path = ngx_queue_data(q, ngx_quic_path_t, queue); | 278 path = ngx_queue_data(q, ngx_quic_path_t, queue); |
586 | 279 |
587 if (path->state != NGX_QUIC_PATH_VALIDATING) { | 280 if (ngx_cmp_sockaddr(c->udp->dgram->sockaddr, c->udp->dgram->socklen, |
281 path->sockaddr, path->socklen, 1) | |
282 == NGX_OK) | |
283 { | |
284 goto update; | |
285 } | |
286 | |
287 if (path->tag == NGX_QUIC_PATH_PROBE) { | |
288 probe = path; | |
289 } | |
290 } | |
291 | |
292 /* packet from new path, drop current probe, if any */ | |
293 | |
294 if (probe && ngx_quic_free_path(c, probe) != NGX_OK) { | |
295 return NGX_ERROR; | |
296 } | |
297 | |
298 /* new path requires new client id */ | |
299 cid = ngx_quic_next_client_id(c); | |
300 if (cid == NULL) { | |
301 ngx_log_error(NGX_LOG_ERR, c->log, 0, | |
302 "quic no available client ids for new path"); | |
303 /* stop processing of this datagram */ | |
304 return NGX_DONE; | |
305 } | |
306 | |
307 path = ngx_quic_new_path(c, c->udp->dgram->sockaddr, | |
308 c->udp->dgram->socklen, cid); | |
309 if (path == NULL) { | |
310 return NGX_ERROR; | |
311 } | |
312 | |
313 path->tag = NGX_QUIC_PATH_PROBE; | |
314 | |
315 /* | |
316 * client arrived using new path and previously seen DCID, | |
317 * this indicates NAT rebinding (or bad client) | |
318 */ | |
319 if (qsock->used) { | |
320 pkt->rebound = 1; | |
321 } | |
322 | |
323 update: | |
324 | |
325 qsock->used = 1; | |
326 pkt->path = path; | |
327 | |
328 /* TODO: this may be too late in some cases; | |
329 * for example, if error happens during decrypt(), we cannot | |
330 * send CC, if error happens in 1st packet, due to amplification | |
331 * limit, because path->received = 0 | |
332 * | |
333 * should we account garbage as received or only decrypting packets? | |
334 */ | |
335 path->received += len; | |
336 | |
337 ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, | |
338 "quic packet len:%O via sock#%uL path#%uL", | |
339 len, qsock->sid.seqnum, path->seqnum); | |
340 ngx_quic_path_dbg(c, "status", path); | |
341 | |
342 return NGX_OK; | |
343 } | |
344 | |
345 | |
346 ngx_int_t | |
347 ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path) | |
348 { | |
349 ngx_quic_connection_t *qc; | |
350 | |
351 qc = ngx_quic_get_connection(c); | |
352 | |
353 ngx_queue_remove(&path->queue); | |
354 ngx_queue_insert_head(&qc->free_paths, &path->queue); | |
355 | |
356 /* | |
357 * invalidate CID that is no longer usable for any other path; | |
358 * this also requests new CIDs from client | |
359 */ | |
360 if (path->cid) { | |
361 if (ngx_quic_free_client_id(c, path->cid) != NGX_OK) { | |
362 return NGX_ERROR; | |
363 } | |
364 } | |
365 | |
366 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, | |
367 "quic path #%uL addr:%V retired", | |
368 path->seqnum, &path->addr_text); | |
369 | |
370 return NGX_OK; | |
371 } | |
372 | |
373 | |
374 static void | |
375 ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path) | |
376 { | |
377 size_t len; | |
378 | |
379 ngx_memcpy(c->sockaddr, path->sockaddr, path->socklen); | |
380 c->socklen = path->socklen; | |
381 | |
382 if (c->addr_text.data) { | |
383 len = ngx_min(c->addr_text.len, path->addr_text.len); | |
384 | |
385 ngx_memcpy(c->addr_text.data, path->addr_text.data, len); | |
386 c->addr_text.len = len; | |
387 } | |
388 | |
389 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, | |
390 "quic send path set to #%uL addr:%V", | |
391 path->seqnum, &path->addr_text); | |
392 } | |
393 | |
394 | |
395 ngx_int_t | |
396 ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) | |
397 { | |
398 ngx_quic_path_t *next, *bkp; | |
399 ngx_quic_send_ctx_t *ctx; | |
400 ngx_quic_connection_t *qc; | |
401 | |
402 /* got non-probing packet via non-active path */ | |
403 | |
404 qc = ngx_quic_get_connection(c); | |
405 | |
406 ctx = ngx_quic_get_send_ctx(qc, pkt->level); | |
407 | |
408 /* | |
409 * RFC 9000, 9.3. Responding to Connection Migration | |
410 * | |
411 * An endpoint only changes the address to which it sends packets in | |
412 * response to the highest-numbered non-probing packet. | |
413 */ | |
414 if (pkt->pn != ctx->largest_pn) { | |
415 return NGX_OK; | |
416 } | |
417 | |
418 next = pkt->path; | |
419 | |
420 /* | |
421 * RFC 9000, 9.3.3: | |
422 * | |
423 * In response to an apparent migration, endpoints MUST validate the | |
424 * previously active path using a PATH_CHALLENGE frame. | |
425 */ | |
426 if (pkt->rebound) { | |
427 | |
428 /* NAT rebinding: client uses new path with old SID */ | |
429 if (ngx_quic_validate_path(c, qc->path) != NGX_OK) { | |
430 return NGX_ERROR; | |
431 } | |
432 } | |
433 | |
434 if (qc->path->validated) { | |
435 | |
436 if (next->tag != NGX_QUIC_PATH_BACKUP) { | |
437 /* can delete backup path, if any */ | |
438 bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); | |
439 | |
440 if (bkp && ngx_quic_free_path(c, bkp) != NGX_OK) { | |
441 return NGX_ERROR; | |
442 } | |
443 } | |
444 | |
445 qc->path->tag = NGX_QUIC_PATH_BACKUP; | |
446 ngx_quic_path_dbg(c, "is now backup", qc->path); | |
447 | |
448 } else { | |
449 if (ngx_quic_free_path(c, qc->path) != NGX_OK) { | |
450 return NGX_ERROR; | |
451 } | |
452 } | |
453 | |
454 /* switch active path to migrated */ | |
455 qc->path = next; | |
456 qc->path->tag = NGX_QUIC_PATH_ACTIVE; | |
457 | |
458 ngx_quic_set_connection_path(c, next); | |
459 | |
460 if (!next->validated && !next->validating) { | |
461 if (ngx_quic_validate_path(c, next) != NGX_OK) { | |
462 return NGX_ERROR; | |
463 } | |
464 } | |
465 | |
466 ngx_log_error(NGX_LOG_INFO, c->log, 0, | |
467 "quic migrated to path#%uL addr:%V", | |
468 qc->path->seqnum, &qc->path->addr_text); | |
469 | |
470 ngx_quic_path_dbg(c, "is now active", qc->path); | |
471 | |
472 return NGX_OK; | |
473 } | |
474 | |
475 | |
476 static ngx_int_t | |
477 ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path) | |
478 { | |
479 ngx_msec_t pto; | |
480 ngx_quic_send_ctx_t *ctx; | |
481 ngx_quic_connection_t *qc; | |
482 | |
483 qc = ngx_quic_get_connection(c); | |
484 | |
485 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, | |
486 "quic initiated validation of path #%uL", path->seqnum); | |
487 | |
488 path->validating = 1; | |
489 | |
490 if (RAND_bytes(path->challenge1, 8) != 1) { | |
491 return NGX_ERROR; | |
492 } | |
493 | |
494 if (RAND_bytes(path->challenge2, 8) != 1) { | |
495 return NGX_ERROR; | |
496 } | |
497 | |
498 if (ngx_quic_send_path_challenge(c, path) != NGX_OK) { | |
499 return NGX_ERROR; | |
500 } | |
501 | |
502 ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); | |
503 pto = ngx_quic_pto(c, ctx); | |
504 | |
505 path->expires = ngx_current_msec + pto; | |
506 path->tries = NGX_QUIC_PATH_RETRIES; | |
507 | |
508 if (!qc->path_validation.timer_set) { | |
509 ngx_add_timer(&qc->path_validation, pto); | |
510 } | |
511 | |
512 return NGX_OK; | |
513 } | |
514 | |
515 | |
516 static ngx_int_t | |
517 ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) | |
518 { | |
519 ngx_quic_frame_t frame; | |
520 | |
521 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, | |
522 "quic path #%uL send path_challenge tries:%ui", | |
523 path->seqnum, path->tries); | |
524 | |
525 ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); | |
526 | |
527 frame.level = ssl_encryption_application; | |
528 frame.type = NGX_QUIC_FT_PATH_CHALLENGE; | |
529 | |
530 ngx_memcpy(frame.u.path_challenge.data, path->challenge1, 8); | |
531 | |
532 /* | |
533 * RFC 9000, 8.2.1. Initiating Path Validation | |
534 * | |
535 * An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame | |
536 * to at least the smallest allowed maximum datagram size of 1200 bytes, | |
537 * unless the anti-amplification limit for the path does not permit | |
538 * sending a datagram of this size. | |
539 */ | |
540 | |
541 /* same applies to PATH_RESPONSE frames */ | |
542 if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) { | |
543 return NGX_ERROR; | |
544 } | |
545 | |
546 ngx_memcpy(frame.u.path_challenge.data, path->challenge2, 8); | |
547 | |
548 if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) { | |
549 return NGX_ERROR; | |
550 } | |
551 | |
552 return NGX_OK; | |
553 } | |
554 | |
555 | |
556 void | |
557 ngx_quic_path_validation_handler(ngx_event_t *ev) | |
558 { | |
559 ngx_msec_t now; | |
560 ngx_queue_t *q; | |
561 ngx_msec_int_t left, next, pto; | |
562 ngx_quic_path_t *path, *bkp; | |
563 ngx_connection_t *c; | |
564 ngx_quic_send_ctx_t *ctx; | |
565 ngx_quic_connection_t *qc; | |
566 | |
567 c = ev->data; | |
568 qc = ngx_quic_get_connection(c); | |
569 | |
570 ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); | |
571 pto = ngx_quic_pto(c, ctx); | |
572 | |
573 next = -1; | |
574 now = ngx_current_msec; | |
575 | |
576 q = ngx_queue_head(&qc->paths); | |
577 | |
578 while (q != ngx_queue_sentinel(&qc->paths)) { | |
579 | |
580 path = ngx_queue_data(q, ngx_quic_path_t, queue); | |
581 q = ngx_queue_next(q); | |
582 | |
583 if (!path->validating) { | |
588 continue; | 584 continue; |
589 } | 585 } |
590 | 586 |
591 left = path->expires - now; | 587 left = path->expires - now; |
592 | 588 |
593 if (left > 0) { | 589 if (left > 0) { |
594 | 590 |
595 if (next == -1 || left < next) { | 591 if (next == -1 || left < next) { |
596 next = path->expires; | 592 next = left; |
597 } | 593 } |
598 | 594 |
599 continue; | 595 continue; |
600 } | 596 } |
601 | 597 |
615 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0, | 611 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0, |
616 "quic path #%uL validation failed", path->seqnum); | 612 "quic path #%uL validation failed", path->seqnum); |
617 | 613 |
618 /* found expired path */ | 614 /* found expired path */ |
619 | 615 |
620 path->state = NGX_QUIC_PATH_NEW; | 616 path->validated = 0; |
617 path->validating = 0; | |
621 path->limited = 1; | 618 path->limited = 1; |
622 | 619 |
623 /* | 620 |
624 * RFC 9000, 9.4. Loss Detection and Congestion Control | 621 /* RFC 9000, 9.3.2. On-Path Address Spoofing |
625 * | 622 * |
626 * If the timer fires before the PATH_RESPONSE is received, the | 623 * To protect the connection from failing due to such a spurious |
627 * endpoint might send a new PATH_CHALLENGE and restart the timer for | 624 * migration, an endpoint MUST revert to using the last validated |
628 * a longer period of time. This timer SHOULD be set as described in | 625 * peer address when validation of a new peer address fails. |
629 * Section 6.2.1 of [QUIC-RECOVERY] and MUST NOT be more aggressive. | |
630 */ | 626 */ |
631 | 627 |
632 if (qc->socket->path != path) { | 628 if (qc->path == path) { |
633 /* the path was not actually used */ | 629 /* active path validation failed */ |
634 continue; | 630 |
635 } | 631 bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); |
636 | 632 |
637 if (ngx_quic_path_restore(c) != NGX_OK) { | 633 if (bkp == NULL) { |
638 qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH; | 634 qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH; |
639 qc->error_reason = "no viable path"; | 635 qc->error_reason = "no viable path"; |
636 ngx_quic_close_connection(c, NGX_ERROR); | |
637 return; | |
638 } | |
639 | |
640 qc->path = bkp; | |
641 qc->path->tag = NGX_QUIC_PATH_ACTIVE; | |
642 | |
643 ngx_quic_set_connection_path(c, qc->path); | |
644 | |
645 ngx_log_error(NGX_LOG_INFO, c->log, 0, | |
646 "quic path #%uL addr:%V is restored from backup", | |
647 qc->path->seqnum, &qc->path->addr_text); | |
648 | |
649 ngx_quic_path_dbg(c, "is active", qc->path); | |
650 } | |
651 | |
652 if (ngx_quic_free_path(c, path) != NGX_OK) { | |
640 ngx_quic_close_connection(c, NGX_ERROR); | 653 ngx_quic_close_connection(c, NGX_ERROR); |
641 return; | 654 return; |
642 } | 655 } |
643 } | 656 } |
644 | 657 |
645 if (next != -1) { | 658 if (next != -1) { |
646 ngx_add_timer(&qc->path_validation, next); | 659 ngx_add_timer(&qc->path_validation, next); |
647 } | 660 } |
648 } | 661 } |
649 | |
650 | |
651 static ngx_int_t | |
652 ngx_quic_path_restore(ngx_connection_t *c) | |
653 { | |
654 ngx_quic_socket_t *qsock; | |
655 ngx_quic_connection_t *qc; | |
656 | |
657 qc = ngx_quic_get_connection(c); | |
658 | |
659 /* | |
660 * RFC 9000, 9.1. Probing a New Path | |
661 * | |
662 * Failure to validate a path does not cause the connection to end | |
663 * | |
664 * RFC 9000, 9.3.2. On-Path Address Spoofing | |
665 * | |
666 * To protect the connection from failing due to such a spurious | |
667 * migration, an endpoint MUST revert to using the last validated | |
668 * peer address when validation of a new peer address fails. | |
669 */ | |
670 | |
671 if (qc->backup == NULL) { | |
672 return NGX_ERROR; | |
673 } | |
674 | |
675 qc->socket = qc->backup; | |
676 qc->backup = NULL; | |
677 | |
678 qsock = qc->socket; | |
679 | |
680 ngx_log_error(NGX_LOG_INFO, c->log, 0, | |
681 "quic active socket is restored to #%uL:%uL:%uL" | |
682 " (%s), no backup", | |
683 qsock->sid.seqnum, qsock->cid->seqnum, qsock->path->seqnum, | |
684 ngx_quic_path_state_str(qsock->path)); | |
685 | |
686 ngx_quic_set_connection_path(c, qsock->path); | |
687 | |
688 return NGX_OK; | |
689 } |