comparison src/event/quic/ngx_event_quic_migration.c @ 9147:58afcd72446f

QUIC: path MTU discovery. MTU selection starts by doubling the initial MTU until the first failure. Then binary search is used to find the path MTU.
author Roman Arutyunyan <arut@nginx.com>
date Mon, 14 Aug 2023 09:21:27 +0400
parents f3412ec3b6d1
children f6b6f3dd7ca0
comparison
equal deleted inserted replaced
9146:f3412ec3b6d1 9147:58afcd72446f
6 6
7 #include <ngx_config.h> 7 #include <ngx_config.h>
8 #include <ngx_core.h> 8 #include <ngx_core.h>
9 #include <ngx_event.h> 9 #include <ngx_event.h>
10 #include <ngx_event_quic_connection.h> 10 #include <ngx_event_quic_connection.h>
11
12
13 #define NGX_QUIC_PATH_MTU_DELAY 100
14 #define NGX_QUIC_PATH_MTU_PRECISION 16
11 15
12 16
13 static void ngx_quic_set_connection_path(ngx_connection_t *c, 17 static void ngx_quic_set_connection_path(ngx_connection_t *c,
14 ngx_quic_path_t *path); 18 ngx_quic_path_t *path);
15 static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c, 19 static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c,
16 ngx_quic_path_t *path); 20 ngx_quic_path_t *path);
17 static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, 21 static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c,
18 ngx_quic_path_t *path); 22 ngx_quic_path_t *path);
19 static void ngx_quic_set_path_timer(ngx_connection_t *c); 23 static void ngx_quic_set_path_timer(ngx_connection_t *c);
24 static ngx_int_t ngx_quic_expire_path_validation(ngx_connection_t *c,
25 ngx_quic_path_t *path);
26 static ngx_int_t ngx_quic_expire_path_mtu_delay(ngx_connection_t *c,
27 ngx_quic_path_t *path);
28 static ngx_int_t ngx_quic_expire_path_mtu_discovery(ngx_connection_t *c,
29 ngx_quic_path_t *path);
20 static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag); 30 static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag);
31 static ngx_int_t ngx_quic_send_path_mtu_probe(ngx_connection_t *c,
32 ngx_quic_path_t *path);
21 33
22 34
23 ngx_int_t 35 ngx_int_t
24 ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, 36 ngx_quic_handle_path_challenge_frame(ngx_connection_t *c,
25 ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) 37 ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f)
95 q != ngx_queue_sentinel(&qc->paths); 107 q != ngx_queue_sentinel(&qc->paths);
96 q = ngx_queue_next(q)) 108 q = ngx_queue_next(q))
97 { 109 {
98 path = ngx_queue_data(q, ngx_quic_path_t, queue); 110 path = ngx_queue_data(q, ngx_quic_path_t, queue);
99 111
100 if (!path->validating) { 112 if (path->state != NGX_QUIC_PATH_VALIDATING) {
101 continue; 113 continue;
102 } 114 }
103 115
104 if (ngx_memcmp(path->challenge1, f->data, sizeof(f->data)) == 0 116 if (ngx_memcmp(path->challenge1, f->data, sizeof(f->data)) == 0
105 || ngx_memcmp(path->challenge2, f->data, sizeof(f->data)) == 0) 117 || ngx_memcmp(path->challenge2, f->data, sizeof(f->data)) == 0)
134 path->sockaddr, path->socklen, 0) 146 path->sockaddr, path->socklen, 0)
135 == NGX_OK) 147 == NGX_OK)
136 { 148 {
137 /* address did not change */ 149 /* address did not change */
138 rst = 0; 150 rst = 0;
151
152 path->mtu = prev->mtu;
153 path->max_mtu = prev->max_mtu;
139 } 154 }
140 } 155 }
141 156
142 if (rst) { 157 if (rst) {
143 ngx_memzero(&qc->congestion, sizeof(ngx_quic_congestion_t)); 158 ngx_memzero(&qc->congestion, sizeof(ngx_quic_congestion_t));
165 path->seqnum, &path->addr_text); 180 path->seqnum, &path->addr_text);
166 181
167 ngx_quic_path_dbg(c, "is validated", path); 182 ngx_quic_path_dbg(c, "is validated", path);
168 183
169 path->validated = 1; 184 path->validated = 1;
170 path->validating = 0; 185
171 186 ngx_quic_discover_path_mtu(c, path);
172 ngx_quic_set_path_timer(c);
173 187
174 return NGX_OK; 188 return NGX_OK;
175 } 189 }
176 190
177 191
214 ngx_memcpy(path->sockaddr, sockaddr, socklen); 228 ngx_memcpy(path->sockaddr, sockaddr, socklen);
215 229
216 path->addr_text.data = path->text; 230 path->addr_text.data = path->text;
217 path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text, 231 path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text,
218 NGX_SOCKADDR_STRLEN, 1); 232 NGX_SOCKADDR_STRLEN, 1);
233
234 path->mtu = NGX_QUIC_MIN_INITIAL_SIZE;
219 235
220 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, 236 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
221 "quic path seq:%uL created addr:%V", 237 "quic path seq:%uL created addr:%V",
222 path->seqnum, &path->addr_text); 238 path->seqnum, &path->addr_text);
223 return path; 239 return path;
462 qc->path = next; 478 qc->path = next;
463 qc->path->tag = NGX_QUIC_PATH_ACTIVE; 479 qc->path->tag = NGX_QUIC_PATH_ACTIVE;
464 480
465 ngx_quic_set_connection_path(c, next); 481 ngx_quic_set_connection_path(c, next);
466 482
467 if (!next->validated && !next->validating) { 483 if (!next->validated && next->state != NGX_QUIC_PATH_VALIDATING) {
468 if (ngx_quic_validate_path(c, next) != NGX_OK) { 484 if (ngx_quic_validate_path(c, next) != NGX_OK) {
469 return NGX_ERROR; 485 return NGX_ERROR;
470 } 486 }
471 } 487 }
472 488
490 qc = ngx_quic_get_connection(c); 506 qc = ngx_quic_get_connection(c);
491 507
492 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, 508 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
493 "quic initiated validation of path seq:%uL", path->seqnum); 509 "quic initiated validation of path seq:%uL", path->seqnum);
494 510
495 path->validating = 1;
496 path->tries = 0; 511 path->tries = 0;
497 512
498 if (RAND_bytes(path->challenge1, 8) != 1) { 513 if (RAND_bytes(path->challenge1, 8) != 1) {
499 return NGX_ERROR; 514 return NGX_ERROR;
500 } 515 }
509 524
510 ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); 525 ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
511 pto = ngx_max(ngx_quic_pto(c, ctx), 1000); 526 pto = ngx_max(ngx_quic_pto(c, ctx), 1000);
512 527
513 path->expires = ngx_current_msec + pto; 528 path->expires = ngx_current_msec + pto;
529 path->state = NGX_QUIC_PATH_VALIDATING;
514 530
515 ngx_quic_set_path_timer(c); 531 ngx_quic_set_path_timer(c);
516 532
517 return NGX_OK; 533 return NGX_OK;
518 } 534 }
556 572
557 return NGX_OK; 573 return NGX_OK;
558 } 574 }
559 575
560 576
577 void
578 ngx_quic_discover_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path)
579 {
580 ngx_quic_connection_t *qc;
581
582 qc = ngx_quic_get_connection(c);
583
584 if (path->max_mtu) {
585 if (path->max_mtu - path->mtu <= NGX_QUIC_PATH_MTU_PRECISION) {
586 path->state = NGX_QUIC_PATH_IDLE;
587 ngx_quic_set_path_timer(c);
588 return;
589 }
590
591 path->mtud = (path->mtu + path->max_mtu) / 2;
592
593 } else {
594 path->mtud = path->mtu * 2;
595
596 if (path->mtud >= qc->ctp.max_udp_payload_size) {
597 path->mtud = qc->ctp.max_udp_payload_size;
598 path->max_mtu = qc->ctp.max_udp_payload_size;
599 }
600 }
601
602 path->state = NGX_QUIC_PATH_WAITING;
603 path->expires = ngx_current_msec + NGX_QUIC_PATH_MTU_DELAY;
604
605 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
606 "quic path seq:%uL schedule mtu:%uz",
607 path->seqnum, path->mtud);
608
609 ngx_quic_set_path_timer(c);
610 }
611
612
561 static void 613 static void
562 ngx_quic_set_path_timer(ngx_connection_t *c) 614 ngx_quic_set_path_timer(ngx_connection_t *c)
563 { 615 {
564 ngx_msec_t now; 616 ngx_msec_t now;
565 ngx_queue_t *q; 617 ngx_queue_t *q;
576 q != ngx_queue_sentinel(&qc->paths); 628 q != ngx_queue_sentinel(&qc->paths);
577 q = ngx_queue_next(q)) 629 q = ngx_queue_next(q))
578 { 630 {
579 path = ngx_queue_data(q, ngx_quic_path_t, queue); 631 path = ngx_queue_data(q, ngx_quic_path_t, queue);
580 632
581 if (!path->validating) { 633 if (path->state == NGX_QUIC_PATH_IDLE) {
582 continue; 634 continue;
583 } 635 }
584 636
585 left = path->expires - now; 637 left = path->expires - now;
586 left = ngx_max(left, 1); 638 left = ngx_max(left, 1);
598 } 650 }
599 } 651 }
600 652
601 653
602 void 654 void
603 ngx_quic_path_validation_handler(ngx_event_t *ev) 655 ngx_quic_path_handler(ngx_event_t *ev)
604 { 656 {
605 ngx_msec_t now; 657 ngx_msec_t now;
606 ngx_queue_t *q; 658 ngx_queue_t *q;
607 ngx_msec_int_t left, next, pto; 659 ngx_msec_int_t left;
608 ngx_quic_path_t *path, *bkp; 660 ngx_quic_path_t *path;
609 ngx_connection_t *c; 661 ngx_connection_t *c;
610 ngx_quic_send_ctx_t *ctx;
611 ngx_quic_connection_t *qc; 662 ngx_quic_connection_t *qc;
612 663
613 c = ev->data; 664 c = ev->data;
614 qc = ngx_quic_get_connection(c); 665 qc = ngx_quic_get_connection(c);
615 666
616 ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
617
618 next = -1;
619 now = ngx_current_msec; 667 now = ngx_current_msec;
620 668
621 q = ngx_queue_head(&qc->paths); 669 q = ngx_queue_head(&qc->paths);
622 670
623 while (q != ngx_queue_sentinel(&qc->paths)) { 671 while (q != ngx_queue_sentinel(&qc->paths)) {
624 672
625 path = ngx_queue_data(q, ngx_quic_path_t, queue); 673 path = ngx_queue_data(q, ngx_quic_path_t, queue);
626 q = ngx_queue_next(q); 674 q = ngx_queue_next(q);
627 675
628 if (!path->validating) { 676 if (path->state == NGX_QUIC_PATH_IDLE) {
629 continue; 677 continue;
630 } 678 }
631 679
632 left = path->expires - now; 680 left = path->expires - now;
633 681
634 if (left > 0) { 682 if (left > 0) {
635 683 continue;
636 if (next == -1 || left < next) { 684 }
637 next = left; 685
686 switch (path->state) {
687 case NGX_QUIC_PATH_VALIDATING:
688 if (ngx_quic_expire_path_validation(c, path) != NGX_OK) {
689 goto failed;
638 } 690 }
639 691
692 break;
693
694 case NGX_QUIC_PATH_WAITING:
695 if (ngx_quic_expire_path_mtu_delay(c, path) != NGX_OK) {
696 goto failed;
697 }
698
699 break;
700
701 case NGX_QUIC_PATH_MTUD:
702 if (ngx_quic_expire_path_mtu_discovery(c, path) != NGX_OK) {
703 goto failed;
704 }
705
706 break;
707
708 default:
709 break;
710 }
711 }
712
713 ngx_quic_set_path_timer(c);
714
715 return;
716
717 failed:
718
719 ngx_quic_close_connection(c, NGX_ERROR);
720 }
721
722
723 static ngx_int_t
724 ngx_quic_expire_path_validation(ngx_connection_t *c, ngx_quic_path_t *path)
725 {
726 ngx_msec_int_t pto;
727 ngx_quic_path_t *bkp;
728 ngx_quic_send_ctx_t *ctx;
729 ngx_quic_connection_t *qc;
730
731 qc = ngx_quic_get_connection(c);
732 ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
733
734 if (++path->tries < NGX_QUIC_PATH_RETRIES) {
735 pto = ngx_max(ngx_quic_pto(c, ctx), 1000) << path->tries;
736 path->expires = ngx_current_msec + pto;
737
738 (void) ngx_quic_send_path_challenge(c, path);
739
740 return NGX_OK;
741 }
742
743 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
744 "quic path seq:%uL validation failed", path->seqnum);
745
746 /* found expired path */
747
748 path->validated = 0;
749
750
751 /* RFC 9000, 9.3.2. On-Path Address Spoofing
752 *
753 * To protect the connection from failing due to such a spurious
754 * migration, an endpoint MUST revert to using the last validated
755 * peer address when validation of a new peer address fails.
756 */
757
758 if (qc->path == path) {
759 /* active path validation failed */
760
761 bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
762
763 if (bkp == NULL) {
764 qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH;
765 qc->error_reason = "no viable path";
766 return NGX_ERROR;
767 }
768
769 qc->path = bkp;
770 qc->path->tag = NGX_QUIC_PATH_ACTIVE;
771
772 ngx_quic_set_connection_path(c, qc->path);
773
774 ngx_log_error(NGX_LOG_INFO, c->log, 0,
775 "quic path seq:%uL addr:%V is restored from backup",
776 qc->path->seqnum, &qc->path->addr_text);
777
778 ngx_quic_path_dbg(c, "is active", qc->path);
779 }
780
781 return ngx_quic_free_path(c, path);
782 }
783
784
785 static ngx_int_t
786 ngx_quic_expire_path_mtu_delay(ngx_connection_t *c, ngx_quic_path_t *path)
787 {
788 ngx_int_t rc;
789 ngx_uint_t i;
790 ngx_msec_t pto;
791 ngx_quic_send_ctx_t *ctx;
792 ngx_quic_connection_t *qc;
793
794 qc = ngx_quic_get_connection(c);
795 ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
796
797 path->tries = 0;
798
799 for ( ;; ) {
800
801 for (i = 0; i < NGX_QUIC_PATH_RETRIES; i++) {
802 path->mtu_pnum[i] = NGX_QUIC_UNSET_PN;
803 }
804
805 rc = ngx_quic_send_path_mtu_probe(c, path);
806
807 if (rc == NGX_ERROR) {
808 return NGX_ERROR;
809 }
810
811 if (rc == NGX_OK) {
812 pto = ngx_quic_pto(c, ctx);
813 path->expires = ngx_current_msec + pto;
814 path->state = NGX_QUIC_PATH_MTUD;
815 return NGX_OK;
816 }
817
818 /* rc == NGX_DECLINED */
819
820 path->max_mtu = path->mtud;
821
822 if (path->max_mtu - path->mtu <= NGX_QUIC_PATH_MTU_PRECISION) {
823 path->state = NGX_QUIC_PATH_IDLE;
824 return NGX_OK;
825 }
826
827 path->mtud = (path->mtu + path->max_mtu) / 2;
828 }
829 }
830
831
832 static ngx_int_t
833 ngx_quic_expire_path_mtu_discovery(ngx_connection_t *c, ngx_quic_path_t *path)
834 {
835 ngx_int_t rc;
836 ngx_msec_int_t pto;
837 ngx_quic_send_ctx_t *ctx;
838 ngx_quic_connection_t *qc;
839
840 qc = ngx_quic_get_connection(c);
841 ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
842
843 if (++path->tries < NGX_QUIC_PATH_RETRIES) {
844 rc = ngx_quic_send_path_mtu_probe(c, path);
845
846 if (rc == NGX_ERROR) {
847 return NGX_ERROR;
848 }
849
850 if (rc == NGX_OK) {
851 pto = ngx_quic_pto(c, ctx) << path->tries;
852 path->expires = ngx_current_msec + pto;
853 return NGX_OK;
854 }
855
856 /* rc == NGX_DECLINED */
857 }
858
859 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
860 "quic path seq:%uL expired mtu:%uz",
861 path->seqnum, path->mtud);
862
863 path->max_mtu = path->mtud;
864
865 ngx_quic_discover_path_mtu(c, path);
866
867 return NGX_OK;
868 }
869
870
871 static ngx_int_t
872 ngx_quic_send_path_mtu_probe(ngx_connection_t *c, ngx_quic_path_t *path)
873 {
874 ngx_int_t rc;
875 ngx_uint_t log_error;
876 ngx_quic_frame_t frame;
877 ngx_quic_send_ctx_t *ctx;
878 ngx_quic_connection_t *qc;
879
880 ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
881
882 frame.level = ssl_encryption_application;
883 frame.type = NGX_QUIC_FT_PING;
884
885 qc = ngx_quic_get_connection(c);
886 ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
887 path->mtu_pnum[path->tries] = ctx->pnum;
888
889 ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
890 "quic path seq:%uL send probe "
891 "mtu:%uz pnum:%uL tries:%ui",
892 path->seqnum, path->mtud, ctx->pnum, path->tries);
893
894 log_error = c->log_error;
895 c->log_error = NGX_ERROR_IGNORE_EMSGSIZE;
896
897 rc = ngx_quic_frame_sendto(c, &frame, path->mtud, path);
898 c->log_error = log_error;
899
900 if (rc == NGX_ERROR) {
901 if (c->write->error) {
902 c->write->error = 0;
903
904 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
905 "quic path seq:%uL rejected mtu:%uz",
906 path->seqnum, path->mtud);
907
908 return NGX_DECLINED;
909 }
910
911 return NGX_ERROR;
912 }
913
914 return NGX_OK;
915 }
916
917
918 ngx_int_t
919 ngx_quic_handle_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path,
920 uint64_t min, uint64_t max)
921 {
922 uint64_t pnum;
923 ngx_uint_t i;
924
925 if (path->state != NGX_QUIC_PATH_MTUD) {
926 return NGX_OK;
927 }
928
929 for (i = 0; i < NGX_QUIC_PATH_RETRIES; i++) {
930 pnum = path->mtu_pnum[i];
931
932 if (pnum == NGX_QUIC_UNSET_PN) {
933 break;
934 }
935
936 if (pnum < min || pnum > max) {
640 continue; 937 continue;
641 } 938 }
642 939
643 if (++path->tries < NGX_QUIC_PATH_RETRIES) { 940 path->mtu = path->mtud;
644 pto = ngx_max(ngx_quic_pto(c, ctx), 1000) << path->tries; 941
645 942 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
646 path->expires = ngx_current_msec + pto; 943 "quic path seq:%uL ack mtu:%uz",
647 944 path->seqnum, path->mtu);
648 if (next == -1 || pto < next) { 945
649 next = pto; 946 ngx_quic_discover_path_mtu(c, path);
650 } 947
651 948 break;
652 /* retransmit */ 949 }
653 (void) ngx_quic_send_path_challenge(c, path); 950
654 951 return NGX_OK;
655 continue; 952 }
656 }
657
658 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
659 "quic path seq:%uL validation failed", path->seqnum);
660
661 /* found expired path */
662
663 path->validated = 0;
664 path->validating = 0;
665
666
667 /* RFC 9000, 9.3.2. On-Path Address Spoofing
668 *
669 * To protect the connection from failing due to such a spurious
670 * migration, an endpoint MUST revert to using the last validated
671 * peer address when validation of a new peer address fails.
672 */
673
674 if (qc->path == path) {
675 /* active path validation failed */
676
677 bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
678
679 if (bkp == NULL) {
680 qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH;
681 qc->error_reason = "no viable path";
682 ngx_quic_close_connection(c, NGX_ERROR);
683 return;
684 }
685
686 qc->path = bkp;
687 qc->path->tag = NGX_QUIC_PATH_ACTIVE;
688
689 ngx_quic_set_connection_path(c, qc->path);
690
691 ngx_log_error(NGX_LOG_INFO, c->log, 0,
692 "quic path seq:%uL addr:%V is restored from backup",
693 qc->path->seqnum, &qc->path->addr_text);
694
695 ngx_quic_path_dbg(c, "is active", qc->path);
696 }
697
698 if (ngx_quic_free_path(c, path) != NGX_OK) {
699 ngx_quic_close_connection(c, NGX_ERROR);
700 return;
701 }
702 }
703
704 if (next != -1) {
705 ngx_add_timer(&qc->path_validation, next);
706 }
707 }