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 }