Mercurial > hg > nginx
comparison src/event/quic/ngx_event_quic_connid.c @ 8971: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 | 32daba3aabb2 |
children | fab36e4abf83 |
comparison
equal
deleted
inserted
replaced
8970:7106a918a277 | 8971:1e2f4e9c8195 |
---|---|
13 | 13 |
14 | 14 |
15 #if (NGX_QUIC_BPF) | 15 #if (NGX_QUIC_BPF) |
16 static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id); | 16 static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id); |
17 #endif | 17 #endif |
18 static ngx_int_t ngx_quic_send_retire_connection_id(ngx_connection_t *c, | 18 static ngx_int_t ngx_quic_retire_client_id(ngx_connection_t *c, |
19 uint64_t seqnum); | 19 ngx_quic_client_id_t *cid); |
20 | |
21 static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, | 20 static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, |
22 ngx_quic_connection_t *qc); | 21 ngx_quic_connection_t *qc); |
23 static ngx_int_t ngx_quic_replace_retired_client_id(ngx_connection_t *c, | |
24 ngx_quic_client_id_t *retired_cid); | |
25 static ngx_int_t ngx_quic_send_server_id(ngx_connection_t *c, | 22 static ngx_int_t ngx_quic_send_server_id(ngx_connection_t *c, |
26 ngx_quic_server_id_t *sid); | 23 ngx_quic_server_id_t *sid); |
27 | 24 |
28 | 25 |
29 ngx_int_t | 26 ngx_int_t |
75 | 72 |
76 ngx_int_t | 73 ngx_int_t |
77 ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, | 74 ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, |
78 ngx_quic_new_conn_id_frame_t *f) | 75 ngx_quic_new_conn_id_frame_t *f) |
79 { | 76 { |
80 uint64_t seq; | |
81 ngx_str_t id; | 77 ngx_str_t id; |
82 ngx_queue_t *q; | 78 ngx_queue_t *q; |
79 ngx_quic_frame_t *frame; | |
83 ngx_quic_client_id_t *cid, *item; | 80 ngx_quic_client_id_t *cid, *item; |
84 ngx_quic_connection_t *qc; | 81 ngx_quic_connection_t *qc; |
85 | 82 |
86 qc = ngx_quic_get_connection(c); | 83 qc = ngx_quic_get_connection(c); |
87 | 84 |
95 * a corresponding RETIRE_CONNECTION_ID frame that retires | 92 * a corresponding RETIRE_CONNECTION_ID frame that retires |
96 * the newly received connection ID, unless it has already | 93 * the newly received connection ID, unless it has already |
97 * done so for that sequence number. | 94 * done so for that sequence number. |
98 */ | 95 */ |
99 | 96 |
100 if (ngx_quic_send_retire_connection_id(c, f->seqnum) != NGX_OK) { | 97 frame = ngx_quic_alloc_frame(c); |
101 return NGX_ERROR; | 98 if (frame == NULL) { |
102 } | 99 return NGX_ERROR; |
100 } | |
101 | |
102 frame->level = ssl_encryption_application; | |
103 frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; | |
104 frame->u.retire_cid.sequence_number = f->seqnum; | |
105 | |
106 ngx_quic_queue_frame(qc, frame); | |
103 | 107 |
104 goto retire; | 108 goto retire; |
105 } | 109 } |
106 | 110 |
107 cid = NULL; | 111 cid = NULL; |
171 | 175 |
172 if (cid->seqnum >= f->retire) { | 176 if (cid->seqnum >= f->retire) { |
173 continue; | 177 continue; |
174 } | 178 } |
175 | 179 |
176 /* this connection id must be retired */ | 180 if (ngx_quic_retire_client_id(c, cid) != NGX_OK) { |
177 seq = cid->seqnum; | |
178 | |
179 if (cid->refcnt) { | |
180 /* we are going to retire client id which is in use */ | |
181 if (ngx_quic_replace_retired_client_id(c, cid) != NGX_OK) { | |
182 return NGX_ERROR; | |
183 } | |
184 | |
185 } else { | |
186 ngx_quic_unref_client_id(c, cid); | |
187 } | |
188 | |
189 if (ngx_quic_send_retire_connection_id(c, seq) != NGX_OK) { | |
190 return NGX_ERROR; | 181 return NGX_ERROR; |
191 } | 182 } |
192 } | 183 } |
193 | 184 |
194 done: | 185 done: |
211 return NGX_OK; | 202 return NGX_OK; |
212 } | 203 } |
213 | 204 |
214 | 205 |
215 static ngx_int_t | 206 static ngx_int_t |
216 ngx_quic_send_retire_connection_id(ngx_connection_t *c, uint64_t seqnum) | 207 ngx_quic_retire_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) |
217 { | 208 { |
218 ngx_quic_frame_t *frame; | 209 ngx_queue_t *q; |
219 ngx_quic_connection_t *qc; | 210 ngx_quic_path_t *path; |
220 | 211 ngx_quic_client_id_t *new_cid; |
221 qc = ngx_quic_get_connection(c); | 212 ngx_quic_connection_t *qc; |
222 | 213 |
223 frame = ngx_quic_alloc_frame(c); | 214 qc = ngx_quic_get_connection(c); |
224 if (frame == NULL) { | 215 |
225 return NGX_ERROR; | 216 if (!cid->used) { |
226 } | 217 return ngx_quic_free_client_id(c, cid); |
227 | 218 } |
228 frame->level = ssl_encryption_application; | 219 |
229 frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; | 220 /* we are going to retire client id which is in use */ |
230 frame->u.retire_cid.sequence_number = seqnum; | 221 |
231 | 222 q = ngx_queue_head(&qc->paths); |
232 ngx_quic_queue_frame(qc, frame); | 223 |
233 | 224 while (q != ngx_queue_sentinel(&qc->paths)) { |
234 /* we are no longer going to use this client id */ | 225 |
226 path = ngx_queue_data(q, ngx_quic_path_t, queue); | |
227 q = ngx_queue_next(q); | |
228 | |
229 if (path->cid != cid) { | |
230 continue; | |
231 } | |
232 | |
233 if (path == qc->path) { | |
234 /* this is the active path: update it with new CID */ | |
235 new_cid = ngx_quic_next_client_id(c); | |
236 if (new_cid == NULL) { | |
237 return NGX_ERROR; | |
238 } | |
239 | |
240 qc->path->cid = new_cid; | |
241 new_cid->used = 1; | |
242 | |
243 return ngx_quic_free_client_id(c, cid); | |
244 } | |
245 | |
246 return ngx_quic_free_path(c, path); | |
247 } | |
235 | 248 |
236 return NGX_OK; | 249 return NGX_OK; |
237 } | 250 } |
238 | 251 |
239 | 252 |
316 q != ngx_queue_sentinel(&qc->client_ids); | 329 q != ngx_queue_sentinel(&qc->client_ids); |
317 q = ngx_queue_next(q)) | 330 q = ngx_queue_next(q)) |
318 { | 331 { |
319 cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); | 332 cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); |
320 | 333 |
321 if (cid->refcnt == 0) { | 334 if (!cid->used) { |
322 return cid; | 335 return cid; |
323 } | |
324 } | |
325 | |
326 return NULL; | |
327 } | |
328 | |
329 | |
330 ngx_quic_client_id_t * | |
331 ngx_quic_used_client_id(ngx_connection_t *c, ngx_quic_path_t *path) | |
332 { | |
333 ngx_queue_t *q; | |
334 ngx_quic_socket_t *qsock; | |
335 ngx_quic_connection_t *qc; | |
336 | |
337 qc = ngx_quic_get_connection(c); | |
338 | |
339 /* best guess: cid used by active path is good for us */ | |
340 if (qc->socket->path == path) { | |
341 return qc->socket->cid; | |
342 } | |
343 | |
344 for (q = ngx_queue_head(&qc->sockets); | |
345 q != ngx_queue_sentinel(&qc->sockets); | |
346 q = ngx_queue_next(q)) | |
347 { | |
348 qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); | |
349 | |
350 if (qsock->path && qsock->path == path) { | |
351 return qsock->cid; | |
352 } | 336 } |
353 } | 337 } |
354 | 338 |
355 return NULL; | 339 return NULL; |
356 } | 340 } |
358 | 342 |
359 ngx_int_t | 343 ngx_int_t |
360 ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, | 344 ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, |
361 ngx_quic_retire_cid_frame_t *f) | 345 ngx_quic_retire_cid_frame_t *f) |
362 { | 346 { |
363 ngx_quic_path_t *path; | 347 ngx_quic_socket_t *qsock; |
364 ngx_quic_socket_t *qsock, **tmp; | |
365 ngx_quic_client_id_t *cid; | |
366 ngx_quic_connection_t *qc; | 348 ngx_quic_connection_t *qc; |
367 | 349 |
368 qc = ngx_quic_get_connection(c); | 350 qc = ngx_quic_get_connection(c); |
369 | 351 |
370 if (f->sequence_number >= qc->server_seqnum) { | 352 if (f->sequence_number >= qc->server_seqnum) { |
406 } | 388 } |
407 | 389 |
408 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, | 390 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, |
409 "quic socket #%uL is retired", qsock->sid.seqnum); | 391 "quic socket #%uL is retired", qsock->sid.seqnum); |
410 | 392 |
411 /* check if client is willing to retire sid we have in use */ | |
412 if (qsock->sid.seqnum == qc->socket->sid.seqnum) { | |
413 tmp = &qc->socket; | |
414 | |
415 } else if (qc->backup && qsock->sid.seqnum == qc->backup->sid.seqnum) { | |
416 tmp = &qc->backup; | |
417 | |
418 } else { | |
419 | |
420 ngx_quic_close_socket(c, qsock); | |
421 | |
422 /* restore socket count up to a limit after deletion */ | |
423 if (ngx_quic_create_sockets(c) != NGX_OK) { | |
424 return NGX_ERROR; | |
425 } | |
426 | |
427 return NGX_OK; | |
428 } | |
429 | |
430 /* preserve path/cid from retired socket */ | |
431 path = qsock->path; | |
432 cid = qsock->cid; | |
433 | |
434 /* ensure that closing_socket will not drop path and cid */ | |
435 path->refcnt++; | |
436 cid->refcnt++; | |
437 | |
438 ngx_quic_close_socket(c, qsock); | 393 ngx_quic_close_socket(c, qsock); |
439 | |
440 /* restore original values */ | |
441 path->refcnt--; | |
442 cid->refcnt--; | |
443 | 394 |
444 /* restore socket count up to a limit after deletion */ | 395 /* restore socket count up to a limit after deletion */ |
445 if (ngx_quic_create_sockets(c) != NGX_OK) { | 396 if (ngx_quic_create_sockets(c) != NGX_OK) { |
446 goto failed; | 397 return NGX_ERROR; |
447 } | 398 } |
448 | 399 |
449 qsock = ngx_quic_get_unconnected_socket(c); | 400 return NGX_OK; |
450 if (qsock == NULL) { | |
451 qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; | |
452 qc->error_reason = "not enough server IDs"; | |
453 goto failed; | |
454 } | |
455 | |
456 ngx_quic_connect(c, qsock, path, cid); | |
457 | |
458 ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, | |
459 "quic %s socket is now #%uL:%uL:%uL (%s)", | |
460 (*tmp) == qc->socket ? "active" : "backup", | |
461 qsock->sid.seqnum, qsock->cid->seqnum, | |
462 qsock->path->seqnum, | |
463 ngx_quic_path_state_str(qsock->path)); | |
464 | |
465 /* restore active/backup pointer in quic connection */ | |
466 *tmp = qsock; | |
467 | |
468 return NGX_OK; | |
469 | |
470 failed: | |
471 | |
472 /* | |
473 * socket was closed, path and cid were preserved artifically | |
474 * to be reused, but it didn't happen, thus unref here | |
475 */ | |
476 | |
477 ngx_quic_unref_path(c, path); | |
478 ngx_quic_unref_client_id(c, cid); | |
479 | |
480 return NGX_ERROR; | |
481 } | 401 } |
482 | 402 |
483 | 403 |
484 ngx_int_t | 404 ngx_int_t |
485 ngx_quic_create_sockets(ngx_connection_t *c) | 405 ngx_quic_create_sockets(ngx_connection_t *c) |
550 | 470 |
551 return NGX_OK; | 471 return NGX_OK; |
552 } | 472 } |
553 | 473 |
554 | 474 |
555 static ngx_int_t | 475 ngx_int_t |
556 ngx_quic_replace_retired_client_id(ngx_connection_t *c, | 476 ngx_quic_free_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) |
557 ngx_quic_client_id_t *retired_cid) | 477 { |
558 { | 478 ngx_quic_frame_t *frame; |
559 ngx_queue_t *q; | 479 ngx_quic_connection_t *qc; |
560 ngx_quic_socket_t *qsock; | 480 |
561 ngx_quic_client_id_t *cid; | 481 qc = ngx_quic_get_connection(c); |
562 ngx_quic_connection_t *qc; | 482 |
563 | 483 frame = ngx_quic_alloc_frame(c); |
564 qc = ngx_quic_get_connection(c); | 484 if (frame == NULL) { |
565 | 485 return NGX_ERROR; |
566 for (q = ngx_queue_head(&qc->sockets); | 486 } |
567 q != ngx_queue_sentinel(&qc->sockets); | 487 |
568 q = ngx_queue_next(q)) | 488 frame->level = ssl_encryption_application; |
569 { | 489 frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; |
570 qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); | 490 frame->u.retire_cid.sequence_number = cid->seqnum; |
571 | 491 |
572 if (qsock->cid == retired_cid) { | 492 ngx_quic_queue_frame(qc, frame); |
573 | 493 |
574 cid = ngx_quic_next_client_id(c); | 494 /* we are no longer going to use this client id */ |
575 if (cid == NULL) { | |
576 return NGX_ERROR; | |
577 } | |
578 | |
579 qsock->cid = cid; | |
580 cid->refcnt++; | |
581 | |
582 ngx_quic_unref_client_id(c, retired_cid); | |
583 | |
584 if (retired_cid->refcnt == 0) { | |
585 return NGX_OK; | |
586 } | |
587 } | |
588 } | |
589 | |
590 return NGX_OK; | |
591 } | |
592 | |
593 | |
594 void | |
595 ngx_quic_unref_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) | |
596 { | |
597 ngx_quic_connection_t *qc; | |
598 | |
599 if (cid->refcnt) { | |
600 cid->refcnt--; | |
601 } /* else: unused client id */ | |
602 | |
603 if (cid->refcnt) { | |
604 return; | |
605 } | |
606 | |
607 qc = ngx_quic_get_connection(c); | |
608 | 495 |
609 ngx_queue_remove(&cid->queue); | 496 ngx_queue_remove(&cid->queue); |
610 ngx_queue_insert_head(&qc->free_client_ids, &cid->queue); | 497 ngx_queue_insert_head(&qc->free_client_ids, &cid->queue); |
611 | 498 |
612 qc->nclient_ids--; | 499 qc->nclient_ids--; |
613 } | 500 |
501 return NGX_OK; | |
502 } |