comparison src/event/quic/ngx_event_quic_migration.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 b7284807b4fa
children 077a1e403446
comparison
equal deleted inserted replaced
8970:7106a918a277 8971: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 }